Issue #1463: give assistance to users in using the filters

Added contextual menus in filter line box and cells for assisting in using
filters and for discovering existing filters.

The menu in the filter line adds "What's This" option and helper options
that add the operator and a selected placeholder (?) for easy editing.
Special options for NULL and empty strings are also added. Those work
directly.

The menu in the cells works as the current "Use as Filter" but for
different operators. In this case, only the range operator is incomplete.

Currently, not all the operators make sense for strings because we take
them as LIKE filters. At least the Not Equal filter (<>) should work, but
the other would also make sense. This will be addressed in future commits.
This commit is contained in:
mgrojo
2018-07-08 14:29:52 +02:00
parent e0fcbf93ba
commit bbe2e33ea4
4 changed files with 156 additions and 9 deletions

View File

@@ -154,13 +154,32 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
// Set up table view context menu
m_contextMenu = new QMenu(this);
QAction* filterAction = new QAction(tr("Use as Exact Filter"), m_contextMenu);
QAction* containingAction = new QAction(tr("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* nullAction = new QAction(tr("Set to NULL"), m_contextMenu);
QAction* copyAction = new QAction(QIcon(":/icons/copy"), tr("Copy"), m_contextMenu);
QAction* copyWithHeadersAction = new QAction(QIcon(":/icons/special_copy"), tr("Copy with Headers"), m_contextMenu);
QAction* copyAsSQLAction = new QAction(QIcon(":/icons/sql_copy"), tr("Copy as SQL"), m_contextMenu);
QAction* pasteAction = new QAction(QIcon(":/icons/paste"), tr("Paste"), m_contextMenu);
QAction* filterAction = new QAction(tr("Use as Filter"), m_contextMenu);
m_contextMenu->addAction(filterAction);
QMenu* filterMenu = m_contextMenu->addMenu(tr("Use in Filter Expression"));
filterMenu->addAction(containingAction);
filterMenu->addAction(notEqualToAction);
filterMenu->addAction(greaterThanAction);
filterMenu->addAction(lessThanAction);
filterMenu->addAction(greaterEqualAction);
filterMenu->addAction(lessEqualAction);
filterMenu->addAction(inRangeAction);
m_contextMenu->addSeparator();
m_contextMenu->addAction(nullAction);
m_contextMenu->addSeparator();
@@ -189,6 +208,7 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
// Deactivate context menu options if there is no model set
bool enabled = model();
filterAction->setEnabled(enabled);
filterMenu->setEnabled(enabled);
copyAction->setEnabled(enabled);
copyWithHeadersAction->setEnabled(enabled);
copyAsSQLAction->setEnabled(enabled);
@@ -202,8 +222,30 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
m_contextMenu->popup(viewport()->mapToGlobal(pos));
});
connect(filterAction, &QAction::triggered, [&]() {
useAsFilter();
useAsFilter(QString ("="));
});
connect(containingAction, &QAction::triggered, [&]() {
useAsFilter(QString (""));
});
connect(notEqualToAction, &QAction::triggered, [&]() {
useAsFilter(QString ("<>"));
});
connect(greaterThanAction, &QAction::triggered, [&]() {
useAsFilter(QString (">"));
});
connect(lessThanAction, &QAction::triggered, [&]() {
useAsFilter(QString ("<"));
});
connect(greaterEqualAction, &QAction::triggered, [&]() {
useAsFilter(QString (">="));
});
connect(lessEqualAction, &QAction::triggered, [&]() {
useAsFilter(QString ("<="));
});
connect(inRangeAction, &QAction::triggered, [&]() {
useAsFilter(QString ("~"), /* binary */ true);
});
connect(nullAction, &QAction::triggered, [&]() {
for(const QModelIndex& index : selectedIndexes())
model()->setData(index, QVariant());
@@ -547,7 +589,7 @@ void ExtendedTableWidget::paste()
}
}
void ExtendedTableWidget::useAsFilter()
void ExtendedTableWidget::useAsFilter(const QString& filterOperator, bool binary)
{
QModelIndex index = selectionModel()->currentIndex();
SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());
@@ -557,13 +599,20 @@ void ExtendedTableWidget::useAsFilter()
return;
QVariant data = model()->data(index, Qt::EditRole);
QString value;
if (data.isNull())
m_tableHeader->setFilter(index.column(), "=NULL");
value = "NULL";
else if (data.toString().isEmpty())
m_tableHeader->setFilter(index.column(), "=''");
value = "''";
else
m_tableHeader->setFilter(index.column(), "=" + data.toString());
value = data.toString();
// 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);
}
void ExtendedTableWidget::keyPressEvent(QKeyEvent* event)

View File

@@ -50,11 +50,11 @@ signals:
void selectedRowsToBeDeleted();
private:
void copy(const bool withHeaders, const bool inSQL );
void copy(const bool withHeaders, const bool inSQL);
void paste();
QString escapeCopiedData(const QByteArray& data) const;
void useAsFilter();
void useAsFilter(const QString& filterOperator, bool binary = false);
typedef QList<QByteArray> QByteArrayList;
static QList<QByteArrayList> m_buffer;

View File

@@ -3,6 +3,8 @@
#include <QTimer>
#include <QKeyEvent>
#include <QMenu>
#include <QWhatsThis>
FilterLineEdit::FilterLineEdit(QWidget* parent, QList<FilterLineEdit*>* filters, int columnnum) : QLineEdit(parent), filterList(filters), columnNumber(columnnum)
{
@@ -35,6 +37,10 @@ FilterLineEdit::FilterLineEdit(QWidget* parent, QList<FilterLineEdit*>* filters,
// 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
connect(this, SIGNAL(editingFinished()), this, SLOT(delayedSignalTimerTriggered()));
// Prepare for adding the What's This information and filter helper actions to the context menu
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showContextMenu(const QPoint &)));
}
void FilterLineEdit::delayedSignalTimerTriggered()
@@ -87,3 +93,91 @@ void FilterLineEdit::setText(const QString& text)
QLineEdit::setText(text);
delayedSignalTimerTriggered();
}
void FilterLineEdit::setFilterHelper(const QString& filterOperator)
{
setText(filterOperator + "?");
// Select the value for easy editing of the expression
setSelection(filterOperator.length(), filterOperator.length());
}
void FilterLineEdit::showContextMenu(const QPoint &pos)
{
// This has to be created here, otherwise the set of enabled options would not update accordingly.
QMenu* editContextMenu = createStandardContextMenu();
editContextMenu->addSeparator();
QMenu* filterMenu = editContextMenu->addMenu(tr("Set Filter Expression"));
QAction* whatsThisAction = new QAction(QIcon(":/icons/whatis"), tr("What's This?"), editContextMenu);
connect(whatsThisAction, &QAction::triggered, [&]() {
QWhatsThis::showText(pos, whatsThis(), this);
});
QAction* isNullAction = new QAction(tr("Is NULL"), editContextMenu);
connect(isNullAction, &QAction::triggered, [&]() {
setText("=NULL");
});
QAction* isNotNullAction = new QAction(tr("Is not NULL"), editContextMenu);
connect(isNotNullAction, &QAction::triggered, [&]() {
setText("<>NULL");
});
QAction* isEmptyAction = new QAction(tr("Is empty"), editContextMenu);
connect(isEmptyAction, &QAction::triggered, [&]() {
setText("=''");
});
QAction* isNotEmptyAction = new QAction(tr("Is not empty"), editContextMenu);
connect(isNotEmptyAction, &QAction::triggered, [&]() {
setText("<>''");
});
QAction* equalToAction = new QAction(tr("Equal to..."), editContextMenu);
connect(equalToAction, &QAction::triggered, [&]() {
setFilterHelper(QString ("="));
});
QAction* notEqualToAction = new QAction(tr("Not equal to..."), editContextMenu);
connect(notEqualToAction, &QAction::triggered, [&]() {
setFilterHelper(QString ("<>"));
});
QAction* greaterThanAction = new QAction(tr("Greater than..."), editContextMenu);
connect(greaterThanAction, &QAction::triggered, [&]() {
setFilterHelper(QString (">"));
});
QAction* lessThanAction = new QAction(tr("Less than..."), editContextMenu);
connect(lessThanAction, &QAction::triggered, [&]() {
setFilterHelper(QString ("<"));
});
QAction* greaterEqualAction = new QAction(tr("Greater or equal..."), editContextMenu);
connect(greaterEqualAction, &QAction::triggered, [&]() {
setFilterHelper(QString (">="));
});
QAction* lessEqualAction = new QAction(tr("Less or equal..."), editContextMenu);
connect(lessEqualAction, &QAction::triggered, [&]() {
setFilterHelper(QString ("<="));
});
QAction* inRangeAction = new QAction(tr("In range..."), editContextMenu);
connect(inRangeAction, &QAction::triggered, [&]() {
setFilterHelper(QString ("?~"));
});
filterMenu->addAction(whatsThisAction);
filterMenu->addSeparator();
filterMenu->addAction(isNullAction);
filterMenu->addAction(isNotNullAction);
filterMenu->addAction(isEmptyAction);
filterMenu->addAction(isNotEmptyAction);
filterMenu->addSeparator();
filterMenu->addAction(equalToAction);
filterMenu->addAction(notEqualToAction);
filterMenu->addAction(greaterThanAction);
filterMenu->addAction(lessThanAction);
filterMenu->addAction(greaterEqualAction);
filterMenu->addAction(lessEqualAction);
filterMenu->addAction(inRangeAction);
editContextMenu->exec(mapToGlobal(pos));
}

View File

@@ -26,12 +26,16 @@ signals:
protected:
void keyReleaseEvent(QKeyEvent* event);
void setFilterHelper(const QString& filterOperator);
private:
QList<FilterLineEdit*>* filterList;
int columnNumber;
QTimer* delaySignalTimer;
QString lastValue;
private slots:
void showContextMenu(const QPoint &pos);
};
#endif