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.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#search_view {
|
||||
#search_bar {
|
||||
background: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
/*
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
/* */
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "qt/element/QtSearchBar.h"
|
||||
|
||||
#include <QCompleter>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
|
||||
#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<SearchIndex::SearchMatch>& autocompletionList)
|
||||
{
|
||||
m_searchBox->setAutocompletionList(autocompletionList);
|
||||
}
|
||||
|
||||
QAbstractItemView* QtSearchBar::getCompleterPopup()
|
||||
{
|
||||
if (m_searchBox->completer())
|
||||
{
|
||||
return m_searchBox->completer()->popup();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -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<SearchIndex::SearchMatch> m_matches;
|
||||
};
|
||||
|
||||
#endif // QT_SEARCH_BOX_H
|
||||
@@ -1,147 +0,0 @@
|
||||
#include "qt/element/QtSearchBox.h"
|
||||
|
||||
#include <QCompleter>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
|
||||
#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<SearchIndex::SearchMatch>& 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<std::string> 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<std::string> 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;
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
#include "qt/element/QtSmartSearchBox.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QCompleter>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#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<SearchIndex::SearchMatch>& 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<int>(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<int>(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<int>(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<std::string> 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<int>(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<QtQueryElement> element = std::make_shared<QtQueryElement>(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<int>(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<QtQueryElement> 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<QtQueryElement> 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#ifndef QT_SMART_SEARCH_BOX_H
|
||||
#define QT_SMART_SEARCH_BOX_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
|
||||
#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<SearchIndex::SearchMatch>& 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<std::string> m_tokens;
|
||||
std::vector<std::shared_ptr<QtQueryElement>> m_elements;
|
||||
|
||||
int m_cursorIndex;
|
||||
|
||||
std::vector<SearchIndex::SearchMatch> m_matches;
|
||||
|
||||
bool m_shiftKeyDown;
|
||||
bool m_mousePressed;
|
||||
int m_mouseX;
|
||||
};
|
||||
|
||||
#endif // QT_SMART_SEARCH_BOX_H
|
||||
@@ -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<QtSearchBox>();
|
||||
m_widget = std::make_shared<QtSearchBar>();
|
||||
setStyleSheet();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <vector>
|
||||
|
||||
#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<const std::vector<SearchIndex::SearchMatch>&> m_setAutocompletionListFunctor;
|
||||
|
||||
std::shared_ptr<QtSearchBox> m_widget;
|
||||
std::shared_ptr<QtSearchBar> m_widget;
|
||||
};
|
||||
|
||||
# endif // QT_SEARCH_VIEW_H
|
||||
|
||||
@@ -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<Id> ids = m_graphAccess->getTokenIdsForQuery(query);
|
||||
if (ids.size())
|
||||
{
|
||||
|
||||
@@ -36,6 +36,8 @@ private:
|
||||
SearchView* getView();
|
||||
|
||||
GraphAccess* m_graphAccess;
|
||||
|
||||
bool m_ignoreNextMessageActivateToken;
|
||||
};
|
||||
|
||||
#endif // SEARCH_CONTROLLER_H
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -48,10 +48,12 @@ std::map<std::string, QueryCommand::CommandType> 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<std::string, CommandType> commandMap = getCommandTypeMap();
|
||||
std::map<std::string, CommandType>::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 = '\'';
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define QUERY_COMMAND_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "data/query/QueryNode.h"
|
||||
|
||||
@@ -46,7 +45,7 @@ public:
|
||||
|
||||
static std::map<std::string, CommandType> 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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define QUERY_NODE_H
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "data/query/QueryOperator.h"
|
||||
|
||||
#include "data/query/QueryCommand.h"
|
||||
#include "data/query/QueryToken.h"
|
||||
|
||||
const std::map<char, QueryOperator::OperatorType>& QueryOperator::getOperatorTypeMap()
|
||||
@@ -15,11 +16,12 @@ const std::map<char, QueryOperator::OperatorType>& 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;
|
||||
}
|
||||
|
||||
@@ -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<QueryNode> m_left;
|
||||
std::shared_ptr<QueryNode> m_right;
|
||||
|
||||
const OperatorType m_type;
|
||||
bool m_isImplicit;
|
||||
};
|
||||
|
||||
#endif // QUERY_OPERATOR_H
|
||||
|
||||
@@ -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<std::string> 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;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define QUERY_TOKEN_H
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -16,7 +16,10 @@ std::deque<std::string> 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<std::string> tokens;
|
||||
@@ -28,12 +31,19 @@ std::deque<std::string> 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<std::string> 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<std::string> 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<std::string> 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<QueryNode> QueryTree::buildTree(std::deque<std::string>& tokens,
|
||||
{
|
||||
if (node->isComplete())
|
||||
{
|
||||
std::shared_ptr<QueryOperator> subNode = std::make_shared<QueryOperator>(QueryOperator::OPERATOR_SUB);
|
||||
std::shared_ptr<QueryOperator> subNode = std::make_shared<QueryOperator>(QueryOperator::OPERATOR_SUB, true);
|
||||
subNode->setLeft(frontNode);
|
||||
subNode->setRight(node);
|
||||
node = subNode;
|
||||
@@ -207,11 +260,14 @@ std::shared_ptr<QueryNode> QueryTree::getNextNode(std::deque<std::string>& token
|
||||
case QueryOperator::OPERATOR_HAS:
|
||||
case QueryOperator::OPERATOR_AND:
|
||||
case QueryOperator::OPERATOR_OR:
|
||||
return std::make_shared<QueryOperator>(type);
|
||||
return std::make_shared<QueryOperator>(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<QueryNode> QueryTree::getNextNode(std::deque<std::string>& token
|
||||
break;
|
||||
|
||||
case QueryOperator::OPERATOR_NONE:
|
||||
return createCommand(token);
|
||||
return createToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,12 +284,16 @@ std::shared_ptr<QueryNode> QueryTree::getNextNode(std::deque<std::string>& token
|
||||
|
||||
std::shared_ptr<QueryNode> QueryTree::createCommand(const std::string& name)
|
||||
{
|
||||
if (name.size() < 3 || name.front() != QueryCommand::BOUNDARY || name.back() != QueryCommand::BOUNDARY)
|
||||
{
|
||||
m_valid = false;
|
||||
}
|
||||
|
||||
std::shared_ptr<QueryCommand> node = std::make_shared<QueryCommand>(name);
|
||||
|
||||
if (node->getType() == QueryCommand::COMMAND_INVALID)
|
||||
{
|
||||
m_valid = false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -244,10 +304,9 @@ std::shared_ptr<QueryNode> 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<QueryToken>(name.substr(1, name.size() - 2));
|
||||
return std::make_shared<QueryToken>(name);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostream, const QueryTree& tree)
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
|
||||
#include "data/query/QueryOperator.h"
|
||||
|
||||
class QueryNode;
|
||||
|
||||
class QueryTree
|
||||
{
|
||||
public:
|
||||
static std::deque<std::string> 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:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "utility/utilityString.h"
|
||||
|
||||
#include <strings.h>
|
||||
|
||||
namespace utility
|
||||
{
|
||||
std::deque<std::string> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user