[feature] New configuration options for attribute table

* Allow reordering the attribute table columns
 * Allow adding a new column to trigger an action to the attribute table
This commit is contained in:
Matthias Kuhn 2016-04-28 15:47:43 +02:00
parent aa9010e8ed
commit dd88fa99f3
27 changed files with 740 additions and 107 deletions

View File

@ -22,6 +22,7 @@
%Include qgsaction.sip
%Include qgsactionmanager.sip
%Include qgsattributeaction.sip
%Include qgsattributetableconfig.sip
%Include qgsbrowsermodel.sip
%Include qgsclipper.sip
%Include qgscolorscheme.sip

View File

@ -0,0 +1,78 @@
/***************************************************************************
qgsattributetableconfig.h - QgsAttributeTableConfig
---------------------
begin : 27.4.2016
copyright : (C) 2016 by Matthias Kuhn
email : matthias@opengis.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. *
* *
***************************************************************************/
/**
* This is a container for configuration of the attribute table.
* The configuration is specific for one vector layer.
*/
class QgsAttributeTableConfig
{
%TypeHeaderCode
#include <qgsattributetableconfig.h>
%End
public:
/**
* The type of an attribute table column.
*/
enum Type
{
Field, //!< This column represents a field
Action //!< This column represents an action widget
};
/**
* Defines the configuration of a column in the attribute table.
*/
struct ColumnConfig
{
QgsAttributeTableConfig::Type mType; //!< The type of this column.
QString mName; //!< The name of the attribute if this column represents a field
bool mHidden; //!< Flag that controls if the column is hidden
};
QgsAttributeTableConfig();
/**
* Get the list with all columns and their configuration.
* The list order defines the order of appearance.
*/
QVector<QgsAttributeTableConfig::ColumnConfig> columns() const;
/**
* Set the list of columns visible in the attribute table.
* The list order defines the order of appearance.
*/
void setColumns( const QVector<QgsAttributeTableConfig::ColumnConfig>& columns );
/**
* Update the configuration with the given fields.
* Any field which is present in the configuration but not present in the
* parameter fields will be removed. Any field which is in the parameter
* fields but not in the configuration will be appended.
*/
void update( const QgsFields& fields );
/**
* Serialize to XML on layer save
*/
void writeXml( QDomNode& node ) const;
/**
* Deserialize to XML on layer load
*/
void readXml( const QDomNode& node );
};

View File

@ -1321,6 +1321,18 @@ class QgsVectorLayer : QgsMapLayer
*/
QgsConditionalLayerStyles *conditionalStyles() const;
/**
* Get the attribute table configuration object.
* This defines the appearance of the attribute table.
*/
QgsAttributeTableConfig attributeTableConfig() const;
/**
* Set the attribute table configuration object.
* This defines the appearance of the attribute table.
*/
void setAttributeTableConfig( const QgsAttributeTableConfig& attributeTableConfig );
public slots:
/**
* Select feature by its ID

View File

@ -13,6 +13,18 @@ class QgsAttributeTableFilterModel: QSortFilterProxyModel, QgsFeatureModel
ShowEdited
};
enum ColumnType
{
ColumnTypeField, //!< This column shows a field
ColumnTypeActionButton //!< This column shows action buttons
};
enum Role
{
TypeRole = QgsAttributeTableModel::UserRole //!< The type of a given column
};
/**
*
* Make sure, the master model is already loaded, so the selection will get synchronized.
@ -99,6 +111,25 @@ class QgsAttributeTableFilterModel: QSortFilterProxyModel, QgsFeatureModel
/** Returns the map canvas*/
QgsMapCanvas* mapCanvas() const;
virtual QVariant data( const QModelIndex& index, int role ) const;
QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
/**
* Get the index of the first column that contains an action widget.
* Returns -1 if none is defined.
*/
int actionColumnIndex() const;
int columnCount( const QModelIndex &parent ) const;
/**
* Set the attribute table configuration to control which fields are shown,
* in which order they are shown as well as if and where an action column
* is shown.
*/
void setAttributeTableConfig( const QgsAttributeTableConfig& config );
protected:
/**
* Returns true if the source row will be accepted

View File

@ -19,7 +19,7 @@ class QgsAttributeTableView : QTableView
/**
* This event filter is installed on the verticalHeader to intercept mouse press and release
* events. These are used to disable / enable live synchronisation with the map canvas selection
* which can be slow due to recurring canvas repaints. Updating the
* which can be slow due to recurring canvas repaints.
*
* @param object The object which is the target of the event.
* @param event The intercepted event

View File

@ -65,6 +65,11 @@ class QgsDualView : QStackedWidget
*/
void setFilterMode( QgsAttributeTableFilterModel::FilterMode filterMode );
/**
* Get the filter mode
*
* @return the filter mode
*/
QgsAttributeTableFilterModel::FilterMode filterMode();
/**
@ -98,6 +103,9 @@ class QgsDualView : QStackedWidget
*/
void setFilteredFeatures( const QgsFeatureIds& filteredFeatures );
/**
* Get a list of currently visible feature ids.
*/
QgsFeatureIds filteredFeatures();
/**
@ -112,6 +120,11 @@ class QgsDualView : QStackedWidget
void setFeatureSelectionManager( QgsIFeatureSelectionManager* featureSelectionManager );
QgsAttributeTableView* tableView();
/**
* Set the attribute table config which should be used to control
* the appearance of the attribute table.
*/
void setAttributeTableConfig( const QgsAttributeTableConfig& config );
protected:
/**

View File

@ -8,16 +8,19 @@ class QgsOrganizeTableColumnsDialog : QDialog
/**
* Constructor
* @param vl The concerned vector layer
* @param visible the cuurent list of visible fields name
* @param visible the current list of visible fields name
* @param parent parent object
* @param flags window flags
*/
QgsOrganizeTableColumnsDialog( const QgsVectorLayer* vl, QStringList visible, QWidget *parent /TransferThis/ = nullptr, Qt::WindowFlags flags = Qt::Window );
QgsOrganizeTableColumnsDialog(const QgsVectorLayer* vl, QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::Window );
/**
* Destructor
*/
~QgsOrganizeTableColumnsDialog();
/**
* Get the selected fields name
* @return The selected fields name
* Get the updated configuration
*/
QStringList selectedFields();
QgsAttributeTableConfig config() const;
};

View File

@ -125,6 +125,7 @@ QgsAttributeTableDialog::QgsAttributeTableDialog( QgsVectorLayer *theLayer, QWid
// Initialize dual view
mMainView->init( mLayer, QgisApp::instance()->mapCanvas(), r, context );
mMainView->setAttributeTableConfig( mLayer->attributeTableConfig() );
// Initialize filter gui elements
mFilterActionMapper = new QSignalMapper( this );
@ -776,17 +777,12 @@ void QgsAttributeTableDialog::on_mFilterTableFields_clicked()
return;
}
QgsOrganizeTableColumnsDialog dialog( mLayer, mVisibleFields );
QgsOrganizeTableColumnsDialog dialog( mLayer );
if ( dialog.exec() == QDialog::Accepted )
{
mVisibleFields = dialog.selectedFields();
const QgsFields layerAttributes = mLayer->fields();
for ( int idx = 0; idx < layerAttributes.count(); ++idx )
{
mMainView->tableView()->setColumnHidden(
idx, !mVisibleFields.contains( layerAttributes[idx].name() ) );
}
QgsAttributeTableConfig config = dialog.config();
mLayer->setAttributeTableConfig( config );
mMainView->setAttributeTableConfig( config );
}
}

View File

@ -79,6 +79,7 @@ SET(QGIS_CORE_SRCS
qgsapplication.cpp
qgsaction.cpp
qgsactionmanager.cpp
qgsattributetableconfig.cpp
qgsbrowsermodel.cpp
qgscachedfeatureiterator.cpp
qgscacheindex.cpp
@ -591,7 +592,7 @@ SET(QGIS_CORE_HDRS
qgis.h
qgsaction.h
qgsactionmanager.h
qgsattributetableconfig.h
qgsattributeaction.h
qgscachedfeatureiterator.h
qgscacheindex.h

View File

@ -23,7 +23,6 @@ email : marco.hugentobler at sourcepole dot com
#include <QString>
class QgsCoordinateTransform;
class QgsMapToPixel;
class QgsCurveV2;
class QgsMultiCurveV2;

View File

@ -0,0 +1,150 @@
/***************************************************************************
qgsattributetableconfig.cpp - QgsAttributeTableConfig
---------------------
begin : 27.4.2016
copyright : (C) 2016 by mku
email : [your-email-here]
***************************************************************************
* *
* 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 "qgsattributetableconfig.h"
#include <QStringList>
QgsAttributeTableConfig::QgsAttributeTableConfig()
{
}
QVector<QgsAttributeTableConfig::ColumnConfig> QgsAttributeTableConfig::columns() const
{
return mColumns;
}
void QgsAttributeTableConfig::setColumns( const QVector<ColumnConfig>& columns )
{
mColumns = columns;
}
void QgsAttributeTableConfig::update( const QgsFields& fields )
{
QStringList columns;
bool containsActionColumn = false;
for ( int i = mColumns.count() - 1; i >= 0; --i )
{
const ColumnConfig& column = mColumns.at( i );
if ( column.mType == Field )
{
if ( fields.fieldNameIndex( column.mName ) == -1 )
{
mColumns.remove( i );
}
else
{
columns.append( column.mName );
}
}
else if ( column.mType == Action )
{
containsActionColumn = true;
}
}
Q_FOREACH ( const QgsField& field, fields )
{
if ( !columns.contains( field.name() ) )
{
ColumnConfig newColumn;
newColumn.mHidden = false;
newColumn.mType = Field;
newColumn.mName = field.name();
mColumns.append( newColumn );
}
}
if ( !containsActionColumn )
{
ColumnConfig actionConfig;
actionConfig.mType = Action;
actionConfig.mHidden = true;
mColumns.append( actionConfig );
}
}
void QgsAttributeTableConfig::readXml( const QDomNode& node )
{
mColumns.clear();
QDomNode configNode = node.namedItem( "attributetableconfig" );
if ( !configNode.isNull() )
{
QDomNode columnsNode = configNode.toElement().namedItem( "columns" );
QDomNodeList columns = columnsNode.childNodes();
for ( int i = 0; i < columns.size(); ++i )
{
QDomElement columnElement = columns.at( i ).toElement();
ColumnConfig column;
if ( columnElement.attribute( "type" ) == "actions" )
{
column.mType = Action;
}
else
{
column.mType = Field;
column.mName = columnElement.attribute( "name" );
}
column.mHidden = columnElement.attribute( "hidden" ) == "1";
mColumns.append( column );
}
}
}
void QgsAttributeTableConfig::writeXml( QDomNode& node ) const
{
QDomDocument doc( node.ownerDocument() );
QDomElement configElement = doc.createElement( "attributetableconfig" );
QDomElement columnsElement = doc.createElement( "columns" );
Q_FOREACH ( const ColumnConfig& column, mColumns )
{
QDomElement columnElement = doc.createElement( "column" );
if ( column.mType == Action )
{
columnElement.setAttribute( "type", "actions" );
}
else
{
columnElement.setAttribute( "type", "field" );
columnElement.setAttribute( "name", column.mName );
}
columnElement.setAttribute( "hidden", column.mHidden );
columnsElement.appendChild( columnElement );
}
configElement.appendChild( columnsElement );
node.appendChild( configElement );
}

View File

@ -0,0 +1,90 @@
/***************************************************************************
qgsattributetableconfig.h - QgsAttributeTableConfig
---------------------
begin : 27.4.2016
copyright : (C) 2016 by Matthias Kuhn
email : matthias@opengis.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. *
* *
***************************************************************************/
#ifndef QGSATTRIBUTETABLECONFIG_H
#define QGSATTRIBUTETABLECONFIG_H
#include <QString>
#include <QVector>
#include <QDomNode>
#include "qgsfield.h"
/**
* This is a container for configuration of the attribute table.
* The configuration is specific for one vector layer.
*/
class CORE_EXPORT QgsAttributeTableConfig
{
public:
/**
* The type of an attribute table column.
*/
enum Type
{
Field, //!< This column represents a field
Action //!< This column represents an action widget
};
/**
* Defines the configuration of a column in the attribute table.
*/
struct ColumnConfig
{
Type mType; //!< The type of this column.
QString mName; //!< The name of the attribute if this column represents a field
bool mHidden; //!< Flag that controls if the column is hidden
};
QgsAttributeTableConfig();
/**
* Get the list with all columns and their configuration.
* The list order defines the order of appearance.
*/
QVector<ColumnConfig> columns() const;
/**
* Set the list of columns visible in the attribute table.
* The list order defines the order of appearance.
*/
void setColumns( const QVector<ColumnConfig>& columns );
/**
* Update the configuration with the given fields.
* Any field which is present in the configuration but not present in the
* parameter fields will be removed. Any field which is in the parameter
* fields but not in the configuration will be appended.
*/
void update( const QgsFields& fields );
/**
* Serialize to XML on layer save
*/
void writeXml( QDomNode& node ) const;
/**
* Deserialize to XML on layer load
*/
void readXml( const QDomNode& node );
private:
QVector<ColumnConfig> mColumns;
};
Q_DECLARE_METATYPE( QgsAttributeTableConfig::ColumnConfig )
#endif // QGSATTRIBUTETABLECONFIG_H

View File

@ -42,7 +42,6 @@ class CORE_EXPORT QgsCoordinateTransformCache
void invalidateCrs( const QString& crsAuthId );
private:
static QgsCoordinateTransformCache* mInstance;
QMultiHash< QPair< QString, QString >, QgsCoordinateTransform* > mTransforms; //same auth_id pairs might have different datum transformations
QgsCoordinateTransformCache();

View File

@ -1926,8 +1926,6 @@ bool QgsVectorLayer::readSymbology( const QDomNode& node, QString& errorMessage
// process the attribute actions
mActions->readXML( node );
mEditFormConfig->readXml( node );
QDomNode annotationFormNode = node.namedItem( "annotationform" );
if ( !annotationFormNode.isNull() )
{
@ -1988,6 +1986,8 @@ bool QgsVectorLayer::readSymbology( const QDomNode& node, QString& errorMessage
mEditFormConfig->readXml( node );
mAttributeTableConfig.readXml( node );
mConditionalStyles->readXml( node );
return true;
@ -2154,6 +2154,8 @@ bool QgsVectorLayer::writeSymbology( QDomNode& node, QDomDocument& doc, QString&
mEditFormConfig->writeXml( node );
mAttributeTableConfig.writeXml( node );
mConditionalStyles->writeXml( node, doc );
return true;
@ -2266,11 +2268,11 @@ void QgsVectorLayer::addAttributeAlias( int attIndex, const QString& aliasString
QString QgsVectorLayer::attributeAlias( int attributeIndex ) const
{
if ( attributeIndex < 0 || attributeIndex >= fields().count() )
return "";
return QString();
QString name = fields().at( attributeIndex ).name();
return mAttributeAliasMap.value( name, "" );
return mAttributeAliasMap.value( name, QString() );
}
QString QgsVectorLayer::attributeDisplayName( int attributeIndex ) const
@ -3591,6 +3593,16 @@ void QgsVectorLayer::readSldLabeling( const QDomNode& node )
}
}
QgsAttributeTableConfig QgsVectorLayer::attributeTableConfig() const
{
return mAttributeTableConfig;
}
void QgsVectorLayer::setAttributeTableConfig( const QgsAttributeTableConfig& attributeTableConfig )
{
mAttributeTableConfig = attributeTableConfig;
}
void QgsVectorLayer::setDiagramLayerSettings( const QgsDiagramLayerSettings& s )
{
if ( !mDiagramLayerSettings )

View File

@ -34,6 +34,7 @@
#include "qgssnapper.h"
#include "qgsvectorsimplifymethod.h"
#include "qgseditformconfig.h"
#include "qgsattributetableconfig.h"
class QPainter;
class QImage;
@ -631,7 +632,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
* The pointer which is returned directly points to the actions object
* which is used by the layer, so any changes are immediately applied.
*/
QgsActionManager *actions() { return mActions; }
QgsActionManager* actions() { return mActions; }
/**
* The number of features that are selected in this layer
@ -1321,7 +1322,12 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
*/
void clearAttributeEditorWidgets() { mEditFormConfig->clearTabs(); }
/** Returns the alias of an attribute name or an empty string if there is no alias */
/**
* Returns the alias of an attribute name or a null string if there is no alias.
*
* @see {@attributeDisplayName( int attributeIndex )} which returns the field name
* if no alias is defined.
*/
QString attributeAlias( int attributeIndex ) const;
/** Convenience function that returns the attribute alias if defined or the field name else */
@ -1686,6 +1692,18 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
*/
QgsConditionalLayerStyles *conditionalStyles() const;
/**
* Get the attribute table configuration object.
* This defines the appearance of the attribute table.
*/
QgsAttributeTableConfig attributeTableConfig() const;
/**
* Set the attribute table configuration object.
* This defines the appearance of the attribute table.
*/
void setAttributeTableConfig( const QgsAttributeTableConfig& attributeTableConfig );
public slots:
/**
* Select feature by its ID
@ -2088,6 +2106,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
QgsFeatureIds mDeletedFids;
QgsAttributeTableConfig mAttributeTableConfig;
friend class QgsVectorLayerFeatureSource;
};

View File

@ -118,29 +118,36 @@ void QgsAttributeTableDelegate::setFeatureSelectionModel( QgsFeatureSelectionMod
mFeatureSelectionModel = featureSelectionModel;
}
void QgsAttributeTableDelegate::setActionWidgetImage( const QImage& image )
{
mActionWidgetImage = image;
}
void QgsAttributeTableDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
QgsFeatureId fid = index.model()->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong();
QgsAttributeTableFilterModel::ColumnType columnType = static_cast<QgsAttributeTableFilterModel::ColumnType>( index.model()->data( index, QgsAttributeTableFilterModel::TypeRole ).toInt() );
QStyleOptionViewItem myOpt = option;
if ( index.model()->data( index, Qt::EditRole ).isNull() )
if ( columnType == QgsAttributeTableFilterModel::ColumnTypeActionButton )
{
myOpt.font.setItalic( true );
myOpt.palette.setColor( QPalette::Text, QColor( "gray" ) );
}
if ( mFeatureSelectionModel && mFeatureSelectionModel->isSelected( fid ) )
myOpt.state |= QStyle::State_Selected;
if ( index.column() == 0 )
{
painter->drawImage( QPoint( 0, 0 ), mActionButtonImage );
QRect r = option.rect.adjusted( -1, 0, 0, 0 );
painter->drawImage( r.x(), r.y(), mActionWidgetImage );
}
else
{
QgsFeatureId fid = index.model()->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong();
QStyleOptionViewItem myOpt = option;
if ( index.model()->data( index, Qt::EditRole ).isNull() )
{
myOpt.font.setItalic( true );
myOpt.palette.setColor( QPalette::Text, QColor( "gray" ) );
}
if ( mFeatureSelectionModel && mFeatureSelectionModel->isSelected( fid ) )
myOpt.state |= QStyle::State_Selected;
QItemDelegate::paint( painter, myOpt, index );
if ( option.state & QStyle::State_HasFocus )

View File

@ -46,7 +46,6 @@ class GUI_EXPORT QgsAttributeTableDelegate : public QItemDelegate
: QItemDelegate( parent )
, mLayer( nullptr )
, mFeatureSelectionModel( nullptr )
, mActionButtonWidget( nullptr )
{
}
@ -80,13 +79,15 @@ class GUI_EXPORT QgsAttributeTableDelegate : public QItemDelegate
void setFeatureSelectionModel( QgsFeatureSelectionModel* featureSelectionModel );
private:
QWidget* createActionWidget( QWidget* parent, QgsVectorLayer* layer ) const;
/**
* Set an image that represents an action widget
*/
void setActionWidgetImage( const QImage& image );
private:
QgsVectorLayer* mLayer;
QgsFeatureSelectionModel* mFeatureSelectionModel;
QWidget* mActionButtonWidget;
QImage mActionButtonImage;
QImage mActionWidgetImage;
};
#endif //QGSATTRIBUTETABLEDELEGATE_H

View File

@ -69,13 +69,93 @@ void QgsAttributeTableFilterModel::sort( int column, Qt::SortOrder order )
QVariant QgsAttributeTableFilterModel::data( const QModelIndex& index, int role ) const
{
if ( index.column() == 0 )
return "Wow";
else
if ( mColumnMapping.at( index.column() ) == -1 ) // actions
{
QModelIndex sourceIndex = QSortFilterProxyModel::index( index.row(), index.column() - 1, index.parent() );
return QSortFilterProxyModel::data( sourceIndex, role );
if ( role == TypeRole )
return ColumnTypeActionButton;
else if ( role == QgsAttributeTableModel::FeatureIdRole )
{
QModelIndex fieldIndex = QSortFilterProxyModel::mapToSource( QSortFilterProxyModel::index( index.row(), 0, index.parent() ) );
return sourceModel()->data( fieldIndex, QgsAttributeTableModel::FeatureIdRole );
}
}
else if ( role == TypeRole )
return ColumnTypeField;
return QSortFilterProxyModel::data( index, role );
}
QVariant QgsAttributeTableFilterModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal )
{
if ( mColumnMapping.at( section ) == -1 && role == Qt::DisplayRole )
return tr( "Actions" );
else
return QSortFilterProxyModel::headerData( section, orientation, role );
}
else
return QSortFilterProxyModel::headerData( section, orientation, role );
}
int QgsAttributeTableFilterModel::actionColumnIndex() const
{
return mColumnMapping.indexOf( -1 );
}
int QgsAttributeTableFilterModel::columnCount( const QModelIndex& parent ) const
{
Q_UNUSED( parent );
return mColumnMapping.count();
}
void QgsAttributeTableFilterModel::setAttributeTableConfig( const QgsAttributeTableConfig& config )
{
int columnIndex = 0;
int configIndex = 0;
bool resetModel = false;
for ( ; configIndex < config.columns().size(); ++configIndex )
{
const QgsAttributeTableConfig::ColumnConfig& columnConfig = config.columns().at( configIndex );
// Hidden? No reason for further checks
if ( columnConfig.mHidden )
continue;
// Do the previous and current definition match?
if ( mColumnMapping.size() > columnIndex )
{
if (( columnConfig.mType == QgsAttributeTableConfig::Action && mColumnMapping.at( columnIndex ) == -1 ) ||
( columnConfig.mType == QgsAttributeTableConfig::Field && mColumnMapping.at( columnIndex ) == layer()->fieldNameIndex( columnConfig.mName ) ) )
{
++columnIndex;
continue;
}
else // There is a mismatch between previous and current configuration: remove all remaining columns, they will be readded
{
mColumnMapping.remove( columnIndex, mColumnMapping.count() - columnIndex );
}
}
if ( ! resetModel )
{
beginResetModel();
resetModel = true;
}
// New column? append
Q_ASSERT( mColumnMapping.size() == columnIndex );
if ( columnConfig.mType == QgsAttributeTableConfig::Action )
mColumnMapping.append( -1 );
else
mColumnMapping.append( layer()->fieldNameIndex( columnConfig.mName ) );
++columnIndex;
}
if ( resetModel )
endResetModel();
}
void QgsAttributeTableFilterModel::setSelectedOnTop( bool selectedOnTop )
@ -96,6 +176,12 @@ void QgsAttributeTableFilterModel::setSourceModel( QgsAttributeTableModel* sourc
{
mTableModel = sourceModel;
mColumnMapping.append( -1 ); // -1 for actions column
for ( int i = 0; i < mTableModel->columnCount(); ++i )
{
mColumnMapping.append( i );
}
QSortFilterProxyModel::setSourceModel( sourceModel );
}
@ -310,14 +396,23 @@ QModelIndexList QgsAttributeTableFilterModel::fidToIndexList( QgsFeatureId fid )
return indexes;
}
QModelIndex QgsAttributeTableFilterModel::mapToMaster( const QModelIndex &proxyIndex ) const
QModelIndex QgsAttributeTableFilterModel::mapToSource( const QModelIndex& proxyIndex ) const
{
// Master is source
return mapToSource( proxyIndex );
if ( !proxyIndex.isValid() )
return QModelIndex();
int sourceColumn = mColumnMapping.at( proxyIndex.column() );
// For the action column there is no matching column in the source model: invalid
if ( sourceColumn == -1 )
return QModelIndex();
return QSortFilterProxyModel::mapToSource( index( proxyIndex.row(), mColumnMapping.at( proxyIndex.column() ), proxyIndex.parent() ) );
}
QModelIndex QgsAttributeTableFilterModel::mapFromMaster( const QModelIndex &sourceIndex ) const
QModelIndex QgsAttributeTableFilterModel::mapFromSource( const QModelIndex& sourceIndex ) const
{
// Master is source
return mapFromSource( sourceIndex );
QModelIndex proxyIndex = QSortFilterProxyModel::mapFromSource( sourceIndex );
return index( proxyIndex.row(), mColumnMapping.indexOf( proxyIndex.column() ), proxyIndex.parent() );
}

View File

@ -42,6 +42,17 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel, pub
ShowEdited
};
enum ColumnType
{
ColumnTypeField, //!< This column shows a field
ColumnTypeActionButton //!< This column shows action buttons
};
enum Role
{
TypeRole = QgsAttributeTableModel::UserRole //!< The type of a given column
};
/**
*
@ -137,11 +148,16 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel, pub
QgsFeatureId rowToId( const QModelIndex& row );
QModelIndex fidToIndex( QgsFeatureId fid ) override;
QModelIndexList fidToIndexList( QgsFeatureId fid );
virtual QModelIndex mapToMaster( const QModelIndex &proxyIndex ) const;
inline QModelIndex mapToMaster( const QModelIndex& proxyIndex ) const { return mapToSource( proxyIndex ); }
virtual QModelIndex mapFromMaster( const QModelIndex &sourceIndex ) const;
inline QModelIndex mapFromMaster( const QModelIndex& sourceIndex ) const { return mapFromSource( sourceIndex ); }
virtual QModelIndex mapToSource( const QModelIndex& proxyIndex ) const override;
virtual QModelIndex mapFromSource( const QModelIndex& sourceIndex ) const override;
/**
* Sort by the given column using the given order.
@ -157,6 +173,23 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel, pub
virtual QVariant data( const QModelIndex& index, int role ) const override;
QVariant headerData( int section, Qt::Orientation orientation, int role ) const override;
/**
* Get the index of the first column that contains an action widget.
* Returns -1 if none is defined.
*/
int actionColumnIndex() const;
int columnCount( const QModelIndex &parent ) const override;
/**
* Set the attribute table configuration to control which fields are shown,
* in which order they are shown as well as if and where an action column
* is shown.
*/
void setAttributeTableConfig( const QgsAttributeTableConfig& config );
protected:
/**
* Returns true if the source row will be accepted
@ -195,6 +228,8 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel, pub
FilterMode mFilterMode;
bool mSelectedOnTop;
QgsAttributeTableModel* mTableModel;
QVector<int> mColumnMapping;
};
#endif

View File

@ -51,9 +51,10 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
public:
enum Role
{
SortRole = Qt::UserRole + 1,
FeatureIdRole = Qt::UserRole + 2,
FieldIndexRole = Qt::UserRole + 3
SortRole = Qt::UserRole + 1, //!< Role used for sorting
FeatureIdRole, //!< Get the feature id of the feature in this row
FieldIndexRole, //!< Get the field index of this column
UserRole //!< Start further roles starting from this role
};
public:

View File

@ -64,14 +64,10 @@ QgsAttributeTableView::QgsAttributeTableView( QWidget *parent )
connect( verticalHeader(), SIGNAL( sectionPressed( int ) ), this, SLOT( selectRow( int ) ) );
connect( verticalHeader(), SIGNAL( sectionEntered( int ) ), this, SLOT( _q_selectRow( int ) ) );
connect( horizontalHeader(), SIGNAL( sectionResized( int, int, int ) ), this, SLOT( columnSizeChanged( int, int, int ) ) );
connect( horizontalHeader(), SIGNAL( sortIndicatorChanged( int, Qt::SortOrder ) ), this, SLOT( showHorizontalSortIndicator() ) );
}
QgsAttributeTableView::~QgsAttributeTableView()
{
delete mActionPopup;
}
bool QgsAttributeTableView::eventFilter( QObject *object, QEvent *event )
{
if ( object == verticalHeader()->viewport() )
@ -123,6 +119,9 @@ void QgsAttributeTableView::setModel( QgsAttributeTableFilterModel* filterModel
connect( mFeatureSelectionModel, SIGNAL( requestRepaint( QModelIndexList ) ), this, SLOT( repaintRequested( QModelIndexList ) ) );
connect( mFeatureSelectionModel, SIGNAL( requestRepaint() ), this, SLOT( repaintRequested() ) );
}
mActionWidget = createActionWidget( 0 );
mActionWidget->setVisible( false );
}
void QgsAttributeTableView::setFeatureSelectionManager( QgsIFeatureSelectionManager* featureSelectionManager )
@ -136,19 +135,35 @@ void QgsAttributeTableView::setFeatureSelectionManager( QgsIFeatureSelectionMana
mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
}
QWidget* QgsAttributeTableView::createActionWidget()
QWidget* QgsAttributeTableView::createActionWidget( QgsFeatureId fid )
{
QToolButton* toolButton = new QToolButton( this );
toolButton->setPopupMode( QToolButton::MenuButtonPopup );
for ( int i = 0; i < mFilterModel->layer()->actions()->size(); ++i )
QgsActionManager* actions = mFilterModel->layer()->actions();
for ( int i = 0; i < actions->size(); ++i )
{
const QgsAction& action = mFilterModel->layer()->actions()->at( i );
const QgsAction& action = actions->at( i );
QAction* act = new QAction( action.icon(), action.shortTitle(), toolButton );
QAction* act = new QAction( action.icon(), action.shortTitle().isEmpty() ? action.name() : action.shortTitle(), toolButton );
act->setToolTip( action.name() );
act->setData( i );
act->setProperty( "fid", fid );
connect( act, SIGNAL( triggered( bool ) ), this, SLOT( actionTriggered() ) );
toolButton->addAction( act );
if ( actions->defaultAction() == i )
toolButton->setDefaultAction( act );
}
if ( !toolButton->actions().isEmpty() && actions->defaultAction() == -1 )
toolButton->setDefaultAction( toolButton->actions().first() );
updateActionImage( toolButton );
return toolButton;
}
@ -176,12 +191,12 @@ void QgsAttributeTableView::mouseReleaseEvent( QMouseEvent *event )
void QgsAttributeTableView::mouseMoveEvent( QMouseEvent *event )
{
QModelIndex index = indexAt( event->pos() );
if ( index.column() == 0 )
if ( index.data( QgsAttributeTableFilterModel::TypeRole ) == QgsAttributeTableFilterModel::ColumnTypeActionButton )
{
Q_ASSERT( index.isValid() );
if ( !indexWidget( index ) )
setIndexWidget( index, createActionWidget() );
setIndexWidget( index, createActionWidget( mFilterModel->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong() ) );
}
setSelectionMode( QAbstractItemView::NoSelection );
@ -246,7 +261,7 @@ void QgsAttributeTableView::contextMenuEvent( QContextMenuEvent* event )
if ( !vlayer )
return;
mActionPopup = new QMenu();
mActionPopup = new QMenu( this );
mActionPopup->addAction( tr( "Select All" ), this, SLOT( selectAll() ), QKeySequence::SelectAll );
@ -318,3 +333,32 @@ void QgsAttributeTableView::showHorizontalSortIndicator()
{
horizontalHeader()->setSortIndicatorShown( true );
}
void QgsAttributeTableView::actionTriggered()
{
QAction* action = qobject_cast<QAction*>( sender() );
QgsFeatureId fid = action->property( "fid" ).toLongLong();
QgsFeature f;
mFilterModel->layerCache()->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f );
mFilterModel->layer()->actions()->doAction( action->data().toInt(), f );
}
void QgsAttributeTableView::columnSizeChanged( int index, int oldWidth, int newWidth )
{
Q_UNUSED( oldWidth )
if ( mFilterModel->actionColumnIndex() == index )
{
mActionWidget->resize( newWidth, mActionWidget->height() );
updateActionImage( mActionWidget );
}
}
void QgsAttributeTableView::updateActionImage( QWidget* widget )
{
QImage image( widget->size(), QImage::Format_ARGB32_Premultiplied );
QPainter painter( &image );
widget->render( &painter );
mTableDelegate->setActionWidgetImage( image );
}

View File

@ -47,7 +47,6 @@ class GUI_EXPORT QgsAttributeTableView : public QTableView
public:
QgsAttributeTableView( QWidget* parent = nullptr );
virtual ~QgsAttributeTableView();
virtual void setModel( QgsAttributeTableFilterModel* filterModel );
@ -140,9 +139,12 @@ class GUI_EXPORT QgsAttributeTableView : public QTableView
private slots:
void modelDeleted();
void showHorizontalSortIndicator();
void actionTriggered();
void columnSizeChanged( int index, int oldWidth, int newWidth );
private:
QWidget* createActionWidget();
void updateActionImage( QWidget* widget );
QWidget* createActionWidget( QgsFeatureId fid );
void selectRow( int row, bool anchor );
QgsAttributeTableModel* mMasterModel;
@ -154,6 +156,7 @@ class GUI_EXPORT QgsAttributeTableView : public QTableView
QMenu *mActionPopup;
int mRowSectionAnchor;
QItemSelectionModel::SelectionFlag mCtrlDragSelectionFlag;
QWidget* mActionWidget;
};
#endif

View File

@ -464,6 +464,11 @@ void QgsDualView::setFeatureSelectionManager( QgsIFeatureSelectionManager* featu
mFeatureSelectionManager = featureSelectionManager;
}
void QgsDualView::setAttributeTableConfig( const QgsAttributeTableConfig& config )
{
mFilterModel->setAttributeTableConfig( config );
}
void QgsDualView::progress( int i, bool& cancel )
{
if ( !mProgressDlg )

View File

@ -141,6 +141,9 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
*/
void setFilteredFeatures( const QgsFeatureIds& filteredFeatures );
/**
* Get a list of currently visible feature ids.
*/
QgsFeatureIds filteredFeatures() { return mFilterModel->filteredFeatures(); }
/**
@ -169,7 +172,13 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
*
* @return The table view
*/
QgsAttributeTableView* tableView() { return mTableView; };
QgsAttributeTableView* tableView() { return mTableView; }
/**
* Set the attribute table config which should be used to control
* the appearance of the attribute table.
*/
void setAttributeTableConfig( const QgsAttributeTableConfig& config );
protected:
/**

View File

@ -41,57 +41,76 @@
#include "qgseditorwidgetregistry.h"
QgsOrganizeTableColumnsDialog::QgsOrganizeTableColumnsDialog( const QgsVectorLayer* vl, const QStringList visble, QWidget *parent, Qt::WindowFlags flags )
QgsOrganizeTableColumnsDialog::QgsOrganizeTableColumnsDialog( const QgsVectorLayer* vl, QWidget* parent, Qt::WindowFlags flags )
: QDialog( parent, flags )
{
setupUi( this );
if ( vl )
{
QgsAttributeTableConfig config = vl->attributeTableConfig();
config.update( vl->fields() );
mFieldsList->clear();
const QgsFields& layerAttributes = vl->fields();
for ( int idx = 0; idx < layerAttributes.count(); ++idx )
Q_FOREACH ( const QgsAttributeTableConfig::ColumnConfig& columnConfig, config.columns() )
{
QListWidgetItem* item = new QListWidgetItem( layerAttributes[idx].name(), mFieldsList );
item->setCheckState( visble.contains( layerAttributes[idx].name() ) ? Qt::Checked : Qt::Unchecked );
switch ( vl->fields().fieldOrigin( idx ) )
QListWidgetItem* item;
if ( columnConfig.mType == QgsAttributeTableConfig::Action )
{
case QgsFields::OriginExpression:
item->setIcon( QgsApplication::getThemeIcon( "/mIconExpression.svg" ) );
break;
item = new QListWidgetItem( tr( "[Action Widget]" ), mFieldsList );
item->setIcon( QgsApplication::getThemeIcon( "/propertyicons/action.svg" ) );
}
else
{
int idx = vl->fieldNameIndex( columnConfig.mName );
item = new QListWidgetItem( vl->attributeDisplayName( idx ), mFieldsList );
case QgsFields::OriginJoin:
item->setIcon( QgsApplication::getThemeIcon( "/propertyicons/join.png" ) );
break;
switch ( vl->fields().fieldOrigin( idx ) )
{
case QgsFields::OriginExpression:
item->setIcon( QgsApplication::getThemeIcon( "/mIconExpression.svg" ) );
break;
default:
item->setIcon( QgsApplication::getThemeIcon( "/propertyicons/attributes.png" ) );
break;
case QgsFields::OriginJoin:
item->setIcon( QgsApplication::getThemeIcon( "/propertyicons/join.png" ) );
break;
default:
item->setIcon( QgsApplication::getThemeIcon( "/propertyicons/attributes.png" ) );
break;
}
}
item->setData( Qt::UserRole, idx );
item->setCheckState( columnConfig.mHidden ? Qt::Unchecked : Qt::Checked );
item->setData( Qt::UserRole, QVariant::fromValue( columnConfig ) );
}
}
QSettings settings;
restoreGeometry( settings.value( "/Windows/QgsFilterTableFieldsDialog/geometry" ).toByteArray() );
restoreGeometry( settings.value( "/Windows/QgsOrganizeTableColumnsDialog/geometry" ).toByteArray() );
}
QgsOrganizeTableColumnsDialog::~QgsOrganizeTableColumnsDialog()
{
QSettings settings;
settings.setValue( "/Windows/QgsFilterTableFieldsDialog/geometry", saveGeometry() );
settings.setValue( "/Windows/QgsOrganizeTableColumnsDialog/geometry", saveGeometry() );
}
QStringList QgsOrganizeTableColumnsDialog::selectedFields() const
QgsAttributeTableConfig QgsOrganizeTableColumnsDialog::config() const
{
QStringList selectionList;
QVector<QgsAttributeTableConfig::ColumnConfig> columns;
for ( int i = 0 ; i < mFieldsList->count() ; i++ )
{
const QListWidgetItem* item = mFieldsList->item( i );
if ( item->checkState() == Qt::Checked )
{
selectionList.push_back( item->text() );
}
QgsAttributeTableConfig::ColumnConfig columnConfig = item->data( Qt::UserRole ).value<QgsAttributeTableConfig::ColumnConfig>();
columnConfig.mHidden = item->checkState() == Qt::Unchecked;
columns.append( columnConfig );
}
return selectionList;
QgsAttributeTableConfig config;
config.setColumns( columns );
return config;
}

View File

@ -21,6 +21,8 @@
#include "ui_qgsorganizetablecolumnsdialog.h"
#include "qgsattributetableconfig.h"
class QgsVectorLayer;
class GUI_EXPORT QgsOrganizeTableColumnsDialog : public QDialog, private Ui::QgsOrganizeTableColumnsDialog
@ -35,17 +37,17 @@ class GUI_EXPORT QgsOrganizeTableColumnsDialog : public QDialog, private Ui::Qgs
* @param parent parent object
* @param flags window flags
*/
QgsOrganizeTableColumnsDialog( const QgsVectorLayer* vl, QStringList visible, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Window );
QgsOrganizeTableColumnsDialog( const QgsVectorLayer* vl, QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::Window );
/**
* Destructor
*/
~QgsOrganizeTableColumnsDialog();
/**
* Get the selected fields name
* @return The selected fields name
* Get the updated configuration
*/
QStringList selectedFields() const;
QgsAttributeTableConfig config() const;
};
#endif

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>240</width>
<height>219</height>
<width>392</width>
<height>357</height>
</rect>
</property>
<property name="windowTitle">
@ -15,7 +15,14 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListWidget" name="mFieldsList"/>
<widget class="QListWidget" name="mFieldsList">
<property name="dragDropMode">
<enum>QAbstractItemView::DragDrop</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">