Regexp filters

This adds support for the REGEXP operator using the syntax /regexp/ in the
filter line boxes.

A new helper option for a not-containing filter is added using this new
syntax. This could be simplified using a NOT LIKE filter, but we don't
currently support that.

The "Use in Filter Expression > Containing" option escapes a possible
slash in the beginning, so it is not interpreted as a regexp filter.

See issue #1522
This commit is contained in:
mgrojo
2018-11-04 19:58:12 +01:00
committed by Manuel
parent a393bbf25d
commit c923cc29b5
5 changed files with 37 additions and 11 deletions

View File

@@ -72,6 +72,10 @@ QString CondFormat::filterToSqlCondition(const QString& value, const QString& en
op = "IS";
numeric = true;
}
} else if(value.left(1) == "/" && value.right(1) == "/" && value.length() > 2) {
val = value.mid(1, value.length() - 2);
op = "REGEXP";
numeric = false;
} else {
// Keep the default LIKE operator

View File

@@ -194,12 +194,14 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
QAction* filterAction = new QAction(tr("Use as Exact Filter"), m_contextMenu);
QAction* containingAction = new QAction(tr("Containing"), m_contextMenu);
QAction* notContainingAction = new QAction(tr("Not containing"), m_contextMenu);
QAction* notEqualToAction = new QAction(tr("Not equal to"), m_contextMenu);
QAction* greaterThanAction = new QAction(tr("Greater than"), m_contextMenu);
QAction* lessThanAction = new QAction(tr("Less than"), m_contextMenu);
QAction* greaterEqualAction = new QAction(tr("Greater or equal"), m_contextMenu);
QAction* lessEqualAction = new QAction(tr("Less or equal"), m_contextMenu);
QAction* inRangeAction = new QAction(tr("Between this and..."), m_contextMenu);
QAction* regexpAction = new QAction(tr("Regular expression"), m_contextMenu);
QAction* nullAction = new QAction(tr("Set to NULL"), m_contextMenu);
QAction* copyAction = new QAction(QIcon(":/icons/copy"), tr("Copy"), m_contextMenu);
@@ -211,12 +213,14 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
m_contextMenu->addAction(filterAction);
QMenu* filterMenu = m_contextMenu->addMenu(tr("Use in Filter Expression"));
filterMenu->addAction(containingAction);
filterMenu->addAction(notContainingAction);
filterMenu->addAction(notEqualToAction);
filterMenu->addAction(greaterThanAction);
filterMenu->addAction(lessThanAction);
filterMenu->addAction(greaterEqualAction);
filterMenu->addAction(lessEqualAction);
filterMenu->addAction(inRangeAction);
filterMenu->addAction(regexpAction);
m_contextMenu->addSeparator();
m_contextMenu->addAction(nullAction);
@@ -274,6 +278,10 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
connect(containingAction, &QAction::triggered, [&]() {
useAsFilter(QString (""));
});
// Use a regular expression for the not containing filter. Simplify this if we ever support the NOT LIKE filter.
connect(notContainingAction, &QAction::triggered, [&]() {
useAsFilter(QString ("/^((?!"), /* binary */ false, QString (").)*$/"));
});
connect(notEqualToAction, &QAction::triggered, [&]() {
useAsFilter(QString ("<>"));
});
@@ -292,6 +300,9 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
connect(inRangeAction, &QAction::triggered, [&]() {
useAsFilter(QString ("~"), /* binary */ true);
});
connect(regexpAction, &QAction::triggered, [&]() {
useAsFilter(QString ("/"), /* binary */ false, QString ("/"));
});
connect(nullAction, &QAction::triggered, [&]() {
for(const QModelIndex& index : selectedIndexes())
@@ -611,7 +622,7 @@ void ExtendedTableWidget::paste()
}
}
void ExtendedTableWidget::useAsFilter(const QString& filterOperator, bool binary)
void ExtendedTableWidget::useAsFilter(const QString& filterOperator, bool binary, const QString& operatorSuffix)
{
QModelIndex index = selectionModel()->currentIndex();
SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());
@@ -632,14 +643,14 @@ void ExtendedTableWidget::useAsFilter(const QString& filterOperator, bool binary
// When Containing filter is requested (empty operator) and the value starts with
// an operator character, the character is escaped.
if (filterOperator.isEmpty())
value.replace(QRegExp("^(<|>|=)"), Settings::getValue("databrowser", "filter_escape").toString() + QString("\\1"));
value.replace(QRegExp("^(<|>|=|/)"), Settings::getValue("databrowser", "filter_escape").toString() + QString("\\1"));
// If binary operator, the cell data is used as first value and
// the second value must be added by the user.
if (binary)
m_tableHeader->setFilter(index.column(), value + filterOperator);
else
m_tableHeader->setFilter(index.column(), filterOperator + value);
m_tableHeader->setFilter(index.column(), filterOperator + value + operatorSuffix);
}
void ExtendedTableWidget::duplicateUpperCell()

View File

@@ -69,7 +69,7 @@ private:
void copy(const bool withHeaders, const bool inSQL);
void paste();
void useAsFilter(const QString& filterOperator, bool binary = false);
void useAsFilter(const QString& filterOperator, bool binary = false, const QString& operatorSuffix = "");
void duplicateUpperCell();
typedef QList<QByteArray> QByteArrayList;

View File

@@ -32,7 +32,8 @@ FilterLineEdit::FilterLineEdit(QWidget* parent, QList<FilterLineEdit*>* filters,
"<=\tEqual to or less\n"
"=\tEqual to: exact match\n"
"<>\tUnequal: exact inverse match\n"
"x~y\tRange: values between x and y"));
"x~y\tRange: values between x and y\n"
"/regexp/\tValues matching the regular expression"));
// Immediately emit the delayed filter value changed signal if the user presses the enter or the return key or
// the line edit widget loses focus
@@ -94,11 +95,11 @@ void FilterLineEdit::setText(const QString& text)
delayedSignalTimerTriggered();
}
void FilterLineEdit::setFilterHelper(const QString& filterOperator)
void FilterLineEdit::setFilterHelper(const QString& filterOperator, const QString& operatorSuffix)
{
setText(filterOperator + "?");
setText(filterOperator + "?" + operatorSuffix);
// Select the value for easy editing of the expression
setSelection(filterOperator.length(), filterOperator.length());
setSelection(filterOperator.length(), 1);
}
void FilterLineEdit::showContextMenu(const QPoint &pos)
@@ -143,7 +144,11 @@ void FilterLineEdit::showContextMenu(const QPoint &pos)
connect(isNotEmptyAction, &QAction::triggered, [&]() {
setText("<>''");
});
// Simplify this if we ever support a NOT LIKE filter
QAction* notContainingAction = new QAction(tr("Not containing..."), editContextMenu);
connect(notContainingAction, &QAction::triggered, [&]() {
setFilterHelper(QString ("/^((?!"), QString(").)*$/"));
});
QAction* equalToAction = new QAction(tr("Equal to..."), editContextMenu);
connect(equalToAction, &QAction::triggered, [&]() {
setFilterHelper(QString ("="));
@@ -173,6 +178,11 @@ void FilterLineEdit::showContextMenu(const QPoint &pos)
setFilterHelper(QString ("?~"));
});
QAction* regexpAction = new QAction(tr("Regular expression..."), editContextMenu);
connect(regexpAction, &QAction::triggered, [&]() {
setFilterHelper(QString ("/"), QString ("/"));
});
editContextMenu->addAction(conditionalFormatAction);
filterMenu->addAction(whatsThisAction);
@@ -182,6 +192,7 @@ void FilterLineEdit::showContextMenu(const QPoint &pos)
filterMenu->addAction(isEmptyAction);
filterMenu->addAction(isNotEmptyAction);
filterMenu->addSeparator();
filterMenu->addAction(notContainingAction);
filterMenu->addAction(equalToAction);
filterMenu->addAction(notEqualToAction);
filterMenu->addAction(greaterThanAction);
@@ -189,6 +200,6 @@ void FilterLineEdit::showContextMenu(const QPoint &pos)
filterMenu->addAction(greaterEqualAction);
filterMenu->addAction(lessEqualAction);
filterMenu->addAction(inRangeAction);
filterMenu->addAction(regexpAction);
editContextMenu->exec(mapToGlobal(pos));
}

View File

@@ -28,7 +28,7 @@ signals:
protected:
void keyReleaseEvent(QKeyEvent* event) override;
void setFilterHelper(const QString& filterOperator);
void setFilterHelper(const QString& filterOperator, const QString& operatorSuffix = "");
private:
QList<FilterLineEdit*>* filterList;