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