QGIS API Documentation 3.99.0-Master (26c88405ac0)
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
18
19#include "qgsapplication.h"
21#include "qgsmessageviewer.h"
22#include "qgsvectorlayer.h"
23
24#include <qaction.h>
25#include <qclipboard.h>
26
27#include "moc_qgsexpressionpreviewwidget.cpp"
28
30 : QWidget( parent )
31{
32 setupUi( this );
33 mPreviewLabel->clear();
34 mPreviewLabel->setContextMenuPolicy( Qt::ActionsContextMenu );
35 mCopyPreviewAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ), tr( "Copy Expression Value" ), this );
36 mPreviewLabel->addAction( mCopyPreviewAction );
37 mFeaturePickerWidget->setShowBrowserButtons( true );
38 mStackedWidget->setSizeMode( QgsStackedWidget::SizeMode::CurrentPageOnly );
39 mStackedWidget->setCurrentWidget( mPageFeaturePicker );
40
41 mCustomButtonNext->setEnabled( false );
42 mCustomButtonPrev->setEnabled( false );
44 connect( mCustomComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsExpressionPreviewWidget::setCustomChoice );
45 connect( mPreviewLabel, &QLabel::linkActivated, this, &QgsExpressionPreviewWidget::linkActivated );
46 connect( mCopyPreviewAction, &QAction::triggered, this, &QgsExpressionPreviewWidget::copyFullExpressionValue );
47 connect( mCustomButtonPrev, &QToolButton::clicked, this, [this] {
48 mCustomComboBox->setCurrentIndex( std::max( 0, mCustomComboBox->currentIndex() - 1 ) );
49 } );
50 connect( mCustomButtonNext, &QToolButton::clicked, this, [this] {
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" )
163 .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="
169 "more"
170 ">(more info)</a>" ) );
171 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
172 setExpressionToolTip( tooltip );
173 emit expressionParsed( false );
174 setParserError( mExpression.hasParserError() );
175 setEvalError( mExpression.hasEvalError() );
176 mCopyPreviewAction->setEnabled( false );
177 }
178 else
179 {
180 mPreviewLabel->setStyleSheet( QString() );
181 const QString longerPreview = QgsExpression::formatPreviewString( value, true, 255 );
182 if ( longerPreview != preview )
183 setExpressionToolTip( longerPreview );
184 else
185 setExpressionToolTip( QString() );
186 emit expressionParsed( true );
187 setParserError( false );
188 setEvalError( false );
189 mCopyPreviewAction->setEnabled( true );
190 }
191 }
192}
193
194void QgsExpressionPreviewWidget::linkActivated( const QString & )
195{
196 QgsMessageViewer mv( this, QgsGuiUtils::ModalDialogFlags, false );
197 mv.setWindowTitle( tr( "More Info on Expression Error" ) );
198 mv.setMessageAsHtml( mToolTip );
199 mv.exec();
200}
201
202void QgsExpressionPreviewWidget::setExpressionToolTip( const QString &toolTip )
203{
204 if ( toolTip == mToolTip )
205 return;
206
207 mToolTip = toolTip;
208 if ( toolTip.isEmpty() )
209 {
210 mPreviewLabel->setToolTip( tr( "Right-click to copy" ) );
211 }
212 else
213 {
214 mPreviewLabel->setToolTip( tr( "%1 (right-click to copy)" ).arg( mToolTip ) );
215 }
216 emit toolTipChanged( mToolTip );
217}
218
219void QgsExpressionPreviewWidget::setParserError( bool parserError )
220{
221 if ( parserError != mParserError )
222 {
223 mParserError = parserError;
224 emit parserErrorChanged();
225 }
226}
228{
229 return mParserError;
230}
231
233{
234 return mPreviewLabel->text();
235}
236
237void QgsExpressionPreviewWidget::setEvalError( bool evalError )
238{
239 if ( evalError == mEvalError )
240 return;
241
242 mEvalError = evalError;
243 emit evalErrorChanged();
244}
245
247{
248 return mEvalError;
249}
250
251void QgsExpressionPreviewWidget::copyFullExpressionValue()
252{
253 QClipboard *clipboard = QApplication::clipboard();
254 const QVariant value = mExpression.evaluate( &mExpressionContext );
255 const QString copiedValue = QgsExpression::formatPreviewString( value, false, 100000 );
256 QgsDebugMsgLevel( QStringLiteral( "set clipboard: %1" ).arg( copiedValue ), 4 );
257 clipboard->setText( copiedValue );
258}
259
260void QgsExpressionPreviewWidget::setCustomChoice( int )
261{
262 const QVariant selectedValue = mCustomComboBox->currentData();
263
264 mCustomButtonPrev->setEnabled( mCustomComboBox->currentIndex() > 0 && mCustomComboBox->count() > 0 );
265 mCustomButtonNext->setEnabled( mCustomComboBox->currentIndex() < ( mCustomComboBox->count() - 1 ) && mCustomComboBox->count() > 0 );
266
267 mExpressionContext = mCustomPreviewGeneratorFunction( selectedValue );
268
269 refreshPreview();
270}
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...
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.
Handles parsing and evaluation of expressions (formerly called "search strings").
static QString formatPreviewString(const QVariant &value, bool htmlOutput=true, int maximumPreviewLength=60)
Formats an expression result for friendly display to the user.
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
@ 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 dataset.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61