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->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsLocatorWidget::selectionChanged );
87 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
94 mPopupTimer.setInterval( 100 );
95 mPopupTimer.setSingleShot(
true );
96 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
97 mFocusTimer.setInterval( 110 );
98 mFocusTimer.setSingleShot(
true );
99 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
101 mLineEdit->installEventFilter(
this );
102 mResultsContainer->installEventFilter(
this );
103 mResultsView->installEventFilter(
this );
104 installEventFilter(
this );
105 window()->installEventFilter(
this );
109 mMenu =
new QMenu(
this );
110 QAction *menuAction = mLineEdit->addAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/search.svg" ) ), QLineEdit::LeadingPosition );
111 connect( menuAction, &QAction::triggered,
this, [ = ]
114 mResultsContainer->hide();
115 mMenu->exec( QCursor::pos() );
117 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
129 return mModelBridge->
locator();
134 if ( mMapCanvas == canvas )
137 for (
const QMetaObject::Connection &conn : std::as_const( mCanvasConnections ) )
141 mCanvasConnections.clear();
156 mLineEdit->setPlaceholderText( text );
167 window()->activateWindow();
168 if (
string.isEmpty() )
170 mLineEdit->setFocus();
171 mLineEdit->selectAll();
175 scheduleDelayedPopup();
176 mLineEdit->setFocus();
177 mLineEdit->setText(
string );
185 mResultsContainer->hide();
188void QgsLocatorWidget::scheduleDelayedPopup()
193void QgsLocatorWidget::resultAdded()
195 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
199 bool selectable =
false;
200 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
203 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
206 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
210void QgsLocatorWidget::showContextMenu(
const QPoint &point )
212 QModelIndex index = mResultsView->indexAt( point );
213 if ( !index.isValid() )
217 QMenu *contextMenu =
new QMenu( mResultsView );
218 for (
auto resultAction : actions )
220 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
221 if ( !resultAction.iconPath.isEmpty() )
222 menuAction->setIcon( QIcon( resultAction.iconPath ) );
223 connect( menuAction, &QAction::triggered,
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
224 contextMenu->addAction( menuAction );
226 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
229void QgsLocatorWidget::performSearch()
236void QgsLocatorWidget::showList()
238 mResultsContainer->show();
239 mResultsContainer->raise();
242void QgsLocatorWidget::triggerSearchAndShowList()
244 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
252 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
254 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
255 switch ( keyEvent->key() )
260 case Qt::Key_PageDown:
261 triggerSearchAndShowList();
262 mHasSelectedResult =
true;
263 QgsApplication::sendEvent( mResultsView, event );
267 if ( keyEvent->modifiers() & Qt::ControlModifier )
269 triggerSearchAndShowList();
270 mHasSelectedResult =
true;
271 QgsApplication::sendEvent( mResultsView, event );
277 acceptCurrentEntry();
280 mResultsContainer->hide();
283 if ( !mLineEdit->performCompletion() )
285 mHasSelectedResult =
true;
286 mResultsView->selectNextResult();
289 case Qt::Key_Backtab:
290 mHasSelectedResult =
true;
291 mResultsView->selectPreviousResult();
297 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
299 mHasSelectedResult =
true;
301 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
303 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
306 mResultsContainer->hide();
309 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
313 else if ( obj == window() && event->type() == QEvent::Resize )
315 mResultsView->recalculateSize();
317 return QWidget::eventFilter( obj, event );
320void QgsLocatorWidget::configMenuAboutToShow()
325 if ( !filter->enabled() )
328 QAction *action =
new QAction( filter->displayName(), mMenu );
329 connect( action, &QAction::triggered,
this, [ = ]
331 QString currentText = mLineEdit->text();
332 if ( currentText.isEmpty() )
333 currentText = tr(
"<type here>" );
336 QStringList parts = currentText.split(
' ' );
337 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
340 currentText = parts.join(
' ' );
344 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
345 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
347 mMenu->addAction( action );
349 mMenu->addSeparator();
350 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
352 mMenu->addAction( configAction );
356void QgsLocatorWidget::acceptCurrentEntry()
364 if ( !mResultsView->isVisible() )
367 QModelIndex index = mResultsView->currentIndex();
368 if ( !index.isValid() )
371 mResultsContainer->hide();
372 mLineEdit->clearFocus();
377void QgsLocatorWidget::selectionChanged(
const QItemSelection &selected,
const QItemSelection &deselected )
379 if ( !mResultsView->isVisible() )
391QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
392 : QTreeView( parent )
394 setRootIsDecorated(
false );
395 setUniformRowHeights(
true );
397 header()->setStretchLastSection(
true );
400void QgsLocatorResultsView::recalculateSize()
402 QStyleOptionViewItem optView;
403#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
404 optView.init(
this );
406 optView.initFrom(
this );
410 int rowSize = 20 * itemDelegate()->sizeHint( optView, model()->index( 0, 0 ) ).height();
413 int width = std::max( 300, window()->size().width() / 2 );
414 QSize newSize( width, rowSize + frameWidth() * 2 );
416 parentWidget()->resize( newSize );
417 QTreeView::resize( newSize );
419 header()->resizeSection( 0, width / 2 );
420 header()->resizeSection( 1, 0 );
423void QgsLocatorResultsView::selectNextResult()
425 const int rowCount = model()->rowCount( QModelIndex() );
429 int nextRow = currentIndex().row() + 1;
430 nextRow = nextRow % rowCount;
431 setCurrentIndex( model()->index( nextRow, 0 ) );
434void QgsLocatorResultsView::selectPreviousResult()
436 const int rowCount = model()->rowCount( QModelIndex() );
440 int previousRow = currentIndex().row() - 1;
441 if ( previousRow < 0 )
442 previousRow = rowCount - 1;
443 setCurrentIndex( model()->index( previousRow, 0 ) );
450QgsLocatorFilterFilter::QgsLocatorFilterFilter(
QgsLocatorWidget *locator, QObject *parent )
452 , mLocator( locator )
455QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const
457 return new QgsLocatorFilterFilter( mLocator );
467 if ( !
string.isEmpty() )
478 if ( filter ==
this || !filter || !filter->enabled() )
484 result.
setUserData( QString( filter->activePrefix() +
' ' ) );
486 emit resultFetched( result );
492 mLocator->search( result.
userData().toString() );
495QgsLocatorLineEdit::QgsLocatorLineEdit(
QgsLocatorWidget *locator, QWidget *parent )
497 , mLocatorWidget( locator )
502void QgsLocatorLineEdit::paintEvent( QPaintEvent *event )
510 QLineEdit::paintEvent( event );
515 QString currentText = text();
517 if ( currentText.length() == 0 || cursorPosition() < currentText.length() )
520 const QStringList completionList = mLocatorWidget->locator()->completionList();
522 mCompletionText.clear();
524 for (
const QString &candidate : completionList )
526 if ( candidate.startsWith( currentText ) )
528 completion = candidate.right( candidate.length() - currentText.length() );
529 mCompletionText = candidate;
534 if ( completion.isEmpty() )
539 QRect cr = cursorRect();
540 QPoint pos = cr.topRight() - QPoint( cr.width() / 2, 0 );
542 QTextLayout l( completion, font() );
544 QTextLine line = l.createLine();
545 line.setLineWidth( width() - pos.x() );
546 line.setPosition( pos );
550 p.setPen( QPen( Qt::gray, 1 ) );
551 l.draw( &p, QPoint( 0, 0 ) );
554bool QgsLocatorLineEdit::performCompletion()
556 if ( !mCompletionText.isEmpty() )
558 setText( mCompletionText );
559 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 selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
This will call filters implementation of selection/deselection of results.
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.
QString description
Descriptive text for result.
void setUserData(const QVariant &userData)
Set userData for the locator result.
QString displayString
String displayed for result.
QVariant userData() const
Returns the userData.
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,...