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