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 mLineEdit->setFocus();
156 mResultsContainer->hide();
159 void QgsLocatorWidget::scheduleDelayedPopup()
164 void QgsLocatorWidget::resultAdded()
166 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
170 bool selectable =
false;
171 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
174 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
177 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
181 void QgsLocatorWidget::showContextMenu(
const QPoint &point )
183 QModelIndex index = mResultsView->indexAt( point );
184 if ( !index.isValid() )
187 const QList<QgsLocatorResult::ResultAction> actions = mResultsView->model()->data( index,
QgsLocatorModel::ResultActionsRole ).value<QList<QgsLocatorResult::ResultAction>>();
188 QMenu *contextMenu =
new QMenu( mResultsView );
189 for (
auto resultAction : actions )
191 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
192 if ( !resultAction.iconPath.isEmpty() )
193 menuAction->setIcon( QIcon( resultAction.iconPath ) );
194 connect( menuAction, &QAction::triggered,
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
195 contextMenu->addAction( menuAction );
197 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
200 void QgsLocatorWidget::performSearch()
207 void QgsLocatorWidget::showList()
209 mResultsContainer->show();
210 mResultsContainer->raise();
213 void QgsLocatorWidget::triggerSearchAndShowList()
215 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
223 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
225 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
226 switch ( keyEvent->key() )
231 case Qt::Key_PageDown:
232 triggerSearchAndShowList();
233 mHasSelectedResult =
true;
234 QgsApplication::sendEvent( mResultsView, event );
238 if ( keyEvent->modifiers() & Qt::ControlModifier )
240 triggerSearchAndShowList();
241 mHasSelectedResult =
true;
242 QgsApplication::sendEvent( mResultsView, event );
248 acceptCurrentEntry();
251 mResultsContainer->hide();
254 mHasSelectedResult =
true;
255 mResultsView->selectNextResult();
257 case Qt::Key_Backtab:
258 mHasSelectedResult =
true;
259 mResultsView->selectPreviousResult();
265 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
267 mHasSelectedResult =
true;
269 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
271 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
274 mResultsContainer->hide();
277 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
281 else if ( obj == window() && event->type() == QEvent::Resize )
283 mResultsView->recalculateSize();
285 return QWidget::eventFilter( obj, event );
288 void QgsLocatorWidget::configMenuAboutToShow()
293 if ( !filter->enabled() )
296 QAction *action =
new QAction( filter->displayName(), mMenu );
297 connect( action, &QAction::triggered,
this, [ = ]
299 QString currentText = mLineEdit->text();
300 if ( currentText.isEmpty() )
301 currentText = tr(
"<type here>" );
304 QStringList parts = currentText.split(
' ' );
305 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
308 currentText = parts.join(
' ' );
312 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
313 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
315 mMenu->addAction( action );
317 mMenu->addSeparator();
318 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
320 mMenu->addAction( configAction );
325 void QgsLocatorWidget::acceptCurrentEntry()
333 if ( !mResultsView->isVisible() )
336 QModelIndex index = mResultsView->currentIndex();
337 if ( !index.isValid() )
340 mResultsContainer->hide();
341 mLineEdit->clearFocus();
354 QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
355 : QTreeView( parent )
357 setRootIsDecorated(
false );
358 setUniformRowHeights(
true );
360 header()->setStretchLastSection(
true );
363 void QgsLocatorResultsView::recalculateSize()
366 int rowSize = 20 * itemDelegate()->sizeHint( viewOptions(), model()->index( 0, 0 ) ).height();
369 int width = std::max( 300, window()->size().width() / 2 );
370 QSize newSize( width, rowSize + frameWidth() * 2 );
372 parentWidget()->resize( newSize );
373 QTreeView::resize( newSize );
375 header()->resizeSection( 0, width / 2 );
376 header()->resizeSection( 1, 0 );
379 void QgsLocatorResultsView::selectNextResult()
381 int nextRow = currentIndex().row() + 1;
382 nextRow = nextRow % model()->rowCount( QModelIndex() );
383 setCurrentIndex( model()->index( nextRow, 0 ) );
386 void QgsLocatorResultsView::selectPreviousResult()
388 int previousRow = currentIndex().row() - 1;
389 if ( previousRow < 0 )
390 previousRow = model()->rowCount( QModelIndex() ) - 1;
391 setCurrentIndex( model()->index( previousRow, 0 ) );
400 , mLocator( locator )
403 QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const 405 return new QgsLocatorFilterFilter( mLocator );
408 QgsLocatorFilter::Flags QgsLocatorFilterFilter::flags()
const 415 if ( !
string.isEmpty() )
426 if ( filter ==
this || !filter || !filter->enabled() )
432 result.
userData = filter->activePrefix() +
' ';
434 emit resultFetched( result );
438 void QgsLocatorFilterFilter::triggerResult(
const QgsLocatorResult &result )
440 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.