Skip to content

Commit

Permalink
Add a QgsLocatorModel subclass which automatically sets up
Browse files Browse the repository at this point in the history
required connections to a QgsLocator

Use this QgsLocatorModel subclass when you want the connections
between a QgsLocator and the model to be automatically created
for you. If more flexibility in model behavior is required,
use the base QgsLocatorModel class instead and setup the
connections manually.
  • Loading branch information
nyalldawson committed Sep 3, 2017
1 parent a8e1d33 commit 2683094
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 5 deletions.
69 changes: 67 additions & 2 deletions python/core/locator/qgslocatormodel.sip
Expand Up @@ -14,6 +14,10 @@ class QgsLocatorModel : QAbstractTableModel
{
%Docstring
An abstract list model for displaying the results of locator searches.

Note that this class should generally be used with a QgsLocatorProxyModel
in order to ensure correct sorting of results by priority and match level.

.. versionadded:: 3.0
%End

Expand All @@ -31,7 +35,7 @@ class QgsLocatorModel : QAbstractTableModel
ResultFilterNameRole,
};

QgsLocatorModel( QObject *parent = 0 );
QgsLocatorModel( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsLocatorModel.
%End
Expand Down Expand Up @@ -68,6 +72,64 @@ class QgsLocatorModel : QAbstractTableModel

};

class QgsLocatorAutomaticModel : QgsLocatorModel
{
%Docstring
A QgsLocatorModel which has is associated directly with a
QgsLocator, and is automatically populated with results
from locator searches.

Use this QgsLocatorModel subclass when you want the connections
between a QgsLocator and the model to be automatically created
for you. If more flexibility in model behavior is required,
use the base QgsLocatorModel class instead and setup the
connections manually.

Note that this class should generally be used with a QgsLocatorProxyModel
in order to ensure correct sorting of results by priority and match level.

.. versionadded:: 3.0
%End

%TypeHeaderCode
#include "qgslocatormodel.h"
%End
public:

explicit QgsLocatorAutomaticModel( QgsLocator *locator /TransferThis/ );
%Docstring
Constructor for QgsLocatorAutomaticModel, linked with the specified ``locator``.

The ``locator`` is used as the model's parent.
%End

QgsLocator *locator();
%Docstring
Returns a pointer to the locator utilized by this model.
:rtype: QgsLocator
%End

void search( const QString &string );
%Docstring
Enqueues a search for a specified ``string`` within the model.

Note that the search may not begin immediately if an existing search request
is still running. In this case the existing search must be completely
terminated before the new search can begin. The model handles this
situation automatically, and will trigger a search for the new
search string as soon as possible.
%End

virtual QgsLocatorContext createContext();
%Docstring
Returns a new locator context for searches. The default implementation
returns a default constructed QgsLocatorContext. Subclasses can override
this method to implement custom context creation logic.
:rtype: QgsLocatorContext
%End

};

class QgsLocatorProxyModel : QSortFilterProxyModel
{
%Docstring
Expand All @@ -81,7 +143,10 @@ class QgsLocatorProxyModel : QSortFilterProxyModel
%End
public:

explicit QgsLocatorProxyModel( QObject *parent = 0 );
explicit QgsLocatorProxyModel( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsLocatorProxyModel, with the specified ``parent`` object.
%End
virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const;

};
Expand Down
60 changes: 60 additions & 0 deletions src/core/locator/qgslocatormodel.cpp
Expand Up @@ -193,6 +193,65 @@ void QgsLocatorModel::addResult( const QgsLocatorResult &result )
mDeferredClear = false;
}


//
// QgsLocatorAutomaticModel
//

QgsLocatorAutomaticModel::QgsLocatorAutomaticModel( QgsLocator *locator )
: QgsLocatorModel( locator )
, mLocator( locator )
{
Q_ASSERT( mLocator );
connect( mLocator, &QgsLocator::foundResult, this, &QgsLocatorAutomaticModel::addResult );
connect( mLocator, &QgsLocator::finished, this, &QgsLocatorAutomaticModel::searchFinished );
}

QgsLocator *QgsLocatorAutomaticModel::locator()
{
return mLocator;
}

void QgsLocatorAutomaticModel::search( const QString &string )
{
if ( mLocator->isRunning() )
{
// can't do anything while a query is running, and can't block
// here waiting for the current query to cancel
// so we queue up this string until cancel has happened
mLocator->cancelWithoutBlocking();
mNextRequestedString = string;
mHasQueuedRequest = true;
return;
}
else
{
deferredClear();
mLocator->fetchResults( string, createContext() );
}
}

QgsLocatorContext QgsLocatorAutomaticModel::createContext()
{
return QgsLocatorContext();
}

void QgsLocatorAutomaticModel::searchFinished()
{
if ( mHasQueuedRequest )
{
// a queued request was waiting for this - run the queued search now
QString nextSearch = mNextRequestedString;
mNextRequestedString.clear();
mHasQueuedRequest = false;
search( nextSearch );
}
}





//
// QgsLocatorProxyModel
//
Expand Down Expand Up @@ -238,3 +297,4 @@ bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex
return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
}


77 changes: 75 additions & 2 deletions src/core/locator/qgslocatormodel.h
Expand Up @@ -33,6 +33,10 @@ class QgsLocatorProxyModel;
* \class QgsLocatorModel
* \ingroup core
* An abstract list model for displaying the results of locator searches.
*
* Note that this class should generally be used with a QgsLocatorProxyModel
* in order to ensure correct sorting of results by priority and match level.
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLocatorModel : public QAbstractTableModel
Expand All @@ -54,7 +58,7 @@ class CORE_EXPORT QgsLocatorModel : public QAbstractTableModel
/**
* Constructor for QgsLocatorModel.
*/
QgsLocatorModel( QObject *parent = nullptr );
QgsLocatorModel( QObject *parent SIP_TRANSFERTHIS = nullptr );

/**
* Resets the model and clears all existing results.
Expand Down Expand Up @@ -103,6 +107,72 @@ class CORE_EXPORT QgsLocatorModel : public QAbstractTableModel
QTimer mDeferredClearTimer;
};

/**
* \class QgsLocatorAutomaticModel
* \ingroup core
* A QgsLocatorModel which has is associated directly with a
* QgsLocator, and is automatically populated with results
* from locator searches.
*
* Use this QgsLocatorModel subclass when you want the connections
* between a QgsLocator and the model to be automatically created
* for you. If more flexibility in model behavior is required,
* use the base QgsLocatorModel class instead and setup the
* connections manually.
*
* Note that this class should generally be used with a QgsLocatorProxyModel
* in order to ensure correct sorting of results by priority and match level.
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLocatorAutomaticModel : public QgsLocatorModel
{
Q_OBJECT

public:

/**
* Constructor for QgsLocatorAutomaticModel, linked with the specified \a locator.
*
* The \a locator is used as the model's parent.
*/
explicit QgsLocatorAutomaticModel( QgsLocator *locator SIP_TRANSFERTHIS );

/**
* Returns a pointer to the locator utilized by this model.
*/
QgsLocator *locator();

/**
* Enqueues a search for a specified \a string within the model.
*
* Note that the search may not begin immediately if an existing search request
* is still running. In this case the existing search must be completely
* terminated before the new search can begin. The model handles this
* situation automatically, and will trigger a search for the new
* search string as soon as possible.
*/
void search( const QString &string );

/**
* Returns a new locator context for searches. The default implementation
* returns a default constructed QgsLocatorContext. Subclasses can override
* this method to implement custom context creation logic.
*/
virtual QgsLocatorContext createContext();

private slots:

void searchFinished();

private:

QgsLocator *mLocator = nullptr;

QString mNextRequestedString;
bool mHasQueuedRequest = false;
};

/**
* \class QgsLocatorProxyModel
* \ingroup core
Expand All @@ -116,7 +186,10 @@ class CORE_EXPORT QgsLocatorProxyModel : public QSortFilterProxyModel

public:

explicit QgsLocatorProxyModel( QObject *parent = nullptr );
/**
* Constructor for QgsLocatorProxyModel, with the specified \a parent object.
*/
explicit QgsLocatorProxyModel( QObject *parent SIP_TRANSFERTHIS = nullptr );
bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override;
};

Expand Down
48 changes: 47 additions & 1 deletion tests/src/python/test_qgslocator.py
Expand Up @@ -19,7 +19,8 @@
QgsLocatorFilter,
QgsLocatorContext,
QgsLocatorResult,
QgsLocatorModel)
QgsLocatorModel,
QgsLocatorAutomaticModel)
from qgis.PyQt.QtCore import QVariant, pyqtSignal, QCoreApplication
from time import sleep
from qgis.testing import start_app, unittest
Expand Down Expand Up @@ -210,6 +211,51 @@ def testModel(self):
QCoreApplication.processEvents()
self.assertEqual(m.rowCount(), 0)

def testAutoModel(self):
"""
Test automatic model, QgsLocatorAutomaticModel - should be no need
for any manual connections
"""
l = QgsLocator()
m = QgsLocatorAutomaticModel(l)

filter_a = test_filter('a')
l.registerFilter(filter_a)

m.search('a')

for i in range(100):
sleep(0.002)
QCoreApplication.processEvents()

# 4 results - one is locator name
self.assertEqual(m.rowCount(), 4)
self.assertEqual(m.data(m.index(0, 0)), 'test')
self.assertEqual(m.data(m.index(0, 0), QgsLocatorModel.ResultTypeRole), 0)
self.assertEqual(m.data(m.index(0, 0), QgsLocatorModel.ResultFilterNameRole), 'test')
self.assertEqual(m.data(m.index(1, 0)), 'a0')
self.assertEqual(m.data(m.index(1, 0), QgsLocatorModel.ResultTypeRole), 1)
self.assertEqual(m.data(m.index(1, 0), QgsLocatorModel.ResultFilterNameRole), 'test')
self.assertEqual(m.data(m.index(2, 0)), 'a1')
self.assertEqual(m.data(m.index(2, 0), QgsLocatorModel.ResultTypeRole), 1)
self.assertEqual(m.data(m.index(2, 0), QgsLocatorModel.ResultFilterNameRole), 'test')
self.assertEqual(m.data(m.index(3, 0)), 'a2')
self.assertEqual(m.data(m.index(3, 0), QgsLocatorModel.ResultTypeRole), 1)
self.assertEqual(m.data(m.index(3, 0), QgsLocatorModel.ResultFilterNameRole), 'test')

m.search('a')

for i in range(100):
sleep(0.002)
QCoreApplication.processEvents()

# 4 results - one is locator name
self.assertEqual(m.rowCount(), 4)
self.assertEqual(m.data(m.index(0, 0)), 'test')
self.assertEqual(m.data(m.index(1, 0)), 'a0')
self.assertEqual(m.data(m.index(2, 0)), 'a1')
self.assertEqual(m.data(m.index(3, 0)), 'a2')


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

0 comments on commit 2683094

Please sign in to comment.