37  , mLineEdit( new QgsLocatorLineEdit( this ) )
 
   38  , mResultsView( new QgsLocatorResultsView() )
 
   40  setObjectName( QStringLiteral( 
"LocatorWidget" ) );
 
   41  mLineEdit->setShowClearButton( 
true );
 
   43  mLineEdit->setPlaceholderText( tr( 
"Type to locate (⌘K)" ) );
 
   45  mLineEdit->setPlaceholderText( tr( 
"Type to locate (Ctrl+K)" ) );
 
   48  int placeholderMinWidth = mLineEdit->fontMetrics().boundingRect( mLineEdit->placeholderText() ).width();
 
   49  int minWidth = std::max( 200, 
static_cast< int >( placeholderMinWidth * 1.8 ) );
 
   50  resize( minWidth, 30 );
 
   51  QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
 
   52  sizePolicy.setHorizontalStretch( 0 );
 
   53  sizePolicy.setVerticalStretch( 0 );
 
   54  setSizePolicy( sizePolicy );
 
   55  setMinimumSize( QSize( minWidth, 0 ) );
 
   57  QHBoxLayout *layout = 
new QHBoxLayout();
 
   58  layout->setContentsMargins( 0, 0, 0, 0 );
 
   59  layout->addWidget( mLineEdit );
 
   62  setFocusProxy( mLineEdit );
 
   70  QHBoxLayout *containerLayout = 
new QHBoxLayout();
 
   71  containerLayout->setContentsMargins( 0, 0, 0, 0 );
 
   72  containerLayout->addWidget( mResultsView );
 
   73  mResultsContainer->setLayout( containerLayout );
 
   74  mResultsContainer->hide();
 
   76  mResultsView->setModel( mModelBridge->
proxyModel() );
 
   77  mResultsView->setUniformRowHeights( 
true );
 
   80  mResultsView->setIconSize( QSize( iconSize, iconSize ) );
 
   81  mResultsView->recalculateSize();
 
   82  mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
 
   84  connect( mLineEdit, &QLineEdit::textChanged, 
this, &QgsLocatorWidget::scheduleDelayedPopup );
 
   85  connect( mResultsView, &QAbstractItemView::activated, 
this, &QgsLocatorWidget::acceptCurrentEntry );
 
   86  connect( mResultsView, &QAbstractItemView::customContextMenuRequested, 
this, &QgsLocatorWidget::showContextMenu );
 
   93  mPopupTimer.setInterval( 100 );
 
   94  mPopupTimer.setSingleShot( 
true );
 
   95  connect( &mPopupTimer, &QTimer::timeout, 
this, &QgsLocatorWidget::performSearch );
 
   96  mFocusTimer.setInterval( 110 );
 
   97  mFocusTimer.setSingleShot( 
true );
 
   98  connect( &mFocusTimer, &QTimer::timeout, 
this, &QgsLocatorWidget::triggerSearchAndShowList );
 
  100  mLineEdit->installEventFilter( 
this );
 
  101  mResultsContainer->installEventFilter( 
this );
 
  102  mResultsView->installEventFilter( 
this );
 
  103  installEventFilter( 
this );
 
  104  window()->installEventFilter( 
this );
 
  108  mMenu = 
new QMenu( 
this );
 
  109  QAction *menuAction = mLineEdit->addAction( 
QgsApplication::getThemeIcon( QStringLiteral( 
"/search.svg" ) ), QLineEdit::LeadingPosition );
 
  110  connect( menuAction, &QAction::triggered, 
this, [ = ]
 
  113    mResultsContainer->hide();
 
  114    mMenu->exec( QCursor::pos() );
 
  116  connect( mMenu, &QMenu::aboutToShow, 
this, &QgsLocatorWidget::configMenuAboutToShow );
 
 
  128  return mModelBridge->
locator();
 
 
  133  if ( mMapCanvas == canvas )
 
  136  for ( 
const QMetaObject::Connection &conn : std::as_const( mCanvasConnections ) )
 
  140  mCanvasConnections.clear();
 
 
  155  window()->activateWindow(); 
 
  156  if ( 
string.isEmpty() )
 
  158    mLineEdit->setFocus();
 
  159    mLineEdit->selectAll();
 
  163    scheduleDelayedPopup();
 
  164    mLineEdit->setFocus();
 
  165    mLineEdit->setText( 
string );
 
 
  173  mResultsContainer->hide();
 
 
  176void QgsLocatorWidget::scheduleDelayedPopup()
 
  181void QgsLocatorWidget::resultAdded()
 
  183  bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
 
  187    bool selectable = 
false;
 
  188    while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
 
  191      selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
 
  194      mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
 
  198void QgsLocatorWidget::showContextMenu( 
const QPoint &point )
 
  200  QModelIndex index = mResultsView->indexAt( point );
 
  201  if ( !index.isValid() )
 
  204  const QList<QgsLocatorResult::ResultAction> actions = mResultsView->model()->data( index, 
QgsLocatorModel::ResultActionsRole ).value<QList<QgsLocatorResult::ResultAction>>();
 
  205  QMenu *contextMenu = 
new QMenu( mResultsView );
 
  206  for ( 
auto resultAction : actions )
 
  208    QAction *menuAction = 
new QAction( resultAction.text, contextMenu );
 
  209    if ( !resultAction.iconPath.isEmpty() )
 
  210      menuAction->setIcon( QIcon( resultAction.iconPath ) );
 
  211    connect( menuAction, &QAction::triggered, 
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
 
  212    contextMenu->addAction( menuAction );
 
  214  contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
 
  217void QgsLocatorWidget::performSearch()
 
  224void QgsLocatorWidget::showList()
 
  226  mResultsContainer->show();
 
  227  mResultsContainer->raise();
 
  230void QgsLocatorWidget::triggerSearchAndShowList()
 
  232  if ( mModelBridge->
proxyModel()->rowCount() == 0 )
 
  240  if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
 
  242    QKeyEvent *keyEvent = 
static_cast<QKeyEvent *
>( event );
 
  243    switch ( keyEvent->key() )
 
  248      case Qt::Key_PageDown:
 
  249        triggerSearchAndShowList();
 
  250        mHasSelectedResult = 
true;
 
  251        QgsApplication::sendEvent( mResultsView, event );
 
  255        if ( keyEvent->modifiers() & Qt::ControlModifier )
 
  257          triggerSearchAndShowList();
 
  258          mHasSelectedResult = 
true;
 
  259          QgsApplication::sendEvent( mResultsView, event );
 
  265        acceptCurrentEntry();
 
  268        mResultsContainer->hide();
 
  271        if ( !mLineEdit->performCompletion() )
 
  273          mHasSelectedResult = 
true;
 
  274          mResultsView->selectNextResult();
 
  277      case Qt::Key_Backtab:
 
  278        mHasSelectedResult = 
true;
 
  279        mResultsView->selectPreviousResult();
 
  285  else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
 
  287    mHasSelectedResult = 
true;
 
  289  else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
 
  291    if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
 
  294      mResultsContainer->hide();
 
  297  else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
 
  301  else if ( obj == window() && event->type() == QEvent::Resize )
 
  303    mResultsView->recalculateSize();
 
  305  return QWidget::eventFilter( obj, event );
 
 
  308void QgsLocatorWidget::configMenuAboutToShow()
 
  313    if ( !filter->enabled() )
 
  316    QAction *action = 
new QAction( filter->displayName(), mMenu );
 
  317    connect( action, &QAction::triggered, 
this, [ = ]
 
  319      QString currentText = mLineEdit->text();
 
  320      if ( currentText.isEmpty() )
 
  321        currentText = tr( 
"<type here>" );
 
  324        QStringList parts = currentText.split( 
' ' );
 
  325        if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
 
  328          currentText = parts.join( 
' ' );
 
  332      mLineEdit->setText( filter->activePrefix() + 
' ' + currentText );
 
  333      mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
 
  335    mMenu->addAction( action );
 
  337  mMenu->addSeparator();
 
  338  QAction *configAction = 
new QAction( tr( 
"Configure…" ), mMenu );
 
  340  mMenu->addAction( configAction );
 
  344void QgsLocatorWidget::acceptCurrentEntry()
 
  352    if ( !mResultsView->isVisible() )
 
  355    QModelIndex index = mResultsView->currentIndex();
 
  356    if ( !index.isValid() )
 
  359    mResultsContainer->hide();
 
  360    mLineEdit->clearFocus();
 
  371QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
 
  372  : QTreeView( parent )
 
  374  setRootIsDecorated( 
false );
 
  375  setUniformRowHeights( 
true );
 
  377  header()->setStretchLastSection( 
true );
 
  380void QgsLocatorResultsView::recalculateSize()
 
  382  QStyleOptionViewItem optView;
 
  383#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 
  384  optView.init( 
this );
 
  386  optView.initFrom( 
this );
 
  390  int rowSize = 20 * itemDelegate()->sizeHint( optView, model()->index( 0, 0 ) ).height();
 
  393  int width = std::max( 300, window()->size().width() / 2 );
 
  394  QSize newSize( width, rowSize + frameWidth() * 2 );
 
  396  parentWidget()->resize( newSize );
 
  397  QTreeView::resize( newSize );
 
  399  header()->resizeSection( 0, width / 2 );
 
  400  header()->resizeSection( 1, 0 );
 
  403void QgsLocatorResultsView::selectNextResult()
 
  405  const int rowCount = model()->rowCount( QModelIndex() );
 
  409  int nextRow = currentIndex().row() + 1;
 
  410  nextRow = nextRow % rowCount;
 
  411  setCurrentIndex( model()->index( nextRow, 0 ) );
 
  414void QgsLocatorResultsView::selectPreviousResult()
 
  416  const int rowCount = model()->rowCount( QModelIndex() );
 
  420  int previousRow = currentIndex().row() - 1;
 
  421  if ( previousRow < 0 )
 
  422    previousRow = rowCount - 1;
 
  423  setCurrentIndex( model()->index( previousRow, 0 ) );
 
  430QgsLocatorFilterFilter::QgsLocatorFilterFilter( 
QgsLocatorWidget *locator, QObject *parent )
 
  432  , mLocator( locator )
 
  435QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
 const 
  437  return new QgsLocatorFilterFilter( mLocator );
 
  440QgsLocatorFilter::Flags QgsLocatorFilterFilter::flags()
 const 
  447  if ( !
string.isEmpty() )
 
  458    if ( filter == 
this || !filter || !filter->enabled() )
 
  464    result.
setUserData( QString( filter->activePrefix() + 
' ' ) );
 
  466    emit resultFetched( result );
 
  472  mLocator->search( result.
getUserData().toString() );
 
  475QgsLocatorLineEdit::QgsLocatorLineEdit( 
QgsLocatorWidget *locator, QWidget *parent )
 
  477  , mLocatorWidget( locator )
 
  482void QgsLocatorLineEdit::paintEvent( QPaintEvent *event )
 
  490  QLineEdit::paintEvent( event );
 
  495  QString currentText = text();
 
  497  if ( currentText.length() == 0 || cursorPosition() < currentText.length() )
 
  500  const QStringList completionList = mLocatorWidget->locator()->completionList();
 
  502  mCompletionText.clear();
 
  504  for ( 
const QString &candidate : completionList )
 
  506    if ( candidate.startsWith( currentText ) )
 
  508      completion = candidate.right( candidate.length() - currentText.length() );
 
  509      mCompletionText = candidate;
 
  514  if ( completion.isEmpty() )
 
  519  QRect cr = cursorRect();
 
  520  QPoint pos = cr.topRight() - QPoint( cr.width() / 2, 0 );
 
  522  QTextLayout l( completion, font() );
 
  524  QTextLine line = l.createLine();
 
  525  line.setLineWidth( width() - pos.x() );
 
  526  line.setPosition( pos );
 
  530  p.setPen( QPen( Qt::gray, 1 ) );
 
  531  l.draw( &p, QPoint( 0, 0 ) );
 
  534bool QgsLocatorLineEdit::performCompletion()
 
  536  if ( !mCompletionText.isEmpty() )
 
  538    setText( mCompletionText );
 
  539    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 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 getUserData() const
Returns the userData.
 
QString description
Descriptive text for result.
 
void setUserData(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,...