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