From a8e1d335bbcf4ee0d0359bb266892d6bcf1c37ec Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 30 Aug 2017 18:46:12 +1000 Subject: [PATCH 1/2] Move locator non-gui classes to core Allows reuse in projects which don't build QGIS gui, e.g. QField --- doc/CMakeLists.txt | 1 + python/CMakeLists.txt | 1 + python/core/core_auto.sip | 4 + python/core/locator/qgslocator.sip | 142 +++++++++++ python/core/locator/qgslocatorcontext.sip | 53 ++++ python/core/locator/qgslocatorfilter.sip | 225 ++++++++++++++++ python/core/locator/qgslocatormodel.sip | 97 +++++++ python/gui/gui_auto.sip | 3 - .../processing/gui/AlgorithmLocatorFilter.py | 6 +- src/app/CMakeLists.txt | 1 + src/core/CMakeLists.txt | 11 + src/{gui => core}/locator/qgslocator.cpp | 0 src/{gui => core}/locator/qgslocator.h | 6 +- src/{gui => core}/locator/qgslocatorcontext.h | 6 +- .../locator/qgslocatorfilter.cpp | 0 src/{gui => core}/locator/qgslocatorfilter.h | 10 +- src/core/locator/qgslocatormodel.cpp | 240 ++++++++++++++++++ src/core/locator/qgslocatormodel.h | 125 +++++++++ src/gui/CMakeLists.txt | 6 - src/gui/locator/qgslocatorwidget.cpp | 221 +--------------- src/gui/locator/qgslocatorwidget.h | 86 ------- tests/src/python/test_qgslocator.py | 66 ++++- 22 files changed, 977 insertions(+), 333 deletions(-) create mode 100644 python/core/locator/qgslocator.sip create mode 100644 python/core/locator/qgslocatorcontext.sip create mode 100644 python/core/locator/qgslocatorfilter.sip create mode 100644 python/core/locator/qgslocatormodel.sip rename src/{gui => core}/locator/qgslocator.cpp (100%) rename src/{gui => core}/locator/qgslocator.h (98%) rename src/{gui => core}/locator/qgslocatorcontext.h (96%) rename src/{gui => core}/locator/qgslocatorfilter.cpp (100%) rename src/{gui => core}/locator/qgslocatorfilter.h (97%) create mode 100644 src/core/locator/qgslocatormodel.cpp create mode 100644 src/core/locator/qgslocatormodel.h diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 1492ab6bb0f..4f4d81cc48f 100755 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -64,6 +64,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 diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 6109cafd823..7e9c97ffbb3 100755 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -115,6 +115,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 diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 37cfc8ee399..bc913552396 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -248,6 +248,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 @@ -369,6 +370,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 diff --git a/python/core/locator/qgslocator.sip b/python/core/locator/qgslocator.sip new file mode 100644 index 00000000000..1dde5c62ae7 --- /dev/null +++ b/python/core/locator/qgslocator.sip @@ -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 * + ************************************************************************/ diff --git a/python/core/locator/qgslocatorcontext.sip b/python/core/locator/qgslocatorcontext.sip new file mode 100644 index 00000000000..bbaa9899d20 --- /dev/null +++ b/python/core/locator/qgslocatorcontext.sip @@ -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 * + ************************************************************************/ diff --git a/python/core/locator/qgslocatorfilter.sip b/python/core/locator/qgslocatorfilter.sip new file mode 100644 index 00000000000..080edd10a25 --- /dev/null +++ b/python/core/locator/qgslocatorfilter.sip @@ -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 * + ************************************************************************/ diff --git a/python/core/locator/qgslocatormodel.sip b/python/core/locator/qgslocatormodel.sip new file mode 100644 index 00000000000..75b04eaf110 --- /dev/null +++ b/python/core/locator/qgslocatormodel.sip @@ -0,0 +1,97 @@ +/************************************************************************ + * 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. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslocatormodel.h" +%End + public: + + enum Role + { + ResultDataRole, + ResultTypeRole, + ResultFilterPriorityRole, + ResultScoreRole, + ResultFilterNameRole, + }; + + QgsLocatorModel( QObject *parent = 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 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 = 0 ); + 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 * + ************************************************************************/ diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 071eb2153b1..8665f8149c9 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -44,7 +44,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 @@ -293,7 +292,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 diff --git a/python/plugins/processing/gui/AlgorithmLocatorFilter.py b/python/plugins/processing/gui/AlgorithmLocatorFilter.py index d6276e9f1a7..01ce8c972d0 100755 --- a/python/plugins/processing/gui/AlgorithmLocatorFilter.py +++ b/python/plugins/processing/gui/AlgorithmLocatorFilter.py @@ -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 diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 0e118d3879c..0d2128996cd 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -511,6 +511,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/core/geometry ${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 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c1c3e45863d..ccb27fe838a 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 @@ -659,6 +663,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 @@ -1036,6 +1044,8 @@ SET(QGIS_CORE_HDRS layertree/qgslayertreeutils.h + locator/qgslocatorcontext.h + geometry/qgsabstractgeometry.h geometry/qgsbox3d.h geometry/qgscircularstring.h @@ -1098,6 +1108,7 @@ INCLUDE_DIRECTORIES( geometry layertree layout + locator metadata pal processing diff --git a/src/gui/locator/qgslocator.cpp b/src/core/locator/qgslocator.cpp similarity index 100% rename from src/gui/locator/qgslocator.cpp rename to src/core/locator/qgslocator.cpp diff --git a/src/gui/locator/qgslocator.h b/src/core/locator/qgslocator.h similarity index 98% rename from src/gui/locator/qgslocator.h rename to src/core/locator/qgslocator.h index ca5f11ae51c..e863efc2921 100644 --- a/src/gui/locator/qgslocator.h +++ b/src/core/locator/qgslocator.h @@ -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 diff --git a/src/gui/locator/qgslocatorcontext.h b/src/core/locator/qgslocatorcontext.h similarity index 96% rename from src/gui/locator/qgslocatorcontext.h rename to src/core/locator/qgslocatorcontext.h index d2816d584fc..466aa854e3a 100644 --- a/src/gui/locator/qgslocatorcontext.h +++ b/src/core/locator/qgslocatorcontext.h @@ -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: diff --git a/src/gui/locator/qgslocatorfilter.cpp b/src/core/locator/qgslocatorfilter.cpp similarity index 100% rename from src/gui/locator/qgslocatorfilter.cpp rename to src/core/locator/qgslocatorfilter.cpp diff --git a/src/gui/locator/qgslocatorfilter.h b/src/core/locator/qgslocatorfilter.h similarity index 97% rename from src/gui/locator/qgslocatorfilter.h rename to src/core/locator/qgslocatorfilter.h index 4225bfa1755..37b38d8d204 100644 --- a/src/gui/locator/qgslocatorfilter.h +++ b/src/core/locator/qgslocatorfilter.h @@ -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 @@ -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 diff --git a/src/core/locator/qgslocatormodel.cpp b/src/core/locator/qgslocatormodel.cpp new file mode 100644 index 00000000000..8b27eed390a --- /dev/null +++ b/src/core/locator/qgslocatormodel.cpp @@ -0,0 +1,240 @@ +/*************************************************************************** + 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; +} + +// +// 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; +} + diff --git a/src/core/locator/qgslocatormodel.h b/src/core/locator/qgslocatormodel.h new file mode 100644 index 00000000000..8c30be8fed9 --- /dev/null +++ b/src/core/locator/qgslocatormodel.h @@ -0,0 +1,125 @@ +/*************************************************************************** + 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 +#include +#include +#include + +class QgsLocator; +class QgsLocatorModel; +class QgsLocatorProxyModel; + +/** + * \class QgsLocatorModel + * \ingroup core + * An abstract list model for displaying the results of locator searches. + * \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 = 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 mResults; + QSet mFoundResultsFromFilterNames; + bool mDeferredClear = false; + QTimer mDeferredClearTimer; +}; + +/** + * \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: + + explicit QgsLocatorProxyModel( QObject *parent = nullptr ); + bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override; +}; + +#endif // QGSLOCATORMODEL_H + + diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 8b98c620211..b922f358908 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -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 @@ -665,8 +663,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}) @@ -765,8 +761,6 @@ SET(QGIS_GUI_HDRS layout/qgslayoutviewmouseevent.h layout/qgslayoutviewrubberband.h - locator/qgslocatorcontext.h - raster/qgsrasterrendererwidget.h symbology/qgssymbolwidgetcontext.h diff --git a/src/gui/locator/qgslocatorwidget.cpp b/src/gui/locator/qgslocatorwidget.cpp index 5e4969eb764..2b183ae1117 100644 --- a/src/gui/locator/qgslocatorwidget.cpp +++ b/src/gui/locator/qgslocatorwidget.cpp @@ -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 // diff --git a/src/gui/locator/qgslocatorwidget.h b/src/gui/locator/qgslocatorwidget.h index 6bb2931d1c2..33147e45f72 100644 --- a/src/gui/locator/qgslocatorwidget.h +++ b/src/gui/locator/qgslocatorwidget.h @@ -22,12 +22,10 @@ #include "qgslocatorfilter.h" #include "qgsfloatingwidget.h" #include -#include #include #include #include #include -#include 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 mResults; - QSet 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 diff --git a/tests/src/python/test_qgslocator.py b/tests/src/python/test_qgslocator.py index 56d484c0d9b..d66d229c775 100644 --- a/tests/src/python/test_qgslocator.py +++ b/tests/src/python/test_qgslocator.py @@ -15,10 +15,11 @@ import qgis # NOQA import os -from qgis.gui import (QgsLocator, - QgsLocatorFilter, - QgsLocatorContext, - QgsLocatorResult) +from qgis.core import (QgsLocator, + QgsLocatorFilter, + QgsLocatorContext, + QgsLocatorResult, + QgsLocatorModel) from qgis.PyQt.QtCore import QVariant, pyqtSignal, QCoreApplication from time import sleep from qgis.testing import start_app, unittest @@ -32,6 +33,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 +159,57 @@ 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) + if __name__ == '__main__': unittest.main() From 26830949d9d8e7bdd2fb0897e906ccd431d1c353 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 3 Sep 2017 16:35:51 +1000 Subject: [PATCH 2/2] Add a QgsLocatorModel subclass which automatically sets up 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. --- python/core/locator/qgslocatormodel.sip | 69 +++++++++++++++++++++- src/core/locator/qgslocatormodel.cpp | 60 +++++++++++++++++++ src/core/locator/qgslocatormodel.h | 77 ++++++++++++++++++++++++- tests/src/python/test_qgslocator.py | 48 ++++++++++++++- 4 files changed, 249 insertions(+), 5 deletions(-) diff --git a/python/core/locator/qgslocatormodel.sip b/python/core/locator/qgslocatormodel.sip index 75b04eaf110..5ea7b544401 100644 --- a/python/core/locator/qgslocatormodel.sip +++ b/python/core/locator/qgslocatormodel.sip @@ -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 @@ -31,7 +35,7 @@ class QgsLocatorModel : QAbstractTableModel ResultFilterNameRole, }; - QgsLocatorModel( QObject *parent = 0 ); + QgsLocatorModel( QObject *parent /TransferThis/ = 0 ); %Docstring Constructor for QgsLocatorModel. %End @@ -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 @@ -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; }; diff --git a/src/core/locator/qgslocatormodel.cpp b/src/core/locator/qgslocatormodel.cpp index 8b27eed390a..f3e72859669 100644 --- a/src/core/locator/qgslocatormodel.cpp +++ b/src/core/locator/qgslocatormodel.cpp @@ -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 // @@ -238,3 +297,4 @@ bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex return QString::localeAwareCompare( leftFilter, rightFilter ) < 0; } + diff --git a/src/core/locator/qgslocatormodel.h b/src/core/locator/qgslocatormodel.h index 8c30be8fed9..5544a261216 100644 --- a/src/core/locator/qgslocatormodel.h +++ b/src/core/locator/qgslocatormodel.h @@ -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 @@ -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. @@ -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 @@ -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; }; diff --git a/tests/src/python/test_qgslocator.py b/tests/src/python/test_qgslocator.py index d66d229c775..6d0e356bbef 100644 --- a/tests/src/python/test_qgslocator.py +++ b/tests/src/python/test_qgslocator.py @@ -19,7 +19,8 @@ from qgis.core import (QgsLocator, 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 @@ -210,6 +211,51 @@ class TestQgsLocator(unittest.TestCase): 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()