Skip to content
Permalink
Browse files

[FEATURE] enable auto completion in locator

a locator filter can now return a completion list while preparing the search
the line edit will use the first matching completion and display it as light grey text
the completion can be triggered by pressing Tab key
  • Loading branch information
3nids committed Sep 8, 2020
1 parent 014de96 commit 002a7033f09f714ef2671d0e778e5418ab513539
@@ -131,6 +131,14 @@ Returns ``True`` if a query is currently being executed by the locator.
Will call clearPreviousResults on all filters

.. versionadded:: 3.2
%End

QStringList completionList() const;
%Docstring
Returns the list for auto completion
This list is updated when preparing the search

.. versionadded:: 3.16
%End

signals:
@@ -139,6 +147,21 @@ Will call clearPreviousResults on all filters
%Docstring
Emitted whenever a filter encounters a matching ``result`` after the :py:func:`~QgsLocator.fetchResults` method
is called.
%End

void searchBegan();
%Docstring
Emitted when locator has begun a search, before actualy preparing it.

.. versionadded:: 3.16
%End

void searchPrepared();
%Docstring
Emitted when locator has prepared the search (:py:func:`QgsLocatorFilter.prepare`)
before the search is actually performed

.. versionadded:: 3.16
%End

void finished();
@@ -163,12 +163,13 @@ results from this filter.
.. seealso:: :py:func:`activePrefix`
%End

virtual void prepare( const QString &string, const QgsLocatorContext &context );
virtual QStringList prepare( const QString &string, const QgsLocatorContext &context );
%Docstring
Prepares the filter instance for an upcoming search for the specified ``string``. This method is always called
from the main thread, and individual filter subclasses should perform whatever
tasks are required in order to allow a subsequent search to safely execute
on a background thread.
The method return an autocompletion list
%End

virtual void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) = 0;
@@ -10,6 +10,7 @@




class QgsLocatorWidget : QWidget
{
%Docstring
@@ -124,6 +124,9 @@ void QgsLocator::registerFilter( QgsLocatorFilter *filter )

void QgsLocator::fetchResults( const QString &string, const QgsLocatorContext &c, QgsFeedback *feedback )
{
mAutocompleList.clear();
emit searchBegan();

QgsLocatorContext context( c );
// ideally this should not be required, as well behaved callers
// will NOT fire up a new fetchResults call while an existing one is
@@ -182,7 +185,15 @@ void QgsLocator::fetchResults( const QString &string, const QgsLocatorContext &c
result.filter = filter;
filterSentResult( result );
} );
clone->prepare( searchString, context );
QStringList autoCompleteList = clone->prepare( searchString, context );
if ( context.usingPrefix )
{
for ( int i = 0; i < autoCompleteList.length(); i++ )
{
autoCompleteList[i].prepend( QStringLiteral( "%1 " ).arg( prefix ) );
}
}
mAutocompleList.append( autoCompleteList );

if ( clone->flags() & QgsLocatorFilter::FlagFast )
{
@@ -191,7 +202,6 @@ void QgsLocator::fetchResults( const QString &string, const QgsLocatorContext &c
}
else
{
// run filter in background
threadedFilters.append( clone.release() );
}
}
@@ -220,6 +230,8 @@ void QgsLocator::fetchResults( const QString &string, const QgsLocatorContext &c
thread->start();
}

emit searchPrepared();

if ( mActiveThreads.empty() )
emit finished();
}
@@ -145,6 +145,13 @@ class CORE_EXPORT QgsLocator : public QObject
*/
void clearPreviousResults();

/**
* Returns the list for auto completion
* This list is updated when preparing the search
* \since QGIS 3.16
*/
QStringList completionList() const {return mAutocompleList;}

signals:

/**
@@ -153,6 +160,19 @@ class CORE_EXPORT QgsLocator : public QObject
*/
void foundResult( const QgsLocatorResult &result );

/**
* Emitted when locator has begun a search, before actualy preparing it.
* \since QGIS 3.16
*/
void searchBegan();

/**
* Emitted when locator has prepared the search (\see QgsLocatorFilter::prepare)
* before the search is actually performed
* \since QGIS 3.16
*/
void searchPrepared();

/**
* Emitted when locator has finished a query, either as a result
* of successful completion or early cancellation.
@@ -171,6 +191,8 @@ class CORE_EXPORT QgsLocator : public QObject
QList< QgsLocatorFilter * > mFilters;
QList< QThread * > mActiveThreads;

QStringList mAutocompleList;

void cancelRunningQuery();

};
@@ -219,8 +219,9 @@ class CORE_EXPORT QgsLocatorFilter : public QObject
* from the main thread, and individual filter subclasses should perform whatever
* tasks are required in order to allow a subsequent search to safely execute
* on a background thread.
* The method return an autocompletion list
*/
virtual void prepare( const QString &string, const QgsLocatorContext &context ) { Q_UNUSED( string ) Q_UNUSED( context ); }
virtual QStringList prepare( const QString &string, const QgsLocatorContext &context ) { Q_UNUSED( string ) Q_UNUSED( context ); return QStringList();}

/**
* Retrieves the filter results for a specified search \a string. The \a context
@@ -24,14 +24,17 @@
#include "qgsapplication.h"
#include "qgslogger.h"
#include "qgsguiutils.h"

#include <QLayout>
#include <QCompleter>
#include <QMenu>
#include <QTextLayout>
#include <QTextLine>

QgsLocatorWidget::QgsLocatorWidget( QWidget *parent )
: QWidget( parent )
, mModelBridge( new QgsLocatorModelBridge( this ) )
, mLineEdit( new QgsFilterLineEdit() )
, mLineEdit( new QgsLocatorLineEdit( this ) )
, mResultsView( new QgsLocatorResultsView() )
{
mLineEdit->setShowClearButton( true );
@@ -85,7 +88,7 @@ QgsLocatorWidget::QgsLocatorWidget( QWidget *parent )

connect( mModelBridge, &QgsLocatorModelBridge::resultAdded, this, &QgsLocatorWidget::resultAdded );
connect( mModelBridge, &QgsLocatorModelBridge::isRunningChanged, this, [ = ]() {mLineEdit->setShowSpinner( mModelBridge->isRunning() );} );
connect( mModelBridge, & QgsLocatorModelBridge::resultsCleared, this, [ = ]() {mHasSelectedResult = false;} );
connect( mModelBridge, &QgsLocatorModelBridge::resultsCleared, this, [ = ]() {mHasSelectedResult = false;} );

// have a tiny delay between typing text in line edit and showing the window
mPopupTimer.setInterval( 100 );
@@ -260,8 +263,11 @@ bool QgsLocatorWidget::eventFilter( QObject *obj, QEvent *event )
mResultsContainer->hide();
return true;
case Qt::Key_Tab:
mHasSelectedResult = true;
mResultsView->selectNextResult();
if ( !mLineEdit->performCompletion() )
{
mHasSelectedResult = true;
mResultsView->selectNextResult();
}
return true;
case Qt::Key_Backtab:
mHasSelectedResult = true;
@@ -330,7 +336,6 @@ void QgsLocatorWidget::configMenuAboutToShow()
}



void QgsLocatorWidget::acceptCurrentEntry()
{
if ( mModelBridge->hasQueueRequested() )
@@ -352,8 +357,6 @@ void QgsLocatorWidget::acceptCurrentEntry()
}
}



///@cond PRIVATE

//
@@ -449,5 +452,75 @@ void QgsLocatorFilterFilter::triggerResult( const QgsLocatorResult &result )
mLocator->search( result.userData.toString() );
}

QgsLocatorLineEdit::QgsLocatorLineEdit( QgsLocatorWidget *locator, QWidget *parent )
: QgsFilterLineEdit( parent )
, mLocatorWidget( locator )
{
connect( mLocatorWidget->locator(), &QgsLocator::searchPrepared, this, [&] { update(); } );
}

void QgsLocatorLineEdit::paintEvent( QPaintEvent *event )
{
// this adds the completion as grey text at the right of the cursor
// see https://stackoverflow.com/a/50425331/1548052
// this is possible that the completion might be badly rendered if the cursor is larger than the line edit
// this sounds acceptable as it is not very likely to use completion for super long texts
// for more details see https://stackoverflow.com/a/54218192/1548052

QLineEdit::paintEvent( event );

if ( !hasFocus() )
return;

QString currentText = text();

if ( currentText.length() == 0 || cursorPosition() < currentText.length() )
return;

const QStringList completionList = mLocatorWidget->locator()->completionList();

QString completion;
for ( const QString &candidate : completionList )
{
if ( candidate.startsWith( currentText ) )
{
completion = candidate.right( candidate.length() - currentText.length() );
mCompletionText = candidate;
break;
}
}

if ( completion.isEmpty() )
return;

ensurePolished(); // ensure font() is up to date

QRect cr = cursorRect();
QPoint pos = cr.topRight() - QPoint( cr.width() / 2, 0 );

QTextLayout l( completion, font() );
l.beginLayout();
QTextLine line = l.createLine();
line.setLineWidth( width() - pos.x() );
line.setPosition( pos );
l.endLayout();

QPainter p( this );
p.setPen( QPen( Qt::gray, 1 ) );
l.draw( &p, QPoint( 0, 0 ) );
}

bool QgsLocatorLineEdit::performCompletion()
{
if ( !mCompletionText.isEmpty() )
{
setText( mCompletionText );
mCompletionText.clear();
return true;
}
else
return false;
}


///@endcond
@@ -21,17 +21,19 @@
#include "qgis_gui.h"
#include "qgslocatorfilter.h"
#include "qgsfloatingwidget.h"
#include "qgsfilterlineedit.h"

#include <QWidget>
#include <QTreeView>
#include <QFocusEvent>
#include <QHeaderView>
#include <QTimer>

class QgsLocator;
class QgsFilterLineEdit;
class QgsLocatorResultsView;
class QgsMapCanvas;
class QgsLocatorModelBridge;
class QgsLocatorLineEdit;

/**
* \class QgsLocatorWidget
@@ -98,7 +100,7 @@ class GUI_EXPORT QgsLocatorWidget : public QWidget

private:
QgsLocatorModelBridge *mModelBridge = nullptr;
QgsFilterLineEdit *mLineEdit = nullptr;
QgsLocatorLineEdit *mLineEdit = nullptr;
QgsFloatingWidget *mResultsContainer = nullptr;
QgsLocatorResultsView *mResultsView = nullptr;
QgsMapCanvas *mMapCanvas = nullptr;
@@ -110,6 +112,7 @@ class GUI_EXPORT QgsLocatorWidget : public QWidget
bool mHasSelectedResult = false;

void acceptCurrentEntry();

};

#ifndef SIP_RUN
@@ -171,6 +174,30 @@ class GUI_EXPORT QgsLocatorResultsView : public QTreeView

};


/**
* \class QgsLocatorLineEdit
* \ingroup gui
* Custom line edit to handle completion within the line edit as a light gray text
* \since QGIS 3.16
*/
class QgsLocatorLineEdit : public QgsFilterLineEdit
{
Q_OBJECT
public:
explicit QgsLocatorLineEdit( QgsLocatorWidget *locator, QWidget *parent = nullptr );

//! Perform completion and returns true if successful
bool performCompletion();

protected:
void paintEvent( QPaintEvent *event ) override;

private:
QgsLocatorWidget *mLocatorWidget = nullptr;
QString mCompletionText = nullptr;
};

///@endcond

#endif

0 comments on commit 002a703

Please sign in to comment.
You can’t perform that action at this time.