35 , mResultsView( new QgsLocatorResultsView() )
39 mLineEdit->setPlaceholderText( tr(
"Type to locate (⌘K)" ) );
41 mLineEdit->setPlaceholderText( tr(
"Type to locate (Ctrl+K)" ) );
44 int placeholderMinWidth = mLineEdit->fontMetrics().width( mLineEdit->placeholderText() );
45 int minWidth = std::max( 200, static_cast< int >( placeholderMinWidth * 1.8 ) );
46 resize( minWidth, 30 );
47 QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
48 sizePolicy.setHorizontalStretch( 0 );
49 sizePolicy.setVerticalStretch( 0 );
50 setSizePolicy( sizePolicy );
51 setMinimumSize( QSize( minWidth, 0 ) );
53 QHBoxLayout *layout =
new QHBoxLayout();
54 layout->setMargin( 0 );
55 layout->setContentsMargins( 0, 0, 0, 0 );
56 layout->addWidget( mLineEdit );
59 setFocusProxy( mLineEdit );
67 QHBoxLayout *containerLayout =
new QHBoxLayout();
68 containerLayout->setMargin( 0 );
69 containerLayout->setContentsMargins( 0, 0, 0, 0 );
70 containerLayout->addWidget( mResultsView );
71 mResultsContainer->setLayout( containerLayout );
72 mResultsContainer->hide();
74 mResultsView->setModel( mModelBridge->
proxyModel() );
75 mResultsView->setUniformRowHeights(
true );
78 mResultsView->setIconSize( QSize( iconSize, iconSize ) );
79 mResultsView->recalculateSize();
80 mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
82 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsLocatorWidget::scheduleDelayedPopup );
83 connect( mResultsView, &QAbstractItemView::activated,
this, &QgsLocatorWidget::acceptCurrentEntry );
84 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
91 mPopupTimer.setInterval( 100 );
92 mPopupTimer.setSingleShot(
true );
93 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
94 mFocusTimer.setInterval( 110 );
95 mFocusTimer.setSingleShot(
true );
96 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
98 mLineEdit->installEventFilter(
this );
99 mResultsContainer->installEventFilter(
this );
100 mResultsView->installEventFilter(
this );
101 installEventFilter(
this );
102 window()->installEventFilter(
this );
106 mMenu =
new QMenu(
this );
107 QAction *menuAction = mLineEdit->addAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/search.svg" ) ), QLineEdit::LeadingPosition );
108 connect( menuAction, &QAction::triggered,
this, [ = ]
111 mResultsContainer->hide();
112 mMenu->exec( QCursor::pos() );
114 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
120 return mModelBridge->
locator();
125 if ( mMapCanvas == canvas )
128 for (
const QMetaObject::Connection &conn : qgis::as_const( mCanvasConnections ) )
132 mCanvasConnections.clear();
147 mLineEdit->setText(
string );
148 window()->activateWindow();
149 scheduleDelayedPopup();
150 mLineEdit->setFocus();
157 mResultsContainer->hide();
160 void QgsLocatorWidget::scheduleDelayedPopup()
165 void QgsLocatorWidget::resultAdded()
167 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
171 bool selectable =
false;
172 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
175 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
178 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
182 void QgsLocatorWidget::showContextMenu(
const QPoint &point )
184 QModelIndex index = mResultsView->indexAt( point );
185 if ( !index.isValid() )
188 const QList<QgsLocatorResult::ResultAction> actions = mResultsView->model()->data( index,
QgsLocatorModel::ResultActionsRole ).value<QList<QgsLocatorResult::ResultAction>>();
189 QMenu *contextMenu =
new QMenu( mResultsView );
190 for (
auto resultAction : actions )
192 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
193 if ( !resultAction.iconPath.isEmpty() )
194 menuAction->setIcon( QIcon( resultAction.iconPath ) );
195 connect( menuAction, &QAction::triggered,
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
196 contextMenu->addAction( menuAction );
198 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
201 void QgsLocatorWidget::performSearch()
208 void QgsLocatorWidget::showList()
210 mResultsContainer->show();
211 mResultsContainer->raise();
214 void QgsLocatorWidget::triggerSearchAndShowList()
216 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
224 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
226 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
227 switch ( keyEvent->key() )
232 case Qt::Key_PageDown:
233 triggerSearchAndShowList();
234 mHasSelectedResult =
true;
235 QgsApplication::sendEvent( mResultsView, event );
239 if ( keyEvent->modifiers() & Qt::ControlModifier )
241 triggerSearchAndShowList();
242 mHasSelectedResult =
true;
243 QgsApplication::sendEvent( mResultsView, event );
249 acceptCurrentEntry();
252 mResultsContainer->hide();
255 mHasSelectedResult =
true;
256 mResultsView->selectNextResult();
258 case Qt::Key_Backtab:
259 mHasSelectedResult =
true;
260 mResultsView->selectPreviousResult();
266 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
268 mHasSelectedResult =
true;
270 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
272 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
275 mResultsContainer->hide();
278 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
282 else if ( obj == window() && event->type() == QEvent::Resize )
284 mResultsView->recalculateSize();
286 return QWidget::eventFilter( obj, event );
289 void QgsLocatorWidget::configMenuAboutToShow()
294 if ( !filter->enabled() )
297 QAction *action =
new QAction( filter->displayName(), mMenu );
298 connect( action, &QAction::triggered,
this, [ = ]
300 QString currentText = mLineEdit->text();
301 if ( currentText.isEmpty() )
302 currentText = tr(
"<type here>" );
305 QStringList parts = currentText.split(
' ' );
306 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
309 currentText = parts.join(
' ' );
313 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
314 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
316 mMenu->addAction( action );
318 mMenu->addSeparator();
319 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
321 mMenu->addAction( configAction );
326 void QgsLocatorWidget::acceptCurrentEntry()
334 if ( !mResultsView->isVisible() )
337 QModelIndex index = mResultsView->currentIndex();
338 if ( !index.isValid() )
341 mResultsContainer->hide();
342 mLineEdit->clearFocus();
355 QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
356 : QTreeView( parent )
358 setRootIsDecorated(
false );
359 setUniformRowHeights(
true );
361 header()->setStretchLastSection(
true );
364 void QgsLocatorResultsView::recalculateSize()
367 int rowSize = 20 * itemDelegate()->sizeHint( viewOptions(), model()->index( 0, 0 ) ).height();
370 int width = std::max( 300, window()->size().width() / 2 );
371 QSize newSize( width, rowSize + frameWidth() * 2 );
373 parentWidget()->resize( newSize );
374 QTreeView::resize( newSize );
376 header()->resizeSection( 0, width / 2 );
377 header()->resizeSection( 1, 0 );
380 void QgsLocatorResultsView::selectNextResult()
382 int nextRow = currentIndex().row() + 1;
383 nextRow = nextRow % model()->rowCount( QModelIndex() );
384 setCurrentIndex( model()->index( nextRow, 0 ) );
387 void QgsLocatorResultsView::selectPreviousResult()
389 int previousRow = currentIndex().row() - 1;
390 if ( previousRow < 0 )
391 previousRow = model()->rowCount( QModelIndex() ) - 1;
392 setCurrentIndex( model()->index( previousRow, 0 ) );
401 , mLocator( locator )
404 QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const 406 return new QgsLocatorFilterFilter( mLocator );
409 QgsLocatorFilter::Flags QgsLocatorFilterFilter::flags()
const 416 if ( !
string.isEmpty() )
427 if ( filter ==
this || !filter || !filter->enabled() )
433 result.
userData = filter->activePrefix() +
' ';
435 emit resultFetched( result );
439 void QgsLocatorFilterFilter::triggerResult(
const QgsLocatorResult &result )
441 mLocator->search( result.
userData.toString() );
void registerFilter(QgsLocatorFilter *filter)
Registers a filter within the locator.
void updateCanvasCrs(const QgsCoordinateReferenceSystem &crs)
Update the canvas CRS used to create search context.
QIcon icon
Icon for result.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly...
Q_INVOKABLE void performSearch(const QString &text)
Perform a search.
The actions to be shown for the given result in a context menu.
void setShowSpinner(bool showSpinner)
Show a spinner icon.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QString description
Descriptive text for result.
The QgsLocatorModelBridge class provides the core functionality to be used in a locator widget...
QList< QgsLocatorFilter * > filters(const QString &prefix=QString())
Returns the list of filters registered in the locator.
void updateCanvasExtent(const QgsRectangle &extent)
Update the canvas extent used to create search context.
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes takes output image size into accou...
Map canvas is a class for displaying all GIS data types on a canvas.
QgsCoordinateReferenceSystem destinationCrs() const
returns CRS of destination coordinate reference system
Base class for feedback objects to be used for cancellation of something running in a worker thread...
QVariant userData
Custom reference or other data set by the filter.
void isRunningChanged()
Emitted when the running status changes.
void triggerResult(const QModelIndex &index, const int actionId=-1)
Triggers the result at given index and with optional actionId if an additional action was triggered...
QString displayString
String displayed for result.
QLineEdit subclass with built in support for clearing the widget's value and handling custom null val...
Encapsulates the properties relating to the context of a locator search.
void destinationCrsChanged()
Emitted when map CRS has changed.
Encapsulates properties of an individual matching result found by a QgsLocatorFilter.
Abstract base class for filters which collect locator results.
Handles the management of QgsLocatorFilter objects and async collection of search results from them...
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
void resultsCleared()
Emitted when the results are cleared.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
Q_INVOKABLE QgsLocatorProxyModel * proxyModel() const
Returns the proxy model.
bool isCanceled() const
Tells whether the operation has been canceled already.
void invalidateResults()
This will invalidate current search results.
bool hasQueueRequested() const
Returns true if some text to be search is pending in the queue.
Filter finds results quickly and can be safely run in the main thread.
void setShowClearButton(bool visible)
Sets whether the widget's clear button is visible.
void extentsChanged()
Emitted when the extents of the map change.
QgsLocator * locator() const
Returns the locator.
void resultAdded()
Emitted when a result is added.