[FEATURE] Add QgsFeatureListComboBox with live-filter-capabilities

This commit is contained in:
Matthias Kuhn 2017-10-25 01:14:44 +02:00
parent d40447e7d0
commit 7811f38e48
No known key found for this signature in database
GPG Key ID: A0E766808764D73F
12 changed files with 1281 additions and 0 deletions

View File

@ -311,6 +311,7 @@
%Include qgsfieldmodel.sip
%Include qgsfieldproxymodel.sip
%Include qgsfiledownloader.sip
%Include qgsfeaturefiltermodel.sip
%Include qgsgeometryvalidator.sip
%Include qgsgml.sip
%Include qgsgmlschema.sip

View File

@ -0,0 +1,118 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsfeaturefiltermodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsFeatureFilterModel : QAbstractItemModel
{
%Docstring
Provides a list of features based on filter conditions.
Features are fetched asynchronously.
%End
%TypeHeaderCode
#include "qgsfeaturefiltermodel.h"
%End
public:
enum Role
{
IdentifierValueRole,
ValueRole
};
QgsFeatureFilterModel( QObject *parent = 0 );
~QgsFeatureFilterModel();
QgsVectorLayer *sourceLayer() const;
%Docstring
:rtype: QgsVectorLayer
%End
void setSourceLayer( QgsVectorLayer *sourceLayer );
QString displayExpression() const;
%Docstring
:rtype: str
%End
void setDisplayExpression( const QString &displayExpression );
QString filterValue() const;
%Docstring
:rtype: str
%End
void setFilterValue( const QString &filterValue );
virtual QModelIndex index( int row, int column, const QModelIndex &parent ) const;
virtual QModelIndex parent( const QModelIndex &child ) const;
virtual int rowCount( const QModelIndex &parent ) const;
virtual int columnCount( const QModelIndex &parent ) const;
virtual QVariant data( const QModelIndex &index, int role ) const;
QString filterExpression() const;
%Docstring
An additional filter expression to apply, next to the filterValue.
Can be used for spatial filtering etc.
:rtype: str
%End
void setFilterExpression( const QString &filterExpression );
%Docstring
An additional filter expression to apply, next to the filterValue.
Can be used for spatial filtering etc.
%End
bool isLoading() const;
%Docstring
:rtype: bool
%End
QString identifierField() const;
%Docstring
:rtype: str
%End
void setIdentifierField( const QString &identifierField );
QVariant extraIdentifierValue() const;
%Docstring
:rtype: QVariant
%End
void setExtraIdentifierValue( const QVariant &extraIdentifierValue );
int extraIdentifierValueIndex() const;
%Docstring
:rtype: int
%End
bool extraValueDoesNotExist() const;
%Docstring
:rtype: bool
%End
signals:
void sourceLayerChanged();
void displayExpressionChanged();
void filterValueChanged();
void filterExpressionChanged();
void isLoadingChanged();
void identifierFieldChanged();
void filterJobCompleted();
void extraIdentifierValueChanged();
void extraIdentifierValueIndexChanged( int index );
void extraValueDoesNotExistChanged();
void beginUpdate();
void endUpdate();
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsfeaturefiltermodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -108,6 +108,7 @@
%Include qgsfeatureselectiondlg.sip
%Include qgsfieldcombobox.sip
%Include qgsfieldexpressionwidget.sip
%Include qgsfeaturelistcombobox.sip
%Include qgsfieldvalidator.sip
%Include qgsfieldvalueslineedit.sip
%Include qgsfilewidget.sip

View File

@ -0,0 +1,104 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsfeaturelistcombobox.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsFeatureListComboBox : QComboBox
{
%Docstring
*************************************************************************
qgsfieldlistcombobox.h - QgsFieldListComboBox
---------------------
begin : 10.3.2017
copyright : (C) 2017 by Matthias Kuhn
email : matthias@opengis.ch
**************************************************************************
*
This program is free software; you can redistribute it and/or modify *
it under the terms of the GNU General Public License as published by *
the Free Software Foundation; either version 2 of the License, or *
(at your option) any later version. *
*
**************************************************************************
%End
%TypeHeaderCode
#include "qgsfeaturelistcombobox.h"
%End
public:
QgsFeatureListComboBox( QWidget *parent = 0 );
QgsVectorLayer *sourceLayer() const;
%Docstring
:rtype: QgsVectorLayer
%End
void setSourceLayer( QgsVectorLayer *sourceLayer );
QString displayExpression() const;
%Docstring
:rtype: str
%End
void setDisplayExpression( const QString &displayExpression );
QString filterExpression() const;
%Docstring
:rtype: str
%End
void setFilterExpression( const QString &filterExpression );
QVariant identifierValue() const;
%Docstring
:rtype: QVariant
%End
void setIdentifierValue( const QVariant &identifierValue );
QgsFeatureRequest currentFeatureRequest() const;
%Docstring
:rtype: QgsFeatureRequest
%End
bool allowNull() const;
%Docstring
:rtype: bool
%End
void setAllowNull( bool allowNull );
QString identifierField() const;
%Docstring
:rtype: str
%End
void setIdentifierField( const QString &identifierField );
QModelIndex currentModelIndex() const;
%Docstring
:rtype: QModelIndex
%End
virtual void focusOutEvent( QFocusEvent *event );
virtual void keyPressEvent( QKeyEvent *event );
signals:
void sourceLayerChanged();
void displayExpressionChanged();
void filterExpressionChanged();
void identifierValueChanged();
void identifierFieldChanged();
void allowNullChanged();
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsfeaturelistcombobox.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -175,6 +175,7 @@ SET(QGIS_CORE_SRCS
qgsfeaturesink.cpp
qgsfeaturesource.cpp
qgsfeaturestore.cpp
qgsfeaturefiltermodel.cpp
qgsfield.cpp
qgsfieldconstraints.cpp
qgsfieldformatter.cpp
@ -590,6 +591,8 @@ SET(QGIS_CORE_MOC_HDRS
qgsfieldmodel.h
qgsfieldproxymodel.h
qgsfiledownloader.h
qgsfeaturefiltermodel.h
qgsfeaturefiltermodel_p.h
qgsgeometryvalidator.h
qgsgml.h
qgsgmlschema.h

View File

@ -0,0 +1,412 @@
/***************************************************************************
qgsfeaturefiltermodel.cpp - QgsFeatureFilterModel
---------------------
begin : 10.3.2017
copyright : (C) 2017 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsfeaturefiltermodel.h"
#include "qgsfeaturefiltermodel_p.h"
QgsFeatureFilterModel::QgsFeatureFilterModel( QObject *parent )
: QAbstractItemModel( parent )
, mSourceLayer( nullptr )
{
mReloadTimer.setInterval( 100 );
mReloadTimer.setSingleShot( true );
connect( &mReloadTimer, &QTimer::timeout, this, &QgsFeatureFilterModel::scheduledReload );
setExtraIdentifierValueUnguarded( QVariant() );
}
QgsFeatureFilterModel::~QgsFeatureFilterModel()
{
if ( mGatherer )
connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
}
QgsVectorLayer *QgsFeatureFilterModel::sourceLayer() const
{
return mSourceLayer;
}
void QgsFeatureFilterModel::setSourceLayer( QgsVectorLayer *sourceLayer )
{
if ( mSourceLayer == sourceLayer )
return;
mSourceLayer = sourceLayer;
reload();
emit sourceLayerChanged();
}
QString QgsFeatureFilterModel::displayExpression() const
{
return mDisplayExpression;
}
void QgsFeatureFilterModel::setDisplayExpression( const QString &displayExpression )
{
if ( mDisplayExpression == displayExpression )
return;
mDisplayExpression = displayExpression;
reload();
emit displayExpressionChanged();
}
QString QgsFeatureFilterModel::filterValue() const
{
return mFilterValue;
}
void QgsFeatureFilterModel::setFilterValue( const QString &filterValue )
{
if ( mFilterValue == filterValue )
return;
mFilterValue = filterValue;
reload();
emit filterValueChanged();
}
QString QgsFeatureFilterModel::filterExpression() const
{
return mFilterExpression;
}
void QgsFeatureFilterModel::setFilterExpression( const QString &filterExpression )
{
if ( mFilterExpression == filterExpression )
return;
mFilterExpression = filterExpression;
reload();
emit filterExpressionChanged();
}
bool QgsFeatureFilterModel::isLoading() const
{
return mGatherer;
}
QModelIndex QgsFeatureFilterModel::index( int row, int column, const QModelIndex &parent ) const
{
Q_UNUSED( parent )
return createIndex( row, column, nullptr );
}
QModelIndex QgsFeatureFilterModel::parent( const QModelIndex &child ) const
{
Q_UNUSED( child )
return QModelIndex();
}
int QgsFeatureFilterModel::rowCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent );
return mEntries.size();
}
int QgsFeatureFilterModel::columnCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent )
return 1;
}
QVariant QgsFeatureFilterModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() )
return QVariant();
switch ( role )
{
case Qt::DisplayRole:
case Qt::EditRole:
case ValueRole:
return mEntries.value( index.row() ).value;
case IdentifierValueRole:
return mEntries.value( index.row() ).identifierValue;
}
return QVariant();
}
void QgsFeatureFilterModel::updateCompleter()
{
emit beginUpdate();
QVector<Entry> entries = mGatherer->entries();
if ( mExtraIdentifierValueIndex == -1 )
setExtraIdentifierValueUnguarded( QVariant() );
// Only reloading the current entry?
if ( mGatherer->data().toBool() )
{
if ( !entries.isEmpty() )
{
mEntries.replace( mExtraIdentifierValueIndex, entries.at( 0 ) );
emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
mShouldReloadCurrentFeature = false;
setExtraValueDoesNotExist( false );
}
else
{
setExtraValueDoesNotExist( true );
}
mShouldReloadCurrentFeature = false;
}
else
{
// We got strings for a filter selection
std::sort( entries.begin(), entries.end(), []( const Entry & a, const Entry & b ) { return a.value.localeAwareCompare( b.value ) < 0; } );
int newEntriesSize = entries.size();
// Find the index of the extra entry in the new list
int currentEntryInNewList = -1;
if ( mExtraIdentifierValueIndex != -1 )
{
for ( int i = 0; i < newEntriesSize; ++i )
{
if ( entries.at( i ).identifierValue == mExtraIdentifierValue )
{
currentEntryInNewList = i;
mEntries.replace( mExtraIdentifierValueIndex, entries.at( i ) );
emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
setExtraValueDoesNotExist( false );
break;
}
}
}
else
{
Q_ASSERT_X( false, "QgsFeatureFilterModel::updateCompleter", "No extra identifier value generated. Should not get here." );
}
int firstRow = 0;
// Move the extra entry to the first position
if ( mExtraIdentifierValueIndex != -1 )
{
if ( mExtraIdentifierValueIndex != 0 )
{
beginMoveRows( QModelIndex(), mExtraIdentifierValueIndex, mExtraIdentifierValueIndex, QModelIndex(), 0 );
mEntries.move( mExtraIdentifierValueIndex, 0 );
endMoveRows();
}
firstRow = 1;
}
// Remove all entries (except for extra entry if existant)
beginRemoveRows( QModelIndex(), firstRow, mEntries.size() - firstRow );
mEntries.remove( firstRow, mEntries.size() - firstRow );
endRemoveRows();
if ( currentEntryInNewList == -1 )
{
beginInsertRows( QModelIndex(), 1, entries.size() + 1 );
mEntries += entries;
endInsertRows();
setExtraIdentifierValueIndex( 0 );
}
else
{
if ( currentEntryInNewList != 0 )
{
beginInsertRows( QModelIndex(), 0, currentEntryInNewList - 1 );
mEntries = entries.mid( 0, currentEntryInNewList );
endInsertRows();
}
else
{
mEntries.replace( 0, entries.at( 0 ) );
}
emit dataChanged( index( currentEntryInNewList, 0, QModelIndex() ), index( currentEntryInNewList, 0, QModelIndex() ) );
beginInsertRows( QModelIndex(), currentEntryInNewList + 1, newEntriesSize - currentEntryInNewList - 1 );
mEntries += entries.mid( currentEntryInNewList + 1 );
endInsertRows();
setExtraIdentifierValueIndex( currentEntryInNewList );
}
emit filterJobCompleted();
}
emit endUpdate();
}
void QgsFeatureFilterModel::gathererThreadFinished()
{
delete mGatherer;
mGatherer = nullptr;
emit isLoadingChanged();
}
void QgsFeatureFilterModel::scheduledReload()
{
if ( !mSourceLayer )
return;
bool wasLoading = false;
if ( mGatherer )
{
// Send the gatherer thread to the graveyard:
// forget about it, tell it to stop and delete when finished
disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
mGatherer->stop();
wasLoading = true;
}
QgsFeatureRequest request;
if ( mShouldReloadCurrentFeature )
{
request.setFilterExpression( QStringLiteral( "%1 = %2" ).arg( QgsExpression::quotedColumnRef( mIdentifierField ), QgsExpression::quotedValue( mExtraIdentifierValue ) ) );
}
else
{
QString filterClause;
if ( mFilterValue.isEmpty() && !mFilterExpression.isEmpty() )
filterClause = mFilterExpression;
else if ( mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
filterClause = QStringLiteral( "(%1) ILIKE '\%%2\%'" ).arg( mDisplayExpression, mFilterValue );
else if ( !mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
filterClause = QStringLiteral( "(%1) AND ((%2) ILIKE '\%%3\%')" ).arg( mFilterExpression, mDisplayExpression, mFilterValue );
request.setFilterExpression( filterClause );
}
request.setLimit( 100 );
mGatherer = new QgsFieldExpressionValuesGatherer( mSourceLayer, mDisplayExpression, mIdentifierField, request );
mGatherer->setData( mShouldReloadCurrentFeature );
connect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
mGatherer->start();
if ( !wasLoading )
emit isLoadingChanged();
}
void QgsFeatureFilterModel::setExtraIdentifierValueIndex( int index )
{
if ( mExtraIdentifierValueIndex == index )
return;
mExtraIdentifierValueIndex = index;
emit extraIdentifierValueIndexChanged( index );
}
void QgsFeatureFilterModel::reloadCurrentFeature()
{
mShouldReloadCurrentFeature = true;
mReloadTimer.start();
}
void QgsFeatureFilterModel::setExtraIdentifierValueUnguarded( const QVariant &extraIdentifierValue )
{
const QVector<Entry> entries = mEntries;
int index = 0;
for ( const Entry &entry : entries )
{
if ( entry.identifierValue == extraIdentifierValue )
{
setExtraIdentifierValueIndex( index );
break;
}
index++;
}
// Value not found in current entries
if ( mExtraIdentifierValueIndex != index )
{
beginInsertRows( QModelIndex(), 0, 0 );
mEntries.prepend( Entry( extraIdentifierValue, QStringLiteral( "(%1)" ).arg( extraIdentifierValue.toString() ) ) );
endInsertRows();
setExtraIdentifierValueIndex( 0 );
reloadCurrentFeature();
}
}
bool QgsFeatureFilterModel::extraValueDoesNotExist() const
{
return mExtraValueDoesNotExist;
}
void QgsFeatureFilterModel::setExtraValueDoesNotExist( bool extraValueDoesNotExist )
{
if ( mExtraValueDoesNotExist == extraValueDoesNotExist )
return;
mExtraValueDoesNotExist = extraValueDoesNotExist;
emit extraValueDoesNotExistChanged();
}
int QgsFeatureFilterModel::extraIdentifierValueIndex() const
{
return mExtraIdentifierValueIndex;
}
QString QgsFeatureFilterModel::identifierField() const
{
return mIdentifierField;
}
void QgsFeatureFilterModel::setIdentifierField( const QString &identifierField )
{
if ( mIdentifierField == identifierField )
return;
mIdentifierField = identifierField;
emit identifierFieldChanged();
}
void QgsFeatureFilterModel::reload()
{
mReloadTimer.start();
}
QVariant QgsFeatureFilterModel::extraIdentifierValue() const
{
return mExtraIdentifierValue;
}
void QgsFeatureFilterModel::setExtraIdentifierValue( const QVariant &extraIdentifierValue )
{
if ( extraIdentifierValue == mExtraIdentifierValue && extraIdentifierValue.isNull() == mExtraIdentifierValue.isNull() )
return;
setExtraIdentifierValueUnguarded( extraIdentifierValue );
mExtraIdentifierValue = extraIdentifierValue;
emit extraIdentifierValueChanged();
}
QVariant QgsFieldExpressionValuesGatherer::data() const
{
return mData;
}
void QgsFieldExpressionValuesGatherer::setData( const QVariant &data )
{
mData = data;
}

View File

@ -0,0 +1,163 @@
/***************************************************************************
qgsfeaturefiltermodel.h - QgsFeatureFilterModel
---------------------
begin : 10.3.2017
copyright : (C) 2017 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSFEATUREFILTERMODEL_H
#define QGSFEATUREFILTERMODEL_H
#include <QAbstractItemModel>
#include "qgsvectorlayer.h"
class QgsFieldExpressionValuesGatherer;
/**
* Provides a list of features based on filter conditions.
* Features are fetched asynchronously.
*/
class CORE_EXPORT QgsFeatureFilterModel : public QAbstractItemModel
{
Q_OBJECT
Q_PROPERTY( QgsVectorLayer *sourceLayer READ sourceLayer WRITE setSourceLayer NOTIFY sourceLayerChanged )
Q_PROPERTY( QString displayExpression READ displayExpression WRITE setDisplayExpression NOTIFY displayExpressionChanged )
Q_PROPERTY( QString filterValue READ filterValue WRITE setFilterValue NOTIFY filterValueChanged )
Q_PROPERTY( QString filterExpression READ filterExpression WRITE setFilterExpression NOTIFY filterExpressionChanged )
Q_PROPERTY( bool isLoading READ isLoading NOTIFY isLoadingChanged )
/**
* A field of sourceLayer that is unique and should be used to identify features.
* Normally the primary key field.
* Needs to match the identifierValue.
*/
Q_PROPERTY( QString identifierField READ identifierField WRITE setIdentifierField NOTIFY identifierFieldChanged )
/**
* The value that identifies the current feature.
*/
Q_PROPERTY( QVariant extraIdentifierValue READ extraIdentifierValue WRITE setExtraIdentifierValue NOTIFY extraIdentifierValueChanged )
Q_PROPERTY( int extraIdentifierValueIndex READ extraIdentifierValueIndex NOTIFY extraIdentifierValueIndexChanged )
public:
enum Role
{
IdentifierValueRole = Qt::UserRole,
ValueRole
};
QgsFeatureFilterModel( QObject *parent = nullptr );
~QgsFeatureFilterModel();
QgsVectorLayer *sourceLayer() const;
void setSourceLayer( QgsVectorLayer *sourceLayer );
QString displayExpression() const;
void setDisplayExpression( const QString &displayExpression );
QString filterValue() const;
void setFilterValue( const QString &filterValue );
virtual QModelIndex index( int row, int column, const QModelIndex &parent ) const override;
virtual QModelIndex parent( const QModelIndex &child ) const override;
virtual int rowCount( const QModelIndex &parent ) const override;
virtual int columnCount( const QModelIndex &parent ) const override;
virtual QVariant data( const QModelIndex &index, int role ) const override;
/**
* An additional filter expression to apply, next to the filterValue.
* Can be used for spatial filtering etc.
*/
QString filterExpression() const;
/**
* An additional filter expression to apply, next to the filterValue.
* Can be used for spatial filtering etc.
*/
void setFilterExpression( const QString &filterExpression );
bool isLoading() const;
QString identifierField() const;
void setIdentifierField( const QString &identifierField );
QVariant extraIdentifierValue() const;
void setExtraIdentifierValue( const QVariant &extraIdentifierValue );
int extraIdentifierValueIndex() const;
bool extraValueDoesNotExist() const;
signals:
void sourceLayerChanged();
void displayExpressionChanged();
void filterValueChanged();
void filterExpressionChanged();
void isLoadingChanged();
void identifierFieldChanged();
void filterJobCompleted();
void extraIdentifierValueChanged();
void extraIdentifierValueIndexChanged( int index );
void extraValueDoesNotExistChanged();
void beginUpdate();
void endUpdate();
private slots:
void updateCompleter();
void gathererThreadFinished();
void scheduledReload();
private:
void setExtraIdentifierValueIndex( int index );
void setExtraValueDoesNotExist( bool extraValueDoesNotExist );
void reload();
void reloadCurrentFeature();
void setExtraIdentifierValueUnguarded( const QVariant &extraIdentifierValue );
struct Entry
{
Entry()
{}
Entry( QVariant _identifierValue, const QString &_value )
: identifierValue( _identifierValue )
, value( _value )
{}
QVariant identifierValue;
QString value;
bool operator()( const Entry &lhs, const Entry &rhs ) const;
};
QgsVectorLayer *mSourceLayer;
QString mDisplayExpression;
QString mFilterValue;
QString mFilterExpression;
QVector<Entry> mEntries;
QgsFieldExpressionValuesGatherer *mGatherer = nullptr;
QTimer mReloadTimer;
bool mShouldReloadCurrentFeature;
bool mExtraValueDoesNotExist = false;
QString mIdentifierField;
QVariant mExtraIdentifierValue;
int mExtraIdentifierValueIndex = -1;
friend class QgsFieldExpressionValuesGatherer;
};
#endif // QGSFEATUREFILTERMODEL_H

View File

@ -0,0 +1,131 @@
/***************************************************************************
qgsfeaturefiltermodel_p - QgsFieldExpressionValuesGatherer
---------------------
begin : 10.3.2017
copyright : (C) 2017 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSFEATUREFILTERMODEL_P_H
#define QGSFEATUREFILTERMODEL_P_H
#include <QThread>
#include "qgsvectorlayer.h"
#include "qgsfeaturefiltermodel.h"
#include "qgslogger.h"
#include "qgsvectorlayerfeatureiterator.h"
#define SIP_NO_FILE
// just internal guff - definitely not for exposing to public API!
///@cond PRIVATE
/**
* \class QgsFieldExpressionValuesGatherer
* Gathers features with substring matching on an expression.
*
* \since QGIS 3.0
*/
class QgsFieldExpressionValuesGatherer: public QThread
{
Q_OBJECT
public:
QgsFieldExpressionValuesGatherer( QgsVectorLayer *layer, const QString &displayExpression, const QString &identifierField, const QgsFeatureRequest &request = QgsFeatureRequest() )
: mSource( new QgsVectorLayerFeatureSource( layer ) )
, mDisplayExpression( displayExpression )
, mRequest( request )
, mWasCanceled( false )
, mIdentifierField( identifierField )
{
}
~QgsFieldExpressionValuesGatherer()
{
}
virtual void run() override
{
mWasCanceled = false;
mIterator = mSource->getFeatures( mRequest );
QgsDebugMsg( QStringLiteral( "New gatherer: %1" ).arg( mRequest.filterExpression()->expression() ) );
mDisplayExpression.prepare( &mExpressionContext );
QgsFeature feat;
int attribute = mSource->fields().indexOf( mIdentifierField );
while ( mIterator.nextFeature( feat ) )
{
mExpressionContext.setFeature( feat );
mEntries.append( QgsFeatureFilterModel::Entry( feat.attribute( attribute ), mDisplayExpression.evaluate( &mExpressionContext ).toString() ) );
if ( mWasCanceled )
return;
}
emit collectedValues();
}
//! Informs the gatherer to immediately stop collecting values
void stop()
{
mWasCanceled = true;
}
//! Returns true if collection was canceled before completion
bool wasCanceled() const { return mWasCanceled; }
QVector<QgsFeatureFilterModel::Entry> entries() const
{
return mEntries;
}
QgsFeatureRequest request() const
{
return mRequest;
}
/**
* Internal data, use for whatever you want. Defaults to -1.
*/
QVariant data() const;
/**
* Internal data, use for whatever you want. Defaults to -1.
*/
void setData( const QVariant &data ); // TODO: Do we still need this???
signals:
/**
* Emitted when values have been collected
* @param values list of unique matching string values
*/
void collectedValues();
private:
std::unique_ptr<QgsVectorLayerFeatureSource> mSource;
QgsExpression mDisplayExpression;
QgsExpressionContext mExpressionContext;
QgsFeatureRequest mRequest;
QgsFeatureIterator mIterator;
bool mWasCanceled;
QVector<QgsFeatureFilterModel::Entry> mEntries;
QString mIdentifierField;
QVariant mData;
};
///@endcond
#endif // QGSFEATUREFILTERMODEL_P_H

View File

@ -241,6 +241,7 @@ SET(QGIS_GUI_SRCS
qgsfeatureselectiondlg.cpp
qgsfieldcombobox.cpp
qgsfieldexpressionwidget.cpp
qgsfeaturelistcombobox.cpp
qgsfieldvalidator.cpp
qgsfieldvalueslineedit.cpp
qgsfilewidget.cpp
@ -412,6 +413,7 @@ SET(QGIS_GUI_MOC_HDRS
qgsfeatureselectiondlg.h
qgsfieldcombobox.h
qgsfieldexpressionwidget.h
qgsfeaturelistcombobox.h
qgsfieldvalidator.h
qgsfieldvalueslineedit.h
qgsfilewidget.h

View File

@ -0,0 +1,217 @@
/***************************************************************************
qgsfieldlistcombobox.cpp - QgsFieldListComboBox
---------------------
begin : 10.3.2017
copyright : (C) 2017 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsfeaturelistcombobox.h"
#include "qgsfeaturefiltermodel.h"
#include "qgsanimatedicon.h"
#include "qgsfilterlineedit.h"
#include "qgslogger.h"
#include <QCompleter>
#include <QLineEdit>
#include <QKeyEvent>
QgsFeatureListComboBox::QgsFeatureListComboBox( QWidget *parent )
: QComboBox( parent )
, mModel( new QgsFeatureFilterModel( this ) )
, mCompleter( new QCompleter( mModel ) )
{
mCompleter->setCaseSensitivity( Qt::CaseInsensitive );
mCompleter->setFilterMode( Qt::MatchContains );
setCompleter( mCompleter );
mCompleter->setWidget( this );
connect( mModel, &QgsFeatureFilterModel::sourceLayerChanged, this, &QgsFeatureListComboBox::sourceLayerChanged );
connect( mModel, &QgsFeatureFilterModel::displayExpressionChanged, this, &QgsFeatureListComboBox::displayExpressionChanged );
connect( mModel, &QgsFeatureFilterModel::filterExpressionChanged, this, &QgsFeatureListComboBox::filterExpressionChanged );
connect( mModel, &QgsFeatureFilterModel::isLoadingChanged, this, &QgsFeatureListComboBox::onLoadingChanged );
connect( mModel, &QgsFeatureFilterModel::filterJobCompleted, this, &QgsFeatureListComboBox::onFilterUpdateCompleted );
connect( mModel, &QgsFeatureFilterModel::extraIdentifierValueChanged, this, &QgsFeatureListComboBox::identifierValueChanged );
connect( mModel, &QgsFeatureFilterModel::extraIdentifierValueIndexChanged, this, &QgsFeatureListComboBox::setCurrentIndex );
connect( mModel, &QgsFeatureFilterModel::identifierFieldChanged, this, &QgsFeatureListComboBox::identifierFieldChanged );
connect( mCompleter, static_cast<void( QCompleter::* )( const QModelIndex & )>( &QCompleter::highlighted ), this, &QgsFeatureListComboBox::onItemSelected );
connect( mCompleter, static_cast<void( QCompleter::* )( const QModelIndex & )>( &QCompleter::activated ), this, &QgsFeatureListComboBox::onActivated );
connect( mModel, &QgsFeatureFilterModel::beginUpdate, this, &QgsFeatureListComboBox::storeLineEditState );
connect( mModel, &QgsFeatureFilterModel::endUpdate, this, &QgsFeatureListComboBox::restoreLineEditState );
connect( this, static_cast<void( QgsFeatureListComboBox::* )( int )>( &QgsFeatureListComboBox::currentIndexChanged ), this, &QgsFeatureListComboBox::onCurrentIndexChanged );
mLineEdit = new QgsFilterLineEdit();
setEditable( true );
setLineEdit( mLineEdit );
setModel( mModel );
connect( mLineEdit, &QgsFilterLineEdit::textEdited, this, &QgsFeatureListComboBox::onCurrentTextChanged );
connect( mLineEdit, &QgsFilterLineEdit::textChanged, this, []( const QString & text )
{
QgsDebugMsg( QStringLiteral( "Edit text changed to %1" ).arg( text ) );
} );
}
QgsVectorLayer *QgsFeatureListComboBox::sourceLayer() const
{
return mModel->sourceLayer();
}
void QgsFeatureListComboBox::setSourceLayer( QgsVectorLayer *sourceLayer )
{
mModel->setSourceLayer( sourceLayer );
}
QString QgsFeatureListComboBox::displayExpression() const
{
return mModel->displayExpression();
}
void QgsFeatureListComboBox::setDisplayExpression( const QString &expression )
{
mModel->setDisplayExpression( expression );
}
void QgsFeatureListComboBox::onCurrentTextChanged( const QString &text )
{
mPopupRequested = true;
mModel->setFilterValue( text );
}
void QgsFeatureListComboBox::onFilterUpdateCompleted()
{
if ( mPopupRequested )
mCompleter->complete();
mPopupRequested = false;
}
void QgsFeatureListComboBox::onLoadingChanged()
{
mLineEdit->setShowSpinner( mModel->isLoading() );
}
void QgsFeatureListComboBox::onItemSelected( const QModelIndex &index )
{
setCurrentIndex( index.row() );
}
void QgsFeatureListComboBox::onCurrentIndexChanged( int i )
{
QModelIndex modelIndex = mModel->index( i, 0, QModelIndex() );
mModel->setExtraIdentifierValue( mModel->data( modelIndex, QgsFeatureFilterModel::IdentifierValueRole ) );
mLineEdit->setText( mModel->data( modelIndex, QgsFeatureFilterModel::ValueRole ).toString() );
}
void QgsFeatureListComboBox::onActivated( QModelIndex modelIndex )
{
setIdentifierValue( mModel->data( modelIndex, QgsFeatureFilterModel::IdentifierValueRole ) );
QgsDebugMsg( QStringLiteral( "Activated index" ) );
QgsDebugMsg( QStringLiteral( "%1 %2" ).arg( QString::number( modelIndex.row() ), mModel->data( modelIndex, QgsFeatureFilterModel::ValueRole ).toString() ) );
mLineEdit->setText( mModel->data( modelIndex, QgsFeatureFilterModel::ValueRole ).toString() );
}
void QgsFeatureListComboBox::storeLineEditState()
{
mLineEditState.store( mLineEdit );
}
void QgsFeatureListComboBox::restoreLineEditState()
{
mLineEditState.restore( mLineEdit );
}
QString QgsFeatureListComboBox::identifierField() const
{
return mModel->identifierField();
}
void QgsFeatureListComboBox::setIdentifierField( const QString &identifierField )
{
mModel->setIdentifierField( identifierField );
}
QModelIndex QgsFeatureListComboBox::currentModelIndex() const
{
return mModel->index( mModel->extraIdentifierValueIndex(), 0, QModelIndex() );
}
void QgsFeatureListComboBox::focusOutEvent( QFocusEvent *event )
{
Q_UNUSED( event )
QComboBox::focusOutEvent( event );
mLineEdit->setText( mModel->data( currentModelIndex(), QgsFeatureFilterModel::ValueRole ).toString() );
}
void QgsFeatureListComboBox::keyPressEvent( QKeyEvent *event )
{
if ( event->key() == Qt::Key_Escape )
{
mLineEdit->setText( mModel->data( currentModelIndex(), QgsFeatureFilterModel::ValueRole ).toString() );
}
QComboBox::keyReleaseEvent( event );
}
bool QgsFeatureListComboBox::allowNull() const
{
return mAllowNull;
}
void QgsFeatureListComboBox::setAllowNull( bool allowNull )
{
if ( mAllowNull == allowNull )
return;
mAllowNull = allowNull;
emit allowNullChanged();
}
QVariant QgsFeatureListComboBox::identifierValue() const
{
return mModel->extraIdentifierValue();
}
void QgsFeatureListComboBox::setIdentifierValue( const QVariant &identifierValue )
{
mModel->setExtraIdentifierValue( identifierValue );
}
QgsFeatureRequest QgsFeatureListComboBox::currentFeatureRequest() const
{
return QgsFeatureRequest().setFilterExpression( QStringLiteral( "%1 = %2" ).arg( QgsExpression::quotedColumnRef( mModel->identifierField() ), QgsExpression::quotedValue( mModel->extraIdentifierValue() ) ) );
}
QString QgsFeatureListComboBox::filterExpression() const
{
return mModel->filterExpression();
}
void QgsFeatureListComboBox::setFilterExpression( const QString &filterExpression )
{
mModel->setFilterExpression( filterExpression );
}
void QgsFeatureListComboBox::LineEditState::store( QLineEdit *lineEdit )
{
text = lineEdit->text();
selectionStart = lineEdit->selectionStart();
selectionLength = lineEdit->selectedText().length();
cursorPosition = lineEdit->cursorPosition();
}
void QgsFeatureListComboBox::LineEditState::restore( QLineEdit *lineEdit ) const
{
lineEdit->setText( text );
lineEdit->setCursorPosition( cursorPosition );
lineEdit->setSelection( selectionStart, selectionLength );
}

View File

@ -0,0 +1,110 @@
/***************************************************************************
qgsfieldlistcombobox.h - QgsFieldListComboBox
---------------------
begin : 10.3.2017
copyright : (C) 2017 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSFIELDLISTCOMBOBOX_H
#define QGSFIELDLISTCOMBOBOX_H
#include <QComboBox>
#include "qgsfeature.h"
#include "qgsfeaturerequest.h"
#include "qgis_gui.h"
class QgsVectorLayer;
class QgsFeatureFilterModel;
class QgsAnimatedIcon;
class QgsFilterLineEdit;
class GUI_EXPORT QgsFeatureListComboBox : public QComboBox
{
Q_OBJECT
Q_PROPERTY( QgsVectorLayer *sourceLayer READ sourceLayer WRITE setSourceLayer NOTIFY sourceLayerChanged )
Q_PROPERTY( QString displayExpression READ displayExpression WRITE setDisplayExpression NOTIFY displayExpressionChanged )
Q_PROPERTY( QString filterExpression READ filterExpression WRITE setFilterExpression NOTIFY filterExpressionChanged )
Q_PROPERTY( QVariant identifierValue READ identifierValue WRITE setIdentifierValue NOTIFY identifierValueChanged )
Q_PROPERTY( QString identifierField READ identifierField WRITE setIdentifierField NOTIFY identifierFieldChanged )
Q_PROPERTY( bool allowNull READ allowNull WRITE setAllowNull NOTIFY allowNullChanged )
public:
QgsFeatureListComboBox( QWidget *parent = nullptr );
QgsVectorLayer *sourceLayer() const;
void setSourceLayer( QgsVectorLayer *sourceLayer );
QString displayExpression() const;
void setDisplayExpression( const QString &displayExpression );
QString filterExpression() const;
void setFilterExpression( const QString &filterExpression );
QVariant identifierValue() const;
void setIdentifierValue( const QVariant &identifierValue );
QgsFeatureRequest currentFeatureRequest() const;
bool allowNull() const;
void setAllowNull( bool allowNull );
QString identifierField() const;
void setIdentifierField( const QString &identifierField );
QModelIndex currentModelIndex() const;
virtual void focusOutEvent( QFocusEvent *event ) override;
virtual void keyPressEvent( QKeyEvent *event ) override;
signals:
void sourceLayerChanged();
void displayExpressionChanged();
void filterExpressionChanged();
void identifierValueChanged();
void identifierFieldChanged();
void allowNullChanged();
private slots:
void onCurrentTextChanged( const QString &text );
void onFilterUpdateCompleted();
void onLoadingChanged();
void onItemSelected( const QModelIndex &index );
void onCurrentIndexChanged( int i );
void onActivated( QModelIndex index );
void storeLineEditState();
void restoreLineEditState();
private:
struct LineEditState
{
void store( QLineEdit *lineEdit );
void restore( QLineEdit *lineEdit ) const;
QString text;
int selectionStart;
int selectionLength;
int cursorPosition;
};
QgsFeatureFilterModel *mModel;
QCompleter *mCompleter;
QString mDisplayExpression;
QgsFilterLineEdit *mLineEdit;
bool mAllowNull = true;
bool mPopupRequested = false;
bool mIsCurrentlyEdited = false;
LineEditState mLineEditState;
};
#endif // QGSFIELDLISTCOMBOBOX_H

View File

@ -0,0 +1,19 @@
/***************************************************************************
qgsfeaturelistcombobox_p.h
---------------------
begin : 24.10.2017
copyright : (C) 2017 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSFEATURELISTCOMBOBOX_P_H
#define QGSFEATURELISTCOMBOBOX_P_H
#endif // QGSFEATURELISTCOMBOBOX_P_H