18#include <nlohmann/json.hpp>
35#include <QStandardItemModel>
37#include <QStringListModel>
41#include "moc_qgsvaluerelationwidgetwrapper.cpp"
43using namespace Qt::StringLiterals;
45using namespace nlohmann;
48QgsFilteredTableWidget::QgsFilteredTableWidget( QWidget *parent,
bool showSearch,
bool displayGroupName )
50 , mDisplayGroupName( displayGroupName )
53 mSearchWidget->setShowSearchIcon(
true );
54 mSearchWidget->setShowClearButton(
true );
55 mTableWidget =
new QTableWidget(
this );
56 mTableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
57 mTableWidget->horizontalHeader()->setVisible(
false );
58 mTableWidget->verticalHeader()->setSectionResizeMode( QHeaderView::Stretch );
59 mTableWidget->verticalHeader()->setVisible(
false );
60 mTableWidget->setShowGrid(
false );
61 mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
62 mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
63 mTableWidget->setContextMenuPolicy( Qt::CustomContextMenu );
65 QVBoxLayout *layout =
new QVBoxLayout();
66 layout->addWidget( mSearchWidget );
67 layout->addWidget( mTableWidget );
68 layout->setContentsMargins( 0, 0, 0, 0 );
69 layout->setSpacing( 0 );
72 mTableWidget->setFocusProxy( mSearchWidget );
73 connect( mSearchWidget, &QgsFilterLineEdit::textChanged,
this, &QgsFilteredTableWidget::filterStringChanged );
74 installEventFilter(
this );
78 mSearchWidget->setVisible(
false );
81 connect( mTableWidget, &QTableWidget::itemChanged,
this, &QgsFilteredTableWidget::itemChanged_p );
82 connect( mTableWidget, &QTableWidget::customContextMenuRequested,
this, &QgsFilteredTableWidget::onTableWidgetCustomContextMenuRequested );
85bool QgsFilteredTableWidget::eventFilter( QObject *watched, QEvent *event )
88 if ( event->type() == QEvent::KeyPress )
90 QKeyEvent *keyEvent =
static_cast<QKeyEvent *
>( event );
91 if ( keyEvent->key() == Qt::Key_Escape && !mSearchWidget->text().isEmpty() )
93 mSearchWidget->clear();
100void QgsFilteredTableWidget::filterStringChanged(
const QString &filterString )
102 auto signalBlockedTableWidget =
whileBlocking( mTableWidget );
103 Q_UNUSED( signalBlockedTableWidget )
105 mTableWidget->clearContents();
106 if ( !mCache.isEmpty() )
109 groups << QVariant();
110 for (
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
112 if ( !groups.contains( pair.first.group ) )
114 groups << pair.first.group;
117 const int groupsCount = mDisplayGroupName ? groups.count() : groups.count() - 1;
119 const int rCount = std::max( 1, (
int ) std::ceil( (
float ) ( mCache.count() + groupsCount ) / (
float ) mColumnCount ) );
120 mTableWidget->setRowCount( rCount );
124 QVariant currentGroup;
125 for (
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
127 if ( column == mColumnCount )
132 if ( currentGroup != pair.first.group )
134 currentGroup = pair.first.group;
135 if ( mDisplayGroupName || !( row == 0 && column == 0 ) )
137 QTableWidgetItem *item =
new QTableWidgetItem( mDisplayGroupName ? pair.first.group.toString() : QString() );
138 item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
139 mTableWidget->setItem( row, column, item );
141 if ( column == mColumnCount )
148 if ( pair.first.value.contains( filterString, Qt::CaseInsensitive ) )
150 QTableWidgetItem *item =
new QTableWidgetItem( pair.first.value );
151 item->setData( Qt::UserRole, pair.first.key );
152 item->setData( Qt::ToolTipRole, pair.first.description );
153 item->setCheckState( pair.second );
154 item->setFlags( mEnabledTable ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
155 mTableWidget->setItem( row, column, item );
159 mTableWidget->setRowCount( row + 1 );
163QStringList QgsFilteredTableWidget::selection()
const
166 for (
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
168 if ( pair.second == Qt::Checked )
169 sel.append( pair.first.key.toString() );
174void QgsFilteredTableWidget::checkItems(
const QStringList &checked )
176 for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
178 const bool isChecked = checked.contains( pair.first.key.toString() );
179 pair.second = isChecked ? Qt::Checked : Qt::Unchecked;
182 filterStringChanged( mSearchWidget->text() );
190 mCache.append( qMakePair( element, Qt::Unchecked ) );
192 filterStringChanged( mSearchWidget->text() );
195void QgsFilteredTableWidget::setIndeterminateState()
197 for (
int rowIndex = 0; rowIndex < mTableWidget->rowCount(); rowIndex++ )
199 for (
int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex )
201 if ( item( rowIndex, columnIndex ) )
203 whileBlocking( mTableWidget )->item( rowIndex, columnIndex )->setCheckState( Qt::PartiallyChecked );
213void QgsFilteredTableWidget::setEnabledTable(
const bool enabled )
215 if ( mEnabledTable == enabled )
218 mEnabledTable = enabled;
220 mSearchWidget->clear();
222 filterStringChanged( mSearchWidget->text() );
225void QgsFilteredTableWidget::setColumnCount(
const int count )
227 mColumnCount = count;
228 mTableWidget->setColumnCount( count );
231void QgsFilteredTableWidget::itemChanged_p( QTableWidgetItem *item )
233 for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
235 if ( pair.first.key == item->data( Qt::UserRole ) )
236 pair.second = item->checkState();
238 emit itemChanged( item );
241void QgsFilteredTableWidget::onTableWidgetCustomContextMenuRequested(
const QPoint &pos )
246 QAction *actionTableWidgetSelectAll =
new QAction( tr(
"Select All" ),
this );
247 QAction *actionTableWidgetDeselectAll =
new QAction( tr(
"Deselect All" ),
this );
248 connect( actionTableWidgetSelectAll, &QAction::triggered,
this, &QgsFilteredTableWidget::onTableWidgetMenuActionSelectAllTriggered );
249 connect( actionTableWidgetDeselectAll, &QAction::triggered,
this, &QgsFilteredTableWidget::onTableWidgetMenuActionDeselectAllTriggered );
251 QMenu *tableWidgetMenu =
new QMenu( mTableWidget );
252 tableWidgetMenu->addAction( actionTableWidgetSelectAll );
253 tableWidgetMenu->addAction( actionTableWidgetDeselectAll );
255 tableWidgetMenu->exec( QCursor::pos() );
258 disconnect( actionTableWidgetSelectAll, &QAction::triggered,
nullptr,
nullptr );
259 disconnect( actionTableWidgetDeselectAll, &QAction::triggered,
nullptr,
nullptr );
260 actionTableWidgetSelectAll->deleteLater();
261 actionTableWidgetDeselectAll->deleteLater();
264 tableWidgetMenu->deleteLater();
267void QgsFilteredTableWidget::onTableWidgetMenuActionSelectAllTriggered()
269 for (
int rowIndex = 0; rowIndex < mTableWidget->rowCount(); ++rowIndex )
271 for (
int columnIndex = 0; columnIndex < mTableWidget->columnCount(); ++columnIndex )
273 QTableWidgetItem *item =
whileBlocking( mTableWidget )->item( rowIndex, columnIndex );
274 if ( item && ( item->flags() & Qt::ItemIsEnabled ) )
276 item->setCheckState( Qt::Checked );
282void QgsFilteredTableWidget::onTableWidgetMenuActionDeselectAllTriggered()
284 for (
int rowIndex = 0; rowIndex < mTableWidget->rowCount(); ++rowIndex )
286 for (
int columnIndex = 0; columnIndex < mTableWidget->columnCount(); ++columnIndex )
288 QTableWidgetItem *item =
whileBlocking( mTableWidget )->item( rowIndex, columnIndex );
289 if ( item && ( item->flags() & Qt::ItemIsEnabled ) )
291 item->setCheckState( Qt::Unchecked );
310 int cbxIdx = mComboBox->currentIndex();
313 v = mComboBox->currentData();
318 else if ( mTableWidget )
320 QStringList selection = mTableWidget->selection();
323 if ( selection.isEmpty() && !
config( u
"AllowNull"_s ).toBool() )
330 for (
const QString &s : std::as_const( selection ) )
333 const QMetaType::Type type { fkType() };
336 case QMetaType::Type::Int:
337 vl.push_back( s.toInt() );
339 case QMetaType::Type::LongLong:
340 vl.push_back( s.toLongLong() );
348 if (
layer()->fields().at(
fieldIdx() ).type() == QMetaType::Type::QVariantMap ||
layer()->fields().at(
fieldIdx() ).type() == QMetaType::Type::QVariantList )
358 else if ( mLineEdit )
362 if ( item.value == mLineEdit->text() )
379 mExpression =
config().value( u
"FilterExpression"_s ).toString();
381 const bool allowMulti =
config( u
"AllowMulti"_s ).toBool();
382 const bool useCompleter =
config( u
"UseCompleter"_s ).toBool();
385 const bool displayGroupName =
config( u
"DisplayGroupName"_s ).toBool();
386 return new QgsFilteredTableWidget( parent, useCompleter, displayGroupName );
388 else if ( useCompleter )
395 combo->setMinimumContentsLength( 1 );
396 combo->setSizeAdjustPolicy( QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon );
403 mComboBox = qobject_cast<QComboBox *>( editor );
404 mTableWidget = qobject_cast<QgsFilteredTableWidget *>( editor );
405 mLineEdit = qobject_cast<QLineEdit *>( editor );
412 mComboBox->view()->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
415 else if ( mTableWidget )
419 else if ( mLineEdit )
421 if (
QgsFilterLineEdit *filterLineEdit = qobject_cast<QgsFilterLineEdit *>( editor ) )
424 if ( mSubWidgetSignalBlocking == 0 )
430 connect( mLineEdit, &QLineEdit::textChanged,
this, &QgsValueRelationWidgetWrapper::emitValueChangedInternal, Qt::UniqueConnection );
437 return mTableWidget || mLineEdit || mComboBox;
440void QgsValueRelationWidgetWrapper::updateValues(
const QVariant &value,
const QVariantList & )
442 updateValue(
value,
true );
445void QgsValueRelationWidgetWrapper::updateValue(
const QVariant &value,
bool forceComboInsertion )
449 QStringList checkList;
451 if (
layer()->fields().at(
fieldIdx() ).type() == QMetaType::Type::QVariantMap ||
layer()->fields().at(
fieldIdx() ).type() == QMetaType::Type::QVariantList )
453 checkList =
value.toStringList();
460 mTableWidget->checkItems( checkList );
462 else if ( mComboBox )
467 for (
int i = 0; i < mComboBox->count(); i++ )
469 QVariant v( mComboBox->itemData( i ) );
483 for (
int i = 0; i < mComboBox->count(); i++ )
491 mComboBox->setCurrentIndex( idx );
495 mComboBox->addItem(
value.toString().prepend(
'(' ).append(
')' ),
value );
496 mComboBox->setCurrentIndex( mComboBox->findData(
value ) );
501 mComboBox->setCurrentIndex( idx );
504 else if ( mLineEdit )
506 mSubWidgetSignalBlocking++;
508 bool wasFound {
false };
509 for (
const QgsValueRelationFieldFormatter::ValueRelationItem &i : std::as_const( mCache ) )
511 if ( i.key ==
value )
513 mLineEdit->setText( i.value );
521 mLineEdit->setText( tr(
"(no selection)" ) );
523 mSubWidgetSignalBlocking--;
533 if ( attributeChanged || isMultieditMode )
535 QVariant oldValue(
value() );
543 updateValue( isMultieditMode ? oldValue :
value(),
false );
558 QString attributeName( formFields.
names().at(
fieldIdx() ) );
588 && !
config( u
"AllowNull"_s ).toBool() )
592 QTimer::singleShot( 0,
this, [
this] {
593 if ( !mCache.isEmpty() )
601int QgsValueRelationWidgetWrapper::columnCount()
const
603 return std::max( 1,
config( u
"NofColumns"_s ).toInt() );
607QMetaType::Type QgsValueRelationWidgetWrapper::fkType()
const
612 QgsFields fields =
layer->fields();
616 return fields.
at( idx ).
type();
619 return QMetaType::Type::UnknownType;
622void QgsValueRelationWidgetWrapper::populate()
627 if (
context().parentFormFeature().isValid() )
636 else if ( mCache.empty() )
643 mComboBox->blockSignals(
true );
645 const bool allowNull =
config( u
"AllowNull"_s ).toBool();
651 if ( !mCache.isEmpty() )
653 QVariant currentGroup;
654 QStandardItemModel *model = qobject_cast<QStandardItemModel *>( mComboBox->model() );
655 const bool displayGroupName =
config( u
"DisplayGroupName"_s ).toBool();
656 for (
const QgsValueRelationFieldFormatter::ValueRelationItem &element : std::as_const( mCache ) )
658 if ( currentGroup != element.group )
660 if ( mComboBox->count() > ( allowNull ? 1 : 0 ) )
662 mComboBox->insertSeparator( mComboBox->count() );
664 if ( displayGroupName )
666 mComboBox->addItem( element.group.toString() );
667 QStandardItem *item = model->item( mComboBox->count() - 1 );
668 item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
670 currentGroup = element.group;
673 mComboBox->addItem( element.value, element.key );
675 if ( !element.description.isEmpty() )
677 mComboBox->setItemData( mComboBox->count() - 1, element.description, Qt::ToolTipRole );
681 mComboBox->blockSignals(
false );
683 else if ( mTableWidget )
685 mTableWidget->setColumnCount( columnCount() );
686 mTableWidget->populate( mCache );
688 else if ( mLineEdit )
691 values.reserve( mCache.size() );
692 for (
const QgsValueRelationFieldFormatter::ValueRelationItem &i : std::as_const( mCache ) )
696 QStringListModel *m =
new QStringListModel( values, mLineEdit );
697 QCompleter *completer =
new QCompleter( m, mLineEdit );
699 const Qt::MatchFlags completerMatchFlags {
config().contains( u
"CompleterMatchFlags"_s ) ?
static_cast<Qt::MatchFlags
>(
config().value( u
"CompleterMatchFlags"_s, Qt::MatchFlag::MatchStartsWith ).toInt() ) : Qt::MatchFlag::MatchStartsWith };
701 if ( completerMatchFlags.testFlag( Qt::MatchFlag::MatchContains ) )
703 completer->setFilterMode( Qt::MatchFlag::MatchContains );
707 completer->setFilterMode( Qt::MatchFlag::MatchStartsWith );
709 completer->setCaseSensitivity( Qt::CaseInsensitive );
710 mLineEdit->setCompleter( completer );
718 mTableWidget->setIndeterminateState();
720 else if ( mComboBox )
724 else if ( mLineEdit )
732 if ( mEnabled == enabled )
739 mTableWidget->setEnabledTable( enabled );
762void QgsValueRelationWidgetWrapper::emitValueChangedInternal(
const QString &
value )
Contains context information for attribute editor widgets.
QgsFeature parentFormFeature() const
Returns the feature of the currently edited parent form in its actual state.
void setParentFormFeature(const QgsFeature &feature)
Sets the feature of the currently edited parent form.
@ MultiEditMode
Multi edit mode, for editing fields of multiple features at once.
Mode attributeFormMode() const
Returns current attributeFormMode.
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.
bool isValid() const
Returns the validity of this feature.
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 dataset.
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.