QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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
41{
42 QString v;
43
44 if ( mTextEdit )
45 {
46 if ( config( u"UseHtml"_s ).toBool() )
47 {
48 if ( mTextEdit->toPlainText().isEmpty() )
49 {
50 v = QString();
51 }
52 else
53 {
54 v = mTextEdit->toHtml();
55 }
56 }
57 else
58 {
59 v = mTextEdit->toPlainText();
60 }
61 }
62
63 if ( mPlainTextEdit )
64 {
65 v = mPlainTextEdit->toPlainText();
66 }
67
68 if ( mLineEdit )
69 {
70 v = mLineEdit->text();
71 }
72
73 if ( ( v.isEmpty() && ( field().type() == QMetaType::Type::Int || field().type() == QMetaType::Type::Double || field().type() == QMetaType::Type::LongLong || field().type() == QMetaType::Type::QDate ) )
75 {
77 }
78
79 if ( !QgsVariantUtils::isNull( defaultValue() ) && v == defaultValue().toString() )
80 {
81 return QVariant::fromValue( QgsUnsetAttributeValue( defaultValue().toString() ) );
82 }
83
84 QVariant res( v );
85 // treat VariantMap fields including JSON differently
86 if ( field().type() != QMetaType::Type::QVariantMap && field().convertCompatible( res ) )
87 {
88 return res;
89 }
90 else if ( field().type() == QMetaType::Type::QString && field().length() > 0 )
91 {
92 // for string fields convertCompatible may return false due to field length limit - in this case just truncate
93 // input rather then discarding it entirely
94 return QVariant( v.left( field().length() ) );
95 }
96 else if ( field().type() == QMetaType::Type::QVariantMap )
97 {
98 // replace empty string (invalid) with quoted empty string
99 if ( v.isEmpty() )
100 {
101 QVariant qjson = QgsJsonUtils::parseJson( std::string( "\"\"" ) );
102 mInvalidJSON = false;
103 return qjson;
104 }
105 if ( json::accept( v.toStdString() ) )
106 {
107 QVariant qjson = QgsJsonUtils::parseJson( v.toStdString() );
108 mInvalidJSON = false;
109 return qjson;
110 }
111 else
112 // return null value if json is invalid
113 {
114 if ( v.length() > 0 )
115 {
116 mInvalidJSON = true;
117 }
118 else
119 {
120 mInvalidJSON = false;
121 }
122 return QVariant();
123 }
124 }
125 else
126 {
127 return QgsVariantUtils::createNullVariant( field().type() );
128 }
129}
130
131QWidget *QgsTextEditWrapper::createWidget( QWidget *parent )
132{
133 mForm = qobject_cast<QgsAttributeForm *>( parent );
134 if ( config( u"IsMultiline"_s ).toBool() )
135 {
136 if ( config( u"UseHtml"_s ).toBool() )
137 {
138 return new QTextBrowser( parent );
139 }
140 else
141 {
142 return new QPlainTextEdit( parent );
143 }
144 }
145 else
146 {
147 return new QgsFilterLineEdit( parent );
148 }
149}
150
151void QgsTextEditWrapper::initWidget( QWidget *editor )
152{
153 mInvalidJSON = false;
154 mTextBrowser = qobject_cast<QTextBrowser *>( editor );
155 mTextEdit = qobject_cast<QTextEdit *>( editor );
156 mPlainTextEdit = qobject_cast<QPlainTextEdit *>( editor );
157 mLineEdit = qobject_cast<QLineEdit *>( editor );
158
159 if ( mTextEdit )
160 connect( mTextEdit, &QTextEdit::textChanged, this, &QgsEditorWidgetWrapper::emitValueChanged );
161
162 if ( mPlainTextEdit )
163 connect( mPlainTextEdit, &QPlainTextEdit::textChanged, this, &QgsEditorWidgetWrapper::emitValueChanged );
164
165 if ( mLineEdit )
166 {
167 mLineEdit->setValidator( new QgsFieldValidator( mLineEdit, field(), defaultValue().toString() ) );
168
169 QVariant defVal = defaultValue();
170 if ( QgsVariantUtils::isNull( defVal ) )
171 {
173 }
174
175 QgsFilterLineEdit *fle = qobject_cast<QgsFilterLineEdit *>( mLineEdit );
176 if ( field().type() == QMetaType::Type::Int || field().type() == QMetaType::Type::Double || field().type() == QMetaType::Type::LongLong || field().type() == QMetaType::Type::QDate )
177 {
178 mPlaceholderText = defVal.toString();
179 mLineEdit->setPlaceholderText( mPlaceholderText );
180 }
181 else if ( fle )
182 {
183 fle->setNullValue( defVal.toString() );
184 }
185
186 connect( mLineEdit, &QLineEdit::textChanged, this, [this]( const QString &value ) {
188 emit valueChanged( value );
190 emit valuesChanged( value );
191 } );
192 connect( mLineEdit, &QLineEdit::textChanged, this, &QgsTextEditWrapper::textChanged );
193 }
194}
195
197{
198 return mLineEdit || mTextEdit || mPlainTextEdit;
199}
200
202{
203 if ( mTextEdit )
204 mTextEdit->blockSignals( true );
205 if ( mPlainTextEdit )
206 mPlainTextEdit->blockSignals( true );
207 if ( mLineEdit )
208 {
209 mLineEdit->blockSignals( true );
210 // for indeterminate state we need to clear the placeholder text - we want an empty line edit, not
211 // one showing the default value (e.g., "NULL")
212 mLineEdit->setPlaceholderText( QString() );
213 }
214
215 //note - this is deliberately a zero length string, not a null string!
216 setWidgetValue( QLatin1String( "" ) ); // skip-keyword-check
217
218 if ( mTextEdit )
219 mTextEdit->blockSignals( false );
220 if ( mPlainTextEdit )
221 mPlainTextEdit->blockSignals( false );
222 if ( mLineEdit )
223 mLineEdit->blockSignals( false );
224}
225
227{
228 // Do nothing if the value has not changed
229 if ( mInvalidJSON )
230 mForm->displayWarning( tr( "Your JSON was invalid and has been reverted back to the last valid edit or the original data" ) );
231 {
232 mInvalidJSON = false;
233 }
234 setFormFeature( feature );
235 setValue( feature.attribute( fieldIdx() ) );
236}
237
238void QgsTextEditWrapper::updateValues( const QVariant &val, const QVariantList & )
239{
240 if ( mLineEdit )
241 {
242 //restore placeholder text, which may have been removed by showIndeterminateState()
243 mLineEdit->setPlaceholderText( mPlaceholderText );
244 }
245 setWidgetValue( val );
246}
247
249{
250 if ( mTextEdit )
251 mTextEdit->setReadOnly( !enabled );
252
253 if ( mPlainTextEdit )
254 mPlainTextEdit->setReadOnly( !enabled );
255
256 if ( mLineEdit )
257 {
258 mLineEdit->setReadOnly( !enabled );
259 mLineEdit->setFrame( enabled );
260 }
261}
262
264{
265 return mInvalidJSON;
266}
267
268void QgsTextEditWrapper::textChanged( const QString & )
269{
270 if ( mLineEdit )
271 {
272 //restore placeholder text, which may have been removed by showIndeterminateState()
273 mLineEdit->setPlaceholderText( mPlaceholderText );
274 }
275}
276
277void QgsTextEditWrapper::setWidgetValue( const QVariant &val )
278{
279 QString v;
280 if ( QgsVariantUtils::isNull( val ) )
281 {
282 if ( !( field().type() == QMetaType::Type::Int || field().type() == QMetaType::Type::Double || field().type() == QMetaType::Type::LongLong || field().type() == QMetaType::Type::QDate ) )
284 }
285 else if ( field().type() == QMetaType::Type::QVariantMap )
286 {
287 // this has to be overridden for json which has only values (i.e. no objects or arrays), as qgsfield.cpp displayString()
288 // uses QJsonDocument which doesn't recognise this as valid JSON although it technically is
289 if ( field().displayString( val ).isEmpty() )
290 {
291 if ( val.userType() == QMetaType::Type::QString && val.toString() != "\"\""_L1 )
292 {
293 v = val.toString().append( "\"" ).insert( 0, "\"" );
294 }
295 else
296 {
297 v = val.toString();
298 }
299 }
300 else
301 {
302 v = field().displayString( val );
303 }
304 }
305 else if ( val.userType() == QMetaType::Type::Double && std::isnan( val.toDouble() ) )
306 {
308 }
309 else if ( val.userType() == qMetaTypeId<QgsUnsetAttributeValue>() )
310 {
311 v = defaultValue().toString();
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 || QgsVariantUtils::isNull( val ) != QgsVariantUtils::isNull( currentValue ) };
335
336 if ( changed )
337 {
338 if ( mTextEdit )
339 {
340 if ( config( u"UseHtml"_s ).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
365void 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 if ( mLineEdit )
376 mLineEdit->setPlaceholderText( mPlaceholderText );
377}
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:314
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:7504
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7503