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