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, [
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, [
this, index, resultAction]() { 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, [
this, filter] {
 
  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.
 
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,...