1 /***************************************************************************
2 qgsrelationaddpolymorphicdialog.cpp
3 ---------------------
4 begin : December 2020
5 copyright : (C) 2020 by Ivan Ivanov
6 email : ivan at opengis dot ch
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16 #include <QDialogButtonBox>
17 #include <QPushButton>
18 #include <QToolButton>
19 #include <QComboBox>
20
21 #include "qgsrelationaddpolymorphicdialog.h"
22 #include "qgsvectorlayer.h"
23 #include "qgsmaplayercombobox.h"
24 #include "qgsfieldcombobox.h"
25 #include "qgsmaplayerproxymodel.h"
26 #include "qgsapplication.h"
27 #include "qgshelp.h"
28 #include "qgsproject.h"
29 #include "qgsrelationmanager.h"
30 #include "qgsfieldexpressionwidget.h"
31
QgsRelationAddPolymorphicDialog(bool isEditDialog,QWidget * parent)32 QgsRelationAddPolymorphicDialog::QgsRelationAddPolymorphicDialog( bool isEditDialog, QWidget *parent )
33 : QDialog( parent )
34 , Ui::QgsRelationManagerAddPolymorphicDialogBase()
35 , mIsEditDialog( isEditDialog )
36 {
37 setupUi( this );
38
39 setWindowTitle( mIsEditDialog
40 ? tr( "Edit Polymorphic Relation" )
41 : tr( "Add Polymorphic Relation" ) );
42
43 mButtonBox->setStandardButtons( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
44 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsRelationAddPolymorphicDialog::accept );
45 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsRelationAddPolymorphicDialog::reject );
46 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, [ = ]
47 {
48 QgsHelp::openHelp( QStringLiteral( "working_with_vector/attribute_table.html#defining-polymorphic-relations" ) );
49 } );
50
51 const QVector<QgsVectorLayer *> layers = QgsProject::instance()->layers<QgsVectorLayer *>();
52 for ( const QgsMapLayer *vl : layers )
53 {
54 if ( !vl || !vl->isValid() )
55 continue;
56
57 mReferencedLayersComboBox->addItem( vl->name(), vl->id() );
58 }
59
60 mRelationStrengthComboBox->addItem( tr( "Association" ), QVariant::fromValue( QgsRelation::RelationStrength::Association ) );
61 mRelationStrengthComboBox->addItem( tr( "Composition" ), QVariant::fromValue( QgsRelation::RelationStrength::Composition ) );
62 mRelationStrengthComboBox->setToolTip( tr( "When composition is selected the child features will be duplicated too.\n"
63 "Duplications are made by the feature duplication action.\n"
64 "The default actions are activated in the Action section of the layer properties." ) );
65
66 mFieldsMappingWidget->setEnabled( false );
67 addFieldsRow();
68 updateTypeConfigWidget();
69 updateDialogButtons();
70 updateReferencedLayerFieldComboBox();
71
72 connect( mFieldsMappingTable, &QTableWidget::itemSelectionChanged, this, &QgsRelationAddPolymorphicDialog::updateFieldsMappingButtons );
73 connect( mFieldsMappingAddButton, &QToolButton::clicked, this, &QgsRelationAddPolymorphicDialog::addFieldsRow );
74 connect( mFieldsMappingRemoveButton, &QToolButton::clicked, this, &QgsRelationAddPolymorphicDialog::removeFieldsRow );
75 connect( mReferencingLayerComboBox, &QgsMapLayerComboBox::layerChanged, this, &QgsRelationAddPolymorphicDialog::updateDialogButtons );
76 connect( mRelationStrengthComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, [ = ]( int index ) { Q_UNUSED( index ); updateDialogButtons(); } );
77 connect( mReferencedLayerExpressionWidget, static_cast<void ( QgsFieldExpressionWidget::* )( const QString & )>( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsRelationAddPolymorphicDialog::updateDialogButtons );
78 connect( mReferencedLayersComboBox, &QgsCheckableComboBox::checkedItemsChanged, this, &QgsRelationAddPolymorphicDialog::referencedLayersChanged );
79 connect( mReferencingLayerComboBox, &QgsMapLayerComboBox::layerChanged, this, &QgsRelationAddPolymorphicDialog::updateChildRelationsComboBox );
80 connect( mReferencingLayerComboBox, &QgsMapLayerComboBox::layerChanged, this, &QgsRelationAddPolymorphicDialog::updateReferencingFieldsComboBoxes );
81 connect( mReferencingLayerComboBox, &QgsMapLayerComboBox::layerChanged, this, &QgsRelationAddPolymorphicDialog::updateReferencedLayerFieldComboBox );
82 }
83
setPolymorphicRelation(const QgsPolymorphicRelation polyRel)84 void QgsRelationAddPolymorphicDialog::setPolymorphicRelation( const QgsPolymorphicRelation polyRel )
85 {
86 mIdLineEdit->setText( polyRel.id() );
87 mReferencingLayerComboBox->setLayer( polyRel.referencingLayer() );
88 mReferencedLayerFieldComboBox->setLayer( polyRel.referencingLayer() );
89 mReferencedLayerFieldComboBox->setField( polyRel.referencedLayerField() );
90 mReferencedLayerExpressionWidget->setExpression( polyRel.referencedLayerExpression() );
91 mRelationStrengthComboBox->setCurrentIndex( mRelationStrengthComboBox->findData( polyRel.strength() ) );
92
93 const QStringList layerIds = polyRel.referencedLayerIds();
94 for ( const QString &layerId : layerIds )
95 mReferencedLayersComboBox->setItemCheckState( mReferencedLayersComboBox->findData( layerId ), Qt::Checked );
96 referencedLayersChanged();
97
98 int index = 0;
99 const QList<QgsRelation::FieldPair> fieldPairs = polyRel.fieldPairs();
100 for ( const QgsRelation::FieldPair &fieldPair : fieldPairs )
101 {
102 qobject_cast<QComboBox *>( mFieldsMappingTable->cellWidget( index, 0 ) )->setCurrentText( fieldPair.referencedField() );
103 qobject_cast<QgsFieldComboBox *>( mFieldsMappingTable->cellWidget( index, 1 ) )->setCurrentText( fieldPair.referencingField() );
104 index++;
105 }
106 }
107
updateTypeConfigWidget()108 void QgsRelationAddPolymorphicDialog::updateTypeConfigWidget()
109 {
110 updateDialogButtons();
111 }
112
addFieldsRow()113 void QgsRelationAddPolymorphicDialog::addFieldsRow()
114 {
115 QgsFieldComboBox *referencingField = new QgsFieldComboBox( this );
116 QComboBox *referencedPolymorphicField = new QComboBox( this );
117 int index = mFieldsMappingTable->rowCount();
118
119 referencingField->setLayer( mReferencingLayerComboBox->currentLayer() );
120
121 mFieldsMappingTable->insertRow( index );
122 mFieldsMappingTable->setCellWidget( index, 0, referencedPolymorphicField );
123 mFieldsMappingTable->setCellWidget( index, 1, referencingField );
124
125 updateFieldsMappingButtons();
126 updateFieldsMappingHeaders();
127 updateDialogButtons();
128 }
129
removeFieldsRow()130 void QgsRelationAddPolymorphicDialog::removeFieldsRow()
131 {
132 if ( mFieldsMappingTable->selectionModel()->hasSelection() )
133 {
134 for ( const QModelIndex &index : mFieldsMappingTable->selectionModel()->selectedRows() )
135 {
136 if ( index.row() == 0 )
137 continue;
138
139 if ( mFieldsMappingTable->rowCount() > 1 )
140 mFieldsMappingTable->removeRow( index.row() );
141 }
142 }
143 else
144 {
145 mFieldsMappingTable->removeRow( mFieldsMappingTable->rowCount() - 1 );
146 }
147
148 updateFieldsMappingButtons();
149 updateFieldsMappingHeaders();
150 updateDialogButtons();
151 }
152
updateFieldsMappingButtons()153 void QgsRelationAddPolymorphicDialog::updateFieldsMappingButtons()
154 {
155 int rowsCount = mFieldsMappingTable->rowCount();
156 int selectedRowsCount = mFieldsMappingTable->selectionModel()->selectedRows().count();
157 bool isRemoveButtonEnabled = selectedRowsCount <= rowsCount - 2;
158
159 mFieldsMappingRemoveButton->setEnabled( isRemoveButtonEnabled );
160 }
161
updateFieldsMappingHeaders()162 void QgsRelationAddPolymorphicDialog::updateFieldsMappingHeaders()
163 {
164 int rowsCount = mFieldsMappingTable->rowCount();
165 QStringList verticalHeaderLabels;
166
167 for ( int i = 0; i < rowsCount; i++ )
168 verticalHeaderLabels << tr( "Field %1" ).arg( i + 1 );
169
170 mFieldsMappingTable->setVerticalHeaderLabels( verticalHeaderLabels );
171 }
172
referencingLayerId()173 QString QgsRelationAddPolymorphicDialog::referencingLayerId()
174 {
175 return mReferencingLayerComboBox->currentLayer()->id();
176 }
177
referencedLayerField()178 QString QgsRelationAddPolymorphicDialog::referencedLayerField()
179 {
180 return mReferencedLayerFieldComboBox->currentField();
181 }
182
referencedLayerExpression()183 QString QgsRelationAddPolymorphicDialog::referencedLayerExpression()
184 {
185 return mReferencedLayerExpressionWidget->expression();
186 }
187
referencedLayerIds()188 QStringList QgsRelationAddPolymorphicDialog::referencedLayerIds()
189 {
190 return QVariant( mReferencedLayersComboBox->checkedItemsData() ).toStringList();
191 }
192
fieldPairs()193 QList< QPair< QString, QString > > QgsRelationAddPolymorphicDialog::fieldPairs()
194 {
195 QList< QPair< QString, QString > > references;
196 for ( int i = 0, l = mFieldsMappingTable->rowCount(); i < l; i++ )
197 {
198 QComboBox *referencedFieldComboBox = qobject_cast<QComboBox *>( mFieldsMappingTable->cellWidget( i, 0 ) );
199 if ( referencedFieldComboBox->currentData().toInt() == -1 )
200 continue;
201 QString referencedField = referencedFieldComboBox->currentText();
202 QString referencingField = qobject_cast<QgsFieldComboBox *>( mFieldsMappingTable->cellWidget( i, 1 ) )->currentField();
203 references << qMakePair( referencingField, referencedField );
204 }
205
206 return references;
207 }
208
relationId()209 QString QgsRelationAddPolymorphicDialog::relationId()
210 {
211 return mIdLineEdit->text();
212 }
213
relationName()214 QString QgsRelationAddPolymorphicDialog::relationName()
215 {
216 QgsVectorLayer *vl = static_cast<QgsVectorLayer *>( mReferencingLayerComboBox->currentLayer() );
217 return tr( "Polymorphic relations for \"%1\"" ).arg( vl ? vl->name() : QStringLiteral( "<NO LAYER>" ) );
218 }
219
relationStrength()220 QgsRelation::RelationStrength QgsRelationAddPolymorphicDialog::relationStrength()
221 {
222 return mRelationStrengthComboBox->currentData().value<QgsRelation::RelationStrength>();
223 }
224
updateDialogButtons()225 void QgsRelationAddPolymorphicDialog::updateDialogButtons()
226 {
227 mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( isDefinitionValid() );
228 }
229
isDefinitionValid()230 bool QgsRelationAddPolymorphicDialog::isDefinitionValid()
231 {
232 bool isValid = true;
233 return isValid;
234 QgsMapLayer *referencedLayer = mReferencingLayerComboBox->currentLayer();
235 isValid &= referencedLayer && referencedLayer->isValid();
236
237 for ( int i = 0, l = mFieldsMappingTable->rowCount(); i < l; i++ )
238 {
239 isValid &= qobject_cast<QComboBox *>( mFieldsMappingTable->cellWidget( i, 0 ) )->currentData().toInt() != -1;
240 isValid &= !qobject_cast<QgsFieldComboBox *>( mFieldsMappingTable->cellWidget( i, 1 ) )->currentField().isNull();
241 }
242
243 return isValid;
244 }
245
updateChildRelationsComboBox()246 void QgsRelationAddPolymorphicDialog::updateChildRelationsComboBox()
247 {
248 QgsVectorLayer *vl = static_cast<QgsVectorLayer *>( mReferencingLayerComboBox->currentLayer() );
249 if ( !vl || !vl->isValid() )
250 return;
251
252 QStringList relationIdsList;
253
254 const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( vl );
255 for ( const QgsRelation &relation : relations )
256 {
257 if ( !relation.isValid() )
258 continue;
259
260 if ( relation.referencingLayer() != vl )
261 continue;
262
263 relationIdsList << relationName();
264 }
265 }
266
updateReferencingFieldsComboBoxes()267 void QgsRelationAddPolymorphicDialog::updateReferencingFieldsComboBoxes()
268 {
269 QgsMapLayer *vl = mReferencingLayerComboBox->currentLayer();
270 if ( !vl || !vl->isValid() )
271 return;
272
273 for ( int i = 0, l = mFieldsMappingTable->rowCount(); i < l; i++ )
274 {
275 auto fieldComboBox = qobject_cast<QgsFieldComboBox *>( mFieldsMappingTable->cellWidget( i, 1 ) );
276 fieldComboBox->setLayer( vl );
277 }
278 }
279
updateReferencedLayerFieldComboBox()280 void QgsRelationAddPolymorphicDialog::updateReferencedLayerFieldComboBox()
281 {
282 mReferencedLayerFieldComboBox->setLayer( mReferencingLayerComboBox->currentLayer() );
283 }
284
referencedLayersChanged()285 void QgsRelationAddPolymorphicDialog::referencedLayersChanged()
286 {
287 const QStringList &layerIds = referencedLayerIds();
288 mFieldsMappingWidget->setEnabled( layerIds.count() > 0 );
289
290 bool firstLayer = true;
291 QSet<QString> fields;
292 for ( const QString &layerId : layerIds )
293 {
294 QgsVectorLayer *vl = QgsProject::instance()->mapLayer<QgsVectorLayer *>( layerId );
295 if ( vl && vl->isValid() )
296 {
297 const QSet layerFields = qgis::listToSet( vl->fields().names() );
298 if ( firstLayer )
299 {
300 fields = layerFields;
301 firstLayer = false;
302 }
303 else
304 {
305 fields.intersect( layerFields );
306 }
307 }
308 }
309
310 for ( int i = 0, l = mFieldsMappingTable->rowCount(); i < l; i++ )
311 {
312 QComboBox *cb = qobject_cast<QComboBox *>( mFieldsMappingTable->cellWidget( i, 0 ) );
313 const QString currentField = cb->currentText();
314 cb->clear();
315 if ( fields.count() > 0 )
316 {
317 const QSet<QString> constFields = fields;
318 for ( const QString &field : constFields )
319 {
320 cb->addItem( field );
321 if ( field == currentField )
322 cb->setCurrentText( field );
323 }
324 }
325 else
326 {
327 cb->addItem( tr( "None" ), -1 );
328 cb->addItem( tr( "the referenced layers have no common fields." ), -1 );
329 QStandardItem *item = qobject_cast<QStandardItemModel *>( cb->model() )->item( 1 );
330 item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
331 }
332 }
333
334 updateDialogButtons();
335 }
336