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