37 , mLineEdit( new QgsLocatorLineEdit( this ) )
38 , mResultsView( new QgsLocatorResultsView() )
40 mLineEdit->setShowClearButton(
true );
42 mLineEdit->setPlaceholderText( tr(
"Type to locate (⌘K)" ) );
44 mLineEdit->setPlaceholderText( tr(
"Type to locate (Ctrl+K)" ) );
47 int placeholderMinWidth = mLineEdit->fontMetrics().boundingRect( mLineEdit->placeholderText() ).width();
48 int minWidth = std::max( 200,
static_cast< int >( placeholderMinWidth * 1.8 ) );
49 resize( minWidth, 30 );
50 QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
51 sizePolicy.setHorizontalStretch( 0 );
52 sizePolicy.setVerticalStretch( 0 );
53 setSizePolicy( sizePolicy );
54 setMinimumSize( QSize( minWidth, 0 ) );
56 QHBoxLayout *layout =
new QHBoxLayout();
57 layout->setContentsMargins( 0, 0, 0, 0 );
58 layout->addWidget( mLineEdit );
61 setFocusProxy( mLineEdit );
69 QHBoxLayout *containerLayout =
new QHBoxLayout();
70 containerLayout->setContentsMargins( 0, 0, 0, 0 );
71 containerLayout->addWidget( mResultsView );
72 mResultsContainer->setLayout( containerLayout );
73 mResultsContainer->hide();
75 mResultsView->setModel( mModelBridge->
proxyModel() );
76 mResultsView->setUniformRowHeights(
true );
80 mResultsView->recalculateSize();
81 mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
83 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsLocatorWidget::scheduleDelayedPopup );
84 connect( mResultsView, &QAbstractItemView::activated,
this, &QgsLocatorWidget::acceptCurrentEntry );
85 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
92 mPopupTimer.setInterval( 100 );
93 mPopupTimer.setSingleShot(
true );
94 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
95 mFocusTimer.setInterval( 110 );
96 mFocusTimer.setSingleShot(
true );
97 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
99 mLineEdit->installEventFilter(
this );
100 mResultsContainer->installEventFilter(
this );
101 mResultsView->installEventFilter(
this );
102 installEventFilter(
this );
103 window()->installEventFilter(
this );
107 mMenu =
new QMenu(
this );
108 QAction *menuAction = mLineEdit->addAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/search.svg" ) ), QLineEdit::LeadingPosition );
109 connect( menuAction, &QAction::triggered,
this, [ = ]
112 mResultsContainer->hide();
113 mMenu->exec( QCursor::pos() );
115 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
127 return mModelBridge->
locator();
132 if ( mMapCanvas == canvas )
135 for (
const QMetaObject::Connection &conn : std::as_const( mCanvasConnections ) )
139 mCanvasConnections.clear();
154 window()->activateWindow();
155 if (
string.isEmpty() )
157 mLineEdit->setFocus();
158 mLineEdit->selectAll();
162 scheduleDelayedPopup();
163 mLineEdit->setFocus();
164 mLineEdit->setText(
string );
172 mResultsContainer->hide();
175void QgsLocatorWidget::scheduleDelayedPopup()
180void QgsLocatorWidget::resultAdded()
182 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
186 bool selectable =
false;
187 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
190 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
193 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
197void QgsLocatorWidget::showContextMenu(
const QPoint &point )
199 QModelIndex index = mResultsView->indexAt( point );
200 if ( !index.isValid() )
203 const QList<QgsLocatorResult::ResultAction> actions = mResultsView->model()->data( index,
QgsLocatorModel::ResultActionsRole ).value<QList<QgsLocatorResult::ResultAction>>();
204 QMenu *contextMenu =
new QMenu( mResultsView );
205 for (
auto resultAction : actions )
207 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
208 if ( !resultAction.iconPath.isEmpty() )
209 menuAction->setIcon( QIcon( resultAction.iconPath ) );
210 connect( menuAction, &QAction::triggered,
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
211 contextMenu->addAction( menuAction );
213 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
216void QgsLocatorWidget::performSearch()
223void QgsLocatorWidget::showList()
225 mResultsContainer->show();
226 mResultsContainer->raise();
229void QgsLocatorWidget::triggerSearchAndShowList()
231 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
239 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
241 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
242 switch ( keyEvent->key() )
247 case Qt::Key_PageDown:
248 triggerSearchAndShowList();
249 mHasSelectedResult =
true;
250 QgsApplication::sendEvent( mResultsView, event );
254 if ( keyEvent->modifiers() & Qt::ControlModifier )
256 triggerSearchAndShowList();
257 mHasSelectedResult =
true;
258 QgsApplication::sendEvent( mResultsView, event );
264 acceptCurrentEntry();
267 mResultsContainer->hide();
270 if ( !mLineEdit->performCompletion() )
272 mHasSelectedResult =
true;
273 mResultsView->selectNextResult();
276 case Qt::Key_Backtab:
277 mHasSelectedResult =
true;
278 mResultsView->selectPreviousResult();
284 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
286 mHasSelectedResult =
true;
288 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
290 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
293 mResultsContainer->hide();
296 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
300 else if ( obj == window() && event->type() == QEvent::Resize )
302 mResultsView->recalculateSize();
304 return QWidget::eventFilter( obj, event );
307void QgsLocatorWidget::configMenuAboutToShow()
312 if ( !filter->enabled() )
315 QAction *action =
new QAction( filter->displayName(), mMenu );
316 connect( action, &QAction::triggered,
this, [ = ]
318 QString currentText = mLineEdit->text();
319 if ( currentText.isEmpty() )
320 currentText = tr(
"<type here>" );
323 QStringList parts = currentText.split(
' ' );
324 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
327 currentText = parts.join(
' ' );
331 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
332 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
334 mMenu->addAction( action );
336 mMenu->addSeparator();
337 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
339 mMenu->addAction( configAction );
343void QgsLocatorWidget::acceptCurrentEntry()
351 if ( !mResultsView->isVisible() )
354 QModelIndex index = mResultsView->currentIndex();
355 if ( !index.isValid() )
358 mResultsContainer->hide();
359 mLineEdit->clearFocus();
370QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
371 : QTreeView( parent )
373 setRootIsDecorated(
false );
374 setUniformRowHeights(
true );
376 header()->setStretchLastSection(
true );
379void QgsLocatorResultsView::recalculateSize()
381 QStyleOptionViewItem optView;
382#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
383 optView.init(
this );
385 optView.initFrom(
this );
389 int rowSize = 20 * itemDelegate()->sizeHint( optView, model()->index( 0, 0 ) ).height();
392 int width = std::max( 300, window()->size().width() / 2 );
393 QSize newSize( width, rowSize + frameWidth() * 2 );
395 parentWidget()->resize( newSize );
396 QTreeView::resize( newSize );
398 header()->resizeSection( 0, width / 2 );
399 header()->resizeSection( 1, 0 );
402void QgsLocatorResultsView::selectNextResult()
404 const int rowCount = model()->rowCount( QModelIndex() );
408 int nextRow = currentIndex().row() + 1;
409 nextRow = nextRow % rowCount;
410 setCurrentIndex( model()->index( nextRow, 0 ) );
413void QgsLocatorResultsView::selectPreviousResult()
415 const int rowCount = model()->rowCount( QModelIndex() );
419 int previousRow = currentIndex().row() - 1;
420 if ( previousRow < 0 )
421 previousRow = rowCount - 1;
422 setCurrentIndex( model()->index( previousRow, 0 ) );
429QgsLocatorFilterFilter::QgsLocatorFilterFilter(
QgsLocatorWidget *locator, QObject *parent )
431 , mLocator( locator )
434QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const
436 return new QgsLocatorFilterFilter( mLocator );
439QgsLocatorFilter::Flags QgsLocatorFilterFilter::flags()
const
446 if ( !
string.isEmpty() )
457 if ( filter ==
this || !filter || !filter->enabled() )
463 result.
userData = QString( filter->activePrefix() +
' ' );
465 emit resultFetched( result );
471 mLocator->search( result.
userData.toString() );
474QgsLocatorLineEdit::QgsLocatorLineEdit(
QgsLocatorWidget *locator, QWidget *parent )
476 , mLocatorWidget( locator )
481void QgsLocatorLineEdit::paintEvent( QPaintEvent *event )
489 QLineEdit::paintEvent( event );
494 QString currentText = text();
496 if ( currentText.length() == 0 || cursorPosition() < currentText.length() )
499 const QStringList completionList = mLocatorWidget->locator()->completionList();
501 mCompletionText.clear();
503 for (
const QString &candidate : completionList )
505 if ( candidate.startsWith( currentText ) )
507 completion = candidate.right( candidate.length() - currentText.length() );
508 mCompletionText = candidate;
513 if ( completion.isEmpty() )
518 QRect cr = cursorRect();
519 QPoint pos = cr.topRight() - QPoint( cr.width() / 2, 0 );
521 QTextLayout l( completion, font() );
523 QTextLine line = l.createLine();
524 line.setLineWidth( width() - pos.x() );
525 line.setPosition( pos );
529 p.setPen( QPen( Qt::gray, 1 ) );
530 l.draw( &p, QPoint( 0, 0 ) );
533bool QgsLocatorLineEdit::performCompletion()
535 if ( !mCompletionText.isEmpty() )
537 setText( mCompletionText );
538 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 SIP_HOLDGIL
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 userData
Custom reference or other data set by the filter.
QString description
Descriptive text for 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.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...