Skip to content

Commit

Permalink
Merge pull request #5488 from opengisch/help_locator
Browse files Browse the repository at this point in the history
Add a QField documentation help locator for the search bar
  • Loading branch information
nirvn authored Jul 30, 2024
2 parents db6bfbd + 8584fb2 commit 2dbbc12
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ set(QFIELD_CORE_SRCS
locator/featureslocatorfilter.cpp
locator/finlandlocatorfilter.cpp
locator/gotolocatorfilter.cpp
locator/helplocatorfilter.cpp
locator/locatormodelsuperbridge.cpp
positioning/gnsspositioninformation.cpp
positioning/internalgnssreceiver.cpp
Expand Down Expand Up @@ -140,6 +141,7 @@ set(QFIELD_CORE_HDRS
locator/featureslocatorfilter.h
locator/finlandlocatorfilter.h
locator/gotolocatorfilter.h
locator/helplocatorfilter.h
locator/locatormodelsuperbridge.h
positioning/abstractgnssreceiver.h
positioning/gnsspositioninformation.h
Expand Down
147 changes: 147 additions & 0 deletions src/core/locator/helplocatorfilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/***************************************************************************
helplocatorfilter.cpp
---------------------
begin : 02.07.2024
copyright : (C) 2024 by Mathieu Pellerin
email : mathieu at opengis dot com
***************************************************************************
* *
* 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 "helplocatorfilter.h"
#include "locatormodelsuperbridge.h"

#include <QDesktopServices>
#include <QNetworkRequest>
#include <QTextDocument>
#include <qgsblockingnetworkrequest.h>
#include <qgsfeedback.h>
#include <qgsstringutils.h>

HelpLocatorFilter::HelpLocatorFilter( LocatorModelSuperBridge *locatorBridge, QObject *parent )
: QgsLocatorFilter( parent )
, mLocatorBridge( locatorBridge )
{
setFetchResultsDelay( 1000 );
setUseWithoutPrefix( false );
}

HelpLocatorFilter *HelpLocatorFilter::clone() const
{
return new HelpLocatorFilter( mLocatorBridge );
}

void HelpLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
{
Q_UNUSED( feedback )

if ( string.length() < 3 )
{
return;
}

const QString searchString = string.trimmed().toLower();
const QStringList words = searchString.split( ' ', Qt::SkipEmptyParts );
if ( string.length() < 3 || words.isEmpty() )
{
return;
}

QNetworkRequest request( QUrl( "https://docs.qfield.org/search/search_index.json" ) );
QgsBlockingNetworkRequest blockingRequest;
const QgsBlockingNetworkRequest::ErrorCode errorCode = blockingRequest.get( request, false, feedback );
if ( errorCode != QgsBlockingNetworkRequest::NoError )
{
return;
}

QJsonParseError err;
const QJsonDocument jsonDoc = QJsonDocument::fromJson( blockingRequest.reply().content(), &err );
if ( jsonDoc.isNull() )
{
return;
}
const QVariantMap searchMap = jsonDoc.object().toVariantMap();
const QStringList lang = searchMap.value( QStringLiteral( "config" ) ).toMap().value( QStringLiteral( "lang" ) ).toStringList();
const QList<QVariant> docs = searchMap.value( QStringLiteral( "docs" ) ).toList();

const QLocale locale;
QString userLocale = locale.name().mid( 0, 2 );
if ( !lang.contains( userLocale ) || userLocale == QStringLiteral( "en" ) )
{
userLocale.clear();
}

QRegularExpression rx( QStringLiteral( "\\A([a-z]{2})\\/" ) );
for ( const QVariant &doc : docs )
{
QVariantMap details = doc.toMap();
const QString title = details.value( QStringLiteral( "title" ) ).toString().toLower();
const QString text = details.value( QStringLiteral( "text" ) ).toString().toLower();

if ( text.isEmpty() )
{
continue;
}

const QString location = details.value( QStringLiteral( "location" ) ).toString();
QString locationLocale;
QRegularExpressionMatch rxMatch = rx.match( location );
if ( rxMatch.hasMatch() )
{
locationLocale = rxMatch.captured( 1 );
}

if ( locationLocale == userLocale )
{
bool match = false;
int matchScore = 0;
for ( const QString &word : words )
{
match = title.contains( word ) || text.contains( word );
matchScore += title.count( word ) * 2 + text.count( word );
}

if ( match )
{
if ( !location.isEmpty() )
{
if ( QgsStringUtils::soundex( title ) == QgsStringUtils::soundex( searchString ) )
{
// When the search term is a near-match to the title, add a big bonus (e.g. search term project matching page title projects)
matchScore += 100;
}

QTextDocument htmlDoc;
htmlDoc.setHtml( details.value( QStringLiteral( "text" ) ).toString() );

QgsLocatorResult result;
result.displayString = details.value( QStringLiteral( "title" ) ).toString();
result.description = htmlDoc.toPlainText();
result.score = matchScore;
result.filter = this;
result.setUserData( QStringLiteral( "https://docs.qfield.org/%1" ).arg( location ) );
emit resultFetched( result );
}
}
}
}
}

void HelpLocatorFilter::triggerResult( const QgsLocatorResult &result )
{
triggerResultFromAction( result, Normal );
}

void HelpLocatorFilter::triggerResultFromAction( const QgsLocatorResult &result, const int )
{
const QString url = result.userData().toString();
qDebug() << url;
QDesktopServices::openUrl( url );
}
57 changes: 57 additions & 0 deletions src/core/locator/helplocatorfilter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/***************************************************************************
helplocatorfilter.h
---------------------
begin : 02.07.2024
copyright : (C) 2024 by Mathieu Pellerin
email : mathieu at opengis dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/


#ifndef HELPLOCATORFILTER_H
#define HELPLOCATORFILTER_H

#include <QObject>
#include <qgslocatorfilter.h>


class LocatorModelSuperBridge;

/**
* HelpLocatorFilter is a locator filter to search
* for and display QField documentation pages.
*/
class HelpLocatorFilter : public QgsLocatorFilter
{
Q_OBJECT

public:
//! Origin of the action which triggers the result
enum ActionOrigin
{
Normal,
};

explicit HelpLocatorFilter( LocatorModelSuperBridge *locatorBridge, QObject *parent = nullptr );
HelpLocatorFilter *clone() const override;
QString name() const override { return QStringLiteral( "optionpages" ); } // name should be "help" but we're working around QGIS guarding against 1-character prefix
QString displayName() const override { return tr( "QField Documentation" ); }
Priority priority() const override { return Medium; }
QString prefix() const override { return QStringLiteral( "?" ); }

void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
void triggerResult( const QgsLocatorResult &result ) override;
void triggerResultFromAction( const QgsLocatorResult &result, const int actionId ) override;

private:
LocatorModelSuperBridge *mLocatorBridge = nullptr;
};

#endif // HELPLOCATORFILTER_H
3 changes: 3 additions & 0 deletions src/core/locator/locatormodelsuperbridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "finlandlocatorfilter.h"
#include "gnsspositioninformation.h"
#include "gotolocatorfilter.h"
#include "helplocatorfilter.h"
#include "locatormodelsuperbridge.h"
#include "peliasgeocoder.h"
#include "qgsquickmapsettings.h"
Expand All @@ -41,6 +42,7 @@ LocatorModelSuperBridge::LocatorModelSuperBridge( QObject *parent )
locator()->registerFilter( new GotoLocatorFilter( this ) );
locator()->registerFilter( new BookmarkLocatorFilter( this ) );
locator()->registerFilter( new ExpressionCalculatorLocatorFilter( this ) );
locator()->registerFilter( new HelpLocatorFilter( this ) );

// Finnish's Digitransit geocoder (disabled until API access can be sorted)
//mFinlandGeocoder = new PeliasGeocoder( QStringLiteral( "https://api.digitransit.fi/geocoding/v1/search" ) );
Expand Down Expand Up @@ -279,6 +281,7 @@ QVariant LocatorFiltersModel::data( const QModelIndex &index, int role ) const
{ QStringLiteral( "goto" ), tr( "Returns a point from a pair of X and Y coordinates - or WGS84 latitude and longitude - typed in the search bar." ) },
{ QStringLiteral( "bookmarks" ), tr( "Returns a list of user and currently open project bookmarks with matching names." ) },
{ QStringLiteral( "calculator" ), tr( "Returns the value of an expression typed in the search bar." ) },
{ QStringLiteral( "optionpages" ), tr( "Returns QField documentation pages matching terms." ) },
{ QStringLiteral( "pelias-finland" ), tr( "Returns a list of locations and addresses within Finland with matching terms." ) } };

if ( !mLocatorModelSuperBridge->locator() || !index.isValid() || index.parent().isValid() || index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
Expand Down
25 changes: 20 additions & 5 deletions src/qml/LocatorItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,17 @@ Item {
height: resultsList.count > 0 ? Math.min(contentHeight, mainWindow.height / 2 - searchFieldRect.height - 10) : 0
clip: true

ScrollBar.vertical: ScrollBar {
width: 6
policy: resultsList.contentHeight > resultsList.height ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff

contentItem: Rectangle {
implicitWidth: 6
implicitHeight: 25
color: Theme.mainColor
}
}

delegate: searchField.displayText !== '' ? resultsComponent : filtersComponent
}

Expand Down Expand Up @@ -389,8 +400,9 @@ Item {
id: nameCell
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 5
anchors.rightMargin: 5
text: Name + ' (' + Prefix + ')' + (Prefix === 'f' && dashBoard.activeLayer ? '' + dashBoard.activeLayer.name : '')
leftPadding: 5
font.bold: false
font.pointSize: Theme.resultFont.pointSize
color: Theme.mainTextColor
Expand All @@ -402,8 +414,9 @@ Item {
id: descriptionCell
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 5
anchors.rightMargin: 5
text: Description || ''
leftPadding: 5
font.bold: false
font.pointSize: Theme.resultFont.pointSize
color: Theme.secondaryTextColor
Expand Down Expand Up @@ -474,8 +487,9 @@ Item {
id: nameCell
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 5
anchors.rightMargin: 5
text: isFilterName ? ResultFilterName : typeof (model.Text) == 'string' ? model.Text.trim() : ''
leftPadding: 5
font.bold: false
font.pointSize: Theme.resultFont.pointSize
color: isFilterName ? "white" : Theme.mainTextColor
Expand All @@ -488,12 +502,13 @@ Item {
visible: !isFilterName && !isGroup && text !== ''
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 5
anchors.rightMargin: 5
text: locator.getLocatorModelDescription(index)
leftPadding: 5
font.bold: false
font.pointSize: Theme.resultFont.pointSize
color: Theme.secondaryTextColor
elide: Text.ElideRight
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignLeft
}
}
Expand Down

1 comment on commit 2dbbc12

@qfield-fairy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.