17#include "moc_qgsvaluerelationwidgetwrapper.cpp" 
   30#include <QStringListModel> 
   36#include <QStandardItemModel> 
   39#include <nlohmann/json.hpp> 
   40using namespace nlohmann;
 
   43QgsFilteredTableWidget::QgsFilteredTableWidget( QWidget *parent, 
bool showSearch, 
bool displayGroupName )
 
   45  , mDisplayGroupName( displayGroupName )
 
   48  mSearchWidget->setShowSearchIcon( 
true );
 
   49  mSearchWidget->setShowClearButton( 
true );
 
   50  mTableWidget = 
new QTableWidget( 
this );
 
   51  mTableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
 
   52  mTableWidget->horizontalHeader()->setVisible( 
false );
 
   53  mTableWidget->verticalHeader()->setSectionResizeMode( QHeaderView::Stretch );
 
   54  mTableWidget->verticalHeader()->setVisible( 
false );
 
   55  mTableWidget->setShowGrid( 
false );
 
   56  mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
 
   57  mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
 
   58  mTableWidget->setContextMenuPolicy( Qt::CustomContextMenu );
 
   60  QVBoxLayout *layout = 
new QVBoxLayout();
 
   61  layout->addWidget( mSearchWidget );
 
   62  layout->addWidget( mTableWidget );
 
   63  layout->setContentsMargins( 0, 0, 0, 0 );
 
   64  layout->setSpacing( 0 );
 
   67    mTableWidget->setFocusProxy( mSearchWidget );
 
   68    connect( mSearchWidget, &QgsFilterLineEdit::textChanged, 
this, &QgsFilteredTableWidget::filterStringChanged );
 
   69    installEventFilter( 
this );
 
   73    mSearchWidget->setVisible( 
false );
 
   76  connect( mTableWidget, &QTableWidget::itemChanged, 
this, &QgsFilteredTableWidget::itemChanged_p );
 
   77  connect( mTableWidget, &QTableWidget::customContextMenuRequested, 
this, &QgsFilteredTableWidget::onTableWidgetCustomContextMenuRequested );
 
   80bool QgsFilteredTableWidget::eventFilter( QObject *watched, QEvent *event )
 
   83  if ( event->type() == QEvent::KeyPress )
 
   85    QKeyEvent *keyEvent = 
static_cast<QKeyEvent *
>( event );
 
   86    if ( keyEvent->key() == Qt::Key_Escape && !mSearchWidget->text().isEmpty() )
 
   88      mSearchWidget->clear();
 
   95void QgsFilteredTableWidget::filterStringChanged( 
const QString &filterString )
 
   97  auto signalBlockedTableWidget = 
whileBlocking( mTableWidget );
 
   98  Q_UNUSED( signalBlockedTableWidget )
 
  100  mTableWidget->clearContents();
 
  101  if ( !mCache.isEmpty() )
 
  104    groups << QVariant();
 
  105    for ( 
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
 
  107      if ( !groups.contains( pair.first.group ) )
 
  109        groups << pair.first.group;
 
  112    const int groupsCount = mDisplayGroupName ? groups.count() : groups.count() - 1;
 
  114    const int rCount = std::max( 1, ( 
int ) std::ceil( ( 
float ) ( mCache.count() + groupsCount ) / ( 
float ) mColumnCount ) );
 
  115    mTableWidget->setRowCount( rCount );
 
  119    QVariant currentGroup;
 
  120    for ( 
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
 
  122      if ( column == mColumnCount )
 
  127      if ( currentGroup != pair.first.group )
 
  129        currentGroup = pair.first.group;
 
  130        if ( mDisplayGroupName || !( row == 0 && column == 0 ) )
 
  132          QTableWidgetItem *item = 
new QTableWidgetItem( mDisplayGroupName ? pair.first.group.toString() : QString() );
 
  133          item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
 
  134          mTableWidget->setItem( row, column, item );
 
  136          if ( column == mColumnCount )
 
  143      if ( pair.first.value.contains( filterString, Qt::CaseInsensitive ) )
 
  145        QTableWidgetItem *item = 
new QTableWidgetItem( pair.first.value );
 
  146        item->setData( Qt::UserRole, pair.first.key );
 
  147        item->setData( Qt::ToolTipRole, pair.first.description );
 
  148        item->setCheckState( pair.second );
 
  149        item->setFlags( mEnabledTable ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
 
  150        mTableWidget->setItem( row, column, item );
 
  154    mTableWidget->setRowCount( row + 1 );
 
  158QStringList QgsFilteredTableWidget::selection()
 const 
  161  for ( 
const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
 
  163    if ( pair.second == Qt::Checked )
 
  164      sel.append( pair.first.key.toString() );
 
  169void QgsFilteredTableWidget::checkItems( 
const QStringList &checked )
 
  171  for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
 
  173    const bool isChecked = checked.contains( pair.first.key.toString() );
 
  174    pair.second = isChecked ? Qt::Checked : Qt::Unchecked;
 
  177  filterStringChanged( mSearchWidget->text() );
 
  185    mCache.append( qMakePair( element, Qt::Unchecked ) );
 
  187  filterStringChanged( mSearchWidget->text() );
 
  190void QgsFilteredTableWidget::setIndeterminateState()
 
  192  for ( 
int rowIndex = 0; rowIndex < mTableWidget->rowCount(); rowIndex++ )
 
  194    for ( 
int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex )
 
  196      if ( item( rowIndex, columnIndex ) )
 
  198        whileBlocking( mTableWidget )->item( rowIndex, columnIndex )->setCheckState( Qt::PartiallyChecked );
 
  208void QgsFilteredTableWidget::setEnabledTable( 
const bool enabled )
 
  210  if ( mEnabledTable == enabled )
 
  213  mEnabledTable = enabled;
 
  215    mSearchWidget->clear();
 
  217  filterStringChanged( mSearchWidget->text() );
 
  220void QgsFilteredTableWidget::setColumnCount( 
const int count )
 
  222  mColumnCount = count;
 
  223  mTableWidget->setColumnCount( count );
 
  226void QgsFilteredTableWidget::itemChanged_p( QTableWidgetItem *item )
 
  228  for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
 
  230    if ( pair.first.key == item->data( Qt::UserRole ) )
 
  231      pair.second = item->checkState();
 
  233  emit itemChanged( item );
 
  236void QgsFilteredTableWidget::onTableWidgetCustomContextMenuRequested( 
const QPoint &pos )
 
  241  QAction *actionTableWidgetSelectAll = 
new QAction( tr( 
"Select All" ), 
this );
 
  242  QAction *actionTableWidgetDeselectAll = 
new QAction( tr( 
"Deselect All" ), 
this );
 
  243  connect( actionTableWidgetSelectAll, &QAction::triggered, 
this, &QgsFilteredTableWidget::onTableWidgetMenuActionSelectAllTriggered );
 
  244  connect( actionTableWidgetDeselectAll, &QAction::triggered, 
this, &QgsFilteredTableWidget::onTableWidgetMenuActionDeselectAllTriggered );
 
  246  QMenu *tableWidgetMenu = 
new QMenu( mTableWidget );
 
  247  tableWidgetMenu->addAction( actionTableWidgetSelectAll );
 
  248  tableWidgetMenu->addAction( actionTableWidgetDeselectAll );
 
  250  tableWidgetMenu->exec( QCursor::pos() );
 
  253  disconnect( actionTableWidgetSelectAll, &QAction::triggered, 
nullptr, 
nullptr );
 
  254  disconnect( actionTableWidgetDeselectAll, &QAction::triggered, 
nullptr, 
nullptr );
 
  255  actionTableWidgetSelectAll->deleteLater();
 
  256  actionTableWidgetDeselectAll->deleteLater();
 
  259  tableWidgetMenu->deleteLater();
 
  262void QgsFilteredTableWidget::onTableWidgetMenuActionSelectAllTriggered()
 
  264  for ( 
int rowIndex = 0; rowIndex < mTableWidget->rowCount(); ++rowIndex )
 
  266    for ( 
int columnIndex = 0; columnIndex < mTableWidget->columnCount(); ++columnIndex )
 
  268      QTableWidgetItem *item = 
whileBlocking( mTableWidget )->item( rowIndex, columnIndex );
 
  269      if ( item && ( item->flags() & Qt::ItemIsEnabled ) )
 
  271        item->setCheckState( Qt::Checked );
 
  277void QgsFilteredTableWidget::onTableWidgetMenuActionDeselectAllTriggered()
 
  279  for ( 
int rowIndex = 0; rowIndex < mTableWidget->rowCount(); ++rowIndex )
 
  281    for ( 
int columnIndex = 0; columnIndex < mTableWidget->columnCount(); ++columnIndex )
 
  283      QTableWidgetItem *item = 
whileBlocking( mTableWidget )->item( rowIndex, columnIndex );
 
  284      if ( item && ( item->flags() & Qt::ItemIsEnabled ) )
 
  286        item->setCheckState( Qt::Unchecked );
 
  305    int cbxIdx = mComboBox->currentIndex();
 
  308      v = mComboBox->currentData();
 
  313  else if ( mTableWidget )
 
  315    QStringList selection = mTableWidget->selection();
 
  318    if ( selection.isEmpty() && !
config( QStringLiteral( 
"AllowNull" ) ).toBool() )
 
  325    for ( 
const QString &s : std::as_const( selection ) )
 
  328      const QMetaType::Type type { fkType() };
 
  331        case QMetaType::Type::Int:
 
  332          vl.push_back( s.toInt() );
 
  334        case QMetaType::Type::LongLong:
 
  335          vl.push_back( s.toLongLong() );
 
  343    if ( 
layer()->fields().at( 
fieldIdx() ).type() == QMetaType::Type::QVariantMap || 
layer()->fields().at( 
fieldIdx() ).type() == QMetaType::Type::QVariantList )
 
  353  else if ( mLineEdit )
 
  357      if ( item.value == mLineEdit->text() )
 
 
  374  mExpression = 
config().value( QStringLiteral( 
"FilterExpression" ) ).toString();
 
  376  const bool allowMulti = 
config( QStringLiteral( 
"AllowMulti" ) ).toBool();
 
  377  const bool useCompleter = 
config( QStringLiteral( 
"UseCompleter" ) ).toBool();
 
  380    const bool displayGroupName = 
config( QStringLiteral( 
"DisplayGroupName" ) ).toBool();
 
  381    return new QgsFilteredTableWidget( parent, useCompleter, displayGroupName );
 
  383  else if ( useCompleter )
 
  390    combo->setMinimumContentsLength( 1 );
 
  391    combo->setSizeAdjustPolicy( QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon );
 
 
  398  mComboBox = qobject_cast<QComboBox *>( editor );
 
  399  mTableWidget = qobject_cast<QgsFilteredTableWidget *>( editor );
 
  400  mLineEdit = qobject_cast<QLineEdit *>( editor );
 
  407    mComboBox->view()->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
 
  410  else if ( mTableWidget )
 
  414  else if ( mLineEdit )
 
  416    if ( 
QgsFilterLineEdit *filterLineEdit = qobject_cast<QgsFilterLineEdit *>( editor ) )
 
  419        if ( mSubWidgetSignalBlocking == 0 )
 
  425      connect( mLineEdit, &QLineEdit::textChanged, 
this, &QgsValueRelationWidgetWrapper::emitValueChangedInternal, Qt::UniqueConnection );
 
 
  432  return mTableWidget || mLineEdit || mComboBox;
 
 
  435void QgsValueRelationWidgetWrapper::updateValues( 
const QVariant &value, 
const QVariantList & )
 
  437  updateValue( 
value, 
true );
 
  440void QgsValueRelationWidgetWrapper::updateValue( 
const QVariant &value, 
bool forceComboInsertion )
 
  444    QStringList checkList;
 
  446    if ( 
layer()->fields().at( 
fieldIdx() ).type() == QMetaType::Type::QVariantMap || 
layer()->fields().at( 
fieldIdx() ).type() == QMetaType::Type::QVariantList )
 
  448      checkList = 
value.toStringList();
 
  455    mTableWidget->checkItems( checkList );
 
  457  else if ( mComboBox )
 
  462    for ( 
int i = 0; i < mComboBox->count(); i++ )
 
  464      QVariant v( mComboBox->itemData( i ) );
 
  477        mComboBox->setCurrentIndex( -1 );
 
  481        mComboBox->addItem( 
value.toString().prepend( 
'(' ).append( 
')' ), 
value );
 
  482        mComboBox->setCurrentIndex( mComboBox->findData( 
value ) );
 
  487      mComboBox->setCurrentIndex( idx );
 
  490  else if ( mLineEdit )
 
  492    mSubWidgetSignalBlocking++;
 
  494    bool wasFound { 
false };
 
  497      if ( i.key == 
value )
 
  499        mLineEdit->setText( i.value );
 
  507      mLineEdit->setText( tr( 
"(no selection)" ) );
 
  509    mSubWidgetSignalBlocking--;
 
  520    QVariant oldValue( 
value() );
 
  528      updateValue( oldValue, 
false );
 
  543        QString attributeName( formFields.
names().at( 
fieldIdx() ) );
 
 
  568       && !
config( QStringLiteral( 
"AllowNull" ) ).toBool() )
 
  572    QTimer::singleShot( 0, 
this, [
this] {
 
  573      if ( !mCache.isEmpty() )
 
  575        updateValues( formFeature().attribute( fieldIdx() ).isValid() ? formFeature().attribute( fieldIdx() ) : mCache.at( 0 ).key );
 
 
  581int QgsValueRelationWidgetWrapper::columnCount()
 const 
  583  return std::max( 1, 
config( QStringLiteral( 
"NofColumns" ) ).toInt() );
 
  587QMetaType::Type QgsValueRelationWidgetWrapper::fkType()
 const 
  596      return fields.
at( idx ).
type();
 
  599  return QMetaType::Type::UnknownType;
 
  602void QgsValueRelationWidgetWrapper::populate()
 
  607    if ( 
context().parentFormFeature().isValid() )
 
  616  else if ( mCache.empty() )
 
  623    mComboBox->blockSignals( 
true );
 
  625    const bool allowNull = 
config( QStringLiteral( 
"AllowNull" ) ).toBool();
 
  631    if ( !mCache.isEmpty() )
 
  633      QVariant currentGroup;
 
  634      QStandardItemModel *model = qobject_cast<QStandardItemModel *>( mComboBox->model() );
 
  635      const bool displayGroupName = 
config( QStringLiteral( 
"DisplayGroupName" ) ).toBool();
 
  638        if ( currentGroup != element.group )
 
  640          if ( mComboBox->count() > ( allowNull ? 1 : 0 ) )
 
  642            mComboBox->insertSeparator( mComboBox->count() );
 
  644          if ( displayGroupName )
 
  646            mComboBox->addItem( element.group.toString() );
 
  647            QStandardItem *item = model->item( mComboBox->count() - 1 );
 
  648            item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
 
  650          currentGroup = element.group;
 
  653        mComboBox->addItem( element.value, element.key );
 
  655        if ( !element.description.isEmpty() )
 
  657          mComboBox->setItemData( mComboBox->count() - 1, element.description, Qt::ToolTipRole );
 
  661    mComboBox->blockSignals( 
false );
 
  663  else if ( mTableWidget )
 
  665    mTableWidget->setColumnCount( columnCount() );
 
  666    mTableWidget->populate( mCache );
 
  668  else if ( mLineEdit )
 
  671    values.reserve( mCache.size() );
 
  676    QStringListModel *m = 
new QStringListModel( values, mLineEdit );
 
  677    QCompleter *completer = 
new QCompleter( m, mLineEdit );
 
  679    const Qt::MatchFlags completerMatchFlags { 
config().contains( QStringLiteral( 
"CompleterMatchFlags" ) ) ? 
static_cast<Qt::MatchFlags
>( 
config().value( QStringLiteral( 
"CompleterMatchFlags" ), Qt::MatchFlag::MatchStartsWith ).toInt() ) : Qt::MatchFlag::MatchStartsWith };
 
  681    if ( completerMatchFlags.testFlag( Qt::MatchFlag::MatchContains ) )
 
  683      completer->setFilterMode( Qt::MatchFlag::MatchContains );
 
  687      completer->setFilterMode( Qt::MatchFlag::MatchStartsWith );
 
  689    completer->setCaseSensitivity( Qt::CaseInsensitive );
 
  690    mLineEdit->setCompleter( completer );
 
  698    mTableWidget->setIndeterminateState();
 
  700  else if ( mComboBox )
 
  704  else if ( mLineEdit )
 
 
  712  if ( mEnabled == enabled )
 
  719    mTableWidget->setEnabledTable( enabled );
 
 
  731  ctx.setParentFormFeature( feature );
 
 
  742void 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.
 
@ 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 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.