Merge pull request #5112 from nyalldawson/locator_core

Move locator non-gui classes to core
This commit is contained in:
Nyall Dawson 2017-09-13 18:50:28 +10:00 committed by GitHub
commit 742f3e5b4d
22 changed files with 1221 additions and 333 deletions

View File

@ -66,6 +66,7 @@ IF(WITH_APIDOC)
${CMAKE_SOURCE_DIR}/src/core/gps
${CMAKE_SOURCE_DIR}/src/core/layertree
${CMAKE_SOURCE_DIR}/src/core/layout
${CMAKE_SOURCE_DIR}/src/core/locator
${CMAKE_SOURCE_DIR}/src/core/metadata
${CMAKE_SOURCE_DIR}/src/core/pal
${CMAKE_SOURCE_DIR}/src/core/processing

View File

@ -117,6 +117,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/gps
${CMAKE_SOURCE_DIR}/src/core/layertree
${CMAKE_SOURCE_DIR}/src/core/layout
${CMAKE_SOURCE_DIR}/src/core/locator
${CMAKE_SOURCE_DIR}/src/core/metadata
${CMAKE_SOURCE_DIR}/src/core/processing
${CMAKE_SOURCE_DIR}/src/core/processing/models

View File

@ -251,6 +251,7 @@
%Include symbology/qgsvectorfieldsymbollayer.sip
%Include symbology/qgsgeometrygeneratorsymbollayer.sip
%Include layertree/qgslayertreeutils.sip
%Include locator/qgslocatorcontext.sip
%Include geometry/qgsabstractgeometry.sip
%Include geometry/qgsbox3d.sip
%Include geometry/qgscircularstring.sip
@ -374,6 +375,9 @@
%Include composer/qgscomposition.sip
%Include composer/qgsgroupungroupitemscommand.sip
%Include composer/qgslayoutmanager.sip
%Include locator/qgslocator.sip
%Include locator/qgslocatorfilter.sip
%Include locator/qgslocatormodel.sip
%Include processing/qgsprocessingalgrunnertask.sip
%Include processing/qgsprocessingfeedback.sip
%Include processing/qgsprocessingprovider.sip

View File

@ -0,0 +1,142 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/locator/qgslocator.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsLocator : QObject
{
%Docstring
Handles the management of QgsLocatorFilter objects and async collection of search results from them.
QgsLocator acts as both a registry for QgsLocatorFilter objects and a means of firing up
asynchronous queries against these filter objects.
Filters are first registered to the locator by calling registerFilter(). Registering filters
transfers their ownership to the locator object. Plugins which register filters to the locator
must take care to correctly call deregisterFilter() and deregister their filter upon plugin
unload to avoid crashes.
In order to trigger a search across registered filters, the fetchResults() method is called.
This triggers threaded calls to QgsLocatorFilter.fetchResults() for all registered filters.
As individual filters find matching results, the foundResult() signal will be triggered
for each result. Callers should connect this signal to an appropriate slot designed
to collect and handle these results. Since foundResult() is triggered whenever a filter
encounters an individual result, it will usually be triggered many times for a single
call to fetchResults().
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslocator.h"
%End
public:
QgsLocator( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsLocator.
%End
~QgsLocator();
%Docstring
Destructor for QgsLocator. Destruction will block while any currently running query is terminated.
%End
void registerFilter( QgsLocatorFilter *filter /Transfer/ );
%Docstring
Registers a ``filter`` within the locator. Ownership of the filter is transferred to the
locator.
\warning Plugins which register filters to the locator must take care to correctly call
deregisterFilter() and deregister their filters upon plugin unload to avoid crashes.
.. seealso:: deregisterFilter()
%End
void deregisterFilter( QgsLocatorFilter *filter );
%Docstring
Deregisters a ``filter`` from the locator and deletes it. Calling this will block whilst
any currently running query is terminated.
Plugins which register filters to the locator must take care to correctly call
deregisterFilter() to deregister their filters upon plugin unload to avoid crashes.
.. seealso:: registerFilter()
%End
QList< QgsLocatorFilter *> filters();
%Docstring
Returns the list of filters registered in the locator.
.. seealso:: prefixedFilters()
:rtype: list of QgsLocatorFilter
%End
QMap< QString, QgsLocatorFilter *> prefixedFilters() const;
%Docstring
Returns a map of prefix to filter, for all registered filters
with valid prefixes.
.. seealso:: filters()
:rtype: QMap< str, QgsLocatorFilter *>
%End
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback = 0 );
%Docstring
Triggers the background fetching of filter results for a specified search ``string``.
The ``context`` argument encapsulates the context relating to the search (such as a map
extent to prioritize).
If specified, the ``feedback`` object must exist for the lifetime of this query.
The foundResult() signal will be emitted for each individual result encountered
by the registered filters.
%End
void cancel();
%Docstring
Cancels any current running query, and blocks until query is completely canceled by
all filters.
.. seealso:: cancelWithoutBlocking()
%End
void cancelWithoutBlocking();
%Docstring
Triggers cancelation of any current running query without blocking. The query may
take some time to cancel after calling this.
.. seealso:: cancel()
%End
bool isRunning() const;
%Docstring
Returns true if a query is currently being executed by the locator.
:rtype: bool
%End
signals:
void foundResult( const QgsLocatorResult &result );
%Docstring
Emitted whenever a filter encounters a matching ``result`` after the fetchResults() method
is called.
%End
void finished();
%Docstring
Emitted when locator has finished a query, either as a result
of successful completion or early cancelation.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/locator/qgslocator.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,53 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/locator/qgslocatorcontext.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsLocatorContext
{
%Docstring
Encapsulates the properties relating to the context of a locator search.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslocatorcontext.h"
%End
public:
QgsLocatorContext();
%Docstring
Constructor for QgsLocatorContext.
%End
QgsRectangle targetExtent;
%Docstring
Map extent to target in results. This can be used to prioritize searching
for results close to the current map extent. The CRS for the extent
is specified by targetExtentCrs.
.. seealso:: targetExtentCrs
%End
QgsCoordinateReferenceSystem targetExtentCrs;
%Docstring
Coordinate reference system for the map extent variable.
.. seealso:: targetExtent
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/locator/qgslocatorcontext.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,225 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/locator/qgslocatorfilter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsLocatorResult
{
%Docstring
Encapsulates properties of an individual matching result found by a QgsLocatorFilter.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslocatorfilter.h"
%End
public:
QgsLocatorResult();
%Docstring
Constructor for QgsLocatorResult.
%End
QgsLocatorResult( QgsLocatorFilter *filter, const QString &displayString, const QVariant &userData = QVariant() );
%Docstring
Constructor for QgsLocatorResult.
%End
QgsLocatorFilter *filter;
%Docstring
Filter from which the result was obtained.
%End
QString displayString;
%Docstring
String displayed for result.
%End
QString description;
%Docstring
Descriptive text for result.
%End
QVariant userData;
%Docstring
Custom reference or other data set by the filter.
%End
QIcon icon;
%Docstring
Icon for result.
%End
double score;
%Docstring
Match score, from 0 - 1, where 1 represents a perfect match.
%End
};
class QgsLocatorFilter : QObject
{
%Docstring
Abstract base class for filters which collect locator results.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslocatorfilter.h"
%End
public:
enum Priority
{
Highest,
High,
Medium,
Low,
Lowest
};
QgsLocatorFilter( QObject *parent = 0 );
%Docstring
Constructor for QgsLocatorFilter.
%End
virtual QString name() const = 0;
%Docstring
Returns the unique name for the filter. This should be an untranslated string identifying the filter.
.. seealso:: displayName()
:rtype: str
%End
virtual QString displayName() const = 0;
%Docstring
Returns a translated, user-friendly name for the filter.
.. seealso:: name()
:rtype: str
%End
virtual Priority priority() const;
%Docstring
Returns the priority for the filter, which controls how results are
ordered in the locator.
:rtype: Priority
%End
virtual QString prefix() const;
%Docstring
Returns the search prefix character(s) for this filter. Prefix a search
with these characters will restrict the locator search to only include
results from this filter.
.. note::
Plugins are not permitted to utilize prefixes with < 3 characters,
as these are reserved for core QGIS functions. If a plugin registers
a filter with a prefix shorter than 3 characters then the prefix will
be ignored.
:rtype: str
%End
virtual void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) = 0;
%Docstring
Retrieves the filter results for a specified search ``string``. The ``context``
argument encapsulates the context relating to the search (such as a map
extent to prioritize).
Implementations of fetchResults() should emit the resultFetched()
signal whenever they encounter a matching result.
Subclasses should periodically check the ``feedback`` object to determine
whether the query has been canceled. If so, the subclass should return
from this method as soon as possible.
%End
virtual void triggerResult( const QgsLocatorResult &result ) = 0;
%Docstring
Triggers a filter ``result`` from this filter. This is called when
one of the results obtained by a call to fetchResults() is triggered
by a user. The filter subclass must implement logic here
to perform the desired operation for the search result.
E.g. a file search filter would open file associated with the triggered
result.
%End
bool useWithoutPrefix() const;
%Docstring
Returns true if the filter should be used when no prefix
is entered.
.. seealso:: setUseWithoutPrefix()
:rtype: bool
%End
void setUseWithoutPrefix( bool useWithoutPrefix );
%Docstring
Sets whether the filter should be used when no prefix
is entered.
.. seealso:: useWithoutPrefix()
%End
static bool stringMatches( const QString &candidate, const QString &search );
%Docstring
Tests a ``candidate`` string to see if it should be considered a match for
a specified ``search`` string.
Filter subclasses should use this method when comparing strings instead
of directly using QString.contains() or Python 'in' checks.
:rtype: bool
%End
bool enabled() const;
%Docstring
Returns true if the filter is enabled.
.. seealso:: setEnabled()
:rtype: bool
%End
void setEnabled( bool enabled );
%Docstring
Sets whether the filter is ``enabled``.
.. seealso:: enabled()
%End
virtual bool hasConfigWidget() const;
%Docstring
Should return true if the filter has a configuration widget.
.. seealso:: createConfigWidget()
:rtype: bool
%End
virtual void openConfigWidget( QWidget *parent = 0 );
%Docstring
Opens the configuration widget for the filter (if it has one), with the specified ``parent`` widget.
The base class implementation does nothing. Subclasses can override this to show their own
custom configuration widget.
.. note::
hasConfigWidget() must return true to indicate that the filter supports configuration.
%End
signals:
void resultFetched( const QgsLocatorResult &result );
%Docstring
Should be emitted by filters whenever they encounter a matching result
during within their fetchResults() implementation.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/locator/qgslocatorfilter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,162 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/locator/qgslocatormodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
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
%TypeHeaderCode
#include "qgslocatormodel.h"
%End
public:
enum Role
{
ResultDataRole,
ResultTypeRole,
ResultFilterPriorityRole,
ResultScoreRole,
ResultFilterNameRole,
};
QgsLocatorModel( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsLocatorModel.
%End
void clear();
%Docstring
Resets the model and clears all existing results.
.. seealso:: deferredClear()
%End
void deferredClear();
%Docstring
Resets the model and clears all existing results after a short delay, or whenever the next result is added to the model
(whichever occurs first). Using deferredClear() instead of clear() can avoid the visually distracting frequent clears
which may occur if the model is being updated quickly multiple times as a result of users typing in a search query.
.. seealso:: deferredClear()
%End
virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
public slots:
void addResult( const QgsLocatorResult &result );
%Docstring
Adds a new ``result`` to the model.
%End
};
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
A sort proxy model for QgsLocatorModel, which automatically sorts
results by precedence.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslocatormodel.h"
%End
public:
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;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/locator/qgslocatormodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -46,7 +46,6 @@
%Include layertree/qgslayertreeembeddedwidgetregistry.sip
%Include layout/qgslayoutviewmouseevent.sip
%Include layout/qgslayoutviewrubberband.sip
%Include locator/qgslocatorcontext.sip
%Include raster/qgsrasterrendererwidget.sip
%Include symbology/qgssymbolwidgetcontext.sip
%Include qgisinterface.sip
@ -295,7 +294,5 @@
%Include layout/qgslayoutviewtooltemporarykeyzoom.sip
%Include layout/qgslayoutviewtooltemporarymousepan.sip
%Include layout/qgslayoutviewtoolzoom.sip
%Include locator/qgslocator.sip
%Include locator/qgslocatorfilter.sip
%Include locator/qgslocatorwidget.sip
%Include qgsadvanceddigitizingcanvasitem.sip

View File

@ -27,9 +27,9 @@ __revision__ = '$Format:%H$'
from qgis.core import (QgsApplication,
QgsProcessingAlgorithm)
from qgis.gui import (QgsLocatorFilter,
QgsLocatorResult)
QgsProcessingAlgorithm,
QgsLocatorFilter,
QgsLocatorResult)
from processing.gui.MessageDialog import MessageDialog
from processing.gui.AlgorithmDialog import AlgorithmDialog
from qgis.utils import iface

View File

@ -512,6 +512,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/geocms/geonode
${CMAKE_SOURCE_DIR}/src/core/metadata
${CMAKE_SOURCE_DIR}/src/core/layertree
${CMAKE_SOURCE_DIR}/src/core/locator
${CMAKE_SOURCE_DIR}/src/core/providers/memory
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/core/scalebar

View File

@ -93,6 +93,10 @@ SET(QGIS_CORE_SRCS
expression/qgsexpressionfunction.cpp
expression/qgsexpressionutils.cpp
locator/qgslocator.cpp
locator/qgslocatorfilter.cpp
locator/qgslocatormodel.cpp
processing/qgsnativealgorithms.cpp
processing/qgsprocessingalgorithm.cpp
processing/qgsprocessingalgrunnertask.cpp
@ -667,6 +671,10 @@ SET(QGIS_CORE_MOC_HDRS
composer/qgslayoutmanager.h
composer/qgspaperitem.h
locator/qgslocator.h
locator/qgslocatorfilter.h
locator/qgslocatormodel.h
processing/qgsnativealgorithms.h
processing/qgsprocessingalgrunnertask.h
processing/qgsprocessingfeedback.h
@ -1050,6 +1058,8 @@ SET(QGIS_CORE_HDRS
layertree/qgslayertreeutils.h
locator/qgslocatorcontext.h
geometry/qgsabstractgeometry.h
geometry/qgsbox3d.h
geometry/qgscircularstring.h
@ -1118,6 +1128,7 @@ INCLUDE_DIRECTORIES(
geocms/geonode
layertree
layout
locator
metadata
pal
processing

View File

@ -18,7 +18,7 @@
#ifndef QGSLOCATOR_H
#define QGSLOCATOR_H
#include "qgis_gui.h"
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgslocatorfilter.h"
#include "qgsfeedback.h"
@ -30,7 +30,7 @@
/**
* \class QgsLocator
* \ingroup gui
* \ingroup core
* Handles the management of QgsLocatorFilter objects and async collection of search results from them.
*
* QgsLocator acts as both a registry for QgsLocatorFilter objects and a means of firing up
@ -51,7 +51,7 @@
*
* \since QGIS 3.0
*/
class GUI_EXPORT QgsLocator : public QObject
class CORE_EXPORT QgsLocator : public QObject
{
Q_OBJECT

View File

@ -18,17 +18,17 @@
#ifndef QGSLOCATORCONTEXT_H
#define QGSLOCATORCONTEXT_H
#include "qgis_gui.h"
#include "qgis_core.h"
#include "qgsrectangle.h"
#include "qgscoordinatereferencesystem.h"
/**
* \class QgsLocatorContext
* \ingroup gui
* \ingroup core
* Encapsulates the properties relating to the context of a locator search.
* \since QGIS 3.0
*/
class GUI_EXPORT QgsLocatorContext
class CORE_EXPORT QgsLocatorContext
{
public:

View File

@ -18,7 +18,7 @@
#ifndef QGSLOCATORFILTER_H
#define QGSLOCATORFILTER_H
#include "qgis_gui.h"
#include "qgis_core.h"
#include "qgslocatorcontext.h"
#include "qgslogger.h"
#include <QString>
@ -30,11 +30,11 @@ class QgsLocatorFilter;
/**
* \class QgsLocatorResult
* \ingroup gui
* \ingroup core
* Encapsulates properties of an individual matching result found by a QgsLocatorFilter.
* \since QGIS 3.0
*/
class GUI_EXPORT QgsLocatorResult
class CORE_EXPORT QgsLocatorResult
{
public:
@ -90,11 +90,11 @@ class GUI_EXPORT QgsLocatorResult
/**
* \class QgsLocatorFilter
* \ingroup gui
* \ingroup core
* Abstract base class for filters which collect locator results.
* \since QGIS 3.0
*/
class GUI_EXPORT QgsLocatorFilter : public QObject
class CORE_EXPORT QgsLocatorFilter : public QObject
{
Q_OBJECT

View File

@ -0,0 +1,300 @@
/***************************************************************************
qgslocatormodel.cpp
--------------------
begin : May 2017
copyright : (C) 2017 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 "qgslocatormodel.h"
#include "qgslocator.h"
#include "qgsapplication.h"
#include "qgslogger.h"
//
// QgsLocatorModel
//
QgsLocatorModel::QgsLocatorModel( QObject *parent )
: QAbstractTableModel( parent )
{
mDeferredClearTimer.setInterval( 100 );
mDeferredClearTimer.setSingleShot( true );
connect( &mDeferredClearTimer, &QTimer::timeout, this, &QgsLocatorModel::clear );
}
void QgsLocatorModel::clear()
{
mDeferredClearTimer.stop();
mDeferredClear = false;
beginResetModel();
mResults.clear();
mFoundResultsFromFilterNames.clear();
endResetModel();
}
void QgsLocatorModel::deferredClear()
{
mDeferredClear = true;
mDeferredClearTimer.start();
}
int QgsLocatorModel::rowCount( const QModelIndex & ) const
{
return mResults.size();
}
int QgsLocatorModel::columnCount( const QModelIndex & ) const
{
return 2;
}
QVariant QgsLocatorModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
return QVariant();
switch ( role )
{
case Qt::DisplayRole:
case Qt::EditRole:
{
switch ( index.column() )
{
case Name:
if ( !mResults.at( index.row() ).filter )
return mResults.at( index.row() ).result.displayString;
else
return mResults.at( index.row() ).filterTitle;
case Description:
if ( !mResults.at( index.row() ).filter )
return mResults.at( index.row() ).result.description;
else
return QVariant();
}
break;
}
case Qt::DecorationRole:
switch ( index.column() )
{
case Name:
if ( !mResults.at( index.row() ).filter )
{
QIcon icon = mResults.at( index.row() ).result.icon;
if ( !icon.isNull() )
return icon;
return QgsApplication::getThemeIcon( "/search.svg" );
}
else
return QVariant();
case Description:
return QVariant();
}
break;
case ResultDataRole:
if ( !mResults.at( index.row() ).filter )
return QVariant::fromValue( mResults.at( index.row() ).result );
else
return QVariant();
case ResultTypeRole:
if ( mResults.at( index.row() ).filter )
return 0;
else
return 1;
case ResultScoreRole:
if ( mResults.at( index.row() ).filter )
return 0;
else
return ( mResults.at( index.row() ).result.score );
case ResultFilterPriorityRole:
if ( !mResults.at( index.row() ).filter )
return mResults.at( index.row() ).result.filter->priority();
else
return mResults.at( index.row() ).filter->priority();
case ResultFilterNameRole:
if ( !mResults.at( index.row() ).filter )
return mResults.at( index.row() ).result.filter->displayName();
else
return mResults.at( index.row() ).filterTitle;
}
return QVariant();
}
Qt::ItemFlags QgsLocatorModel::flags( const QModelIndex &index ) const
{
if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
return QAbstractTableModel::flags( index );
Qt::ItemFlags flags = QAbstractTableModel::flags( index );
if ( !mResults.at( index.row() ).filterTitle.isEmpty() )
{
flags = flags & ~( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
}
return flags;
}
void QgsLocatorModel::addResult( const QgsLocatorResult &result )
{
mDeferredClearTimer.stop();
if ( mDeferredClear )
{
mFoundResultsFromFilterNames.clear();
}
int pos = mResults.size();
bool addingFilter = !result.filter->displayName().isEmpty() && !mFoundResultsFromFilterNames.contains( result.filter->name() );
if ( addingFilter )
mFoundResultsFromFilterNames << result.filter->name();
if ( mDeferredClear )
{
beginResetModel();
mResults.clear();
}
else
beginInsertRows( QModelIndex(), pos, pos + ( addingFilter ? 1 : 0 ) );
if ( addingFilter )
{
Entry entry;
entry.filterTitle = result.filter->displayName();
entry.filter = result.filter;
mResults << entry;
}
Entry entry;
entry.result = result;
mResults << entry;
if ( mDeferredClear )
endResetModel();
else
endInsertRows();
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
//
QgsLocatorProxyModel::QgsLocatorProxyModel( QObject *parent )
: QSortFilterProxyModel( parent )
{
setDynamicSortFilter( true );
setSortLocaleAware( true );
setFilterCaseSensitivity( Qt::CaseInsensitive );
sort( 0 );
}
bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
{
// first go by filter priority
int leftFilterPriority = sourceModel()->data( left, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
int rightFilterPriority = sourceModel()->data( right, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
if ( leftFilterPriority != rightFilterPriority )
return leftFilterPriority < rightFilterPriority;
// then filter name
QString leftFilter = sourceModel()->data( left, QgsLocatorModel::ResultFilterNameRole ).toString();
QString rightFilter = sourceModel()->data( right, QgsLocatorModel::ResultFilterNameRole ).toString();
if ( leftFilter != rightFilter )
return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
// then make sure filter title appears before filter's results
int leftTypeRole = sourceModel()->data( left, QgsLocatorModel::ResultTypeRole ).toInt();
int rightTypeRole = sourceModel()->data( right, QgsLocatorModel::ResultTypeRole ).toInt();
if ( leftTypeRole != rightTypeRole )
return leftTypeRole < rightTypeRole;
// sort filter's results by score
double leftScore = sourceModel()->data( left, QgsLocatorModel::ResultScoreRole ).toDouble();
double rightScore = sourceModel()->data( right, QgsLocatorModel::ResultScoreRole ).toDouble();
if ( !qgsDoubleNear( leftScore, rightScore ) )
return leftScore > rightScore;
// lastly sort filter's results by string
leftFilter = sourceModel()->data( left, Qt::DisplayRole ).toString();
rightFilter = sourceModel()->data( right, Qt::DisplayRole ).toString();
return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
}

View File

@ -0,0 +1,198 @@
/***************************************************************************
qgslocatormodel.h
------------------
begin : May 2017
copyright : (C) 2017 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 QGSLOCATORMODEL_H
#define QGSLOCATORMODEL_H
#include "qgis_core.h"
#include "qgslocatorfilter.h"
#include <QAbstractListModel>
#include <QTimer>
#include <QSet>
#include <QSortFilterProxyModel>
class QgsLocator;
class QgsLocatorModel;
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
{
Q_OBJECT
public:
//! Custom model roles
enum Role
{
ResultDataRole = Qt::UserRole + 1, //!< QgsLocatorResult data
ResultTypeRole, //!< Result type
ResultFilterPriorityRole, //!< Result priority, used by QgsLocatorProxyModel for sorting roles.
ResultScoreRole, //!< Result match score, used by QgsLocatorProxyModel for sorting roles.
ResultFilterNameRole, //!< Associated filter name which created the result
};
/**
* Constructor for QgsLocatorModel.
*/
QgsLocatorModel( QObject *parent SIP_TRANSFERTHIS = nullptr );
/**
* Resets the model and clears all existing results.
* \see deferredClear()
*/
void clear();
/**
* Resets the model and clears all existing results after a short delay, or whenever the next result is added to the model
* (whichever occurs first). Using deferredClear() instead of clear() can avoid the visually distracting frequent clears
* which may occur if the model is being updated quickly multiple times as a result of users typing in a search query.
* \see deferredClear()
*/
void deferredClear();
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
Qt::ItemFlags flags( const QModelIndex &index ) const override;
public slots:
/**
* Adds a new \a result to the model.
*/
void addResult( const QgsLocatorResult &result );
private:
enum ColumnCount
{
Name = 0,
Description
};
struct Entry
{
QgsLocatorResult result;
QString filterTitle;
QgsLocatorFilter *filter = nullptr;
};
QList<Entry> mResults;
QSet<QString> mFoundResultsFromFilterNames;
bool mDeferredClear = false;
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
* A sort proxy model for QgsLocatorModel, which automatically sorts
* results by precedence.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLocatorProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
/**
* 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;
};
#endif // QGSLOCATORMODEL_H

View File

@ -175,8 +175,6 @@ SET(QGIS_GUI_SRCS
layout/qgslayoutviewtooltemporarymousepan.cpp
layout/qgslayoutviewtoolzoom.cpp
locator/qgslocator.cpp
locator/qgslocatorfilter.cpp
locator/qgslocatorwidget.cpp
ogr/qgsogrhelperfunctions.cpp
@ -667,8 +665,6 @@ SET(QGIS_GUI_MOC_HDRS
layout/qgslayoutviewtooltemporarymousepan.h
layout/qgslayoutviewtoolzoom.h
locator/qgslocator.h
locator/qgslocatorfilter.h
locator/qgslocatorwidget.h
)
SET_PROPERTY(GLOBAL PROPERTY QGIS_GUI_MOC_HDRS ${QGIS_GUI_MOC_HDRS})
@ -769,8 +765,6 @@ SET(QGIS_GUI_HDRS
layout/qgslayoutviewmouseevent.h
layout/qgslayoutviewrubberband.h
locator/qgslocatorcontext.h
raster/qgsrasterrendererwidget.h
symbology/qgssymbolwidgetcontext.h

View File

@ -18,6 +18,7 @@
#include "qgslocatorwidget.h"
#include "qgslocator.h"
#include "qgslocatormodel.h"
#include "qgsfilterlineedit.h"
#include "qgsmapcanvas.h"
#include "qgsapplication.h"
@ -344,179 +345,6 @@ QgsLocatorContext QgsLocatorWidget::createContext()
///@cond PRIVATE
//
// QgsLocatorModel
//
QgsLocatorModel::QgsLocatorModel( QObject *parent )
: QAbstractTableModel( parent )
{
mDeferredClearTimer.setInterval( 100 );
mDeferredClearTimer.setSingleShot( true );
connect( &mDeferredClearTimer, &QTimer::timeout, this, &QgsLocatorModel::clear );
}
void QgsLocatorModel::clear()
{
mDeferredClearTimer.stop();
mDeferredClear = false;
beginResetModel();
mResults.clear();
mFoundResultsFromFilterNames.clear();
endResetModel();
}
void QgsLocatorModel::deferredClear()
{
mDeferredClear = true;
mDeferredClearTimer.start();
}
int QgsLocatorModel::rowCount( const QModelIndex & ) const
{
return mResults.size();
}
int QgsLocatorModel::columnCount( const QModelIndex & ) const
{
return 2;
}
QVariant QgsLocatorModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
return QVariant();
switch ( role )
{
case Qt::DisplayRole:
case Qt::EditRole:
{
switch ( index.column() )
{
case Name:
if ( !mResults.at( index.row() ).filter )
return mResults.at( index.row() ).result.displayString;
else
return mResults.at( index.row() ).filterTitle;
case Description:
if ( !mResults.at( index.row() ).filter )
return mResults.at( index.row() ).result.description;
else
return QVariant();
}
break;
}
case Qt::DecorationRole:
switch ( index.column() )
{
case Name:
if ( !mResults.at( index.row() ).filter )
{
QIcon icon = mResults.at( index.row() ).result.icon;
if ( !icon.isNull() )
return icon;
return QgsApplication::getThemeIcon( "/search.svg" );
}
else
return QVariant();
case Description:
return QVariant();
}
break;
case ResultDataRole:
if ( !mResults.at( index.row() ).filter )
return QVariant::fromValue( mResults.at( index.row() ).result );
else
return QVariant();
case ResultTypeRole:
if ( mResults.at( index.row() ).filter )
return 0;
else
return 1;
case ResultScoreRole:
if ( mResults.at( index.row() ).filter )
return 0;
else
return ( mResults.at( index.row() ).result.score );
case ResultFilterPriorityRole:
if ( !mResults.at( index.row() ).filter )
return mResults.at( index.row() ).result.filter->priority();
else
return mResults.at( index.row() ).filter->priority();
case ResultFilterNameRole:
if ( !mResults.at( index.row() ).filter )
return mResults.at( index.row() ).result.filter->displayName();
else
return mResults.at( index.row() ).filterTitle;
}
return QVariant();
}
Qt::ItemFlags QgsLocatorModel::flags( const QModelIndex &index ) const
{
if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
return QAbstractTableModel::flags( index );
Qt::ItemFlags flags = QAbstractTableModel::flags( index );
if ( !mResults.at( index.row() ).filterTitle.isEmpty() )
{
flags = flags & ~( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
}
return flags;
}
void QgsLocatorModel::addResult( const QgsLocatorResult &result )
{
mDeferredClearTimer.stop();
if ( mDeferredClear )
{
mFoundResultsFromFilterNames.clear();
}
int pos = mResults.size();
bool addingFilter = !result.filter->displayName().isEmpty() && !mFoundResultsFromFilterNames.contains( result.filter->name() );
if ( addingFilter )
mFoundResultsFromFilterNames << result.filter->name();
if ( mDeferredClear )
{
beginResetModel();
mResults.clear();
}
else
beginInsertRows( QModelIndex(), pos, pos + ( addingFilter ? 1 : 0 ) );
if ( addingFilter )
{
Entry entry;
entry.filterTitle = result.filter->displayName();
entry.filter = result.filter;
mResults << entry;
}
Entry entry;
entry.result = result;
mResults << entry;
if ( mDeferredClear )
endResetModel();
else
endInsertRows();
mDeferredClear = false;
}
//
// QgsLocatorResultsView
//
@ -561,53 +389,6 @@ void QgsLocatorResultsView::selectPreviousResult()
setCurrentIndex( model()->index( previousRow, 0 ) );
}
//
// QgsLocatorProxyModel
//
QgsLocatorProxyModel::QgsLocatorProxyModel( QObject *parent )
: QSortFilterProxyModel( parent )
{
setDynamicSortFilter( true );
setSortLocaleAware( true );
setFilterCaseSensitivity( Qt::CaseInsensitive );
sort( 0 );
}
bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
{
// first go by filter priority
int leftFilterPriority = sourceModel()->data( left, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
int rightFilterPriority = sourceModel()->data( right, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
if ( leftFilterPriority != rightFilterPriority )
return leftFilterPriority < rightFilterPriority;
// then filter name
QString leftFilter = sourceModel()->data( left, QgsLocatorModel::ResultFilterNameRole ).toString();
QString rightFilter = sourceModel()->data( right, QgsLocatorModel::ResultFilterNameRole ).toString();
if ( leftFilter != rightFilter )
return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
// then make sure filter title appears before filter's results
int leftTypeRole = sourceModel()->data( left, QgsLocatorModel::ResultTypeRole ).toInt();
int rightTypeRole = sourceModel()->data( right, QgsLocatorModel::ResultTypeRole ).toInt();
if ( leftTypeRole != rightTypeRole )
return leftTypeRole < rightTypeRole;
// sort filter's results by score
double leftScore = sourceModel()->data( left, QgsLocatorModel::ResultScoreRole ).toDouble();
double rightScore = sourceModel()->data( right, QgsLocatorModel::ResultScoreRole ).toDouble();
if ( !qgsDoubleNear( leftScore, rightScore ) )
return leftScore > rightScore;
// lastly sort filter's results by string
leftFilter = sourceModel()->data( left, Qt::DisplayRole ).toString();
rightFilter = sourceModel()->data( right, Qt::DisplayRole ).toString();
return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
}
//
// QgsLocatorFilterFilter
//

View File

@ -22,12 +22,10 @@
#include "qgslocatorfilter.h"
#include "qgsfloatingwidget.h"
#include <QWidget>
#include <QAbstractListModel>
#include <QTreeView>
#include <QFocusEvent>
#include <QHeaderView>
#include <QTimer>
#include <QSortFilterProxyModel>
class QgsLocator;
class QgsFilterLineEdit;
@ -147,90 +145,6 @@ class QgsLocatorFilterFilter : public QgsLocatorFilter
QgsLocatorWidget *mLocator = nullptr;
};
/**
* \class QgsLocatorModel
* \ingroup gui
* An abstract list model for displaying the results in a QgsLocatorWidget.
* \since QGIS 3.0
*/
class QgsLocatorModel : public QAbstractTableModel
{
Q_OBJECT
public:
//! Custom model roles
enum Role
{
ResultDataRole = Qt::UserRole + 1, //!< QgsLocatorResult data
ResultTypeRole,
ResultFilterPriorityRole,
ResultScoreRole,
ResultFilterNameRole,
};
enum columnCount
{
Name = 0,
Description
};
/**
* Constructor for QgsLocatorModel.
*/
QgsLocatorModel( QObject *parent = nullptr );
/**
* Resets the model and clears all existing results.
* \see deferredClear()
*/
void clear();
/**
* Resets the model and clears all existing results after a short delay, or whenever the next result is added to the model
* (whichever occurs first). Using deferredClear() instead of clear() can avoid the visually distracting frequent clears
* which may occur if the model is being updated quickly multiple times as a result of users typing in a search query.
* \see deferredClear()
*/
void deferredClear();
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
Qt::ItemFlags flags( const QModelIndex &index ) const override;
public slots:
/**
* Adds a new \a result to the model.
*/
void addResult( const QgsLocatorResult &result );
private:
struct Entry
{
QgsLocatorResult result;
QString filterTitle;
QgsLocatorFilter *filter = nullptr;
};
QList<Entry> mResults;
QSet<QString> mFoundResultsFromFilterNames;
bool mDeferredClear = false;
QTimer mDeferredClearTimer;
};
class QgsLocatorProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit QgsLocatorProxyModel( QObject *parent = nullptr );
bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override;
};
/**
* \class QgsLocatorResultsView
* \ingroup gui

View File

@ -15,10 +15,12 @@ import qgis # NOQA
import os
from qgis.gui import (QgsLocator,
QgsLocatorFilter,
QgsLocatorContext,
QgsLocatorResult)
from qgis.core import (QgsLocator,
QgsLocatorFilter,
QgsLocatorContext,
QgsLocatorResult,
QgsLocatorModel,
QgsLocatorAutomaticModel)
from qgis.PyQt.QtCore import QVariant, pyqtSignal, QCoreApplication
from time import sleep
from qgis.testing import start_app, unittest
@ -32,6 +34,12 @@ class test_filter(QgsLocatorFilter):
super().__init__(parent)
self.prefix = prefix
def name(self):
return 'test'
def displayName(self):
return 'test'
def fetchResults(self, string, context, feedback):
for i in range(3):
#if feedback.isCanceled():
@ -152,6 +160,102 @@ class TestQgsLocator(unittest.TestCase):
l.fetchResults('a', context)
l.cancel()
def testModel(self):
m = QgsLocatorModel()
l = QgsLocator()
filter_a = test_filter('a')
l.registerFilter(filter_a)
l.foundResult.connect(m.addResult)
context = QgsLocatorContext()
l.fetchResults('a', context)
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.clear()
self.assertEqual(m.rowCount(), 0)
l.fetchResults('b', context)
for i in range(100):
sleep(0.002)
QCoreApplication.processEvents()
self.assertEqual(m.rowCount(), 4)
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')
m.deferredClear()
# should not be immediately cleared!
self.assertEqual(m.rowCount(), 4)
for i in range(100):
sleep(0.002)
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()