QGIS API Documentation 3.36.0-Maidenhead (09951dc0acf)
Loading...
Searching...
No Matches
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 if ( layer != mLayer )
45 {
46 mLayer = layer;
47 mFeaturePickerWidget->setLayer( layer );
48 }
49}
50
51void QgsExpressionPreviewWidget::setExpressionText( const QString &expression )
52{
53 if ( expression != mExpressionText )
54 {
55 mExpressionText = expression;
56 refreshPreview();
57 }
58}
59
61{
62 // todo: update the combo box if it has been set externaly?
63 if ( feature != mExpressionContext.feature() )
64 {
65 mExpressionContext.setFeature( feature );
66 refreshPreview();
67 }
68}
69
71{
72 mDa = da;
73 mUseGeomCalculator = true;
74}
75
77{
78 mExpressionContext = context;
79}
80
81void QgsExpressionPreviewWidget::refreshPreview()
82{
83 // If the string is empty the expression will still "fail" although
84 // we don't show the user an error as it will be confusing.
85 if ( mExpressionText.isEmpty() )
86 {
87 mPreviewLabel->clear();
88 mPreviewLabel->setStyleSheet( QString() );
89 mCopyPreviewAction->setEnabled( false );
90 setExpressionToolTip( QString() );
91 emit expressionParsed( false );
92 mExpression = QgsExpression();
93 }
94 else
95 {
96 mExpression = QgsExpression( mExpressionText );
97
98 if ( mUseGeomCalculator )
99 {
100 // only set an explicit geometry calculator if a call to setGeomCalculator was made. If not,
101 // let the expression context handle this correctly
102 mExpression.setGeomCalculator( &mDa );
103 }
104 const QVariant value = mExpression.evaluate( &mExpressionContext );
105 const QString preview = QgsExpression::formatPreviewString( value );
106 if ( !mExpression.hasEvalError() )
107 {
108 mPreviewLabel->setText( preview );
109 mCopyPreviewAction->setEnabled( true );
110 }
111
112 if ( mExpression.hasParserError() || mExpression.hasEvalError() )
113 {
114 // if parser error was a result of missing feature, then skip the misleading parser error message
115 // and instead show a friendly message, and allow the user to accept the expression anyway
116 // refs https://github.com/qgis/QGIS/issues/42884
117 if ( !mExpressionContext.feature().isValid() )
118 {
119 if ( !mExpression.referencedColumns().isEmpty() || mExpression.needsGeometry() )
120 {
121 mPreviewLabel->setText( tr( "No feature was found on this layer to evaluate the expression." ) );
122 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(220, 125, 0, 255);" ) );
123 emit expressionParsed( true );
124 setParserError( false );
125 setEvalError( false );
126 return;
127 }
128 }
129
130 const QString errorString = mExpression.parserErrorString().replace( QLatin1String( "\n" ), QLatin1String( "<br>" ) );
131 QString tooltip;
132 if ( mExpression.hasParserError() )
133 tooltip = QStringLiteral( "<b>%1:</b>"
134 "%2" ).arg( tr( "Parser Errors" ), errorString );
135 // Only show the eval error if there is no parser error.
136 if ( !mExpression.hasParserError() && mExpression.hasEvalError() )
137 tooltip += QStringLiteral( "<b>%1:</b> %2" ).arg( tr( "Eval Error" ), mExpression.evalErrorString() );
138
139 mPreviewLabel->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
140 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
141 setExpressionToolTip( tooltip );
142 emit expressionParsed( false );
143 setParserError( mExpression.hasParserError() );
144 setEvalError( mExpression.hasEvalError() );
145 mCopyPreviewAction->setEnabled( false );
146 }
147 else
148 {
149 mPreviewLabel->setStyleSheet( QString() );
150 const QString longerPreview = QgsExpression::formatPreviewString( value, true, 255 );
151 if ( longerPreview != preview )
152 setExpressionToolTip( longerPreview );
153 else
154 setExpressionToolTip( QString() );
155 emit expressionParsed( true );
156 setParserError( false );
157 setEvalError( false );
158 mCopyPreviewAction->setEnabled( true );
159 }
160 }
161}
162
163void QgsExpressionPreviewWidget::linkActivated( const QString & )
164{
165 QgsMessageViewer mv( this, QgsGuiUtils::ModalDialogFlags, false );
166 mv.setWindowTitle( tr( "More Info on Expression Error" ) );
167 mv.setMessageAsHtml( mToolTip );
168 mv.exec();
169}
170
171void QgsExpressionPreviewWidget::setExpressionToolTip( const QString &toolTip )
172{
173 if ( toolTip == mToolTip )
174 return;
175
176 mToolTip = toolTip;
177 if ( toolTip.isEmpty() )
178 {
179 mPreviewLabel->setToolTip( tr( "Right-click to copy" ) );
180 }
181 else
182 {
183 mPreviewLabel->setToolTip( tr( "%1 (right-click to copy)" ).arg( mToolTip ) );
184 }
185 emit toolTipChanged( mToolTip );
186}
187
188void QgsExpressionPreviewWidget::setParserError( bool parserError )
189{
190 if ( parserError != mParserError )
191 {
192 mParserError = parserError;
193 emit parserErrorChanged();
194 }
195}
197{
198 return mParserError;
199}
200
201void QgsExpressionPreviewWidget::setEvalError( bool evalError )
202{
203 if ( evalError == mEvalError )
204 return;
205
206 mEvalError = evalError;
207 emit evalErrorChanged();
208}
209
211{
212 return mEvalError;
213}
214
215void QgsExpressionPreviewWidget::copyFullExpressionValue()
216{
217 QClipboard *clipboard = QApplication::clipboard();
218 const QVariant value = mExpression.evaluate( &mExpressionContext );
219 const QString copiedValue = QgsExpression::formatPreviewString( value, false, 100000 );
220 QgsDebugMsgLevel( QStringLiteral( "set clipboard: %1" ).arg( copiedValue ), 4 );
221 clipboard->setText( copiedValue );
222}
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.
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