21#include "moc_qgslocatorwidget.cpp"
38 , mLineEdit( new QgsLocatorLineEdit( this ) )
39 , mResultsView( new QgsLocatorResultsView() )
41 setObjectName( QStringLiteral(
"LocatorWidget" ) );
42 mLineEdit->setShowClearButton(
true );
44 mLineEdit->setPlaceholderText( tr(
"Type to locate (⌘K)" ) );
46 mLineEdit->setPlaceholderText( tr(
"Type to locate (Ctrl+K)" ) );
49 int placeholderMinWidth = mLineEdit->fontMetrics().boundingRect( mLineEdit->placeholderText() ).width();
50 int minWidth = std::max( 200,
static_cast< int >( placeholderMinWidth * 1.8 ) );
51 resize( minWidth, 30 );
52 QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
53 sizePolicy.setHorizontalStretch( 0 );
54 sizePolicy.setVerticalStretch( 0 );
55 setSizePolicy( sizePolicy );
56 setMinimumSize( QSize( minWidth, 0 ) );
58 QHBoxLayout *layout =
new QHBoxLayout();
59 layout->setContentsMargins( 0, 0, 0, 0 );
60 layout->addWidget( mLineEdit );
63 setFocusProxy( mLineEdit );
71 QHBoxLayout *containerLayout =
new QHBoxLayout();
72 containerLayout->setContentsMargins( 0, 0, 0, 0 );
73 containerLayout->addWidget( mResultsView );
74 mResultsContainer->setLayout( containerLayout );
75 mResultsContainer->hide();
77 mResultsView->setModel( mModelBridge->
proxyModel() );
78 mResultsView->setUniformRowHeights(
true );
81 mResultsView->setIconSize( QSize( iconSize, iconSize ) );
82 mResultsView->recalculateSize();
83 mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
85 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsLocatorWidget::scheduleDelayedPopup );
86 connect( mResultsView, &QAbstractItemView::activated,
this, &QgsLocatorWidget::acceptCurrentEntry );
87 connect( mResultsView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsLocatorWidget::selectionChanged );
88 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
95 mPopupTimer.setInterval( 100 );
96 mPopupTimer.setSingleShot(
true );
97 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
98 mFocusTimer.setInterval( 110 );
99 mFocusTimer.setSingleShot(
true );
100 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
102 mLineEdit->installEventFilter(
this );
103 mResultsContainer->installEventFilter(
this );
104 mResultsView->installEventFilter(
this );
105 installEventFilter(
this );
106 window()->installEventFilter(
this );
110 mMenu =
new QMenu(
this );
111 QAction *menuAction = mLineEdit->addAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/search.svg" ) ), QLineEdit::LeadingPosition );
112 connect( menuAction, &QAction::triggered,
this, [ = ]
115 mResultsContainer->hide();
116 mMenu->exec( QCursor::pos() );
118 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
130 return mModelBridge->
locator();
135 if ( mMapCanvas == canvas )
138 for (
const QMetaObject::Connection &conn : std::as_const( mCanvasConnections ) )
142 mCanvasConnections.clear();
157 mLineEdit->setPlaceholderText( text );
168 window()->activateWindow();
169 if (
string.isEmpty() )
171 mLineEdit->setFocus();
172 mLineEdit->selectAll();
176 scheduleDelayedPopup();
177 mLineEdit->setFocus();
178 mLineEdit->setText(
string );
186 mResultsContainer->hide();
189void QgsLocatorWidget::scheduleDelayedPopup()
194void QgsLocatorWidget::resultAdded()
196 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
200 bool selectable =
false;
201 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
204 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
207 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
211void QgsLocatorWidget::showContextMenu(
const QPoint &point )
213 QModelIndex index = mResultsView->indexAt( point );
214 if ( !index.isValid() )
218 QMenu *contextMenu =
new QMenu( mResultsView );
219 for (
auto resultAction : actions )
221 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
222 if ( !resultAction.iconPath.isEmpty() )
223 menuAction->setIcon( QIcon( resultAction.iconPath ) );
224 connect( menuAction, &QAction::triggered,
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
225 contextMenu->addAction( menuAction );
227 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
230void QgsLocatorWidget::performSearch()
237void QgsLocatorWidget::showList()
239 mResultsContainer->show();
240 mResultsContainer->raise();
243void QgsLocatorWidget::triggerSearchAndShowList()
245 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
253 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
255 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
256 switch ( keyEvent->key() )
261 case Qt::Key_PageDown:
262 triggerSearchAndShowList();
263 mHasSelectedResult =
true;
264 QgsApplication::sendEvent( mResultsView, event );
268 if ( keyEvent->modifiers() & Qt::ControlModifier )
270 triggerSearchAndShowList();
271 mHasSelectedResult =
true;
272 QgsApplication::sendEvent( mResultsView, event );
278 acceptCurrentEntry();
281 mResultsContainer->hide();
284 if ( !mLineEdit->performCompletion() )
286 mHasSelectedResult =
true;
287 mResultsView->selectNextResult();
290 case Qt::Key_Backtab:
291 mHasSelectedResult =
true;
292 mResultsView->selectPreviousResult();
298 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
300 mHasSelectedResult =
true;
302 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
304 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
307 mResultsContainer->hide();
310 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
314 else if ( obj == window() && event->type() == QEvent::Resize )
316 mResultsView->recalculateSize();
318 return QWidget::eventFilter( obj, event );
321void QgsLocatorWidget::configMenuAboutToShow()
326 if ( !filter->enabled() )
329 QAction *action =
new QAction( filter->displayName(), mMenu );
330 connect( action, &QAction::triggered,
this, [ = ]
332 QString currentText = mLineEdit->text();
333 if ( currentText.isEmpty() )
334 currentText = tr(
"<type here>" );
337 QStringList parts = currentText.split(
' ' );
338 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
341 currentText = parts.join(
' ' );
345 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
346 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
348 mMenu->addAction( action );
350 mMenu->addSeparator();
351 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
353 mMenu->addAction( configAction );
357void QgsLocatorWidget::acceptCurrentEntry()
365 if ( !mResultsView->isVisible() )
368 QModelIndex index = mResultsView->currentIndex();
369 if ( !index.isValid() )
372 mResultsContainer->hide();
373 mLineEdit->clearFocus();
378void QgsLocatorWidget::selectionChanged(
const QItemSelection &selected,
const QItemSelection &deselected )
380 if ( !mResultsView->isVisible() )
392QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
393 : QTreeView( parent )
395 setRootIsDecorated(
false );
396 setUniformRowHeights(
true );
398 header()->setStretchLastSection(
true );
401void QgsLocatorResultsView::recalculateSize()
403 QStyleOptionViewItem optView;
404#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
405 optView.init(
this );
407 optView.initFrom(
this );
411 int rowSize = 20 * itemDelegate()->sizeHint( optView, model()->index( 0, 0 ) ).height();
414 int width = std::max( 300, window()->size().width() / 2 );
415 QSize newSize( width, rowSize + frameWidth() * 2 );
417 parentWidget()->resize( newSize );
418 QTreeView::resize( newSize );
420 header()->resizeSection( 0, width / 2 );
421 header()->resizeSection( 1, 0 );
424void QgsLocatorResultsView::selectNextResult()
426 const int rowCount = model()->rowCount( QModelIndex() );
430 int nextRow = currentIndex().row() + 1;
431 nextRow = nextRow % rowCount;
432 setCurrentIndex( model()->index( nextRow, 0 ) );
435void QgsLocatorResultsView::selectPreviousResult()
437 const int rowCount = model()->rowCount( QModelIndex() );
441 int previousRow = currentIndex().row() - 1;
442 if ( previousRow < 0 )
443 previousRow = rowCount - 1;
444 setCurrentIndex( model()->index( previousRow, 0 ) );
451QgsLocatorFilterFilter::QgsLocatorFilterFilter(
QgsLocatorWidget *locator, QObject *parent )
453 , mLocator( locator )
456QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const
458 return new QgsLocatorFilterFilter( mLocator );
468 if ( !
string.isEmpty() )
479 if ( filter ==
this || !filter || !filter->enabled() )
485 result.
setUserData( QString( filter->activePrefix() +
' ' ) );
487 emit resultFetched( result );
493 mLocator->search( result.
userData().toString() );
496QgsLocatorLineEdit::QgsLocatorLineEdit(
QgsLocatorWidget *locator, QWidget *parent )
498 , mLocatorWidget( locator )
503void QgsLocatorLineEdit::paintEvent( QPaintEvent *event )
511 QLineEdit::paintEvent( event );
516 QString currentText = text();
518 if ( currentText.length() == 0 || cursorPosition() < currentText.length() )
521 const QStringList completionList = mLocatorWidget->locator()->completionList();
523 mCompletionText.clear();
525 for (
const QString &candidate : completionList )
527 if ( candidate.startsWith( currentText ) )
529 completion = candidate.right( candidate.length() - currentText.length() );
530 mCompletionText = candidate;
535 if ( completion.isEmpty() )
540 QRect cr = cursorRect();
541 QPoint pos = cr.topRight() - QPoint( cr.width() / 2, 0 );
543 QTextLayout l( completion, font() );
545 QTextLine line = l.createLine();
546 line.setLineWidth( width() - pos.x() );
547 line.setPosition( pos );
551 p.setPen( QPen( Qt::gray, 1 ) );
552 l.draw( &p, QPoint( 0, 0 ) );
555bool QgsLocatorLineEdit::performCompletion()
557 if ( !mCompletionText.isEmpty() )
559 setText( mCompletionText );
560 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.
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,...