35#include "moc_qgslocatorwidget.cpp"
44 , mLineEdit( new QgsLocatorLineEdit( this ) )
45 , mResultsView( new QgsLocatorResultsView() )
47 setObjectName( QStringLiteral(
"LocatorWidget" ) );
48 mLineEdit->setShowClearButton(
true );
50 mLineEdit->setPlaceholderText( tr(
"Type to locate (⌘K)" ) );
52 mLineEdit->setPlaceholderText( tr(
"Type to locate (Ctrl+K)" ) );
55 int placeholderMinWidth = mLineEdit->fontMetrics().boundingRect( mLineEdit->placeholderText() ).width();
56 int minWidth = std::max( 200,
static_cast<int>( placeholderMinWidth * 1.8 ) );
57 resize( minWidth, 30 );
58 QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
59 sizePolicy.setHorizontalStretch( 0 );
60 sizePolicy.setVerticalStretch( 0 );
61 setSizePolicy( sizePolicy );
62 setMinimumSize( QSize( minWidth, 0 ) );
64 QHBoxLayout *layout =
new QHBoxLayout();
65 layout->setContentsMargins( 0, 0, 0, 0 );
66 layout->addWidget( mLineEdit );
69 setFocusProxy( mLineEdit );
73 mResultsContainer->setAnchorWidget( mLineEdit );
77 QHBoxLayout *containerLayout =
new QHBoxLayout();
78 containerLayout->setContentsMargins( 0, 0, 0, 0 );
79 containerLayout->addWidget( mResultsView );
80 mResultsContainer->setLayout( containerLayout );
81 mResultsContainer->hide();
83 mResultsView->setModel( mModelBridge->proxyModel() );
84 mResultsView->setUniformRowHeights(
true );
87 mResultsView->setIconSize( QSize( iconSize, iconSize ) );
88 mResultsView->recalculateSize();
89 mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
91 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsLocatorWidget::scheduleDelayedPopup );
92 connect( mResultsView, &QAbstractItemView::activated,
this, &QgsLocatorWidget::acceptCurrentEntry );
93 connect( mResultsView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsLocatorWidget::selectionChanged );
94 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
101 mPopupTimer.setInterval( 100 );
102 mPopupTimer.setSingleShot(
true );
103 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
104 mFocusTimer.setInterval( 110 );
105 mFocusTimer.setSingleShot(
true );
106 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
108 mLineEdit->installEventFilter(
this );
109 mResultsContainer->installEventFilter(
this );
110 mResultsView->installEventFilter(
this );
111 installEventFilter(
this );
112 window()->installEventFilter(
this );
114 mModelBridge->locator()->registerFilter(
new QgsLocatorFilterFilter(
this,
this ) );
116 mMenu =
new QMenu(
this );
117 QAction *menuAction = mLineEdit->addAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/search.svg" ) ), QLineEdit::LeadingPosition );
118 connect( menuAction, &QAction::triggered,
this, [
this] {
120 mResultsContainer->hide();
121 mMenu->exec( QCursor::pos() );
123 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
133 return mModelBridge->locator();
138 if ( mMapCanvas == canvas )
141 for (
const QMetaObject::Connection &conn : std::as_const( mCanvasConnections ) )
145 mCanvasConnections.clear();
150 mModelBridge->updateCanvasExtent( mMapCanvas->mapSettings().visibleExtent() );
151 mModelBridge->updateCanvasCrs( mMapCanvas->mapSettings().destinationCrs() );
153 << connect( mMapCanvas, &
QgsMapCanvas::extentsChanged,
this, [
this]() { mModelBridge->updateCanvasExtent( mMapCanvas->mapSettings().visibleExtent() ); } )
160 mLineEdit->setPlaceholderText( text );
165 mResultsContainer->setAnchorPoint( anchorPoint );
166 mResultsContainer->setAnchorWidgetPoint( anchorWidgetPoint );
171 window()->activateWindow();
172 if (
string.isEmpty() )
174 mLineEdit->setFocus();
175 mLineEdit->selectAll();
179 scheduleDelayedPopup();
180 mLineEdit->setFocus();
181 mLineEdit->setText(
string );
188 mModelBridge->invalidateResults();
189 mResultsContainer->hide();
192void QgsLocatorWidget::scheduleDelayedPopup()
197void QgsLocatorWidget::resultAdded()
199 bool selectFirst = !mHasSelectedResult || mModelBridge->proxyModel()->rowCount() == 0;
203 bool selectable =
false;
204 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
207 selectable = mModelBridge->proxyModel()->flags( mModelBridge->proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
210 mResultsView->setCurrentIndex( mModelBridge->proxyModel()->index( row, 0 ) );
214void QgsLocatorWidget::showContextMenu(
const QPoint &point )
216 QModelIndex index = mResultsView->indexAt( point );
217 if ( !index.isValid() )
221 QMenu *contextMenu =
new QMenu( mResultsView );
222 for (
auto resultAction : actions )
224 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
225 if ( !resultAction.iconPath.isEmpty() )
226 menuAction->setIcon( QIcon( resultAction.iconPath ) );
227 connect( menuAction, &QAction::triggered,
this, [
this, index, resultAction]() { mModelBridge->triggerResult( index, resultAction.id ); } );
228 contextMenu->addAction( menuAction );
230 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
233void QgsLocatorWidget::performSearch()
236 mModelBridge->performSearch( mLineEdit->text() );
240void QgsLocatorWidget::showList()
242 mResultsContainer->show();
243 mResultsContainer->raise();
246void QgsLocatorWidget::triggerSearchAndShowList()
248 if ( mModelBridge->proxyModel()->rowCount() == 0 )
256 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
258 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
259 switch ( keyEvent->key() )
264 case Qt::Key_PageDown:
265 triggerSearchAndShowList();
266 mHasSelectedResult =
true;
267 QgsApplication::sendEvent( mResultsView, event );
271 if ( keyEvent->modifiers() & Qt::ControlModifier )
273 triggerSearchAndShowList();
274 mHasSelectedResult =
true;
275 QgsApplication::sendEvent( mResultsView, event );
281 acceptCurrentEntry();
284 mResultsContainer->hide();
287 if ( !mLineEdit->performCompletion() )
289 mHasSelectedResult =
true;
290 mResultsView->selectNextResult();
293 case Qt::Key_Backtab:
294 mHasSelectedResult =
true;
295 mResultsView->selectPreviousResult();
301 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
303 mHasSelectedResult =
true;
305 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
307 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
310 mResultsContainer->hide();
313 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
317 else if ( obj == window() && event->type() == QEvent::Resize )
319 mResultsView->recalculateSize();
321 return QWidget::eventFilter( obj, event );
324void QgsLocatorWidget::configMenuAboutToShow()
329 if ( !filter->enabled() )
332 QAction *action =
new QAction( filter->displayName(), mMenu );
333 connect( action, &QAction::triggered,
this, [
this, filter] {
334 QString currentText = mLineEdit->text();
335 if ( currentText.isEmpty() )
336 currentText = tr(
"<type here>" );
339 QStringList parts = currentText.split(
' ' );
340 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
343 currentText = parts.join(
' ' );
347 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
348 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
350 mMenu->addAction( action );
352 mMenu->addSeparator();
353 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
355 mMenu->addAction( configAction );
359void QgsLocatorWidget::acceptCurrentEntry()
361 if ( mModelBridge->hasQueueRequested() )
367 if ( !mResultsView->isVisible() )
370 QModelIndex index = mResultsView->currentIndex();
371 if ( !index.isValid() )
374 mResultsContainer->hide();
375 mLineEdit->clearFocus();
376 mModelBridge->triggerResult( index );
380void QgsLocatorWidget::selectionChanged(
const QItemSelection &selected,
const QItemSelection &deselected )
382 if ( !mResultsView->isVisible() )
385 mModelBridge->selectionChanged( selected, deselected );
394QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
395 : QTreeView( parent )
397 setRootIsDecorated(
false );
398 setUniformRowHeights(
true );
400 header()->setStretchLastSection(
true );
403void QgsLocatorResultsView::recalculateSize()
405 QStyleOptionViewItem optView;
406#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
407 optView.init(
this );
409 optView.initFrom(
this );
416 int width = std::max( 300, window()->size().width() / 2 );
417 QSize newSize( width, rowSize + frameWidth() * 2 );
419 parentWidget()->resize( newSize );
420 QTreeView::resize( newSize );
422 header()->resizeSection( 0, width / 2 );
423 header()->resizeSection( 1, 0 );
426void QgsLocatorResultsView::selectNextResult()
428 const int rowCount = model()->rowCount( QModelIndex() );
432 int nextRow = currentIndex().row() + 1;
433 nextRow = nextRow % rowCount;
434 setCurrentIndex( model()->index( nextRow, 0 ) );
437void QgsLocatorResultsView::selectPreviousResult()
439 const int rowCount = model()->rowCount( QModelIndex() );
443 int previousRow = currentIndex().row() - 1;
444 if ( previousRow < 0 )
445 previousRow = rowCount - 1;
446 setCurrentIndex( model()->index( previousRow, 0 ) );
453QgsLocatorFilterFilter::QgsLocatorFilterFilter(
QgsLocatorWidget *locator, QObject *parent )
455 , mLocator( locator )
458QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const
460 return new QgsLocatorFilterFilter( mLocator );
470 if ( !
string.isEmpty() )
481 if ( filter ==
this || !filter || !filter->enabled() )
487 result.
setUserData( QString( filter->activePrefix() +
' ' ) );
489 emit resultFetched( result );
495 mLocator->search( result.
userData().toString() );
498QgsLocatorLineEdit::QgsLocatorLineEdit(
QgsLocatorWidget *locator, QWidget *parent )
500 , mLocatorWidget( locator )
505void QgsLocatorLineEdit::paintEvent( QPaintEvent *event )
513 QLineEdit::paintEvent( event );
518 QString currentText = text();
520 if ( currentText.length() == 0 || cursorPosition() < currentText.length() )
523 const QStringList completionList = mLocatorWidget->locator()->completionList();
525 mCompletionText.clear();
527 for (
const QString &candidate : completionList )
529 if ( candidate.startsWith( currentText ) )
531 completion = candidate.right( candidate.length() - currentText.length() );
532 mCompletionText = candidate;
537 if ( completion.isEmpty() )
542 QRect cr = cursorRect();
543 QPoint pos = cr.topRight() - QPoint( cr.width() / 2, 0 );
545 QTextLayout l( completion, font() );
547 QTextLine line = l.createLine();
548 line.setLineWidth( width() - pos.x() );
549 line.setPosition( pos );
553 p.setPen( QPen( Qt::gray, 1 ) );
554 l.draw( &p, QPoint( 0, 0 ) );
557bool QgsLocatorLineEdit::performCompletion()
559 if ( !mCompletionText.isEmpty() )
561 setText( mCompletionText );
562 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,...