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