1 /***************************************************************************
2 qgsexpressionpreviewwidget.cpp
3 --------------------------------------
4 Date : march 2020 - quarantine day 12
5 Copyright : (C) 2020 by Denis Rouzaud
6 Email : denis@opengis.ch
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 #include "qgsexpressionpreviewwidget.h"
17 #include "qgsmessageviewer.h"
18 #include "qgsvectorlayer.h"
19 #include "qgsfeaturepickerwidget.h"
20
21
22
23
24
QgsExpressionPreviewWidget(QWidget * parent)25 QgsExpressionPreviewWidget::QgsExpressionPreviewWidget( QWidget *parent )
26 : QWidget( parent )
27 {
28 setupUi( this );
29 mPreviewLabel->clear();
30 mFeaturePickerWidget->setShowBrowserButtons( true );
31
32 connect( mFeaturePickerWidget, &QgsFeaturePickerWidget::featureChanged, this, &QgsExpressionPreviewWidget::setCurrentFeature );
33 connect( mPreviewLabel, &QLabel::linkActivated, this, &QgsExpressionPreviewWidget::linkActivated );
34 }
35
setLayer(QgsVectorLayer * layer)36 void QgsExpressionPreviewWidget::setLayer( QgsVectorLayer *layer )
37 {
38 mLayer = layer;
39 mFeaturePickerWidget->setLayer( layer );
40 }
41
setExpressionText(const QString & expression)42 void QgsExpressionPreviewWidget::setExpressionText( const QString &expression )
43 {
44 mExpressionText = expression;
45 refreshPreview();
46 }
47
setCurrentFeature(const QgsFeature & feature)48 void QgsExpressionPreviewWidget::setCurrentFeature( const QgsFeature &feature )
49 {
50 // todo: update the combo box if it has been set externaly?
51
52 mExpressionContext.setFeature( feature );
53 refreshPreview();
54 }
55
setGeomCalculator(const QgsDistanceArea & da)56 void QgsExpressionPreviewWidget::setGeomCalculator( const QgsDistanceArea &da )
57 {
58 mDa = da;
59 mUseGeomCalculator = true;
60 }
61
setExpressionContext(const QgsExpressionContext & context)62 void QgsExpressionPreviewWidget::setExpressionContext( const QgsExpressionContext &context )
63 {
64 mExpressionContext = context;
65 }
66
refreshPreview()67 void QgsExpressionPreviewWidget::refreshPreview()
68 {
69 // If the string is empty the expression will still "fail" although
70 // we don't show the user an error as it will be confusing.
71 if ( mExpressionText.isEmpty() )
72 {
73 mPreviewLabel->clear();
74 mPreviewLabel->setStyleSheet( QString() );
75 setExpressionToolTip( QString() );
76 emit expressionParsed( false );
77 mExpression = QgsExpression();
78 }
79 else
80 {
81 mExpression = QgsExpression( mExpressionText );
82
83 if ( mUseGeomCalculator )
84 {
85 // only set an explicit geometry calculator if a call to setGeomCalculator was made. If not,
86 // let the expression context handle this correctly
87 mExpression.setGeomCalculator( &mDa );
88 }
89
90 const QVariant value = mExpression.evaluate( &mExpressionContext );
91 const QString preview = QgsExpression::formatPreviewString( value );
92 if ( !mExpression.hasEvalError() )
93 {
94 mPreviewLabel->setText( preview );
95 }
96
97 if ( mExpression.hasParserError() || mExpression.hasEvalError() )
98 {
99 // if parser error was a result of missing feature, then skip the misleading parser error message
100 // and instead show a friendly message, and allow the user to accept the expression anyway
101 // refs https://github.com/qgis/QGIS/issues/42884
102 if ( !mExpressionContext.feature().isValid() )
103 {
104 if ( !mExpression.referencedColumns().isEmpty() || mExpression.needsGeometry() )
105 {
106 mPreviewLabel->setText( tr( "No feature was found on this layer to evaluate the expression." ) );
107 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(220, 125, 0, 255);" ) );
108 emit expressionParsed( true );
109 setParserError( false );
110 setEvalError( false );
111 return;
112 }
113 }
114
115 const QString errorString = mExpression.parserErrorString().replace( QLatin1String( "\n" ), QLatin1String( "<br>" ) );
116 QString tooltip;
117 if ( mExpression.hasParserError() )
118 tooltip = QStringLiteral( "<b>%1:</b>"
119 "%2" ).arg( tr( "Parser Errors" ), errorString );
120 // Only show the eval error if there is no parser error.
121 if ( !mExpression.hasParserError() && mExpression.hasEvalError() )
122 tooltip += QStringLiteral( "<b>%1:</b> %2" ).arg( tr( "Eval Error" ), mExpression.evalErrorString() );
123
124 mPreviewLabel->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
125 mPreviewLabel->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
126 setExpressionToolTip( tooltip );
127 emit expressionParsed( false );
128 setParserError( mExpression.hasParserError() );
129 setEvalError( mExpression.hasEvalError() );
130 }
131 else
132 {
133 mPreviewLabel->setStyleSheet( QString() );
134 const QString longerPreview = QgsExpression::formatPreviewString( value, true, 255 );
135 if ( longerPreview != preview )
136 setExpressionToolTip( longerPreview );
137 else
138 setExpressionToolTip( QString() );
139 emit expressionParsed( true );
140 setParserError( false );
141 setEvalError( false );
142 }
143 }
144 }
145
linkActivated(const QString &)146 void QgsExpressionPreviewWidget::linkActivated( const QString & )
147 {
148 QgsMessageViewer mv( this, QgsGuiUtils::ModalDialogFlags, false );
149 mv.setWindowTitle( tr( "More Info on Expression Error" ) );
150 mv.setMessageAsHtml( mToolTip );
151 mv.exec();
152 }
153
setExpressionToolTip(const QString & toolTip)154 void QgsExpressionPreviewWidget::setExpressionToolTip( const QString &toolTip )
155 {
156 if ( toolTip == mToolTip )
157 return;
158
159 mToolTip = toolTip;
160 mPreviewLabel->setToolTip( mToolTip );
161 emit toolTipChanged( mToolTip );
162 }
163
setParserError(bool parserError)164 void QgsExpressionPreviewWidget::setParserError( bool parserError )
165 {
166 if ( parserError != mParserError )
167 {
168 mParserError = parserError;
169 emit parserErrorChanged();
170 }
171 }
parserError() const172 bool QgsExpressionPreviewWidget::parserError() const
173 {
174 return mParserError;
175 }
176
setEvalError(bool evalError)177 void QgsExpressionPreviewWidget::setEvalError( bool evalError )
178 {
179 if ( evalError == mEvalError )
180 return;
181
182 mEvalError = evalError;
183 emit evalErrorChanged();
184 }
185
evalError() const186 bool QgsExpressionPreviewWidget::evalError() const
187 {
188 return mEvalError;
189 }
190