QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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 <nlohmann/json.hpp>
19
20#include "qgsapplication.h"
21#include "qgsfields.h"
22#include "qgsfieldvalidator.h"
23#include "qgsfilterlineedit.h"
24#include "qgsjsonutils.h"
25#include "qgslogger.h"
26#include "qgsmessagebar.h"
27
28#include <QSettings>
29
30#include "moc_qgstexteditwrapper.cpp"
31
32QgsTextEditWrapper::QgsTextEditWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent )
33 : QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent )
34
35{
36}
37
39{
40 QString v;
41
42 if ( mTextEdit )
43 {
44 if ( config( QStringLiteral( "UseHtml" ) ).toBool() )
45 {
46 if ( mTextEdit->toPlainText().isEmpty() )
47 {
48 v = QString();
49 }
50 else
51 {
52 v = mTextEdit->toHtml();
53 }
54 }
55 else
56 {
57 v = mTextEdit->toPlainText();
58 }
59 }
60
61 if ( mPlainTextEdit )
62 {
63 v = mPlainTextEdit->toPlainText();
64 }
65
66 if ( mLineEdit )
67 {
68 v = mLineEdit->text();
69 }
70
71 if ( ( v.isEmpty() && ( field().type() == QMetaType::Type::Int || field().type() == QMetaType::Type::Double || field().type() == QMetaType::Type::LongLong || field().type() == QMetaType::Type::QDate ) )
73 {
75 }
76
77 if ( !QgsVariantUtils::isNull( defaultValue() ) && v == defaultValue().toString() )
78 {
79 return QVariant::fromValue( QgsUnsetAttributeValue( defaultValue().toString() ) );
80 }
81
82 QVariant res( v );
83 // treat VariantMap fields including JSON differently
84 if ( field().type() != QMetaType::Type::QVariantMap && field().convertCompatible( res ) )
85 {
86 return res;
87 }
88 else if ( field().type() == QMetaType::Type::QString && 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() == QMetaType::Type::QVariantMap )
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.toStdString() ) )
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 QgsVariantUtils::createNullVariant( field().type() );
126 }
127}
128
129QWidget *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
149void 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 ( QgsVariantUtils::isNull( defVal ) )
169 {
171 }
172
173 QgsFilterLineEdit *fle = qobject_cast<QgsFilterLineEdit *>( mLineEdit );
174 if ( field().type() == QMetaType::Type::Int || field().type() == QMetaType::Type::Double || field().type() == QMetaType::Type::LongLong || field().type() == QMetaType::Type::QDate )
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, [this]( const QString &value ) {
186 emit valueChanged( value );
188 emit valuesChanged( value );
189 } );
190 connect( mLineEdit, &QLineEdit::textChanged, this, &QgsTextEditWrapper::textChanged );
191 }
192}
193
195{
196 return mLineEdit || mTextEdit || mPlainTextEdit;
197}
198
200{
201 if ( mTextEdit )
202 mTextEdit->blockSignals( true );
203 if ( mPlainTextEdit )
204 mPlainTextEdit->blockSignals( true );
205 if ( mLineEdit )
206 {
207 mLineEdit->blockSignals( true );
208 // for indeterminate state we need to clear the placeholder text - we want an empty line edit, not
209 // one showing the default value (e.g., "NULL")
210 mLineEdit->setPlaceholderText( QString() );
211 }
212
213 //note - this is deliberately a zero length string, not a null string!
214 setWidgetValue( QLatin1String( "" ) ); // skip-keyword-check
215
216 if ( mTextEdit )
217 mTextEdit->blockSignals( false );
218 if ( mPlainTextEdit )
219 mPlainTextEdit->blockSignals( false );
220 if ( mLineEdit )
221 mLineEdit->blockSignals( false );
222}
223
225{
226 // Do nothing if the value has not changed
227 if ( mInvalidJSON )
228 mForm->displayWarning( tr( "Your JSON was invalid and has been reverted back to the last valid edit or the original data" ) );
229 {
230 mInvalidJSON = false;
231 }
232 setFormFeature( feature );
233 setValue( feature.attribute( fieldIdx() ) );
234}
235
236void QgsTextEditWrapper::updateValues( const QVariant &val, const QVariantList & )
237{
238 if ( mLineEdit )
239 {
240 //restore placeholder text, which may have been removed by showIndeterminateState()
241 mLineEdit->setPlaceholderText( mPlaceholderText );
242 }
243 setWidgetValue( val );
244}
245
247{
248 if ( mTextEdit )
249 mTextEdit->setReadOnly( !enabled );
250
251 if ( mPlainTextEdit )
252 mPlainTextEdit->setReadOnly( !enabled );
253
254 if ( mLineEdit )
255 {
256 mLineEdit->setReadOnly( !enabled );
257 mLineEdit->setFrame( enabled );
258 }
259}
260
262{
263 return mInvalidJSON;
264}
265
266void QgsTextEditWrapper::textChanged( const QString & )
267{
268 if ( mLineEdit )
269 {
270 //restore placeholder text, which may have been removed by showIndeterminateState()
271 mLineEdit->setPlaceholderText( mPlaceholderText );
272 }
273}
274
275void QgsTextEditWrapper::setWidgetValue( const QVariant &val )
276{
277 QString v;
278 if ( QgsVariantUtils::isNull( val ) )
279 {
280 if ( !( field().type() == QMetaType::Type::Int || field().type() == QMetaType::Type::Double || field().type() == QMetaType::Type::LongLong || field().type() == QMetaType::Type::QDate ) )
282 }
283 else if ( field().type() == QMetaType::Type::QVariantMap )
284 {
285 // this has to be overridden for json which has only values (i.e. no objects or arrays), as qgsfield.cpp displayString()
286 // uses QJsonDocument which doesn't recognise this as valid JSON although it technically is
287 if ( field().displayString( val ).isEmpty() )
288 {
289 if ( val.userType() == QMetaType::Type::QString && val.toString() != QLatin1String( "\"\"" ) )
290 {
291 v = val.toString().append( "\"" ).insert( 0, "\"" );
292 }
293 else
294 {
295 v = val.toString();
296 }
297 }
298 else
299 {
300 v = field().displayString( val );
301 }
302 }
303 else if ( val.userType() == QMetaType::Type::Double && std::isnan( val.toDouble() ) )
304 {
306 }
307 else if ( val.userType() == qMetaTypeId<QgsUnsetAttributeValue>() )
308 {
309 v = defaultValue().toString();
310 }
311 else
312 {
313 v = field().displayString( val );
314 }
315 // For numbers, remove the group separator that might cause validation errors
316 // when the user is editing the field value.
317 // We are checking for editable layer because in the form field context we do not
318 // want to strip the separator unless the layer is editable.
319 // Also check that we have something like a number in the value to avoid
320 // stripping out dots from nextval when we have a schema: see https://github.com/qgis/QGIS/issues/28021
321 // "Wrong sequence detection with Postgres"
322 bool canConvertToDouble;
323 QLocale().toDouble( v, &canConvertToDouble );
324 if ( canConvertToDouble && layer() && layer()->isEditable() && !QLocale().groupSeparator().isNull() && field().isNumeric() )
325 {
326 v = v.remove( QLocale().groupSeparator() );
327 }
328
329 const QVariant currentValue = value();
330 // Note: comparing QVariants leads to funny (and wrong) results:
331 // QVariant(0.0) == QVariant(QVariant.Double) -> True
332 const bool changed { val != currentValue || QgsVariantUtils::isNull( val ) != QgsVariantUtils::isNull( currentValue ) };
333
334 if ( changed )
335 {
336 if ( mTextEdit )
337 {
338 if ( config( QStringLiteral( "UseHtml" ) ).toBool() )
339 {
340 mTextEdit->setHtml( v );
341 if ( mTextBrowser )
342 {
343 mTextBrowser->setTextInteractionFlags( Qt::LinksAccessibleByMouse );
344 mTextBrowser->setOpenExternalLinks( true );
345 }
346 }
347 else
348 {
349 mTextEdit->setPlainText( v );
350 }
351 }
352 else if ( mPlainTextEdit )
353 {
354 mPlainTextEdit->setPlainText( v );
355 }
356 else if ( mLineEdit )
357 {
358 mLineEdit->setText( v );
359 }
360 }
361}
362
363void QgsTextEditWrapper::setHint( const QString &hintText )
364{
365 if ( hintText.isNull() )
366 mPlaceholderText = mPlaceholderTextBackup;
367 else
368 {
369 mPlaceholderTextBackup = mPlaceholderText;
370 mPlaceholderText = hintText;
371 }
372
373 if ( mLineEdit )
374 mLineEdit->setPlaceholderText( mPlaceholderText );
375}
static QString nullRepresentation()
Returns the string used to represent the value NULL throughout QGIS.
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.
QgsEditorWidgetWrapper(QgsVectorLayer *vl, int fieldIdx, QWidget *editor=nullptr, QWidget *parent=nullptr)
Create a new widget wrapper.
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:58
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
A QValidator for validation against a QgsField's constraints and field type.
QString displayString(const QVariant &v) const
Formats string for display.
Definition qgsfield.cpp:319
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
void setEnabled(bool enabled) override
bool isInvalidJSON() const
Returns whether the text edit widget contains Invalid JSON.
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 default, "not-specified" value for a feature attribute.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static QVariant createNullVariant(QMetaType::Type metaType)
Helper method to properly create a null QVariant from a metaType Returns the created QVariant.
Represents a vector layer which manages a vector based dataset.
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:7170
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7169