diff options
Diffstat (limited to 'src/dotclassgraph.cpp')
-rw-r--r-- | src/dotclassgraph.cpp | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/src/dotclassgraph.cpp b/src/dotclassgraph.cpp new file mode 100644 index 0000000..3f5d228 --- /dev/null +++ b/src/dotclassgraph.cpp @@ -0,0 +1,548 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 by Dimitri van Heesch. +* +* Permission to use, copy, modify, and distribute this software and its +* documentation under the terms of the GNU General Public License is hereby +* granted. No representations are made about the suitability of this software +* for any purpose. It is provided "as is" without express or implied warranty. +* See the GNU General Public License for more details. +* +* Documents produced by Doxygen are derivative works derived from the +* input used in their production; they are not affected by this license. +* +*/ + +#include "dotclassgraph.h" +#include "dotnode.h" + +#include "config.h" +#include "util.h" + +#define HIDE_SCOPE_NAMES Config_getBool(HIDE_SCOPE_NAMES) +#define MAX_DOT_GRAPH_DEPTH Config_getInt(MAX_DOT_GRAPH_DEPTH) +#define UML_LOOK Config_getBool(UML_LOOK) +#define TEMPLATE_RELATIONS Config_getBool(TEMPLATE_RELATIONS) +#define DOT_GRAPH_MAX_NODES Config_getInt(DOT_GRAPH_MAX_NODES) + +void DotClassGraph::addClass(const ClassDef *cd,DotNode *n,int prot, + const char *label,const char *usedName,const char *templSpec,bool base,int distance) +{ + if (Config_getBool(HIDE_UNDOC_CLASSES) && !cd->isLinkable()) return; + + int edgeStyle = (label || prot==EdgeInfo::Orange || prot==EdgeInfo::Orange2) ? EdgeInfo::Dashed : EdgeInfo::Solid; + QCString className; + if (cd->isAnonymous()) + { + className="anonymous:"; + className+=label; + } + else if (usedName) // name is a typedef + { + className=usedName; + } + else if (templSpec) // name has a template part + { + className=insertTemplateSpecifierInScope(cd->name(),templSpec); + } + else // just a normal name + { + className=cd->displayName(); + } + //printf("DotClassGraph::addClass(class='%s',parent=%s,prot=%d,label=%s,dist=%d,usedName=%s,templSpec=%s,base=%d)\n", + // className.data(),n->label().data(),prot,label,distance,usedName,templSpec,base); + DotNode *bn = m_usedNodes->find(className); + if (bn) // class already inserted + { + if (base) + { + n->addChild(bn,prot,edgeStyle,label); + bn->addParent(n); + } + else + { + bn->addChild(n,prot,edgeStyle,label); + n->addParent(bn); + } + bn->setDistance(distance); + //printf(" add exiting node %s of %s\n",bn->label().data(),n->label().data()); + } + else // new class + { + QCString displayName=className; + if (HIDE_SCOPE_NAMES) displayName=stripScope(displayName); + QCString tmp_url; + if (cd->isLinkable() && !cd->isHidden()) + { + tmp_url=cd->getReference()+"$"+cd->getOutputFileBase(); + if (!cd->anchor().isEmpty()) + { + tmp_url+="#"+cd->anchor(); + } + } + QCString tooltip = cd->briefDescriptionAsTooltip(); + bn = new DotNode(getNextNodeNumber(), + displayName, + tooltip, + tmp_url.data(), + FALSE, // rootNode + cd + ); + if (base) + { + n->addChild(bn,prot,edgeStyle,label); + bn->addParent(n); + } + else + { + bn->addChild(n,prot,edgeStyle,label); + n->addParent(bn); + } + bn->setDistance(distance); + m_usedNodes->insert(className,bn); + //printf(" add new child node '%s' to %s hidden=%d url=%s\n", + // className.data(),n->label().data(),cd->isHidden(),tmp_url.data()); + + buildGraph(cd,bn,base,distance+1); + } +} + +void DotClassGraph::determineTruncatedNodes(QList<DotNode> &queue,bool includeParents) +{ + while (queue.count()>0) + { + DotNode *n = queue.take(0); + if (n->isVisible() && n->isTruncated()==DotNode::Unknown) + { + bool truncated = FALSE; + if (n->children()) + { + QListIterator<DotNode> li(*n->children()); + const DotNode *dn; + for (li.toFirst();(dn=li.current());++li) + { + if (!dn->isVisible()) + truncated = TRUE; + else + queue.append(dn); + } + } + if (n->parents() && includeParents) + { + QListIterator<DotNode> li(*n->parents()); + const DotNode *dn; + for (li.toFirst();(dn=li.current());++li) + { + if (!dn->isVisible()) + truncated = TRUE; + else + queue.append(dn); + } + } + n->markAsTruncated(truncated); + } + } +} + +bool DotClassGraph::determineVisibleNodes(DotNode *rootNode, + int maxNodes,bool includeParents) +{ + QList<DotNode> childQueue; + QList<DotNode> parentQueue; + QArray<int> childTreeWidth; + QArray<int> parentTreeWidth; + childQueue.append(rootNode); + if (includeParents) parentQueue.append(rootNode); + bool firstNode=TRUE; // flag to force reprocessing rootNode in the parent loop + // despite being marked visible in the child loop + while ((childQueue.count()>0 || parentQueue.count()>0) && maxNodes>0) + { + if (childQueue.count()>0) + { + DotNode *n = childQueue.take(0); + int distance = n->distance(); + if (!n->isVisible() && distance<=MAX_DOT_GRAPH_DEPTH) // not yet processed + { + if (distance>0) + { + int oldSize=(int)childTreeWidth.size(); + if (distance>oldSize) + { + childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance)); + int i; for (i=oldSize;i<distance;i++) childTreeWidth[i]=0; + } + childTreeWidth[distance-1]+=n->label().length(); + } + n->markAsVisible(); + maxNodes--; + // add direct children + if (n->children()) + { + QListIterator<DotNode> li(*n->children()); + const DotNode *dn; + for (li.toFirst();(dn=li.current());++li) + { + childQueue.append(dn); + } + } + } + } + if (includeParents && parentQueue.count()>0) + { + DotNode *n = parentQueue.take(0); + if ((!n->isVisible() || firstNode) && n->distance()<=MAX_DOT_GRAPH_DEPTH) // not yet processed + { + firstNode=FALSE; + int distance = n->distance(); + if (distance>0) + { + int oldSize = (int)parentTreeWidth.size(); + if (distance>oldSize) + { + parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance)); + int i; for (i=oldSize;i<distance;i++) parentTreeWidth[i]=0; + } + parentTreeWidth[distance-1]+=n->label().length(); + } + n->markAsVisible(); + maxNodes--; + // add direct parents + if (n->parents()) + { + QListIterator<DotNode> li(*n->parents()); + const DotNode *dn; + for (li.toFirst();(dn=li.current());++li) + { + parentQueue.append(dn); + } + } + } + } + } + if (UML_LOOK) return FALSE; // UML graph are always top to bottom + int maxWidth=0; + int maxHeight=(int)QMAX(childTreeWidth.size(),parentTreeWidth.size()); + uint i; + for (i=0;i<childTreeWidth.size();i++) + { + if (childTreeWidth.at(i)>maxWidth) maxWidth=childTreeWidth.at(i); + } + for (i=0;i<parentTreeWidth.size();i++) + { + if (parentTreeWidth.at(i)>maxWidth) maxWidth=parentTreeWidth.at(i); + } + //printf("max tree width=%d, max tree height=%d\n",maxWidth,maxHeight); + return maxWidth>80 && maxHeight<12; // used metric to decide to render the tree + // from left to right instead of top to bottom, + // with the idea to render very wide trees in + // left to right order. +} + +void DotClassGraph::buildGraph(const ClassDef *cd,DotNode *n,bool base,int distance) +{ + //printf("DocClassGraph::buildGraph(%s,distance=%d,base=%d)\n", + // cd->name().data(),distance,base); + // ---- Add inheritance relations + + if (m_graphType == Inheritance || m_graphType==Collaboration) + { + BaseClassList *bcl = base ? cd->baseClasses() : cd->subClasses(); + if (bcl) + { + BaseClassListIterator bcli(*bcl); + BaseClassDef *bcd; + for ( ; (bcd=bcli.current()) ; ++bcli ) + { + //printf("-------- inheritance relation %s->%s templ='%s'\n", + // cd->name().data(),bcd->classDef->name().data(),bcd->templSpecifiers.data()); + addClass(bcd->classDef,n,bcd->prot,0,bcd->usedName, + bcd->templSpecifiers,base,distance); + } + } + } + if (m_graphType == Collaboration) + { + // ---- Add usage relations + + UsesClassDict *dict = + base ? cd->usedImplementationClasses() : + cd->usedByImplementationClasses() + ; + if (dict) + { + UsesClassDictIterator ucdi(*dict); + UsesClassDef *ucd; + for (;(ucd=ucdi.current());++ucdi) + { + QCString label; + QDictIterator<void> dvi(*ucd->accessors); + const char *s; + bool first=TRUE; + int count=0; + int maxLabels=10; + for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count) + { + if (first) + { + label=s; + first=FALSE; + } + else + { + label+=QCString("\n")+s; + } + } + if (count==maxLabels) label+="\n..."; + //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data()); + addClass(ucd->classDef,n,EdgeInfo::Purple,label,0, + ucd->templSpecifiers,base,distance); + } + } + } + if (TEMPLATE_RELATIONS && base) + { + ConstraintClassDict *dict = cd->templateTypeConstraints(); + if (dict) + { + ConstraintClassDictIterator ccdi(*dict); + ConstraintClassDef *ccd; + for (;(ccd=ccdi.current());++ccdi) + { + QCString label; + QDictIterator<void> dvi(*ccd->accessors); + const char *s; + bool first=TRUE; + int count=0; + int maxLabels=10; + for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count) + { + if (first) + { + label=s; + first=FALSE; + } + else + { + label+=QCString("\n")+s; + } + } + if (count==maxLabels) label+="\n..."; + //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data()); + addClass(ccd->classDef,n,EdgeInfo::Orange2,label,0, + 0,TRUE,distance); + } + } + } + + // ---- Add template instantiation relations + + if (TEMPLATE_RELATIONS) + { + if (base) // template relations for base classes + { + const ClassDef *templMaster=cd->templateMaster(); + if (templMaster) + { + QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances()); + const ClassDef *templInstance; + for (;(templInstance=cli.current());++cli) + { + if (templInstance==cd) + { + addClass(templMaster,n,EdgeInfo::Orange,cli.currentKey(),0, + 0,TRUE,distance); + } + } + } + } + else // template relations for super classes + { + const QDict<ClassDef> *templInstances = cd->getTemplateInstances(); + if (templInstances) + { + QDictIterator<ClassDef> cli(*templInstances); + const ClassDef *templInstance; + for (;(templInstance=cli.current());++cli) + { + addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0, + 0,FALSE,distance); + } + } + } + } +} + +DotClassGraph::DotClassGraph(const ClassDef *cd,GraphType t) +{ + //printf("--------------- DotClassGraph::DotClassGraph '%s'\n",cd->displayName().data()); + m_graphType = t; + QCString tmp_url=""; + if (cd->isLinkable() && !cd->isHidden()) + { + tmp_url=cd->getReference()+"$"+cd->getOutputFileBase(); + if (!cd->anchor().isEmpty()) + { + tmp_url+="#"+cd->anchor(); + } + } + QCString className = cd->displayName(); + QCString tooltip = cd->briefDescriptionAsTooltip(); + m_startNode = new DotNode(getNextNodeNumber(), + className, + tooltip, + tmp_url.data(), + TRUE, // is a root node + cd + ); + m_startNode->setDistance(0); + m_usedNodes = new QDict<DotNode>(1009); + m_usedNodes->insert(className,m_startNode); + + buildGraph(cd,m_startNode,TRUE,1); + if (t==Inheritance) buildGraph(cd,m_startNode,FALSE,1); + + m_lrRank = determineVisibleNodes(m_startNode,DOT_GRAPH_MAX_NODES,t==Inheritance); + QList<DotNode> openNodeQueue; + openNodeQueue.append(m_startNode); + determineTruncatedNodes(openNodeQueue,t==Inheritance); + + m_collabFileName = cd->collaborationGraphFileName(); + m_inheritFileName = cd->inheritanceGraphFileName(); +} + +bool DotClassGraph::isTrivial() const +{ + if (m_graphType==Inheritance) + return m_startNode->children()==0 && m_startNode->parents()==0; + else + return !UML_LOOK && m_startNode->children()==0; +} + +bool DotClassGraph::isTooBig() const +{ + int numNodes = 0; + numNodes+= m_startNode->children() ? m_startNode->children()->count() : 0; + if (m_graphType==Inheritance) + { + numNodes+= m_startNode->parents() ? m_startNode->parents()->count() : 0; + } + return numNodes>=DOT_GRAPH_MAX_NODES; +} + +DotClassGraph::~DotClassGraph() +{ + DotNode::deleteNodes(m_startNode); + delete m_usedNodes; +} + +QCString DotClassGraph::getBaseName() const +{ + switch (m_graphType) + { + case Collaboration: + return m_collabFileName; + break; + case Inheritance: + return m_inheritFileName; + break; + default: + ASSERT(0); + break; + } + return ""; +} + +void DotClassGraph::computeTheGraph() +{ + computeGraph( + m_startNode, + m_graphType, + m_graphFormat, + m_lrRank ? "LR" : "", + m_graphType == Inheritance, + TRUE, + m_startNode->label(), + m_theGraph + ); +} + +QCString DotClassGraph::getMapLabel() const +{ + QCString mapName; + switch (m_graphType) + { + case Collaboration: + mapName="coll_map"; + break; + case Inheritance: + mapName="inherit_map"; + break; + default: + ASSERT(0); + break; + } + + return escapeCharsInString(m_startNode->label(),FALSE)+"_"+escapeCharsInString(mapName,FALSE); +} + +QCString DotClassGraph::getImgAltText() const +{ + switch (m_graphType) + { + case Collaboration: + return "Collaboration graph"; + break; + case Inheritance: + return "Inheritance graph"; + break; + default: + ASSERT(0); + break; + } + return ""; +} + +QCString DotClassGraph::writeGraph(FTextStream &out, + GraphOutputFormat graphFormat, + EmbeddedOutputFormat textFormat, + const char *path, + const char *fileName, + const char *relPath, + bool /*isTBRank*/, + bool generateImageMap, + int graphId) +{ + return DotGraph::writeGraph(out, graphFormat, textFormat, path, fileName, relPath, generateImageMap, graphId); +} + +//-------------------------------------------------------------------- + +void DotClassGraph::writeXML(FTextStream &t) +{ + QDictIterator<DotNode> dni(*m_usedNodes); + DotNode *node; + for (;(node=dni.current());++dni) + { + node->writeXML(t,TRUE); + } +} + +void DotClassGraph::writeDocbook(FTextStream &t) +{ + QDictIterator<DotNode> dni(*m_usedNodes); + DotNode *node; + for (;(node=dni.current());++dni) + { + node->writeDocbook(t,TRUE); + } +} + +void DotClassGraph::writeDEF(FTextStream &t) +{ + QDictIterator<DotNode> dni(*m_usedNodes); + DotNode *node; + for (;(node=dni.current());++dni) + { + node->writeDEF(t); + } +} |