1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2003-2020 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5 
6 #include "umlforeignkeyconstraintdialog.h"
7 
8 #include "attribute.h"
9 #include "classifier.h"
10 #include "classifierlistitem.h"
11 #include "debug_utils.h"
12 #include "dialog_utils.h"
13 #include "entityattribute.h"
14 #include "enumliteral.h"
15 #include "enum.h"
16 #include "entity.h"
17 #include "foreignkeyconstraint.h"
18 #include "object_factory.h"
19 #include "operation.h"
20 #include "template.h"
21 #include "uml.h"
22 #include "umldoc.h"
23 #include "umlentitylist.h"
24 #include "uniqueconstraint.h"
25 #include "icon_utils.h"
26 
27 #include <kcombobox.h>
28 #if QT_VERSION < 0x050000
29 #include <kdialogbuttonbox.h>
30 #endif
31 #include <klineedit.h>
32 #include <KLocalizedString>
33 #include <KMessageBox>
34 
35 #include <QApplication>
36 #include <QGridLayout>
37 #include <QGroupBox>
38 #include <QHBoxLayout>
39 #include <QLabel>
40 #include <QPushButton>
41 #include <QTreeWidget>
42 #include <QVBoxLayout>
43 
44 /**
45  *  Sets up the UMLForeignKeyConstraintDialog
46  *
47  *  @param parent   The parent to the UMLForeignKeyConstraintDialog.
48  *  @param pForeignKeyConstraint The Unique Constraint to show the properties of
49  */
UMLForeignKeyConstraintDialog(QWidget * parent,UMLForeignKeyConstraint * pForeignKeyConstraint)50 UMLForeignKeyConstraintDialog::UMLForeignKeyConstraintDialog(QWidget* parent, UMLForeignKeyConstraint* pForeignKeyConstraint)
51   : MultiPageDialogBase(parent),
52     m_doc(UMLApp::app()->document()),
53     m_pForeignKeyConstraint(pForeignKeyConstraint)
54 {
55     setCaption(i18n("Foreign Key Setup"));
56     setupGeneralPage();
57     setupColumnPage();
58 
59     connect(this, SIGNAL(okClicked()), this, SLOT(slotOk()));
60     connect(this, SIGNAL(applyClicked()), this, SLOT(slotApply()));
61 }
62 
63 /**
64  * Standard destructor.
65  */
~UMLForeignKeyConstraintDialog()66 UMLForeignKeyConstraintDialog::~UMLForeignKeyConstraintDialog()
67 {
68 }
69 
70 /**
71  * Adds pair to the list.
72  */
slotAddPair()73 void UMLForeignKeyConstraintDialog::slotAddPair()
74 {
75     // get the index of the selected local column and referenced column
76     int indexL = m_ColumnWidgets.localColumnCB->currentIndex();
77     int indexR = m_ColumnWidgets.referencedColumnCB->currentIndex();
78 
79     if (indexL == -1 || indexR == -1) {
80         return;
81     }
82 
83     // local entity attribute
84     UMLEntityAttribute* localColumn = m_pLocalAttributeList.at(indexL);
85     // referenced entity attribute
86     UMLEntityAttribute* referencedColumn = m_pReferencedAttributeList.at(indexR);
87 
88     // remove from combo boxes
89     m_ColumnWidgets.localColumnCB->removeItem(indexL);
90     m_ColumnWidgets.referencedColumnCB->removeItem(indexR);
91 
92     // remove from local cache
93     m_pLocalAttributeList.removeAt(indexL);
94     m_pReferencedAttributeList.removeAt(indexR);
95 
96     // add to local cache of mapping
97     EntityAttributePair pair = qMakePair(localColumn, referencedColumn);
98     m_pAttributeMapList.append(pair);
99     // update mapping view
100 
101     QTreeWidgetItem* mapping = new QTreeWidgetItem(m_ColumnWidgets.mappingTW);
102     mapping->setText(0, localColumn->toString(Uml::SignatureType::SigNoVis));
103     mapping->setText(1, referencedColumn->toString(Uml::SignatureType::SigNoVis));
104 
105     m_ColumnWidgets.mappingTW->addTopLevelItem(mapping);
106 
107     slotResetWidgetState();
108 }
109 
110 /**
111  * Deletes a pair from the list.
112  */
slotDeletePair()113 void UMLForeignKeyConstraintDialog::slotDeletePair()
114 {
115     // get the index of the selected pair in the view
116     QTreeWidgetItem* twi = m_ColumnWidgets.mappingTW->currentItem();
117     int indexP = m_ColumnWidgets.mappingTW->indexOfTopLevelItem(twi);
118     if (indexP == -1) {
119         return;
120     }
121 
122     //find pair in local cache
123     EntityAttributePair pair = m_pAttributeMapList.at(indexP);
124 
125     // remove them from the view and the list
126     m_ColumnWidgets.mappingTW->takeTopLevelItem(indexP);
127     m_pAttributeMapList.removeAt(indexP);
128 
129     // add the attributes to the local caches
130     m_pLocalAttributeList.append(pair.first);
131     m_pReferencedAttributeList.append(pair.second);
132 
133     // add them to the view (combo boxes)
134     uDebug() << (pair.first) << (pair.second);
135     m_ColumnWidgets.localColumnCB->addItem((pair.first)->toString(Uml::SignatureType::SigNoVis));
136     m_ColumnWidgets.referencedColumnCB->addItem((pair.second)->toString(Uml::SignatureType::SigNoVis));
137 
138     foreach(const EntityAttributePair& p, m_pAttributeMapList) {
139         uDebug() << (p.first)->name() << " " << (p.first)->baseType() << " "
140                  << (p.second)->name() << " " << (p.second)->baseType();
141     }
142 
143     slotResetWidgetState();
144 }
145 
146 /**
147  * Checks if changes are valid and applies them if they are,
148  * else returns false.
149  */
apply()150 bool UMLForeignKeyConstraintDialog::apply()
151 {
152     // set the Referenced Entity
153     QString entityName = m_GeneralWidgets.referencedEntityCB->currentText();
154     UMLObject* uo = m_doc->findUMLObjectRecursive(Uml::ModelType::EntityRelationship,
155                                                   entityName,
156                                                   UMLObject::ot_Entity);
157 
158     UMLEntity* ue = uo->asUMLEntity();
159 
160     if (ue == 0) {
161         uDebug() << " Could not find UML Entity with name " << entityName;
162         return false;
163     }
164 
165     m_pForeignKeyConstraint->setReferencedEntity(ue);
166 
167     // set all the update and delete actions
168     UMLForeignKeyConstraint::UpdateDeleteAction updateAction, deleteAction;
169     updateAction = (UMLForeignKeyConstraint::UpdateDeleteAction) m_GeneralWidgets.updateActionCB->currentIndex();
170     deleteAction = (UMLForeignKeyConstraint::UpdateDeleteAction) m_GeneralWidgets.deleteActionCB->currentIndex();
171     m_pForeignKeyConstraint->setUpdateAction(updateAction);
172     m_pForeignKeyConstraint->setDeleteAction(deleteAction);
173 
174     // remove all existing mappings first
175     m_pForeignKeyConstraint->clearMappings();
176 
177     // add all mappings  present in local cache
178     foreach(const EntityAttributePair& pair, m_pAttributeMapList) {
179         if (!m_pForeignKeyConstraint->addEntityAttributePair(pair.first, pair.second)) {
180             return false;
181         }
182     }
183 
184     // set the name
185     m_pForeignKeyConstraint->setName(m_GeneralWidgets.nameT->text());
186 
187     // propagate changes to tree view
188     m_pForeignKeyConstraint->emitModified();
189 
190     return true;
191 }
192 
193 /**
194  * Setup the General Page.
195  */
setupGeneralPage()196 void UMLForeignKeyConstraintDialog::setupGeneralPage()
197 {
198     //setup General page
199     QWidget* page = new QWidget();
200     QVBoxLayout* topLayout = new QVBoxLayout();
201     page->setLayout(topLayout);
202 
203     pageGeneral = createPage(i18nc("general page title", "General"), i18n("General Settings"),
204                              Icon_Utils::it_Properties_General, page);
205 
206     m_GeneralWidgets.generalGB = new QGroupBox(i18nc("general group title", "General"));
207     topLayout->addWidget(m_GeneralWidgets.generalGB);
208 
209     QGridLayout* generalLayout = new QGridLayout(m_GeneralWidgets.generalGB);
210     generalLayout->setSpacing(spacingHint());
211     generalLayout->setMargin(fontMetrics().height());
212 
213     Dialog_Utils::makeLabeledEditField(generalLayout, 0,
214                                        m_GeneralWidgets.nameL, i18nc("label for entering name", "Name"),
215                                        m_GeneralWidgets.nameT);
216 
217     m_GeneralWidgets.referencedEntityL = new QLabel(i18n("Referenced Entity"));
218     generalLayout->addWidget(m_GeneralWidgets.referencedEntityL, 1, 0);
219 
220     m_GeneralWidgets.referencedEntityCB = new KComboBox();
221     generalLayout->addWidget(m_GeneralWidgets.referencedEntityCB, 1, 1);
222 
223     m_GeneralWidgets.actionGB = new QGroupBox(i18n("Actions"));
224     topLayout->addWidget(m_GeneralWidgets.actionGB);
225 
226     QGridLayout* actionLayout = new QGridLayout(m_GeneralWidgets.actionGB);
227     generalLayout->setSpacing(spacingHint());
228     generalLayout->setMargin(fontMetrics().height());
229 
230     m_GeneralWidgets.onUpdateL = new QLabel(i18n("On Update"));
231     actionLayout->addWidget(m_GeneralWidgets.onUpdateL, 0, 0);
232 
233     m_GeneralWidgets.updateActionCB = new KComboBox(page);
234     actionLayout->addWidget(m_GeneralWidgets.updateActionCB, 0, 1);
235 
236     m_GeneralWidgets.onDeleteL = new QLabel(i18n("On Delete"));
237     actionLayout->addWidget(m_GeneralWidgets.onDeleteL, 1, 0);
238 
239     m_GeneralWidgets.deleteActionCB = new KComboBox();
240     actionLayout->addWidget(m_GeneralWidgets.deleteActionCB, 1, 1);
241 
242     // set the name
243     m_GeneralWidgets.nameT->setText(m_pForeignKeyConstraint->name());
244 
245     // fill up the combo boxes
246 
247     // reference entity combo box
248     UMLEntityList entList = m_doc->entities();
249 
250     foreach(UMLEntity* ent, entList) {
251         m_GeneralWidgets.referencedEntityCB->addItem(ent->name());
252     }
253 
254     UMLEntity* referencedEntity = m_pForeignKeyConstraint->getReferencedEntity();
255 
256     int index;
257     if (referencedEntity != 0) {
258         index = m_GeneralWidgets.referencedEntityCB->findText(referencedEntity->name());
259         if (index != -1)
260             m_GeneralWidgets.referencedEntityCB->setCurrentIndex(index);
261     }
262     m_pReferencedEntityIndex = m_GeneralWidgets.referencedEntityCB->currentIndex();
263 
264     // action combo boxes
265 
266     // do not change order. It is according to enum specification in foreignkeyconstraint.h
267     QStringList actions;
268     actions << i18n("No Action") << i18n("Restrict") << i18n("Cascade") << i18n("Set Null")
269             << i18n("Set Default");
270 
271     m_GeneralWidgets.updateActionCB->addItems(actions);
272     m_GeneralWidgets.deleteActionCB->addItems(actions);
273 
274     m_GeneralWidgets.updateActionCB->setCurrentIndex(m_pForeignKeyConstraint->getUpdateAction());
275     m_GeneralWidgets.deleteActionCB->setCurrentIndex(m_pForeignKeyConstraint->getDeleteAction());
276 
277     connect(m_GeneralWidgets.referencedEntityCB, SIGNAL(activated(int)), this, SLOT(slotReferencedEntityChanged(int)));
278 }
279 
280 /**
281  * Setup Column Page.
282  */
setupColumnPage()283 void UMLForeignKeyConstraintDialog::setupColumnPage()
284 {
285     //setup Columns page
286     QWidget* page = new QWidget();
287     QVBoxLayout* topLayout = new QVBoxLayout();
288     page->setLayout(topLayout);
289 
290     pageColumn = createPage(i18n("Columns"), i18n("Columns"),
291                             Icon_Utils::it_Properties_Columns, page);
292 
293     m_ColumnWidgets.mappingTW = new QTreeWidget();
294     topLayout->addWidget(m_ColumnWidgets.mappingTW);
295 
296     QStringList headers;
297     headers << i18nc("column header local", "Local") << i18nc("column header referenced", "Referenced");
298     m_ColumnWidgets.mappingTW->setHeaderLabels(headers);
299 
300     QWidget* columns = new QWidget();
301     topLayout->addWidget(columns);
302     QGridLayout* columnsLayout = new QGridLayout(columns);
303 
304     m_ColumnWidgets.localColumnL = new QLabel(i18n("Local Column"));
305     columnsLayout->addWidget(m_ColumnWidgets.localColumnL, 0, 0);
306 
307     m_ColumnWidgets.localColumnCB = new KComboBox();
308     columnsLayout->addWidget(m_ColumnWidgets.localColumnCB, 0, 1);
309 
310     m_ColumnWidgets.referencedColumnL = new QLabel(i18n("Referenced Column"));
311     columnsLayout->addWidget(m_ColumnWidgets.referencedColumnL, 1, 0);
312 
313     m_ColumnWidgets.referencedColumnCB = new KComboBox();
314     columnsLayout->addWidget(m_ColumnWidgets.referencedColumnCB, 1, 1);
315 
316 #if QT_VERSION >= 0x050000
317     QDialogButtonBox* buttonBox = new QDialogButtonBox();
318     m_ColumnWidgets.addPB = buttonBox->addButton(i18n("&Add"), QDialogButtonBox::ActionRole);
319     connect(m_ColumnWidgets.addPB, SIGNAL(clicked()), this, SLOT(slotAddPair()));
320     m_ColumnWidgets.removePB = buttonBox->addButton(i18n("&Delete"), QDialogButtonBox::ActionRole);
321     connect(m_ColumnWidgets.removePB, SIGNAL(clicked()), this, SLOT(slotDeletePair()));
322 #else
323     KDialogButtonBox* buttonBox = new KDialogButtonBox(page);
324     m_ColumnWidgets.addPB = buttonBox->addButton(i18n("&Add"), KDialogButtonBox::ActionRole, this,
325                             SLOT(slotAddPair()));
326     m_ColumnWidgets.removePB = buttonBox->addButton(i18n("&Delete"), KDialogButtonBox::ActionRole, this,
327                                SLOT(slotDeletePair()));
328 #endif
329 
330     columnsLayout->addWidget(buttonBox, 2, 1);
331 
332     // fill the column boxes and their local cache.
333     refillLocalAttributeCB();
334     refillReferencedAttributeCB();
335 
336     QMap<UMLEntityAttribute*, UMLEntityAttribute*>::iterator i;
337     QMap<UMLEntityAttribute*, UMLEntityAttribute*>  map = m_pForeignKeyConstraint->getEntityAttributePairs();
338     for (i = map.begin(); i != map.end() ; ++i) {
339 
340         UMLEntityAttribute* localColumn, *referencedColumn;
341 
342         localColumn = const_cast<UMLEntityAttribute*>(i.key());
343         referencedColumn = const_cast<UMLEntityAttribute*>(i.value());
344 
345         // remove these columns from local cache
346         int indexL = m_pLocalAttributeList.indexOf(localColumn);
347         int indexR = m_pReferencedAttributeList.indexOf(referencedColumn);
348 
349         m_pLocalAttributeList.removeAt(indexL);
350         m_pReferencedAttributeList.removeAt(indexR);
351 
352         // remove them from combo boxes
353         // the conditions may never be violated . Just for safety though
354         if (indexL >= 0 && indexL < (m_ColumnWidgets.localColumnCB)->count())
355             m_ColumnWidgets.localColumnCB->removeItem(indexL);
356         if (indexR >= 0 && indexR < (m_ColumnWidgets.referencedColumnCB)->count())
357             m_ColumnWidgets.referencedColumnCB->removeItem(indexR);
358 
359         // add to local cache
360         m_pAttributeMapList.append(qMakePair(localColumn, referencedColumn));
361 
362         // add to view
363         QTreeWidgetItem* mapping = new QTreeWidgetItem(m_ColumnWidgets.mappingTW);
364         mapping->setText(0, localColumn->toString(Uml::SignatureType::SigNoVis));
365         mapping->setText(1, referencedColumn->toString(Uml::SignatureType::SigNoVis));
366 
367         m_ColumnWidgets.mappingTW->insertTopLevelItem(0, mapping);
368     }
369 
370     slotResetWidgetState();
371 
372     connect(m_ColumnWidgets.mappingTW, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(slotResetWidgetState()));
373 }
374 
375 /**
376  * Used when the Apply button is clicked.
377  */
slotApply()378 void UMLForeignKeyConstraintDialog::slotApply()
379 {
380     apply();
381 }
382 
383 /**
384  * Used when the OK button is clicked.  Calls apply().
385  */
slotOk()386 void UMLForeignKeyConstraintDialog::slotOk()
387 {
388     if (apply()) {
389         accept();
390     }
391 }
392 
slotReferencedEntityChanged(int index)393 void UMLForeignKeyConstraintDialog::slotReferencedEntityChanged(int index)
394 {
395     if (index == m_pReferencedEntityIndex) {
396         return;
397     }
398 
399     if (!m_pAttributeMapList.empty()) {
400         int result = KMessageBox::questionYesNo(this, i18n("You are attempting to change the Referenced Entity of this ForeignKey Constraint. Any unapplied changes to the mappings between local and referenced entities will be lost. Are you sure you want to continue ?"));
401 
402         if (result != KMessageBox::Yes) {
403             // revert back to old index
404             m_GeneralWidgets.referencedEntityCB->setCurrentIndex(m_pReferencedEntityIndex);
405             return;
406         }
407     }
408 
409     // set the referenced entity index to current index
410     m_pReferencedEntityIndex = index;
411     m_ColumnWidgets.mappingTW->clear();
412     refillReferencedAttributeCB();
413     refillLocalAttributeCB();
414 }
415 
refillReferencedAttributeCB()416 void UMLForeignKeyConstraintDialog::refillReferencedAttributeCB()
417 {
418     m_pReferencedAttributeList.clear();
419     m_ColumnWidgets.referencedColumnCB->clear();
420 
421     // fill the combo boxes
422 
423     UMLObject* uo = m_doc->findUMLObjectRecursive(Uml::ModelType::EntityRelationship,
424                                                   m_GeneralWidgets.referencedEntityCB->currentText(),
425                                                   UMLObject::ot_Entity);
426 
427     UMLEntity* ue = uo->asUMLEntity();
428 
429     if (ue) {
430         UMLClassifierListItemList ual = ue->getFilteredList(UMLObject::ot_EntityAttribute);
431         foreach(UMLClassifierListItem* att, ual) {
432             m_pReferencedAttributeList.append(att->asUMLEntityAttribute());
433             m_ColumnWidgets.referencedColumnCB->addItem(att->toString(Uml::SignatureType::SigNoVis));
434         }
435     }
436 }
437 
refillLocalAttributeCB()438 void UMLForeignKeyConstraintDialog::refillLocalAttributeCB()
439 {
440     m_pLocalAttributeList.clear();
441     m_ColumnWidgets.localColumnCB->clear();
442     // fill the combo boxes
443     UMLEntity* ue = m_pForeignKeyConstraint->umlParent()->asUMLEntity();
444 
445     if (ue) {
446         UMLClassifierListItemList ual = ue->getFilteredList(UMLObject::ot_EntityAttribute);
447         foreach(UMLClassifierListItem* att, ual) {
448             m_pLocalAttributeList.append(att->asUMLEntityAttribute());
449             m_ColumnWidgets.localColumnCB->addItem(att->toString(Uml::SignatureType::SigNoVis));
450         }
451     }
452 }
453 
454 /**
455  * Enable/Disable the widgets in the Dialog Box.
456  */
slotResetWidgetState()457 void UMLForeignKeyConstraintDialog::slotResetWidgetState()
458 {
459     m_ColumnWidgets.addPB->setEnabled(true);
460     m_ColumnWidgets.removePB->setEnabled(true);
461     m_ColumnWidgets.localColumnCB->setEnabled(true);
462     m_ColumnWidgets.referencedColumnCB->setEnabled(true);
463 
464     // If one of the Combo Boxes is empty, then disable the Combo Box
465     if (m_ColumnWidgets.localColumnCB->count() == 0 || m_ColumnWidgets.referencedColumnCB->count() == 0) {
466         m_ColumnWidgets.localColumnCB->setEnabled(false);
467         m_ColumnWidgets.referencedColumnCB->setEnabled(false);
468         m_ColumnWidgets.addPB->setEnabled(false);
469     }
470 
471     // get index of selected Attribute in List Box
472     if (m_ColumnWidgets.mappingTW->currentItem() == 0) {
473         m_ColumnWidgets.removePB->setEnabled(false);
474     }
475 }
476 
477 
478