Skip to content
Permalink
Browse files

[feature] Allow using multiple columns in attribute forms

When using the drag and drop designer, a user can specify over how many
columns the fields should be distributed.

A double click on an existing group will allow adapting the value.
  • Loading branch information
m-kuhn committed Apr 1, 2016
1 parent 9c96be1 commit faf6b2654cf4887e093f4beb4cd6121fa78b4151
@@ -23,6 +23,7 @@

#include <QTreeWidgetItem>
#include <QComboBox>
#include <QRadioButton>

QgsAddTabOrGroup::QgsAddTabOrGroup( QgsVectorLayer *lyr, const QList < TabPair >& tabList, QWidget * parent )
: QDialog( parent )
@@ -50,6 +51,8 @@ QgsAddTabOrGroup::QgsAddTabOrGroup( QgsVectorLayer *lyr, const QList < TabPair >
connect( mTabButton, SIGNAL( toggled( bool ) ), this, SLOT( on_mTabButton_toggled( bool ) ) );
connect( mGroupButton, SIGNAL( toggled( bool ) ), this, SLOT( on_mGroupButton_toggled( bool ) ) );

mColumnCountSpinBox->setValue( QSettings().value( "/qgis/attributeForm/defaultTabColumnCount", 1 ).toInt() );

setWindowTitle( tr( "Add tab or group for %1" ).arg( mLayer->name() ) );
} // QgsVectorLayerProperties ctor

@@ -68,17 +71,46 @@ QTreeWidgetItem* QgsAddTabOrGroup::tab()
return tab.second;
}

int QgsAddTabOrGroup::columnCount() const
{
return mColumnCountSpinBox->value();
}

bool QgsAddTabOrGroup::tabButtonIsChecked()
{
return mTabButton->isChecked();
}

void QgsAddTabOrGroup::accept()
{
if ( mColumnCountSpinBox->value() > 0 )
{
if ( mGroupButton->isChecked() )
{
QSettings().setValue( "/qgis/attributeForm/defaultGroupColumnCount", mColumnCountSpinBox->value() );
}
else
{
QSettings().setValue( "/qgis/attributeForm/defaultTabColumnCount", mColumnCountSpinBox->value() );
}
}

QDialog::accept();
}

void QgsAddTabOrGroup::on_mGroupButton_toggled( bool checked )
{
mTabList->setEnabled( checked );

if ( checked )
{
mColumnCountSpinBox->setValue( QSettings().value( "/qgis/attributeForm/defaultGroupColumnCount", 1 ).toInt() );
}
}

void QgsAddTabOrGroup::on_mTabButton_toggled( bool checked )
{
mTabList->setEnabled( !checked );
if ( checked )
mColumnCountSpinBox->setValue( QSettings().value( "/qgis/attributeForm/defaultTabColumnCount", 1 ).toInt() );
}
@@ -40,9 +40,13 @@ class APP_EXPORT QgsAddTabOrGroup : public QDialog, private Ui::QgsAddTabOrGroup

QTreeWidgetItem* tab();

int columnCount() const;

bool tabButtonIsChecked();

public slots:
virtual void accept() override;

private slots:
void on_mGroupButton_toggled( bool checked );
void on_mTabButton_toggled( bool checked );

@@ -39,6 +39,7 @@
#include <QSettings>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QFormLayout>

QgsFieldsProperties::QgsFieldsProperties( QgsVectorLayer *layer, QWidget* parent )
: QWidget( parent )
@@ -169,13 +170,14 @@ QTreeWidgetItem *QgsFieldsProperties::loadAttributeEditorTreeItem( QgsAttributeE

case QgsAttributeEditorElement::AeTypeContainer:
{
newWidget = mDesignerTree->addItem( parent, DesignerTreeItemData( DesignerTreeItemData::Container, widgetDef->name() ) );
DesignerTreeItemData itemData( DesignerTreeItemData::Container, widgetDef->name() );

const QgsAttributeEditorContainer* container = dynamic_cast<const QgsAttributeEditorContainer*>( widgetDef );
if ( !container )
{
break;
}

itemData.setColumnCount( container->columnCount() );
newWidget = mDesignerTree->addItem( parent, itemData );

Q_FOREACH ( QgsAttributeEditorElement* wdg, container->children() )
{
@@ -425,12 +427,12 @@ void QgsFieldsProperties::on_mAddTabOrGroupButton_clicked()
QString name = addTabOrGroup.name();
if ( addTabOrGroup.tabButtonIsChecked() )
{
mDesignerTree->addContainer( mDesignerTree->invisibleRootItem(), name );
mDesignerTree->addContainer( mDesignerTree->invisibleRootItem(), name, addTabOrGroup.columnCount() );
}
else
{
QTreeWidgetItem* tabItem = addTabOrGroup.tab();
mDesignerTree->addContainer( tabItem, name );
mDesignerTree->addContainer( tabItem, name, addTabOrGroup.columnCount() );
}
}

@@ -830,6 +832,7 @@ QgsAttributeEditorElement* QgsFieldsProperties::createAttributeEditorWidget( QTr
case DesignerTreeItemData::Container:
{
QgsAttributeEditorContainer* container = new QgsAttributeEditorContainer( item->text( 0 ), parent );
container->setColumnCount( itemData.columnCount() );

for ( int t = 0; t < item->childCount(); t++ )
{
@@ -919,8 +922,6 @@ void QgsFieldsProperties::apply()
}
}



// tabs and groups
mLayer->clearAttributeEditorWidgets();
for ( int t = 0; t < mDesignerTree->invisibleRootItem()->childCount(); t++ )
@@ -1032,24 +1033,32 @@ QMimeData* DragList::mimeData( const QList<QTableWidgetItem*> items ) const
* DesignerTree implementation
*/

QTreeWidgetItem* DesignerTree::addContainer( QTreeWidgetItem* parent, const QString& title )
QTreeWidgetItem* DesignerTree::addContainer( QTreeWidgetItem* parent, const QString& title, int columnCount )
{
QTreeWidgetItem *newItem = new QTreeWidgetItem( QStringList() << title );
newItem->setBackground( 0, QBrush( Qt::lightGray ) );
newItem->setFlags( Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled );
newItem->setData( 0, QgsFieldsProperties::DesignerTreeRole, QgsFieldsProperties::DesignerTreeItemData( QgsFieldsProperties::DesignerTreeItemData::Container, title ).asQVariant() );
newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled );
QgsFieldsProperties::DesignerTreeItemData itemData( QgsFieldsProperties::DesignerTreeItemData::Container, title );
itemData.setColumnCount( columnCount );
newItem->setData( 0, QgsFieldsProperties::DesignerTreeRole, itemData.asQVariant() );
parent->addChild( newItem );
newItem->setExpanded( true );
return newItem;
}

DesignerTree::DesignerTree( QWidget* parent )
: QTreeWidget( parent )
{
connect( this, SIGNAL( itemDoubleClicked( QTreeWidgetItem*, int ) ), this, SLOT( onItemDoubleClicked( QTreeWidgetItem*, int ) ) );
}

QTreeWidgetItem* DesignerTree::addItem( QTreeWidgetItem* parent, QgsFieldsProperties::DesignerTreeItemData data )
{
QTreeWidgetItem* newItem = new QTreeWidgetItem( QStringList() << data.name() );
newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
if ( data.type() == QgsFieldsProperties::DesignerTreeItemData::Container )
{
newItem->setFlags( Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled );
newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled );
newItem->setBackground( 0, QBrush( Qt::lightGray ) );

#if 0
@@ -1198,6 +1207,44 @@ QMimeData* DesignerTree::mimeData( const QList<QTreeWidgetItem*> items ) const
return data;
}

void DesignerTree::onItemDoubleClicked( QTreeWidgetItem* item, int column )
{
Q_UNUSED( column )
QgsFieldsProperties::DesignerTreeItemData itemData = item->data( 0, QgsFieldsProperties::DesignerTreeRole ).value<QgsFieldsProperties::DesignerTreeItemData>();

if ( itemData.type() == QgsFieldsProperties::DesignerTreeItemData::Container )
{
QDialog dlg;
dlg.setWindowTitle( tr( "Configure container" ) );
QFormLayout* layout = new QFormLayout() ;
dlg.setLayout( layout );

QLineEdit* title = new QLineEdit( itemData.name() );
QSpinBox* columnCount = new QSpinBox();
columnCount->setRange( 1, 5 );
columnCount->setValue( itemData.columnCount() );

layout->addRow( tr( "Title" ), title );
layout->addRow( tr( "Column count" ), columnCount );

QDialogButtonBox* buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok
| QDialogButtonBox::Cancel );

connect( buttonBox, SIGNAL( accepted() ), &dlg, SLOT( accept() ) );
connect( buttonBox, SIGNAL( rejected() ), &dlg, SLOT( reject() ) );

layout->addWidget( buttonBox );

if ( dlg.exec() )
{
itemData.setColumnCount( columnCount->value() );
itemData.setName( title->text() );
item->setData( 0, QgsFieldsProperties::DesignerTreeRole, itemData.asQVariant() );
item->setText( 0, title->text() );
}
}
}

/*
* Serialization helpers for DesigerTreeItemData so we can stuff this easily into QMimeData
*/
@@ -21,6 +21,7 @@
#include <QTableWidget>
#include <QTreeWidget>
#include <QWidget>
#include <QSpinBox>


#include "qgsvectorlayer.h"
@@ -68,9 +69,13 @@ class APP_EXPORT QgsFieldsProperties : public QWidget, private Ui_QgsFieldsPrope

QVariant asQVariant() { return QVariant::fromValue<DesignerTreeItemData>( *this ); }

protected:
int columnCount() const { return mColumnCount; }
void setColumnCount( int count ) { mColumnCount = count; }

private:
Type mType;
QString mName;
int mColumnCount;
};

/**
@@ -244,11 +249,9 @@ class DesignerTree : public QTreeWidget
Q_OBJECT

public:
explicit DesignerTree( QWidget* parent = nullptr )
: QTreeWidget( parent )
{}
explicit DesignerTree( QWidget* parent = nullptr );
QTreeWidgetItem* addItem( QTreeWidgetItem* parent, QgsFieldsProperties::DesignerTreeItemData data );
QTreeWidgetItem* addContainer( QTreeWidgetItem* parent, const QString& title );
QTreeWidgetItem* addContainer( QTreeWidgetItem* parent, const QString& title , int columnCount );

protected:
virtual void dragMoveEvent( QDragMoveEvent *event ) override;
@@ -260,6 +263,9 @@ class DesignerTree : public QTreeWidget
protected:
virtual QStringList mimeTypes() const override;
virtual QMimeData* mimeData( const QList<QTreeWidgetItem*> items ) const override;

private slots:
void onItemDoubleClicked( QTreeWidgetItem* item, int column );
};

Q_DECLARE_METATYPE( QgsFieldsProperties::FieldConfig )
@@ -369,6 +369,11 @@ QgsAttributeEditorElement* QgsEditFormConfig::attributeEditorElementFromDomEleme
if ( elem.tagName() == "attributeEditorContainer" )
{
QgsAttributeEditorContainer* container = new QgsAttributeEditorContainer( elem.attribute( "name" ), parent );
bool ok;
int cc = elem.attribute( "columnCount" ).toInt( &ok );
if ( !ok )
cc = 0;
container->setColumnCount( cc );

QDomNodeList childNodeList = elem.childNodes();

@@ -420,3 +425,13 @@ void QgsEditFormConfig::onRelationsLoaded()
}
}
}

int QgsAttributeEditorContainer::columnCount() const
{
return mColumnCount;
}

void QgsAttributeEditorContainer::setColumnCount( int columnCount )
{
mColumnCount = columnCount;
}
@@ -158,14 +158,23 @@ class CORE_EXPORT QgsAttributeEditorContainer : public QgsAttributeEditorElement

/**
* Change the name of this container
*
* @param name
*/
void setName( const QString& name );

/**
* Get the number of columns in this group
*/
int columnCount() const;

/**
* Set the number of columns in this group
*/
void setColumnCount( int columnCount );

private:
bool mIsGroupBox;
QList<QgsAttributeEditorElement*> mChildren;
int mColumnCount;
};

/**
@@ -3827,6 +3827,7 @@ QDomElement QgsAttributeEditorContainer::toDomElement( QDomDocument& doc ) const
{
QDomElement elem = doc.createElement( "attributeEditorContainer" );
elem.setAttribute( "name", mName );
elem.setAttribute( "columnCount", mColumnCount );

Q_FOREACH ( QgsAttributeEditorElement* child, mChildren )
{
@@ -735,6 +735,11 @@ QWidget* QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement
if ( !container )
break;

int columnCount = container->columnCount();

if ( columnCount <= 0 )
columnCount = 1;

QWidget* myContainer;
if ( container->isGroupBox() )
{
@@ -759,7 +764,8 @@ QWidget* QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement
QGridLayout* gbLayout = new QGridLayout();
myContainer->setLayout( gbLayout );

int index = 0;
int row = 0;
int column = 0;

QList<QgsAttributeEditorElement*> children = container->children();

@@ -771,25 +777,31 @@ QWidget* QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement

if ( labelText.isNull() )
{
gbLayout->addWidget( editor, index, 0, 1, 2 );
gbLayout->addWidget( editor, row, column, 1, 2 );
column += 2;
}
else
{
QLabel* mypLabel = new QLabel( labelText );
if ( labelOnTop )
{
gbLayout->addWidget( mypLabel, index, 0, 1, 2 );
++index;
gbLayout->addWidget( editor, index, 0, 1, 2 );
gbLayout->addWidget( mypLabel, row, column, 1, 2 );
++row;
gbLayout->addWidget( editor, row, column, 1, 2 );
column += 2;
}
else
{
gbLayout->addWidget( mypLabel, index, 0 );
gbLayout->addWidget( editor, index, 1 );
gbLayout->addWidget( mypLabel, row, column++ );
gbLayout->addWidget( editor, row, column++ );
}
}

++index;
if ( column >= columnCount * 2 )
{
column = 0;
row += 1;
}
}
QWidget* spacer = new QWidget();
spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );

6 comments on commit faf6b26

@3nids

This comment has been minimized.

Copy link
Member

@3nids 3nids replied Apr 1, 2016

goo job @m-kuhn !!!

wouldn't it make more sense to fill column-by-column rather than row-by-row ?
then, adding a vertical line would make it perfect!

@m-kuhn

This comment has been minimized.

Copy link
Member Author

@m-kuhn m-kuhn replied Apr 1, 2016

I think that in general things are "grouped" block wise vertically. Filling column by column assumes that you would want to have things "grouped" column-wise. With a tree-structure only one or the other can be made straightforward.

A few enhancements I have in mind:

  • making a "spacer" item to keep a field empty
  • making a "break" item that forces a new row (one could also just use groupboxes instead)
@m-kuhn

This comment has been minimized.

Copy link
Member Author

@m-kuhn m-kuhn replied Apr 1, 2016

Btw, you can also make a toplevel 2-column tab and insert two 1-column groupboxes, that will result in a column-wise behavior.

@3nids

This comment has been minimized.

Copy link
Member

@3nids 3nids replied Apr 4, 2016

I tend to disagree, more especially if we add a separator between the 2 columns.
To me, it's similar to a newspaper: you read column by column rather than row by row.

@m-kuhn

This comment has been minimized.

Copy link
Member Author

@m-kuhn m-kuhn replied Apr 4, 2016

Does the approach

  • tab (2 column)
    • groupbox (1 column)
    • groupbox (1 column)

not work?

@3nids

This comment has been minimized.

Copy link
Member

@3nids 3nids replied Apr 4, 2016

yes, that would make it, but it'll have the frame and title of the group box.
I just think it makes more sense to go the other way.
Although, I understand it might be different depending on the number of fields (for a few fields it makes more sense column wise, and for a lot row wise).
No big deal, I can indeed use your approach.

Please sign in to comment.
You can’t perform that action at this time.