Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[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 faf6b26
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 40 deletions.
32 changes: 32 additions & 0 deletions src/app/qgsaddtaborgroup.cpp
Expand Up @@ -23,6 +23,7 @@

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

QgsAddTabOrGroup::QgsAddTabOrGroup( QgsVectorLayer *lyr, const QList < TabPair >& tabList, QWidget * parent )
: QDialog( parent )
Expand Down Expand Up @@ -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

Expand All @@ -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() );
}
6 changes: 5 additions & 1 deletion src/app/qgsaddtaborgroup.h
Expand Up @@ -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 );

Expand Down
69 changes: 58 additions & 11 deletions src/app/qgsfieldsproperties.cpp
Expand Up @@ -39,6 +39,7 @@
#include <QSettings>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QFormLayout>

QgsFieldsProperties::QgsFieldsProperties( QgsVectorLayer *layer, QWidget* parent )
: QWidget( parent )
Expand Down Expand Up @@ -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() )
{
Expand Down Expand Up @@ -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() );
}
}

Expand Down Expand Up @@ -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++ )
{
Expand Down Expand Up @@ -919,8 +922,6 @@ void QgsFieldsProperties::apply()
}
}



// tabs and groups
mLayer->clearAttributeEditorWidgets();
for ( int t = 0; t < mDesignerTree->invisibleRootItem()->childCount(); t++ )
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
*/
Expand Down
16 changes: 11 additions & 5 deletions src/app/qgsfieldsproperties.h
Expand Up @@ -21,6 +21,7 @@
#include <QTableWidget>
#include <QTreeWidget>
#include <QWidget>
#include <QSpinBox>


#include "qgsvectorlayer.h"
Expand Down Expand Up @@ -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;
};

/**
Expand Down Expand Up @@ -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;
Expand All @@ -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 )
Expand Down
15 changes: 15 additions & 0 deletions src/core/qgseditformconfig.cpp
Expand Up @@ -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();

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

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

void QgsAttributeEditorContainer::setColumnCount( int columnCount )
{
mColumnCount = columnCount;
}
13 changes: 11 additions & 2 deletions src/core/qgseditformconfig.h
Expand Up @@ -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;
};

/**
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsvectorlayer.cpp
Expand Up @@ -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 )
{
Expand Down
28 changes: 20 additions & 8 deletions src/gui/qgsattributeform.cpp
Expand Up @@ -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() )
{
Expand All @@ -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();

Expand All @@ -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 );
Expand Down

6 comments on commit faf6b26

@3nids
Copy link
Member

@3nids 3nids commented on faf6b26 Apr 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member Author

@m-kuhn m-kuhn commented on faf6b26 Apr 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member Author

@m-kuhn m-kuhn commented on faf6b26 Apr 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member

@3nids 3nids commented on faf6b26 Apr 4, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member Author

@m-kuhn m-kuhn commented on faf6b26 Apr 4, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the approach

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

not work?

@3nids
Copy link
Member

@3nids 3nids commented on faf6b26 Apr 4, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.