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