QGIS API Documentation  3.25.0-Master (10b47c2603)
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
6  Email : [email protected]
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 "qgsmessageviewer.h"
22 #include "qgsvectorlayer.h"
23 #include "qgsfeaturepickerwidget.h"
24 
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 
38  connect( mPreviewLabel, &QLabel::linkActivated, this, &QgsExpressionPreviewWidget::linkActivated );
39  connect( mCopyPreviewAction, &QAction::triggered, this, &QgsExpressionPreviewWidget::copyFullExpressionValue );
40 }
41 
43 {
44  mLayer = layer;
45  mFeaturePickerWidget->setLayer( layer );
46 }
47 
48 void QgsExpressionPreviewWidget::setExpressionText( const QString &expression )
49 {
50  mExpressionText = expression;
51  refreshPreview();
52 }
53 
55 {
56  // todo: update the combo box if it has been set externaly?
57 
58  mExpressionContext.setFeature( feature );
59  refreshPreview();
60 }
61 
63 {
64  mDa = da;
65  mUseGeomCalculator = true;
66 }
67 
69 {
70  mExpressionContext = context;
71 }
72 
73 void QgsExpressionPreviewWidget::refreshPreview()
74 {
75  // If the string is empty the expression will still "fail" although
76  // we don't show the user an error as it will be confusing.
77  if ( mExpressionText.isEmpty() )
78  {
79  mPreviewLabel->clear();
80  mPreviewLabel->setStyleSheet( QString() );
81  mCopyPreviewAction->setEnabled( false );
82  setExpressionToolTip( QString() );
83  emit expressionParsed( false );
84  mExpression = QgsExpression();
85  }
86  else
87  {
88  mExpression = QgsExpression( mExpressionText );
89 
90  if ( mUseGeomCalculator )
91  {
92  // only set an explicit geometry calculator if a call to setGeomCalculator was made. If not,
93  // let the expression context handle this correctly
94  mExpression.setGeomCalculator( &mDa );
95  }
96  const QVariant value = mExpression.evaluate( &mExpressionContext );
97  const QString preview = QgsExpression::formatPreviewString( value );
98  if ( !mExpression.hasEvalError() )
99  {
100  mPreviewLabel->setText( preview );
101  mCopyPreviewAction->setEnabled( true );
102  }
103 
104  if ( mExpression.hasParserError() || mExpression.hasEvalError() )
105  {
106  // if parser error was a result of missing feature, then skip the misleading parser error message
107  // and instead show a friendly message, and allow the user to accept the expression anyway
108  // refs https://github.com/qgis/QGIS/issues/42884
109  if ( !mExpressionContext.feature().isValid() )
110  {
111  if ( !mExpression.referencedColumns().isEmpty() || mExpression.needsGeometry() )
112  {
113  mPreviewLabel->setText( tr( "No feature was found on this layer to evaluate the expression." ) );
114  mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(220, 125, 0, 255);" ) );
115  emit expressionParsed( true );
116  setParserError( false );
117  setEvalError( false );
118  return;
119  }
120  }
121 
122  const QString errorString = mExpression.parserErrorString().replace( QLatin1String( "\n" ), QLatin1String( "<br>" ) );
123  QString tooltip;
124  if ( mExpression.hasParserError() )
125  tooltip = QStringLiteral( "<b>%1:</b>"
126  "%2" ).arg( tr( "Parser Errors" ), errorString );
127  // Only show the eval error if there is no parser error.
128  if ( !mExpression.hasParserError() && mExpression.hasEvalError() )
129  tooltip += QStringLiteral( "<b>%1:</b> %2" ).arg( tr( "Eval Error" ), mExpression.evalErrorString() );
130 
131  mPreviewLabel->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
132  mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
133  setExpressionToolTip( tooltip );
134  emit expressionParsed( false );
135  setParserError( mExpression.hasParserError() );
136  setEvalError( mExpression.hasEvalError() );
137  mCopyPreviewAction->setEnabled( false );
138  }
139  else
140  {
141  mPreviewLabel->setStyleSheet( QString() );
142  const QString longerPreview = QgsExpression::formatPreviewString( value, true, 255 );
143  if ( longerPreview != preview )
144  setExpressionToolTip( longerPreview );
145  else
146  setExpressionToolTip( QString() );
147  emit expressionParsed( true );
148  setParserError( false );
149  setEvalError( false );
150  mCopyPreviewAction->setEnabled( true );
151  }
152  }
153 }
154 
155 void QgsExpressionPreviewWidget::linkActivated( const QString & )
156 {
157  QgsMessageViewer mv( this, QgsGuiUtils::ModalDialogFlags, false );
158  mv.setWindowTitle( tr( "More Info on Expression Error" ) );
159  mv.setMessageAsHtml( mToolTip );
160  mv.exec();
161 }
162 
163 void QgsExpressionPreviewWidget::setExpressionToolTip( const QString &toolTip )
164 {
165  if ( toolTip == mToolTip )
166  return;
167 
168  mToolTip = toolTip;
169  if ( toolTip.isEmpty() )
170  {
171  mPreviewLabel->setToolTip( tr( "Right-click to copy" ) );
172  }
173  else
174  {
175  mPreviewLabel->setToolTip( tr( "%1 (right-click to copy)" ).arg( mToolTip ) );
176  }
177  emit toolTipChanged( mToolTip );
178 }
179 
180 void QgsExpressionPreviewWidget::setParserError( bool parserError )
181 {
182  if ( parserError != mParserError )
183  {
184  mParserError = parserError;
185  emit parserErrorChanged();
186  }
187 }
189 {
190  return mParserError;
191 }
192 
193 void QgsExpressionPreviewWidget::setEvalError( bool evalError )
194 {
195  if ( evalError == mEvalError )
196  return;
197 
198  mEvalError = evalError;
199  emit evalErrorChanged();
200 }
201 
203 {
204  return mEvalError;
205 }
206 
207 void QgsExpressionPreviewWidget::copyFullExpressionValue()
208 {
209  QClipboard *clipboard = QApplication::clipboard();
210  const QVariant value = mExpression.evaluate( &mExpressionContext );
211  const QString copiedValue = QgsExpression::formatPreviewString( value, false, 100000 );
212  QgsDebugMsgLevel( QStringLiteral( "set clipboard: %1" ).arg( copiedValue ), 4 );
213  clipboard->setText( copiedValue );
214 }
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.
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:56
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:209
A generic message view for displaying QGIS messages.
Represents a vector layer which manages a vector based data sets.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39