QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsexpressionpreviewwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsexpressionpreviewwidget.cpp
3 --------------------------------------
4 Date : march 2020 - quarantine day 12
5 Copyright : (C) 2020 by Denis Rouzaud
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16
17#include "qclipboard.h"
18#include "qaction.h"
19#include "qgsapplication.h"
21#include "qgsmessageviewer.h"
22#include "qgsvectorlayer.h"
24
25
26
28 : QWidget( parent )
29{
30 setupUi( this );
31 mPreviewLabel->clear();
32 mPreviewLabel->setContextMenuPolicy( Qt::ActionsContextMenu );
33 mCopyPreviewAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ), tr( "Copy Expression Value" ), this );
34 mPreviewLabel->addAction( mCopyPreviewAction );
35 mFeaturePickerWidget->setShowBrowserButtons( true );
36
38 connect( mPreviewLabel, &QLabel::linkActivated, this, &QgsExpressionPreviewWidget::linkActivated );
39 connect( mCopyPreviewAction, &QAction::triggered, this, &QgsExpressionPreviewWidget::copyFullExpressionValue );
40}
41
43{
44 mLayer = layer;
45 mFeaturePickerWidget->setLayer( layer );
46}
47
48void QgsExpressionPreviewWidget::setExpressionText( const QString &expression )
49{
50 mExpressionText = expression;
51 refreshPreview();
52}
53
55{
56 // todo: update the combo box if it has been set externaly?
57
58 mExpressionContext.setFeature( feature );
59 refreshPreview();
60}
61
63{
64 mDa = da;
65 mUseGeomCalculator = true;
66}
67
69{
70 mExpressionContext = context;
71}
72
73void QgsExpressionPreviewWidget::refreshPreview()
74{
75 // If the string is empty the expression will still "fail" although
76 // we don't show the user an error as it will be confusing.
77 if ( mExpressionText.isEmpty() )
78 {
79 mPreviewLabel->clear();
80 mPreviewLabel->setStyleSheet( QString() );
81 mCopyPreviewAction->setEnabled( false );
82 setExpressionToolTip( QString() );
83 emit expressionParsed( false );
84 mExpression = QgsExpression();
85 }
86 else
87 {
88 mExpression = QgsExpression( mExpressionText );
89
90 if ( mUseGeomCalculator )
91 {
92 // only set an explicit geometry calculator if a call to setGeomCalculator was made. If not,
93 // let the expression context handle this correctly
94 mExpression.setGeomCalculator( &mDa );
95 }
96 const QVariant value = mExpression.evaluate( &mExpressionContext );
97 const QString preview = QgsExpression::formatPreviewString( value );
98 if ( !mExpression.hasEvalError() )
99 {
100 mPreviewLabel->setText( preview );
101 mCopyPreviewAction->setEnabled( true );
102 }
103
104 if ( mExpression.hasParserError() || mExpression.hasEvalError() )
105 {
106 // if parser error was a result of missing feature, then skip the misleading parser error message
107 // and instead show a friendly message, and allow the user to accept the expression anyway
108 // refs https://github.com/qgis/QGIS/issues/42884
109 if ( !mExpressionContext.feature().isValid() )
110 {
111 if ( !mExpression.referencedColumns().isEmpty() || mExpression.needsGeometry() )
112 {
113 mPreviewLabel->setText( tr( "No feature was found on this layer to evaluate the expression." ) );
114 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(220, 125, 0, 255);" ) );
115 emit expressionParsed( true );
116 setParserError( false );
117 setEvalError( false );
118 return;
119 }
120 }
121
122 const QString errorString = mExpression.parserErrorString().replace( QLatin1String( "\n" ), QLatin1String( "<br>" ) );
123 QString tooltip;
124 if ( mExpression.hasParserError() )
125 tooltip = QStringLiteral( "<b>%1:</b>"
126 "%2" ).arg( tr( "Parser Errors" ), errorString );
127 // Only show the eval error if there is no parser error.
128 if ( !mExpression.hasParserError() && mExpression.hasEvalError() )
129 tooltip += QStringLiteral( "<b>%1:</b> %2" ).arg( tr( "Eval Error" ), mExpression.evalErrorString() );
130
131 mPreviewLabel->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
132 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
133 setExpressionToolTip( tooltip );
134 emit expressionParsed( false );
135 setParserError( mExpression.hasParserError() );
136 setEvalError( mExpression.hasEvalError() );
137 mCopyPreviewAction->setEnabled( false );
138 }
139 else
140 {
141 mPreviewLabel->setStyleSheet( QString() );
142 const QString longerPreview = QgsExpression::formatPreviewString( value, true, 255 );
143 if ( longerPreview != preview )
144 setExpressionToolTip( longerPreview );
145 else
146 setExpressionToolTip( QString() );
147 emit expressionParsed( true );
148 setParserError( false );
149 setEvalError( false );
150 mCopyPreviewAction->setEnabled( true );
151 }
152 }
153}
154
155void QgsExpressionPreviewWidget::linkActivated( const QString & )
156{
157 QgsMessageViewer mv( this, QgsGuiUtils::ModalDialogFlags, false );
158 mv.setWindowTitle( tr( "More Info on Expression Error" ) );
159 mv.setMessageAsHtml( mToolTip );
160 mv.exec();
161}
162
163void QgsExpressionPreviewWidget::setExpressionToolTip( const QString &toolTip )
164{
165 if ( toolTip == mToolTip )
166 return;
167
168 mToolTip = toolTip;
169 if ( toolTip.isEmpty() )
170 {
171 mPreviewLabel->setToolTip( tr( "Right-click to copy" ) );
172 }
173 else
174 {
175 mPreviewLabel->setToolTip( tr( "%1 (right-click to copy)" ).arg( mToolTip ) );
176 }
177 emit toolTipChanged( mToolTip );
178}
179
180void QgsExpressionPreviewWidget::setParserError( bool parserError )
181{
182 if ( parserError != mParserError )
183 {
184 mParserError = parserError;
185 emit parserErrorChanged();
186 }
187}
189{
190 return mParserError;
191}
192
193void QgsExpressionPreviewWidget::setEvalError( bool evalError )
194{
195 if ( evalError == mEvalError )
196 return;
197
198 mEvalError = evalError;
199 emit evalErrorChanged();
200}
201
203{
204 return mEvalError;
205}
206
207void QgsExpressionPreviewWidget::copyFullExpressionValue()
208{
209 QClipboard *clipboard = QApplication::clipboard();
210 const QVariant value = mExpression.evaluate( &mExpressionContext );
211 const QString copiedValue = QgsExpression::formatPreviewString( value, false, 100000 );
212 QgsDebugMsgLevel( QStringLiteral( "set clipboard: %1" ).arg( copiedValue ), 4 );
213 clipboard->setText( copiedValue );
214}
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsFeature feature() const
Convenience function for retrieving the feature for the context, if set.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
QgsExpressionPreviewWidget(QWidget *parent=nullptr)
Constructor.
void parserErrorChanged()
Will be set to true if the current expression text reported a parser error with the context.
void evalErrorChanged()
Will be set to true if the current expression text reported an eval error with the context.
void toolTipChanged(const QString &toolTip)
Emitted whenever the tool tip changed.
bool parserError() const
Will be set to true if the current expression text reports a parser error with the context.
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
bool evalError() const
Will be set to true if the current expression text reported an eval error with the context.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context for the widget.
void setLayer(QgsVectorLayer *layer)
Sets the layer used in the preview.
void setCurrentFeature(const QgsFeature &feature)
sets the current feature used
void setExpressionText(const QString &expression)
Sets the expression.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QString evalErrorString() const
Returns evaluation error.
QString parserErrorString() const
Returns parser error.
static QString formatPreviewString(const QVariant &value, bool htmlOutput=true, int maximumPreviewLength=60)
Formats an expression result for friendly display to the user.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
void setGeomCalculator(const QgsDistanceArea *calc)
Sets the geometry calculator used for distance and area calculations in expressions.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
bool needsGeometry() const
Returns true if the expression uses feature geometry for some computation.
QVariant evaluate()
Evaluate the feature and return the result.
void featureChanged(const QgsFeature &feature)
Sends the feature as soon as it is chosen.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:219
A generic message view for displaying QGIS messages.
Represents a vector layer which manages a vector based data sets.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39