1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2006-2020 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5 
6 // own header
7 #include "folder.h"
8 
9 // app includes
10 #include "debug_utils.h"
11 #include "dialog_utils.h"
12 #include "model_utils.h"
13 #include "object_factory.h"
14 #include "optionstate.h"
15 #include "uml.h"
16 #include "umldoc.h"
17 #include "umlscene.h"
18 #include "umlview.h"
19 #include "datatype.h"
20 
21 // kde includes
22 #include <KLocalizedString>
23 #include <KMessageBox>
24 
25 // qt includes
26 #include <QFile>
27 #include <QXmlStreamWriter>
28 
29 /**
30  * Sets up a Folder.
31  * @param name    The name of the Folder.
32  * @param id      The unique id of the Folder. A new ID will be generated
33  *                if this argument is left away.
34  */
UMLFolder(const QString & name,Uml::ID::Type id)35 UMLFolder::UMLFolder(const QString & name, Uml::ID::Type id)
36   : UMLPackage(name, id)
37 {
38     m_BaseType = UMLObject::ot_Folder;
39     UMLObject::setStereotypeCmd(QLatin1String("folder"));
40 }
41 
42 /**
43  * Empty destructor.
44  */
~UMLFolder()45 UMLFolder::~UMLFolder()
46 {
47     qDeleteAll(m_diagrams);
48     m_diagrams.clear();
49 }
50 
51 /**
52  * Make a clone of this object.
53  */
clone() const54 UMLObject* UMLFolder::clone() const
55 {
56     UMLFolder *clone = new UMLFolder();
57     UMLObject::copyInto(clone);
58     return clone;
59 }
60 
61 /**
62  * Set the localized name of this folder.
63  * This is set for the predefined root views (Logical,
64  * UseCase, Component, Deployment, EntityRelationship,
65  * and the Datatypes folder inside the Logical View.)
66  */
setLocalName(const QString & localName)67 void UMLFolder::setLocalName(const QString& localName)
68 {
69     m_localName = localName;
70 }
71 
72 /**
73  * Return the localized name of this folder.
74  * Only useful for the predefined root folders.
75  */
localName() const76 QString UMLFolder::localName() const
77 {
78     return m_localName;
79 }
80 
81 /**
82  * Add a view to the diagram list.
83  */
addView(UMLView * view)84 void UMLFolder::addView(UMLView *view)
85 {
86     m_diagrams.append(view);
87 }
88 
89 /**
90  * Remove a view from the diagram list.
91  */
removeView(UMLView * view)92 void UMLFolder::removeView(UMLView *view)
93 {
94     m_diagrams.removeAll(view);
95 }
96 
97 /**
98  * Append the views in this folder to the given diagram list.
99  * @param viewList       The UMLViewList to which to append the diagrams.
100  * @param includeNested  Whether to include diagrams from nested folders
101  *                       (default: true.)
102  */
appendViews(UMLViewList & viewList,bool includeNested)103 void UMLFolder::appendViews(UMLViewList& viewList, bool includeNested)
104 {
105     if (includeNested) {
106         foreach (UMLObject* o, m_objects) {
107             uIgnoreZeroPointer(o);
108             if (o->baseType() == UMLObject::ot_Folder) {
109                 UMLFolder *f = o->asUMLFolder();
110                 f->appendViews(viewList);
111             }
112         }
113     }
114     foreach (UMLView* v, m_diagrams) {
115         viewList.append(v);
116     }
117 }
118 
119 /**
120  * Activate the views in this folder.
121  * "Activation": Some widgets require adjustments after loading from file,
122  * those are done here.
123  */
activateViews()124 void UMLFolder::activateViews()
125 {
126     foreach (UMLObject* o, m_objects) {
127         uIgnoreZeroPointer(o);
128         if (o->baseType() == UMLObject::ot_Folder) {
129             UMLFolder *f = o->asUMLFolder();
130             f->activateViews();
131         }
132     }
133 
134     foreach (UMLView* v, m_diagrams) {
135         v->umlScene()->activateAfterLoad();
136     }
137     // Make sure we have a treeview item for each diagram.
138     // It may happen that we are missing them after switching off tabbed widgets.
139     Settings::OptionState optionState = Settings::optionState();
140     if (optionState.generalState.tabdiagrams) {
141         return;
142     }
143     Model_Utils::treeViewAddViews(m_diagrams);
144 }
145 
146 /**
147  * Seek a view of the given ID in this folder.
148  * @param id   ID of the view to find.
149  * @return     Pointer to the view if found, NULL if no view found.
150  */
findView(Uml::ID::Type id)151 UMLView *UMLFolder::findView(Uml::ID::Type id)
152 {
153     foreach (UMLView* v, m_diagrams) {
154         if (v && v->umlScene() && v->umlScene()->ID() == id) {
155             return v;
156         }
157     }
158 
159     UMLView* v = 0;
160     UMLPackageList packages;
161     appendPackages(packages);
162     foreach (UMLPackage *o, packages) {
163         if (o->baseType() != UMLObject::ot_Folder) {
164             continue;
165         }
166         UMLFolder *f = o->asUMLFolder();
167         v = f->findView(id);
168         if (v) {
169             break;
170         }
171     }
172     return v;
173 }
174 
175 /**
176  * Seek a view by the type and name given.
177  * @param type              The type of view to find.
178  * @param name              The name of the view to find.
179  * @param searchAllScopes   Search in all subfolders (default: true.)
180  * @return  Pointer to the view found, or NULL if not found.
181  */
findView(Uml::DiagramType::Enum type,const QString & name,bool searchAllScopes)182 UMLView *UMLFolder::findView(Uml::DiagramType::Enum type, const QString &name, bool searchAllScopes)
183 {
184     foreach (UMLView* v, m_diagrams) {
185         if (v->umlScene()->type() == type && v->umlScene()->name() == name) {
186             return v;
187         }
188     }
189 
190     UMLView* v = 0;
191     if (searchAllScopes) {
192         foreach (UMLObject* o, m_objects) {
193             uIgnoreZeroPointer(o);
194             if (o->baseType() != UMLObject::ot_Folder) {
195                 continue;
196             }
197             UMLFolder *f = o->asUMLFolder();
198             v = f->findView(type, name, searchAllScopes);
199             if (v) {
200                 break;
201             }
202         }
203     }
204     return v;
205 }
206 
207 /**
208  * Set the options for the views in this folder.
209  */
setViewOptions(const Settings::OptionState & optionState)210 void UMLFolder::setViewOptions(const Settings::OptionState& optionState)
211 {
212     // for each view update settings
213     foreach (UMLView* v, m_diagrams) {
214         v->umlScene()->setOptionState(optionState);
215     }
216 }
217 
218 /**
219  * Remove all views in this folder.
220  */
removeAllViews()221 void UMLFolder::removeAllViews()
222 {
223     foreach (UMLObject* o, m_objects) {
224         uIgnoreZeroPointer(o);
225         if (o->baseType() != UMLObject::ot_Folder)
226             continue;
227         UMLFolder *f = o->asUMLFolder();
228         f->removeAllViews();
229     }
230 
231     foreach (UMLView* v, m_diagrams) {
232         // TODO ------------------ check this code - bad: calling back to UMLDoc::removeView()
233         v->umlScene()->removeAllAssociations(); // note : It may not be apparent, but when we remove all associations
234         // from a view, it also causes any UMLAssociations that lack parent
235         // association widgets (but once had them) to remove themselves from
236         // this document.
237         uDebug() << "removing " << v->umlScene()->name();
238         UMLApp::app()->document()->removeView(v, false);
239     }
240 
241     qDeleteAll(m_diagrams);
242     m_diagrams.clear();
243 }
244 
245 /**
246  * Set the folder file name for a separate submodel.
247  */
setFolderFile(const QString & fileName)248 void UMLFolder::setFolderFile(const QString& fileName)
249 {
250     m_folderFile = fileName;
251 }
252 
253 /**
254  * Get the folder file name for a separate submodel.
255  */
folderFile() const256 QString UMLFolder::folderFile() const
257 {
258     return m_folderFile;
259 }
260 
261 /**
262  * Auxiliary to saveToXMI1(): Save the contained objects and diagrams.
263  * Can be used regardless of whether saving to the main model file
264  * or to an external folder file (see m_folderFile.)
265  */
saveContents1(QXmlStreamWriter & writer)266 void UMLFolder::saveContents1(QXmlStreamWriter& writer)
267 {
268     writer.writeStartElement(QLatin1String("UML:Namespace.ownedElement"));
269     // Save contained objects if any.
270     foreach (UMLObject *obj, m_objects) {
271         uIgnoreZeroPointer(obj);
272         obj->saveToXMI1 (writer);
273     }
274     // Save associations if any.
275     foreach (UMLObject *obj, subordinates()) {
276         obj->saveToXMI1 (writer);
277     }
278     writer.writeEndElement();
279     // Save diagrams to `extension'.
280     if (m_diagrams.count()) {
281         writer.writeStartElement(QLatin1String("XMI.extension"));
282         writer.writeAttribute(QLatin1String("xmi.extender"), QLatin1String("umbrello"));
283         writer.writeStartElement(QLatin1String("diagrams"));
284         if (UMLApp::app()->document()->resolution() != 0.0) {
285             writer.writeAttribute(QLatin1String("resolution"),
286                                   QString::number(UMLApp::app()->document()->resolution()));
287         }
288         foreach (UMLView* pView, m_diagrams) {
289             pView->umlScene()->saveToXMI1(writer);
290         }
291         writer.writeEndElement();            // diagrams
292         writer.writeEndElement();   // XMI.extension
293     }
294 }
295 
296 /**
297  * Auxiliary to saveToXMI1(): Creates a <UML:Model> element when saving
298  * a predefined modelview, or a <UML:Package> element when saving a
299  * user created folder. Invokes saveContents() with the newly created
300  * element.
301  */
save1(QXmlStreamWriter & writer)302 void UMLFolder::save1(QXmlStreamWriter& writer)
303 {
304     UMLDoc *umldoc = UMLApp::app()->document();
305     QString elementName(QLatin1String("UML:Package"));
306     const Uml::ModelType::Enum mt = umldoc->rootFolderType(this);
307     if (mt != Uml::ModelType::N_MODELTYPES) {
308         elementName = QLatin1String("UML:Model");
309     }
310     UMLObject::save1(elementName, writer);
311     saveContents1(writer);
312     writer.writeEndElement();
313 }
314 
315 /**
316  * Creates a UML:Model or UML:Package element:
317  * UML:Model is created for the predefined fixed folders,
318  * UML:Package with stereotype "folder" is created for all else.
319  */
saveToXMI1(QXmlStreamWriter & writer)320 void UMLFolder::saveToXMI1(QXmlStreamWriter& writer)
321 {
322     if (m_folderFile.isEmpty()) {
323         save1(writer);
324         return;
325     }
326     // See if we can create the external file.
327     // If not then internalize the folder.
328     UMLDoc *umldoc = UMLApp::app()->document();
329 #if QT_VERSION >= 0x050000
330     QString fileName = umldoc->url().adjusted(QUrl::RemoveFilename).path() + m_folderFile;
331 #else
332     QString fileName = umldoc->url().directory() + QLatin1Char('/') + m_folderFile;
333 #endif
334     QFile file(fileName);
335     if (!file.open(QIODevice::WriteOnly)) {
336         uError() << m_folderFile << QLatin1String(": ")
337             << "cannot create file, contents will be saved in main model file";
338         m_folderFile.clear();
339         save1(writer);
340         return;
341     }
342     // External file is writable.  Create XMI.extension stub in main file.
343     UMLObject::save1(QLatin1String("UML:Package"), writer);
344     writer.writeStartElement(QLatin1String("XMI.extension"));
345     writer.writeAttribute(QLatin1String("xmi.extender"), QLatin1String("umbrello"));
346     writer.writeStartElement(QLatin1String("external_file"));
347     writer.writeAttribute(QLatin1String("name"), m_folderFile);
348     writer.writeEndElement();            // external_file
349     writer.writeEndElement();        // XMI.extension
350     writer.writeEndElement();    // UML:Package
351 
352     // Write the external file.
353     QXmlStreamWriter xfWriter(&file);
354     xfWriter.setCodec("UTF-8");
355     xfWriter.setAutoFormatting(true);
356     xfWriter.setAutoFormattingIndent(2);
357     xfWriter.writeStartDocument();
358     xfWriter.writeStartElement(QLatin1String("external_file"));
359     xfWriter.writeAttribute(QLatin1String("name"), name());
360     xfWriter.writeAttribute(QLatin1String("filename"), m_folderFile);
361     xfWriter.writeAttribute(QLatin1String("mainModel"), umldoc->url().fileName());
362     xfWriter.writeAttribute(QLatin1String("parentId"), Uml::ID::toString(umlPackage()->id()));
363     xfWriter.writeAttribute(QLatin1String("parent"), umlPackage()->fullyQualifiedName(QLatin1String("::"), true));
364     saveContents1(xfWriter);
365     xfWriter.writeEndElement();
366     file.close();
367 }
368 
369 /**
370  * Auxiliary to load():
371  * Load the diagrams from the "diagrams" in the <XMI.extension>
372  */
loadDiagramsFromXMI1(QDomNode & node)373 bool UMLFolder::loadDiagramsFromXMI1(QDomNode& node)
374 {
375     qreal resolution = 0.0;
376     QString res = node.toElement().attribute(QLatin1String("resolution"), QLatin1String(""));
377     if (!res.isEmpty()) {
378        resolution = res.toDouble();
379     }
380     if (resolution != 0.0) {
381        UMLApp::app()->document()->setResolution(resolution);
382     } else {
383        /* FIXME how to get dpi ?
384         * 1. from user -> will open a dialog box for any old file
385         * 2. after loading from user changeable document settings
386         * 3. estimated from contained widgets
387         */
388        UMLApp::app()->document()->setResolution(0.0);
389     }
390 
391     QDomNode diagrams = node.firstChild();
392     const Settings::OptionState optionState = Settings::optionState();
393     UMLDoc *umldoc = UMLApp::app()->document();
394     bool totalSuccess = true;
395     for (QDomElement diagram = diagrams.toElement(); !diagram.isNull();
396          diagrams = diagrams.nextSibling(), diagram = diagrams.toElement()) {
397         QString tag = diagram.tagName();
398         if (tag != QLatin1String("diagram")) {
399             uDebug() << "ignoring " << tag << " in <diagrams>";
400             continue;
401         }
402         UMLView * pView = new UMLView(this);
403         pView->umlScene()->setOptionState(optionState);
404         if (pView->umlScene()->loadFromXMI1(diagram)) {
405             pView->hide();
406             umldoc->addView(pView);
407         } else {
408             delete pView;
409             totalSuccess = false;
410         }
411     }
412     return totalSuccess;
413 }
414 
415 /**
416  * Folders in the listview can be marked such that their contents
417  * are saved to a separate file.
418  * This method loads the separate folder file.
419  * CAVEAT: This is not XMI standard compliant.
420  * If standard compliance is an issue then avoid folder files.
421  * @param path  Fully qualified file name, i.e. absolute directory
422  *              plus file name.
423  * @return   True for success.
424  */
loadFolderFile(const QString & path)425 bool UMLFolder::loadFolderFile(const QString& path)
426 {
427     QFile file(path);
428     if (!file.exists()) {
429         KMessageBox::error(0, i18n("The folderfile %1 does not exist.", path), i18n("Load Error"));
430         return false;
431     }
432     if (!file.open(QIODevice::ReadOnly)) {
433         KMessageBox::error(0, i18n("The folderfile %1 cannot be opened.", path), i18n("Load Error"));
434         return false;
435     }
436     QTextStream stream(&file);
437     QString data = stream.readAll();
438     file.close();
439     QDomDocument doc;
440     QString error;
441     int line;
442     if (!doc.setContent(data, false, &error, &line)) {
443         uError() << "Cannot set content:" << error << " line:" << line;
444         return false;
445     }
446     QDomNode rootNode = doc.firstChild();
447     while (rootNode.isComment() || rootNode.isProcessingInstruction()) {
448         rootNode = rootNode.nextSibling();
449     }
450     if (rootNode.isNull()) {
451         uError() << "Root node is Null";
452         return false;
453     }
454     QDomElement element = rootNode.toElement();
455     QString type = element.tagName();
456     if (type != QLatin1String("external_file")) {
457         uError() << "Root node has unknown type " << type;
458         return false;
459     }
460     return load1(element);
461 }
462 
463 /**
464  * Loads the owned elements of the <UML:Model>.
465  */
load1(QDomElement & element)466 bool UMLFolder::load1(QDomElement& element)
467 {
468     UMLDoc *umldoc = UMLApp::app()->document();
469     bool totalSuccess = true;
470     for (QDomNode node = element.firstChild(); !node.isNull();
471             node = node.nextSibling()) {
472         if (node.isComment())
473             continue;
474         QDomElement tempElement = node.toElement();
475         QString type = tempElement.tagName();
476         if (Model_Utils::isCommonXMI1Attribute(type))
477             continue;
478         if (UMLDoc::tagEq(type, QLatin1String("Namespace.ownedElement")) ||
479                 UMLDoc::tagEq(type, QLatin1String("Namespace.contents"))) {
480             //CHECK: Umbrello currently assumes that nested elements
481             // are ownedElements anyway.
482             // Therefore these tags are not further interpreted.
483             if (! load1(tempElement)) {
484                 uDebug() << "An error happened while loading the " << type
485                     << " of the " << name();
486                 totalSuccess = false;
487             }
488             continue;
489         } else if (UMLDoc::tagEq(type, QLatin1String("packagedElement")) ||
490                    UMLDoc::tagEq(type, QLatin1String("ownedElement"))) {
491             type = tempElement.attribute(QLatin1String("xmi:type"));
492         } else if (type == QLatin1String("XMI.extension")) {
493             for (QDomNode xtnode = node.firstChild(); !xtnode.isNull();
494                                               xtnode = xtnode.nextSibling()) {
495                 QDomElement el = xtnode.toElement();
496                 const QString xtag = el.tagName();
497                 if (xtag == QLatin1String("diagrams")) {
498                     umldoc->addDiagramToLoad(this, xtnode);
499                 } else if (xtag == QLatin1String("external_file")) {
500 #if QT_VERSION >= 0x050000
501                     const QString rootDir(umldoc->url().adjusted(QUrl::RemoveFilename).path());
502 #else
503                     const QString rootDir(umldoc->url().directory());
504 #endif
505                     QString fileName = el.attribute(QLatin1String("name"));
506                     const QString path(rootDir + QLatin1Char('/') + fileName);
507                     if (loadFolderFile(path))
508                         m_folderFile = fileName;
509                 } else {
510                     uDebug() << name() << ": ignoring XMI.extension " << xtag;
511                     continue;
512                 }
513             }
514             continue;
515         }
516         // Do not re-create the predefined Datatypes folder in the Logical View,
517         // it already exists.
518         UMLFolder *logicalView = umldoc->rootFolder(Uml::ModelType::Logical);
519         if (this == logicalView && UMLDoc::tagEq(type, QLatin1String("Package"))) {
520             QString thisName = tempElement.attribute(QLatin1String("name"));
521             if (thisName == QLatin1String("Datatypes")) {
522                 UMLFolder *datatypeFolder = umldoc->datatypeFolder();
523                 if (!datatypeFolder->loadFromXMI1(tempElement))
524                     totalSuccess = false;
525                 continue;
526             }
527         }
528         UMLObject *pObject = 0;
529         // Avoid duplicate creation of forward declared object
530         QString idStr = Model_Utils::getXmiId(tempElement);
531         if (!idStr.isEmpty()) {
532             Uml::ID::Type id = Uml::ID::fromString(idStr);
533             pObject = umldoc->findObjectById(id);
534             if (pObject) {
535                 uDebug() << "object " << idStr << "already exists";
536             }
537         }
538         // Avoid duplicate creation of datatype
539         if (pObject == 0 && this == umldoc->datatypeFolder()) {
540             QString name = tempElement.attribute(QLatin1String("name"));
541             foreach (UMLObject *o, m_objects) {
542                 uIgnoreZeroPointer(o);
543                 if (o->name() == name) {
544                     UMLDatatype *dt = o->asUMLDatatype();
545                     if (dt) {
546                         QString isActive = tempElement.attribute(QLatin1String("isActive"));
547                         dt->setActive(isActive != QLatin1String("false"));
548                         pObject = dt;
549                         break;
550                     }
551                 }
552             }
553         }
554         if (pObject == 0) {
555             QString stereoID = tempElement.attribute(QLatin1String("stereotype"));
556             pObject = Object_Factory::makeObjectFromXMI(type, stereoID);
557             if (!pObject) {
558                 uWarning() << "Unknown type of umlobject to create: " << type;
559                 continue;
560             }
561         }
562         // check for invalid namespaces
563         QString ns = tempElement.attribute(QLatin1String("namespace"));
564         Uml::ID::Type id = Uml::ID::fromString(ns);
565         if (id != this->id()) {
566             uError() << "namespace" << ns << "not present; ignoring object with id" << idStr;
567             delete pObject;
568             pObject = 0;
569             totalSuccess = false;
570             continue;
571         }
572         pObject->setUMLPackage(this);
573         if (!pObject->loadFromXMI1(tempElement)) {
574             removeObject(pObject);
575             delete pObject;
576             totalSuccess = false;
577         }
578     }
579     return totalSuccess;
580 }
581 
showPropertiesDialog(QWidget * parent)582 bool UMLFolder::showPropertiesDialog(QWidget *parent)
583 {
584     Q_UNUSED(parent);
585     QString folderName = this->name();
586     bool ok = Dialog_Utils::askRenameName(UMLObject::ot_Folder, folderName);
587     if (ok) {
588         setName(folderName);
589     }
590     return ok;
591 }
592 
593 /**
594  * Overloading operator for debugging output.
595  */
operator <<(QDebug out,const UMLFolder & item)596 QDebug operator<<(QDebug out, const UMLFolder& item)
597 {
598     out.nospace() << "UMLFolder: localName=" << item.m_localName
599         << ", folderFile=" << item.m_folderFile
600         << ", diagrams=" << item.m_diagrams.count();
601     return out.space();
602 }
603 
604