Skip to content

Commit

Permalink
Add API to QgsFieldModel/QgsFieldComboBox to manually set the fields
Browse files Browse the repository at this point in the history
to show in the widget (i.e. when no layer is available)

(cherry picked from commit 9a6cb46)
  • Loading branch information
nyalldawson committed Feb 27, 2020
1 parent cfe5f4b commit 58f3174
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 2 deletions.
23 changes: 23 additions & 0 deletions python/core/auto_generated/qgsfieldmodel.sip.in
Expand Up @@ -134,6 +134,29 @@ Returns a HTML formatted tooltip string for a ``field``, containing details
like the field name, alias and type.

.. versionadded:: 3.0
%End

void setFields( const QgsFields &fields );
%Docstring
Manually sets the ``fields`` to use for the model.

This method should only be used when the model ISN'T associated with a layer()
and needs to show the fields from an arbitrary field collection instead. Calling
setFields() will automatically clear any existing layer().

.. seealso:: :py:func:`fields`

.. versionadded:: 3.14
%End

QgsFields fields() const;
%Docstring
Returns the fields currently shown in the model.

This will either be fields from the associated layer() or the fields
manually set by a call to setFields().

.. versionadded:: 3.14
%End

public slots:
Expand Down
23 changes: 23 additions & 0 deletions python/gui/auto_generated/qgsfieldcombobox.sip.in
Expand Up @@ -72,6 +72,29 @@ Returns the currently selected field
Returns the layer currently associated with the combobox.

.. seealso:: :py:func:`setLayer`
%End

void setFields( const QgsFields &fields );
%Docstring
Manually sets the ``fields`` to use for the combo box.

This method should only be used when the combo box ISN'T associated with a layer()
and needs to show the fields from an arbitrary field collection instead. Calling
setFields() will automatically clear any existing layer().

.. seealso:: :py:func:`fields`

.. versionadded:: 3.14
%End

QgsFields fields() const;
%Docstring
Returns the fields currently shown in the combobox.

This will either be fields from the associated layer() or the fields
manually set by a call to setFields().

.. versionadded:: 3.14
%End

signals:
Expand Down
17 changes: 17 additions & 0 deletions src/core/qgsfieldmodel.cpp
Expand Up @@ -411,6 +411,10 @@ QVariant QgsFieldModel::data( const QModelIndex &index, int role ) const
{
return mLayer->attributeDisplayName( index.row() - fieldOffset );
}
else if ( mFields.size() > index.row() - fieldOffset )
{
return mFields.field( index.row() - fieldOffset ).displayName();
}
else
return QVariant();
}
Expand Down Expand Up @@ -490,3 +494,16 @@ QString QgsFieldModel::fieldToolTip( const QgsField &field )
toolTip += QStringLiteral( "<p>%1</p>" ).arg( typeString );
return toolTip;
}

void QgsFieldModel::setFields( const QgsFields &fields )
{
setLayer( nullptr );
beginResetModel();
mFields = fields;
endResetModel();
}

QgsFields QgsFieldModel::fields() const
{
return mFields;
}
22 changes: 22 additions & 0 deletions src/core/qgsfieldmodel.h
Expand Up @@ -137,6 +137,28 @@ class CORE_EXPORT QgsFieldModel : public QAbstractItemModel
*/
static QString fieldToolTip( const QgsField &field );

/**
* Manually sets the \a fields to use for the model.
*
* This method should only be used when the model ISN'T associated with a layer()
* and needs to show the fields from an arbitrary field collection instead. Calling
* setFields() will automatically clear any existing layer().
*
* \see fields()
* \since QGIS 3.14
*/
void setFields( const QgsFields &fields );

/**
* Returns the fields currently shown in the model.
*
* This will either be fields from the associated layer() or the fields
* manually set by a call to setFields().
*
* \since QGIS 3.14
*/
QgsFields fields() const;

public slots:

/**
Expand Down
10 changes: 10 additions & 0 deletions src/gui/qgsfieldcombobox.cpp
Expand Up @@ -54,6 +54,16 @@ QgsVectorLayer *QgsFieldComboBox::layer() const
return mFieldProxyModel->sourceFieldModel()->layer();
}

void QgsFieldComboBox::setFields( const QgsFields &fields )
{
mFieldProxyModel->sourceFieldModel()->setFields( fields );
}

QgsFields QgsFieldComboBox::fields() const
{
return mFieldProxyModel->sourceFieldModel()->fields();
}

void QgsFieldComboBox::setField( const QString &fieldName )
{
const QString prevField = currentField();
Expand Down
23 changes: 23 additions & 0 deletions src/gui/qgsfieldcombobox.h
Expand Up @@ -25,6 +25,7 @@

class QgsMapLayer;
class QgsVectorLayer;
class QgsFields;

/**
* \ingroup gui
Expand Down Expand Up @@ -77,6 +78,28 @@ class GUI_EXPORT QgsFieldComboBox : public QComboBox
*/
QgsVectorLayer *layer() const;

/**
* Manually sets the \a fields to use for the combo box.
*
* This method should only be used when the combo box ISN'T associated with a layer()
* and needs to show the fields from an arbitrary field collection instead. Calling
* setFields() will automatically clear any existing layer().
*
* \see fields()
* \since QGIS 3.14
*/
void setFields( const QgsFields &fields );

/**
* Returns the fields currently shown in the combobox.
*
* This will either be fields from the associated layer() or the fields
* manually set by a call to setFields().
*
* \since QGIS 3.14
*/
QgsFields fields() const;

signals:
//! Emitted when the currently selected field changes.
void fieldChanged( const QString &fieldName );
Expand Down
19 changes: 18 additions & 1 deletion tests/src/python/test_qgsfieldcombobox.py
Expand Up @@ -12,7 +12,7 @@

import qgis # NOQA

from qgis.core import QgsFields, QgsVectorLayer, QgsFieldProxyModel
from qgis.core import QgsFields, QgsVectorLayer, QgsFieldProxyModel, QgsField, QgsFieldModel
from qgis.gui import QgsFieldComboBox
from qgis.PyQt.QtCore import QVariant, Qt
from qgis.PyQt.QtTest import QSignalSpy
Expand Down Expand Up @@ -47,6 +47,13 @@ def testGettersSetters(self):
w.setField('fldint')
self.assertEqual(w.currentField(), 'fldint')

fields = QgsFields()
fields.append(QgsField('test1', QVariant.String))
fields.append(QgsField('test2', QVariant.String))
w.setFields(fields)
self.assertIsNone(w.layer())
self.assertEqual(w.fields(), fields)

def testFilter(self):
""" test setting field with filter """
l = create_layer()
Expand Down Expand Up @@ -80,6 +87,16 @@ def testSignals(self):
self.assertEqual(len(spy), 3)
self.assertEqual(spy[-1][0], None)

def testManualFields(self):
fields = QgsFields()
fields.append(QgsField('test1', QVariant.String))
fields.append(QgsField('test2', QVariant.String))
w = QgsFieldComboBox()
w.setFields(fields)
self.assertEqual(w.count(), 2)
self.assertEqual(w.itemText(0), 'test1')
self.assertEqual(w.itemText(1), 'test2')


if __name__ == '__main__':
unittest.main()
19 changes: 18 additions & 1 deletion tests/src/python/test_qgsfieldmodel.py
Expand Up @@ -20,7 +20,7 @@
QgsEditorWidgetSetup,
QgsProject,
QgsVectorLayerJoinInfo)
from qgis.PyQt.QtCore import QVariant, Qt
from qgis.PyQt.QtCore import QVariant, Qt, QModelIndex

from qgis.testing import start_app, unittest

Expand Down Expand Up @@ -64,6 +64,13 @@ def testGettersSetters(self):
m.setAllowEmptyFieldName(False)
self.assertFalse(m.allowEmptyFieldName())

fields = QgsFields()
fields.append(QgsField('test1', QVariant.String))
fields.append(QgsField('test2', QVariant.String))
m.setFields(fields)
self.assertIsNone(m.layer())
self.assertEqual(m.fields(), fields)

def testIndexFromName(self):
l, m = create_model()
i = m.indexFromName('fldtxt')
Expand Down Expand Up @@ -251,6 +258,16 @@ def testDisplayRole(self):
m.setAllowEmptyFieldName(True)
self.assertFalse(m.data(m.indexFromName(None), Qt.DisplayRole))

def testManualFields(self):
_, m = create_model()
fields = QgsFields()
fields.append(QgsField('f1', QVariant.String))
fields.append(QgsField('f2', QVariant.String))
m.setFields(fields)
self.assertEqual(m.rowCount(), 2)
self.assertEqual(m.data(m.index(0, 0, QModelIndex()), Qt.DisplayRole), 'f1')
self.assertEqual(m.data(m.index(1, 0, QModelIndex()), Qt.DisplayRole), 'f2')

def testEditorWidgetTypeRole(self):
l, m = create_model()
self.assertEqual(m.data(m.indexFromName('fldtxt'), QgsFieldModel.EditorWidgetType), 'Hidden')
Expand Down

0 comments on commit 58f3174

Please sign in to comment.