Skip to content

Commit

Permalink
custom filtered table implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
uclaros committed Dec 14, 2022
1 parent cfbf5ef commit 8b43bf3
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 109 deletions.
276 changes: 170 additions & 106 deletions src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.cpp
Expand Up @@ -29,24 +29,178 @@
#include "qgspostgresstringutils.h"
#include "qgsapplication.h"

#include <QHeaderView>
#include <QComboBox>
#include <QLineEdit>
#include <QTableWidget>
#include <QStringListModel>
#include <QCompleter>
#include <QTimer>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QKeyEvent>

#include <nlohmann/json.hpp>
using namespace nlohmann;


QgsFilteredTableWidget::QgsFilteredTableWidget( QWidget *parent, bool showSearch )
: QWidget( parent )
{
mSearchWidget = new QgsFilterLineEdit( this );
mSearchWidget->setShowSearchIcon( true );
mSearchWidget->setShowClearButton( true );
mTableWidget = new QTableWidget( this );
mTableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
mTableWidget->horizontalHeader()->setVisible( false );
mTableWidget->verticalHeader()->setSectionResizeMode( QHeaderView::Stretch );
mTableWidget->verticalHeader()->setVisible( false );
mTableWidget->setShowGrid( false );
mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget( mSearchWidget );
layout->addWidget( mTableWidget );
layout->setMargin( 0 );
layout->setSpacing( 0 );
if ( showSearch )
{
mTableWidget->setFocusProxy( mSearchWidget );
connect( mSearchWidget, &QgsFilterLineEdit::textChanged, this, &QgsFilteredTableWidget::filterStringChanged );
installEventFilter( this );
}
else
{
mSearchWidget->setVisible( false );
}
setLayout( layout );
connect( mTableWidget, &QTableWidget::itemChanged, this, &QgsFilteredTableWidget::itemChanged_p );
}

bool QgsFilteredTableWidget::eventFilter( QObject *watched, QEvent *event )
{
Q_UNUSED( watched )
if ( event->type() == QEvent::KeyPress )
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
if ( keyEvent->key() == Qt::Key_Escape &&
!mSearchWidget->text().isEmpty() )
{
mSearchWidget->clear();
return true;
}
}
return false;
}

void QgsFilteredTableWidget::filterStringChanged( const QString &filterString )
{
auto signalBlockedTableWidget = whileBlocking( mTableWidget );
Q_UNUSED( signalBlockedTableWidget )
mTableWidget->clearContents();
const int rCount = std::max( 1, ( int ) std::ceil( ( float ) mCache.count() / ( float ) mColumnCount ) );
mTableWidget->setRowCount( rCount );
int row = 0;
int column = 0;

for ( const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
{
if ( column == mColumnCount )
{
row++;
column = 0;
}
if ( pair.first.value.contains( filterString, Qt::CaseInsensitive ) )
{
QTableWidgetItem *item = nullptr;
item = new QTableWidgetItem( pair.first.value );
item->setData( Qt::UserRole, pair.first.key );
item->setData( Qt::ToolTipRole, pair.first.description );
item->setCheckState( pair.second );
item->setFlags( mEnabledTable ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
mTableWidget->setItem( row, column, item );
column++;
}
}
mTableWidget->setRowCount( row + 1 );
}

QStringList QgsFilteredTableWidget::selection() const
{
QStringList sel;
for ( const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
{
if ( pair.second == Qt::Checked )
sel.append( pair.first.key.toString() );
}
return sel;
}

void QgsFilteredTableWidget::checkItems( const QStringList &checked )
{
for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
{
const bool isChecked = checked.contains( pair.first.key.toString() );
pair.second = isChecked ? Qt::Checked : Qt::Unchecked;
}

filterStringChanged( mSearchWidget->text() );
}

void QgsFilteredTableWidget::populate( QgsValueRelationFieldFormatter::ValueRelationCache cache )
{
mCache.clear();
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : std::as_const( cache ) )
{
mCache.append( qMakePair( element, Qt::Unchecked ) );
}
filterStringChanged( mSearchWidget->text() );
}

void QgsFilteredTableWidget::setIndeterminateState()
{
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
{
for ( int i = 0; i < mColumnCount; ++i )
{
whileBlocking( mTableWidget )->item( j, i )->setCheckState( Qt::PartiallyChecked );
}
}
}

void QgsFilteredTableWidget::setEnabledTable( const bool enabled )
{
if ( mEnabledTable == enabled )
return;

mEnabledTable = enabled;
if ( !enabled )
mSearchWidget->clear();

filterStringChanged( mSearchWidget->text() );
}

void QgsFilteredTableWidget::setColumnCount( const int count )
{
mColumnCount = count;
mTableWidget->setColumnCount( count );
}

void QgsFilteredTableWidget::itemChanged_p( QTableWidgetItem *item )
{
for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
{
if ( pair.first.key == item->data( Qt::UserRole ) )
pair.second = item->checkState();
}
emit itemChanged( item );
}



QgsValueRelationWidgetWrapper::QgsValueRelationWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent )
: QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent )
{
}


QVariant QgsValueRelationWidgetWrapper::value() const
{
QVariant v;
Expand All @@ -63,20 +217,7 @@ QVariant QgsValueRelationWidgetWrapper::value() const
}
else if ( mTableWidget )
{
const int nofColumns = columnCount();
QStringList selection;
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
{
for ( int i = 0; i < nofColumns; ++i )
{
QTableWidgetItem *item = mTableWidget->item( j, i );
if ( item )
{
if ( item->checkState() == Qt::Checked )
selection << item->data( Qt::UserRole ).toString();
}
}
}
QStringList selection = mTableWidget->selection();

// If there is no selection and allow NULL is not checked return NULL.
if ( selection.isEmpty() && ! config( QStringLiteral( "AllowNull" ) ).toBool( ) )
Expand Down Expand Up @@ -138,11 +279,13 @@ QWidget *QgsValueRelationWidgetWrapper::createWidget( QWidget *parent )

mExpression = config().value( QStringLiteral( "FilterExpression" ) ).toString();

if ( config( QStringLiteral( "AllowMulti" ) ).toBool() )
const bool allowMulti = config( QStringLiteral( "AllowMulti" ) ).toBool();
const bool useCompleter = config( QStringLiteral( "UseCompleter" ) ).toBool();
if ( allowMulti )
{
return new QTableWidget( parent );
return new QgsFilteredTableWidget( parent, useCompleter );
}
else if ( config( QStringLiteral( "UseCompleter" ) ).toBool() )
else if ( useCompleter )
{
return new QgsFilterLineEdit( parent );
}
Expand All @@ -156,7 +299,7 @@ void QgsValueRelationWidgetWrapper::initWidget( QWidget *editor )
{

mComboBox = qobject_cast<QComboBox *>( editor );
mTableWidget = qobject_cast<QTableWidget *>( editor );
mTableWidget = qobject_cast<QgsFilteredTableWidget *>( editor );
mLineEdit = qobject_cast<QLineEdit *>( editor );

// Read current initial form values from the editor context
Expand All @@ -170,14 +313,7 @@ void QgsValueRelationWidgetWrapper::initWidget( QWidget *editor )
}
else if ( mTableWidget )
{
mTableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
mTableWidget->horizontalHeader()->setVisible( false );
mTableWidget->verticalHeader()->setSectionResizeMode( QHeaderView::Stretch );
mTableWidget->verticalHeader()->setVisible( false );
mTableWidget->setShowGrid( false );
mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
connect( mTableWidget, &QTableWidget::itemChanged, this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
connect( mTableWidget, &QgsFilteredTableWidget::itemChanged, this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
}
else if ( mLineEdit )
{
Expand Down Expand Up @@ -206,33 +342,7 @@ void QgsValueRelationWidgetWrapper::updateValues( const QVariant &value, const Q
checkList = QgsValueRelationFieldFormatter::valueToStringList( value );
}

QTableWidgetItem *lastChangedItem = nullptr;

const int nofColumns = columnCount();

// This block is needed because item->setCheckState triggers dataChanged gets back to value()
// and iterate over all items again! This can be extremely slow on large items sets.
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
{
auto signalBlockedTableWidget = whileBlocking( mTableWidget );
Q_UNUSED( signalBlockedTableWidget )

for ( int i = 0; i < nofColumns; ++i )
{
QTableWidgetItem *item = mTableWidget->item( j, i );
if ( item )
{
item->setCheckState( checkList.contains( item->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );
//re-set enabled state because it's lost after reloading items
item->setFlags( mEnabled ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
lastChangedItem = item;
}
}
}
// let's trigger the signal now, once and for all
if ( lastChangedItem )
lastChangedItem->setCheckState( checkList.contains( lastChangedItem->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );

mTableWidget->checkItems( checkList );
}
else if ( mComboBox )
{
Expand Down Expand Up @@ -415,33 +525,8 @@ void QgsValueRelationWidgetWrapper::populate( )
}
else if ( mTableWidget )
{
const int nofColumns = columnCount();

if ( ! mCache.empty() )
{
mTableWidget->setRowCount( ( mCache.size() + nofColumns - 1 ) / nofColumns );
}
else
mTableWidget->setRowCount( 1 );
mTableWidget->setColumnCount( nofColumns );

whileBlocking( mTableWidget )->clear();
int row = 0;
int column = 0;
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : std::as_const( mCache ) )
{
if ( column == nofColumns )
{
row++;
column = 0;
}
QTableWidgetItem *item = nullptr;
item = new QTableWidgetItem( element.value );
item->setData( Qt::UserRole, element.key );
whileBlocking( mTableWidget )->setItem( row, column, item );
column++;
}

mTableWidget->setColumnCount( columnCount() );
mTableWidget->populate( mCache );
}
else if ( mLineEdit )
{
Expand All @@ -460,17 +545,9 @@ void QgsValueRelationWidgetWrapper::populate( )

void QgsValueRelationWidgetWrapper::showIndeterminateState()
{
const int nofColumns = columnCount();

if ( mTableWidget )
{
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
{
for ( int i = 0; i < nofColumns; ++i )
{
whileBlocking( mTableWidget )->item( j, i )->setCheckState( Qt::PartiallyChecked );
}
}
mTableWidget->setIndeterminateState();
}
else if ( mComboBox )
{
Expand All @@ -491,20 +568,7 @@ void QgsValueRelationWidgetWrapper::setEnabled( bool enabled )

if ( mTableWidget )
{
auto signalBlockedTableWidget = whileBlocking( mTableWidget );
Q_UNUSED( signalBlockedTableWidget )

for ( int j = 0; j < mTableWidget->rowCount(); j++ )
{
for ( int i = 0; i < mTableWidget->columnCount(); ++i )
{
QTableWidgetItem *item = mTableWidget->item( j, i );
if ( item )
{
item->setFlags( enabled ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
}
}
}
mTableWidget->setEnabledTable( enabled );
}
else
QgsEditorWidgetWrapper::setEnabled( enabled );
Expand Down

0 comments on commit 8b43bf3

Please sign in to comment.