QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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] { mCustomComboBox->setCurrentIndex( std::max( 0, mCustomComboBox->currentIndex() - 1 ) ); } );
51 connect( mCustomButtonNext, &QToolButton::clicked, this, [this] { mCustomComboBox->setCurrentIndex( std::min( mCustomComboBox->count() - 1, mCustomComboBox->currentIndex() + 1 ) ); } );
52}
53
55{
56 if ( layer != mLayer )
57 {
58 mLayer = layer;
59 mFeaturePickerWidget->setLayer( layer );
60 }
61}
62
64 const QString &label, const QList<QPair<QString, QVariant>> &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator
65)
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( u"color: rgba(220, 125, 0, 255);"_s );
152 emit expressionParsed( true );
153 setParserError( false );
154 setEvalError( false );
155 return;
156 }
157 }
158
159 const QString errorString = mExpression.parserErrorString().replace( "\n"_L1, "<br>"_L1 );
160 QString tooltip;
161 if ( mExpression.hasParserError() )
162 tooltip = QStringLiteral(
163 "<b>%1:</b>"
164 "%2"
165 )
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(
172 "Expression is invalid <a href="
173 "more"
174 ">(more info)</a>"
175 ) );
176 mPreviewLabel->setStyleSheet( u"color: rgba(255, 6, 10, 255);"_s );
177 setExpressionToolTip( tooltip );
178 emit expressionParsed( false );
179 setParserError( mExpression.hasParserError() );
180 setEvalError( mExpression.hasEvalError() );
181 mCopyPreviewAction->setEnabled( false );
182 }
183 else
184 {
185 mPreviewLabel->setStyleSheet( QString() );
186 const QString longerPreview = QgsExpression::formatPreviewString( value, true, 255 );
187 if ( longerPreview != preview )
188 setExpressionToolTip( longerPreview );
189 else
190 setExpressionToolTip( QString() );
191 emit expressionParsed( true );
192 setParserError( false );
193 setEvalError( false );
194 mCopyPreviewAction->setEnabled( true );
195 }
196 }
197}
198
199void QgsExpressionPreviewWidget::linkActivated( const QString & )
200{
201 QgsMessageViewer mv( this, QgsGuiUtils::ModalDialogFlags, false );
202 mv.setWindowTitle( tr( "More Info on Expression Error" ) );
203 mv.setMessageAsHtml( mToolTip );
204 mv.exec();
205}
206
207void QgsExpressionPreviewWidget::setExpressionToolTip( const QString &toolTip )
208{
209 if ( toolTip == mToolTip )
210 return;
211
212 mToolTip = toolTip;
213 if ( toolTip.isEmpty() )
214 {
215 mPreviewLabel->setToolTip( tr( "Right-click to copy" ) );
216 }
217 else
218 {
219 mPreviewLabel->setToolTip( tr( "%1 (right-click to copy)" ).arg( mToolTip ) );
220 }
221 emit toolTipChanged( mToolTip );
222}
223
224void QgsExpressionPreviewWidget::setParserError( bool parserError )
225{
226 if ( parserError != mParserError )
227 {
228 mParserError = parserError;
229 emit parserErrorChanged();
230 }
231}
233{
234 return mParserError;
235}
236
238{
239 return mPreviewLabel->text();
240}
241
242void QgsExpressionPreviewWidget::setEvalError( bool evalError )
243{
244 if ( evalError == mEvalError )
245 return;
246
247 mEvalError = evalError;
248 emit evalErrorChanged();
249}
250
252{
253 return mEvalError;
254}
255
256void QgsExpressionPreviewWidget::copyFullExpressionValue()
257{
258 QClipboard *clipboard = QApplication::clipboard();
259 const QVariant value = mExpression.evaluate( &mExpressionContext );
260 const QString copiedValue = QgsExpression::formatPreviewString( value, false, 100000 );
261 QgsDebugMsgLevel( u"set clipboard: %1"_s.arg( copiedValue ), 4 );
262 clipboard->setText( copiedValue );
263}
264
265void QgsExpressionPreviewWidget::setCustomChoice( int )
266{
267 const QVariant selectedValue = mCustomComboBox->currentData();
268
269 mCustomButtonPrev->setEnabled( mCustomComboBox->currentIndex() > 0 && mCustomComboBox->count() > 0 );
270 mCustomButtonNext->setEnabled( mCustomComboBox->currentIndex() < ( mCustomComboBox->count() - 1 ) && mCustomComboBox->count() > 0 );
271
272 mExpressionContext = mCustomPreviewGeneratorFunction( selectedValue );
273
274 refreshPreview();
275}
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