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,...