Skip to content

Commit

Permalink
[FEATURE] customizable attribute forms using Qt Designer dialog UIs
Browse files Browse the repository at this point in the history
- add support for checkbox edit type and edit forms for vector layers
- selection of ui file in vector layer properties (general tab)
- the forms are opened when a feature is added or via identify.
  The widgets on the ui have to be named like the attribute they are supposed
  to edit or show.  If the vector layer is not in editing mode, the widgets
  will be disabled



git-svn-id: http://svn.osgeo.org/qgis/trunk/qgis@12077 c8812cc2-4d05-0410-92ff-de0c093fc19c
  • Loading branch information
jef committed Nov 10, 2009
1 parent 64eeec8 commit 88e59f5
Show file tree
Hide file tree
Showing 21 changed files with 699 additions and 342 deletions.
16 changes: 8 additions & 8 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
SUBDIRS(plugins)
IF (WIN32)
SET(BINDINGS_CORE_LIB ${CMAKE_CURRENT_BINARY_DIR}/core/core.pyd)
SET(BINDINGS_GUI_LIB ${CMAKE_CURRENT_BINARY_DIR}/gui/gui.pyd)
SET(BINDINGS_ANALYSIS_LIB ${CMAKE_CURRENT_BINARY_DIR}/analysis/analysis.pyd)
SET(BINDINGS_CORE_LIB ${CMAKE_CURRENT_BINARY_DIR}/core/core.pyd)
SET(BINDINGS_GUI_LIB ${CMAKE_CURRENT_BINARY_DIR}/gui/gui.pyd)
SET(BINDINGS_ANALYSIS_LIB ${CMAKE_CURRENT_BINARY_DIR}/analysis/analysis.pyd)
IF (NOT MSVC)
SET(QGIS_CORE_LIB ${CMAKE_BINARY_DIR}/src/core/libqgis_core.dll)
SET(QGIS_GUI_LIB ${CMAKE_BINARY_DIR}/src/gui/libqgis_gui.dll)
SET(QGIS_ANALYSIS_LIB ${CMAKE_BINARY_DIR}/src/analysis/libqgis_analysis.dll)
ELSE (NOT MSVC)
SET(QGIS_CORE_LIB ${CMAKE_BINARY_DIR}/src/core/${CMAKE_CFG_INTDIR}/qgis_core.lib)
SET(QGIS_GUI_LIB ${CMAKE_BINARY_DIR}/src/gui/${CMAKE_CFG_INTDIR}/qgis_gui.lib)
SET(QGIS_CORE_LIB ${CMAKE_BINARY_DIR}/src/core/${CMAKE_CFG_INTDIR}/qgis_core.lib)
SET(QGIS_GUI_LIB ${CMAKE_BINARY_DIR}/src/gui/${CMAKE_CFG_INTDIR}/qgis_gui.lib)
SET(QGIS_ANALYSIS_LIB ${CMAKE_BINARY_DIR}/src/analysis/${CMAKE_CFG_INTDIR}/qgis_analysis.lib)
ENDIF (NOT MSVC)
ELSE (WIN32)
Expand Down Expand Up @@ -60,9 +60,9 @@ ENDIF (MSVC)
ADD_CUSTOM_COMMAND(OUTPUT ${BINDINGS_CORE_MAKEFILE} ${BINDINGS_GUI_MAKEFILE} ${BINDINGS_ANALYSIS_MAKEFILE} PRE_BUILD
COMMAND ${PYTHON_EXECUTABLE}
ARGS ${CMAKE_CURRENT_BINARY_DIR}/configure.py ${CMAKE_CFG_INTDIR} ${EXPORT}
DEPENDS ${QGIS_CORE_LIB} ${QGIS_GUI_LIB} ${QGIS_ANALYSIS_LIB}
${CMAKE_CURRENT_BINARY_DIR}/configure.py
${CORE_SIP_FILES} ${GUI_SIP_FILES} ${ANALYSIS_SIP_FILES})
DEPENDS ${QGIS_CORE_LIB} ${QGIS_GUI_LIB} ${QGIS_ANALYSIS_LIB}
${CMAKE_CURRENT_BINARY_DIR}/configure.py
${CORE_SIP_FILES} ${GUI_SIP_FILES} ${ANALYSIS_SIP_FILES})

# Step 3: run make in core and gui subdirs
ADD_CUSTOM_COMMAND(OUTPUT ${BINDINGS_CORE_LIB} PRE_LINK
Expand Down
30 changes: 26 additions & 4 deletions python/core/qgsvectorlayer.sip
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ public:
Classification,
EditRange,
SliderRange
CheckBox, /* @note added in 1.4 */
FileName,
Enumeration,
Immutable
Enumeration, /* @note added in 1.4 */
Immutable /* @note added in 1.4 */
};

struct RangeData {
Expand Down Expand Up @@ -130,7 +131,7 @@ public:
/** Write the symbology for the layer into the docment provided.
* @param QDomNode the node that will have the style element added to it.
* @param QDomDocument the document that will have the QDomNode added.
* @param errorMessage reference to string that will be updated with any error messages
* @param errorMessage reference to string that will be updated with any error messages
* @return true in case of success.
*/
bool writeSymbology(QDomNode&, QDomDocument& doc, QString& errorMessage) const;
Expand All @@ -154,7 +155,7 @@ public:
* @param subset The subset string. This may be the where clause of a sql statement
* or other defintion string specific to the underlying dataprovider
* and data store.
* @return true, when setting the string was successful, false otherwise (added in 1.4)
* @return true, when setting the string was successful, false otherwise (@note added in 1.4)
*/
virtual bool setSubsetString(QString subset);

Expand Down Expand Up @@ -387,6 +388,27 @@ public:
/**set edit type*/
void setEditType(int idx, EditType edit);

/** set string representing 'true' for a checkbox
@note added in 1.4
*/
void setCheckedState( int idx, QString checked, QString notChecked );

/** return string representing 'true' for a checkbox
@note added in 1.4
*/
// FIXME: need SIP binding for QPair<QString, QString>
// QPair<QString, QString> checkedState( int idx );

/** get edit form
@note added in 1.4
*/
QString editForm();

/** set edit form
@note added in 1.4
*/
void setEditForm( QString ui );

/**access value map*/
QMap<QString, QVariant> &valueMap(int idx);

Expand Down
1 change: 0 additions & 1 deletion src/analysis/vector/qgsoverlayanalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ bool QgsOverlayAnalyzer::intersection( QgsVectorLayer* layerA, QgsVectorLayer* l

QgsVectorFileWriter vWriter( shapefileName, dpA->encoding(), fieldsA, outputType, &crs );
QgsFeature currentFeature;
QgsGeometry* dissolveGeometry; //dissolve geometry (if dissolve enabled)
QgsSpatialIndex index;

//take only selection
Expand Down
2 changes: 2 additions & 0 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_SOURCE_DIR} composer legend attributetable
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_BINARY_DIR}/../ui
${QT_QTUITOOLS_INCLUDE_DIR}
../core
../core/composer ../core/raster ../core/renderer ../core/symbology
../gui
Expand Down Expand Up @@ -295,6 +296,7 @@ TARGET_LINK_LIBRARIES(qgis
${QT_QTSVG_LIBRARY}
${QT_QTNETWORK_LIBRARY}
${QT_QTSQL_LIBRARY}
${QT_QTUITOOLS_LIBRARY}
#should only be needed for win
${QT_QTMAIN_LIBRARY}
qgis_core
Expand Down
2 changes: 1 addition & 1 deletion src/app/attributetable/qgsattributetabledelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ QWidget *QgsAttributeTableDelegate::createEditor(
if ( vl == NULL )
return NULL;

QWidget *widget = QgsAttributeEditor::createAttributeEditor( parent, vl, fieldIdx( index ), index.model()->data( index, Qt::EditRole ) );
QWidget *widget = QgsAttributeEditor::createAttributeEditor( parent, 0, vl, fieldIdx( index ), index.model()->data( index, Qt::EditRole ) );

return widget;
}
Expand Down
203 changes: 147 additions & 56 deletions src/app/qgsattributedialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,82 +30,168 @@
#include <QLabel>
#include <QFrame>
#include <QScrollArea>
#include <QFile>
#include <QDialogButtonBox>
#include <QUiLoader>
#include <QDialog>
#include <QVBoxLayout>

QgsAttributeDialog::QgsAttributeDialog( QgsVectorLayer *vl, QgsFeature *thepFeature )
: QDialog(),
: mDialog( 0 ),
mSettingsPath( "/Windows/AttributeDialog/" ),
mLayer( vl ),
mpFeature( thepFeature )
{
setupUi( this );
if ( mpFeature == NULL || vl->dataProvider() == NULL )
return;

const QgsFieldMap &theFieldMap = vl->pendingFields();

if ( theFieldMap.isEmpty() )
return;

QgsAttributeMap myAttributes = mpFeature->attributeMap();
//
//Set up dynamic inside a scroll box
//
QVBoxLayout * mypOuterLayout = new QVBoxLayout();
mypOuterLayout->setContentsMargins( 0, 0, 0, 0 );
//transfers layout ownership so no need to call delete
mFrame->setLayout( mypOuterLayout );
QScrollArea * mypScrollArea = new QScrollArea();
//transfers scroll area ownership so no need to call delete
mypOuterLayout->addWidget( mypScrollArea );
QFrame * mypInnerFrame = new QFrame();
mypInnerFrame->setFrameShape( QFrame::NoFrame );
mypInnerFrame->setFrameShadow( QFrame::Plain );
//transfers frame ownership so no need to call delete
mypScrollArea->setWidget( mypInnerFrame );
mypScrollArea->setWidgetResizable( true );
QGridLayout * mypInnerLayout = new QGridLayout( mypInnerFrame );

int index = 0;
for ( QgsAttributeMap::const_iterator it = myAttributes.begin();
it != myAttributes.end();
++it )
{
const QgsField &field = theFieldMap[it.key()];

//show attribute alias if available
QString myFieldName = vl->attributeDisplayName( it.key() );
int myFieldType = field.type();
QDialogButtonBox *buttonBox = NULL;

QWidget *myWidget = QgsAttributeEditor::createAttributeEditor( 0, vl, it.key(), it.value() );
if ( !myWidget )
continue;
if ( !vl->editForm().isEmpty() )
{
QFile file( vl->editForm() );
file.open( QFile::ReadOnly );
QUiLoader loader;
QWidget *myWidget = loader.load( &file, NULL );
file.close();

mDialog = qobject_cast<QDialog*>( myWidget );
buttonBox = myWidget->findChild<QDialogButtonBox*>();
}

QLabel * mypLabel = new QLabel();
mypInnerLayout->addWidget( mypLabel, index, 0 );
if ( myFieldType == QVariant::Int )
if ( !mDialog )
{
mDialog = new QDialog();

QGridLayout *gridLayout;
QFrame *mFrame;

if ( mDialog->objectName().isEmpty() )
mDialog->setObjectName( QString::fromUtf8( "QgsAttributeDialogBase" ) );

mDialog->resize( 447, 343 );
gridLayout = new QGridLayout( mDialog );
gridLayout->setSpacing( 6 );
gridLayout->setMargin( 11 );
gridLayout->setObjectName( QString::fromUtf8( "gridLayout" ) );
mFrame = new QFrame( mDialog );
mFrame->setObjectName( QString::fromUtf8( "mFrame" ) );
mFrame->setFrameShape( QFrame::StyledPanel );
mFrame->setFrameShadow( QFrame::Raised );

gridLayout->addWidget( mFrame, 0, 0, 1, 1 );

buttonBox = new QDialogButtonBox( mDialog );
buttonBox->setObjectName( QString::fromUtf8( "buttonBox" ) );
gridLayout->addWidget( buttonBox, 2, 0, 1, 1 );

//
//Set up dynamic inside a scroll box
//
QVBoxLayout * mypOuterLayout = new QVBoxLayout();
mypOuterLayout->setContentsMargins( 0, 0, 0, 0 );
//transfers layout ownership so no need to call delete

mFrame->setLayout( mypOuterLayout );
QScrollArea * mypScrollArea = new QScrollArea();
//transfers scroll area ownership so no need to call delete
mypOuterLayout->addWidget( mypScrollArea );
QFrame *mypInnerFrame = new QFrame();
mypInnerFrame->setFrameShape( QFrame::NoFrame );
mypInnerFrame->setFrameShadow( QFrame::Plain );
//transfers frame ownership so no need to call delete
mypScrollArea->setWidget( mypInnerFrame );
mypScrollArea->setWidgetResizable( true );
QGridLayout * mypInnerLayout = new QGridLayout( mypInnerFrame );

int index = 0;
for ( QgsAttributeMap::const_iterator it = myAttributes.begin(); it != myAttributes.end(); ++it )
{
mypLabel->setText( myFieldName + tr( " (int)" ) );
const QgsField &field = theFieldMap[it.key()];

//show attribute alias if available
QString myFieldName = vl->attributeDisplayName( it.key() );
int myFieldType = field.type();

QWidget *myWidget = QgsAttributeEditor::createAttributeEditor( 0, 0, vl, it.key(), it.value() );
if ( !myWidget )
continue;

QLabel * mypLabel = new QLabel();
mypInnerLayout->addWidget( mypLabel, index, 0 );
if ( myFieldType == QVariant::Int )
{
mypLabel->setText( myFieldName + tr( " (int)" ) );
}
else if ( myFieldType == QVariant::Double )
{
mypLabel->setText( myFieldName + tr( " (dbl)" ) );
}
else //string
{
//any special behaviour for string goes here
mypLabel->setText( myFieldName + tr( " (txt)" ) );
}

myWidget->setEnabled( vl->isEditable() );

mypInnerLayout->addWidget( myWidget, index, 1 );
mpIndizes << it.key();
mpWidgets << myWidget;
++index;
}
else if ( myFieldType == QVariant::Double )
// Set focus to first widget in list, to help entering data without moving the mouse.
if ( mpWidgets.size() > 0 )
{
mypLabel->setText( myFieldName + tr( " (dbl)" ) );
mpWidgets.first()->setFocus( Qt::OtherFocusReason );
}
else //string
}
else
{
for ( QgsAttributeMap::const_iterator it = myAttributes.begin(); it != myAttributes.end(); ++it )
{
//any special behaviour for string goes here
mypLabel->setText( myFieldName + tr( " (txt)" ) );
}
const QgsField &field = theFieldMap[it.key()];

QWidget *myWidget = mDialog->findChild<QWidget*>( field.name() );
if ( !myWidget )
continue;

QgsAttributeEditor::createAttributeEditor( mDialog, myWidget, vl, it.key(), it.value() );

mypInnerLayout->addWidget( myWidget, index, 1 );
mpIndizes << it.key();
mpWidgets << myWidget;
++index;
myWidget->setEnabled( vl->isEditable() );

mpIndizes << it.key();
mpWidgets << myWidget;
}
}
// Set focus to first widget in list, to help entering data without moving the mouse.
if ( mpWidgets.size() > 0 )

if ( buttonBox )
{
mpWidgets.first()->setFocus( Qt::OtherFocusReason );
buttonBox->clear();

if( vl->isEditable() )
{
buttonBox->setStandardButtons( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
connect( buttonBox, SIGNAL( accepted() ), mDialog, SLOT( accept() ) );
connect( buttonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
}
else
{
buttonBox->setStandardButtons( QDialogButtonBox::Cancel );
}

connect( buttonBox, SIGNAL( rejected() ), mDialog, SLOT( reject() ) );
connect( buttonBox, SIGNAL( rejected() ), this, SLOT( rejected() ) );
}

QMetaObject::connectSlotsByName( mDialog );

restoreGeometry();
}

Expand All @@ -117,12 +203,13 @@ QgsAttributeDialog::~QgsAttributeDialog()

void QgsAttributeDialog::accept()
{
if ( !mLayer->isEditable() )
return;

//write the new values back to the feature
QgsAttributeMap myAttributes = mpFeature->attributeMap();
int myIndex = 0;
for ( QgsAttributeMap::const_iterator it = myAttributes.begin();
it != myAttributes.end();
++it )
for ( QgsAttributeMap::const_iterator it = myAttributes.begin(); it != myAttributes.end(); ++it )
{
QVariant value;

Expand All @@ -132,17 +219,21 @@ void QgsAttributeDialog::accept()

++myIndex;
}
QDialog::accept();
}

int QgsAttributeDialog::exec()
{
return mDialog->exec();
}

void QgsAttributeDialog::saveGeometry()
{
QSettings settings;
settings.setValue( mSettingsPath + "geometry", QDialog::saveGeometry() );
settings.setValue( mSettingsPath + "geometry", mDialog->saveGeometry() );
}

void QgsAttributeDialog::restoreGeometry()
{
QSettings settings;
QDialog::restoreGeometry( settings.value( mSettingsPath + "geometry" ).toByteArray() );
mDialog->restoreGeometry( settings.value( mSettingsPath + "geometry" ).toByteArray() );
}

0 comments on commit 88e59f5

Please sign in to comment.