From bbe2e33ea4f1f22c85d133bdc8af20ea986eea2f Mon Sep 17 00:00:00 2001 From: mgrojo Date: Sun, 8 Jul 2018 14:29:52 +0200 Subject: [PATCH] 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. --- src/ExtendedTableWidget.cpp | 63 ++++++++++++++++++++++--- src/ExtendedTableWidget.h | 4 +- src/FilterLineEdit.cpp | 94 +++++++++++++++++++++++++++++++++++++ src/FilterLineEdit.h | 4 ++ 4 files changed, 156 insertions(+), 9 deletions(-) diff --git a/src/ExtendedTableWidget.cpp b/src/ExtendedTableWidget.cpp index c2ae1c93..2321cd87 100644 --- a/src/ExtendedTableWidget.cpp +++ b/src/ExtendedTableWidget.cpp @@ -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(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) diff --git a/src/ExtendedTableWidget.h b/src/ExtendedTableWidget.h index 572a18f8..7f3b3c69 100644 --- a/src/ExtendedTableWidget.h +++ b/src/ExtendedTableWidget.h @@ -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 QByteArrayList; static QList m_buffer; diff --git a/src/FilterLineEdit.cpp b/src/FilterLineEdit.cpp index 7c9d0a99..18def580 100644 --- a/src/FilterLineEdit.cpp +++ b/src/FilterLineEdit.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include FilterLineEdit::FilterLineEdit(QWidget* parent, QList* filters, int columnnum) : QLineEdit(parent), filterList(filters), columnNumber(columnnum) { @@ -35,6 +37,10 @@ FilterLineEdit::FilterLineEdit(QWidget* parent, QList* 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)); +} diff --git a/src/FilterLineEdit.h b/src/FilterLineEdit.h index fb803990..3cd58314 100644 --- a/src/FilterLineEdit.h +++ b/src/FilterLineEdit.h @@ -26,12 +26,16 @@ signals: protected: void keyReleaseEvent(QKeyEvent* event); + void setFilterHelper(const QString& filterOperator); private: QList* filterList; int columnNumber; QTimer* delaySignalTimer; QString lastValue; + +private slots: + void showContextMenu(const QPoint &pos); }; #endif