35 , mResultsView( new QgsLocatorResultsView() )
39 mLineEdit->setPlaceholderText( tr(
"Type to locate (⌘K)" ) );
41 mLineEdit->setPlaceholderText( tr(
"Type to locate (Ctrl+K)" ) );
44 int placeholderMinWidth = mLineEdit->fontMetrics().boundingRect( mLineEdit->placeholderText() ).width();
45 int minWidth = std::max( 200,
static_cast< int >( placeholderMinWidth * 1.8 ) );
46 resize( minWidth, 30 );
47 QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
48 sizePolicy.setHorizontalStretch( 0 );
49 sizePolicy.setVerticalStretch( 0 );
50 setSizePolicy( sizePolicy );
51 setMinimumSize( QSize( minWidth, 0 ) );
53 QHBoxLayout *layout =
new QHBoxLayout();
54 layout->setMargin( 0 );
55 layout->setContentsMargins( 0, 0, 0, 0 );
56 layout->addWidget( mLineEdit );
59 setFocusProxy( mLineEdit );
67 QHBoxLayout *containerLayout =
new QHBoxLayout();
68 containerLayout->setMargin( 0 );
69 containerLayout->setContentsMargins( 0, 0, 0, 0 );
70 containerLayout->addWidget( mResultsView );
71 mResultsContainer->setLayout( containerLayout );
72 mResultsContainer->hide();
74 mResultsView->setModel( mModelBridge->
proxyModel() );
75 mResultsView->setUniformRowHeights(
true );
79 mResultsView->recalculateSize();
80 mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
82 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsLocatorWidget::scheduleDelayedPopup );
83 connect( mResultsView, &QAbstractItemView::activated,
this, &QgsLocatorWidget::acceptCurrentEntry );
84 connect( mResultsView, &QAbstractItemView::customContextMenuRequested,
this, &QgsLocatorWidget::showContextMenu );
91 mPopupTimer.setInterval( 100 );
92 mPopupTimer.setSingleShot(
true );
93 connect( &mPopupTimer, &QTimer::timeout,
this, &QgsLocatorWidget::performSearch );
94 mFocusTimer.setInterval( 110 );
95 mFocusTimer.setSingleShot(
true );
96 connect( &mFocusTimer, &QTimer::timeout,
this, &QgsLocatorWidget::triggerSearchAndShowList );
98 mLineEdit->installEventFilter(
this );
99 mResultsContainer->installEventFilter(
this );
100 mResultsView->installEventFilter(
this );
101 installEventFilter(
this );
102 window()->installEventFilter(
this );
106 mMenu =
new QMenu(
this );
107 QAction *menuAction = mLineEdit->addAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/search.svg" ) ), QLineEdit::LeadingPosition );
108 connect( menuAction, &QAction::triggered,
this, [ = ]
111 mResultsContainer->hide();
112 mMenu->exec( QCursor::pos() );
114 connect( mMenu, &QMenu::aboutToShow,
this, &QgsLocatorWidget::configMenuAboutToShow );
120 return mModelBridge->
locator();
125 if ( mMapCanvas == canvas )
128 for (
const QMetaObject::Connection &conn : qgis::as_const( mCanvasConnections ) )
132 mCanvasConnections.clear();
147 window()->activateWindow();
148 if (
string.isEmpty() )
150 mLineEdit->setFocus();
151 mLineEdit->selectAll();
155 scheduleDelayedPopup();
156 mLineEdit->setFocus();
157 mLineEdit->setText(
string );
165 mResultsContainer->hide();
168 void QgsLocatorWidget::scheduleDelayedPopup()
173 void QgsLocatorWidget::resultAdded()
175 bool selectFirst = !mHasSelectedResult || mModelBridge->
proxyModel()->rowCount() == 0;
179 bool selectable =
false;
180 while ( !selectable && row < mModelBridge->proxyModel()->rowCount() )
183 selectable = mModelBridge->
proxyModel()->flags( mModelBridge->
proxyModel()->index( row, 0 ) ).testFlag( Qt::ItemIsSelectable );
186 mResultsView->setCurrentIndex( mModelBridge->
proxyModel()->index( row, 0 ) );
190 void QgsLocatorWidget::showContextMenu(
const QPoint &point )
192 QModelIndex index = mResultsView->indexAt( point );
193 if ( !index.isValid() )
196 const QList<QgsLocatorResult::ResultAction> actions = mResultsView->model()->data( index,
QgsLocatorModel::ResultActionsRole ).value<QList<QgsLocatorResult::ResultAction>>();
197 QMenu *contextMenu =
new QMenu( mResultsView );
198 for (
auto resultAction : actions )
200 QAction *menuAction =
new QAction( resultAction.text, contextMenu );
201 if ( !resultAction.iconPath.isEmpty() )
202 menuAction->setIcon( QIcon( resultAction.iconPath ) );
203 connect( menuAction, &QAction::triggered,
this, [ = ]() {mModelBridge->
triggerResult( index, resultAction.id );} );
204 contextMenu->addAction( menuAction );
206 contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
209 void QgsLocatorWidget::performSearch()
216 void QgsLocatorWidget::showList()
218 mResultsContainer->show();
219 mResultsContainer->raise();
222 void QgsLocatorWidget::triggerSearchAndShowList()
224 if ( mModelBridge->
proxyModel()->rowCount() == 0 )
232 if ( obj == mLineEdit && event->type() == QEvent::KeyPress )
234 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
235 switch ( keyEvent->key() )
240 case Qt::Key_PageDown:
241 triggerSearchAndShowList();
242 mHasSelectedResult =
true;
243 QgsApplication::sendEvent( mResultsView, event );
247 if ( keyEvent->modifiers() & Qt::ControlModifier )
249 triggerSearchAndShowList();
250 mHasSelectedResult =
true;
251 QgsApplication::sendEvent( mResultsView, event );
257 acceptCurrentEntry();
260 mResultsContainer->hide();
263 mHasSelectedResult =
true;
264 mResultsView->selectNextResult();
266 case Qt::Key_Backtab:
267 mHasSelectedResult =
true;
268 mResultsView->selectPreviousResult();
274 else if ( obj == mResultsView && event->type() == QEvent::MouseButtonPress )
276 mHasSelectedResult =
true;
278 else if ( event->type() == QEvent::FocusOut && ( obj == mLineEdit || obj == mResultsContainer || obj == mResultsView ) )
280 if ( !mLineEdit->hasFocus() && !mResultsContainer->hasFocus() && !mResultsView->hasFocus() )
283 mResultsContainer->hide();
286 else if ( event->type() == QEvent::FocusIn && obj == mLineEdit )
290 else if ( obj == window() && event->type() == QEvent::Resize )
292 mResultsView->recalculateSize();
294 return QWidget::eventFilter( obj, event );
297 void QgsLocatorWidget::configMenuAboutToShow()
302 if ( !filter->enabled() )
305 QAction *action =
new QAction( filter->displayName(), mMenu );
306 connect( action, &QAction::triggered,
this, [ = ]
308 QString currentText = mLineEdit->text();
309 if ( currentText.isEmpty() )
310 currentText = tr(
"<type here>" );
313 QStringList parts = currentText.split(
' ' );
314 if ( parts.count() > 1 && mModelBridge->
locator()->
filters( parts.at( 0 ) ).count() > 0 )
317 currentText = parts.join(
' ' );
321 mLineEdit->setText( filter->activePrefix() +
' ' + currentText );
322 mLineEdit->setSelection( filter->activePrefix().length() + 1, currentText.length() );
324 mMenu->addAction( action );
326 mMenu->addSeparator();
327 QAction *configAction =
new QAction( tr(
"Configure…" ), mMenu );
329 mMenu->addAction( configAction );
334 void QgsLocatorWidget::acceptCurrentEntry()
342 if ( !mResultsView->isVisible() )
345 QModelIndex index = mResultsView->currentIndex();
346 if ( !index.isValid() )
349 mResultsContainer->hide();
350 mLineEdit->clearFocus();
363 QgsLocatorResultsView::QgsLocatorResultsView( QWidget *parent )
364 : QTreeView( parent )
366 setRootIsDecorated(
false );
367 setUniformRowHeights(
true );
369 header()->setStretchLastSection(
true );
372 void QgsLocatorResultsView::recalculateSize()
375 int rowSize = 20 * itemDelegate()->sizeHint( viewOptions(), model()->index( 0, 0 ) ).height();
378 int width = std::max( 300, window()->size().width() / 2 );
379 QSize newSize( width, rowSize + frameWidth() * 2 );
381 parentWidget()->resize( newSize );
382 QTreeView::resize( newSize );
384 header()->resizeSection( 0, width / 2 );
385 header()->resizeSection( 1, 0 );
388 void QgsLocatorResultsView::selectNextResult()
390 int nextRow = currentIndex().row() + 1;
391 nextRow = nextRow % model()->rowCount( QModelIndex() );
392 setCurrentIndex( model()->index( nextRow, 0 ) );
395 void QgsLocatorResultsView::selectPreviousResult()
397 int previousRow = currentIndex().row() - 1;
398 if ( previousRow < 0 )
399 previousRow = model()->rowCount( QModelIndex() ) - 1;
400 setCurrentIndex( model()->index( previousRow, 0 ) );
407 QgsLocatorFilterFilter::QgsLocatorFilterFilter(
QgsLocatorWidget *locator, QObject *parent )
409 , mLocator( locator )
412 QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone()
const
414 return new QgsLocatorFilterFilter( mLocator );
417 QgsLocatorFilter::Flags QgsLocatorFilterFilter::flags()
const
424 if ( !
string.isEmpty() )
435 if ( filter ==
this || !filter || !filter->enabled() )
441 result.
userData = filter->activePrefix() +
' ';
443 emit resultFetched( result );
447 void QgsLocatorFilterFilter::triggerResult(
const QgsLocatorResult &result )
449 mLocator->search( result.
userData.toString() );