Skip to content

Commit b22121a

Browse files
authored
[FEATURE][needs-docs] Hyperlink functions to show help in builder (#6796)
1 parent 44c72a0 commit b22121a

8 files changed

+222
-0
lines changed

python/core/expression/qgsexpressionnode.sip.in

+7
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,13 @@ work like for example resolving a column name to an attribute index.
228228
.. versionadded:: 2.12
229229
%End
230230

231+
int parserFirstLine;
232+
233+
int parserFirstColumn;
234+
235+
int parserLastLine;
236+
237+
int parserLastColumn;
231238

232239
protected:
233240

python/core/expression/qgsexpressionnodeimpl.sip.in

+29
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,21 @@ A combination of when and then. Simple as that.
409409
QgsExpressionNodeCondition::WhenThen *clone() const /Factory/;
410410
%Docstring
411411
Get a deep copy of this WhenThen combination.
412+
%End
413+
414+
QgsExpressionNode *whenExp() const;
415+
%Docstring
416+
The expression that makes the WHEN part of the condition.
417+
418+
:return: The expression node that makes the WHEN part of the condition check.
419+
%End
420+
421+
422+
QgsExpressionNode *thenExp() const;
423+
%Docstring
424+
The expression node that makes the THEN result part of the condition.
425+
426+
:return: The expression node that makes the THEN result part of the condition.
412427
%End
413428

414429
private:
@@ -433,6 +448,20 @@ Create a new node with the given list of ``conditions`` and an optional ``elseEx
433448
virtual QString dump() const;
434449

435450

451+
WhenThenList conditions() const;
452+
%Docstring
453+
The list of WHEN THEN expression parts of the expression.
454+
455+
:return: The list of WHEN THEN expression parts of the expression.
456+
%End
457+
458+
QgsExpressionNode *elseExp() const;
459+
%Docstring
460+
The ELSE expression used for the condition.
461+
462+
:return: The ELSE expression used for the condition.
463+
%End
464+
436465
virtual QSet<QString> referencedColumns() const;
437466

438467
virtual QSet<QString> referencedVariables() const;

src/core/expression/qgsexpressionnode.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,8 @@ void QgsExpressionNode::cloneTo( QgsExpressionNode *target ) const
5252
{
5353
target->mHasCachedValue = mHasCachedValue;
5454
target->mCachedStaticValue = mCachedStaticValue;
55+
target->parserLastColumn = parserLastColumn;
56+
target->parserLastLine = parserLastLine;
57+
target->parserFirstColumn = parserFirstColumn;
58+
target->parserFirstLine = parserFirstLine;
5559
}

src/core/expression/qgsexpressionnode.h

+27
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,33 @@ class CORE_EXPORT QgsExpressionNode SIP_ABSTRACT
252252
*/
253253
bool prepare( QgsExpression *parent, const QgsExpressionContext *context );
254254

255+
/**
256+
* First line in the parser this node was found.
257+
* \note This might not be complete for all nodes. Currently
258+
* only \see QgsExpressionNode has this complete
259+
*/
260+
int parserFirstLine = 0;
261+
262+
/**
263+
* First column in the parser this node was found.
264+
* \note This might not be complete for all nodes. Currently
265+
* only \see QgsExpressionNode has this complete
266+
*/
267+
int parserFirstColumn = 0;
268+
269+
/**
270+
* Last line in the parser this node was found.
271+
* \note This might not be complete for all nodes. Currently
272+
* only \see QgsExpressionNode has this complete
273+
*/
274+
int parserLastLine = 0;
275+
276+
/**
277+
* Last column in the parser this node was found.
278+
* \note This might not be complete for all nodes. Currently
279+
* only \see QgsExpressionNode has this complete
280+
*/
281+
int parserLastColumn = 0;
255282

256283
protected:
257284

src/core/expression/qgsexpressionnodeimpl.h

+25
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,19 @@ class CORE_EXPORT QgsExpressionNodeCondition : public QgsExpressionNode
398398
*/
399399
QgsExpressionNodeCondition::WhenThen *clone() const SIP_FACTORY;
400400

401+
/**
402+
* The expression that makes the WHEN part of the condition.
403+
* \return The expression node that makes the WHEN part of the condition check.
404+
*/
405+
QgsExpressionNode *whenExp() const { return mWhenExp; }
406+
407+
/**
408+
* The expression node that makes the THEN result part of the condition.
409+
* \return The expression node that makes the THEN result part of the condition.
410+
*/
411+
412+
QgsExpressionNode *thenExp() const { return mThenExp; }
413+
401414
private:
402415
#ifdef SIP_RUN
403416
WhenThen( const QgsExpressionNodeCondition::WhenThen &rh );
@@ -429,6 +442,18 @@ class CORE_EXPORT QgsExpressionNodeCondition : public QgsExpressionNode
429442
bool prepareNode( QgsExpression *parent, const QgsExpressionContext *context ) override;
430443
QString dump() const override;
431444

445+
/**
446+
* The list of WHEN THEN expression parts of the expression.
447+
* \return The list of WHEN THEN expression parts of the expression.
448+
*/
449+
WhenThenList conditions() const { return mConditions; }
450+
451+
/**
452+
* The ELSE expression used for the condition.
453+
* \return The ELSE expression used for the condition.
454+
*/
455+
QgsExpressionNode *elseExp() const { return mElseExp; }
456+
432457
QSet<QString> referencedColumns() const override;
433458
QSet<QString> referencedVariables() const override;
434459
bool needsGeometry() const override;

src/core/qgsexpressionparser.yy

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <qglobal.h>
1818
#include <QList>
1919
#include <cstdlib>
20+
#include "qgslogger.h"
2021
#include "expression/qgsexpression.h"
2122
#include "expression/qgsexpressionnode.h"
2223
#include "expression/qgsexpressionnodeimpl.h"
@@ -68,6 +69,14 @@ struct expression_parser_context
6869

6970
#define BINOP(x, y, z) new QgsExpressionNodeBinaryOperator(x, y, z)
7071

72+
void addParserLocation(YYLTYPE* yyloc, QgsExpressionNode *node)
73+
{
74+
node->parserFirstLine = yyloc->first_line;
75+
node->parserFirstColumn = yyloc->first_column;
76+
node->parserLastLine = yyloc->last_line;
77+
node->parserLastColumn = yyloc->last_column;
78+
}
79+
7180
%}
7281

7382
// make the parser reentrant
@@ -221,6 +230,7 @@ expression:
221230
YYERROR;
222231
}
223232
$$ = new QgsExpressionNodeFunction(fnIndex, $3);
233+
addParserLocation(&@1, $$);
224234
}
225235

226236
| FUNCTION '(' ')'
@@ -246,6 +256,7 @@ expression:
246256
YYERROR;
247257
}
248258
$$ = new QgsExpressionNodeFunction(fnIndex, new QgsExpressionNode::NodeList());
259+
addParserLocation(&@1, $$);
249260
}
250261

251262
| expression IN '(' exp_list ')' { $$ = new QgsExpressionNodeInOperator($1, $4, false); }

src/gui/qgsexpressionbuilderwidget.cpp

+114
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "qgslogger.h"
1818
#include "qgsexpression.h"
1919
#include "qgsexpressionfunction.h"
20+
#include "qgsexpressionnodeimpl.h"
2021
#include "qgsmessageviewer.h"
2122
#include "qgsapplication.h"
2223
#include "qgspythonrunner.h"
@@ -118,6 +119,7 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
118119
txtExpressionString->setWrapMode( QsciScintilla::WrapWord );
119120
lblAutoSave->clear();
120121

122+
121123
// Note: If you add a indicator here you should add it to clearErrors method if you need to clear it on text parse.
122124
txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionUnknown );
123125
txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionWrongArgs );
@@ -133,6 +135,14 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
133135
txtExpressionString->setIndicatorForegroundColor( QColor( Qt::red ), -1 );
134136
txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::red ), -1 );
135137
txtExpressionString->setIndicatorOutlineColor( QColor( Qt::red ), -1 );
138+
139+
// Hidden function markers.
140+
txtExpressionString->indicatorDefine( QgsCodeEditor::HiddenIndicator, FUNCTION_MARKER_ID );
141+
txtExpressionString->setIndicatorForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
142+
txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
143+
txtExpressionString->setIndicatorHoverStyle( QgsCodeEditor::DotsIndicator, FUNCTION_MARKER_ID );
144+
145+
connect( txtExpressionString, &QgsCodeEditorSQL::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
136146
}
137147

138148

@@ -384,6 +394,17 @@ void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, int
384394
mFieldValues[fieldName] = strValues;
385395
}
386396

397+
QString QgsExpressionBuilderWidget::getFunctionHelp( QgsExpressionFunction *function )
398+
{
399+
if ( !function )
400+
return QString();
401+
402+
QString helpContents = QgsExpression::helpText( function->name() );
403+
404+
return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
405+
406+
}
407+
387408
void QgsExpressionBuilderWidget::registerItem( const QString &group,
388409
const QString &label,
389410
const QString &expressionText,
@@ -666,6 +687,7 @@ void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
666687
emit expressionParsed( true );
667688
setParserError( false );
668689
setEvalError( false );
690+
createMarkers( exp.rootNode() );
669691
}
670692

671693
}
@@ -767,6 +789,86 @@ void QgsExpressionBuilderWidget::showEvent( QShowEvent *e )
767789
txtExpressionString->setFocus();
768790
}
769791

792+
void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode )
793+
{
794+
switch ( inNode->nodeType() )
795+
{
796+
case QgsExpressionNode::NodeType::ntFunction:
797+
{
798+
const QgsExpressionNodeFunction *node = static_cast<const QgsExpressionNodeFunction *>( inNode );
799+
txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, FUNCTION_MARKER_ID );
800+
txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORVALUE, node->fnIndex() );
801+
int start = inNode->parserFirstColumn - 1;
802+
int end = inNode->parserLastColumn - 1;
803+
int start_pos = txtExpressionString->positionFromLineIndex( inNode->parserFirstLine - 1, start );
804+
txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start_pos, end - start );
805+
if ( node->args() )
806+
{
807+
const QList< QgsExpressionNode * > nodeList = node->args()->list();
808+
for ( QgsExpressionNode *n : nodeList )
809+
{
810+
createMarkers( n );
811+
}
812+
}
813+
break;
814+
}
815+
case QgsExpressionNode::NodeType::ntLiteral:
816+
{
817+
break;
818+
}
819+
case QgsExpressionNode::NodeType::ntUnaryOperator:
820+
{
821+
const QgsExpressionNodeUnaryOperator *node = static_cast<const QgsExpressionNodeUnaryOperator *>( inNode );
822+
createMarkers( node->operand() );
823+
break;
824+
}
825+
case QgsExpressionNode::NodeType::ntBinaryOperator:
826+
{
827+
const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
828+
createMarkers( node->opLeft() );
829+
createMarkers( node->opRight() );
830+
break;
831+
}
832+
case QgsExpressionNode::NodeType::ntColumnRef:
833+
{
834+
break;
835+
}
836+
case QgsExpressionNode::NodeType::ntInOperator:
837+
{
838+
const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
839+
if ( node->list() )
840+
{
841+
const QList< QgsExpressionNode * > nodeList = node->list()->list();
842+
for ( QgsExpressionNode *n : nodeList )
843+
{
844+
createMarkers( n );
845+
}
846+
}
847+
break;
848+
}
849+
case QgsExpressionNode::NodeType::ntCondition:
850+
{
851+
const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
852+
for ( QgsExpressionNodeCondition::WhenThen *cond : node->conditions() )
853+
{
854+
createMarkers( cond->whenExp() );
855+
createMarkers( cond->thenExp() );
856+
}
857+
if ( node->elseExp() )
858+
{
859+
createMarkers( node->elseExp() );
860+
}
861+
break;
862+
}
863+
}
864+
}
865+
866+
void QgsExpressionBuilderWidget::clearFunctionMarkers()
867+
{
868+
int lastLine = txtExpressionString->lines() - 1;
869+
txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
870+
}
871+
770872
void QgsExpressionBuilderWidget::clearErrors()
771873
{
772874
int lastLine = txtExpressionString->lines() - 1;
@@ -903,6 +1005,18 @@ void QgsExpressionBuilderWidget::autosave()
9031005
anim->start( QAbstractAnimation::DeleteWhenStopped );
9041006
}
9051007

1008+
void QgsExpressionBuilderWidget::indicatorClicked( int line, int index, Qt::KeyboardModifiers state )
1009+
{
1010+
if ( state & Qt::ControlModifier )
1011+
{
1012+
int position = txtExpressionString->positionFromLineIndex( line, index );
1013+
long fncIndex = txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORVALUEAT, FUNCTION_MARKER_ID, ( long int )position );
1014+
QgsExpressionFunction *func = QgsExpression::Functions()[fncIndex];
1015+
QString help = getFunctionHelp( func );
1016+
txtHelpText->setText( help );
1017+
}
1018+
}
1019+
9061020
void QgsExpressionBuilderWidget::setExpressionState( bool state )
9071021
{
9081022
mExpressionValid = state;

src/gui/qgsexpressionbuilderwidget.h

+5
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp
307307
void setAutoSave( bool enabled ) { mAutoSave = enabled; }
308308

309309
private slots:
310+
void indicatorClicked( int line, int index, Qt::KeyboardModifiers state );
310311
void showContextMenu( QPoint );
311312
void setExpressionState( bool state );
312313
void currentChanged( const QModelIndex &index, const QModelIndex & );
@@ -352,10 +353,14 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp
352353
void showEvent( QShowEvent *e ) override;
353354

354355
private:
356+
int FUNCTION_MARKER_ID = 25;
357+
void createMarkers( const QgsExpressionNode *node );
358+
void clearFunctionMarkers();
355359
void clearErrors();
356360
void runPythonCode( const QString &code );
357361
void updateFunctionTree();
358362
void fillFieldValues( const QString &fieldName, int countLimit );
363+
QString getFunctionHelp( QgsExpressionFunction *function );
359364
QString loadFunctionHelp( QgsExpressionItem *functionName );
360365
QString helpStylesheet() const;
361366

0 commit comments

Comments
 (0)