Skip to content

Commit

Permalink
QML locator filter framework for QField plugin (#5841)
Browse files Browse the repository at this point in the history
  • Loading branch information
nirvn authored Nov 27, 2024
1 parent ca3b47d commit 6a0f143
Show file tree
Hide file tree
Showing 23 changed files with 590 additions and 12 deletions.
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(QFIELD_CORE_SRCS
locator/finlandlocatorfilter.cpp
locator/gotolocatorfilter.cpp
locator/helplocatorfilter.cpp
locator/qfieldlocatorfilter.cpp
locator/locatormodelsuperbridge.cpp
positioning/abstractgnssreceiver.cpp
positioning/gnsspositioninformation.cpp
Expand Down Expand Up @@ -146,6 +147,7 @@ set(QFIELD_CORE_HDRS
locator/finlandlocatorfilter.h
locator/gotolocatorfilter.h
locator/helplocatorfilter.h
locator/qfieldlocatorfilter.h
locator/locatormodelsuperbridge.h
positioning/abstractgnssreceiver.h
positioning/gnsspositioninformation.h
Expand Down
1 change: 1 addition & 0 deletions src/core/appinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <QObject>
#include <QPointF>
#include <QQmlComponent>
#include <QStandardItemModel>

class QgisMobileapp;
Expand Down
8 changes: 4 additions & 4 deletions src/core/locator/activelayerfeatureslocatorfilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,10 @@ void ActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, cons
result.userData = QVariantList() << f.id() << mLayerId;
#endif
result.score = static_cast<double>( searchString.length() ) / result.displayString.size();
result.actions << QgsLocatorResult::ResultAction( OpenForm, tr( "Open form" ), QStringLiteral( "ic_baseline-list_white_24dp" ) );
result.actions << QgsLocatorResult::ResultAction( OpenForm, tr( "Open form" ), QStringLiteral( "qrc:/themes/qfield/nodpi/ic_baseline-list_white_24dp.svg" ) );
if ( mLayerIsSpatial )
{
result.actions << QgsLocatorResult::ResultAction( Navigation, tr( "Set feature as destination" ), QStringLiteral( "ic_navigation_flag_purple_24dp" ) );
result.actions << QgsLocatorResult::ResultAction( Navigation, tr( "Set feature as destination" ), QStringLiteral( "qrc:/themes/qfield/nodpi/ic_navigation_flag_purple_24dp.svg" ) );
}

emit resultFetched( result );
Expand Down Expand Up @@ -296,10 +296,10 @@ void ActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, cons
result.userData = QVariantList() << f.id() << mLayerId;
#endif
result.score = static_cast<double>( searchString.length() ) / result.displayString.size();
result.actions << QgsLocatorResult::ResultAction( OpenForm, tr( "Open form" ), QStringLiteral( "ic_baseline-list_white_24dp" ) );
result.actions << QgsLocatorResult::ResultAction( OpenForm, tr( "Open form" ), QStringLiteral( "qrc:/themes/qfield/nodpi/ic_baseline-list_white_24dp.svg" ) );
if ( mLayerIsSpatial )
{
result.actions << QgsLocatorResult::ResultAction( Navigation, tr( "Set feature as destination" ), QStringLiteral( "ic_navigation_flag_purple_24dp" ) );
result.actions << QgsLocatorResult::ResultAction( Navigation, tr( "Set feature as destination" ), QStringLiteral( "qrc:/themes/qfield/nodpi/ic_navigation_flag_purple_24dp.svg" ) );
}

emit resultFetched( result );
Expand Down
4 changes: 2 additions & 2 deletions src/core/locator/featureslocatorfilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ void FeaturesLocatorFilter::fetchResults( const QString &string, const QgsLocato
#endif
result.icon = preparedLayer->layerIcon;
result.score = static_cast<double>( string.length() ) / result.displayString.size();
result.actions << QgsLocatorResult::ResultAction( OpenForm, tr( "Open form" ), QStringLiteral( "ic_baseline-list_white_24dp" ) );
result.actions << QgsLocatorResult::ResultAction( OpenForm, tr( "Open form" ), QStringLiteral( "qrc:/themes/qfield/nodpi/ic_baseline-list_white_24dp.svg" ) );
if ( preparedLayer->layerGeometryType != Qgis::GeometryType::Null && preparedLayer->layerGeometryType != Qgis::GeometryType::Unknown )
{
result.actions << QgsLocatorResult::ResultAction( Navigation, tr( "Set feature as destination" ), QStringLiteral( "ic_navigation_flag_purple_24dp" ) );
result.actions << QgsLocatorResult::ResultAction( Navigation, tr( "Set feature as destination" ), QStringLiteral( "qrc:/themes/qfield/nodpi/ic_navigation_flag_purple_24dp.svg" ) );
}

emit resultFetched( result );
Expand Down
25 changes: 25 additions & 0 deletions src/core/locator/locatormodelsuperbridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "helplocatorfilter.h"
#include "locatormodelsuperbridge.h"
#include "peliasgeocoder.h"
#include "qfieldlocatorfilter.h"
#include "qgsquickmapsettings.h"

#include <QStandardItem>
Expand All @@ -49,6 +50,18 @@ LocatorModelSuperBridge::LocatorModelSuperBridge( QObject *parent )
//locator()->registerFilter( new FinlandLocatorFilter( mFinlandGeocoder, this ) );
}

void LocatorModelSuperBridge::registerQFieldLocatorFilter( QFieldLocatorFilter *filter )
{
locator()->registerFilter( filter );
emit locatorFiltersChanged();
}

void LocatorModelSuperBridge::deregisterQFieldLocatorFilter( QFieldLocatorFilter *filter )
{
locator()->deregisterFilter( filter );
emit locatorFiltersChanged();
}

Navigation *LocatorModelSuperBridge::navigation() const
{
return mNavigation;
Expand Down Expand Up @@ -399,13 +412,25 @@ LocatorModelSuperBridge *LocatorFiltersModel::locatorModelSuperBridge() const
return mLocatorModelSuperBridge;
}

void LocatorFiltersModel::locatorFiltersChanged()
{
emit beginResetModel();
emit endResetModel();
}

void LocatorFiltersModel::setLocatorModelSuperBridge( LocatorModelSuperBridge *locatorModelSuperBridge )
{
if ( mLocatorModelSuperBridge == locatorModelSuperBridge )
return;

if ( mLocatorModelSuperBridge )
{
disconnect( mLocatorModelSuperBridge, &LocatorModelSuperBridge::locatorFiltersChanged, this, &LocatorFiltersModel::locatorFiltersChanged );
}
emit beginResetModel();
mLocatorModelSuperBridge = locatorModelSuperBridge;
emit locatorModelSuperBridgeChanged();
emit endResetModel();

connect( mLocatorModelSuperBridge, &LocatorModelSuperBridge::locatorFiltersChanged, this, &LocatorFiltersModel::locatorFiltersChanged );
}
16 changes: 15 additions & 1 deletion src/core/locator/locatormodelsuperbridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class QgsQuickMapSettings;
class FeatureListExtentController;
class PeliasGeocoder;
class GnssPositionInformation;
class QFieldLocatorFilter;
class QgsLocator;

/**
Expand Down Expand Up @@ -136,6 +137,16 @@ class LocatorModelSuperBridge : public QgsLocatorModelBridge
*/
Q_INVOKABLE QString getPrefixFromSearchString( const QString &string );

/**
* Registers a given \a filter with the locator.
*/
Q_INVOKABLE void registerQFieldLocatorFilter( QFieldLocatorFilter *filter );

/**
* Deregisters a given \a filter with the locator.
*/
Q_INVOKABLE void deregisterQFieldLocatorFilter( QFieldLocatorFilter *filter );

void emitMessage( const QString &text );

signals:
Expand All @@ -148,6 +159,7 @@ class LocatorModelSuperBridge : public QgsLocatorModelBridge
void messageEmitted( const QString &text );
void keepScaleChanged();
void searchTextChangeRequested( const QString &text );
void locatorFiltersChanged();

public slots:
Q_INVOKABLE void triggerResultAtRow( const int row, const int id = -1 );
Expand Down Expand Up @@ -199,9 +211,11 @@ class LocatorFiltersModel : public QAbstractListModel
Q_INVOKABLE void setGeocoderLocatorFiltersDefaulByPosition( const GnssPositionInformation &position );

signals:

void locatorModelSuperBridgeChanged();

private slots:
void locatorFiltersChanged();

private:
LocatorModelSuperBridge *mLocatorModelSuperBridge = nullptr;
};
Expand Down
175 changes: 175 additions & 0 deletions src/core/locator/qfieldlocatorfilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/***************************************************************************
qfieldlocatorfilter.cpp
---------------------
Date : November 2024
Copyright : (C) 2024 by Mathieu Pellerin
Email : mathieu at opengis dot ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qfieldlocatorfilter.h"

#include <QEventLoop>
#include <QQmlContext>
#include <QQmlEngine>
#include <QUrlQuery>
#include <qgsfeedback.h>

QFieldLocatorFilter::QFieldLocatorFilter( QObject *parent )
: QgsLocatorFilter( parent )
{
setUseWithoutPrefix( false );
}

QFieldLocatorFilter *QFieldLocatorFilter::clone() const
{
QFieldLocatorFilter *filter = new QFieldLocatorFilter();
filter->setFetchResultsDelay( fetchResultsDelay() );
filter->setName( mName );
filter->setDisplayName( mDisplayName );
filter->setPrefix( mPrefix );
filter->setParameters( mParameters );
filter->setSource( mSource );
filter->setLocatorBridge( mLocatorBridge );
return filter;
}

void QFieldLocatorFilter::setParameters( const QVariantMap &parameters )
{
if ( mParameters == parameters )
return;

mParameters = parameters;
emit parametersChanged();
}

void QFieldLocatorFilter::setSource( const QUrl &source )
{
if ( mSource.path() == source.path() )
return;

mSource = source;
QUrlQuery query( mSource );
if ( !query.hasQueryItem( "t" ) )
{
// Bypass caching to insure updated QML content is reloaded when the plugin is loaded
query.addQueryItem( QStringLiteral( "t" ), QStringLiteral( "t=%1" ).arg( QDateTime::currentSecsSinceEpoch() ) );
mSource.setQuery( query );
}
emit sourceChanged();
}

void QFieldLocatorFilter::setLocatorBridge( LocatorModelSuperBridge *locatorBridge )
{
if ( mLocatorBridge == locatorBridge )
return;

mLocatorBridge = locatorBridge;
emit locatorBridgeChanged();
}

void QFieldLocatorFilter::setDelay( int delay )
{
if ( fetchResultsDelay() == delay )
return;

setFetchResultsDelay( delay );
emit delayChanged();
}

void QFieldLocatorFilter::setName( const QString &name )
{
if ( mName == name )
return;

mName = name;
emit nameChanged();
}

void QFieldLocatorFilter::setDisplayName( const QString &displayName )
{
if ( mDisplayName == displayName )
return;

mDisplayName = displayName;
emit displayNameChanged();
}

void QFieldLocatorFilter::setPrefix( const QString &prefix )
{
if ( mPrefix == prefix )
return;

mPrefix = prefix;
emit prefixChanged();
}

void QFieldLocatorFilter::fetchResultsEnded()
{
mFetchResultsEnded = true;
}

void QFieldLocatorFilter::prepareResult( const QVariant &details )
{
const QVariantMap detailsMap = details.toMap();
QgsLocatorResult result;
result.setUserData( detailsMap.value( QStringLiteral( "userData" ) ) );
result.displayString = detailsMap.value( QStringLiteral( "displayString" ), QString() ).toString();
result.description = detailsMap.value( QStringLiteral( "description" ), QString() ).toString();
result.score = detailsMap.value( QStringLiteral( "score" ), 0.5 ).toDouble();
result.group = detailsMap.value( QStringLiteral( "group" ), QString() ).toString();
result.groupScore = detailsMap.value( QStringLiteral( "groupScore" ), 0.5 ).toDouble();
const QVariantList actions = detailsMap.value( QStringLiteral( "actions" ) ).toList();
for ( const QVariant &action : actions )
{
const QVariantMap actionMap = action.toMap();
result.actions << QgsLocatorResult::ResultAction( actionMap.value( QStringLiteral( "id" ), 0 ).toInt(), actionMap.value( QStringLiteral( "name" ), QString() ).toString(), actionMap.value( QStringLiteral( "icon" ), QString() ).toString() );
}
emit resultFetched( result );
}

void QFieldLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback )
{
if ( mSource.isEmpty() )
return;

QQmlEngine engine;
QQmlComponent component( &engine );
component.loadUrl( mSource );
QObject *object = component.create();
if ( object )
{
mFetchResultsEnded = false;
// These SIGNAL() SLOT() macros are needed, it is the only connect syntax that will work with signals declared within the QML environment itself
connect( object, SIGNAL( prepareResult( QVariant ) ), this, SLOT( prepareResult( QVariant ) ) );
connect( object, SIGNAL( fetchResultsEnded() ), this, SLOT( fetchResultsEnded() ) );

QEventLoop loop;
connect( object, SIGNAL( fetchResultsEnded() ), &loop, SLOT( quit() ) );
connect( feedback, &QgsFeedback::canceled, &loop, &QEventLoop::quit );

QMetaObject::invokeMethod( object, QStringLiteral( "fetchResults" ).toStdString().c_str(), QVariant( string ), QVariant::fromValue<QgsLocatorContext>( context ), QVariant::fromValue( mParameters ) );
if ( !mFetchResultsEnded )
{
loop.exec();
}
object->deleteLater();
}
}

void QFieldLocatorFilter::triggerResult( const QgsLocatorResult &result )
{
QMetaObject::invokeMethod( this, QStringLiteral( "triggerResult" ).toStdString().c_str(), QVariant::fromValue<QgsLocatorResult>( result ) );
}

void QFieldLocatorFilter::triggerResultFromAction( const QgsLocatorResult &result, const int actionId )
{
QMetaObject::invokeMethod( this, QStringLiteral( "triggerResultFromAction" ).toStdString().c_str(), QVariant::fromValue<QgsLocatorResult>( result ), QVariant( actionId ) );
}
Loading

0 comments on commit 6a0f143

Please sign in to comment.