Skip to content
Permalink
Browse files

[quick] Fix slow value relation widget

If value relation widget in qgis_quick was populated with lots of items,
it was getting very slow - this was due to the use of ListModel QML item.
The fix introduces a c++ based model which is fast to populate and look up
values in the list.
  • Loading branch information
wonder-sk committed Mar 11, 2020
1 parent 5552e12 commit 31ee21d814d4c84206493d9bafc43a6f63bac1f2
@@ -18,6 +18,7 @@ SET(QGIS_QUICK_GUI_MOC_HDRS
qgsquickscalebarkit.h
qgsquicksimulatedpositionsource.h
qgsquickutils.h
qgsquickvaluerelationlistmodel.h
)

SET(QGIS_QUICK_GUI_HDRS
@@ -43,6 +44,7 @@ SET(QGIS_QUICK_GUI_SRC
qgsquickscalebarkit.cpp
qgsquicksimulatedpositionsource.cpp
qgsquickutils.cpp
qgsquickvaluerelationlistmodel.cpp
)

INCLUDE_DIRECTORIES(
@@ -44,7 +44,7 @@ ComboBox {
delegate: ItemDelegate {
width: comboBox.width
height: comboBox.height * 0.8
text: modelData
text: model.display
font.weight: comboBox.currentIndex === index ? Font.DemiBold : Font.Normal
font.pixelSize: comboStyle.fontPixelSize
highlighted: comboBox.highlightedIndex == index
@@ -41,7 +41,7 @@ Item {
property var currentValue: value

comboStyle: customStyle.fields
textRole: 'text'
textRole: 'display'
height: parent.height
model: ListModel {
id: listModel
@@ -57,7 +57,7 @@ Item {
{
var currentMap = config['map'][i]
var currentKey = Object.keys(currentMap)[0]
listModel.append( { text: currentKey } )
listModel.append( { display: currentKey } )
reverseConfig[currentMap[currentKey]] = currentKey;
}
}
@@ -67,7 +67,7 @@ Item {
var currentMap = config['map'].length ? config['map'][currentIndex] : config['map']
var currentKey = Object.keys(currentMap)[0]
for(var key in config['map']) {
listModel.append( { text: key } )
listModel.append( { display: key } )
reverseConfig[config['map'][key]] = key;
}
}
@@ -36,42 +36,30 @@ Item {
}

QgsQuick.EditorWidgetComboBox {
// Value relation cache map
property var currentMap
// Reversed to currentMap. It is used to find key (currentValue) according value (currentText)
property var reversedMap: ({})

property var currentValue: value

comboStyle: customStyle.fields
textRole: 'text'
textRole: 'display'
height: parent.height
model: ListModel {
id: listModel

model: QgsQuick.ValueRelationListModel {
id: vrModel
}

Component.onCompleted: {
currentMap = QgsQuick.Utils.createValueRelationCache(config)
var valueInKeys = false
var keys = Object.keys(currentMap)
for(var i=0; i< keys.length; i++)
{
var currentKey = keys[i]
if (value == currentKey) valueInKeys = true
var valueText = currentMap[currentKey]
listModel.append( { text: valueText } )
reversedMap[valueText] = currentKey;
}
model = listModel
currentIndex = valueInKeys ? find(currentMap[value]) : -1
vrModel.populate(config)
currentIndex = vrModel.rowForKey(value);
}

onCurrentTextChanged: {
valueChanged(reversedMap[currentText], false)
// Called when user makes selection in the combo box
onCurrentIndexChanged: {
valueChanged(vrModel.keyForRow(currentIndex), false)
}

// Workaround to get a signal when the value has changed
// Called when the same form is used for a different feature
onCurrentValueChanged: {
currentIndex = currentMap ? find(currentMap[value]) : -1
currentIndex = vrModel.rowForKey(value);
}

}
@@ -46,6 +46,7 @@
#include "qgsquickscalebarkit.h"
#include "qgsquicksubmodel.h"
#include "qgsquickutils.h"
#include "qgsquickvaluerelationlistmodel.h"

static QObject *_utilsProvider( QQmlEngine *engine, QJSEngine *scriptEngine )
{
@@ -87,6 +88,7 @@ void QgsQuickPlugin::registerTypes( const char *uri )
qmlRegisterType< QgsQuickScaleBarKit >( uri, 0, 1, "ScaleBarKit" );
qmlRegisterType< QgsQuickSubModel >( uri, 0, 1, "SubModel" );
qmlRegisterType< QgsVectorLayer >( uri, 0, 1, "VectorLayer" );
qmlRegisterType< QgsQuickValueRelationListModel > ( uri, 0, 1, "ValueRelationListModel" );

qmlRegisterSingletonType< QgsQuickUtils >( uri, 0, 1, "Utils", _utilsProvider );
}
@@ -0,0 +1,72 @@
/***************************************************************************
qgsquickvaluerelationlistmodel.cpp
--------------------------------------
Date : March 2020
Copyright : (C) 2020 by Martin Dobias
Email : wonder dot sk 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 "qgsquickvaluerelationlistmodel.h"

#include "qgslogger.h"


QgsQuickValueRelationListModel::QgsQuickValueRelationListModel( QObject *parent )
: QAbstractListModel( parent )
{
}

void QgsQuickValueRelationListModel::populate( const QVariantMap &config, const QgsFeature &formFeature )
{
beginResetModel();
mCache = QgsValueRelationFieldFormatter::createCache( config, formFeature );
endResetModel();
}

QVariant QgsQuickValueRelationListModel::keyForRow( int row ) const
{
if ( row < 0 || row >= mCache.count() )
{
QgsDebugMsg( "keyForRow: access outside of range " + QString::number( row ) );
return QVariant();
}
return mCache[row].key;
}

int QgsQuickValueRelationListModel::rowForKey( const QVariant &key ) const
{
for ( int i = 0; i < mCache.count(); ++i )
{
if ( mCache[i].key == key )
return i;
}
QgsDebugMsg( "rowForKey: key not found: " + key.toString() );
return -1;
}

int QgsQuickValueRelationListModel::rowCount( const QModelIndex & ) const
{
return mCache.count();
}

QVariant QgsQuickValueRelationListModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() )
return QVariant();

int row = index.row();
if ( row < 0 || row >= mCache.count() )
return QVariant();

if ( role == Qt::DisplayRole )
return mCache[row].value;

return QVariant();
}
@@ -0,0 +1,57 @@
/***************************************************************************
qgsquickvaluerelationlistmodel.h
--------------------------------------
Date : March 2020
Copyright : (C) 2020 by Martin Dobias
Email : wonder dot sk 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 QGSQUICKVALUERELATIONLISTMODEL_H
#define QGSQUICKVALUERELATIONLISTMODEL_H

#include "qgis_quick.h"

#include <QAbstractListModel>

#include "qgsvaluerelationfieldformatter.h"

/**
* \ingroup quick
* List model for combo box in Value Relation widget.
* The model keeps a list of key-value pairs fetched from a vector layer. "Values" are the human readable
* descriptions (QString), "keys" are unique identifiers of items (QVariant).
*
* \note QML Type: ValueRelationListModel
*
* \since QGIS 3.14
*/
class QUICK_EXPORT QgsQuickValueRelationListModel : public QAbstractListModel
{
Q_OBJECT
public:
//! Construct an empty list model
QgsQuickValueRelationListModel( QObject *parent = nullptr );

//! Populate the model from vector layer's widget configuration
Q_INVOKABLE void populate( const QVariantMap &config, const QgsFeature &formFeature = QgsFeature() );

//! Returns key for the given rown number (invalid variant if outside of the valid range)
Q_INVOKABLE QVariant keyForRow( int row ) const;
//! Returns row number
Q_INVOKABLE int rowForKey( const QVariant &key ) const;

int rowCount( const QModelIndex & ) const override;
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;

private:
QgsValueRelationFieldFormatter::ValueRelationCache mCache;
};

#endif // QGSQUICKVALUERELATIONLISTMODEL_H

0 comments on commit 31ee21d

Please sign in to comment.
You can’t perform that action at this time.