Skip to content

Commit

Permalink
[FEATURE] Form based select/filter in attribute table
Browse files Browse the repository at this point in the history
This commit adds a new mode to the attribute table dialog for searching
and filtering features. When activated (using a button on the toolbar
or by pressng CTRL+F), the dialog will switch to form view and all
widgets are replaced with their search widget wrapper variant.

Alongside each widget is a tool button with options for controlling
the search/filter behaviour for that field, eg "equal to", "not equal
to", "is null", "greater than", etc.., with the options presented
matching themselves to the corresponding field and widget type.

New buttons appear at the bottom of the form for either selecting
matching features (with options for add to selection/remove from
selection/select within current selection) or filtering features
in the table (with options for adding features to a current filter
or further restricting a current filter).

Sponsored by SIGE
  • Loading branch information
nyalldawson committed May 23, 2016
1 parent 3fcd1fd commit ca5c7e2
Show file tree
Hide file tree
Showing 34 changed files with 2,312 additions and 53 deletions.
19 changes: 19 additions & 0 deletions python/gui/attributetable/qgsdualview.sip
Expand Up @@ -183,6 +183,12 @@ class QgsDualView : QStackedWidget
*/
void setMultiEditEnabled( bool enabled );

/** Toggles whether search mode should be enabled in the form.
* @param enabled set to true to switch on search mode
* @note added in QGIS 2.16
*/
void toggleSearchMode( bool enabled );

signals:
/**
* Is emitted, whenever the display expression is successfully changed
Expand All @@ -194,6 +200,19 @@ class QgsDualView : QStackedWidget
* Is emitted, whenever the filter changes
*/
void filterChanged();

/** Is emitted when a filter expression is set using the view.
* @param expression filter expression
* @param type filter type
* @note added in QGIS 2.16
*/
void filterExpressionSet( const QString& expression, QgsAttributeForm::FilterType type );

/** Emitted when the form changes mode.
* @param mode new mode
*/
void formModeChanged( QgsAttributeForm::Mode mode );

};

class QgsAttributeTableAction : QAction
Expand Down
131 changes: 131 additions & 0 deletions python/gui/editorwidgets/core/qgssearchwidgetwrapper.sip
@@ -1,3 +1,50 @@
%MappedType QList<QgsSearchWidgetWrapper::FilterFlag>
{
%TypeHeaderCode
#include <QList>
%End

%ConvertFromTypeCode
// Create the list.
PyObject *l;

if ((l = PyList_New(sipCpp->size())) == NULL)
return NULL;

// Set the list elements.
QList<QgsSearchWidgetWrapper::FilterFlag>::iterator it = sipCpp->begin();
for (int i = 0; it != sipCpp->end(); ++it, ++i)
{
PyObject *tobj;

if ((tobj = sipConvertFromEnum(*it, sipType_QgsSearchWidgetWrapper_FilterFlag)) == NULL)
{
Py_DECREF(l);
return NULL;
}
PyList_SET_ITEM(l, i, tobj);
}

return l;
%End

%ConvertToTypeCode
// Check the type if that is all that is required.
if (sipIsErr == NULL)
return PyList_Check(sipPy);

QList<QgsSearchWidgetWrapper::FilterFlag> *qlist = new QList<QgsSearchWidgetWrapper::FilterFlag>;

for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
{
*qlist << (QgsSearchWidgetWrapper::FilterFlag)SIPLong_AsLong(PyList_GET_ITEM(sipPy, i));
}

*sipCppPtr = qlist;
return sipGetState(sipTransferObj);
%End
};

/**
* Manages an editor widget
* Widget and wrapper share the same parent
Expand All @@ -14,6 +61,43 @@ class QgsSearchWidgetWrapper : QgsWidgetWrapper
#include <qgssearchwidgetwrapper.h>
%End
public:

//! Flags which indicate what types of filtering and searching is possible using the widget
//! @note added in QGIS 2.16
enum FilterFlag
{
EqualTo, /*!< Supports equal to */
NotEqualTo, /*!< Supports not equal to */
GreaterThan, /*!< Supports greater than */
LessThan, /*!< Supports less than */
GreaterThanOrEqualTo, /*!< Supports >= */
LessThanOrEqualTo, /*!< Supports <= */
Between, /*!< Supports searches between two values */
CaseInsensitive, /*!< Supports case insensitive searching */
Contains, /*!< Supports value "contains" searching */
DoesNotContain, /*!< Supports value does not contain searching */
IsNull, /*!< Supports searching for null values */
};
typedef QFlags<QgsSearchWidgetWrapper::FilterFlag> FilterFlags;

/** Returns a list of exclusive filter flags, which cannot be combined with other flags (eg EqualTo/NotEqualTo)
* @note added in QGIS 2.16
* @see nonExclusiveFilterFlags()
*/
static QList< QgsSearchWidgetWrapper::FilterFlag > exclusiveFilterFlags();

/** Returns a list of non-exclusive filter flags, which can be combined with other flags (eg CaseInsensitive)
* @note added in QGIS 2.16
* @see exclusiveFilterFlags()
*/
static QList< QgsSearchWidgetWrapper::FilterFlag > nonExclusiveFilterFlags();

/** Returns a translated string representing a filter flag.
* @param flag flag to convert to string
* @note added in QGIS 2.16
*/
static QString toString( FilterFlag flag );

/**
* Create a new widget wrapper
*
Expand All @@ -23,6 +107,18 @@ class QgsSearchWidgetWrapper : QgsWidgetWrapper
*/
explicit QgsSearchWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* parent /TransferThis/ = nullptr );

/** Returns filter flags supported by the search widget.
* @note added in QGIS 2.16
* @see defaultFlags()
*/
virtual FilterFlags supportedFlags() const;

/** Returns the filter flags which should be set by default for the search widget.
* @note added in QGIS 2.16
* @see supportedFlags()
*/
virtual FilterFlags defaultFlags() const;

/**
* Will be used to access the widget's value. Read the value from the widget and
* return it properly formatted to be saved in the attribute.
Expand All @@ -39,6 +135,28 @@ class QgsSearchWidgetWrapper : QgsWidgetWrapper
*/
virtual bool applyDirectly() = 0;

/** Creates a filter expression based on the current state of the search widget
* and the specified filter flags.
* @param flags filter flags
* @returns filter expression
* @note added in QGIS 2.16
*/
// TODO QGIS 3.0 - make pure virtual
virtual QString createExpression( FilterFlags flags ) const;

public slots:

/** Clears the widget's current value and resets it back to the default state
* @note added in QGIS 2.16
*/
virtual void clearWidget();

/** Toggles whether the search widget is enabled or disabled.
* @param enabled set to true to enable widget
* @note added in QGIS 2.16
*/
virtual void setEnabled( bool enabled );

signals:

/**
Expand All @@ -47,6 +165,17 @@ class QgsSearchWidgetWrapper : QgsWidgetWrapper
*/
void expressionChanged( const QString& exp );

/** Emitted when a user changes the value of the search widget.
* @note added in QGIS 2.16
*/
void valueChanged();

/** Emitted when a user changes the value of the search widget back
* to an empty, default state.
* @note added in QGIS 2.16
*/
void valueCleared();

protected slots:

virtual void setExpression( QString value ) = 0;
Expand All @@ -57,3 +186,5 @@ class QgsSearchWidgetWrapper : QgsWidgetWrapper
void clearExpression();

};

QFlags<QgsSearchWidgetWrapper::FilterFlag> operator|(QgsSearchWidgetWrapper::FilterFlag f1, QFlags<QgsSearchWidgetWrapper::FilterFlag> f2);
46 changes: 46 additions & 0 deletions python/gui/editorwidgets/qgsdefaultsearchwidgetwrapper.sip
@@ -0,0 +1,46 @@
/**
* Wraps a search widget. Default form is just a QgsLineFilterEdit
*/
class QgsDefaultSearchWidgetWrapper : QgsSearchWidgetWrapper
{
%TypeHeaderCode
#include <qgsdefaultsearchwidgetwrapper.h>
%End
public:

explicit QgsDefaultSearchWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* parent /TransferThis/ = nullptr );

// QgsSearchWidgetWrapper interface
public:
QString expression();
bool applyDirectly();
FilterFlags supportedFlags() const;
FilterFlags defaultFlags() const;
virtual QString createExpression( FilterFlags flags ) const;

public slots:

virtual void clearWidget();
virtual void setEnabled( bool enabled );

protected slots:
void setExpression( QString exp );

protected:
QWidget* createWidget( QWidget* parent );
void initWidget( QWidget* editor );
bool valid() const;

/** Returns a pointer to the line edit part of the widget.
* @note this method is in place for unit testing only, and is not considered
* stable API
*/
QgsFilterLineEdit* lineEdit();

/** Returns a pointer to the case sensitivity check box in the widget.
* @note this method is in place for unit testing only, and is not considered
* stable API
*/
QCheckBox* caseSensitiveCheckBox();

};
90 changes: 90 additions & 0 deletions python/gui/editorwidgets/qgssearchwidgettoolbutton.sip
@@ -0,0 +1,90 @@
/** \ingroup gui
* \class QgsSearchWidgetToolButton
* A tool button widget which is displayed next to search widgets in forms, and
* allows for controlling how the widget behaves and how the filtering/searching
* operates.
* \note Added in version 2.16
*/
class QgsSearchWidgetToolButton : QToolButton
{
%TypeHeaderCode
#include <qgssearchwidgettoolbutton.h>
%End
public:

/** Constructor for QgsSearchWidgetToolButton.
* @param parent parent object
*/
explicit QgsSearchWidgetToolButton( QWidget *parent /TransferThis/ = nullptr );

/** Sets the search widget wrapper associated with this button.
* Calling this will automatically set the available flags to match those
* supported by the wrapper and reset the active flags to match the wrapper's
* default flags.
* @param wrapper search wrapper. Ownership is not transferred.
*/
void setSearchWidgetWrapper( QgsSearchWidgetWrapper* wrapper );

/** Sets the available filter flags to show in the widget. Any active flags
* (see activeFlags()) which are not present in the new available filter
* flags will be cleared;
* @param flags available flags to show in widget
* @see availableFlags()
* @see setActiveFlags()
*/
void setAvailableFlags( QgsSearchWidgetWrapper::FilterFlags flags );

/** Returns the available filter flags shown in the widget.
* @see setAvailableFlags()
* @see activeFlags()
*/
QgsSearchWidgetWrapper::FilterFlags availableFlags() const;

/** Sets the current active filter flags for the widget. Any flags
* which are not present in the available filter flags (see availableFlags())
* will not be set.
* @param flags active flags to show in widget
* @see toggleFlag()
* @see activeFlags()
* @see setAvailableFlags()
*/
void setActiveFlags( QgsSearchWidgetWrapper::FilterFlags flags );

/** Toggles an individual active filter flag for the widget. Any flags
* which are not present in the available filter flags (see availableFlags())
* will be ignore. Other flags may be cleared if they conflict with the newly
* toggled flag.
* @param flag flag to toggle
* @see setActiveFlags()
* @see activeFlags()
*/
void toggleFlag( QgsSearchWidgetWrapper::FilterFlag flag );

/** Returns the active filter flags shown in the widget.
* @see setActiveFlags()
* @see toggleFlag()
* @see availableFlags()
*/
QgsSearchWidgetWrapper::FilterFlags activeFlags() const;

/** Returns true if the widget is set to be included in the search.
* @see setInactive()
* @see setActive()
*/
bool isActive() const;

public slots:

/** Sets the search widget as inactive, ie do not search the corresponding field.
* @see isActive()
* @see setActive()
*/
void setInactive();

/** Sets the search widget as active by selecting the first available search type.
* @see isActive()
* @see setInactive()
*/
void setActive();

};
34 changes: 34 additions & 0 deletions python/gui/editorwidgets/qgsvaluemapsearchwidgetwrapper.sip
@@ -0,0 +1,34 @@
/**
* Wraps a value map search widget. This widget will offer a combobox with values from another layer
* referenced by a foreign key (a constraint may be set but is not required on data level).
* It will be used as a search widget and produces expression to look for in the layer.
*/

class QgsValueMapSearchWidgetWrapper : QgsSearchWidgetWrapper
{
%TypeHeaderCode
#include <qgsvaluemapsearchwidgetwrapper.h>
%End
public:

explicit QgsValueMapSearchWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* parent /TransferThis/ = nullptr );
bool applyDirectly();
QString expression();
bool valid() const;
FilterFlags supportedFlags() const;
FilterFlags defaultFlags() const;
virtual QString createExpression( FilterFlags flags ) const;

public slots:

virtual void clearWidget();
virtual void setEnabled( bool enabled );

protected:
QWidget* createWidget( QWidget* parent );
void initWidget( QWidget* editor );

protected slots:
void setExpression( QString exp );

};

0 comments on commit ca5c7e2

Please sign in to comment.