17#include "moc_qgsvaluerelationwidgetwrapper.cpp"
35#include <QStringListModel>
41#include <QStandardItemModel>
43#include <nlohmann/json.hpp>
44using namespace nlohmann;
47QgsFilteredTableWidget::QgsFilteredTableWidget( QWidget *parent,
bool showSearch,
bool displayGroupName )
49 , mDisplayGroupName( displayGroupName )
52 mSearchWidget->setShowSearchIcon(
true );
53 mSearchWidget->setShowClearButton(
true );
54 mTableWidget =
new QTableWidget(
this );
55 mTableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
56 mTableWidget->horizontalHeader()->setVisible(
false );
57 mTableWidget->verticalHeader()->setSectionResizeMode( QHeaderView::Stretch );
58 mTableWidget->verticalHeader()->setVisible(
false );
59 mTableWidget->setShowGrid(
false );
60 mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
61 mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
62 QVBoxLayout *layout =
new QVBoxLayout();
63 layout->addWidget( mSearchWidget );
64 layout->addWidget( mTableWidget );
65 layout->setContentsMargins( 0, 0, 0, 0 );
66 layout->setSpacing( 0 );
69 mTableWidget->setFocusProxy( mSearchWidget );
70 connect( mSearchWidget, &QgsFilterLineEdit::textChanged,
this, &QgsFilteredTableWidget::filterStringChanged );
71 installEventFilter(
this );
75 mSearchWidget->setVisible(
false );
78 connect( mTableWidget, &QTableWidget::itemChanged,
this, &QgsFilteredTableWidget::itemChanged_p );
81bool QgsFilteredTableWidget::eventFilter( QObject *watched, QEvent *event )
84 if ( event->type() == QEvent::KeyPress )
86 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
87 if ( keyEvent->key() == Qt::Key_Escape &&
88 !mSearchWidget->text().isEmpty() )
90 mSearchWidget->clear();
97void QgsFilteredTableWidget::filterStringChanged(
const QString &filterString )
99 auto signalBlockedTableWidget =
whileBlocking( mTableWidget );
100 Q_UNUSED( signalBlockedTableWidget )
102 mTableWidget->clearContents();
103 if ( !mCache.isEmpty() )
106 groups << QVariant();
107 for (
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
109 if ( !groups.contains( pair.first.group ) )
111 groups << pair.first.group;
114 const int groupsCount = mDisplayGroupName ? groups.count() : groups.count() - 1;
116 const int rCount = std::max( 1, (
int ) std::ceil( (
float )( mCache.count() + groupsCount ) / (
float ) mColumnCount ) );
117 mTableWidget->setRowCount( rCount );
121 QVariant currentGroup;
122 for (
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
124 if ( column == mColumnCount )
129 if ( currentGroup != pair.first.group )
131 currentGroup = pair.first.group;
132 if ( mDisplayGroupName || !( row == 0 && column == 0 ) )
134 QTableWidgetItem *item =
new QTableWidgetItem( mDisplayGroupName ? pair.first.group.toString() : QString() );
135 item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
136 mTableWidget->setItem( row, column, item );
138 if ( column == mColumnCount )
145 if ( pair.first.value.contains( filterString, Qt::CaseInsensitive ) )
147 QTableWidgetItem *item =
new QTableWidgetItem( pair.first.value );
148 item->setData( Qt::UserRole, pair.first.key );
149 item->setData( Qt::ToolTipRole, pair.first.description );
150 item->setCheckState( pair.second );
151 item->setFlags( mEnabledTable ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
152 mTableWidget->setItem( row, column, item );
156 mTableWidget->setRowCount( row + 1 );
160QStringList QgsFilteredTableWidget::selection()
const
163 for (
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
165 if ( pair.second == Qt::Checked )
166 sel.append( pair.first.key.toString() );
171void QgsFilteredTableWidget::checkItems(
const QStringList &checked )
173 for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
175 const bool isChecked = checked.contains( pair.first.key.toString() );
176 pair.second = isChecked ? Qt::Checked : Qt::Unchecked;
179 filterStringChanged( mSearchWidget->text() );
187 mCache.append( qMakePair( element, Qt::Unchecked ) );
189 filterStringChanged( mSearchWidget->text() );
192void QgsFilteredTableWidget::setIndeterminateState()
194 for (
int rowIndex = 0; rowIndex < mTableWidget->rowCount(); rowIndex++ )
196 for (
int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex )
198 if ( item( rowIndex, columnIndex ) )
200 whileBlocking( mTableWidget )->item( rowIndex, columnIndex )->setCheckState( Qt::PartiallyChecked );
210void QgsFilteredTableWidget::setEnabledTable(
const bool enabled )
212 if ( mEnabledTable == enabled )
215 mEnabledTable = enabled;
217 mSearchWidget->clear();
219 filterStringChanged( mSearchWidget->text() );
222void QgsFilteredTableWidget::setColumnCount(
const int count )
224 mColumnCount = count;
225 mTableWidget->setColumnCount( count );
228void QgsFilteredTableWidget::itemChanged_p( QTableWidgetItem *item )
230 for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
232 if ( pair.first.key == item->data( Qt::UserRole ) )
233 pair.second = item->checkState();
235 emit itemChanged( item );
251 int cbxIdx = mComboBox->currentIndex();
254 v = mComboBox->currentData();
259 else if ( mTableWidget )
261 QStringList selection = mTableWidget->selection();
264 if ( selection.isEmpty() && !
config( QStringLiteral(
"AllowNull" ) ).toBool( ) )
271 for (
const QString &s : std::as_const( selection ) )
274 const QMetaType::Type type { fkType() };
277 case QMetaType::Type::Int:
278 vl.push_back( s.toInt() );
280 case QMetaType::Type::LongLong:
281 vl.push_back( s.toLongLong() );
289 if (
layer()->fields().at(
fieldIdx() ).type() == QMetaType::Type::QVariantMap ||
290 layer()->fields().at(
fieldIdx() ).type() == QMetaType::Type::QVariantList )
300 else if ( mLineEdit )
304 if ( item.value == mLineEdit->text() )
321 mExpression =
config().value( QStringLiteral(
"FilterExpression" ) ).toString();
323 const bool allowMulti =
config( QStringLiteral(
"AllowMulti" ) ).toBool();
324 const bool useCompleter =
config( QStringLiteral(
"UseCompleter" ) ).toBool();
327 const bool displayGroupName =
config( QStringLiteral(
"DisplayGroupName" ) ).toBool();
328 return new QgsFilteredTableWidget( parent, useCompleter, displayGroupName );
330 else if ( useCompleter )
337 combo->setMinimumContentsLength( 1 );
338 combo->setSizeAdjustPolicy( QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon );
345 mComboBox = qobject_cast<QComboBox *>( editor );
346 mTableWidget = qobject_cast<QgsFilteredTableWidget *>( editor );
347 mLineEdit = qobject_cast<QLineEdit *>( editor );
354 mComboBox->view()->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
355 connect( mComboBox,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
358 else if ( mTableWidget )
362 else if ( mLineEdit )
364 if (
QgsFilterLineEdit *filterLineEdit = qobject_cast<QgsFilterLineEdit *>( editor ) )
368 if ( mSubWidgetSignalBlocking == 0 )
374 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsValueRelationWidgetWrapper::emitValueChangedInternal, Qt::UniqueConnection );
381 return mTableWidget || mLineEdit || mComboBox;
384void QgsValueRelationWidgetWrapper::updateValues(
const QVariant &value,
const QVariantList & )
388 QStringList checkList;
390 if (
layer()->fields().at(
fieldIdx() ).type() == QMetaType::Type::QVariantMap ||
391 layer()->fields().at(
fieldIdx() ).type() == QMetaType::Type::QVariantList )
393 checkList =
value.toStringList();
400 mTableWidget->checkItems( checkList );
402 else if ( mComboBox )
407 for (
int i = 0; i < mComboBox->count(); i++ )
409 QVariant v( mComboBox->itemData( i ) );
422 mComboBox->setCurrentIndex( -1 );
426 mComboBox->addItem(
value.toString().prepend(
'(' ).append(
')' ),
value );
427 mComboBox->setCurrentIndex( mComboBox->findData(
value ) );
432 mComboBox->setCurrentIndex( idx );
435 else if ( mLineEdit )
437 mSubWidgetSignalBlocking ++;
439 bool wasFound {
false };
442 if ( i.key ==
value )
444 mLineEdit->setText( i.value );
452 mLineEdit->setText( tr(
"(no selection)" ) );
454 mSubWidgetSignalBlocking --;
465 QVariant oldValue(
value( ) );
473 updateValues(
value( ) );
488 QString attributeName( formFields.
names().at(
fieldIdx() ) );
512 && ! mCache.isEmpty()
513 && !
config( QStringLiteral(
"AllowNull" ) ).toBool( ) )
517 QTimer::singleShot( 0,
this, [
this ]
519 if ( ! mCache.isEmpty() )
521 updateValues( formFeature().attribute( fieldIdx() ).isValid() ? formFeature().attribute( fieldIdx() ) : mCache.at( 0 ).key );
527int QgsValueRelationWidgetWrapper::columnCount()
const
529 return std::max( 1,
config( QStringLiteral(
"NofColumns" ) ).toInt() );
533QMetaType::Type QgsValueRelationWidgetWrapper::fkType()
const
542 return fields.
at( idx ).
type();
545 return QMetaType::Type::UnknownType;
548void QgsValueRelationWidgetWrapper::populate()
554 if (
context().parentFormFeature().isValid() )
563 else if ( mCache.empty() )
570 mComboBox->blockSignals(
true );
572 const bool allowNull =
config( QStringLiteral(
"AllowNull" ) ).toBool();
578 if ( !mCache.isEmpty() )
580 QVariant currentGroup;
581 QStandardItemModel *model = qobject_cast<QStandardItemModel *>( mComboBox->model() );
582 const bool displayGroupName =
config( QStringLiteral(
"DisplayGroupName" ) ).toBool();
585 if ( currentGroup != element.group )
587 if ( mComboBox->count() > ( allowNull ? 1 : 0 ) )
589 mComboBox->insertSeparator( mComboBox->count() );
591 if ( displayGroupName )
593 mComboBox->addItem( element.group.toString() );
594 QStandardItem *item = model->item( mComboBox->count() - 1 );
595 item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
597 currentGroup = element.group;
600 mComboBox->addItem( element.value, element.key );
602 if ( !element.description.isEmpty() )
604 mComboBox->setItemData( mComboBox->count() - 1, element.description, Qt::ToolTipRole );
608 mComboBox->blockSignals(
false );
610 else if ( mTableWidget )
612 mTableWidget->setColumnCount( columnCount() );
613 mTableWidget->populate( mCache );
615 else if ( mLineEdit )
618 values.reserve( mCache.size() );
623 QStringListModel *m =
new QStringListModel( values, mLineEdit );
624 QCompleter *completer =
new QCompleter( m, mLineEdit );
626 const Qt::MatchFlags completerMatchFlags {
config().contains( QStringLiteral(
"CompleterMatchFlags" ) ) ?
static_cast<Qt::MatchFlags
>(
config().value( QStringLiteral(
"CompleterMatchFlags" ), Qt::MatchFlag::MatchStartsWith ).toInt( ) ) : Qt::MatchFlag::MatchStartsWith };
628 if ( completerMatchFlags.testFlag( Qt::MatchFlag::MatchContains ) )
630 completer->setFilterMode( Qt::MatchFlag::MatchContains );
634 completer->setFilterMode( Qt::MatchFlag::MatchStartsWith );
636 completer->setCaseSensitivity( Qt::CaseInsensitive );
637 mLineEdit->setCompleter( completer );
645 mTableWidget->setIndeterminateState();
647 else if ( mComboBox )
651 else if ( mLineEdit )
659 if ( mEnabled == enabled )
666 mTableWidget->setEnabledTable( enabled );
679 ctx.setParentFormFeature( feature );
684 && (
config( QStringLiteral(
"Value" ) ).toString() == attribute ||
685 config( QStringLiteral(
"Key" ) ).toString() == attribute ||
694void 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...
Q_INVOKABLE bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Container of fields for a vector layer.
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
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.
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.
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.