QGIS/src/app/qgssourcefieldsproperties.cpp
Sandro Santilli f5cd856531 Refresh virtual fields editing button state based on field selection
Allows removing virtual fields defined on read-only PostgreSQL tables.
Closes #17593
2017-12-12 15:59:40 +01:00

437 lines
16 KiB
C++

/***************************************************************************
qgssourcefieldsproperties.cpp
---------------------
begin : July 2017
copyright : (C) 2017 by David Signer
email : david at opengis dot ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgssourcefieldsproperties.h"
QgsSourceFieldsProperties::QgsSourceFieldsProperties( QgsVectorLayer *layer, QWidget *parent )
: QWidget( parent )
, mLayer( layer )
{
if ( !layer )
return;
setupUi( this );
layout()->setContentsMargins( 0, 0, 0, 0 );
layout()->setMargin( 0 );
//button appearance
mAddAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewAttribute.svg" ) ) );
mDeleteAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteAttribute.svg" ) ) );
mToggleEditingButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
mCalculateFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCalculateField.svg" ) ) );
//button signals
connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::toggleEditing );
connect( mAddAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::addAttributeClicked );
connect( mDeleteAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::deleteAttributeClicked );
connect( mCalculateFieldButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::calculateFieldClicked );
//slots
connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsSourceFieldsProperties::editingToggled );
connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsSourceFieldsProperties::editingToggled );
connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsSourceFieldsProperties::attributeAdded );
connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsSourceFieldsProperties::attributeDeleted );
//field list appearance
mFieldsList->setColumnCount( AttrColCount );
mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
mFieldsList->setDragDropMode( QAbstractItemView::DragOnly );
mFieldsList->setHorizontalHeaderItem( AttrIdCol, new QTableWidgetItem( tr( "Id" ) ) );
mFieldsList->setHorizontalHeaderItem( AttrNameCol, new QTableWidgetItem( tr( "Name" ) ) );
mFieldsList->setHorizontalHeaderItem( AttrTypeCol, new QTableWidgetItem( tr( "Type" ) ) );
mFieldsList->setHorizontalHeaderItem( AttrTypeNameCol, new QTableWidgetItem( tr( "Type name" ) ) );
mFieldsList->setHorizontalHeaderItem( AttrLengthCol, new QTableWidgetItem( tr( "Length" ) ) );
mFieldsList->setHorizontalHeaderItem( AttrPrecCol, new QTableWidgetItem( tr( "Precision" ) ) );
mFieldsList->setHorizontalHeaderItem( AttrCommentCol, new QTableWidgetItem( tr( "Comment" ) ) );
mFieldsList->setHorizontalHeaderItem( AttrWMSCol, new QTableWidgetItem( QStringLiteral( "WMS" ) ) );
mFieldsList->setHorizontalHeaderItem( AttrWFSCol, new QTableWidgetItem( QStringLiteral( "WFS" ) ) );
mFieldsList->setHorizontalHeaderItem( AttrAliasCol, new QTableWidgetItem( tr( "Alias" ) ) );
mFieldsList->setSortingEnabled( true );
mFieldsList->sortByColumn( 0, Qt::AscendingOrder );
mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
mFieldsList->setSelectionMode( QAbstractItemView::ExtendedSelection );
mFieldsList->verticalHeader()->hide();
//load buttons and field list
updateButtons();
}
void QgsSourceFieldsProperties::init()
{
loadRows();
}
void QgsSourceFieldsProperties::loadRows()
{
disconnect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
const QgsFields &fields = mLayer->fields();
mIndexedWidgets.clear();
mFieldsList->setRowCount( 0 );
for ( int i = 0; i < fields.count(); ++i )
attributeAdded( i );
mFieldsList->resizeColumnsToContents();
connect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
connect( mFieldsList, &QTableWidget::cellPressed, this, &QgsSourceFieldsProperties::attributesListCellPressed );
updateButtons();
updateFieldRenamingStatus();
}
void QgsSourceFieldsProperties::updateFieldRenamingStatus()
{
bool canRenameFields = mLayer->isEditable() && ( mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::RenameAttributes ) && !mLayer->readOnly();
for ( int row = 0; row < mFieldsList->rowCount(); ++row )
{
if ( canRenameFields )
mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
else
mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
}
}
void QgsSourceFieldsProperties::updateExpression()
{
QToolButton *btn = qobject_cast<QToolButton *>( sender() );
Q_ASSERT( btn );
int index = btn->property( "Index" ).toInt();
const QString exp = mLayer->expressionField( index );
QgsExpressionContext context;
context << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() );
QgsExpressionBuilderDialog dlg( mLayer, exp, nullptr, QStringLiteral( "generic" ), context );
if ( dlg.exec() )
{
mLayer->updateExpressionField( index, dlg.expressionText() );
loadRows();
}
}
void QgsSourceFieldsProperties::attributeAdded( int idx )
{
bool sorted = mFieldsList->isSortingEnabled();
if ( sorted )
mFieldsList->setSortingEnabled( false );
const QgsFields &fields = mLayer->fields();
int row = mFieldsList->rowCount();
mFieldsList->insertRow( row );
setRow( row, idx, fields.at( idx ) );
mFieldsList->setCurrentCell( row, idx );
//in case there are rows following, there is increased the id to the correct ones
for ( int i = idx + 1; i < mIndexedWidgets.count(); i++ )
mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
if ( sorted )
mFieldsList->setSortingEnabled( true );
for ( int i = 0; i < mFieldsList->columnCount(); i++ )
{
switch ( mLayer->fields().fieldOrigin( idx ) )
{
case QgsFields::OriginExpression:
if ( i == 7 ) continue;
mFieldsList->item( row, i )->setBackgroundColor( QColor( 200, 200, 255 ) );
break;
case QgsFields::OriginJoin:
mFieldsList->item( row, i )->setBackgroundColor( QColor( 200, 255, 200 ) );
break;
default:
mFieldsList->item( row, i )->setBackgroundColor( QColor( 255, 255, 200 ) );
break;
}
}
}
void QgsSourceFieldsProperties::attributeDeleted( int idx )
{
mFieldsList->removeRow( mIndexedWidgets.at( idx )->row() );
mIndexedWidgets.removeAt( idx );
for ( int i = idx; i < mIndexedWidgets.count(); i++ )
{
mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
}
}
void QgsSourceFieldsProperties::setRow( int row, int idx, const QgsField &field )
{
QTableWidgetItem *dataItem = new QTableWidgetItem();
dataItem->setData( Qt::DisplayRole, idx );
switch ( mLayer->fields().fieldOrigin( idx ) )
{
case QgsFields::OriginExpression:
dataItem->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
break;
case QgsFields::OriginJoin:
dataItem->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/join.png" ) ) );
break;
default:
dataItem->setIcon( mLayer->fields().iconForField( idx ) );
break;
}
mFieldsList->setItem( row, AttrIdCol, dataItem );
mIndexedWidgets.insert( idx, mFieldsList->item( row, 0 ) );
mFieldsList->setItem( row, AttrNameCol, new QTableWidgetItem( field.name() ) );
mFieldsList->setItem( row, AttrAliasCol, new QTableWidgetItem( field.alias() ) );
mFieldsList->setItem( row, AttrTypeCol, new QTableWidgetItem( QVariant::typeToName( field.type() ) ) );
mFieldsList->setItem( row, AttrTypeNameCol, new QTableWidgetItem( field.typeName() ) );
mFieldsList->setItem( row, AttrLengthCol, new QTableWidgetItem( QString::number( field.length() ) ) );
mFieldsList->setItem( row, AttrPrecCol, new QTableWidgetItem( QString::number( field.precision() ) ) );
if ( mLayer->fields().fieldOrigin( idx ) == QgsFields::OriginExpression )
{
QWidget *expressionWidget = new QWidget;
expressionWidget->setLayout( new QHBoxLayout );
QToolButton *editExpressionButton = new QToolButton;
editExpressionButton->setProperty( "Index", idx );
editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
connect( editExpressionButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::updateExpression );
expressionWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
expressionWidget->layout()->addWidget( editExpressionButton );
expressionWidget->layout()->addWidget( new QLabel( mLayer->expressionField( idx ) ) );
expressionWidget->setStyleSheet( "background-color: rgb( 200, 200, 255 )" );
mFieldsList->setCellWidget( row, AttrCommentCol, expressionWidget );
}
else
{
mFieldsList->setItem( row, AttrCommentCol, new QTableWidgetItem( field.comment() ) );
}
QList<int> notEditableCols = QList<int>()
<< AttrIdCol
<< AttrNameCol
<< AttrAliasCol
<< AttrTypeCol
<< AttrTypeNameCol
<< AttrLengthCol
<< AttrPrecCol
<< AttrCommentCol;
Q_FOREACH ( int i, notEditableCols )
{
if ( notEditableCols[i] != AttrCommentCol || mLayer->fields().fieldOrigin( idx ) != QgsFields::OriginExpression )
mFieldsList->item( row, i )->setFlags( mFieldsList->item( row, i )->flags() & ~Qt::ItemIsEditable );
if ( notEditableCols[i] == AttrAliasCol )
mFieldsList->item( row, i )->setToolTip( tr( "Edit alias in the Form config tab" ) );
}
bool canRenameFields = mLayer->isEditable() && ( mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::RenameAttributes ) && !mLayer->readOnly();
if ( canRenameFields )
mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
else
mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
//published WMS/WFS attributes
QTableWidgetItem *wmsAttrItem = new QTableWidgetItem();
wmsAttrItem->setCheckState( mLayer->excludeAttributesWms().contains( field.name() ) ? Qt::Unchecked : Qt::Checked );
wmsAttrItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable );
mFieldsList->setItem( row, AttrWMSCol, wmsAttrItem );
QTableWidgetItem *wfsAttrItem = new QTableWidgetItem();
wfsAttrItem->setCheckState( mLayer->excludeAttributesWfs().contains( field.name() ) ? Qt::Unchecked : Qt::Checked );
wfsAttrItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable );
mFieldsList->setItem( row, AttrWFSCol, wfsAttrItem );
}
bool QgsSourceFieldsProperties::addAttribute( const QgsField &field )
{
QgsDebugMsg( "inserting attribute " + field.name() + " of type " + field.typeName() );
mLayer->beginEditCommand( tr( "Added attribute" ) );
if ( mLayer->addAttribute( field ) )
{
mLayer->endEditCommand();
return true;
}
else
{
mLayer->destroyEditCommand();
QMessageBox::critical( this, tr( "Failed to add field" ), tr( "Failed to add field '%1' of type '%2'. Is the field name unique?" ).arg( field.name(), field.typeName() ) );
return false;
}
}
void QgsSourceFieldsProperties::apply()
{
QSet<QString> excludeAttributesWMS, excludeAttributesWFS;
for ( int i = 0; i < mFieldsList->rowCount(); i++ )
{
if ( mFieldsList->item( i, AttrWMSCol )->checkState() == Qt::Unchecked )
{
excludeAttributesWMS.insert( mFieldsList->item( i, AttrNameCol )->text() );
}
if ( mFieldsList->item( i, AttrWFSCol )->checkState() == Qt::Unchecked )
{
excludeAttributesWFS.insert( mFieldsList->item( i, AttrNameCol )->text() );
}
}
mLayer->setExcludeAttributesWms( excludeAttributesWMS );
mLayer->setExcludeAttributesWfs( excludeAttributesWFS );
}
//SLOTS
void QgsSourceFieldsProperties::editingToggled()
{
updateButtons();
updateFieldRenamingStatus();
}
void QgsSourceFieldsProperties::addAttributeClicked()
{
QgsAddAttrDialog dialog( mLayer, this );
if ( dialog.exec() == QDialog::Accepted )
{
addAttribute( dialog.field() );
loadRows();
}
}
void QgsSourceFieldsProperties::deleteAttributeClicked()
{
QSet<int> providerFields;
QSet<int> expressionFields;
Q_FOREACH ( QTableWidgetItem *item, mFieldsList->selectedItems() )
{
if ( item->column() == 0 )
{
int idx = mIndexedWidgets.indexOf( item );
if ( idx < 0 )
continue;
if ( mLayer->fields().fieldOrigin( idx ) == QgsFields::OriginExpression )
expressionFields << idx;
else
providerFields << idx;
}
}
if ( !expressionFields.isEmpty() )
mLayer->deleteAttributes( expressionFields.toList() );
if ( !providerFields.isEmpty() )
{
mLayer->beginEditCommand( tr( "Deleted attributes" ) );
if ( mLayer->deleteAttributes( providerFields.toList() ) )
mLayer->endEditCommand();
else
mLayer->destroyEditCommand();
}
}
void QgsSourceFieldsProperties::calculateFieldClicked()
{
if ( !mLayer )
{
return;
}
QgsFieldCalculator calc( mLayer, this );
if ( calc.exec() == QDialog::Accepted )
{
loadRows();
}
}
void QgsSourceFieldsProperties::attributesListCellChanged( int row, int column )
{
if ( column == AttrNameCol && mLayer && mLayer->isEditable() )
{
int idx = mIndexedWidgets.indexOf( mFieldsList->item( row, AttrIdCol ) );
QTableWidgetItem *nameItem = mFieldsList->item( row, column );
//avoiding that something will be changed, just because this is triggered by simple re-sorting
if ( !nameItem ||
nameItem->text().isEmpty() ||
!mLayer->fields().exists( idx ) ||
mLayer->fields().at( idx ).name() == nameItem->text()
)
return;
mLayer->beginEditCommand( tr( "Rename attribute" ) );
if ( mLayer->renameAttribute( idx, nameItem->text() ) )
{
mLayer->endEditCommand();
}
else
{
mLayer->destroyEditCommand();
QMessageBox::critical( this, tr( "Failed to rename field" ), tr( "Failed to rename field to '%1'. Is the field name unique?" ).arg( nameItem->text() ) );
}
}
}
void QgsSourceFieldsProperties::attributesListCellPressed( int /*row*/, int /*column*/ )
{
updateButtons();
}
//NICE FUNCTIONS
void QgsSourceFieldsProperties::updateButtons()
{
int cap = mLayer->dataProvider()->capabilities();
mToggleEditingButton->setEnabled( ( cap & QgsVectorDataProvider::ChangeAttributeValues ) && !mLayer->readOnly() );
if ( mLayer->isEditable() )
{
mDeleteAttributeButton->setEnabled( cap & QgsVectorDataProvider::DeleteAttributes );
mAddAttributeButton->setEnabled( cap & QgsVectorDataProvider::AddAttributes );
mToggleEditingButton->setChecked( true );
}
else
{
mToggleEditingButton->setChecked( false );
mAddAttributeButton->setEnabled( false );
// Enable delete button if items are selected
mDeleteAttributeButton->setEnabled( !mFieldsList->selectedItems().isEmpty() );
// and only if all selected items have their origin in an expression
Q_FOREACH ( QTableWidgetItem *item, mFieldsList->selectedItems() )
{
if ( item->column() == 0 )
{
int idx = mIndexedWidgets.indexOf( item );
if ( mLayer->fields().fieldOrigin( idx ) != QgsFields::OriginExpression )
{
mDeleteAttributeButton->setEnabled( false );
break;
}
}
}
}
}