34#include <QStringListModel>
41#include <nlohmann/json.hpp>
42using namespace nlohmann;
45QgsFilteredTableWidget::QgsFilteredTableWidget( QWidget *parent,
bool showSearch )
49 mSearchWidget->setShowSearchIcon(
true );
50 mSearchWidget->setShowClearButton(
true );
51 mTableWidget =
new QTableWidget(
this );
52 mTableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
53 mTableWidget->horizontalHeader()->setVisible(
false );
54 mTableWidget->verticalHeader()->setSectionResizeMode( QHeaderView::Stretch );
55 mTableWidget->verticalHeader()->setVisible(
false );
56 mTableWidget->setShowGrid(
false );
57 mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
58 mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
59 QVBoxLayout *layout =
new QVBoxLayout();
60 layout->addWidget( mSearchWidget );
61 layout->addWidget( mTableWidget );
62 layout->setContentsMargins( 0, 0, 0, 0 );
63 layout->setSpacing( 0 );
66 mTableWidget->setFocusProxy( mSearchWidget );
67 connect( mSearchWidget, &QgsFilterLineEdit::textChanged,
this, &QgsFilteredTableWidget::filterStringChanged );
68 installEventFilter(
this );
72 mSearchWidget->setVisible(
false );
75 connect( mTableWidget, &QTableWidget::itemChanged,
this, &QgsFilteredTableWidget::itemChanged_p );
78bool QgsFilteredTableWidget::eventFilter( QObject *watched, QEvent *event )
81 if ( event->type() == QEvent::KeyPress )
83 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
84 if ( keyEvent->key() == Qt::Key_Escape &&
85 !mSearchWidget->text().isEmpty() )
87 mSearchWidget->clear();
94void QgsFilteredTableWidget::filterStringChanged(
const QString &filterString )
96 auto signalBlockedTableWidget =
whileBlocking( mTableWidget );
97 Q_UNUSED( signalBlockedTableWidget )
98 mTableWidget->clearContents();
99 const int rCount = std::max( 1, (
int ) std::ceil( (
float ) mCache.count() / (
float ) mColumnCount ) );
100 mTableWidget->setRowCount( rCount );
104 for (
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
106 if ( column == mColumnCount )
111 if ( pair.first.value.contains( filterString, Qt::CaseInsensitive ) )
113 QTableWidgetItem *item =
nullptr;
114 item =
new QTableWidgetItem( pair.first.value );
115 item->setData( Qt::UserRole, pair.first.key );
116 item->setData( Qt::ToolTipRole, pair.first.description );
117 item->setCheckState( pair.second );
118 item->setFlags( mEnabledTable ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
119 mTableWidget->setItem( row, column, item );
123 mTableWidget->setRowCount( row + 1 );
126QStringList QgsFilteredTableWidget::selection()
const
129 for (
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
131 if ( pair.second == Qt::Checked )
132 sel.append( pair.first.key.toString() );
137void QgsFilteredTableWidget::checkItems(
const QStringList &checked )
139 for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
141 const bool isChecked = checked.contains( pair.first.key.toString() );
142 pair.second = isChecked ? Qt::Checked : Qt::Unchecked;
145 filterStringChanged( mSearchWidget->text() );
153 mCache.append( qMakePair( element, Qt::Unchecked ) );
155 filterStringChanged( mSearchWidget->text() );
158void QgsFilteredTableWidget::setIndeterminateState()
160 for (
int rowIndex = 0; rowIndex < mTableWidget->rowCount(); rowIndex++ )
162 for (
int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex )
164 if ( item( rowIndex, columnIndex ) )
166 whileBlocking( mTableWidget )->item( rowIndex, columnIndex )->setCheckState( Qt::PartiallyChecked );
176void QgsFilteredTableWidget::setEnabledTable(
const bool enabled )
178 if ( mEnabledTable == enabled )
181 mEnabledTable = enabled;
183 mSearchWidget->clear();
185 filterStringChanged( mSearchWidget->text() );
188void QgsFilteredTableWidget::setColumnCount(
const int count )
190 mColumnCount = count;
191 mTableWidget->setColumnCount( count );
194void QgsFilteredTableWidget::itemChanged_p( QTableWidgetItem *item )
196 for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
198 if ( pair.first.key == item->data( Qt::UserRole ) )
199 pair.second = item->checkState();
201 emit itemChanged( item );
217 int cbxIdx = mComboBox->currentIndex();
220 v = mComboBox->currentData();
222 v = QVariant(
field().type() );
225 else if ( mTableWidget )
227 QStringList selection = mTableWidget->selection();
230 if ( selection.isEmpty() && !
config( QStringLiteral(
"AllowNull" ) ).toBool( ) )
232 return QVariant( QVariant::Type::List );
237 for (
const QString &s : std::as_const( selection ) )
240 const QVariant::Type type { fkType() };
243 case QVariant::Type::Int:
244 vl.push_back( s.toInt() );
246 case QVariant::Type::LongLong:
247 vl.push_back( s.toLongLong() );
255 if (
layer()->fields().at(
fieldIdx() ).type() == QVariant::Map ||
256 layer()->fields().at(
fieldIdx() ).type() == QVariant::List )
266 else if ( mLineEdit )
270 if ( item.value == mLineEdit->text() )
287 mExpression =
config().value( QStringLiteral(
"FilterExpression" ) ).toString();
289 const bool allowMulti =
config( QStringLiteral(
"AllowMulti" ) ).toBool();
290 const bool useCompleter =
config( QStringLiteral(
"UseCompleter" ) ).toBool();
293 return new QgsFilteredTableWidget( parent, useCompleter );
295 else if ( useCompleter )
302 combo->setMinimumContentsLength( 1 );
303 combo->setSizeAdjustPolicy( QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon );
310 mComboBox = qobject_cast<QComboBox *>( editor );
311 mTableWidget = qobject_cast<QgsFilteredTableWidget *>( editor );
312 mLineEdit = qobject_cast<QLineEdit *>( editor );
319 mComboBox->view()->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
320 connect( mComboBox,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
323 else if ( mTableWidget )
327 else if ( mLineEdit )
329 if (
QgsFilterLineEdit *filterLineEdit = qobject_cast<QgsFilterLineEdit *>( editor ) )
333 if ( mSubWidgetSignalBlocking == 0 )
339 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsValueRelationWidgetWrapper::emitValueChangedInternal, Qt::UniqueConnection );
346 return mTableWidget || mLineEdit || mComboBox;
349void QgsValueRelationWidgetWrapper::updateValues(
const QVariant &value,
const QVariantList & )
353 QStringList checkList;
355 if (
layer()->fields().at(
fieldIdx() ).type() == QVariant::Map ||
356 layer()->fields().at(
fieldIdx() ).type() == QVariant::List )
358 checkList =
value.toStringList();
365 mTableWidget->checkItems( checkList );
367 else if ( mComboBox )
372 for (
int i = 0; i < mComboBox->count(); i++ )
374 QVariant v( mComboBox->itemData( i ) );
387 mComboBox->setCurrentIndex( -1 );
391 mComboBox->addItem(
value.toString().prepend(
'(' ).append(
')' ),
value );
392 mComboBox->setCurrentIndex( mComboBox->findData(
value ) );
397 mComboBox->setCurrentIndex( idx );
400 else if ( mLineEdit )
402 mSubWidgetSignalBlocking ++;
404 bool wasFound {
false };
407 if ( i.key ==
value )
409 mLineEdit->setText( i.value );
417 mLineEdit->setText( tr(
"(no selection)" ) );
419 mSubWidgetSignalBlocking --;
427 if ( attributeChanged )
429 QVariant oldValue(
value( ) );
437 updateValues(
value( ) );
452 QString attributeName( formFields.
names().at(
fieldIdx() ) );
476 && ! mCache.isEmpty()
477 && !
config( QStringLiteral(
"AllowNull" ) ).toBool( ) )
481 QTimer::singleShot( 0,
this, [
this ]
483 if ( ! mCache.isEmpty() )
485 updateValues( formFeature().attribute( fieldIdx() ).isValid() ? formFeature().attribute( fieldIdx() ) : mCache.at( 0 ).key );
491int QgsValueRelationWidgetWrapper::columnCount()
const
493 return std::max( 1,
config( QStringLiteral(
"NofColumns" ) ).toInt() );
497QVariant::Type QgsValueRelationWidgetWrapper::fkType()
const
506 return fields.
at( idx ).
type();
509 return QVariant::Type::Invalid;
512void QgsValueRelationWidgetWrapper::populate()
518 if (
context().parentFormFeature().isValid() )
527 else if ( mCache.empty() )
535 if (
config( QStringLiteral(
"AllowNull" ) ).toBool( ) )
537 whileBlocking( mComboBox )->addItem( tr(
"(no selection)" ), QVariant(
field().type( ) ) );
542 whileBlocking( mComboBox )->addItem( element.value, element.key );
543 if ( !element.description.isEmpty() )
544 mComboBox->setItemData( mComboBox->count() - 1, element.description, Qt::ToolTipRole );
548 else if ( mTableWidget )
550 mTableWidget->setColumnCount( columnCount() );
551 mTableWidget->populate( mCache );
553 else if ( mLineEdit )
556 values.reserve( mCache.size() );
561 QStringListModel *m =
new QStringListModel( values, mLineEdit );
562 QCompleter *completer =
new QCompleter( m, mLineEdit );
563 completer->setCaseSensitivity( Qt::CaseInsensitive );
564 mLineEdit->setCompleter( completer );
572 mTableWidget->setIndeterminateState();
574 else if ( mComboBox )
578 else if ( mLineEdit )
586 if ( mEnabled == enabled )
593 mTableWidget->setEnabledTable( enabled );
606 ctx.setParentFormFeature( feature );
611 && (
config( QStringLiteral(
"Value" ) ).toString() == attribute ||
612 config( QStringLiteral(
"Key" ) ).toString() == attribute ||
621void QgsValueRelationWidgetWrapper::emitValueChangedInternal(
const QString &value )
This class contains context information for attribute editor widgets.
QgsFeature parentFormFeature() const
Returns the feature of the currently edited parent form in its actual state.
@ MultiEditMode
Multi edit mode, for editing fields of multiple features at once.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Container of fields for a vector layer.
int count() const
Returns number of items.
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
QStringList names() const
Returns a list with field names.
QLineEdit subclass with built in support for clearing the widget's value and handling custom null val...
void valueChanged(const QString &value)
Same as textChanged() but with support for null values.
static QString buildArray(const QVariantList &list)
Build a postgres array like formatted list in a string from a QVariantList.
static QgsProject * instance()
Returns the QgsProject singleton instance.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based data sets.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
bool qgsVariantEqual(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether they are equal, two NULL values are always treated a...
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.