1 /*
2 SPDX-License-Identifier: GPL-2.0-or-later
3 SPDX-FileCopyrightText: 2002-2021 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5
6 // own header
7 #include "umlclipboard.h"
8
9 // local includes
10 #include "debug_utils.h"
11 #include "diagram_utils.h"
12 #include "umldragdata.h"
13 #include "idchangelog.h"
14 #include "associationwidget.h"
15 #include "attribute.h"
16 #include "classifier.h"
17 #include "enum.h"
18 #include "entity.h"
19 #include "floatingtextwidget.h"
20 #include "messagewidget.h"
21 #include "operation.h"
22 #include "template.h"
23 #include "enumliteral.h"
24 #include "entityattribute.h"
25 #include "model_utils.h"
26 #include "notewidget.h"
27 #include "umldoc.h"
28 #include "umllistview.h"
29 #include "umllistviewitem.h"
30 #include "umlobjectlist.h"
31 #include "umlscene.h"
32 #include "umlview.h"
33 #include "umlwidget.h"
34 #include "uml.h"
35
36 // kde includes
37 #include <KMessageBox>
38 #include <KLocalizedString>
39
40 // qt includes
41 #include <QMimeData>
42 #include <QPixmap>
43
44 /**
45 * Constructor.
46 */
UMLClipboard()47 UMLClipboard::UMLClipboard()
48 {
49 m_type = clip1;
50 }
51
52 /**
53 * Deconstructor.
54 */
~UMLClipboard()55 UMLClipboard::~UMLClipboard()
56 {
57 }
58
59 /**
60 * Copy operation.
61 * @param fromView flag if it is from view
62 * @return the mime data
63 */
copy(bool fromView)64 QMimeData* UMLClipboard::copy(bool fromView/*=false*/)
65 {
66 // Clear previous copied data
67 m_AssociationList.clear();
68 m_ObjectList.clear();
69 m_ViewList.clear();
70
71 UMLDragData *data = 0;
72 QPixmap* png = 0;
73
74 UMLListView * listView = UMLApp::app()->listView();
75
76 if (fromView) {
77 m_type = clip4;
78 UMLView *view = UMLApp::app()->currentView();
79 if (view == 0) {
80 uError() << "UMLApp::app()->currentView() is NULL";
81 return 0;
82 }
83 UMLScene *scene = view->umlScene();
84 if (scene == 0) {
85 uError() << "currentView umlScene() is NULL";
86 return 0;
87 }
88 m_WidgetList = scene->selectedWidgetsExt();
89 //if there is no selected widget then there is no copy action
90 if (!m_WidgetList.count()) {
91 return 0;
92 }
93 m_AssociationList = scene->selectedAssocs();
94 scene->copyAsImage(png);
95
96 // Clip4 needs related widgets.
97 addRelatedWidgets();
98
99 // Clip4 needs UMLObjects because it's possible the UMLObject
100 // is no longer there when pasting this mime data. This happens for
101 // example when using cut-paste or pasting to another Umbrello
102 // instance.
103 fillObjectListForWidgets(m_WidgetList);
104
105 foreach (WidgetBase* widget, m_AssociationList) {
106 if (widget->umlObject() != 0) {
107 m_ObjectList.append(widget->umlObject());
108 }
109 }
110 } else {
111 // The copy action is being performed from the ListView
112 UMLListViewItemList itemsSelected = listView->selectedItems();
113 if (itemsSelected.count() <= 0) {
114 return 0;
115 }
116
117 // Set What type of copy operation are we performing and
118 // also fill m_ViewList with all the selected Diagrams
119 setCopyType(itemsSelected);
120
121 // If we are copying a diagram or part of a diagram, select the items
122 // on the ListView that correspond to a UseCase, Actor or Concept
123 // in the Diagram
124 if (m_type == clip2) {
125 foreach (UMLView* view, m_ViewList) {
126 UMLScene *scene = view->umlScene();
127 if (scene == 0) {
128 uError() << "currentView umlScene() is NULL";
129 continue;
130 }
131 fillObjectListForWidgets(scene->widgetList());
132
133 AssociationWidgetList associations = scene->associationList();
134 foreach (AssociationWidget* association, associations) {
135 if (association->umlObject() != 0) {
136 m_ObjectList.append(association->umlObject());
137 }
138 }
139 }
140 } else {
141 // Clip1, 4 and 5: fill the clip with only the specific objects
142 // selected in the list view
143 if (!fillSelectionLists(itemsSelected)) {
144 return 0;
145 }
146
147 if (itemsSelected.count() <= 0) {
148 return 0;
149 }
150 }
151 }
152
153 int i = 0;
154 switch(m_type) {
155 case clip1:
156 data = new UMLDragData(m_ObjectList);
157 break;
158 case clip2:
159 data = new UMLDragData(m_ObjectList, m_ViewList);
160 break;
161 case clip3:
162 data = new UMLDragData(m_ItemList);
163 break;
164 case clip4:
165 if (png) {
166 UMLView *view = UMLApp::app()->currentView();
167 data = new UMLDragData(m_ObjectList, m_WidgetList,
168 m_AssociationList, *png, view->umlScene());
169 } else {
170 return 0;
171 }
172 break;
173 case clip5:
174 data = new UMLDragData(m_ObjectList, i);
175 // The int i is used to differentiate
176 // which UMLDragData constructor gets called.
177 break;
178 }
179
180 return (QMimeData*)data;
181 }
182
183 /**
184 * Inserts the clipboard's contents.
185 *
186 * @param data Pointer to the MIME format clipboard data.
187 * @return True for successful operation.
188 */
paste(const QMimeData * data)189 bool UMLClipboard::paste(const QMimeData* data)
190 {
191 UMLDoc *doc = UMLApp::app()->document();
192
193 int codingType = UMLDragData::getCodingType(data);
194 if (codingType == 6
195 && UMLApp::app()->currentView()) {
196 return Diagram_Utils::importGraph(data, UMLApp::app()->currentView()->umlScene());
197 }
198 QString mimeType = QLatin1String("application/x-uml-clip") + QString::number(codingType);
199 uDebug() << "Pasting mimeType=" << mimeType << "data=" << data->data(mimeType);
200
201 bool result = false;
202 doc->beginPaste();
203
204 switch (codingType) {
205 case 1:
206 result = pasteClip1(data);
207 break;
208 case 2:
209 result = pasteClip2(data);
210 break;
211 case 3:
212 result = pasteClip3(data);
213 break;
214 case 4:
215 result = pasteClip4(data);
216 break;
217 case 5:
218 result = pasteClip5(data);
219 break;
220 default:
221 break;
222 }
223 doc->endPaste();
224 return result;
225 }
226
227 /**
228 * Fills object list based on a selection of widgets
229 *
230 * @param UMLWidgetList& widgets
231 */
addRelatedWidgets()232 void UMLClipboard::addRelatedWidgets()
233 {
234 UMLWidgetList relatedWidgets;
235 UMLWidget *pWA =0, *pWB = 0;
236
237 foreach (UMLWidget* widget, m_WidgetList) {
238 if (widget->isMessageWidget()) {
239 MessageWidget * pMessage = widget->asMessageWidget();
240 pWA = (UMLWidget*)pMessage->objectWidget(Uml::RoleType::A);
241 pWB = (UMLWidget*)pMessage->objectWidget(Uml::RoleType::B);
242 if (!relatedWidgets.contains(pWA))
243 relatedWidgets.append(pWA);
244 if (!relatedWidgets.contains(pWB))
245 relatedWidgets.append(pWB);
246 }
247 }
248
249 foreach(AssociationWidget *pAssoc, m_AssociationList) {
250 pWA = pAssoc->widgetForRole(Uml::RoleType::A);
251 pWB = pAssoc->widgetForRole(Uml::RoleType::B);
252 if (!relatedWidgets.contains(pWA))
253 relatedWidgets.append(pWA);
254 if (!relatedWidgets.contains(pWB))
255 relatedWidgets.append(pWB);
256 }
257
258 foreach(UMLWidget *widget, relatedWidgets) {
259 if (!m_WidgetList.contains(widget))
260 m_WidgetList.append(widget);
261 }
262 }
263
264 /**
265 * Fills object list based on a selection of widgets
266 *
267 * @param UMLWidgetList& widgets
268 */
fillObjectListForWidgets(const UMLWidgetList & widgets)269 void UMLClipboard::fillObjectListForWidgets(const UMLWidgetList& widgets)
270 {
271 // The order of the packages in the clip matters. So we collect
272 // the packages and add them from the root package to the deeper levels
273 UMLObjectList packages;
274
275 foreach (UMLWidget* widget, widgets) {
276 UMLObject* widgetObject = widget->umlObject();
277 if (widgetObject != 0) {
278 packages.clear();
279
280 UMLPackage* package = widgetObject->umlPackage();
281 while (package != 0) {
282 packages.prepend(package);
283 package = package->umlPackage();
284 }
285
286 foreach (UMLObject* package, packages) {
287 if (!m_ObjectList.contains(package)) {
288 m_ObjectList.append(package);
289 }
290 }
291
292 if (!m_ObjectList.contains(widgetObject)) {
293 m_ObjectList.append(widgetObject);
294 }
295 }
296 }
297 }
298
299 /**
300 * Fills the member lists with all the objects and other
301 * stuff to be copied to the clipboard.
302 * @param selectedItems list of selected items
303 */
fillSelectionLists(UMLListViewItemList & selectedItems)304 bool UMLClipboard::fillSelectionLists(UMLListViewItemList& selectedItems)
305 {
306 UMLListViewItem::ListViewType type;
307 switch(m_type) {
308 case clip4:
309 break;
310 case clip3:
311 foreach (UMLListViewItem* item, selectedItems) {
312 type = item->type();
313 if (!Model_Utils::typeIsClassifierList(type)) {
314 m_ItemList.append(item);
315 insertItemChildren(item, selectedItems);
316 }
317 }
318 break;
319 case clip2:
320 case clip1:
321 foreach (UMLListViewItem* item, selectedItems) {
322 type = item->type();
323 if (!Model_Utils::typeIsClassifierList(type)) {
324 if (Model_Utils::typeIsCanvasWidget(type)) {
325 if (item->umlObject() == nullptr)
326 uError() << "UMLClipboard::fillSelectionLists: selected lvitem has no umlObject";
327 else
328 m_ObjectList.append(item->umlObject());
329 }
330 insertItemChildren(item, selectedItems);
331 }
332 }
333 break;
334 case clip5:
335 foreach (UMLListViewItem* item, selectedItems) {
336 type = item->type();
337 if(Model_Utils::typeIsClassifierList(type)) {
338 m_ObjectList.append(item->umlObject());
339 } else {
340 return false;
341 }
342 }
343 break;
344 }
345
346 return true;
347 }
348
349 /**
350 * Checks the whole list to determine the copy action
351 * type to be performed, sets the type in the m_type
352 * member variable.
353 * @param selectedItems list of selected items
354 */
setCopyType(UMLListViewItemList & selectedItems)355 void UMLClipboard::setCopyType(UMLListViewItemList& selectedItems)
356 {
357 bool withDiagrams = false; //If the selection includes diagrams
358 bool withObjects = false; //If the selection includes objects
359 bool onlyAttsOps = false; //If the selection only includes Attributes and/or Operations
360
361 foreach (UMLListViewItem* item, selectedItems) {
362 checkItemForCopyType(item, withDiagrams, withObjects, onlyAttsOps);
363 }
364 if (onlyAttsOps) {
365 m_type = clip5;
366 } else if (withDiagrams) {
367 m_type = clip2;
368 } else if(withObjects) {
369 m_type = clip1;
370 } else {
371 m_type = clip3;
372 }
373 }
374
375 /**
376 * Searches the child items of a UMLListViewItem to
377 * establish which Copy type is to be performed.
378 * @param item parent of the children
379 * @param withDiagrams includes diagrams
380 * @param withObjects includes objects
381 * @param onlyAttsOps includes only attributes and/or operations
382 */
checkItemForCopyType(UMLListViewItem * item,bool & withDiagrams,bool & withObjects,bool & onlyAttsOps)383 void UMLClipboard::checkItemForCopyType(UMLListViewItem* item, bool & withDiagrams, bool & withObjects,
384 bool & onlyAttsOps)
385 {
386 if(!item) {
387 return;
388 }
389 UMLDoc *doc = UMLApp::app()->document();
390 onlyAttsOps = true;
391 UMLView * view = 0;
392 UMLListViewItem * child = 0;
393 UMLListViewItem::ListViewType type = item->type();
394 if (Model_Utils::typeIsCanvasWidget(type)) {
395 withObjects = true;
396 onlyAttsOps = false;
397 } else if (Model_Utils::typeIsDiagram(type)) {
398 withDiagrams = true;
399 onlyAttsOps = false;
400 view = doc->findView(item->ID());
401 if (view)
402 m_ViewList.append(view);
403 else
404 uError() << "doc->findView(" << Uml::ID::toString(item->ID()) << ") returns NULL";
405 } else if (Model_Utils::typeIsFolder(type)) {
406 onlyAttsOps = false;
407 for (int i =0; i < item->childCount(); i++) {
408 child = (UMLListViewItem*)item->child(i);
409 checkItemForCopyType(child, withDiagrams, withObjects, onlyAttsOps);
410 }
411 }
412 }
413
414 /**
415 * Traverse children of a UMLListViewItem and add its UMLObjects to the list
416 *
417 * @param item parent of the children to insert
418 * @param selectedItems list of selected items
419 * @return success flag
420 */
insertItemChildren(UMLListViewItem * item,UMLListViewItemList & selectedItems)421 bool UMLClipboard::insertItemChildren(UMLListViewItem * item, UMLListViewItemList& selectedItems)
422 {
423 if (item->childCount()) {
424 for(int i = 0; i < item->childCount(); i++) {
425 UMLListViewItem * child = (UMLListViewItem*)item->child(i);
426 m_ItemList.append(child);
427 UMLListViewItem::ListViewType type = child->type();
428 if (!Model_Utils::typeIsClassifierList(type) &&
429 !Model_Utils::typeIsDiagram(type)) {
430 m_ObjectList.append(child->umlObject());
431 }
432 // If the child is selected, remove it from the list of selected items
433 // otherwise it will be inserted twice in m_ObjectList.
434 if (child->isSelected()) {
435 selectedItems.removeAll(child);
436 }
437 insertItemChildren(child, selectedItems);
438 }
439 }
440 return true;
441 }
442
443 /**
444 * If clipboard has mime type application/x-uml-clip1,
445 * Pastes the data from the clipboard into the current Doc.
446 * @param data mime type
447 */
pasteClip1(const QMimeData * data)448 bool UMLClipboard::pasteClip1(const QMimeData* data)
449 {
450 UMLObjectList objects;
451 return UMLDragData::decodeClip1(data, objects);
452 }
453
454 /**
455 * If clipboard has mime type application/x-uml-clip2,
456 * Pastes the data from the clipboard into the current Doc.
457 * @param data mime type
458 * @return success flag
459 */
pasteClip2(const QMimeData * data)460 bool UMLClipboard::pasteClip2(const QMimeData* data)
461 {
462 UMLDoc* doc = UMLApp::app()->document();
463 UMLObjectList objects;
464 UMLViewList views;
465
466 if (!UMLDragData::decodeClip2(data, objects, views)) {
467 uDebug() << "UMLDragData::decodeClip2 returned error";
468 return false;
469 }
470
471 if (NoteWidget::s_pCurrentNote) {
472 NoteWidget::s_pCurrentNote = 0;
473 } else {
474 foreach (UMLView* pView, views) {
475 if (!doc->addUMLView(pView)) {
476 return false;
477 }
478 }
479 }
480
481 return true;
482 }
483
484 /**
485 * If clipboard has mime type application/x-uml-clip3,
486 * Pastes the data from the clipboard into the current Doc.
487 *
488 * Note: clip3 is only used to determine if the selected items can be dragged
489 * onto the view. Pasting only listview items makes no sense. Clip3 is implemented
490 * as a fallback-clip when clip 1, 2, 4 or 5 are not applicable. But that should
491 * never happen.
492 *
493 * Todo: remove clip3 altogether.
494 *
495 * @param data mime type
496 * @return success flag
497 */
pasteClip3(const QMimeData * data)498 bool UMLClipboard::pasteClip3(const QMimeData* data)
499 {
500 UMLDoc *doc = UMLApp::app()->document();
501 UMLListViewItemList itemdatalist;
502 IDChangeLog* idchanges = doc->changeLog();
503
504 if(!idchanges) {
505 return false;
506 }
507
508 UMLListView *listView = UMLApp::app()->listView();
509 return UMLDragData::decodeClip3(data, itemdatalist, listView);
510 }
511
512 /**
513 * If clipboard has mime type application/x-uml-clip4,
514 * Pastes the data from the clipboard into the current Doc.
515 * @param data mime type
516 * @return success flag
517 */
pasteClip4(const QMimeData * data)518 bool UMLClipboard::pasteClip4(const QMimeData* data)
519 {
520 UMLDoc *doc = UMLApp::app()->document();
521
522 UMLObjectList objects;
523 UMLWidgetList widgets;
524 AssociationWidgetList assocs;
525
526 IDChangeLog* idchanges = 0;
527
528 Uml::DiagramType::Enum diagramType;
529
530 if(!UMLDragData::decodeClip4(data, objects, widgets, assocs, diagramType)) {
531 return false;
532 }
533
534 UMLScene *currentScene = UMLApp::app()->currentView()->umlScene();
535
536 idchanges = doc->changeLog();
537 if(!idchanges) {
538 return false;
539 }
540 //make sure the file we are pasting into has the objects
541 //we need if there are widgets to be pasted
542 foreach (UMLObject* obj, objects) {
543 if(!doc->assignNewIDs(obj)) {
544 return false;
545 }
546
547 }
548
549 //now add any widget we are want to paste
550 bool objectAlreadyExists = false;
551 currentScene->beginPartialWidgetPaste();
552
553 foreach (UMLWidget* widget, widgets) {
554
555 Uml::ID::Type oldId = widget->id();
556 Uml::ID::Type newId = idchanges->findNewID(oldId);
557 // how should findWidget find ::None id, which is returned for the first entry ?
558 if (currentScene->findWidget(newId)) {
559 uError() << "widget (oldID=" << Uml::ID::toString(oldId) << ", newID="
560 << Uml::ID::toString(newId) << ") already exists in target view.";
561 widgets.removeAll(widget);
562 delete widget;
563 objectAlreadyExists = true;
564 } else {
565 if (currentScene->isActivityDiagram() || currentScene->isStateDiagram()) {
566 widget->setID(doc->assignNewID(widget->id()));
567 }
568 }
569 }
570
571 //now paste the associations
572 foreach (AssociationWidget* assoc, assocs) {
573 if (!currentScene->addAssociation(assoc, true)) {
574 currentScene->endPartialWidgetPaste();
575 return false;
576 }
577 }
578
579 currentScene->clearSelected();
580 currentScene->selectWidgets(widgets);
581 foreach (AssociationWidget* assoc, assocs) {
582 currentScene->selectWidgetsOfAssoc(assoc);
583 }
584
585 //Activate all the pasted associations and widgets
586 currentScene->activate();
587 currentScene->endPartialWidgetPaste();
588
589 if (objectAlreadyExists) {
590 pasteItemAlreadyExists();
591 }
592 return true;
593 }
594
595 /**
596 * If clipboard has mime type application/x-uml-clip5,
597 * Pastes the data from the clipboard into the current Doc.
598 * @param data mime type
599 * @return success flag
600 */
pasteClip5(const QMimeData * data)601 bool UMLClipboard::pasteClip5(const QMimeData* data)
602 {
603 UMLDoc *doc = UMLApp::app()->document();
604 UMLListView *listView = UMLApp::app()->listView();
605 UMLListViewItem* lvitem = dynamic_cast<UMLListViewItem *>(listView->currentItem());
606 if (!lvitem || !Model_Utils::typeIsClassifier(lvitem->type())) {
607 return false;
608 }
609 UMLClassifier *parent = lvitem->umlObject()->asUMLClassifier();
610
611 if (parent == 0) {
612 uError() << "parent is not a UMLClassifier";
613 return false;
614 }
615
616 UMLObjectList objects;
617
618 IDChangeLog* idchanges = 0;
619 bool result = UMLDragData::decodeClip5(data, objects, parent);
620
621 if(!result) {
622 return false;
623 }
624
625 doc->setModified(true);
626 idchanges = doc->changeLog();
627 // Assume success if at least one child object could be pasted
628 if (objects.count())
629 result = false;
630
631 foreach (UMLObject* obj, objects) {
632 obj->setID(doc->assignNewID(obj->id()));
633 switch(obj->baseType()) {
634 case UMLObject::ot_Attribute :
635 {
636 UMLObject *exist = parent->findChildObject(obj->name(), UMLObject::ot_Attribute);
637 if (exist) {
638 QString newName = parent->uniqChildName(UMLObject::ot_Attribute, obj->name());
639 obj->setName(newName);
640 }
641 UMLAttribute *att = obj->asUMLAttribute();
642 if (parent->addAttribute(att, idchanges)) {
643 result = true;
644 } else {
645 uError() << parent->name() << "->addAttribute("
646 << att->name() << ") failed";
647 }
648 break;
649 }
650 case UMLObject::ot_Operation :
651 {
652 UMLOperation *op = obj->asUMLOperation();
653 UMLOperation *exist = parent->checkOperationSignature(op->name(), op->getParmList());
654 if (exist) {
655 QString newName = parent->uniqChildName(UMLObject::ot_Operation, obj->name());
656 op->setName(newName);
657 }
658 if (parent->addOperation(op, idchanges)) {
659 result = true;
660 } else {
661 uError() << parent->name() << "->addOperation("
662 << op->name() << ") failed";
663 }
664 break;
665 }
666 case UMLObject::ot_Template:
667 {
668 UMLTemplate* tp = obj->asUMLTemplate();
669 UMLTemplate* exist = parent->findTemplate(tp->name());
670 if (exist) {
671 QString newName = parent->uniqChildName(UMLObject::ot_Template, obj->name());
672 tp->setName(newName);
673 }
674 if (parent->addTemplate(tp, idchanges)) {
675 result = true;
676 } else {
677 uError() << parent->name() << "->addTemplate("
678 << tp->name() << ") failed";
679 }
680 break;
681 }
682 case UMLObject::ot_EnumLiteral:
683 {
684 UMLEnum* enumParent = parent->asUMLEnum();
685 // if parent is not a UMLEnum, bail out immediately;
686 if (!enumParent) {
687 result = false;
688 uError() << "Parent is not a UMLEnum";
689 break;
690 }
691
692 UMLObject* exist = enumParent->findChildObject(obj->name(), UMLObject::ot_EnumLiteral);
693 if (exist) {
694 QString newName = enumParent->uniqChildName(UMLObject::ot_EnumLiteral, obj->name());
695 obj->setName(newName);
696 }
697 UMLEnumLiteral* enl = obj->asUMLEnumLiteral();
698
699 if (enumParent->addEnumLiteral(enl, idchanges)) {
700 result = true;
701 } else {
702 uError() << enumParent->name() << "->addEnumLiteral("
703 << enl->name() << ") failed";
704 }
705 break;
706 }
707 case UMLObject::ot_EntityAttribute :
708 {
709 UMLEntity* entityParent = parent->asUMLEntity();
710 // if parent is not a UMLEntity, bail out immediately;
711 if (!entityParent) {
712 result = false;
713 uError() << "Parent is not a UMLEntity";
714 break;
715 }
716 UMLObject *exist = entityParent->findChildObject(obj->name(), UMLObject::ot_EntityAttribute);
717 if (exist) {
718 QString newName = entityParent->uniqChildName(UMLObject::ot_EntityAttribute, obj->name());
719 obj->setName(newName);
720 }
721 UMLEntityAttribute *att = obj->asUMLEntityAttribute();
722
723 if (entityParent->addEntityAttribute(att, idchanges)) {
724 result = true;
725 } else {
726 uError() << parent->name() << "->addEntityAttribute(" << att->name() << ") failed";
727 }
728 break;
729 }
730 default :
731 uWarning() << "pasting unknown children type in clip type 5";
732 return false;
733 }
734 }
735
736 return result;
737 }
738
739 /**
740 * Gives a `sorry' message box if you're pasting an item which
741 * already exists and can't be duplicated.
742 */
pasteItemAlreadyExists()743 void UMLClipboard::pasteItemAlreadyExists()
744 {
745 UMLView *currentView = UMLApp::app()->currentView();
746 KMessageBox::sorry(currentView,
747 i18n("At least one of the items in the clipboard "
748 "could not be pasted because an item of the "
749 "same name already exists. Any other items "
750 "have been pasted."),
751 i18n("Paste Error"));
752 }
753
754