Add a "Between" type search/filter in filter form mode

When Between searches are selected, two search widget wrappers
will be created to allow entry of the minimum/maximum allowed
values.
This commit is contained in:
Nyall Dawson 2016-05-23 19:25:12 +10:00
parent 026e352cd9
commit 02c0174d41
10 changed files with 211 additions and 82 deletions

View File

@ -17,23 +17,23 @@ class QgsSearchWidgetToolButton : QToolButton
*/
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()
* @see setDefaultFlags()
*/
void setAvailableFlags( QgsSearchWidgetWrapper::FilterFlags flags );
/** Sets the default filter flags to show in the widget.
* @param flags default flags to show in widget
* @see setAvailableFlags()
* @see setActiveFlags()
*/
void setDefaultFlags( QgsSearchWidgetWrapper::FilterFlags flags );
/** Returns the available filter flags shown in the widget.
* @see setAvailableFlags()
* @see activeFlags()
@ -87,4 +87,11 @@ class QgsSearchWidgetToolButton : QToolButton
*/
void setActive();
signals:
/** Emitted when the active flags selected in the widget is changed
* @param flags active flags
*/
void activeFlagsChanged( QgsSearchWidgetWrapper::FilterFlags flags );
};

View File

@ -30,19 +30,16 @@ class QgsAttributeFormEditorWidget : QWidget
QgsAttributeForm* form /TransferThis/ );
~QgsAttributeFormEditorWidget();
/** Sets the search widget wrapper for the widget used when the form is in
/** Creates the search widget wrappers for the widget used when the form is in
* search mode.
* @param wrapper search widget wrapper.
* @note the search widget wrapper should be created using searchWidgetFrame()
* as its parent
* @param widgetId id of the widget type to create a search wrapper for
* @param fieldIdx index of field associated with widget
* @param config configuration which should be used for the widget creation
* @param context editor context (not available in python bindings)
*/
void setSearchWidgetWrapper( QgsSearchWidgetWrapper* wrapper );
/** Returns the widget which should be used as a parent during construction
* of the search widget wrapper.
* @see setSearchWidgetWrapper()
*/
QWidget* searchWidgetFrame();
void createSearchWidgetWrappers( const QString& widgetId, int fieldIdx,
const QgsEditorWidgetConfig& config,
const QgsAttributeEditorContext &context = QgsAttributeEditorContext() );
/** Sets the current mode for the widget. The widget will adapt its state and visible widgets to
* reflect the updated mode. Eg, showing multi edit tool buttons if the mode is set to MultiEditMode.
@ -106,4 +103,28 @@ class QgsAttributeFormEditorWidget : QWidget
*/
QgsSearchWidgetToolButton* searchWidgetToolButton();
/** Sets the search widget wrapper for the widget used when the form is in
* search mode.
* @param wrapper search widget wrapper.
* @note the search widget wrapper should be created using searchWidgetFrame()
* as its parent
* @note this method is in place for unit testing only, and is not considered
* stable AP
*/
void setSearchWidgetWrapper( QgsSearchWidgetWrapper* wrapper );
/** Returns the widget which should be used as a parent during construction
* of the search widget wrapper.
* @note this method is in place for unit testing only, and is not considered
* stable AP
*/
QWidget* searchWidgetFrame();
/** Returns the search widget wrapper used in this widget. The wrapper must
* first be created using createSearchWidgetWrapper()
* @note this method is in place for unit testing only, and is not considered
* stable AP
*/
QList< QgsSearchWidgetWrapper* > searchWidgetWrappers();
};

View File

@ -51,7 +51,7 @@ QVariant QgsDateTimeSearchWidgetWrapper::value() const
QgsSearchWidgetWrapper::FilterFlags QgsDateTimeSearchWidgetWrapper::supportedFlags() const
{
return EqualTo | NotEqualTo | GreaterThan | LessThan | GreaterThanOrEqualTo | LessThanOrEqualTo | IsNull;
return EqualTo | NotEqualTo | GreaterThan | LessThan | GreaterThanOrEqualTo | LessThanOrEqualTo | IsNull | Between;
}
QgsSearchWidgetWrapper::FilterFlags QgsDateTimeSearchWidgetWrapper::defaultFlags() const

View File

@ -20,8 +20,8 @@
QgsSearchWidgetToolButton::QgsSearchWidgetToolButton( QWidget* parent )
: QToolButton( parent )
, mAvailableFilterFlags( QgsSearchWidgetWrapper::EqualTo | QgsSearchWidgetWrapper::NotEqualTo | QgsSearchWidgetWrapper::CaseInsensitive )
, mDefaultFilterFlags( QgsSearchWidgetWrapper::EqualTo )
, mFilterFlags( QgsSearchWidgetWrapper::EqualTo )
, mSearchWrapper( nullptr )
, mMenu( nullptr )
{
setFocusPolicy( Qt::StrongFocus );
@ -35,22 +35,19 @@ QgsSearchWidgetToolButton::QgsSearchWidgetToolButton( QWidget* parent )
updateState();
}
void QgsSearchWidgetToolButton::setSearchWidgetWrapper( QgsSearchWidgetWrapper* wrapper )
{
mSearchWrapper = wrapper;
setAvailableFlags( mSearchWrapper->supportedFlags() );
setActiveFlags( QgsSearchWidgetWrapper::FilterFlags() );
connect( mSearchWrapper, SIGNAL( valueChanged() ), this, SLOT( searchWidgetValueChanged() ) );
connect( mSearchWrapper, SIGNAL( valueCleared() ), this, SLOT( setInactive() ) );
}
void QgsSearchWidgetToolButton::setAvailableFlags( QgsSearchWidgetWrapper::FilterFlags flags )
{
mFilterFlags &= flags;
mAvailableFilterFlags = flags;
mDefaultFilterFlags = mDefaultFilterFlags & flags;
updateState();
}
void QgsSearchWidgetToolButton::setDefaultFlags( QgsSearchWidgetWrapper::FilterFlags flags )
{
mDefaultFilterFlags = flags & mAvailableFilterFlags;
}
void QgsSearchWidgetToolButton::setActiveFlags( QgsSearchWidgetWrapper::FilterFlags flags )
{
// sanitize list
@ -194,9 +191,6 @@ void QgsSearchWidgetToolButton::setInactive()
if ( !isActive() )
return;
if ( mSearchWrapper )
mSearchWrapper->clearWidget();
QgsSearchWidgetWrapper::FilterFlags newFlags;
Q_FOREACH ( QgsSearchWidgetWrapper::FilterFlag flag, QgsSearchWidgetWrapper::nonExclusiveFilterFlags() )
{
@ -215,12 +209,7 @@ void QgsSearchWidgetToolButton::setActive()
Q_FOREACH ( QgsSearchWidgetWrapper::FilterFlag flag, QgsSearchWidgetWrapper::exclusiveFilterFlags() )
{
if ( mSearchWrapper && mSearchWrapper->defaultFlags() & flag )
{
toggleFlag( flag );
return;
}
else if ( !mSearchWrapper && mAvailableFilterFlags & flag )
if ( mDefaultFilterFlags & flag )
{
toggleFlag( flag );
return;
@ -230,9 +219,6 @@ void QgsSearchWidgetToolButton::setActive()
void QgsSearchWidgetToolButton::updateState()
{
if ( mSearchWrapper )
mSearchWrapper->setEnabled( !( mFilterFlags & QgsSearchWidgetWrapper::IsNull ) );
bool active = false;
QStringList toolTips;
Q_FOREACH ( QgsSearchWidgetWrapper::FilterFlag flag, QgsSearchWidgetWrapper::exclusiveFilterFlags() )
@ -262,4 +248,6 @@ void QgsSearchWidgetToolButton::updateState()
setText( tr( "Exclude field" ) );
setToolTip( QString() );
}
emit activeFlagsChanged( mFilterFlags );
}

View File

@ -37,23 +37,23 @@ class GUI_EXPORT QgsSearchWidgetToolButton : public QToolButton
*/
explicit QgsSearchWidgetToolButton( QWidget *parent = 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()
* @see setDefaultFlags()
*/
void setAvailableFlags( QgsSearchWidgetWrapper::FilterFlags flags );
/** Sets the default filter flags to show in the widget.
* @param flags default flags to show in widget
* @see setAvailableFlags()
* @see setActiveFlags()
*/
void setDefaultFlags( QgsSearchWidgetWrapper::FilterFlags flags );
/** Returns the available filter flags shown in the widget.
* @see setAvailableFlags()
* @see activeFlags()
@ -107,6 +107,13 @@ class GUI_EXPORT QgsSearchWidgetToolButton : public QToolButton
*/
void setActive();
signals:
/** Emitted when the active flags selected in the widget is changed
* @param flags active flags
*/
void activeFlagsChanged( QgsSearchWidgetWrapper::FilterFlags flags );
private slots:
void aboutToShowMenu();
@ -118,8 +125,8 @@ class GUI_EXPORT QgsSearchWidgetToolButton : public QToolButton
private:
QgsSearchWidgetWrapper::FilterFlags mAvailableFilterFlags;
QgsSearchWidgetWrapper::FilterFlags mDefaultFilterFlags;
QgsSearchWidgetWrapper::FilterFlags mFilterFlags;
QgsSearchWidgetWrapper* mSearchWrapper;
QMenu* mMenu;
void updateState();

View File

@ -903,9 +903,7 @@ void QgsAttributeForm::init()
QgsAttributeFormEditorWidget* formWidget = new QgsAttributeFormEditorWidget( eww, this );
w = formWidget;
mFormEditorWidgets.insert( idx, formWidget );
QgsSearchWidgetWrapper* sww = QgsEditorWidgetRegistry::instance()->createSearchWidget( widgetType, mLayer, idx, widgetConfig,
formWidget->searchWidgetFrame(), mContext );
formWidget->setSearchWidgetWrapper( sww );
formWidget->createSearchWidgetWrappers( widgetType, idx, widgetConfig, mContext );
}
else
{
@ -1162,8 +1160,7 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt
QgsAttributeFormEditorWidget* w = new QgsAttributeFormEditorWidget( eww, this );
mFormEditorWidgets.insert( fldIdx, w );
QgsSearchWidgetWrapper* sww = QgsEditorWidgetRegistry::instance()->createSearchWidget( widgetType, mLayer, fldIdx, widgetConfig, w->searchWidgetFrame(), mContext );
w->setSearchWidgetWrapper( sww );
w->createSearchWidgetWrappers( widgetType, fldIdx, widgetConfig, mContext );
newWidgetInfo.widget = w;
addWidgetWrapper( eww );

View File

@ -19,6 +19,9 @@
#include "qgssearchwidgettoolbutton.h"
#include "qgseditorwidgetwrapper.h"
#include "qgssearchwidgetwrapper.h"
#include "qgseditorwidgetconfig.h"
#include "qgsattributeeditorcontext.h"
#include "qgseditorwidgetregistry.h"
#include <QLayout>
#include <QLabel>
#include <QStackedWidget>
@ -27,7 +30,6 @@ QgsAttributeFormEditorWidget::QgsAttributeFormEditorWidget( QgsEditorWidgetWrapp
QgsAttributeForm* form )
: QWidget( form )
, mWidget( editorWidget )
, mSearchWidget( nullptr )
, mForm( form )
, mMode( DefaultMode )
, mMultiEditButton( new QgsMultiEditToolButton() )
@ -54,6 +56,8 @@ QgsAttributeFormEditorWidget::QgsAttributeFormEditorWidget( QgsEditorWidgetWrapp
mSearchPage->setLayout( l );
l->addWidget( mSearchFrame, 1 );
mSearchWidgetToolButton = new QgsSearchWidgetToolButton();
connect( mSearchWidgetToolButton, SIGNAL( activeFlagsChanged( QgsSearchWidgetWrapper::FilterFlags ) ),
this, SLOT( searchWidgetFlagsChanged( QgsSearchWidgetWrapper::FilterFlags ) ) );
l->addWidget( mSearchWidgetToolButton, 0 );
@ -91,11 +95,42 @@ QgsAttributeFormEditorWidget::~QgsAttributeFormEditorWidget()
delete mMultiEditButton;
}
void QgsAttributeFormEditorWidget::createSearchWidgetWrappers( const QString& widgetId, int fieldIdx, const QgsEditorWidgetConfig& config, const QgsAttributeEditorContext& context )
{
QgsSearchWidgetWrapper* sww = QgsEditorWidgetRegistry::instance()->createSearchWidget( widgetId, layer(), fieldIdx, config,
mSearchFrame, context );
setSearchWidgetWrapper( sww );
if ( sww->supportedFlags() & QgsSearchWidgetWrapper::Between )
{
// create secondary widget for between type searches
QgsSearchWidgetWrapper* sww2 = QgsEditorWidgetRegistry::instance()->createSearchWidget( widgetId, layer(), fieldIdx, config,
mSearchFrame, context );
mSearchWidgets << sww2;
mSearchFrame->layout()->addWidget( sww2->widget() );
sww2->widget()->hide();
}
}
void QgsAttributeFormEditorWidget::setSearchWidgetWrapper( QgsSearchWidgetWrapper* wrapper )
{
mSearchWidget = wrapper;
mSearchWidgets.clear();
mSearchWidgets << wrapper;
mSearchFrame->layout()->addWidget( wrapper->widget() );
mSearchWidgetToolButton->setSearchWidgetWrapper( wrapper );
mSearchWidgetToolButton->setAvailableFlags( wrapper->supportedFlags() );
mSearchWidgetToolButton->setActiveFlags( QgsSearchWidgetWrapper::FilterFlags() );
mSearchWidgetToolButton->setDefaultFlags( wrapper->defaultFlags() );
connect( wrapper, SIGNAL( valueChanged() ), mSearchWidgetToolButton, SLOT( searchWidgetValueChanged() ) );
connect( wrapper, SIGNAL( valueCleared() ), mSearchWidgetToolButton, SLOT( setInactive() ) );
}
QWidget*QgsAttributeFormEditorWidget::searchWidgetFrame()
{
return mSearchFrame;
}
QList< QgsSearchWidgetWrapper* > QgsAttributeFormEditorWidget::searchWidgetWrappers()
{
return mSearchWidgets;
}
void QgsAttributeFormEditorWidget::setMode( QgsAttributeFormEditorWidget::Mode mode )
@ -125,8 +160,10 @@ void QgsAttributeFormEditorWidget::changesCommitted()
void QgsAttributeFormEditorWidget::resetSearch()
{
mSearchWidgetToolButton->setInactive();
if ( mSearchWidget )
mSearchWidget->clearWidget();
Q_FOREACH ( QgsSearchWidgetWrapper* widget, mSearchWidgets )
{
widget->clearWidget();
}
}
void QgsAttributeFormEditorWidget::initialize( const QVariant& initialValue, bool mixedValues )
@ -150,15 +187,21 @@ QVariant QgsAttributeFormEditorWidget::currentValue() const
QString QgsAttributeFormEditorWidget::currentFilterExpression() const
{
if ( mSearchWidgets.isEmpty() )
return QString();
if ( !mSearchWidgetToolButton->isActive() )
return QString();
return mSearchWidget->createExpression( mSearchWidgetToolButton->activeFlags() );
}
if ( mSearchWidgetToolButton->activeFlags() & QgsSearchWidgetWrapper::Between )
{
// special case: Between search
QString filter1 = mSearchWidgets.at( 0 )->createExpression( QgsSearchWidgetWrapper::GreaterThanOrEqualTo );
QString filter2 = mSearchWidgets.at( 1 )->createExpression( QgsSearchWidgetWrapper::LessThanOrEqualTo );
return QString( "%1 AND %2" ).arg( filter1, filter2 );
}
QWidget* QgsAttributeFormEditorWidget::searchWidgetFrame()
{
return mSearchFrame;
return mSearchWidgets.at( 0 )->createExpression( mSearchWidgetToolButton->activeFlags() );
}
void QgsAttributeFormEditorWidget::editorWidgetChanged( const QVariant& value )
@ -208,6 +251,23 @@ void QgsAttributeFormEditorWidget::setFieldTriggered()
mIsChanged = true;
}
void QgsAttributeFormEditorWidget::searchWidgetFlagsChanged( QgsSearchWidgetWrapper::FilterFlags flags )
{
Q_FOREACH ( QgsSearchWidgetWrapper* widget, mSearchWidgets )
{
widget->setEnabled( !( flags & QgsSearchWidgetWrapper::IsNull ) );
if ( !mSearchWidgetToolButton->isActive() )
{
widget->clearWidget();
}
}
if ( mSearchWidgets.count() >= 2 )
{
mSearchWidgets.at( 1 )->widget()->setVisible( flags & QgsSearchWidgetWrapper::Between );
}
}
QgsSearchWidgetToolButton* QgsAttributeFormEditorWidget::searchWidgetToolButton()
{
return mSearchWidgetToolButton;

View File

@ -18,14 +18,17 @@
#include <QWidget>
#include <QVariant>
#include "qgseditorwidgetconfig.h"
#include "qgsattributeeditorcontext.h"
#include "qgssearchwidgetwrapper.h"
class QgsAttributeForm;
class QgsEditorWidgetWrapper;
class QgsMultiEditToolButton;
class QgsSearchWidgetToolButton;
class QgsVectorLayer;
class QgsSearchWidgetWrapper;
class QStackedWidget;
class QgsAttributeEditorContext;
/** \ingroup gui
* \class QgsAttributeFormEditorWidget
@ -58,19 +61,16 @@ class GUI_EXPORT QgsAttributeFormEditorWidget : public QWidget
~QgsAttributeFormEditorWidget();
/** Sets the search widget wrapper for the widget used when the form is in
/** Creates the search widget wrappers for the widget used when the form is in
* search mode.
* @param wrapper search widget wrapper.
* @note the search widget wrapper should be created using searchWidgetFrame()
* as its parent
* @param widgetId id of the widget type to create a search wrapper for
* @param fieldIdx index of field associated with widget
* @param config configuration which should be used for the widget creation
* @param context editor context (not available in python bindings)
*/
void setSearchWidgetWrapper( QgsSearchWidgetWrapper* wrapper );
/** Returns the widget which should be used as a parent during construction
* of the search widget wrapper.
* @see setSearchWidgetWrapper()
*/
QWidget* searchWidgetFrame();
void createSearchWidgetWrappers( const QString& widgetId, int fieldIdx,
const QgsEditorWidgetConfig& config,
const QgsAttributeEditorContext &context = QgsAttributeEditorContext() );
/** Sets the current mode for the widget. The widget will adapt its state and visible widgets to
* reflect the updated mode. Eg, showing multi edit tool buttons if the mode is set to MultiEditMode.
@ -137,6 +137,9 @@ class GUI_EXPORT QgsAttributeFormEditorWidget : public QWidget
//! Triggered when the multi edit tool button "set field value" action is selected
void setFieldTriggered();
//! Triggered when search button flags are changed
void searchWidgetFlagsChanged( QgsSearchWidgetWrapper::FilterFlags flags );
protected:
/** Returns a pointer to the search widget tool button in the widget.
@ -145,6 +148,30 @@ class GUI_EXPORT QgsAttributeFormEditorWidget : public QWidget
*/
QgsSearchWidgetToolButton* searchWidgetToolButton();
/** Sets the search widget wrapper for the widget used when the form is in
* search mode.
* @param wrapper search widget wrapper.
* @note the search widget wrapper should be created using searchWidgetFrame()
* as its parent
* @note this method is in place for unit testing only, and is not considered
* stable AP
*/
void setSearchWidgetWrapper( QgsSearchWidgetWrapper* wrapper );
/** Returns the widget which should be used as a parent during construction
* of the search widget wrapper.
* @note this method is in place for unit testing only, and is not considered
* stable AP
*/
QWidget* searchWidgetFrame();
/** Returns the search widget wrapper used in this widget. The wrapper must
* first be created using createSearchWidgetWrapper()
* @note this method is in place for unit testing only, and is not considered
* stable AP
*/
QList< QgsSearchWidgetWrapper* > searchWidgetWrappers();
private:
QWidget* mEditPage;
@ -153,7 +180,7 @@ class GUI_EXPORT QgsAttributeFormEditorWidget : public QWidget
QWidget* mSearchFrame;
QgsEditorWidgetWrapper* mWidget;
QgsSearchWidgetWrapper* mSearchWidget;
QList< QgsSearchWidgetWrapper* > mSearchWidgets;
QgsAttributeForm* mForm;
Mode mMode;

View File

@ -17,12 +17,17 @@ import qgis # NOQA
from qgis.gui import (QgsSearchWidgetWrapper,
QgsAttributeFormEditorWidget,
QgsSearchWidgetToolButton,
QgsDefaultSearchWidgetWrapper)
QgsDefaultSearchWidgetWrapper,
QgsAttributeForm,
QgsEditorWidgetRegistry
)
from qgis.core import (QgsVectorLayer)
from qgis.PyQt.QtWidgets import QWidget
from qgis.PyQt.QtWidgets import QWidget, QDateTimeEdit
from qgis.PyQt.QtCore import QDateTime, QDate, QTime
from qgis.testing import start_app, unittest
start_app()
QgsEditorWidgetRegistry.instance().initEditors()
class PyQgsAttributeFormEditorWidget(unittest.TestCase):
@ -70,5 +75,21 @@ class PyQgsAttributeFormEditorWidget(unittest.TestCase):
# check that correct default flag was taken from search widget wrapper
self.assertTrue(sb.activeFlags() & QgsSearchWidgetWrapper.EqualTo)
def testBetweenFilter(self):
""" Test creating a between type filter """
layer = QgsVectorLayer("Point?field=fldtext:string&field=fldint:integer", "test", "memory")
form = QgsAttributeForm(layer)
af = QgsAttributeFormEditorWidget(None, form)
af.createSearchWidgetWrappers("DateTime", 0, {})
d1 = af.findChildren(QDateTimeEdit)[0]
d2 = af.findChildren(QDateTimeEdit)[1]
d1.setDateTime(QDateTime(QDate(2013, 5, 6), QTime()))
d2.setDateTime(QDateTime(QDate(2013, 5, 16), QTime()))
af.searchWidgetToolButton().setActiveFlags(QgsSearchWidgetWrapper.Between)
self.assertEquals(af.currentFilterExpression(), '"fldtext">=\'2013-05-06\' AND "fldtext"<=\'2013-05-16\'')
if __name__ == '__main__':
unittest.main()

View File

@ -146,12 +146,13 @@ class TestQgsSearchWidgetToolButton(unittest.TestCase):
self.assertFalse(w.isActive())
def testSetActive(self):
""" Test setting the search as active """
""" Test setting the search as active should adopt default flags"""
w = QgsSearchWidgetToolButton()
w.setAvailableFlags(QgsSearchWidgetWrapper.Between |
QgsSearchWidgetWrapper.NotEqualTo |
QgsSearchWidgetWrapper.CaseInsensitive)
w.setActiveFlags(QgsSearchWidgetWrapper.CaseInsensitive)
w.setDefaultFlags(QgsSearchWidgetWrapper.NotEqualTo)
self.assertFalse(w.isActive())
w.setActive()
flags = w.activeFlags()