Skip to content

Commit 71f7872

Browse files
committed
[FEATURE] Implement a QtCreator style locator bar in the QGIS status bar
This adds a new "locator" bar to the QGIS status bar. If you're not familiar with QtCreator's locator, it's a quick search bar (activated by Ctrl+K) which displays matching search results from any number of registered search filters. Search filters are subclassed from QgsLocatorFilter, and added to the app's locator via iface.registerLocatorFilter(...) Searching is handled using threads, so that results always become available as quickly as possible, regardless of whether any slow search filters may be installed. They also appear as soon as each result is encountered by each filter, which means that e.g. a file search filter will show results one by one as the file tree is scanned. This ensures that the UI is always responsive even if a very slow search filter is present (e.g. one which uses an online service). This framework is designed to be extended by plugins, such as OSM nominatim searches, direct database searching (i.e. Discovery plugin), layer catalog searches, etc...
1 parent afc9788 commit 71f7872

21 files changed

+1412
-1
lines changed

doc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ IF(WITH_APIDOC)
7878
${CMAKE_SOURCE_DIR}/src/gui/editorwidgets/core
7979
${CMAKE_SOURCE_DIR}/src/gui/effects
8080
${CMAKE_SOURCE_DIR}/src/gui/layertree
81+
${CMAKE_SOURCE_DIR}/src/gui/locator
8182
${CMAKE_SOURCE_DIR}/src/gui/raster
8283
${CMAKE_SOURCE_DIR}/src/gui/symbology-ng
8384
${CMAKE_SOURCE_DIR}/src/analysis

python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ INCLUDE_DIRECTORIES(
122122
../src/gui/editorwidgets/core
123123
../src/gui/effects
124124
../src/gui/layertree
125+
../src/gui/locator
125126

126127
../src/plugins
127128

python/gui/gui.sip

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@
211211
%Include layertree/qgslayertreeview.sip
212212
%Include layertree/qgslayertreeviewdefaultactions.sip
213213

214+
%Include locator/qgslocator.sip
215+
%Include locator/qgslocatorfilter.sip
216+
214217
%Include raster/qgsmultibandcolorrendererwidget.sip
215218
%Include raster/qgspalettedrendererwidget.sip
216219
%Include raster/qgsrasterbandcombobox.sip

python/gui/locator/qgslocator.sip

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/locator/qgslocator.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
class QgsLocator : QObject
13+
{
14+
%Docstring
15+
Handles the management of QgsLocatorFilter objects and async collection of search results from them.
16+
17+
QgsLocator acts as both a registry for QgsLocatorFilter objects and a means of firing up
18+
asynchronous queries against these filter objects.
19+
20+
Filters are first registered to the locator by calling registerFilter(). Registering filters
21+
transfers their ownership to the locator object. Plugins which register filters to the locator
22+
must take care to correctly call deregisterFilter() and deregister their filter upon plugin
23+
unload to avoid crashes.
24+
25+
In order to trigger a search across registered filters, the fetchResults() method is called.
26+
This triggers threaded calls to QgsLocatorFilter.fetchResults() for all registered filters.
27+
As individual filters find matching results, the foundResult() signal will be triggered
28+
for each result. Callers should connect this signal to an appropriate slot designed
29+
to collect and handle these results. Since foundResult() is triggered whenever a filter
30+
encounters an individual result, it will usually be triggered many times for a single
31+
call to fetchResults().
32+
33+
.. versionadded:: 3.0
34+
%End
35+
36+
%TypeHeaderCode
37+
#include "qgslocator.h"
38+
%End
39+
public:
40+
41+
QgsLocator( QObject *parent /TransferThis/ = 0 );
42+
%Docstring
43+
Constructor for QgsLocator.
44+
%End
45+
46+
~QgsLocator();
47+
%Docstring
48+
Destructor for QgsLocator. Destruction will block while any currently running query is terminated.
49+
%End
50+
51+
void registerFilter( QgsLocatorFilter *filter /Transfer/ );
52+
%Docstring
53+
Registers a ``filter`` within the locator. Ownership of the filter is transferred to the
54+
locator.
55+
\warning Plugins which register filters to the locator must take care to correctly call
56+
deregisterFilter() and deregister their filters upon plugin unload to avoid crashes.
57+
.. seealso:: deregisterFilter()
58+
%End
59+
60+
void deregisterFilter( QgsLocatorFilter *filter );
61+
%Docstring
62+
Deregisters a ``filter`` from the locator and deletes it. Calling this will block whilst
63+
any currently running query is terminated.
64+
65+
Plugins which register filters to the locator must take care to correctly call
66+
deregisterFilter() to deregister their filters upon plugin unload to avoid crashes.
67+
68+
.. seealso:: registerFilter()
69+
%End
70+
71+
QList< QgsLocatorFilter *> filters();
72+
%Docstring
73+
Returns the list of filters registered in the locator.
74+
:rtype: list of QgsLocatorFilter
75+
%End
76+
77+
void fetchResults( const QString &string, QgsFeedback *feedback = 0 );
78+
%Docstring
79+
Triggers the background fetching of filter results for a specified search ``string``.
80+
If specified, the ``feedback`` object must exist for the lifetime of this query.
81+
82+
The foundResult() signal will be emitted for each individual result encountered
83+
by the registered filters.
84+
%End
85+
86+
void cancel();
87+
%Docstring
88+
Cancels any current running query, and blocks until query is completely canceled by
89+
all filters.
90+
.. seealso:: cancelWithoutBlocking()
91+
%End
92+
93+
void cancelWithoutBlocking();
94+
%Docstring
95+
Triggers cancelation of any current running query without blocking. The query may
96+
take some time to cancel after calling this.
97+
.. seealso:: cancel()
98+
%End
99+
100+
bool isRunning() const;
101+
%Docstring
102+
Returns true if a query is currently being executed by the locator.
103+
:rtype: bool
104+
%End
105+
106+
signals:
107+
108+
void foundResult( const QgsLocatorResult &result );
109+
%Docstring
110+
Emitted whenever a filter encounters a matching ``result`` after the fetchResults() method
111+
is called.
112+
%End
113+
114+
void finished();
115+
%Docstring
116+
Emitted when locator has finished a query, either as a result
117+
of successful completion or early cancelation.
118+
%End
119+
120+
};
121+
122+
123+
124+
/************************************************************************
125+
* This file has been generated automatically from *
126+
* *
127+
* src/gui/locator/qgslocator.h *
128+
* *
129+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
130+
************************************************************************/
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/locator/qgslocatorfilter.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
13+
class QgsLocatorResult
14+
{
15+
%Docstring
16+
Encapsulates properties of an individual matching result found by a QgsLocatorFilter.
17+
.. versionadded:: 3.0
18+
%End
19+
20+
%TypeHeaderCode
21+
#include "qgslocatorfilter.h"
22+
%End
23+
public:
24+
25+
QgsLocatorResult();
26+
27+
QgsLocatorResult( QgsLocatorFilter *filter, const QString &displayString, const QVariant &userData = QVariant() );
28+
%Docstring
29+
Constructor for QgsLocatorResult.
30+
%End
31+
32+
QgsLocatorFilter *filter;
33+
%Docstring
34+
Filter from which the result was obtained.
35+
%End
36+
37+
QString displayString;
38+
%Docstring
39+
String displayed for result.
40+
%End
41+
42+
QVariant userData;
43+
%Docstring
44+
Custom reference or other data set by the filter.
45+
%End
46+
47+
};
48+
49+
class QgsLocatorFilter : QObject
50+
{
51+
%Docstring
52+
Abstract base class for filters which collect locator results.
53+
.. versionadded:: 3.0
54+
%End
55+
56+
%TypeHeaderCode
57+
#include "qgslocatorfilter.h"
58+
%End
59+
public:
60+
61+
QgsLocatorFilter( QObject *parent = 0 );
62+
%Docstring
63+
Constructor for QgsLocatorFilter.
64+
%End
65+
66+
virtual void fetchResults( const QString &string, QgsFeedback *feedback ) = 0;
67+
%Docstring
68+
Retrieves the filter results for a specified search ``string``.
69+
70+
Implementations of fetchResults() should emit the resultFetched()
71+
signal whenever they encounter a matching result.
72+
73+
Subclasses should periodically check the ``feedback`` object to determine
74+
whether the query has been canceled. If so, the subclass should return
75+
from this method as soon as possible.
76+
%End
77+
78+
virtual void triggerResult( const QgsLocatorResult &result ) = 0;
79+
%Docstring
80+
Triggers a filter ``result`` from this filter. This is called when
81+
one of the results obtained by a call to fetchResults() is triggered
82+
by a user. The filter subclass must implement logic here
83+
to perform the desired operation for the search result.
84+
E.g. a file search filter would open file associated with the triggered
85+
result.
86+
%End
87+
88+
signals:
89+
90+
void resultFetched( const QgsLocatorResult &result );
91+
%Docstring
92+
Should be emitted by filters whenever they encounter a matching result
93+
during within their fetchResults() implementation.
94+
%End
95+
96+
};
97+
98+
99+
100+
101+
/************************************************************************
102+
* This file has been generated automatically from *
103+
* *
104+
* src/gui/locator/qgslocatorfilter.h *
105+
* *
106+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
107+
************************************************************************/

python/gui/qgisinterface.sip

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,28 @@ class QgisInterface : QObject
509509
/** Get timeout for timed messages: default of 5 seconds */
510510
virtual int messageTimeout() = 0;
511511

512+
/**
513+
* Registers a locator \a filter for the app's locator bar. Ownership of the filter is transferred to the
514+
* locator.
515+
* \warning Plugins which register filters to the locator bar must take care to correctly call
516+
* deregisterLocatorFilter() and deregister their filters upon plugin unload to avoid crashes.
517+
* \see deregisterLocatorFilter()
518+
* \since QGIS 3.0
519+
*/
520+
virtual void registerLocatorFilter( QgsLocatorFilter *filter /Transfer/ ) = 0;
521+
522+
/**
523+
* Deregisters a locator \a filter from the app's locator bar and deletes it. Calling this will block whilst
524+
* any currently running query is terminated.
525+
*
526+
* Plugins which register filters to the locator bar must take care to correctly call
527+
* deregisterLocatorFilter() to deregister their filters upon plugin unload to avoid crashes.
528+
*
529+
* \see registerLocatorFilter()
530+
* \since QGIS 3.0
531+
*/
532+
virtual void deregisterLocatorFilter( QgsLocatorFilter *filter ) = 0;
533+
512534
signals:
513535
void currentLayerChanged( QgsMapLayer *layer );
514536
void currentThemeChanged( const QString &theme );

src/app/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ INCLUDE_DIRECTORIES(
509509
../gui/symbology-ng
510510
../gui/attributetable
511511
../gui/auth
512+
../gui/locator
512513
../gui/raster
513514
../gui/editorwidgets
514515
../gui/editorwidgets/core

src/app/qgisapp.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
188188
#include "qgslayertreeview.h"
189189
#include "qgslayertreeviewdefaultactions.h"
190190
#include "qgslayoutmanager.h"
191+
#include "qgslocatorwidget.h"
191192
#include "qgslogger.h"
192193
#include "qgsmapcanvas.h"
193194
#include "qgsmapcanvasdockwidget.h"
@@ -2630,11 +2631,18 @@ void QgisApp::createStatusBar()
26302631
mMessageButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mMessageLogRead.svg" ) ) );
26312632
mMessageButton->setToolTip( tr( "Messages" ) );
26322633
mMessageButton->setWhatsThis( tr( "Messages" ) );
2633-
mMessageButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
26342634
mMessageButton->setObjectName( QStringLiteral( "mMessageLogViewerButton" ) );
26352635
mMessageButton->setMaximumHeight( mScaleWidget->height() );
26362636
mMessageButton->setCheckable( true );
26372637
statusBar()->addPermanentWidget( mMessageButton, 0 );
2638+
2639+
mLocatorWidget = new QgsLocatorWidget( statusBar() );
2640+
statusBar()->addPermanentWidget( mLocatorWidget );
2641+
QShortcut *locatorShortCut = new QShortcut( QKeySequence( tr( "Ctrl+K" ) ), this );
2642+
connect( locatorShortCut, &QShortcut::activated, mLocatorWidget, [ = ] { mLocatorWidget->search( QString() ); } );
2643+
locatorShortCut->setObjectName( QStringLiteral( "Locator" ) );
2644+
locatorShortCut->setWhatsThis( tr( "Trigger Locator" ) );
2645+
26382646
}
26392647

26402648
void QgisApp::setIconSizes( int size )

src/app/qgisapp.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class QgsTileScaleWidget;
115115
class QgsLabelingWidget;
116116
class QgsLayerStylingWidget;
117117
class QgsDiagramProperties;
118+
class QgsLocatorWidget;
118119

119120
#include <QMainWindow>
120121
#include <QToolBar>
@@ -1974,6 +1975,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
19741975

19751976
QSystemTrayIcon *mTray = nullptr;
19761977

1978+
QgsLocatorWidget *mLocatorWidget = nullptr;
1979+
19771980
friend class TestQgisAppPython;
19781981
};
19791982

src/app/qgisappinterface.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
#include "qgsfeatureaction.h"
4545
#include "qgsactionmanager.h"
4646
#include "qgsattributetabledialog.h"
47+
#include "qgslocatorwidget.h"
48+
#include "qgslocator.h"
4749

4850

4951
QgisAppInterface::QgisAppInterface( QgisApp *_qgis )
@@ -733,3 +735,13 @@ int QgisAppInterface::messageTimeout()
733735
{
734736
return qgis->messageTimeout();
735737
}
738+
739+
void QgisAppInterface::registerLocatorFilter( QgsLocatorFilter *filter )
740+
{
741+
qgis->mLocatorWidget->locator()->registerFilter( filter );
742+
}
743+
744+
void QgisAppInterface::deregisterLocatorFilter( QgsLocatorFilter *filter )
745+
{
746+
qgis->mLocatorWidget->locator()->deregisterFilter( filter );
747+
}

0 commit comments

Comments
 (0)