QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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( QStringLiteral( "" ) ); // 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 }
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
void displayWarning(const QString &message)
Displays a warning message in the form message bar.
Manages an editor widget Widget and wrapper share the same parent.
Q_DECL_DEPRECATED void valueChanged(const QVariant &value)
Emit this signal, whenever the value changed.
void setFormFeature(const QgsFeature &feature)
Set the feature currently being edited to feature.
int fieldIdx() const
Access the field index.
QVariant defaultValue() const
Access the default value of the field.
void valuesChanged(const QVariant &value, const QVariantList &additionalFieldValues=QVariantList())
Emit this signal, whenever the value changed.
void emitValueChanged()
Will call the value() method to determine the emitted value.
QgsField field() const
Access the field.
virtual void setValue(const QVariant &value)
Is called when the value of the widget needs to be changed.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
QString displayString(const QVariant &v) const
Formats string for display.
Definition: qgsfield.cpp:255
QLineEdit subclass with built in support for clearing the widget's value and handling custom null val...
void setNullValue(const QString &nullValue)
Sets the string representation for null values in the widget.
static QVariant parseJson(const std::string &jsonString)
Converts JSON jsonString to a QVariant, in case of parsing error an invalid QVariant is returned and ...
void initWidget(QWidget *editor) override
This method should initialize the editor widget with runtime data.
QWidget * createWidget(QWidget *parent) override
This method should create a new widget with the provided parent.
void showIndeterminateState() override
Sets the widget to display in an indeterminate "mixed value" state.
void setHint(const QString &hintText) override
Add a hint text on the widget.
QgsTextEditWrapper(QgsVectorLayer *layer, int fieldIdx, QWidget *editor=nullptr, QWidget *parent=nullptr)
Constructor for QgsTextEditWrapper.
void setFeature(const QgsFeature &feature) override
bool isInvalidJSON()
Returns whether the text edit widget contains Invalid JSON.
void setEnabled(bool enabled) override
bool valid() const override
Returns true if the widget has been properly initialized.
QVariant value() const override
Will be used to access the widget's value.
Represents a vector layer which manages a vector based data sets.
QgsVectorLayer * layer() const
Returns the vector layer associated with the widget.
QVariantMap config() const
Returns the whole config.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2065
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2064