36#include "moc_qgslocatorwidget.cpp"
38using namespace Qt::StringLiterals;
48 , mLineEdit( new QgsLocatorLineEdit( this ) )
49 , mResultsView( new QgsLocatorResultsView() )
51 setObjectName( u
"LocatorWidget"_s );
52 mLineEdit->setShowClearButton(
true );
54 mLineEdit->setPlaceholderText( tr(
"Type to locate (⌘K)" ) );
56 mLineEdit->setPlaceholderText( tr(
"Type to locate (Ctrl+K)" ) );
59 int placeholderMinWidth = mLineEdit->fontMetrics().boundingRect( mLineEdit->placeholderText() ).width();
60 int minWidth = std::max( 200,
static_cast<int>( placeholderMinWidth * 1.8 ) );
61 resize( minWidth, 30 );
62 QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
63 sizePolicy.setHorizontalStretch( 0 );
64 sizePolicy.setVerticalStretch( 0 );
65 setSizePolicy( sizePolicy );
66 setMinimumSize( QSize( minWidth, 0 ) );
68 QHBoxLayout *layout =
new QHBoxLayout();
69 layout->setContentsMargins( 0, 0, 0, 0 );
70 layout->addWidget( mLineEdit );
73 setFocusProxy( mLineEdit );
77 mResultsContainer->setAnchorWidget( mLineEdit );
81 QHBoxLayout *containerLayout =
new QHBoxLayout();
82 containerLayout->setContentsMargins( 0, 0, 0, 0 );
83 containerLayout->addWidget( mResultsView );
84 mResultsContainer->setLayout( containerLayout );
85 mResultsContainer->hide();
87 mResultsView->setModel( mModelBridge->proxyModel() );
88 mResultsView->setUniformRowHeights(
true );
91 mResultsView->setIconSize( QSize( iconSize, iconSize ) );
92 mResultsView->recalculateSize();
93 mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
95 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsLocatorWidget::scheduleDelayedPopup );
96 connect( mResultsView, &QAbstractItemView::activated,
this, &QgsLocatorWidget::acceptCurrentEntry );
97 connect( mResultsView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsLocatorWidget::selectionChanged );
98 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
105 mPopupTimer.setInterval( 100 );
106 mPopupTimer.setSingleShot(
true );
107 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
108 mFocusTimer.setInterval( 110 );
109 mFocusTimer.setSingleShot(
true );
110 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
112 mLineEdit->installEventFilter(
this );
113 mResultsContainer->installEventFilter(
this );
114 mResultsView->installEventFilter(
this );
115 installEventFilter(
this );
116 window()->installEventFilter(
this );
118 mModelBridge->locator()->registerFilter(
new QgsLocatorFilterFilter(
this,
this ) );
120 mMenu =
new QMenu(
this );
122 connect( menuAction, &QAction::triggered,
this, [
this] {
124 mResultsContainer->hide();
125 mMenu->exec( QCursor::pos() );
127 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
135 return mModelBridge->locator();
140 if ( mMapCanvas == canvas )
143 for (
const QMetaObject::Connection &conn : std::as_const( mCanvasConnections ) )
147 mCanvasConnections.clear();
152 mModelBridge->updateCanvasExtent( mMapCanvas->mapSettings().visibleExtent() );
153 mModelBridge->updateCanvasCrs( mMapCanvas->mapSettings().destinationCrs() );
155 mModelBridge->updateCanvasExtent( mMapCanvas->mapSettings().visibleExtent() );
162 mLineEdit->setPlaceholderText( text );
167 mResultsContainer->setAnchorPoint( anchorPoint );
168 mResultsContainer->setAnchorWidgetPoint( anchorWidgetPoint );
173 window()->activateWindow();
174 if (
string.isEmpty() )
176 mLineEdit->setFocus();
177 mLineEdit->selectAll();
181 scheduleDelayedPopup();
182 mLineEdit->setFocus();
183 mLineEdit->setText(
string );
190 mModelBridge->invalidateResults();
191 mResultsContainer->hide();
194void QgsLocatorWidget::scheduleDelayedPopup()
199void QgsLocatorWidget::resultAdded()
201 bool selectFirst = !mHasSelectedResult || mModelBridge->proxyModel()->rowCount() == 0;
205 bool selectable =
false;
206 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
209 selectable = mModelBridge->proxyModel()->flags( mModelBridge->proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
212 mResultsView->setCurrentIndex( mModelBridge->proxyModel()->index( row, 0 ) );
216void QgsLocatorWidget::showContextMenu(
const QPoint &point )
218 QModelIndex index = mResultsView->indexAt( point );
219 if ( !index.isValid() )
223 QMenu *contextMenu =
new QMenu( mResultsView );
224 for (
auto resultAction : actions )
226 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
227 if ( !resultAction.iconPath.isEmpty() )
228 menuAction->setIcon( QIcon( resultAction.iconPath ) );
229 connect( menuAction, &QAction::triggered,
this, [
this, index, resultAction]() { mModelBridge->triggerResult( index, resultAction.id ); } );
230 contextMenu->addAction( menuAction );
232 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
235void QgsLocatorWidget::performSearch()
238 mModelBridge->performSearch( mLineEdit->text() );
242void QgsLocatorWidget::showList()
244 mResultsContainer->show();
245 mResultsContainer->raise();
248void QgsLocatorWidget::triggerSearchAndShowList()
250 if ( mModelBridge->proxyModel()->rowCount() == 0 )
258 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
260 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
261 switch ( keyEvent->key() )
266 case Qt::Key_PageDown:
267 triggerSearchAndShowList();
268 mHasSelectedResult =
true;
269 QgsApplication::sendEvent( mResultsView, event );
273 if ( keyEvent->modifiers() & Qt::ControlModifier )
275 triggerSearchAndShowList();
276 mHasSelectedResult =
true;
277 QgsApplication::sendEvent( mResultsView, event );
283 acceptCurrentEntry();
286 mResultsContainer->hide();
289 if ( !mLineEdit->performCompletion() )
291 mHasSelectedResult =
true;
292 mResultsView->selectNextResult();
295 case Qt::Key_Backtab:
296 mHasSelectedResult =
true;
297 mResultsView->selectPreviousResult();
303 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
305 mHasSelectedResult =
true;
307 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
309 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
312 mResultsContainer->hide();
315 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
319 else if ( obj == window() && event->type() == QEvent::Resize )
321 mResultsView->recalculateSize();
323 return QWidget::eventFilter( obj, event );
326void QgsLocatorWidget::configMenuAboutToShow()
331 if ( !filter->enabled() )
334 QAction *action =
new QAction( filter->displayName(), mMenu );
335 connect( action, &QAction::triggered,
this, [
this, filter] {
336 QString currentText = mLineEdit->text();
337 if ( currentText.isEmpty() )
338 currentText = tr(
"<type here>" );
341 QStringList parts = currentText.split(
' ' );
342 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
345 currentText = parts.join(
' ' );
349 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
350 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
352 mMenu->addAction( action );
354 mMenu->addSeparator();
355 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
357 mMenu->addAction( configAction );
361void QgsLocatorWidget::acceptCurrentEntry()
363 if ( mModelBridge->hasQueueRequested() )
369 if ( !mResultsView->isVisible() )
372 QModelIndex index = mResultsView->currentIndex();
373 if ( !index.isValid() )
376 mResultsContainer->hide();
377 mLineEdit->clearFocus();
378 mModelBridge->triggerResult( index );
382void QgsLocatorWidget::selectionChanged(
const QItemSelection &selected,
const QItemSelection &deselected )
384 if ( !mResultsView->isVisible() )
387 mModelBridge->selectionChanged( selected, deselected );
396QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
397 : QTreeView( parent )
399 setRootIsDecorated(
false );
400 setUniformRowHeights(
true );
402 header()->setStretchLastSection(
true );
405void QgsLocatorResultsView::recalculateSize()
407 QStyleOptionViewItem optView;
408 optView.initFrom(
this );
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();
QFlags< SettingsOption > SettingsOptions
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.
Provides the core functionality to be used in a locator widget.
void isRunningChanged()
Emitted when the running status changes.
void resultAdded()
Emitted when a result is added.
QgsLocator * locator() const
Returns the locator.
void resultsCleared()
Emitted when the results are cleared.
@ 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 (.
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.
static QgsProject * instance()
Returns the QgsProject singleton instance.
void transformContextChanged()
Emitted when the project transformContext() is changed.
An integer settings entry.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...