QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
qgstexteditwrapper.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstexteditwrapper.cpp
3  --------------------------------------
4  Date : 5.1.2014
5  Copyright : (C) 2014 Matthias Kuhn
6  Email : matthias at opengis dot 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 "qgstexteditwrapper.h"
17 
18 #include "qgsfields.h"
19 #include "qgsfieldvalidator.h"
20 #include "qgsfilterlineedit.h"
21 #include "qgsapplication.h"
22 #include "qgsjsonutils.h"
23 #include "qgsmessagebar.h"
24 #include "qgslogger.h"
25 
26 #include <QSettings>
27 #include <nlohmann/json.hpp>
28 
29 QgsTextEditWrapper::QgsTextEditWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent )
30  : QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent )
31 
32 {
33 }
34 
35 QVariant QgsTextEditWrapper::value() const
36 {
37  QString v;
38 
39  if ( mTextEdit )
40  {
41  if ( config( QStringLiteral( "UseHtml" ) ).toBool() )
42  {
43  v = mTextEdit->toHtml();
44  }
45  else
46  {
47  v = mTextEdit->toPlainText();
48  }
49  }
50 
51  if ( mPlainTextEdit )
52  {
53  v = mPlainTextEdit->toPlainText();
54  }
55 
56  if ( mLineEdit )
57  {
58  v = mLineEdit->text();
59  }
60 
61  if ( ( v.isEmpty() && ( field().type() == QVariant::Int
62  || field().type() == QVariant::Double
63  || field().type() == QVariant::LongLong
64  || field().type() == QVariant::Date ) )
66  {
67  return QVariant( field().type() );
68  }
69 
70  if ( !defaultValue().isNull() && v == defaultValue().toString() )
71  {
72  return defaultValue();
73  }
74 
75  QVariant res( v );
76  // treat VariantMap fields including JSON differently
77  if ( field().type() != QVariant::Map && field().convertCompatible( res ) )
78  {
79  return res;
80  }
81  else if ( field().type() == QVariant::String && field().length() > 0 )
82  {
83  // for string fields convertCompatible may return false due to field length limit - in this case just truncate
84  // input rather then discarding it entirely
85  return QVariant( v.left( field().length() ) );
86  }
87  else if ( field().type() == QVariant::Map )
88  {
89  // replace empty string (invalid) with quoted empty string
90  if ( v.isEmpty() )
91  {
92  QVariant qjson = QgsJsonUtils::parseJson( std::string( "\"\"" ) );
93  mInvalidJSON = false;
94  return qjson;
95  }
96  if ( json::accept( v.toUtf8() ) )
97  {
98  QVariant qjson = QgsJsonUtils::parseJson( v.toStdString() );
99  mInvalidJSON = false;
100  return qjson;
101  }
102  else
103  // return null value if json is invalid
104  {
105  if ( v.length() > 0 )
106  {
107  mInvalidJSON = true;
108  }
109  else
110  {
111  mInvalidJSON = false;
112  }
113  return QVariant();
114  }
115  }
116  else
117  {
118  return QVariant( field().type() );
119  }
120 }
121 
122 QWidget *QgsTextEditWrapper::createWidget( QWidget *parent )
123 {
124  mForm = qobject_cast<QgsAttributeForm *>( parent );
125  if ( config( QStringLiteral( "IsMultiline" ) ).toBool() )
126  {
127  if ( config( QStringLiteral( "UseHtml" ) ).toBool() )
128  {
129  return new QTextBrowser( parent );
130  }
131  else
132  {
133  return new QPlainTextEdit( parent );
134  }
135  }
136  else
137  {
138  return new QgsFilterLineEdit( parent );
139  }
140 }
141 
142 void QgsTextEditWrapper::initWidget( QWidget *editor )
143 {
144  mInvalidJSON = false;
145  mTextBrowser = qobject_cast<QTextBrowser *>( editor );
146  mTextEdit = qobject_cast<QTextEdit *>( editor );
147  mPlainTextEdit = qobject_cast<QPlainTextEdit *>( editor );
148  mLineEdit = qobject_cast<QLineEdit *>( editor );
149 
150  if ( mTextEdit )
151  connect( mTextEdit, &QTextEdit::textChanged, this, &QgsEditorWidgetWrapper::emitValueChanged );
152 
153  if ( mPlainTextEdit )
154  connect( mPlainTextEdit, &QPlainTextEdit::textChanged, this, &QgsEditorWidgetWrapper::emitValueChanged );
155 
156  if ( mLineEdit )
157  {
158  mLineEdit->setValidator( new QgsFieldValidator( mLineEdit, field(), defaultValue().toString() ) );
159 
160  QVariant defVal = defaultValue();
161  if ( defVal.isNull() )
162  {
164  }
165 
166  QgsFilterLineEdit *fle = qobject_cast<QgsFilterLineEdit *>( mLineEdit );
167  if ( field().type() == QVariant::Int || field().type() == QVariant::Double || field().type() == QVariant::LongLong || field().type() == QVariant::Date )
168  {
169  mPlaceholderText = defVal.toString();
170  mLineEdit->setPlaceholderText( mPlaceholderText );
171  }
172  else if ( fle )
173  {
174  fle->setNullValue( defVal.toString() );
175  }
176 
177  connect( mLineEdit, &QLineEdit::textChanged, this, [ = ]( const QString & value )
178  {
180  emit valueChanged( value );
182  emit valuesChanged( value );
183  } );
184  connect( mLineEdit, &QLineEdit::textChanged, this, &QgsTextEditWrapper::textChanged );
185 
186  mWritablePalette = mLineEdit->palette();
187  mReadOnlyPalette = mLineEdit->palette();
188  }
189 }
190 
192 {
193  return mLineEdit || mTextEdit || mPlainTextEdit;
194 }
195 
197 {
198  //note - this is deliberately a zero length string, not a null string!
199  if ( mTextEdit )
200  mTextEdit->blockSignals( true );
201  if ( mPlainTextEdit )
202  mPlainTextEdit->blockSignals( true );
203  if ( mLineEdit )
204  {
205  mLineEdit->blockSignals( true );
206  // for indeterminate state we need to clear the placeholder text - we want an empty line edit, not
207  // one showing the default value (e.g., "NULL")
208  mLineEdit->setPlaceholderText( QString() );
209  }
210 
211  setWidgetValue( QString() );
212 
213  if ( mTextEdit )
214  mTextEdit->blockSignals( false );
215  if ( mPlainTextEdit )
216  mPlainTextEdit->blockSignals( false );
217  if ( mLineEdit )
218  mLineEdit->blockSignals( false );
219 }
220 
222 {
223  // Do nothing if the value has not changed
224  if ( mInvalidJSON )
225  mForm->displayWarning( tr( "Your JSON was invalid and has been reverted back to the last valid edit or the original data" ) );
226  {
227  mInvalidJSON = false;
228  }
229  setFormFeature( feature );
230  setValue( feature.attribute( fieldIdx() ) );
231 }
232 
233 void QgsTextEditWrapper::updateValues( const QVariant &val, const QVariantList & )
234 {
235  if ( mLineEdit )
236  {
237  //restore placeholder text, which may have been removed by showIndeterminateState()
238  mLineEdit->setPlaceholderText( mPlaceholderText );
239  }
240  setWidgetValue( val );
241 }
242 
243 void QgsTextEditWrapper::setEnabled( bool enabled )
244 {
245  if ( mTextEdit )
246  mTextEdit->setReadOnly( !enabled );
247 
248  if ( mPlainTextEdit )
249  mPlainTextEdit->setReadOnly( !enabled );
250 
251  if ( mLineEdit )
252  {
253  mLineEdit->setReadOnly( !enabled );
254  if ( enabled )
255  mLineEdit->setPalette( mWritablePalette );
256  else
257  {
258  mLineEdit->setPalette( mReadOnlyPalette );
259  // removing frame + setting transparent background to distinguish the readonly lineEdit from a normal one
260  // did not get this working via the Palette:
261  mLineEdit->setStyleSheet( QStringLiteral( "QLineEdit { background-color: rgba(255, 255, 255, 75%); }" ) );
262  }
263  mLineEdit->setFrame( enabled );
264  }
265 }
266 
268 {
269  return mInvalidJSON;
270 }
271 
272 void QgsTextEditWrapper::textChanged( const QString & )
273 {
274  if ( mLineEdit )
275  {
276  //restore placeholder text, which may have been removed by showIndeterminateState()
277  mLineEdit->setPlaceholderText( mPlaceholderText );
278  }
279 }
280 
281 void QgsTextEditWrapper::setWidgetValue( const QVariant &val )
282 {
283  QString v;
284  if ( val.isNull() )
285  {
286  if ( !( field().type() == QVariant::Int || field().type() == QVariant::Double || field().type() == QVariant::LongLong || field().type() == QVariant::Date ) )
288  }
289  else if ( field().type() == QVariant::Map )
290  {
291  // this has to be overridden for json which has only values (i.e. no objects or arrays), as qgsfield.cpp displayString()
292  // uses QJsonDocument which doesn't recognise this as valid JSON although it technically is
293  if ( field().displayString( val ).isEmpty() )
294  {
295  if ( val.type() == QVariant::String && val.toString() != QLatin1String( "\"\"" ) )
296  {
297  v = val.toString().append( "\"" ).insert( 0, "\"" );
298  }
299  else
300  {
301  v = val.toString();
302  }
303  }
304  else
305  {
306  v = field().displayString( val );
307  }
308  }
309  else if ( val.type() == QVariant::Double && std::isnan( val.toDouble() ) )
310  {
312  }
313  else
314  {
315  v = field().displayString( val );
316  }
317  // For numbers, remove the group separator that might cause validation errors
318  // when the user is editing the field value.
319  // We are checking for editable layer because in the form field context we do not
320  // want to strip the separator unless the layer is editable.
321  // Also check that we have something like a number in the value to avoid
322  // stripping out dots from nextval when we have a schema: see https://github.com/qgis/QGIS/issues/28021
323  // "Wrong sequence detection with Postgres"
324  bool canConvertToDouble;
325  QLocale().toDouble( v, &canConvertToDouble );
326  if ( canConvertToDouble && layer() && layer()->isEditable() && ! QLocale().groupSeparator().isNull() && field().isNumeric() )
327  {
328  v = v.remove( QLocale().groupSeparator() );
329  }
330 
331  const QVariant currentValue { value( ) };
332  // Note: comparing QVariants leads to funny (and wrong) results:
333  // QVariant(0.0) == QVariant(QVariant.Double) -> True
334  const bool changed { val != currentValue || val.isNull() != currentValue.isNull() };
335 
336  if ( changed )
337  {
338  if ( mTextEdit )
339  {
340  if ( config( QStringLiteral( "UseHtml" ) ).toBool() )
341  {
342  mTextEdit->setHtml( v );
343  if ( mTextBrowser )
344  {
345  mTextBrowser->setTextInteractionFlags( Qt::LinksAccessibleByMouse );
346  mTextBrowser->setOpenExternalLinks( true );
347  }
348  }
349  else
350  {
351  mTextEdit->setPlainText( v );
352  }
353  }
354  else if ( mPlainTextEdit )
355  {
356  mPlainTextEdit->setPlainText( v );
357  }
358  else if ( mLineEdit )
359  {
360  mLineEdit->setText( v );
361  }
362  }
363 }
364 
365 void QgsTextEditWrapper::setHint( const QString &hintText )
366 {
367  if ( hintText.isNull() )
368  mPlaceholderText = mPlaceholderTextBackup;
369  else
370  {
371  mPlaceholderTextBackup = mPlaceholderText;
372  mPlaceholderText = hintText;
373  }
374 
375  mLineEdit->setPlaceholderText( mPlaceholderText );
376 }
qgsfields.h
QgsTextEditWrapper::createWidget
QWidget * createWidget(QWidget *parent) override
This method should create a new widget with the provided parent.
Definition: qgstexteditwrapper.cpp:122
QgsTextEditWrapper::isInvalidJSON
bool isInvalidJSON()
Returns whether the text edit widget contains Invalid JSON.
Definition: qgstexteditwrapper.cpp:267
QgsTextEditWrapper::setFeature
void setFeature(const QgsFeature &feature) override
Definition: qgstexteditwrapper.cpp:221
QgsField::displayString
QString displayString(const QVariant &v) const
Formats string for display.
Definition: qgsfield.cpp:254
QgsEditorWidgetWrapper::setValue
virtual void setValue(const QVariant &value)
Is called when the value of the widget needs to be changed.
Definition: qgseditorwidgetwrapper.cpp:79
qgsfilterlineedit.h
QgsFilterLineEdit
QLineEdit subclass with built in support for clearing the widget's value and handling custom null val...
Definition: qgsfilterlineedit.h:40
QgsEditorWidgetWrapper
Manages an editor widget Widget and wrapper share the same parent.
Definition: qgseditorwidgetwrapper.h:48
QgsEditorWidgetWrapper::fieldIdx
int fieldIdx() const
Access the field index.
Definition: qgseditorwidgetwrapper.cpp:34
qgsapplication.h
QgsEditorWidgetWrapper::valueChanged
Q_DECL_DEPRECATED void valueChanged(const QVariant &value)
Emit this signal, whenever the value changed.
Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:797
QgsWidgetWrapper::layer
QgsVectorLayer * layer() const
Returns the vector layer associated with the widget.
Definition: qgswidgetwrapper.cpp:91
QgsApplication::nullRepresentation
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
Definition: qgsapplication.cpp:1851
QgsTextEditWrapper::valid
bool valid() const override
Returns true if the widget has been properly initialized.
Definition: qgstexteditwrapper.cpp:191
QgsTextEditWrapper::showIndeterminateState
void showIndeterminateState() override
Sets the widget to display in an indeterminate "mixed value" state.
Definition: qgstexteditwrapper.cpp:196
QgsTextEditWrapper::setHint
void setHint(const QString &hintText) override
Add a hint text on the widget.
Definition: qgstexteditwrapper.cpp:365
QgsTextEditWrapper::setEnabled
void setEnabled(bool enabled) override
Definition: qgstexteditwrapper.cpp:243
QgsEditorWidgetWrapper::field
QgsField field() const
Access the field.
Definition: qgseditorwidgetwrapper.cpp:39
QgsFeature::attribute
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:264
qgsmessagebar.h
QgsEditorWidgetWrapper::emitValueChanged
void emitValueChanged()
Will call the value() method to determine the emitted value.
Definition: qgseditorwidgetwrapper.cpp:91
QgsEditorWidgetWrapper::setFormFeature
void setFormFeature(const QgsFeature &feature)
Set the feature currently being edited to feature.
Definition: qgseditorwidgetwrapper.h:353
QgsEditorWidgetWrapper::valuesChanged
void valuesChanged(const QVariant &value, const QVariantList &additionalFieldValues=QVariantList())
Emit this signal, whenever the value changed.
qgsfieldvalidator.h
QgsTextEditWrapper::QgsTextEditWrapper
QgsTextEditWrapper(QgsVectorLayer *layer, int fieldIdx, QWidget *editor=nullptr, QWidget *parent=nullptr)
Constructor for QgsTextEditWrapper.
Definition: qgstexteditwrapper.cpp:29
QgsWidgetWrapper::config
QVariantMap config() const
Returns the whole config.
Definition: qgswidgetwrapper.cpp:81
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:387
QgsFieldValidator
Definition: qgsfieldvalidator.h:34
QgsAttributeForm::displayWarning
void displayWarning(const QString &message)
Displays a warning message in the form message bar.
Definition: qgsattributeform.cpp:626
QgsTextEditWrapper::value
QVariant value() const override
Will be used to access the widget's value.
Definition: qgstexteditwrapper.cpp:35
QgsEditorWidgetWrapper::defaultValue
QVariant defaultValue() const
Access the default value of the field.
Definition: qgseditorwidgetwrapper.cpp:48
QgsFilterLineEdit::setNullValue
void setNullValue(const QString &nullValue)
Sets the string representation for null values in the widget.
Definition: qgsfilterlineedit.h:115
QgsFeature
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:56
qgsjsonutils.h
qgslogger.h
Q_NOWARN_DEPRECATED_PUSH
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:796
QgsTextEditWrapper::initWidget
void initWidget(QWidget *editor) override
This method should initialize the editor widget with runtime data.
Definition: qgstexteditwrapper.cpp:142
qgstexteditwrapper.h
QgsJsonUtils::parseJson
static QVariant parseJson(const std::string &jsonString)
Converts JSON jsonString to a QVariant, in case of parsing error an invalid QVariant is returned.
Definition: qgsjsonutils.cpp:455