mirror of
https://github.com/qgis/QGIS.git
synced 2025-06-19 00:02:48 -04:00
[FEATURE] Show field values in autocompleter in form filter mode
This adds a new gui widget QgsFieldValuesLineEdit which includes an autocompleter populated with current field values. The autocompleter is nicely updated in the background so that the gui remains nice and responsive, even if there's millions of records in the associated table. It's now used as a search widget for text fields, so can be seen in the browser window if you set the filter to a text field, or if you launch the form based select/filter by selecting a layer and pressing F3.
This commit is contained in:
parent
e27822bf39
commit
0bfc9bb6e3
@ -75,6 +75,7 @@
|
||||
%Include qgsfieldmodel.sip
|
||||
%Include qgsfieldproxymodel.sip
|
||||
%Include qgsfieldvalidator.sip
|
||||
%Include qgsfieldvalueslineedit.sip
|
||||
%Include qgsfiledropedit.sip
|
||||
%Include qgsfilewidget.sip
|
||||
%Include qgsfiledownloader.sip
|
||||
|
59
python/gui/qgsfieldvalueslineedit.sip
Normal file
59
python/gui/qgsfieldvalueslineedit.sip
Normal file
@ -0,0 +1,59 @@
|
||||
/** \class QgsFieldValuesLineEdit
|
||||
* \ingroup gui
|
||||
* A line edit with an autocompleter which takes unique values from a vector layer's fields.
|
||||
* The autocompleter is populated from the vector layer in the background to ensure responsive
|
||||
* interaction with the widget.
|
||||
* \note added in QGIS 3.0
|
||||
*/
|
||||
class QgsFieldValuesLineEdit: QgsFilterLineEdit
|
||||
{
|
||||
%TypeHeaderCode
|
||||
#include <qgsfieldvalueslineedit.h>
|
||||
%End
|
||||
public:
|
||||
|
||||
/** Constructor for QgsFieldValuesLineEdit
|
||||
* @param parent parent widget
|
||||
*/
|
||||
QgsFieldValuesLineEdit( QWidget *parent /TransferThis/ = nullptr );
|
||||
|
||||
virtual ~QgsFieldValuesLineEdit();
|
||||
|
||||
/** Sets the layer containing the field that values will be shown from.
|
||||
* @param layer vector layer
|
||||
* @see layer()
|
||||
* @see setAttributeIndex()
|
||||
*/
|
||||
void setLayer( QgsVectorLayer* layer );
|
||||
|
||||
/** Returns the layer containing the field that values will be shown from.
|
||||
* @see setLayer()
|
||||
* @see attributeIndex()
|
||||
*/
|
||||
QgsVectorLayer* layer() const;
|
||||
|
||||
/** Sets the attribute index for the field containing values to show in the widget.
|
||||
* @param index index of attribute
|
||||
* @see attributeIndex()
|
||||
* @see setLayer()
|
||||
*/
|
||||
void setAttributeIndex( int index );
|
||||
|
||||
/** Returns the attribute index for the field containing values shown in the widget.
|
||||
* @see setAttributeIndex()
|
||||
* @see layer()
|
||||
*/
|
||||
int attributeIndex() const;
|
||||
|
||||
signals:
|
||||
|
||||
/** Emitted when the layer associated with the widget changes.
|
||||
* @param layer vector layer
|
||||
*/
|
||||
void layerChanged( QgsVectorLayer* layer );
|
||||
|
||||
/** Emitted when the field associated with the widget changes.
|
||||
* @param index new attribute index for field
|
||||
*/
|
||||
void attributeIndexChanged( int index );
|
||||
};
|
@ -223,6 +223,7 @@ SET(QGIS_GUI_SRCS
|
||||
qgsfieldmodel.cpp
|
||||
qgsfieldproxymodel.cpp
|
||||
qgsfieldvalidator.cpp
|
||||
qgsfieldvalueslineedit.cpp
|
||||
qgsfiledropedit.cpp
|
||||
qgsfilewidget.cpp
|
||||
qgsfilterlineedit.cpp
|
||||
@ -391,6 +392,7 @@ SET(QGIS_GUI_MOC_HDRS
|
||||
qgsfieldmodel.h
|
||||
qgsfieldproxymodel.h
|
||||
qgsfieldvalidator.h
|
||||
qgsfieldvalueslineedit.h
|
||||
qgsfiledropedit.h
|
||||
qgsfilewidget.h
|
||||
qgsfilterlineedit.h
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "qgsfields.h"
|
||||
#include "qgsfieldvalidator.h"
|
||||
#include "qgsexpression.h"
|
||||
#include "qgsfieldvalueslineedit.h"
|
||||
#include <QSettings>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
@ -255,10 +256,20 @@ void QgsDefaultSearchWidgetWrapper::initWidget( QWidget* widget )
|
||||
mContainer->setLayout( new QHBoxLayout() );
|
||||
mContainer->layout()->setMargin( 0 );
|
||||
mContainer->layout()->setContentsMargins( 0, 0, 0, 0 );
|
||||
mLineEdit = new QgsFilterLineEdit();
|
||||
QVariant::Type fldType = layer()->fields().at( mFieldIdx ).type();
|
||||
|
||||
if ( fldType == QVariant::String )
|
||||
{
|
||||
mLineEdit = new QgsFieldValuesLineEdit();
|
||||
static_cast< QgsFieldValuesLineEdit* >( mLineEdit )->setLayer( layer() );
|
||||
static_cast< QgsFieldValuesLineEdit* >( mLineEdit )->setAttributeIndex( mFieldIdx );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLineEdit = new QgsFilterLineEdit();
|
||||
}
|
||||
mContainer->layout()->addWidget( mLineEdit );
|
||||
|
||||
QVariant::Type fldType = layer()->fields().at( mFieldIdx ).type();
|
||||
if ( fldType == QVariant::String )
|
||||
{
|
||||
mCheckbox = new QCheckBox( QStringLiteral( "Case sensitive" ) );
|
||||
|
@ -17,7 +17,7 @@
|
||||
#define QGSDEFAULTSEARCHWIDGETWRAPPER_H
|
||||
|
||||
#include "qgssearchwidgetwrapper.h"
|
||||
#include <qgsfilterlineedit.h>
|
||||
#include "qgsfilterlineedit.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
|
137
src/gui/qgsfieldvalueslineedit.cpp
Normal file
137
src/gui/qgsfieldvalueslineedit.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
/***************************************************************************
|
||||
qgsfieldvalueslineedit.cpp
|
||||
-------------------------
|
||||
Date : 20-08-2016
|
||||
Copyright : (C) 2016 by Nyall Dawson
|
||||
Email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* 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 "qgsfieldvalueslineedit.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgsfloatingwidget.h"
|
||||
#include <QCompleter>
|
||||
#include <QStringListModel>
|
||||
#include <QTimer>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
QgsFieldValuesLineEdit::QgsFieldValuesLineEdit( QWidget *parent )
|
||||
: QgsFilterLineEdit( parent )
|
||||
, mLayer( nullptr )
|
||||
, mAttributeIndex( -1 )
|
||||
, mUpdateRequested( false )
|
||||
, mGatherer( nullptr )
|
||||
{
|
||||
QCompleter* c = new QCompleter( this );
|
||||
c->setCaseSensitivity( Qt::CaseInsensitive );
|
||||
c->setFilterMode( Qt::MatchContains );
|
||||
setCompleter( c );
|
||||
connect( this, &QgsFieldValuesLineEdit::textEdited, this, &QgsFieldValuesLineEdit::requestCompleterUpdate );
|
||||
mShowPopupTimer.setSingleShot( true );
|
||||
mShowPopupTimer.setInterval( 100 );
|
||||
connect( &mShowPopupTimer, &QTimer::timeout, this, &QgsFieldValuesLineEdit::triggerCompleterUpdate );
|
||||
}
|
||||
|
||||
QgsFieldValuesLineEdit::~QgsFieldValuesLineEdit()
|
||||
{
|
||||
if ( mGatherer )
|
||||
{
|
||||
mGatherer->stop();
|
||||
mGatherer->wait(); // mGatherer is deleted when wait completes
|
||||
}
|
||||
}
|
||||
|
||||
void QgsFieldValuesLineEdit::setLayer( QgsVectorLayer* layer )
|
||||
{
|
||||
if ( mLayer == layer )
|
||||
return;
|
||||
|
||||
mLayer = layer;
|
||||
emit layerChanged( layer );
|
||||
}
|
||||
|
||||
void QgsFieldValuesLineEdit::setAttributeIndex( int index )
|
||||
{
|
||||
if ( mAttributeIndex == index )
|
||||
return;
|
||||
|
||||
mAttributeIndex = index;
|
||||
emit attributeIndexChanged( index );
|
||||
}
|
||||
|
||||
void QgsFieldValuesLineEdit::requestCompleterUpdate()
|
||||
{
|
||||
mUpdateRequested = true;
|
||||
mShowPopupTimer.start();
|
||||
}
|
||||
|
||||
void QgsFieldValuesLineEdit::triggerCompleterUpdate()
|
||||
{
|
||||
mShowPopupTimer.stop();
|
||||
QString currentText = text();
|
||||
|
||||
if ( currentText.isEmpty() )
|
||||
{
|
||||
if ( mGatherer )
|
||||
mGatherer->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
updateCompletionList( currentText );
|
||||
}
|
||||
|
||||
void QgsFieldValuesLineEdit::updateCompletionList( const QString &text )
|
||||
{
|
||||
if ( text.isEmpty() )
|
||||
{
|
||||
if ( mGatherer )
|
||||
mGatherer->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
mUpdateRequested = true;
|
||||
if ( mGatherer )
|
||||
{
|
||||
mRequestedCompletionText = text;
|
||||
mGatherer->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
mGatherer = new QgsFieldValuesLineEditValuesGatherer( mLayer, mAttributeIndex );
|
||||
mGatherer->setSubstring( text );
|
||||
|
||||
connect( mGatherer, &QgsFieldValuesLineEditValuesGatherer::collectedValues, this, &QgsFieldValuesLineEdit::updateCompleter );
|
||||
connect( mGatherer, &QgsFieldValuesLineEditValuesGatherer::finished, this, &QgsFieldValuesLineEdit::gathererThreadFinished );
|
||||
|
||||
mGatherer->start();
|
||||
}
|
||||
|
||||
void QgsFieldValuesLineEdit::gathererThreadFinished()
|
||||
{
|
||||
bool wasCancelled = mGatherer->wasCancelled();
|
||||
|
||||
delete mGatherer;
|
||||
mGatherer = nullptr;
|
||||
|
||||
if ( wasCancelled )
|
||||
{
|
||||
QString text = mRequestedCompletionText;
|
||||
mRequestedCompletionText.clear();
|
||||
updateCompletionList( text );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void QgsFieldValuesLineEdit::updateCompleter( const QStringList& values )
|
||||
{
|
||||
mUpdateRequested = false;
|
||||
completer()->setModel( new QStringListModel( values ) );
|
||||
completer()->complete();
|
||||
}
|
||||
|
217
src/gui/qgsfieldvalueslineedit.h
Normal file
217
src/gui/qgsfieldvalueslineedit.h
Normal file
@ -0,0 +1,217 @@
|
||||
/***************************************************************************
|
||||
qgsfieldvalueslineedit.h
|
||||
-----------------------
|
||||
Date : 20-08-2016
|
||||
Copyright : (C) 2016 by Nyall Dawson
|
||||
Email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* 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 QGSFIELDVALUESLINEEDIT_H
|
||||
#define QGSFIELDVALUESLINEEDIT_H
|
||||
|
||||
#include "qgsfilterlineedit.h"
|
||||
#include "qgsfeedback.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include <QStringListModel>
|
||||
#include <QTreeView>
|
||||
#include <QFocusEvent>
|
||||
#include <QHeaderView>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
|
||||
class QgsFloatingWidget;
|
||||
|
||||
// just internal guff - definitely not for exposing to public API!
|
||||
///@cond PRIVATE
|
||||
|
||||
/** \class QgsFieldValuesLineEditValuesGatherer
|
||||
* Collates unique values containing a matching substring in a thread.
|
||||
*/
|
||||
class QgsFieldValuesLineEditValuesGatherer: public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QgsFieldValuesLineEditValuesGatherer( QgsVectorLayer* layer, int attributeIndex )
|
||||
: mLayer( layer )
|
||||
, mAttributeIndex( attributeIndex )
|
||||
, mFeedback( nullptr )
|
||||
, mWasCancelled( false )
|
||||
{}
|
||||
|
||||
/** Sets the substring to find matching values containing
|
||||
*/
|
||||
void setSubstring( const QString& string ) { mSubstring = string; }
|
||||
|
||||
virtual void run() override
|
||||
{
|
||||
mWasCancelled = false;
|
||||
if ( mSubstring.isEmpty() )
|
||||
{
|
||||
emit collectedValues( QStringList() );
|
||||
return;
|
||||
}
|
||||
|
||||
// allow responsive cancellation
|
||||
mFeedback = new QgsFeedback();
|
||||
// just get 100 values... maybe less/more would be useful?
|
||||
mValues = mLayer->uniqueStringsMatching( mAttributeIndex, mSubstring, 100, mFeedback );
|
||||
|
||||
// be overly cautious - it's *possible* stop() might be called between deleting mFeedback and nulling it
|
||||
mFeedbackMutex.lock();
|
||||
delete mFeedback;
|
||||
mFeedback = nullptr;
|
||||
mFeedbackMutex.unlock();
|
||||
|
||||
emit collectedValues( mValues );
|
||||
}
|
||||
|
||||
//! Informs the gatherer to immediately stop collecting values
|
||||
void stop()
|
||||
{
|
||||
// be cautious, in case gatherer stops naturally just as we are cancelling it and mFeedback gets deleted
|
||||
mFeedbackMutex.lock();
|
||||
if ( mFeedback )
|
||||
mFeedback->cancel();
|
||||
mFeedbackMutex.unlock();
|
||||
|
||||
mWasCancelled = true;
|
||||
}
|
||||
|
||||
//! Returns true if collection was cancelled before completion
|
||||
bool wasCancelled() const { return mWasCancelled; }
|
||||
|
||||
signals:
|
||||
|
||||
/** Emitted when values have been collected
|
||||
* @param values list of unique matching string values
|
||||
*/
|
||||
void collectedValues( const QStringList& values );
|
||||
|
||||
private:
|
||||
|
||||
QgsVectorLayer* mLayer;
|
||||
int mAttributeIndex;
|
||||
QString mSubstring;
|
||||
QStringList mValues;
|
||||
QgsFeedback* mFeedback;
|
||||
QMutex mFeedbackMutex;
|
||||
bool mWasCancelled;
|
||||
};
|
||||
|
||||
///@endcond
|
||||
|
||||
/** \class QgsFieldValuesLineEdit
|
||||
* \ingroup gui
|
||||
* A line edit with an autocompleter which takes unique values from a vector layer's fields.
|
||||
* The autocompleter is populated from the vector layer in the background to ensure responsive
|
||||
* interaction with the widget.
|
||||
* \note added in QGIS 3.0
|
||||
*/
|
||||
class GUI_EXPORT QgsFieldValuesLineEdit: public QgsFilterLineEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY( QgsVectorLayer* layer READ layer WRITE setLayer NOTIFY layerChanged )
|
||||
Q_PROPERTY( int attributeIndex READ attributeIndex WRITE setAttributeIndex NOTIFY attributeIndexChanged )
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor for QgsFieldValuesLineEdit
|
||||
* @param parent parent widget
|
||||
*/
|
||||
QgsFieldValuesLineEdit( QWidget *parent = nullptr );
|
||||
|
||||
virtual ~QgsFieldValuesLineEdit();
|
||||
|
||||
/** Sets the layer containing the field that values will be shown from.
|
||||
* @param layer vector layer
|
||||
* @see layer()
|
||||
* @see setAttributeIndex()
|
||||
*/
|
||||
void setLayer( QgsVectorLayer* layer );
|
||||
|
||||
/** Returns the layer containing the field that values will be shown from.
|
||||
* @see setLayer()
|
||||
* @see attributeIndex()
|
||||
*/
|
||||
QgsVectorLayer* layer() const { return mLayer; }
|
||||
|
||||
/** Sets the attribute index for the field containing values to show in the widget.
|
||||
* @param index index of attribute
|
||||
* @see attributeIndex()
|
||||
* @see setLayer()
|
||||
*/
|
||||
void setAttributeIndex( int index );
|
||||
|
||||
/** Returns the attribute index for the field containing values shown in the widget.
|
||||
* @see setAttributeIndex()
|
||||
* @see layer()
|
||||
*/
|
||||
int attributeIndex() const { return mAttributeIndex; }
|
||||
|
||||
signals:
|
||||
|
||||
/** Emitted when the layer associated with the widget changes.
|
||||
* @param layer vector layer
|
||||
*/
|
||||
void layerChanged( QgsVectorLayer* layer );
|
||||
|
||||
/** Emitted when the field associated with the widget changes.
|
||||
* @param index new attribute index for field
|
||||
*/
|
||||
void attributeIndexChanged( int index );
|
||||
|
||||
private slots:
|
||||
|
||||
/** Requests that the autocompleter updates its completion list. The update will not occur immediately
|
||||
* but after a preset timeout to avoid multiple updates while a user is quickly typing.
|
||||
*/
|
||||
void requestCompleterUpdate();
|
||||
|
||||
/** Updates the autocompleter list immediately. Calling
|
||||
* this will trigger a background request to the layer to fetch matching unique values.
|
||||
*/
|
||||
void triggerCompleterUpdate();
|
||||
|
||||
/** Updates the values shown in the completer list.
|
||||
* @param values list of string values to show
|
||||
*/
|
||||
void updateCompleter( const QStringList& values );
|
||||
|
||||
/** Called when the gatherer thread is complete, regardless of whether it finished collecting values.
|
||||
* Cleans up the gatherer thread and triggers a new background thread if the widget's text has changed
|
||||
* in the meantime.
|
||||
*/
|
||||
void gathererThreadFinished();
|
||||
|
||||
private:
|
||||
|
||||
QgsVectorLayer* mLayer;
|
||||
int mAttributeIndex;
|
||||
|
||||
//! Will be true when a background update of the completer values is occurring
|
||||
bool mUpdateRequested;
|
||||
|
||||
//! Timer to prevent multiple updates of autocomplete list
|
||||
QTimer mShowPopupTimer;
|
||||
|
||||
//! Background value gatherer thread
|
||||
QgsFieldValuesLineEditValuesGatherer* mGatherer;
|
||||
|
||||
//! Will be set to the latest completion text string which should be requested
|
||||
QString mRequestedCompletionText;
|
||||
|
||||
//! Kicks off the gathering of completer text values for a specified substring
|
||||
void updateCompletionList( const QString& substring );
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //QGSFIELDVALUESLINEEDIT_H
|
Loading…
x
Reference in New Issue
Block a user