31 #include <QTextLayout>
37 , mLineEdit( new QgsLocatorLineEdit( this ) )
38 , mResultsView( new QgsLocatorResultsView() )
40 mLineEdit->setShowClearButton(
true );
42 mLineEdit->setPlaceholderText( tr(
"Type to locate (⌘K)" ) );
44 mLineEdit->setPlaceholderText( tr(
"Type to locate (Ctrl+K)" ) );
47 int placeholderMinWidth = mLineEdit->fontMetrics().boundingRect( mLineEdit->placeholderText() ).width();
48 int minWidth = std::max( 200,
static_cast< int >( placeholderMinWidth * 1.8 ) );
49 resize( minWidth, 30 );
50 QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
51 sizePolicy.setHorizontalStretch( 0 );
52 sizePolicy.setVerticalStretch( 0 );
53 setSizePolicy( sizePolicy );
54 setMinimumSize( QSize( minWidth, 0 ) );
56 QHBoxLayout *layout =
new QHBoxLayout();
57 layout->setContentsMargins( 0, 0, 0, 0 );
58 layout->addWidget( mLineEdit );
61 setFocusProxy( mLineEdit );
69 QHBoxLayout *containerLayout =
new QHBoxLayout();
70 containerLayout->setContentsMargins( 0, 0, 0, 0 );
71 containerLayout->addWidget( mResultsView );
72 mResultsContainer->setLayout( containerLayout );
73 mResultsContainer->hide();
75 mResultsView->setModel( mModelBridge->
proxyModel() );
76 mResultsView->setUniformRowHeights(
true );
80 mResultsView->recalculateSize();
81 mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
83 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsLocatorWidget::scheduleDelayedPopup );
84 connect( mResultsView, &QAbstractItemView::activated,
this, &QgsLocatorWidget::acceptCurrentEntry );
85 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
92 mPopupTimer.setInterval( 100 );
93 mPopupTimer.setSingleShot(
true );
94 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
95 mFocusTimer.setInterval( 110 );
96 mFocusTimer.setSingleShot(
true );
97 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
99 mLineEdit->installEventFilter(
this );
100 mResultsContainer->installEventFilter(
this );
101 mResultsView->installEventFilter(
this );
102 installEventFilter(
this );
103 window()->installEventFilter(
this );
107 mMenu =
new QMenu(
this );
108 QAction *menuAction = mLineEdit->addAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/search.svg" ) ), QLineEdit::LeadingPosition );
109 connect( menuAction, &QAction::triggered,
this, [ = ]
112 mResultsContainer->hide();
113 mMenu->exec( QCursor::pos() );
115 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
127 return mModelBridge->
locator();
132 if ( mMapCanvas == canvas )
135 for (
const QMetaObject::Connection &conn : std::as_const( mCanvasConnections ) )
139 mCanvasConnections.clear();
154 window()->activateWindow();
155 if (
string.isEmpty() )
157 mLineEdit->setFocus();
158 mLineEdit->selectAll();
162 scheduleDelayedPopup();
163 mLineEdit->setFocus();
164 mLineEdit->setText(
string );
172 mResultsContainer->hide();
175 void QgsLocatorWidget::scheduleDelayedPopup()
180 void QgsLocatorWidget::resultAdded()
182 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
186 bool selectable =
false;
187 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
190 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
193 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
197 void QgsLocatorWidget::showContextMenu(
const QPoint &point )
199 QModelIndex index = mResultsView->indexAt( point );
200 if ( !index.isValid() )
203 const QList<QgsLocatorResult::ResultAction> actions = mResultsView->model()->data( index,
QgsLocatorModel::ResultActionsRole ).value<QList<QgsLocatorResult::ResultAction>>();
204 QMenu *contextMenu =
new QMenu( mResultsView );
205 for (
auto resultAction : actions )
207 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
208 if ( !resultAction.iconPath.isEmpty() )
209 menuAction->setIcon( QIcon( resultAction.iconPath ) );
210 connect( menuAction, &QAction::triggered,
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
211 contextMenu->addAction( menuAction );
213 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
216 void QgsLocatorWidget::performSearch()
223 void QgsLocatorWidget::showList()
225 mResultsContainer->show();
226 mResultsContainer->raise();
229 void QgsLocatorWidget::triggerSearchAndShowList()
231 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
239 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
241 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
242 switch ( keyEvent->key() )
247 case Qt::Key_PageDown:
248 triggerSearchAndShowList();
249 mHasSelectedResult =
true;
250 QgsApplication::sendEvent( mResultsView, event );
254 if ( keyEvent->modifiers() & Qt::ControlModifier )
256 triggerSearchAndShowList();
257 mHasSelectedResult =
true;
258 QgsApplication::sendEvent( mResultsView, event );
264 acceptCurrentEntry();
267 mResultsContainer->hide();
270 if ( !mLineEdit->performCompletion() )
272 mHasSelectedResult =
true;
273 mResultsView->selectNextResult();
276 case Qt::Key_Backtab:
277 mHasSelectedResult =
true;
278 mResultsView->selectPreviousResult();
284 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
286 mHasSelectedResult =
true;
288 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
290 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
293 mResultsContainer->hide();
296 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
300 else if ( obj == window() && event->type() == QEvent::Resize )
302 mResultsView->recalculateSize();
304 return QWidget::eventFilter( obj, event );
307 void QgsLocatorWidget::configMenuAboutToShow()
312 if ( !filter->enabled() )
315 QAction *action =
new QAction( filter->displayName(), mMenu );
316 connect( action, &QAction::triggered,
this, [ = ]
318 QString currentText = mLineEdit->text();
319 if ( currentText.isEmpty() )
320 currentText = tr(
"<type here>" );
323 QStringList parts = currentText.split(
' ' );
324 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
327 currentText = parts.join(
' ' );
331 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
332 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
334 mMenu->addAction( action );
336 mMenu->addSeparator();
337 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
339 mMenu->addAction( configAction );
343 void QgsLocatorWidget::acceptCurrentEntry()
351 if ( !mResultsView->isVisible() )
354 QModelIndex index = mResultsView->currentIndex();
355 if ( !index.isValid() )
358 mResultsContainer->hide();
359 mLineEdit->clearFocus();
370 QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
371 : QTreeView( parent )
373 setRootIsDecorated(
false );
374 setUniformRowHeights(
true );
376 header()->setStretchLastSection(
true );
379 void QgsLocatorResultsView::recalculateSize()
382 int rowSize = 20 * itemDelegate()->sizeHint( viewOptions(), model()->index( 0, 0 ) ).height();
385 int width = std::max( 300, window()->size().width() / 2 );
386 QSize newSize( width, rowSize + frameWidth() * 2 );
388 parentWidget()->resize( newSize );
389 QTreeView::resize( newSize );
391 header()->resizeSection( 0, width / 2 );
392 header()->resizeSection( 1, 0 );
395 void QgsLocatorResultsView::selectNextResult()
397 int nextRow = currentIndex().row() + 1;
398 nextRow = nextRow % model()->rowCount( QModelIndex() );
399 setCurrentIndex( model()->index( nextRow, 0 ) );
402 void QgsLocatorResultsView::selectPreviousResult()
404 int previousRow = currentIndex().row() - 1;
405 if ( previousRow < 0 )
406 previousRow = model()->rowCount( QModelIndex() ) - 1;
407 setCurrentIndex( model()->index( previousRow, 0 ) );
414 QgsLocatorFilterFilter::QgsLocatorFilterFilter(
QgsLocatorWidget *locator, QObject *parent )
416 , mLocator( locator )
419 QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const
421 return new QgsLocatorFilterFilter( mLocator );
424 QgsLocatorFilter::Flags QgsLocatorFilterFilter::flags()
const
431 if ( !
string.isEmpty() )
442 if ( filter ==
this || !filter || !filter->enabled() )
448 result.
userData = QString( filter->activePrefix() +
' ' );
450 emit resultFetched( result );
454 void QgsLocatorFilterFilter::triggerResult(
const QgsLocatorResult &result )
456 mLocator->search( result.
userData.toString() );
459 QgsLocatorLineEdit::QgsLocatorLineEdit(
QgsLocatorWidget *locator, QWidget *parent )
461 , mLocatorWidget( locator )
466 void QgsLocatorLineEdit::paintEvent( QPaintEvent *event )
474 QLineEdit::paintEvent( event );
479 QString currentText = text();
481 if ( currentText.length() == 0 || cursorPosition() < currentText.length() )
484 const QStringList completionList = mLocatorWidget->locator()->completionList();
486 mCompletionText.clear();
488 for (
const QString &candidate : completionList )
490 if ( candidate.startsWith( currentText ) )
492 completion = candidate.right( candidate.length() - currentText.length() );
493 mCompletionText = candidate;
498 if ( completion.isEmpty() )
503 QRect cr = cursorRect();
504 QPoint pos = cr.topRight() - QPoint( cr.width() / 2, 0 );
506 QTextLayout l( completion, font() );
508 QTextLine line = l.createLine();
509 line.setLineWidth( width() - pos.x() );
510 line.setPosition( pos );
514 p.setPen( QPen( Qt::gray, 1 ) );
515 l.draw( &p, QPoint( 0, 0 ) );
518 bool QgsLocatorLineEdit::performCompletion()
520 if ( !mCompletionText.isEmpty() )
522 setText( mCompletionText );
523 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 SIP_HOLDGIL
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 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.
@ ResultActionsRole
The actions to be shown for the given result in a context menu.
Encapsulates properties of an individual matching result found by a QgsLocatorFilter.
QVariant userData
Custom reference or other data set by the filter.
QString description
Descriptive text for 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 takes output image size into accou...
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.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...