21#include "moc_qgslocatorwidget.cpp"
42 , mLineEdit( new QgsLocatorLineEdit( this ) )
43 , mResultsView( new QgsLocatorResultsView() )
45 setObjectName( QStringLiteral(
"LocatorWidget" ) );
46 mLineEdit->setShowClearButton(
true );
48 mLineEdit->setPlaceholderText( tr(
"Type to locate (⌘K)" ) );
50 mLineEdit->setPlaceholderText( tr(
"Type to locate (Ctrl+K)" ) );
53 int placeholderMinWidth = mLineEdit->fontMetrics().boundingRect( mLineEdit->placeholderText() ).width();
54 int minWidth = std::max( 200,
static_cast<int>( placeholderMinWidth * 1.8 ) );
55 resize( minWidth, 30 );
56 QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
57 sizePolicy.setHorizontalStretch( 0 );
58 sizePolicy.setVerticalStretch( 0 );
59 setSizePolicy( sizePolicy );
60 setMinimumSize( QSize( minWidth, 0 ) );
62 QHBoxLayout *layout =
new QHBoxLayout();
63 layout->setContentsMargins( 0, 0, 0, 0 );
64 layout->addWidget( mLineEdit );
67 setFocusProxy( mLineEdit );
75 QHBoxLayout *containerLayout =
new QHBoxLayout();
76 containerLayout->setContentsMargins( 0, 0, 0, 0 );
77 containerLayout->addWidget( mResultsView );
78 mResultsContainer->setLayout( containerLayout );
79 mResultsContainer->hide();
81 mResultsView->setModel( mModelBridge->
proxyModel() );
82 mResultsView->setUniformRowHeights(
true );
85 mResultsView->setIconSize( QSize( iconSize, iconSize ) );
86 mResultsView->recalculateSize();
87 mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
89 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsLocatorWidget::scheduleDelayedPopup );
90 connect( mResultsView, &QAbstractItemView::activated,
this, &QgsLocatorWidget::acceptCurrentEntry );
91 connect( mResultsView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsLocatorWidget::selectionChanged );
92 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
99 mPopupTimer.setInterval( 100 );
100 mPopupTimer.setSingleShot(
true );
101 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
102 mFocusTimer.setInterval( 110 );
103 mFocusTimer.setSingleShot(
true );
104 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
106 mLineEdit->installEventFilter(
this );
107 mResultsContainer->installEventFilter(
this );
108 mResultsView->installEventFilter(
this );
109 installEventFilter(
this );
110 window()->installEventFilter(
this );
114 mMenu =
new QMenu(
this );
115 QAction *menuAction = mLineEdit->addAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/search.svg" ) ), QLineEdit::LeadingPosition );
116 connect( menuAction, &QAction::triggered,
this, [=] {
118 mResultsContainer->hide();
119 mMenu->exec( QCursor::pos() );
121 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
131 return mModelBridge->
locator();
136 if ( mMapCanvas == canvas )
139 for (
const QMetaObject::Connection &conn : std::as_const( mCanvasConnections ) )
143 mCanvasConnections.clear();
158 mLineEdit->setPlaceholderText( text );
169 window()->activateWindow();
170 if (
string.isEmpty() )
172 mLineEdit->setFocus();
173 mLineEdit->selectAll();
177 scheduleDelayedPopup();
178 mLineEdit->setFocus();
179 mLineEdit->setText(
string );
187 mResultsContainer->hide();
190void QgsLocatorWidget::scheduleDelayedPopup()
195void QgsLocatorWidget::resultAdded()
197 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
201 bool selectable =
false;
202 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
205 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
208 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
212void QgsLocatorWidget::showContextMenu(
const QPoint &point )
214 QModelIndex index = mResultsView->indexAt( point );
215 if ( !index.isValid() )
219 QMenu *contextMenu =
new QMenu( mResultsView );
220 for (
auto resultAction : actions )
222 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
223 if ( !resultAction.iconPath.isEmpty() )
224 menuAction->setIcon( QIcon( resultAction.iconPath ) );
225 connect( menuAction, &QAction::triggered,
this, [=]() { mModelBridge->
triggerResult( index, resultAction.id ); } );
226 contextMenu->addAction( menuAction );
228 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
231void QgsLocatorWidget::performSearch()
238void QgsLocatorWidget::showList()
240 mResultsContainer->show();
241 mResultsContainer->raise();
244void QgsLocatorWidget::triggerSearchAndShowList()
246 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
254 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
256 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
257 switch ( keyEvent->key() )
262 case Qt::Key_PageDown:
263 triggerSearchAndShowList();
264 mHasSelectedResult =
true;
265 QgsApplication::sendEvent( mResultsView, event );
269 if ( keyEvent->modifiers() & Qt::ControlModifier )
271 triggerSearchAndShowList();
272 mHasSelectedResult =
true;
273 QgsApplication::sendEvent( mResultsView, event );
279 acceptCurrentEntry();
282 mResultsContainer->hide();
285 if ( !mLineEdit->performCompletion() )
287 mHasSelectedResult =
true;
288 mResultsView->selectNextResult();
291 case Qt::Key_Backtab:
292 mHasSelectedResult =
true;
293 mResultsView->selectPreviousResult();
299 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
301 mHasSelectedResult =
true;
303 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
305 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
308 mResultsContainer->hide();
311 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
315 else if ( obj == window() && event->type() == QEvent::Resize )
317 mResultsView->recalculateSize();
319 return QWidget::eventFilter( obj, event );
322void QgsLocatorWidget::configMenuAboutToShow()
327 if ( !filter->enabled() )
330 QAction *action =
new QAction( filter->displayName(), mMenu );
331 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 );
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.
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.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
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,...