QGIS API Documentation 3.99.0-Master (d270888f95f)
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#include <QString>
30
31#include "moc_qgstexteditwrapper.cpp"
32
33using namespace Qt::StringLiterals;
34
35QgsTextEditWrapper::QgsTextEditWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent )
36 : QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent )
37
38{
39}
40
42{
43 QString v;
44
45 if ( mTextEdit )
46 {
47 if ( config( u"UseHtml"_s ).toBool() )
48 {
49 if ( mTextEdit->toPlainText().isEmpty() )
50 {
51 v = QString();
52 }
53 else
54 {
55 v = mTextEdit->toHtml();
56 }
57 }
58 else
59 {
60 v = mTextEdit->toPlainText();
61 }
62 }
63
64 if ( mPlainTextEdit )
65 {
66 v = mPlainTextEdit->toPlainText();
67 }
68
69 if ( mLineEdit )
70 {
71 v = mLineEdit->text();
72 }
73
74 if ( ( v.isEmpty() && ( field().type() == QMetaType::Type::Int || field().type() == QMetaType::Type::Double || field().type() == QMetaType::Type::LongLong || field().type() == QMetaType::Type::QDate ) )
76 {
78 }
79
80 if ( !QgsVariantUtils::isNull( defaultValue() ) && v == defaultValue().toString() )
81 {
82 return QVariant::fromValue( QgsUnsetAttributeValue( defaultValue().toString() ) );
83 }
84
85 QVariant res( v );
86 // treat VariantMap fields including JSON differently
87 if ( field().type() != QMetaType::Type::QVariantMap && field().convertCompatible( res ) )
88 {
89 return res;
90 }
91 else if ( field().type() == QMetaType::Type::QString && field().length() > 0 )
92 {
93 // for string fields convertCompatible may return false due to field length limit - in this case just truncate
94 // input rather then discarding it entirely
95 return QVariant( v.left( field().length() ) );
96 }
97 else if ( field().type() == QMetaType::Type::QVariantMap )
98 {
99 // replace empty string (invalid) with quoted empty string
100 if ( v.isEmpty() )
101 {
102 QVariant qjson = QgsJsonUtils::parseJson( std::string( "\"\"" ) );
103 mInvalidJSON = false;
104 return qjson;
105 }
106 if ( json::accept( v.toStdString() ) )
107 {
108 QVariant qjson = QgsJsonUtils::parseJson( v.toStdString() );
109 mInvalidJSON = false;
110 return qjson;
111 }
112 else
113 // return null value if json is invalid
114 {
115 if ( v.length() > 0 )
116 {
117 mInvalidJSON = true;
118 }
119 else
120 {
121 mInvalidJSON = false;
122 }
123 return QVariant();
124 }
125 }
126 else
127 {
128 return QgsVariantUtils::createNullVariant( field().type() );
129 }
130}
131
132QWidget *QgsTextEditWrapper::createWidget( QWidget *parent )
133{
134 mForm = qobject_cast<QgsAttributeForm *>( parent );
135 if ( config( u"IsMultiline"_s ).toBool() )
136 {
137 if ( config( u"UseHtml"_s ).toBool() )
138 {
139 return new QTextBrowser( parent );
140 }
141 else
142 {
143 return new QPlainTextEdit( parent );
144 }
145 }
146 else
147 {
148 return new QgsFilterLineEdit( parent );
149 }
150}
151
152void QgsTextEditWrapper::initWidget( QWidget *editor )
153{
154 mInvalidJSON = false;
155 mTextBrowser = qobject_cast<QTextBrowser *>( editor );
156 mTextEdit = qobject_cast<QTextEdit *>( editor );
157 mPlainTextEdit = qobject_cast<QPlainTextEdit *>( editor );
158 mLineEdit = qobject_cast<QLineEdit *>( editor );
159
160 if ( mTextEdit )
161 connect( mTextEdit, &QTextEdit::textChanged, this, &QgsEditorWidgetWrapper::emitValueChanged );
162
163 if ( mPlainTextEdit )
164 connect( mPlainTextEdit, &QPlainTextEdit::textChanged, this, &QgsEditorWidgetWrapper::emitValueChanged );
165
166 if ( mLineEdit )
167 {
168 mLineEdit->setValidator( new QgsFieldValidator( mLineEdit, field(), defaultValue().toString() ) );
169
170 QVariant defVal = defaultValue();
171 if ( QgsVariantUtils::isNull( defVal ) )
172 {
174 }
175
176 QgsFilterLineEdit *fle = qobject_cast<QgsFilterLineEdit *>( mLineEdit );
177 if ( field().type() == QMetaType::Type::Int || field().type() == QMetaType::Type::Double || field().type() == QMetaType::Type::LongLong || field().type() == QMetaType::Type::QDate )
178 {
179 mPlaceholderText = defVal.toString();
180 mLineEdit->setPlaceholderText( mPlaceholderText );
181 }
182 else if ( fle )
183 {
184 fle->setNullValue( defVal.toString() );
185 }
186
187 connect( mLineEdit, &QLineEdit::textChanged, this, [this]( const QString &value ) {
189 emit valueChanged( value );
191 emit valuesChanged( value );
192 } );
193 connect( mLineEdit, &QLineEdit::textChanged, this, &QgsTextEditWrapper::textChanged );
194 }
195}
196
198{
199 return mLineEdit || mTextEdit || mPlainTextEdit;
200}
201
203{
204 if ( mTextEdit )
205 mTextEdit->blockSignals( true );
206 if ( mPlainTextEdit )
207 mPlainTextEdit->blockSignals( true );
208 if ( mLineEdit )
209 {
210 mLineEdit->blockSignals( true );
211 // for indeterminate state we need to clear the placeholder text - we want an empty line edit, not
212 // one showing the default value (e.g., "NULL")
213 mLineEdit->setPlaceholderText( QString() );
214 }
215
216 //note - this is deliberately a zero length string, not a null string!
217 setWidgetValue( QLatin1String( "" ) ); // skip-keyword-check
218
219 if ( mTextEdit )
220 mTextEdit->blockSignals( false );
221 if ( mPlainTextEdit )
222 mPlainTextEdit->blockSignals( false );
223 if ( mLineEdit )
224 mLineEdit->blockSignals( false );
225}
226
228{
229 // Do nothing if the value has not changed
230 if ( mInvalidJSON )
231 mForm->displayWarning( tr( "Your JSON was invalid and has been reverted back to the last valid edit or the original data" ) );
232 {
233 mInvalidJSON = false;
234 }
235 setFormFeature( feature );
236 setValue( feature.attribute( fieldIdx() ) );
237}
238
239void QgsTextEditWrapper::updateValues( const QVariant &val, const QVariantList & )
240{
241 if ( mLineEdit )
242 {
243 //restore placeholder text, which may have been removed by showIndeterminateState()
244 mLineEdit->setPlaceholderText( mPlaceholderText );
245 }
246 setWidgetValue( val );
247}
248
250{
251 if ( mTextEdit )
252 mTextEdit->setReadOnly( !enabled );
253
254 if ( mPlainTextEdit )
255 mPlainTextEdit->setReadOnly( !enabled );
256
257 if ( mLineEdit )
258 {
259 mLineEdit->setReadOnly( !enabled );
260 mLineEdit->setFrame( enabled );
261 }
262}
263
265{
266 return mInvalidJSON;
267}
268
269void QgsTextEditWrapper::textChanged( const QString & )
270{
271 if ( mLineEdit )
272 {
273 //restore placeholder text, which may have been removed by showIndeterminateState()
274 mLineEdit->setPlaceholderText( mPlaceholderText );
275 }
276}
277
278void QgsTextEditWrapper::setWidgetValue( const QVariant &val )
279{
280 QString v;
281 if ( QgsVariantUtils::isNull( val ) )
282 {
283 if ( !( field().type() == QMetaType::Type::Int || field().type() == QMetaType::Type::Double || field().type() == QMetaType::Type::LongLong || field().type() == QMetaType::Type::QDate ) )
285 }
286 else if ( field().type() == QMetaType::Type::QVariantMap )
287 {
288 // this has to be overridden for json which has only values (i.e. no objects or arrays), as qgsfield.cpp displayString()
289 // uses QJsonDocument which doesn't recognise this as valid JSON although it technically is
290 if ( field().displayString( val ).isEmpty() )
291 {
292 if ( val.userType() == QMetaType::Type::QString && val.toString() != "\"\""_L1 )
293 {
294 v = val.toString().append( "\"" ).insert( 0, "\"" );
295 }
296 else
297 {
298 v = val.toString();
299 }
300 }
301 else
302 {
303 v = field().displayString( val );
304 }
305 }
306 else if ( val.userType() == QMetaType::Type::Double && std::isnan( val.toDouble() ) )
307 {
309 }
310 else if ( val.userType() == qMetaTypeId<QgsUnsetAttributeValue>() )
311 {
312 v = defaultValue().toString();
313 }
314 else
315 {
316 v = field().displayString( val );
317 }
318 // For numbers, remove the group separator that might cause validation errors
319 // when the user is editing the field value.
320 // We are checking for editable layer because in the form field context we do not
321 // want to strip the separator unless the layer is editable.
322 // Also check that we have something like a number in the value to avoid
323 // stripping out dots from nextval when we have a schema: see https://github.com/qgis/QGIS/issues/28021
324 // "Wrong sequence detection with Postgres"
325 bool canConvertToDouble;
326 QLocale().toDouble( v, &canConvertToDouble );
327 if ( canConvertToDouble && layer() && layer()->isEditable() && !QLocale().groupSeparator().isNull() && field().isNumeric() )
328 {
329 v = v.remove( QLocale().groupSeparator() );
330 }
331
332 const QVariant currentValue = value();
333 // Note: comparing QVariants leads to funny (and wrong) results:
334 // QVariant(0.0) == QVariant(QVariant.Double) -> True
335 const bool changed { val != currentValue || QgsVariantUtils::isNull( val ) != QgsVariantUtils::isNull( currentValue ) };
336
337 if ( changed )
338 {
339 if ( mTextEdit )
340 {
341 if ( config( u"UseHtml"_s ).toBool() )
342 {
343 mTextEdit->setHtml( v );
344 if ( mTextBrowser )
345 {
346 mTextBrowser->setTextInteractionFlags( Qt::LinksAccessibleByMouse );
347 mTextBrowser->setOpenExternalLinks( true );
348 }
349 }
350 else
351 {
352 mTextEdit->setPlainText( v );
353 }
354 }
355 else if ( mPlainTextEdit )
356 {
357 mPlainTextEdit->setPlainText( v );
358 }
359 else if ( mLineEdit )
360 {
361 mLineEdit->setText( v );
362 }
363 }
364}
365
366void QgsTextEditWrapper::setHint( const QString &hintText )
367{
368 if ( hintText.isNull() )
369 mPlaceholderText = mPlaceholderTextBackup;
370 else
371 {
372 mPlaceholderTextBackup = mPlaceholderText;
373 mPlaceholderText = hintText;
374 }
375
376 if ( mLineEdit )
377 mLineEdit->setPlaceholderText( mPlaceholderText );
378}
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:60
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:323
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:7451
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7450