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