[wip] Add container, onItemDoubleClick (create 2 subclasses for tree view's base subclass to get ride of type()). Invert selection. Remove selected items. Other cleanups. Drag and drop initial (partial) implementation.

This commit is contained in:
Germán Carrillo 2025-04-01 20:13:57 -05:00
parent 89246fbbb6
commit c96c989add
8 changed files with 1059 additions and 1249 deletions

View File

@ -44,7 +44,7 @@ class GUI_EXPORT QgsAttributeFormContainerEdit : public QWidget, private Ui_QgsA
/**
* Sets up the container type comboBox
* @param isTopLevelContainer Whether the container is allowed to be a tab or not
* \param isTopLevelContainer Whether the container is allowed to be a tab or not
*
* \since QGIS 3.44
*/

View File

@ -22,13 +22,12 @@
#include "qgssettings.h"
#include "qgshelp.h"
#include <QTreeWidgetItem>
#include <QComboBox>
#include <QRadioButton>
QgsAddAttributeFormContainerDialog::QgsAddAttributeFormContainerDialog( QgsVectorLayer *lyr, const QList<ContainerPair> &existingContainerList, QTreeWidgetItem *currentTab, QWidget *parent )
QgsAddAttributeFormContainerDialog::QgsAddAttributeFormContainerDialog( QgsVectorLayer *layer, const QList<ContainerPair> &existingContainerList, QModelIndex &currentNodeIndex, QWidget *parent )
: QDialog( parent )
, mLayer( lyr )
, mLayer( layer )
, mExistingContainers( existingContainerList )
{
setupUi( this );
@ -46,9 +45,9 @@ QgsAddAttributeFormContainerDialog::QgsAddAttributeFormContainerDialog( QgsVecto
for ( const ContainerPair &container : std::as_const( mExistingContainers ) )
{
mParentCombo->addItem( container.first, i );
if ( container.second == currentTab )
if ( currentNodeIndex.isValid() && container.second == currentNodeIndex )
{
mParentCombo->setCurrentIndex( i );
mParentCombo->setCurrentIndex( i + 1 ); // Take empty item into account
mTypeCombo->setCurrentIndex( mTypeCombo->findData( QVariant::fromValue( Qgis::AttributeEditorContainerType::GroupBox ) ) );
}
++i;
@ -70,13 +69,13 @@ QString QgsAddAttributeFormContainerDialog::name()
return mName->text();
}
QTreeWidgetItem *QgsAddAttributeFormContainerDialog::parentContainerItem()
QModelIndex QgsAddAttributeFormContainerDialog::parentContainerNode() const
{
if ( containerType() == Qgis::AttributeEditorContainerType::Tab )
return nullptr;
return QModelIndex();
if ( !mParentCombo->currentData().isValid() )
return nullptr;
return QModelIndex();
const ContainerPair tab = mExistingContainers.at( mParentCombo->currentData().toInt() );
return tab.second;

View File

@ -26,12 +26,11 @@
#include "qgsguiutils.h"
#include "qgis_gui.h"
class QTreeWidgetItem;
class QgsVectorLayer;
/**
* \ingroup gui
* \brief Dialog to add a tab or group of attributes
* \brief Dialog to add a container for attribute widgets
*
* \note This class is not a part of public API
* \since QGIS 3.14
@ -41,13 +40,13 @@ class GUI_EXPORT QgsAddAttributeFormContainerDialog : public QDialog, private Ui
Q_OBJECT
public:
typedef QPair<QString, QTreeWidgetItem *> ContainerPair;
typedef QPair<QString, QModelIndex> ContainerPair;
public:
//! constructor
QgsAddAttributeFormContainerDialog( QgsVectorLayer *lyr, const QList<ContainerPair> &existingContainerList, QTreeWidgetItem *currentTab = nullptr, QWidget *parent = nullptr );
QgsAddAttributeFormContainerDialog( QgsVectorLayer *layer, const QList<ContainerPair> &existingContainerList, QModelIndex &currentNodeIndex, QWidget *parent = nullptr );
//! Returns the name of the tab or group
//! Returns the name of the container
QString name();
/**
@ -55,7 +54,7 @@ class GUI_EXPORT QgsAddAttributeFormContainerDialog : public QDialog, private Ui
*
* Will be NULLPTR when a new tab is created.
*/
QTreeWidgetItem *parentContainerItem();
QModelIndex parentContainerNode() const;
//! Returns the column count
int columnCount() const;

View File

@ -1,3 +1,17 @@
/***************************************************************************
qgsattributesformmodel.cpp
---------------------
begin : March 2025
copyright : (C) 2025 by Germán Carrillo
email : german 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 "qgsactionmanager.h"
#include "qgsattributesformmodel.h"
@ -11,6 +25,8 @@
#include "qgsattributeeditortextelement.h"
#include "qgsattributeeditorspacerelement.h"
#include "QMimeData"
/*
* FieldConfig implementation
*/
@ -201,14 +217,27 @@ AttributesFormTreeNode *AttributesFormTreeNode::firstChildRecursive( const QgsAt
if ( !mChildren.empty() && nodeId.trimmed().isEmpty() )
return nullptr;
// for ( const auto &child : std::as_const( mChildren ) )
// {
// if ( child->childCount() == 0 )
// {
// if ( child->type() == nodeType && child->id() == nodeId )
// return child.get();
// }
// else
// {
// AttributesFormTreeNode *node = child->firstChildRecursive( nodeType, nodeId );
// if ( node )
// return node;
// }
// }
for ( const auto &child : std::as_const( mChildren ) )
{
if ( child->childCount() == 0 )
{
if ( child->type() == nodeType && child->id() == nodeId )
return child.get();
}
else
if ( child->type() == nodeType && child->id() == nodeId )
return child.get();
if ( child->childCount() > 0 )
{
AttributesFormTreeNode *node = child->firstChildRecursive( nodeType, nodeId );
if ( node )
@ -243,12 +272,29 @@ void AttributesFormTreeNode::addChildItem( std::unique_ptr< AttributesFormTreeNo
if ( !item )
return;
Q_ASSERT( !item->mParent );
item->mParent = this;
if ( !item->mParent )
item->mParent = this;
mChildren.push_back( std::move( item ) );
}
void AttributesFormTreeNode::insertChildNode( int position, std::unique_ptr< AttributesFormTreeNode > &&node )
{
if ( position < 0 || position > ( int ) mChildren.size() || !node )
return;
if ( !node->mParent )
node->mParent = this;
mChildren.insert( mChildren.begin() + position, std::move( node ) );
}
void AttributesFormTreeNode::deleteChildAtIndex( int index )
{
if ( index >= 0 && index < ( int ) mChildren.size() )
mChildren.erase( mChildren.begin() + index );
}
void AttributesFormTreeNode::deleteChildren()
{
mChildren.clear();
@ -349,6 +395,46 @@ int QgsAttributesFormModel::columnCount( const QModelIndex &parent ) const
return 1;
}
QStringList QgsAttributesFormModel::mimeTypes() const
{
return QStringList() << QStringLiteral( "application/x-qgsattributetabledesignerelement" );
}
QMimeData *QgsAttributesFormModel::mimeData( const QModelIndexList &indexes ) const
{
if ( indexes.count() == 0 )
return nullptr;
const QStringList types = mimeTypes();
if ( types.isEmpty() )
return nullptr;
QMimeData *data = new QMimeData();
const QString format = types.at( 0 );
QByteArray encoded;
QDataStream stream( &encoded, QIODevice::WriteOnly );
// Sort indexes since their order reflects selection order
QModelIndexList sortedIndexes = indexes;
std::sort( sortedIndexes.begin(), sortedIndexes.end() );
for ( const QModelIndex &index : std::as_const( sortedIndexes ) )
{
if ( index.isValid() )
{
const QString nodeId = index.data( QgsAttributesFormModel::NodeIdRole ).toString();
const QString nodeName = index.data( QgsAttributesFormModel::NodeNameRole ).toString();
int nodeType = index.data( QgsAttributesFormModel::NodeTypeRole ).toInt();
const auto nodeData = index.data( QgsAttributesFormModel::NodeDataRole );
stream << nodeId << nodeType << nodeName << nodeData;
}
}
data->setData( format, encoded );
return data;
}
QModelIndex QgsAttributesFormModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( !hasIndex( row, column, parent ) )
@ -402,6 +488,20 @@ QgsAttributesAvailableWidgetsModel::QgsAttributesAvailableWidgetsModel( QgsVecto
//QgsAttributesAvailableWidgetsModel::~QgsAttributesAvailableWidgetsModel() = default;
Qt::ItemFlags QgsAttributesAvailableWidgetsModel::flags( const QModelIndex &index ) const
{
if ( !index.isValid() )
return Qt::NoItemFlags;
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
AttributesFormTreeNode *node = getItem( index );
if ( node->type() == QgsAttributeFormTreeData::WidgetType )
flags |= Qt::ItemIsDropEnabled;
return flags;
}
QVariant QgsAttributesAvailableWidgetsModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
Q_UNUSED( section )
@ -644,6 +744,20 @@ QVariant QgsAttributesFormLayoutModel::headerData( int section, Qt::Orientation
return orientation == Qt::Horizontal && role == Qt::DisplayRole ? tr( "Form Layout" ) : QVariant {};
}
Qt::ItemFlags QgsAttributesFormLayoutModel::flags( const QModelIndex &index ) const
{
if ( !index.isValid() )
return Qt::NoItemFlags;
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
AttributesFormTreeNode *node = getItem( index );
if ( node->type() == QgsAttributeFormTreeData::WidgetType || node->type() == QgsAttributeFormTreeData::Container )
flags |= Qt::ItemIsDropEnabled;
return flags;
}
void QgsAttributesFormLayoutModel::populate()
{
beginResetModel();
@ -899,3 +1013,170 @@ bool QgsAttributesFormLayoutModel::setData( const QModelIndex &index, const QVar
return result;
}
bool QgsAttributesFormLayoutModel::removeRows( int row, int count, const QModelIndex &parent )
{
if ( row < 0 )
return false;
AttributesFormTreeNode *node = getItem( parent );
if ( row >= node->childCount() )
return false;
beginRemoveRows( parent, row, row + count - 1 );
// for (int i=row+count-1; i==row; i-- )
// {
// node->deleteChildAtIndex( i );
// }
while ( count-- )
node->deleteChildAtIndex( row );
endRemoveRows();
return true;
}
bool QgsAttributesFormLayoutModel::removeRow( int row, const QModelIndex &parent )
{
beginRemoveRows( parent, row, row );
AttributesFormTreeNode *node = getItem( parent );
node->deleteChildAtIndex( row );
endRemoveRows();
return true;
}
Qt::DropActions QgsAttributesFormLayoutModel::supportedDropActions() const
{
return Qt::DropAction::CopyAction | Qt::DropAction::MoveAction;
}
bool QgsAttributesFormLayoutModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
Q_UNUSED( column )
bool bDropSuccessful = false;
int rows = 0;
if ( row == -1 ) // Dropped at invalid index
row = rowCount( parent ); // Let's append the item
if ( action == Qt::IgnoreAction )
{
bDropSuccessful = true;
}
else if ( data->hasFormat( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) ) )
{
QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) );
QDataStream stream( &itemData, QIODevice::ReadOnly );
QgsAttributeFormTreeData::DnDTreeItemData itemElement;
while ( !stream.atEnd() )
{
QString nodeId;
int nodeTypeInt;
QString nodeName;
QgsAttributeFormTreeData::DnDTreeItemData nodeData;
stream >> nodeId >> nodeTypeInt >> nodeName >> nodeData;
const auto nodeType = static_cast< QgsAttributeFormTreeData::AttributeFormTreeItemType >( nodeTypeInt );
insertNode( parent, row + rows, nodeId, nodeType, nodeName, nodeData );
bDropSuccessful = true;
if ( nodeType == QgsAttributeFormTreeData::QmlWidget
|| nodeType == QgsAttributeFormTreeData::HtmlWidget
|| nodeType == QgsAttributeFormTreeData::TextWidget
|| nodeType == QgsAttributeFormTreeData::SpacerWidget )
{
// Emit signal to open their dialogs
}
//QModelIndex addedIndex = index( row + rows, 0, parent );
rows++;
//emit nodeDropped( addedIndex );
}
}
return bDropSuccessful;
}
QList< QgsAddAttributeFormContainerDialog::ContainerPair > QgsAttributesFormLayoutModel::getRecursiveListOfContainers( AttributesFormTreeNode *parent ) const
{
QList< QgsAddAttributeFormContainerDialog::ContainerPair > containerList;
for ( int i = 0; i < parent->childCount(); i++ )
{
AttributesFormTreeNode *child = parent->child( i );
if ( child->type() == QgsAttributeFormTreeData::Container )
{
containerList << QgsAddAttributeFormContainerDialog::ContainerPair( child->name(), createIndex( child->row(), 0, child ) );
}
if ( child->childCount() > 0 )
{
containerList.append( getRecursiveListOfContainers( child ) );
}
}
return containerList;
}
QList< QgsAddAttributeFormContainerDialog::ContainerPair > QgsAttributesFormLayoutModel::getListOfContainers() const
{
return getRecursiveListOfContainers( mRootItem.get() );
}
void QgsAttributesFormLayoutModel::addContainer( QModelIndex &parent, const QString &title, int columnCount, Qgis::AttributeEditorContainerType type )
{
beginInsertRows( parent, rowCount( parent ), rowCount( parent ) );
AttributesFormTreeNode *parentNode = getItem( parent );
std::unique_ptr< AttributesFormTreeNode > containerNode = std::make_unique< AttributesFormTreeNode >( QgsAttributeFormTreeData::Container, title, QString(), parentNode );
QgsAttributeFormTreeData::DnDTreeItemData nodeData;
nodeData.setColumnCount( columnCount );
nodeData.setContainerType( parent.isValid() ? type : Qgis::AttributeEditorContainerType::Tab );
containerNode->setData( QgsAttributesFormModel::NodeDataRole, nodeData );
parentNode->addChildItem( std::move( containerNode ) );
endInsertRows();
}
void QgsAttributesFormLayoutModel::insertNode( const QModelIndex &parent, int row, QString &nodeId, QgsAttributeFormTreeData::AttributeFormTreeItemType nodeType, QString &nodeName, QgsAttributeFormTreeData::DnDTreeItemData nodeData )
{
if ( row < 0 )
return;
beginInsertRows( parent, row, row );
std::unique_ptr< AttributesFormTreeNode > node = std::make_unique< AttributesFormTreeNode >();
node->setData( QgsAttributesFormModel::NodeIdRole, nodeId );
node->setData( QgsAttributesFormModel::NodeTypeRole, nodeType );
node->setData( QgsAttributesFormModel::NodeNameRole, nodeName );
node->setData( QgsAttributesFormModel::NodeDataRole, nodeData );
getItem( parent )->insertChildNode( row, std::move( node ) );
endInsertRows();
}
/*
* Serialization helpers for DesigerTreeItemData so we can stuff this easily into QMimeData
*/
QDataStream &operator<<( QDataStream &stream, const QgsAttributeFormTreeData::DnDTreeItemData &data )
{
QVariant streamData = QVariant::fromValue<QgsAttributeFormTreeData::DnDTreeItemData>( data );
stream << streamData;
return stream;
}
QDataStream &operator>>( QDataStream &stream, QgsAttributeFormTreeData::DnDTreeItemData &data )
{
QVariant streamData;
stream >> streamData;
data = streamData.value< QgsAttributeFormTreeData::DnDTreeItemData >();
return stream;
}

View File

@ -1,6 +1,22 @@
/***************************************************************************
qgsattributesformmodel.h
---------------------
begin : March 2025
copyright : (C) 2025 by Germán Carrillo
email : german 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. *
* *
***************************************************************************/
#ifndef QGSATTRIBUTESFORMMODEL_H
#define QGSATTRIBUTESFORMMODEL_H
#include "qgsaddtaborgroup.h"
#include "qgsattributeeditorelement.h"
#include "qgsoptionalexpression.h"
#include "qgsvectorlayer.h"
@ -322,10 +338,8 @@ class AttributesFormTreeNode
int childCount() const;
//bool insertChildren(int position, int count, int columns);
//bool insertColumns(int position, int columns);
AttributesFormTreeNode *parent() { return mParent; }
//bool removeChildren(int position, int count);
//bool removeColumns(int position, int columns);
int row() const;
QVariant data( int role ) const;
bool setData( int role, const QVariant &value );
@ -335,13 +349,16 @@ class AttributesFormTreeNode
*/
void addChildItem( std::unique_ptr< AttributesFormTreeNode > &&child );
void insertChildNode( int position, std::unique_ptr< AttributesFormTreeNode > &&node );
void deleteChildAtIndex( int index );
/**
* Deletes all child items from this item.
*/
void deleteChildren();
QgsAttributeFormTreeData::AttributeFormTreeItemType type() const { return mNodeType; }
QString id() const { return mNodeId; }
QString name() const { return mName; }
@ -398,6 +415,12 @@ class QgsAttributesFormModel : public QAbstractItemModel
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
// Drag and drop support
//Qt::DropActions supportedDropActions() const override;
QStringList mimeTypes() const override;
QMimeData *mimeData( const QModelIndexList &indexes ) const override;
//bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) override;
//QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
//bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;
@ -435,7 +458,7 @@ class QgsAttributesAvailableWidgetsModel : public QgsAttributesFormModel
//~QgsAttributesAvailableWidgetsModel() override;
// Header:
Qt::ItemFlags flags( const QModelIndex &index ) const override;
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;
// Basic functionality:
@ -448,6 +471,12 @@ class QgsAttributesAvailableWidgetsModel : public QgsAttributesFormModel
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;
// Drag and drop support
//Qt::DropActions supportedDropActions() const override;
//QStringList mimeTypes() const override;
//QMimeData *mimeData( const QModelIndexList &indexes ) const override;
//bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) override;
// Add data:
//bool insertRows( int row, int count, const QModelIndex &parent = QModelIndex() ) override;
//bool insertColumns( int column, int count, const QModelIndex &parent = QModelIndex() ) override;
@ -477,7 +506,7 @@ class QgsAttributesFormLayoutModel : public QgsAttributesFormModel
//~QgsAttributesFormLayoutModel() override;
// Header:
Qt::ItemFlags flags( const QModelIndex &index ) const override;
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;
// Basic functionality:
@ -490,21 +519,38 @@ class QgsAttributesFormLayoutModel : public QgsAttributesFormModel
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;
// Add data:
// Add/remove data:
//bool insertRows( int row, int count, const QModelIndex &parent = QModelIndex() ) override;
//bool insertColumns( int column, int count, const QModelIndex &parent = QModelIndex() ) override;
bool removeRows( int row, int count, const QModelIndex &parent = QModelIndex() ) override;
bool removeRow( int row, const QModelIndex &parent = QModelIndex() );
// Remove data:
//bool removeRows( int row, int count, const QModelIndex &parent = QModelIndex() ) override;
//bool removeColumns( int column, int count, const QModelIndex &parent = QModelIndex() ) override;
// Drag and drop support
Qt::DropActions supportedDropActions() const override;
//QStringList mimeTypes() const override;
//QMimeData *mimeData( const QModelIndexList &indexes ) const override;
bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) override;
// Other methods
//QModelIndex getFieldModelIndex( const QString &fieldName ) const;
QList< QgsAddAttributeFormContainerDialog::ContainerPair > getListOfContainers() const;
/**
* Adds a new container to \a parent.
*
* If no \a parent is set then the container will be forced to be a tab widget.
*/
void addContainer( QModelIndex &parent, const QString &title, int columnCount, Qgis::AttributeEditorContainerType type );
void insertNode( const QModelIndex &parent, int row, QString &nodeId, QgsAttributeFormTreeData::AttributeFormTreeItemType nodeType, QString &nodeName, QgsAttributeFormTreeData::DnDTreeItemData nodeData );
public slots:
void populate() override;
signals:
//! Informs that nodes were inserted (via drop) in the model.
void nodeDropped( QModelIndex &index );
private:
QList< QgsAddAttributeFormContainerDialog::ContainerPair > getRecursiveListOfContainers( AttributesFormTreeNode *parent ) const;
void loadAttributeEditorElementItem( QgsAttributeEditorElement *const editorElement, AttributesFormTreeNode *parent );
//AttributeFormLayoutTreeItem *getItem( const QModelIndex &index ) const;
@ -514,6 +560,10 @@ class QgsAttributesFormLayoutModel : public QgsAttributesFormModel
};
QDataStream &operator<<( QDataStream &stream, const QgsAttributeFormTreeData::DnDTreeItemData &data );
QDataStream &operator>>( QDataStream &stream, QgsAttributeFormTreeData::DnDTreeItemData &data );
Q_DECLARE_METATYPE( QgsAttributeFormTreeData::RelationEditorConfiguration )
Q_DECLARE_METATYPE( QgsAttributeFormTreeData::FieldConfig )
Q_DECLARE_METATYPE( QgsAttributeFormTreeData::DnDTreeItemData )

File diff suppressed because it is too large Load Diff

View File

@ -45,11 +45,10 @@
#include "qgspropertycollection.h"
#include "qgsmessagebar.h"
class QgsAttributesDnDTree;
class QgsAttributeFormContainerEdit;
class QgsAttributeTypeDialog;
class QgsAttributeWidgetEdit;
class QgsAttributesFormTreeView;
class QgsAttributesFormBaseTreeView;
/**
* \ingroup gui
@ -68,320 +67,6 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress
FieldNameRole,
};
struct RelationEditorConfiguration
{
operator QVariant();
QString mRelationWidgetType;
QVariantMap mRelationWidgetConfig;
QVariant nmRelationId;
bool forceSuppressFormPopup = false;
QString label;
};
struct QmlElementEditorConfiguration
{
QString qmlCode;
};
struct HtmlElementEditorConfiguration
{
QString htmlCode;
};
struct TextElementEditorConfiguration
{
QString text;
};
struct SpacerElementEditorConfiguration
{
bool drawLine = false;
};
/**
* \ingroup gui
* \class DnDTreeItemData
* \brief A tree widget item containing drag-and-drop form designer elements.
*/
class DnDTreeItemData : public QTreeWidgetItem
{
public:
enum Type
{
Field,
Relation,
Container, //!< Container for the form
QmlWidget,
HtmlWidget,
WidgetType, //!< In the widget tree, the type of widget
Action, //!< Layer action
TextWidget, //!< Text widget type, \since QGIS 3.30
SpacerWidget, //!< Spacer widget type, \since QGIS 3.30
};
//do we need that
DnDTreeItemData() = default;
DnDTreeItemData( Type type, const QString &name, const QString &displayName, const QColor &backgroundColor = QColor() )
: mType( type )
, mName( name )
, mDisplayName( displayName )
, mBackgroundColor( backgroundColor )
{}
QString name() const { return mName; }
void setName( const QString &name ) { mName = name; }
QString displayName() const { return mDisplayName; }
void setDisplayName( const QString &displayName ) { mDisplayName = displayName; }
Type type() const { return mType; }
void setType( Type type ) { mType = type; }
operator QVariant() { return QVariant::fromValue<DnDTreeItemData>( *this ); }
int columnCount() const { return mColumnCount; }
void setColumnCount( int count ) { mColumnCount = count; }
/**
* Returns the container type.
*
* \see setContainerType()
* \since QGIS 3.32
*/
Qgis::AttributeEditorContainerType containerType() const;
/**
* Sets the container type.
*
* \see containerType()
* \since QGIS 3.32
*/
void setContainerType( Qgis::AttributeEditorContainerType type );
/**
* For group box containers returns if this group box is collapsed.
*
* \returns TRUE if the group box is collapsed, FALSE otherwise.
* \see collapsed()
* \see setCollapsed()
* \since QGIS 3.26
*/
bool collapsed() const { return mCollapsed; };
/**
* For group box containers sets if this group box is \a collapsed.
*
* \see collapsed()
* \see setCollapsed()
* \since QGIS 3.26
*/
void setCollapsed( bool collapsed ) { mCollapsed = collapsed; };
/**
* Returns the label style.
* \see setLabelStyle()
* \since QGIS 3.26
*/
const QgsAttributeEditorElement::LabelStyle labelStyle() const;
/**
* Sets the label style to \a labelStyle.
* \see labelStyle()
* \since QGIS 3.26
*/
void setLabelStyle( const QgsAttributeEditorElement::LabelStyle &labelStyle );
bool showLabel() const;
void setShowLabel( bool showLabel );
/**
* Returns the horizontal stretch factor for the element.
*
* \see setHorizontalStretch()
* \see verticalStretch()
*
* \since QGIS 3.32
*/
int horizontalStretch() const { return mHorizontalStretch; }
/**
* Sets the horizontal \a stretch factor for the element.
*
* \see horizontalStretch()
* \see setVerticalStretch()
*
* \since QGIS 3.32
*/
void setHorizontalStretch( int stretch ) { mHorizontalStretch = stretch; }
/**
* Returns the vertical stretch factor for the element.
*
* \see setVerticalStretch()
* \see horizontalStretch()
*
* \since QGIS 3.32
*/
int verticalStretch() const { return mVerticalStretch; }
/**
* Sets the vertical \a stretch factor for the element.
*
* \see verticalStretch()
* \see setHorizontalStretch()
*
* \since QGIS 3.32
*/
void setVerticalStretch( int stretch ) { mVerticalStretch = stretch; }
QgsOptionalExpression visibilityExpression() const;
/**
* Sets the optional \a visibilityExpression that dynamically controls the visibility status of a container.
*
* \see visibilityExpression()
* \since QGIS 3.26
*/
void setVisibilityExpression( const QgsOptionalExpression &visibilityExpression );
/**
* Returns the optional expression that dynamically controls the collapsed status of a group box container.
*
* \see collapsed()
* \see setCollapsed()
* \see setCollapsedExpression()
* \since QGIS 3.26
*/
QgsOptionalExpression collapsedExpression() const;
/**
* Sets the optional \a collapsedExpression that dynamically controls the collapsed status of a group box container.
*
* \see collapsed()
* \see setCollapsed()
* \see collapsedExpression()
* \since QGIS 3.26
*/
void setCollapsedExpression( const QgsOptionalExpression &collapsedExpression );
/**
* Returns the relation editor configuration.
*
* \see setRelationEditorConfiguration()
*/
RelationEditorConfiguration relationEditorConfiguration() const;
/**
* Sets the relation editor configuration.
*
* \see relationEditorConfiguration()
*/
void setRelationEditorConfiguration( const RelationEditorConfiguration &relationEditorConfiguration );
/**
* Returns the QML editor configuration.
*
* \see setQmlElementEditorConfiguration()
*/
QmlElementEditorConfiguration qmlElementEditorConfiguration() const;
/**
* Sets the QML editor configuration.
*
* \see qmlElementEditorConfiguration()
*/
void setQmlElementEditorConfiguration( const QmlElementEditorConfiguration &qmlElementEditorConfiguration );
/**
* Returns the HTML editor configuration.
*
* \see setHtmlElementEditorConfiguration()
*/
HtmlElementEditorConfiguration htmlElementEditorConfiguration() const;
/**
* Sets the HTML editor configuration.
*
* \see htmlElementEditorConfiguration()
*/
void setHtmlElementEditorConfiguration( const HtmlElementEditorConfiguration &htmlElementEditorConfiguration );
/**
* Returns the spacer element configuration
* \since QGIS 3.30
*/
SpacerElementEditorConfiguration spacerElementEditorConfiguration() const;
/**
* Sets the the spacer element configuration to \a spacerElementEditorConfiguration
* \since QGIS 3.30
*/
void setSpacerElementEditorConfiguration( SpacerElementEditorConfiguration spacerElementEditorConfiguration );
QColor backgroundColor() const;
void setBackgroundColor( const QColor &backgroundColor );
/**
* Returns the editor configuration for text element.
* \since QGIS 3.30
*/
TextElementEditorConfiguration textElementEditorConfiguration() const;
/**
* Sets the editor configuration for text element to \a textElementEditorConfiguration.
* \since QGIS 3.30
*/
void setTextElementEditorConfiguration( const TextElementEditorConfiguration &textElementEditorConfiguration );
private:
Type mType = Field;
QString mName;
QString mDisplayName;
int mColumnCount = 1;
Qgis::AttributeEditorContainerType mContainerType = Qgis::AttributeEditorContainerType::Tab;
bool mShowLabel = true;
int mHorizontalStretch = 0;
int mVerticalStretch = 0;
QgsOptionalExpression mVisibilityExpression;
RelationEditorConfiguration mRelationEditorConfiguration;
QmlElementEditorConfiguration mQmlElementEditorConfiguration;
HtmlElementEditorConfiguration mHtmlElementEditorConfiguration;
TextElementEditorConfiguration mTextElementEditorConfiguration;
SpacerElementEditorConfiguration mSpacerElementEditorConfiguration;
QColor mBackgroundColor;
bool mCollapsed = false;
QgsOptionalExpression mCollapsedExpression;
QgsAttributeEditorElement::LabelStyle mLabelStyle;
};
/**
* Holds the configuration for a field
*/
struct FieldConfig
{
FieldConfig() = default;
FieldConfig( QgsVectorLayer *layer, int idx );
bool mEditable = true;
bool mLabelOnTop = false;
bool mReuseLastValues = false;
QgsFieldConstraints mFieldConstraints;
QPushButton *mButton = nullptr;
QString mEditorWidgetType;
QMap<QString, QVariant> mEditorWidgetConfig;
QString mAlias;
QgsPropertyCollection mDataDefinedProperties;
QString mComment;
Qgis::FieldDomainSplitPolicy mSplitPolicy = Qgis::FieldDomainSplitPolicy::Duplicate;
Qgis::FieldDuplicatePolicy mDuplicatePolicy = Qgis::FieldDuplicatePolicy::Duplicate;
Qgis::FieldDomainMergePolicy mMergePolicy = Qgis::FieldDomainMergePolicy::DefaultValue;
operator QVariant();
};
public:
explicit QgsAttributesFormProperties( QgsVectorLayer *layer, QWidget *parent = nullptr );
@ -419,11 +104,8 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress
//QList<QgsRelation> mRelations;
QgsVectorLayer *mLayer = nullptr;
QgsAttributesDnDTree *mAvailableWidgetsTree = nullptr;
QgsAttributesDnDTree *mFormLayoutTree = nullptr;
QgsAttributesFormTreeView *mAvailableWidgetsTreeView = nullptr;
QgsAttributesFormTreeView *mFormLayoutTreeView = nullptr;
QgsAttributesFormBaseTreeView *mAvailableWidgetsTreeView = nullptr;
QgsAttributesFormBaseTreeView *mFormLayoutTreeView = nullptr;
QgsAttributeWidgetEdit *mAttributeWidgetEdit = nullptr;
QgsAttributeTypeDialog *mAttributeTypeDialog = nullptr;
@ -438,7 +120,7 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress
void mTbInitCode_clicked();
void onInvertSelectionButtonClicked( bool checked );
void loadAttributeSpecificEditor( QgsAttributesFormTreeView *emitter, QgsAttributesFormTreeView *receiver, QModelIndex &deselectedFormLayoutIndex );
void loadAttributeSpecificEditor( QgsAttributesFormBaseTreeView *emitter, QgsAttributesFormBaseTreeView *receiver, QModelIndex &deselectedFormLayoutIndex );
void onAttributeSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected );
void onFormLayoutSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected );
@ -448,7 +130,7 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress
void updatedFields();
private:
QModelIndex getPreviousIndex( const QgsAttributesFormTreeView *treeView, const QItemSelection &deselected ) const;
QModelIndex getPreviousIndex( const QgsAttributesFormBaseTreeView *treeView, const QItemSelection &deselected ) const;
//! this will clean the right panel
void clearAttributeTypeFrame();
@ -472,8 +154,6 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress
void copyWidgetConfiguration();
void pasteWidgetConfiguration();
QTreeWidgetItem *loadAttributeEditorTreeItem( QgsAttributeEditorElement *widgetDef, QTreeWidgetItem *parent, QgsAttributesDnDTree *tree );
QgsAttributesAvailableWidgetsModel *mAvailableWidgetsModel;
QgsAttributesFormLayoutModel *mFormLayoutModel;
@ -494,133 +174,71 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress
};
//QDataStream &operator<<( QDataStream &stream, const QgsAttributesFormProperties::DnDTreeItemData &data );
//QDataStream &operator>>( QDataStream &stream, QgsAttributesFormProperties::DnDTreeItemData &data );
/**
* \ingroup gui
* \class QgsAttributesDnDTree
* \class QgsAttributesFormBaseTreeView
*
* \brief Overrides mime type handling to be able to work with
* the drag and drop attribute editor.
*
* The mime type is application/x-qgsattributetablefield
*
* Graphical representation for the attribute editor drag and drop editor
* Graphical representation for the attribute drag and drop editor
*/
class GUI_EXPORT QgsAttributesDnDTree : public QTreeWidget, private QgsExpressionContextGenerator
class GUI_EXPORT QgsAttributesFormBaseTreeView : public QTreeView, protected QgsExpressionContextGenerator
{
Q_OBJECT
public:
explicit QgsAttributesDnDTree( QgsVectorLayer *layer, QWidget *parent = nullptr );
/**
* Adds a new item to a \a parent. If \a index is -1, the item is added to the end of the parent's existing children.
* Otherwise it is inserted at the specified \a index.
*/
//QTreeWidgetItem *addItem( QTreeWidgetItem *parent, const QgsAttributesFormProperties::DnDTreeItemData &data, int index = -1, const QIcon &icon = QIcon() );
/**
* Adds a new container to \a parent.
*
* If no \a parent is set then the container will be forced to a tab widget.
*/
QTreeWidgetItem *addContainer( QTreeWidgetItem *parent, const QString &title, int columnCount, Qgis::AttributeEditorContainerType type );
enum Type
{
Drag,
Drop
};
Type type() const;
void setType( QgsAttributesDnDTree::Type value );
public slots:
void selectFirstMatchingItem( const QgsAttributeFormTreeData::DnDTreeItemData &data );
protected:
void dragMoveEvent( QDragMoveEvent *event ) override;
void dropEvent( QDropEvent *event ) override;
bool dropMimeData( QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action ) override;
// QTreeWidget interface
protected:
QStringList mimeTypes() const override;
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
QMimeData *mimeData( const QList<QTreeWidgetItem *> items ) const override;
#else
QMimeData *mimeData( const QList<QTreeWidgetItem *> &items ) const override;
#endif
private slots:
void onItemDoubleClicked( QTreeWidgetItem *item, int column );
private:
QgsVectorLayer *mLayer = nullptr;
Type mType = QgsAttributesDnDTree::Type::Drag;
explicit QgsAttributesFormBaseTreeView( QgsVectorLayer *layer, QWidget *parent = nullptr );
// QgsExpressionContextGenerator interface
public:
QgsExpressionContext createExpressionContext() const override;
};
class GUI_EXPORT QgsAttributesFormTreeView : public QTreeView, private QgsExpressionContextGenerator
{
Q_OBJECT
public:
explicit QgsAttributesFormTreeView( QgsVectorLayer *layer, QWidget *parent = nullptr );
/**
* Adds a new item to a \a parent. If \a index is -1, the item is added to the end of the parent's existing children.
* Otherwise it is inserted at the specified \a index.
*/
//QTreeWidgetItem *addItem( QTreeWidgetItem *parent, const QgsAttributesFormProperties::DnDTreeItemData &data, int index = -1, const QIcon &icon = QIcon() );
enum Type
{
Drag,
Drop
};
Type type() const;
void setType( QgsAttributesDnDTree::Type value );
public slots:
void selectFirstMatchingItem( const QgsAttributeFormTreeData::AttributeFormTreeItemType &nodeType, const QString &nodeId );
// protected:
// void dragMoveEvent( QDragMoveEvent *event ) override;
// void dropEvent( QDropEvent *event ) override;
// bool dropMimeData( QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action ) override;
protected:
QgsVectorLayer *mLayer = nullptr;
};
// QTreeWidget interface
// protected:
// QStringList mimeTypes() const override;
class GUI_EXPORT QgsAttributesAvailableWidgetsTreeView : public QgsAttributesFormBaseTreeView
{
Q_OBJECT
// #if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
// QMimeData *mimeData( const QList<QTreeWidgetItem *> items ) const override;
// #else
// QMimeData *mimeData( const QList<QTreeWidgetItem *> &items ) const override;
// #endif
public:
explicit QgsAttributesAvailableWidgetsTreeView( QgsVectorLayer *layer, QWidget *parent = nullptr );
//! Overridden setModel() from base class. Only QgsAttributesAvailableWidgetsModel is an acceptable model.
void setModel( QAbstractItemModel *model ) override;
//! Gets access to the QgsAttributesAvailableWidgetsModel model
QgsAttributesAvailableWidgetsModel *availableWidgetsModel() const;
private:
QgsAttributesAvailableWidgetsModel *mModel;
};
class GUI_EXPORT QgsAttributesFormLayoutTreeView : public QgsAttributesFormBaseTreeView
{
Q_OBJECT
public:
explicit QgsAttributesFormLayoutTreeView( QgsVectorLayer *layer, QWidget *parent = nullptr );
//! Overridden setModel() from base class. Only QgsAttributesFormLayoutModel is an acceptable model.
void setModel( QAbstractItemModel *model ) override;
//! Gets access to the QgsAttributesFormLayoutModel model
QgsAttributesFormLayoutModel *formLayoutModel() const;
protected:
void dragEnterEvent( QDragEnterEvent *event ) override;
void dragMoveEvent( QDragMoveEvent *event ) override;
void dropEvent( QDropEvent *event ) override;
private slots:
void onItemDoubleClicked( const QModelIndex &index );
void selectDroppedNode( QModelIndex &index );
private:
QgsVectorLayer *mLayer = nullptr;
// Type mType = QgsAttributesDnDTree::Type::Drag;
// QgsExpressionContextGenerator interface
public:
QgsExpressionContext createExpressionContext() const override;
QgsAttributesFormLayoutModel *mModel;
};
#endif // QGSATTRIBUTESFORMPROPERTIES_H

View File

@ -63,26 +63,26 @@ void TestQgsAttributesFormProperties::testConfigStored()
attributeFormProperties.init();
// Get the fields
QVERIFY( attributeFormProperties.mAvailableWidgetsTree );
QTreeWidgetItem *fieldsItem = attributeFormProperties.mAvailableWidgetsTree->topLevelItem( 0 );
QVERIFY( fieldsItem );
QCOMPARE( fieldsItem->text( 0 ), QStringLiteral( "Fields" ) );
QCOMPARE( fieldsItem->childCount(), 2 );
// QVERIFY( attributeFormProperties.mAvailableWidgetsTree );
// QTreeWidgetItem *fieldsItem = attributeFormProperties.mAvailableWidgetsTree->topLevelItem( 0 );
// QVERIFY( fieldsItem );
// QCOMPARE( fieldsItem->text( 0 ), QStringLiteral( "Fields" ) );
// QCOMPARE( fieldsItem->childCount(), 2 );
// Insure that the configuration was stored when switching from one available widgets tree item to another
attributeFormProperties.mAvailableWidgetsTree->setCurrentItem( fieldsItem->child( 0 ) );
QVERIFY( attributeFormProperties.mAttributeTypeDialog );
attributeFormProperties.mAttributeTypeDialog->setAlias( QStringLiteral( "alias0" ) );
attributeFormProperties.mAvailableWidgetsTree->setCurrentItem( fieldsItem->child( 1 ) );
QVERIFY( attributeFormProperties.mAttributeTypeDialog );
attributeFormProperties.mAttributeTypeDialog->setAlias( QStringLiteral( "alias1" ) );
// // Insure that the configuration was stored when switching from one available widgets tree item to another
// attributeFormProperties.mAvailableWidgetsTree->setCurrentItem( fieldsItem->child( 0 ) );
// QVERIFY( attributeFormProperties.mAttributeTypeDialog );
// attributeFormProperties.mAttributeTypeDialog->setAlias( QStringLiteral( "alias0" ) );
// attributeFormProperties.mAvailableWidgetsTree->setCurrentItem( fieldsItem->child( 1 ) );
// QVERIFY( attributeFormProperties.mAttributeTypeDialog );
// attributeFormProperties.mAttributeTypeDialog->setAlias( QStringLiteral( "alias1" ) );
attributeFormProperties.mAvailableWidgetsTree->setCurrentItem( fieldsItem->child( 0 ) );
QVERIFY( attributeFormProperties.mAttributeTypeDialog );
QCOMPARE( attributeFormProperties.mAttributeTypeDialog->alias(), QStringLiteral( "alias0" ) );
attributeFormProperties.mAvailableWidgetsTree->setCurrentItem( fieldsItem->child( 1 ) );
QVERIFY( attributeFormProperties.mAttributeTypeDialog );
QCOMPARE( attributeFormProperties.mAttributeTypeDialog->alias(), QStringLiteral( "alias1" ) );
// attributeFormProperties.mAvailableWidgetsTree->setCurrentItem( fieldsItem->child( 0 ) );
// QVERIFY( attributeFormProperties.mAttributeTypeDialog );
// QCOMPARE( attributeFormProperties.mAttributeTypeDialog->alias(), QStringLiteral( "alias0" ) );
// attributeFormProperties.mAvailableWidgetsTree->setCurrentItem( fieldsItem->child( 1 ) );
// QVERIFY( attributeFormProperties.mAttributeTypeDialog );
// QCOMPARE( attributeFormProperties.mAttributeTypeDialog->alias(), QStringLiteral( "alias1" ) );
}
QGSTEST_MAIN( TestQgsAttributesFormProperties )