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 window()->activateWindow();
156 if (
string.isEmpty() )
158 mLineEdit->setFocus();
159 mLineEdit->selectAll();
163 scheduleDelayedPopup();
164 mLineEdit->setFocus();
165 mLineEdit->setText(
string );
173 mResultsContainer->hide();
176void QgsLocatorWidget::scheduleDelayedPopup()
181void QgsLocatorWidget::resultAdded()
183 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
187 bool selectable =
false;
188 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
191 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
194 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
198void QgsLocatorWidget::showContextMenu(
const QPoint &point )
200 QModelIndex index = mResultsView->indexAt( point );
201 if ( !index.isValid() )
204 const QList<QgsLocatorResult::ResultAction> actions = mResultsView->model()->data( index,
QgsLocatorModel::ResultActionsRole ).value<QList<QgsLocatorResult::ResultAction>>();
205 QMenu *contextMenu =
new QMenu( mResultsView );
206 for (
auto resultAction : actions )
208 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
209 if ( !resultAction.iconPath.isEmpty() )
210 menuAction->setIcon( QIcon( resultAction.iconPath ) );
211 connect( menuAction, &QAction::triggered,
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
212 contextMenu->addAction( menuAction );
214 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
217void QgsLocatorWidget::performSearch()
224void QgsLocatorWidget::showList()
226 mResultsContainer->show();
227 mResultsContainer->raise();
230void QgsLocatorWidget::triggerSearchAndShowList()
232 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
240 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
242 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
243 switch ( keyEvent->key() )
248 case Qt::Key_PageDown:
249 triggerSearchAndShowList();
250 mHasSelectedResult =
true;
251 QgsApplication::sendEvent( mResultsView, event );
255 if ( keyEvent->modifiers() & Qt::ControlModifier )
257 triggerSearchAndShowList();
258 mHasSelectedResult =
true;
259 QgsApplication::sendEvent( mResultsView, event );
265 acceptCurrentEntry();
268 mResultsContainer->hide();
271 if ( !mLineEdit->performCompletion() )
273 mHasSelectedResult =
true;
274 mResultsView->selectNextResult();
277 case Qt::Key_Backtab:
278 mHasSelectedResult =
true;
279 mResultsView->selectPreviousResult();
285 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
287 mHasSelectedResult =
true;
289 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
291 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
294 mResultsContainer->hide();
297 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
301 else if ( obj == window() && event->type() == QEvent::Resize )
303 mResultsView->recalculateSize();
305 return QWidget::eventFilter( obj, event );
308void QgsLocatorWidget::configMenuAboutToShow()
313 if ( !filter->enabled() )
316 QAction *action =
new QAction( filter->displayName(), mMenu );
317 connect( action, &QAction::triggered,
this, [ = ]
319 QString currentText = mLineEdit->text();
320 if ( currentText.isEmpty() )
321 currentText = tr(
"<type here>" );
324 QStringList parts = currentText.split(
' ' );
325 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
328 currentText = parts.join(
' ' );
332 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
333 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
335 mMenu->addAction( action );
337 mMenu->addSeparator();
338 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
340 mMenu->addAction( configAction );
344void QgsLocatorWidget::acceptCurrentEntry()
352 if ( !mResultsView->isVisible() )
355 QModelIndex index = mResultsView->currentIndex();
356 if ( !index.isValid() )
359 mResultsContainer->hide();
360 mLineEdit->clearFocus();
371QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
372 : QTreeView( parent )
374 setRootIsDecorated(
false );
375 setUniformRowHeights(
true );
377 header()->setStretchLastSection(
true );
380void QgsLocatorResultsView::recalculateSize()
382 QStyleOptionViewItem optView;
383#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
384 optView.init(
this );
386 optView.initFrom(
this );
390 int rowSize = 20 * itemDelegate()->sizeHint( optView, model()->index( 0, 0 ) ).height();
393 int width = std::max( 300, window()->size().width() / 2 );
394 QSize newSize( width, rowSize + frameWidth() * 2 );
396 parentWidget()->resize( newSize );
397 QTreeView::resize( newSize );
399 header()->resizeSection( 0, width / 2 );
400 header()->resizeSection( 1, 0 );
403void QgsLocatorResultsView::selectNextResult()
405 const int rowCount = model()->rowCount( QModelIndex() );
409 int nextRow = currentIndex().row() + 1;
410 nextRow = nextRow % rowCount;
411 setCurrentIndex( model()->index( nextRow, 0 ) );
414void QgsLocatorResultsView::selectPreviousResult()
416 const int rowCount = model()->rowCount( QModelIndex() );
420 int previousRow = currentIndex().row() - 1;
421 if ( previousRow < 0 )
422 previousRow = rowCount - 1;
423 setCurrentIndex( model()->index( previousRow, 0 ) );
430QgsLocatorFilterFilter::QgsLocatorFilterFilter(
QgsLocatorWidget *locator, QObject *parent )
432 , mLocator( locator )
435QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const
437 return new QgsLocatorFilterFilter( mLocator );
440QgsLocatorFilter::Flags QgsLocatorFilterFilter::flags()
const
447 if ( !
string.isEmpty() )
458 if ( filter ==
this || !filter || !filter->enabled() )
464 result.
setUserData( QString( filter->activePrefix() +
' ' ) );
466 emit resultFetched( result );
472 mLocator->search( result.
getUserData().toString() );
475QgsLocatorLineEdit::QgsLocatorLineEdit(
QgsLocatorWidget *locator, QWidget *parent )
477 , mLocatorWidget( locator )
482void QgsLocatorLineEdit::paintEvent( QPaintEvent *event )
490 QLineEdit::paintEvent( event );
495 QString currentText = text();
497 if ( currentText.length() == 0 || cursorPosition() < currentText.length() )
500 const QStringList completionList = mLocatorWidget->locator()->completionList();
502 mCompletionText.clear();
504 for (
const QString &candidate : completionList )
506 if ( candidate.startsWith( currentText ) )
508 completion = candidate.right( candidate.length() - currentText.length() );
509 mCompletionText = candidate;
514 if ( completion.isEmpty() )
519 QRect cr = cursorRect();
520 QPoint pos = cr.topRight() - QPoint( cr.width() / 2, 0 );
522 QTextLayout l( completion, font() );
524 QTextLine line = l.createLine();
525 line.setLineWidth( width() - pos.x() );
526 line.setPosition( pos );
530 p.setPen( QPen( Qt::gray, 1 ) );
531 l.draw( &p, QPoint( 0, 0 ) );
534bool QgsLocatorLineEdit::performCompletion()
536 if ( !mCompletionText.isEmpty() )
538 setText( mCompletionText );
539 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.
@ ResultActionsRole
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,...