QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 mStackedWidget->setSizeMode( QgsStackedWidget::SizeMode::CurrentPageOnly );
37 mStackedWidget->setCurrentWidget( mPageFeaturePicker );
38
39 mCustomButtonNext->setEnabled( false );
40 mCustomButtonPrev->setEnabled( false );
42 connect( mCustomComboBox, qOverload< int >( &QComboBox::currentIndexChanged ), this, &QgsExpressionPreviewWidget::setCustomChoice );
43 connect( mPreviewLabel, &QLabel::linkActivated, this, &QgsExpressionPreviewWidget::linkActivated );
44 connect( mCopyPreviewAction, &QAction::triggered, this, &QgsExpressionPreviewWidget::copyFullExpressionValue );
45 connect( mCustomButtonPrev, &QToolButton::clicked, this, [this]
46 {
47 mCustomComboBox->setCurrentIndex( std::max( 0, mCustomComboBox->currentIndex() - 1 ) );
48 } );
49 connect( mCustomButtonNext, &QToolButton::clicked, this, [this]
50 {
51 mCustomComboBox->setCurrentIndex( std::min( mCustomComboBox->count() - 1, mCustomComboBox->currentIndex() + 1 ) );
52 } );
53}
54
56{
57 if ( layer != mLayer )
58 {
59 mLayer = layer;
60 mFeaturePickerWidget->setLayer( layer );
61 }
62}
63
64void QgsExpressionPreviewWidget::setCustomPreviewGenerator( const QString &label, const QList<QPair<QString, QVariant> > &choices, const std::function< QgsExpressionContext( const QVariant & ) > &previewContextGenerator )
65{
66 mCustomPreviewGeneratorFunction = previewContextGenerator;
67 mStackedWidget->setCurrentWidget( mPageCustomPicker );
68 mCustomLabel->setText( label );
69 mCustomComboBox->blockSignals( true );
70 mCustomComboBox->clear();
71 for ( const auto &choice : choices )
72 {
73 mCustomComboBox->addItem( choice.first, choice.second );
74 }
75 mCustomComboBox->blockSignals( false );
76 setCustomChoice( 0 );
77}
78
79void QgsExpressionPreviewWidget::setExpressionText( const QString &expression )
80{
81 if ( expression != mExpressionText )
82 {
83 mExpressionText = expression;
84 refreshPreview();
85 }
86}
87
89{
90 // todo: update the combo box if it has been set externally?
91 if ( feature != mExpressionContext.feature() )
92 {
93 mExpressionContext.setFeature( feature );
94 refreshPreview();
95 }
96}
97
99{
100 mDa = da;
101 mUseGeomCalculator = true;
102}
103
105{
106 mExpressionContext = context;
107}
108
109void QgsExpressionPreviewWidget::refreshPreview()
110{
111 // If the string is empty the expression will still "fail" although
112 // we don't show the user an error as it will be confusing.
113 if ( mExpressionText.isEmpty() )
114 {
115 mPreviewLabel->clear();
116 mPreviewLabel->setStyleSheet( QString() );
117 mCopyPreviewAction->setEnabled( false );
118 setExpressionToolTip( QString() );
119 emit expressionParsed( false );
120 mExpression = QgsExpression();
121 }
122 else
123 {
124 mExpression = QgsExpression( mExpressionText );
125
126 if ( mUseGeomCalculator )
127 {
128 // only set an explicit geometry calculator if a call to setGeomCalculator was made. If not,
129 // let the expression context handle this correctly
130 mExpression.setGeomCalculator( &mDa );
131 }
132 const QVariant value = mExpression.evaluate( &mExpressionContext );
133 const QString preview = QgsExpression::formatPreviewString( value );
134 if ( !mExpression.hasEvalError() )
135 {
136 mPreviewLabel->setText( preview );
137 mCopyPreviewAction->setEnabled( true );
138 }
139
140 if ( mExpression.hasParserError() || mExpression.hasEvalError() )
141 {
142 // if parser error was a result of missing feature, then skip the misleading parser error message
143 // and instead show a friendly message, and allow the user to accept the expression anyway
144 // refs https://github.com/qgis/QGIS/issues/42884
145 if ( !mExpressionContext.feature().isValid() )
146 {
147 if ( !mExpression.referencedColumns().isEmpty() || mExpression.needsGeometry() )
148 {
149 mPreviewLabel->setText( tr( "No feature was found on this layer to evaluate the expression." ) );
150 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(220, 125, 0, 255);" ) );
151 emit expressionParsed( true );
152 setParserError( false );
153 setEvalError( false );
154 return;
155 }
156 }
157
158 const QString errorString = mExpression.parserErrorString().replace( QLatin1String( "\n" ), QLatin1String( "<br>" ) );
159 QString tooltip;
160 if ( mExpression.hasParserError() )
161 tooltip = QStringLiteral( "<b>%1:</b>"
162 "%2" ).arg( tr( "Parser Errors" ), errorString );
163 // Only show the eval error if there is no parser error.
164 if ( !mExpression.hasParserError() && mExpression.hasEvalError() )
165 tooltip += QStringLiteral( "<b>%1:</b> %2" ).arg( tr( "Eval Error" ), mExpression.evalErrorString() );
166
167 mPreviewLabel->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
168 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
169 setExpressionToolTip( tooltip );
170 emit expressionParsed( false );
171 setParserError( mExpression.hasParserError() );
172 setEvalError( mExpression.hasEvalError() );
173 mCopyPreviewAction->setEnabled( false );
174 }
175 else
176 {
177 mPreviewLabel->setStyleSheet( QString() );
178 const QString longerPreview = QgsExpression::formatPreviewString( value, true, 255 );
179 if ( longerPreview != preview )
180 setExpressionToolTip( longerPreview );
181 else
182 setExpressionToolTip( QString() );
183 emit expressionParsed( true );
184 setParserError( false );
185 setEvalError( false );
186 mCopyPreviewAction->setEnabled( true );
187 }
188 }
189}
190
191void QgsExpressionPreviewWidget::linkActivated( const QString & )
192{
193 QgsMessageViewer mv( this, QgsGuiUtils::ModalDialogFlags, false );
194 mv.setWindowTitle( tr( "More Info on Expression Error" ) );
195 mv.setMessageAsHtml( mToolTip );
196 mv.exec();
197}
198
199void QgsExpressionPreviewWidget::setExpressionToolTip( const QString &toolTip )
200{
201 if ( toolTip == mToolTip )
202 return;
203
204 mToolTip = toolTip;
205 if ( toolTip.isEmpty() )
206 {
207 mPreviewLabel->setToolTip( tr( "Right-click to copy" ) );
208 }
209 else
210 {
211 mPreviewLabel->setToolTip( tr( "%1 (right-click to copy)" ).arg( mToolTip ) );
212 }
213 emit toolTipChanged( mToolTip );
214}
215
216void QgsExpressionPreviewWidget::setParserError( bool parserError )
217{
218 if ( parserError != mParserError )
219 {
220 mParserError = parserError;
221 emit parserErrorChanged();
222 }
223}
225{
226 return mParserError;
227}
228
230{
231 return mPreviewLabel->text();
232}
233
234void QgsExpressionPreviewWidget::setEvalError( bool evalError )
235{
236 if ( evalError == mEvalError )
237 return;
238
239 mEvalError = evalError;
240 emit evalErrorChanged();
241}
242
244{
245 return mEvalError;
246}
247
248void QgsExpressionPreviewWidget::copyFullExpressionValue()
249{
250 QClipboard *clipboard = QApplication::clipboard();
251 const QVariant value = mExpression.evaluate( &mExpressionContext );
252 const QString copiedValue = QgsExpression::formatPreviewString( value, false, 100000 );
253 QgsDebugMsgLevel( QStringLiteral( "set clipboard: %1" ).arg( copiedValue ), 4 );
254 clipboard->setText( copiedValue );
255}
256
257void QgsExpressionPreviewWidget::setCustomChoice( int )
258{
259 const QVariant selectedValue = mCustomComboBox->currentData();
260
261 mCustomButtonPrev->setEnabled( mCustomComboBox->currentIndex() > 0 && mCustomComboBox->count() > 0 );
262 mCustomButtonNext->setEnabled( mCustomComboBox->currentIndex() < ( mCustomComboBox->count() - 1 ) && mCustomComboBox->count() > 0 );
263
264 mExpressionContext = mCustomPreviewGeneratorFunction( selectedValue );
265
266 refreshPreview();
267}
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.
void setCustomPreviewGenerator(const QString &label, const QList< QPair< QString, QVariant > > &choices, const std::function< QgsExpressionContext(const QVariant &) > &previewContextGenerator)
Sets the widget to run using a custom preview generator.
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.
QString currentPreviewText() const
Returns the current expression result preview text.
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:216
A generic message view for displaying QGIS messages.
@ CurrentPageOnly
Only the size of the current page is considered when calculating the stacked widget size.
Represents a vector layer which manages a vector based data sets.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39