Skip to content

Commit 6b7ad07

Browse files
author
jef
committed
[FEATURE] customizable attribute forms using Qt Designer dialog UIs
- 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@12077 c8812cc2-4d05-0410-92ff-de0c093fc19c
1 parent be95d79 commit 6b7ad07

21 files changed

+699
-342
lines changed

python/CMakeLists.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
SUBDIRS(plugins)
22
IF (WIN32)
3-
SET(BINDINGS_CORE_LIB ${CMAKE_CURRENT_BINARY_DIR}/core/core.pyd)
4-
SET(BINDINGS_GUI_LIB ${CMAKE_CURRENT_BINARY_DIR}/gui/gui.pyd)
5-
SET(BINDINGS_ANALYSIS_LIB ${CMAKE_CURRENT_BINARY_DIR}/analysis/analysis.pyd)
3+
SET(BINDINGS_CORE_LIB ${CMAKE_CURRENT_BINARY_DIR}/core/core.pyd)
4+
SET(BINDINGS_GUI_LIB ${CMAKE_CURRENT_BINARY_DIR}/gui/gui.pyd)
5+
SET(BINDINGS_ANALYSIS_LIB ${CMAKE_CURRENT_BINARY_DIR}/analysis/analysis.pyd)
66
IF (NOT MSVC)
77
SET(QGIS_CORE_LIB ${CMAKE_BINARY_DIR}/src/core/libqgis_core.dll)
88
SET(QGIS_GUI_LIB ${CMAKE_BINARY_DIR}/src/gui/libqgis_gui.dll)
99
SET(QGIS_ANALYSIS_LIB ${CMAKE_BINARY_DIR}/src/analysis/libqgis_analysis.dll)
1010
ELSE (NOT MSVC)
11-
SET(QGIS_CORE_LIB ${CMAKE_BINARY_DIR}/src/core/${CMAKE_CFG_INTDIR}/qgis_core.lib)
12-
SET(QGIS_GUI_LIB ${CMAKE_BINARY_DIR}/src/gui/${CMAKE_CFG_INTDIR}/qgis_gui.lib)
11+
SET(QGIS_CORE_LIB ${CMAKE_BINARY_DIR}/src/core/${CMAKE_CFG_INTDIR}/qgis_core.lib)
12+
SET(QGIS_GUI_LIB ${CMAKE_BINARY_DIR}/src/gui/${CMAKE_CFG_INTDIR}/qgis_gui.lib)
1313
SET(QGIS_ANALYSIS_LIB ${CMAKE_BINARY_DIR}/src/analysis/${CMAKE_CFG_INTDIR}/qgis_analysis.lib)
1414
ENDIF (NOT MSVC)
1515
ELSE (WIN32)
@@ -60,9 +60,9 @@ ENDIF (MSVC)
6060
ADD_CUSTOM_COMMAND(OUTPUT ${BINDINGS_CORE_MAKEFILE} ${BINDINGS_GUI_MAKEFILE} ${BINDINGS_ANALYSIS_MAKEFILE} PRE_BUILD
6161
COMMAND ${PYTHON_EXECUTABLE}
6262
ARGS ${CMAKE_CURRENT_BINARY_DIR}/configure.py ${CMAKE_CFG_INTDIR} ${EXPORT}
63-
DEPENDS ${QGIS_CORE_LIB} ${QGIS_GUI_LIB} ${QGIS_ANALYSIS_LIB}
64-
${CMAKE_CURRENT_BINARY_DIR}/configure.py
65-
${CORE_SIP_FILES} ${GUI_SIP_FILES} ${ANALYSIS_SIP_FILES})
63+
DEPENDS ${QGIS_CORE_LIB} ${QGIS_GUI_LIB} ${QGIS_ANALYSIS_LIB}
64+
${CMAKE_CURRENT_BINARY_DIR}/configure.py
65+
${CORE_SIP_FILES} ${GUI_SIP_FILES} ${ANALYSIS_SIP_FILES})
6666

6767
# Step 3: run make in core and gui subdirs
6868
ADD_CUSTOM_COMMAND(OUTPUT ${BINDINGS_CORE_LIB} PRE_LINK

python/core/qgsvectorlayer.sip

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ public:
1313
Classification,
1414
EditRange,
1515
SliderRange
16+
CheckBox, /* @note added in 1.4 */
1617
FileName,
17-
Enumeration,
18-
Immutable
18+
Enumeration, /* @note added in 1.4 */
19+
Immutable /* @note added in 1.4 */
1920
};
2021

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

@@ -387,6 +388,27 @@ public:
387388
/**set edit type*/
388389
void setEditType(int idx, EditType edit);
389390

391+
/** set string representing 'true' for a checkbox
392+
@note added in 1.4
393+
*/
394+
void setCheckedState( int idx, QString checked, QString notChecked );
395+
396+
/** return string representing 'true' for a checkbox
397+
@note added in 1.4
398+
*/
399+
// FIXME: need SIP binding for QPair<QString, QString>
400+
// QPair<QString, QString> checkedState( int idx );
401+
402+
/** get edit form
403+
@note added in 1.4
404+
*/
405+
QString editForm();
406+
407+
/** set edit form
408+
@note added in 1.4
409+
*/
410+
void setEditForm( QString ui );
411+
390412
/**access value map*/
391413
QMap<QString, QVariant> &valueMap(int idx);
392414

src/analysis/vector/qgsoverlayanalyzer.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ bool QgsOverlayAnalyzer::intersection( QgsVectorLayer* layerA, QgsVectorLayer* l
5252

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

5857
//take only selection

src/app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ INCLUDE_DIRECTORIES(
251251
${CMAKE_CURRENT_SOURCE_DIR} composer legend attributetable
252252
${CMAKE_CURRENT_BINARY_DIR}
253253
${CMAKE_CURRENT_BINARY_DIR}/../ui
254+
${QT_QTUITOOLS_INCLUDE_DIR}
254255
../core
255256
../core/composer ../core/raster ../core/renderer ../core/symbology
256257
../gui
@@ -295,6 +296,7 @@ TARGET_LINK_LIBRARIES(qgis
295296
${QT_QTSVG_LIBRARY}
296297
${QT_QTNETWORK_LIBRARY}
297298
${QT_QTSQL_LIBRARY}
299+
${QT_QTUITOOLS_LIBRARY}
298300
#should only be needed for win
299301
${QT_QTMAIN_LIBRARY}
300302
qgis_core

src/app/attributetable/qgsattributetabledelegate.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ QWidget *QgsAttributeTableDelegate::createEditor(
6262
if ( vl == NULL )
6363
return NULL;
6464

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

6767
return widget;
6868
}

src/app/qgsattributedialog.cpp

Lines changed: 147 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -30,82 +30,168 @@
3030
#include <QLabel>
3131
#include <QFrame>
3232
#include <QScrollArea>
33+
#include <QFile>
34+
#include <QDialogButtonBox>
35+
#include <QUiLoader>
36+
#include <QDialog>
37+
#include <QVBoxLayout>
3338

3439
QgsAttributeDialog::QgsAttributeDialog( QgsVectorLayer *vl, QgsFeature *thepFeature )
35-
: QDialog(),
40+
: mDialog( 0 ),
3641
mSettingsPath( "/Windows/AttributeDialog/" ),
3742
mLayer( vl ),
3843
mpFeature( thepFeature )
3944
{
40-
setupUi( this );
4145
if ( mpFeature == NULL || vl->dataProvider() == NULL )
4246
return;
4347

4448
const QgsFieldMap &theFieldMap = vl->pendingFields();
45-
4649
if ( theFieldMap.isEmpty() )
4750
return;
4851

4952
QgsAttributeMap myAttributes = mpFeature->attributeMap();
50-
//
51-
//Set up dynamic inside a scroll box
52-
//
53-
QVBoxLayout * mypOuterLayout = new QVBoxLayout();
54-
mypOuterLayout->setContentsMargins( 0, 0, 0, 0 );
55-
//transfers layout ownership so no need to call delete
56-
mFrame->setLayout( mypOuterLayout );
57-
QScrollArea * mypScrollArea = new QScrollArea();
58-
//transfers scroll area ownership so no need to call delete
59-
mypOuterLayout->addWidget( mypScrollArea );
60-
QFrame * mypInnerFrame = new QFrame();
61-
mypInnerFrame->setFrameShape( QFrame::NoFrame );
62-
mypInnerFrame->setFrameShadow( QFrame::Plain );
63-
//transfers frame ownership so no need to call delete
64-
mypScrollArea->setWidget( mypInnerFrame );
65-
mypScrollArea->setWidgetResizable( true );
66-
QGridLayout * mypInnerLayout = new QGridLayout( mypInnerFrame );
67-
68-
int index = 0;
69-
for ( QgsAttributeMap::const_iterator it = myAttributes.begin();
70-
it != myAttributes.end();
71-
++it )
72-
{
73-
const QgsField &field = theFieldMap[it.key()];
7453

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

79-
QWidget *myWidget = QgsAttributeEditor::createAttributeEditor( 0, vl, it.key(), it.value() );
80-
if ( !myWidget )
81-
continue;
56+
if ( !vl->editForm().isEmpty() )
57+
{
58+
QFile file( vl->editForm() );
59+
file.open( QFile::ReadOnly );
60+
QUiLoader loader;
61+
QWidget *myWidget = loader.load( &file, NULL );
62+
file.close();
63+
64+
mDialog = qobject_cast<QDialog*>( myWidget );
65+
buttonBox = myWidget->findChild<QDialogButtonBox*>();
66+
}
8267

83-
QLabel * mypLabel = new QLabel();
84-
mypInnerLayout->addWidget( mypLabel, index, 0 );
85-
if ( myFieldType == QVariant::Int )
68+
if ( !mDialog )
69+
{
70+
mDialog = new QDialog();
71+
72+
QGridLayout *gridLayout;
73+
QFrame *mFrame;
74+
75+
if ( mDialog->objectName().isEmpty() )
76+
mDialog->setObjectName( QString::fromUtf8( "QgsAttributeDialogBase" ) );
77+
78+
mDialog->resize( 447, 343 );
79+
gridLayout = new QGridLayout( mDialog );
80+
gridLayout->setSpacing( 6 );
81+
gridLayout->setMargin( 11 );
82+
gridLayout->setObjectName( QString::fromUtf8( "gridLayout" ) );
83+
mFrame = new QFrame( mDialog );
84+
mFrame->setObjectName( QString::fromUtf8( "mFrame" ) );
85+
mFrame->setFrameShape( QFrame::StyledPanel );
86+
mFrame->setFrameShadow( QFrame::Raised );
87+
88+
gridLayout->addWidget( mFrame, 0, 0, 1, 1 );
89+
90+
buttonBox = new QDialogButtonBox( mDialog );
91+
buttonBox->setObjectName( QString::fromUtf8( "buttonBox" ) );
92+
gridLayout->addWidget( buttonBox, 2, 0, 1, 1 );
93+
94+
//
95+
//Set up dynamic inside a scroll box
96+
//
97+
QVBoxLayout * mypOuterLayout = new QVBoxLayout();
98+
mypOuterLayout->setContentsMargins( 0, 0, 0, 0 );
99+
//transfers layout ownership so no need to call delete
100+
101+
mFrame->setLayout( mypOuterLayout );
102+
QScrollArea * mypScrollArea = new QScrollArea();
103+
//transfers scroll area ownership so no need to call delete
104+
mypOuterLayout->addWidget( mypScrollArea );
105+
QFrame *mypInnerFrame = new QFrame();
106+
mypInnerFrame->setFrameShape( QFrame::NoFrame );
107+
mypInnerFrame->setFrameShadow( QFrame::Plain );
108+
//transfers frame ownership so no need to call delete
109+
mypScrollArea->setWidget( mypInnerFrame );
110+
mypScrollArea->setWidgetResizable( true );
111+
QGridLayout * mypInnerLayout = new QGridLayout( mypInnerFrame );
112+
113+
int index = 0;
114+
for ( QgsAttributeMap::const_iterator it = myAttributes.begin(); it != myAttributes.end(); ++it )
86115
{
87-
mypLabel->setText( myFieldName + tr( " (int)" ) );
116+
const QgsField &field = theFieldMap[it.key()];
117+
118+
//show attribute alias if available
119+
QString myFieldName = vl->attributeDisplayName( it.key() );
120+
int myFieldType = field.type();
121+
122+
QWidget *myWidget = QgsAttributeEditor::createAttributeEditor( 0, 0, vl, it.key(), it.value() );
123+
if ( !myWidget )
124+
continue;
125+
126+
QLabel * mypLabel = new QLabel();
127+
mypInnerLayout->addWidget( mypLabel, index, 0 );
128+
if ( myFieldType == QVariant::Int )
129+
{
130+
mypLabel->setText( myFieldName + tr( " (int)" ) );
131+
}
132+
else if ( myFieldType == QVariant::Double )
133+
{
134+
mypLabel->setText( myFieldName + tr( " (dbl)" ) );
135+
}
136+
else //string
137+
{
138+
//any special behaviour for string goes here
139+
mypLabel->setText( myFieldName + tr( " (txt)" ) );
140+
}
141+
142+
myWidget->setEnabled( vl->isEditable() );
143+
144+
mypInnerLayout->addWidget( myWidget, index, 1 );
145+
mpIndizes << it.key();
146+
mpWidgets << myWidget;
147+
++index;
88148
}
89-
else if ( myFieldType == QVariant::Double )
149+
// Set focus to first widget in list, to help entering data without moving the mouse.
150+
if ( mpWidgets.size() > 0 )
90151
{
91-
mypLabel->setText( myFieldName + tr( " (dbl)" ) );
152+
mpWidgets.first()->setFocus( Qt::OtherFocusReason );
92153
}
93-
else //string
154+
}
155+
else
156+
{
157+
for ( QgsAttributeMap::const_iterator it = myAttributes.begin(); it != myAttributes.end(); ++it )
94158
{
95-
//any special behaviour for string goes here
96-
mypLabel->setText( myFieldName + tr( " (txt)" ) );
97-
}
159+
const QgsField &field = theFieldMap[it.key()];
160+
161+
QWidget *myWidget = mDialog->findChild<QWidget*>( field.name() );
162+
if ( !myWidget )
163+
continue;
164+
165+
QgsAttributeEditor::createAttributeEditor( mDialog, myWidget, vl, it.key(), it.value() );
98166

99-
mypInnerLayout->addWidget( myWidget, index, 1 );
100-
mpIndizes << it.key();
101-
mpWidgets << myWidget;
102-
++index;
167+
myWidget->setEnabled( vl->isEditable() );
168+
169+
mpIndizes << it.key();
170+
mpWidgets << myWidget;
171+
}
103172
}
104-
// Set focus to first widget in list, to help entering data without moving the mouse.
105-
if ( mpWidgets.size() > 0 )
173+
174+
if ( buttonBox )
106175
{
107-
mpWidgets.first()->setFocus( Qt::OtherFocusReason );
176+
buttonBox->clear();
177+
178+
if( vl->isEditable() )
179+
{
180+
buttonBox->setStandardButtons( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
181+
connect( buttonBox, SIGNAL( accepted() ), mDialog, SLOT( accept() ) );
182+
connect( buttonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
183+
}
184+
else
185+
{
186+
buttonBox->setStandardButtons( QDialogButtonBox::Cancel );
187+
}
188+
189+
connect( buttonBox, SIGNAL( rejected() ), mDialog, SLOT( reject() ) );
190+
connect( buttonBox, SIGNAL( rejected() ), this, SLOT( rejected() ) );
108191
}
192+
193+
QMetaObject::connectSlotsByName( mDialog );
194+
109195
restoreGeometry();
110196
}
111197

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

118204
void QgsAttributeDialog::accept()
119205
{
206+
if ( !mLayer->isEditable() )
207+
return;
208+
120209
//write the new values back to the feature
121210
QgsAttributeMap myAttributes = mpFeature->attributeMap();
122211
int myIndex = 0;
123-
for ( QgsAttributeMap::const_iterator it = myAttributes.begin();
124-
it != myAttributes.end();
125-
++it )
212+
for ( QgsAttributeMap::const_iterator it = myAttributes.begin(); it != myAttributes.end(); ++it )
126213
{
127214
QVariant value;
128215

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

133220
++myIndex;
134221
}
135-
QDialog::accept();
222+
}
223+
224+
int QgsAttributeDialog::exec()
225+
{
226+
return mDialog->exec();
136227
}
137228

138229
void QgsAttributeDialog::saveGeometry()
139230
{
140231
QSettings settings;
141-
settings.setValue( mSettingsPath + "geometry", QDialog::saveGeometry() );
232+
settings.setValue( mSettingsPath + "geometry", mDialog->saveGeometry() );
142233
}
143234

144235
void QgsAttributeDialog::restoreGeometry()
145236
{
146237
QSettings settings;
147-
QDialog::restoreGeometry( settings.value( mSettingsPath + "geometry" ).toByteArray() );
238+
mDialog->restoreGeometry( settings.value( mSettingsPath + "geometry" ).toByteArray() );
148239
}

0 commit comments

Comments
 (0)