37 , mLineEdit( new QgsLocatorLineEdit( this ) )
38 , mResultsView( new QgsLocatorResultsView() )
40 setObjectName( QStringLiteral(
"LocatorWidget" ) );
41 mLineEdit->setShowClearButton(
true );
43 mLineEdit->setPlaceholderText( tr(
"Type to locate (⌘K)" ) );
45 mLineEdit->setPlaceholderText( tr(
"Type to locate (Ctrl+K)" ) );
48 int placeholderMinWidth = mLineEdit->fontMetrics().boundingRect( mLineEdit->placeholderText() ).width();
49 int minWidth = std::max( 200,
static_cast< int >( placeholderMinWidth * 1.8 ) );
50 resize( minWidth, 30 );
51 QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
52 sizePolicy.setHorizontalStretch( 0 );
53 sizePolicy.setVerticalStretch( 0 );
54 setSizePolicy( sizePolicy );
55 setMinimumSize( QSize( minWidth, 0 ) );
57 QHBoxLayout *layout =
new QHBoxLayout();
58 layout->setContentsMargins( 0, 0, 0, 0 );
59 layout->addWidget( mLineEdit );
62 setFocusProxy( mLineEdit );
70 QHBoxLayout *containerLayout =
new QHBoxLayout();
71 containerLayout->setContentsMargins( 0, 0, 0, 0 );
72 containerLayout->addWidget( mResultsView );
73 mResultsContainer->setLayout( containerLayout );
74 mResultsContainer->hide();
76 mResultsView->setModel( mModelBridge->
proxyModel() );
77 mResultsView->setUniformRowHeights(
true );
80 mResultsView->setIconSize( QSize( iconSize, iconSize ) );
81 mResultsView->recalculateSize();
82 mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
84 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsLocatorWidget::scheduleDelayedPopup );
85 connect( mResultsView, &QAbstractItemView::activated,
this, &QgsLocatorWidget::acceptCurrentEntry );
86 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
93 mPopupTimer.setInterval( 100 );
94 mPopupTimer.setSingleShot(
true );
95 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
96 mFocusTimer.setInterval( 110 );
97 mFocusTimer.setSingleShot(
true );
98 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
100 mLineEdit->installEventFilter(
this );
101 mResultsContainer->installEventFilter(
this );
102 mResultsView->installEventFilter(
this );
103 installEventFilter(
this );
104 window()->installEventFilter(
this );
108 mMenu =
new QMenu(
this );
109 QAction *menuAction = mLineEdit->addAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/search.svg" ) ), QLineEdit::LeadingPosition );
110 connect( menuAction, &QAction::triggered,
this, [ = ]
113 mResultsContainer->hide();
114 mMenu->exec( QCursor::pos() );
116 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
128 return mModelBridge->
locator();
133 if ( mMapCanvas == canvas )
136 for (
const QMetaObject::Connection &conn : std::as_const( mCanvasConnections ) )
140 mCanvasConnections.clear();
155 mLineEdit->setPlaceholderText( text );
166 window()->activateWindow();
167 if (
string.isEmpty() )
169 mLineEdit->setFocus();
170 mLineEdit->selectAll();
174 scheduleDelayedPopup();
175 mLineEdit->setFocus();
176 mLineEdit->setText(
string );
184 mResultsContainer->hide();
187void QgsLocatorWidget::scheduleDelayedPopup()
192void QgsLocatorWidget::resultAdded()
194 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
198 bool selectable =
false;
199 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
202 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
205 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
209void QgsLocatorWidget::showContextMenu(
const QPoint &point )
211 QModelIndex index = mResultsView->indexAt( point );
212 if ( !index.isValid() )
216 QMenu *contextMenu =
new QMenu( mResultsView );
217 for (
auto resultAction : actions )
219 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
220 if ( !resultAction.iconPath.isEmpty() )
221 menuAction->setIcon( QIcon( resultAction.iconPath ) );
222 connect( menuAction, &QAction::triggered,
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
223 contextMenu->addAction( menuAction );
225 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
228void QgsLocatorWidget::performSearch()
235void QgsLocatorWidget::showList()
237 mResultsContainer->show();
238 mResultsContainer->raise();
241void QgsLocatorWidget::triggerSearchAndShowList()
243 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
251 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
253 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
254 switch ( keyEvent->key() )
259 case Qt::Key_PageDown:
260 triggerSearchAndShowList();
261 mHasSelectedResult =
true;
262 QgsApplication::sendEvent( mResultsView, event );
266 if ( keyEvent->modifiers() & Qt::ControlModifier )
268 triggerSearchAndShowList();
269 mHasSelectedResult =
true;
270 QgsApplication::sendEvent( mResultsView, event );
276 acceptCurrentEntry();
279 mResultsContainer->hide();
282 if ( !mLineEdit->performCompletion() )
284 mHasSelectedResult =
true;
285 mResultsView->selectNextResult();
288 case Qt::Key_Backtab:
289 mHasSelectedResult =
true;
290 mResultsView->selectPreviousResult();
296 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
298 mHasSelectedResult =
true;
300 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
302 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
305 mResultsContainer->hide();
308 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
312 else if ( obj == window() && event->type() == QEvent::Resize )
314 mResultsView->recalculateSize();
316 return QWidget::eventFilter( obj, event );
319void QgsLocatorWidget::configMenuAboutToShow()
324 if ( !filter->enabled() )
327 QAction *action =
new QAction( filter->displayName(), mMenu );
328 connect( action, &QAction::triggered,
this, [ = ]
330 QString currentText = mLineEdit->text();
331 if ( currentText.isEmpty() )
332 currentText = tr(
"<type here>" );
335 QStringList parts = currentText.split(
' ' );
336 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
339 currentText = parts.join(
' ' );
343 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
344 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
346 mMenu->addAction( action );
348 mMenu->addSeparator();
349 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
351 mMenu->addAction( configAction );
355void QgsLocatorWidget::acceptCurrentEntry()
363 if ( !mResultsView->isVisible() )
366 QModelIndex index = mResultsView->currentIndex();
367 if ( !index.isValid() )
370 mResultsContainer->hide();
371 mLineEdit->clearFocus();
382QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
383 : QTreeView( parent )
385 setRootIsDecorated(
false );
386 setUniformRowHeights(
true );
388 header()->setStretchLastSection(
true );
391void QgsLocatorResultsView::recalculateSize()
393 QStyleOptionViewItem optView;
394#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
395 optView.init(
this );
397 optView.initFrom(
this );
401 int rowSize = 20 * itemDelegate()->sizeHint( optView, model()->index( 0, 0 ) ).height();
404 int width = std::max( 300, window()->size().width() / 2 );
405 QSize newSize( width, rowSize + frameWidth() * 2 );
407 parentWidget()->resize( newSize );
408 QTreeView::resize( newSize );
410 header()->resizeSection( 0, width / 2 );
411 header()->resizeSection( 1, 0 );
414void QgsLocatorResultsView::selectNextResult()
416 const int rowCount = model()->rowCount( QModelIndex() );
420 int nextRow = currentIndex().row() + 1;
421 nextRow = nextRow % rowCount;
422 setCurrentIndex( model()->index( nextRow, 0 ) );
425void QgsLocatorResultsView::selectPreviousResult()
427 const int rowCount = model()->rowCount( QModelIndex() );
431 int previousRow = currentIndex().row() - 1;
432 if ( previousRow < 0 )
433 previousRow = rowCount - 1;
434 setCurrentIndex( model()->index( previousRow, 0 ) );
441QgsLocatorFilterFilter::QgsLocatorFilterFilter(
QgsLocatorWidget *locator, QObject *parent )
443 , mLocator( locator )
446QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const
448 return new QgsLocatorFilterFilter( mLocator );
451QgsLocatorFilter::Flags QgsLocatorFilterFilter::flags()
const
458 if ( !
string.isEmpty() )
469 if ( filter ==
this || !filter || !filter->enabled() )
475 result.
setUserData( QString( filter->activePrefix() +
' ' ) );
477 emit resultFetched( result );
483 mLocator->search( result.
getUserData().toString() );
486QgsLocatorLineEdit::QgsLocatorLineEdit(
QgsLocatorWidget *locator, QWidget *parent )
488 , mLocatorWidget( locator )
493void QgsLocatorLineEdit::paintEvent( QPaintEvent *event )
501 QLineEdit::paintEvent( event );
506 QString currentText = text();
508 if ( currentText.length() == 0 || cursorPosition() < currentText.length() )
511 const QStringList completionList = mLocatorWidget->locator()->completionList();
513 mCompletionText.clear();
515 for (
const QString &candidate : completionList )
517 if ( candidate.startsWith( currentText ) )
519 completion = candidate.right( candidate.length() - currentText.length() );
520 mCompletionText = candidate;
525 if ( completion.isEmpty() )
530 QRect cr = cursorRect();
531 QPoint pos = cr.topRight() - QPoint( cr.width() / 2, 0 );
533 QTextLayout l( completion, font() );
535 QTextLine line = l.createLine();
536 line.setLineWidth( width() - pos.x() );
537 line.setPosition( pos );
541 p.setPen( QPen( Qt::gray, 1 ) );
542 l.draw( &p, QPoint( 0, 0 ) );
545bool QgsLocatorLineEdit::performCompletion()
547 if ( !mCompletionText.isEmpty() )
549 setText( mCompletionText );
550 mCompletionText.clear();
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
bool isCanceled() const
Tells whether the operation has been canceled already.
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.
Abstract base class for filters which collect locator results.
@ FlagFast
Filter finds results quickly and can be safely run in the main thread.
The QgsLocatorModelBridge class provides the core functionality to be used in a locator widget.
Q_INVOKABLE QgsLocatorProxyModel * proxyModel() const
Returns the proxy model.
void isRunningChanged()
Emitted when the running status changes.
void resultAdded()
Emitted when a result is added.
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.
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which should be used whenever the locator constructs a coordin...
QgsLocator * locator() const
Returns the locator.
bool hasQueueRequested() const
Returns true if some text to be search is pending in the queue.
Q_INVOKABLE void performSearch(const QString &text)
Perform a search.
void resultsCleared()
Emitted when the results are cleared.
void updateCanvasCrs(const QgsCoordinateReferenceSystem &crs)
Update the canvas CRS used to create search context.
void updateCanvasExtent(const QgsRectangle &extent)
Update the canvas extent used to create search context.
void invalidateResults()
This will invalidate current search results.
@ ResultActions
The actions to be shown for the given result in a context menu.
Encapsulates properties of an individual matching result found by a QgsLocatorFilter.
QVariant getUserData() const
Returns the userData.
QString description
Descriptive text for result.
void setUserData(QVariant userData)
Set userData for the locator result.
QString displayString
String displayed for result.
QIcon icon
Icon for result.
Handles the management of QgsLocatorFilter objects and async collection of search results from them.
void searchPrepared()
Emitted when locator has prepared the search (.
void registerFilter(QgsLocatorFilter *filter)
Registers a filter within the locator.
QList< QgsLocatorFilter * > filters(const QString &prefix=QString())
Returns the list of filters registered in the locator.
Map canvas is a class for displaying all GIS data types on a canvas.
void extentsChanged()
Emitted when the extents of the map change.
void destinationCrsChanged()
Emitted when map CRS has changed.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
static QgsProject * instance()
Returns the QgsProject singleton instance.
void transformContextChanged()
Emitted when the project transformContext() is changed.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...