QGIS API Documentation 3.41.0-Master (3440c17df1d)
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
27
29 : QWidget( parent )
30{
31 setupUi( this );
32 mPreviewLabel->clear();
33 mPreviewLabel->setContextMenuPolicy( Qt::ActionsContextMenu );
34 mCopyPreviewAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ), tr( "Copy Expression Value" ), this );
35 mPreviewLabel->addAction( mCopyPreviewAction );
36 mFeaturePickerWidget->setShowBrowserButtons( true );
37 mStackedWidget->setSizeMode( QgsStackedWidget::SizeMode::CurrentPageOnly );
38 mStackedWidget->setCurrentWidget( mPageFeaturePicker );
39
40 mCustomButtonNext->setEnabled( false );
41 mCustomButtonPrev->setEnabled( false );
43 connect( mCustomComboBox, qOverload< int >( &QComboBox::currentIndexChanged ), this, &QgsExpressionPreviewWidget::setCustomChoice );
44 connect( mPreviewLabel, &QLabel::linkActivated, this, &QgsExpressionPreviewWidget::linkActivated );
45 connect( mCopyPreviewAction, &QAction::triggered, this, &QgsExpressionPreviewWidget::copyFullExpressionValue );
46 connect( mCustomButtonPrev, &QToolButton::clicked, this, [this]
47 {
48 mCustomComboBox->setCurrentIndex( std::max( 0, mCustomComboBox->currentIndex() - 1 ) );
49 } );
50 connect( mCustomButtonNext, &QToolButton::clicked, this, [this]
51 {
52 mCustomComboBox->setCurrentIndex( std::min( mCustomComboBox->count() - 1, mCustomComboBox->currentIndex() + 1 ) );
53 } );
54}
55
57{
58 if ( layer != mLayer )
59 {
60 mLayer = layer;
61 mFeaturePickerWidget->setLayer( layer );
62 }
63}
64
65void QgsExpressionPreviewWidget::setCustomPreviewGenerator( const QString &label, const QList<QPair<QString, QVariant> > &choices, const std::function< QgsExpressionContext( const QVariant & ) > &previewContextGenerator )
66{
67 mCustomPreviewGeneratorFunction = previewContextGenerator;
68 mStackedWidget->setCurrentWidget( mPageCustomPicker );
69 mCustomLabel->setText( label );
70 mCustomComboBox->blockSignals( true );
71 mCustomComboBox->clear();
72 for ( const auto &choice : choices )
73 {
74 mCustomComboBox->addItem( choice.first, choice.second );
75 }
76 mCustomComboBox->blockSignals( false );
77 setCustomChoice( 0 );
78}
79
80void QgsExpressionPreviewWidget::setExpressionText( const QString &expression )
81{
82 if ( expression != mExpressionText )
83 {
84 mExpressionText = expression;
85 refreshPreview();
86 }
87}
88
90{
91 // todo: update the combo box if it has been set externally?
92 if ( feature != mExpressionContext.feature() )
93 {
94 mExpressionContext.setFeature( feature );
95 refreshPreview();
96 }
97}
98
100{
101 mDa = da;
102 mUseGeomCalculator = true;
103}
104
106{
107 mExpressionContext = context;
108}
109
110void QgsExpressionPreviewWidget::refreshPreview()
111{
112 // If the string is empty the expression will still "fail" although
113 // we don't show the user an error as it will be confusing.
114 if ( mExpressionText.isEmpty() )
115 {
116 mPreviewLabel->clear();
117 mPreviewLabel->setStyleSheet( QString() );
118 mCopyPreviewAction->setEnabled( false );
119 setExpressionToolTip( QString() );
120 emit expressionParsed( false );
121 mExpression = QgsExpression();
122 }
123 else
124 {
125 mExpression = QgsExpression( mExpressionText );
126
127 if ( mUseGeomCalculator )
128 {
129 // only set an explicit geometry calculator if a call to setGeomCalculator was made. If not,
130 // let the expression context handle this correctly
131 mExpression.setGeomCalculator( &mDa );
132 }
133 const QVariant value = mExpression.evaluate( &mExpressionContext );
134 const QString preview = QgsExpression::formatPreviewString( value );
135 if ( !mExpression.hasEvalError() )
136 {
137 mPreviewLabel->setText( preview );
138 mCopyPreviewAction->setEnabled( true );
139 }
140
141 if ( mExpression.hasParserError() || mExpression.hasEvalError() )
142 {
143 // if parser error was a result of missing feature, then skip the misleading parser error message
144 // and instead show a friendly message, and allow the user to accept the expression anyway
145 // refs https://github.com/qgis/QGIS/issues/42884
146 if ( !mExpressionContext.feature().isValid() )
147 {
148 if ( !mExpression.referencedColumns().isEmpty() || mExpression.needsGeometry() )
149 {
150 mPreviewLabel->setText( tr( "No feature was found on this layer to evaluate the expression." ) );
151 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(220, 125, 0, 255);" ) );
152 emit expressionParsed( true );
153 setParserError( false );
154 setEvalError( false );
155 return;
156 }
157 }
158
159 const QString errorString = mExpression.parserErrorString().replace( QLatin1String( "\n" ), QLatin1String( "<br>" ) );
160 QString tooltip;
161 if ( mExpression.hasParserError() )
162 tooltip = QStringLiteral( "<b>%1:</b>"
163 "%2" ).arg( tr( "Parser Errors" ), errorString );
164 // Only show the eval error if there is no parser error.
165 if ( !mExpression.hasParserError() && mExpression.hasEvalError() )
166 tooltip += QStringLiteral( "<b>%1:</b> %2" ).arg( tr( "Eval Error" ), mExpression.evalErrorString() );
167
168 mPreviewLabel->setText( tr( "Expression is invalid <a href=""more"">(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.
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: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