Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add API to show empty entries in QgsProviderConnectionComboBox
  • Loading branch information
nyalldawson committed Mar 10, 2020
1 parent 3b40c3a commit 6969db1
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 8 deletions.
15 changes: 15 additions & 0 deletions python/core/auto_generated/qgsproviderconnectionmodel.sip.in
Expand Up @@ -33,6 +33,7 @@ A model containing registered connection names for a specific data provider.
RoleConnectionName,
RoleUri,
RoleConfiguration,
RoleEmpty,
};

explicit QgsProviderConnectionModel( const QString &provider, QObject *parent /TransferThis/ = 0 );
Expand All @@ -45,6 +46,20 @@ Constructor for QgsProviderConnectionModel, for the specified ``provider``.
in order for the model to work correctly.
%End

void setAllowEmptyConnection( bool allowEmpty );
%Docstring
Sets whether an optional empty connection ("not set") option is present in the model.

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

bool allowEmptyConnection() const;
%Docstring
Returns ``True`` if the model allows the empty connection ("not set") choice.

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

virtual QModelIndex parent( const QModelIndex &child ) const;

virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
Expand Down
15 changes: 15 additions & 0 deletions python/gui/auto_generated/qgsproviderconnectioncombobox.sip.in
Expand Up @@ -10,6 +10,7 @@




class QgsProviderConnectionComboBox : QComboBox
{
%Docstring
Expand Down Expand Up @@ -38,6 +39,20 @@ Constructor for QgsProviderConnectionComboBox, for the specified ``provider``.
in order for the model to work correctly.
%End

void setAllowEmptyConnection( bool allowEmpty );
%Docstring
Sets whether an optional empty connection ("not set") option is present in the combobox.

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

bool allowEmptyConnection() const;
%Docstring
Returns ``True`` if the combobox allows the empty connection ("not set") choice.

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

QString currentConnection() const;
%Docstring
Returns the name of the current connection selected in the combo box.
Expand Down
39 changes: 35 additions & 4 deletions src/core/qgsproviderconnectionmodel.cpp
Expand Up @@ -29,20 +29,39 @@ QgsProviderConnectionModel::QgsProviderConnectionModel( const QString &provider,
mConnections = mMetadata->connections().keys();
}

void QgsProviderConnectionModel::setAllowEmptyConnection( bool allowEmpty )
{
if ( allowEmpty == mAllowEmpty )
return;

if ( allowEmpty )
{
beginInsertRows( QModelIndex(), 0, 0 );
mAllowEmpty = true;
endInsertRows();
}
else
{
beginRemoveRows( QModelIndex(), 0, 0 );
mAllowEmpty = false;
endRemoveRows();
}
}

void QgsProviderConnectionModel::removeConnection( const QString &connection )
{
int index = mConnections.indexOf( connection );
if ( index < 0 )
return;

beginRemoveRows( QModelIndex(), index, index );
beginRemoveRows( QModelIndex(), index + ( mAllowEmpty ? 1 : 0 ), index + ( mAllowEmpty ? 1 : 0 ) );
mConnections.removeAt( index );
endRemoveRows();
}

void QgsProviderConnectionModel::addConnection( const QString &connection )
{
beginInsertRows( QModelIndex(), mConnections.count(), mConnections.count() );
beginInsertRows( QModelIndex(), mConnections.count() + ( mAllowEmpty ? 1 : 0 ), mConnections.count() + ( mAllowEmpty ? 1 : 0 ) );
mConnections.append( connection );
endInsertRows();
}
Expand All @@ -59,7 +78,7 @@ int QgsProviderConnectionModel::rowCount( const QModelIndex &parent ) const
if ( parent.isValid() )
return 0;

return mConnections.count();
return mConnections.count() + ( mAllowEmpty ? 1 : 0 );
}

int QgsProviderConnectionModel::columnCount( const QModelIndex &parent ) const
Expand All @@ -74,10 +93,22 @@ QVariant QgsProviderConnectionModel::data( const QModelIndex &index, int role )
if ( !index.isValid() )
return QVariant();

const QString connectionName = mConnections.value( index.row() );
if ( index.row() == 0 && mAllowEmpty )
{
if ( role == RoleEmpty )
return true;

return QVariant();
}

const QString connectionName = mConnections.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
switch ( role )
{
case RoleEmpty:
return false;

case Qt::DisplayRole:
case Qt::EditRole:
case RoleConnectionName:
{
return connectionName;
Expand Down
14 changes: 14 additions & 0 deletions src/core/qgsproviderconnectionmodel.h
Expand Up @@ -47,6 +47,7 @@ class CORE_EXPORT QgsProviderConnectionModel : public QAbstractItemModel
RoleConnectionName = Qt::UserRole, //!< Connection name
RoleUri, //!< Connection URI string
RoleConfiguration, //!< Connection configuration variant map
RoleEmpty, //!< Entry is an empty entry
};

/**
Expand All @@ -57,6 +58,18 @@ class CORE_EXPORT QgsProviderConnectionModel : public QAbstractItemModel
*/
explicit QgsProviderConnectionModel( const QString &provider, QObject *parent SIP_TRANSFERTHIS = nullptr );

/**
* Sets whether an optional empty connection ("not set") option is present in the model.
* \see allowEmptyConnection()
*/
void setAllowEmptyConnection( bool allowEmpty );

/**
* Returns TRUE if the model allows the empty connection ("not set") choice.
* \see setAllowEmptyConnection()
*/
bool allowEmptyConnection() const { return mAllowEmpty; }

// QAbstractItemModel interface
QModelIndex parent( const QModelIndex &child ) const override;
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
Expand All @@ -71,6 +84,7 @@ class CORE_EXPORT QgsProviderConnectionModel : public QAbstractItemModel
QString mProvider;
QgsProviderMetadata *mMetadata = nullptr;
QStringList mConnections;
bool mAllowEmpty = false;
};

#endif // QGSPROVIDERCONNECTIONMODEL_H
44 changes: 41 additions & 3 deletions src/gui/qgsproviderconnectioncombobox.cpp
Expand Up @@ -21,7 +21,7 @@ QgsProviderConnectionComboBox::QgsProviderConnectionComboBox( const QString &pro
{
mModel = new QgsProviderConnectionModel( provider, this );

mSortModel = new QSortFilterProxyModel( this );
mSortModel = new QgsProviderConnectionComboBoxSortModel( this );
mSortModel->setSourceModel( mModel );
mSortModel->setSortRole( Qt::DisplayRole );
mSortModel->setSortLocaleAware( true );
Expand All @@ -36,14 +36,27 @@ QgsProviderConnectionComboBox::QgsProviderConnectionComboBox( const QString &pro
connect( mSortModel, &QAbstractItemModel::rowsRemoved, this, &QgsProviderConnectionComboBox::rowsChanged );
}

void QgsProviderConnectionComboBox::setAllowEmptyConnection( bool allowEmpty )
{
mModel->setAllowEmptyConnection( allowEmpty );
}

bool QgsProviderConnectionComboBox::allowEmptyConnection() const
{
return mModel->allowEmptyConnection();
}

void QgsProviderConnectionComboBox::setConnection( const QString &connection )
{
if ( connection == currentConnection() )
return;

if ( connection.isEmpty() )
{
setCurrentIndex( -1 );
if ( mModel->allowEmptyConnection() )
setCurrentIndex( 0 );
else
setCurrentIndex( -1 );
emit connectionChanged( QString() );
return;
}
Expand Down Expand Up @@ -93,7 +106,7 @@ void QgsProviderConnectionComboBox::indexChanged( int i )

void QgsProviderConnectionComboBox::rowsChanged()
{
if ( count() == 1 )
if ( count() == 1 || ( mModel->allowEmptyConnection() && count() == 2 && currentIndex() == 1 ) )
{
//currently selected connection item has changed
emit connectionChanged( currentConnection() );
Expand All @@ -103,3 +116,28 @@ void QgsProviderConnectionComboBox::rowsChanged()
emit connectionChanged( QString() );
}
}


///@cond PRIVATE
QgsProviderConnectionComboBoxSortModel::QgsProviderConnectionComboBoxSortModel( QObject *parent )
: QSortFilterProxyModel( parent )
{

}

bool QgsProviderConnectionComboBoxSortModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
{
// empty row is always first
if ( sourceModel()->data( left, QgsProviderConnectionModel::RoleEmpty ).toBool() )
return true;
else if ( sourceModel()->data( right, QgsProviderConnectionModel::RoleEmpty ).toBool() )
return false;

// default mode is alphabetical order
QString leftStr = sourceModel()->data( left ).toString();
QString rightStr = sourceModel()->data( right ).toString();
return QString::localeAwareCompare( leftStr, rightStr ) < 0;
}


///@endcond
27 changes: 26 additions & 1 deletion src/gui/qgsproviderconnectioncombobox.h
Expand Up @@ -20,9 +20,22 @@

#include "qgis_gui.h"
#include "qgis_sip.h"
#include <QSortFilterProxyModel>

class QgsProviderConnectionModel;
class QSortFilterProxyModel;

///@cond PRIVATE
#ifndef SIP_RUN
class GUI_EXPORT QgsProviderConnectionComboBoxSortModel: public QSortFilterProxyModel
{
public:
explicit QgsProviderConnectionComboBoxSortModel( QObject *parent = nullptr );
protected:
bool lessThan( const QModelIndex &source_left, const QModelIndex &source_right ) const override;

};
#endif
///@endcond

/**
* \ingroup gui
Expand All @@ -47,6 +60,18 @@ class GUI_EXPORT QgsProviderConnectionComboBox : public QComboBox
*/
explicit QgsProviderConnectionComboBox( const QString &provider, QWidget *parent SIP_TRANSFERTHIS = nullptr );

/**
* Sets whether an optional empty connection ("not set") option is present in the combobox.
* \see allowEmptyConnection()
*/
void setAllowEmptyConnection( bool allowEmpty );

/**
* Returns TRUE if the combobox allows the empty connection ("not set") choice.
* \see setAllowEmptyConnection()
*/
bool allowEmptyConnection() const;

/**
* Returns the name of the current connection selected in the combo box.
*/
Expand Down
58 changes: 58 additions & 0 deletions tests/src/python/test_qgsproviderconnectioncombobox.py
Expand Up @@ -114,6 +114,64 @@ def testCombo(self):
self.assertEqual(len(spy), 4)
self.assertEqual(spy[-1][0], 'aaa_qgis_test2')

md.deleteConnection('aaa_qgis_test2')

def testComboWithEmpty(self):
""" test combobox functionality with empty entry """
m = QgsProviderConnectionComboBox('ogr')
m.setAllowEmptyConnection(True)
spy = QSignalSpy(m.connectionChanged)
self.assertEqual(m.count(), 1)
self.assertFalse(m.currentConnection())
self.assertFalse(m.currentConnectionUri())

md = QgsProviderRegistry.instance().providerMetadata('ogr')
conn = md.createConnection(self.gpkg_path, {})
md.saveConnection(conn, 'qgis_test1')

self.assertEqual(m.count(), 2)
self.assertFalse(m.itemText(0))
self.assertEqual(m.itemText(1), 'qgis_test1')
self.assertFalse(m.currentConnection())
self.assertFalse(m.currentConnectionUri())
self.assertEqual(len(spy), 0)

m.setConnection('qgis_test1')
self.assertEqual(len(spy), 1)
m.setConnection('')
self.assertFalse(m.currentConnection())
self.assertFalse(m.currentConnectionUri())
self.assertEqual(len(spy), 2)
self.assertFalse(spy[-1][0])
m.setConnection('')
self.assertEqual(m.currentIndex(), 0)
self.assertEqual(len(spy), 2)
self.assertFalse(m.currentConnection())
self.assertFalse(m.currentConnectionUri())

m.setConnection('qgis_test1')
self.assertEqual(len(spy), 3)
self.assertEqual(m.currentConnection(), 'qgis_test1')
self.assertEqual(m.currentConnectionUri(), self.gpkg_path)
self.assertEqual(spy[-1][0], 'qgis_test1')

conn2 = md.createConnection(self.gpkg_path2, {})
md.saveConnection(conn2, 'aaa_qgis_test2')
self.assertEqual(m.count(), 3)
self.assertFalse(m.itemText(0))
self.assertEqual(m.itemText(1), 'aaa_qgis_test2')
self.assertEqual(m.itemText(2), 'qgis_test1')

self.assertEqual(m.currentConnection(), 'qgis_test1')
self.assertEqual(m.currentConnectionUri(), self.gpkg_path)
self.assertEqual(len(spy), 3)

md.deleteConnection('qgis_test1')
self.assertEqual(m.currentConnection(), 'aaa_qgis_test2')
self.assertEqual(m.currentConnectionUri(), self.gpkg_path2)
self.assertEqual(len(spy), 4)
self.assertEqual(spy[-1][0], 'aaa_qgis_test2')


if __name__ == '__main__':
unittest.main()

0 comments on commit 6969db1

Please sign in to comment.