/***************************************************************************** * * * * * Copyright (C) 1997-2008 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. * */ #ifdef _WIN32 #include #define BITMAP W_BITMAP #endif #include #include "dot.h" #include "doxygen.h" #include "message.h" #include "util.h" #include "config.h" #include "language.h" #include "defargs.h" #include "docparser.h" #include "debug.h" #include "pagedef.h" #include "portable.h" #include "dirdef.h" #include #include #include #include #define MAP_CMD "cmapx" //#define FONTNAME "FreeSans" #define FONTNAME getDotFontName() #define FONTSIZE getDotFontSize() //-------------------------------------------------------------------- static const int maxCmdLine = 40960; /*! mapping from protection levels to color names */ static const char *edgeColorMap[] = { "midnightblue", // Public "darkgreen", // Protected "firebrick4", // Private "darkorchid3", // "use" relation "grey75", // Undocumented "orange" // template relation }; static const char *arrowStyle[] = { "empty", // Public "empty", // Protected "empty", // Private "open", // "use" relation 0, // Undocumented 0 // template relation }; static const char *edgeStyleMap[] = { "solid", // inheritance "dashed" // usage }; static QCString getDotFontName() { static QCString dotFontName = Config_getString("DOT_FONTNAME"); if (dotFontName.isEmpty()) dotFontName="FreeSans"; return dotFontName; } static int getDotFontSize() { static int dotFontSize = Config_getInt("DOT_FONTSIZE"); if (dotFontSize<4) dotFontSize=4; return dotFontSize; } static void writeGraphHeader(QTextStream &t) { t << "digraph G" << endl; t << "{" << endl; if (Config_getBool("DOT_TRANSPARENT")) { t << " bgcolor=\"transparent\";" << endl; } t << " edge [fontname=\"" << FONTNAME << "\"," "fontsize=\"" << FONTSIZE << "\"," "labelfontname=\"" << FONTNAME << "\"," "labelfontsize=\"" << FONTSIZE << "\"];\n"; t << " node [fontname=\"" << FONTNAME << "\"," "fontsize=\"" << FONTSIZE << "\",shape=record];\n"; } static void writeGraphFooter(QTextStream &t) { t << "}" << endl; } /*! converts the rectangles in a client site image map into a stream * \param t the stream to which the result is written. * \param mapName the name of the map file. * \param relPath the relative path to the root of the output directory * (used in case CREATE_SUBDIRS is enabled). * \param urlOnly if FALSE the url field in the map contains an external * references followed by a $ and then the URL. * \param context the context (file, class, or namespace) in which the * map file was found * \returns TRUE if succesful. */ static bool convertMapFile(QTextStream &t,const char *mapName, const QCString relPath, bool urlOnly=FALSE, const QString &context=QString()) { QFile f(mapName); if (!f.open(IO_ReadOnly)) { err("Error opening map file %s for inclusion in the docs!\n",mapName); return FALSE; } const int maxLineLen=10240; while (!f.atEnd()) // foreach line { QCString buf(maxLineLen); int numBytes = f.readLine(buf.data(),maxLineLen); buf[numBytes-1]='\0'; if (buf.left(5)=="ref().isEmpty()) { if ((dest=Doxygen::tagDestinationDict[df->ref()])) result += *dest + "/"; } else if (!relPath.isEmpty()) { result += relPath; } if (!df->file().isEmpty()) result += df->file().data() + Doxygen::htmlFileExtension; if (!df->anchor().isEmpty()) result += "#" + df->anchor(); delete df; result += "\""; } else { result = "href=\"" + link + "\""; } } else // ref$url (external ref via tag file), or $url (local ref) { int marker = link.find('$'); if (marker!=-1) { QCString ref = link.left(marker); QCString url = link.mid(marker+1); if (!ref.isEmpty()) { result = "doxygen=\"" + ref + ":"; if ((dest=Doxygen::tagDestinationDict[ref])) result += *dest + "/"; result += "\" "; } result+= "href=\""; if (!ref.isEmpty()) { if ((dest=Doxygen::tagDestinationDict[ref])) result += *dest + "/"; } else if (!relPath.isEmpty()) { result += relPath; } result+= url + "\""; } else // should not happen, but handle properly anyway { result = "href=\"" + link + "\""; } } QCString leftPart = buf.left(indexS); QCString rightPart = buf.mid(indexE+1); buf = leftPart + result + rightPart; } t << buf; } } return TRUE; } static QArray s_newNumber; static int s_max_newNumber=0; inline int reNumberNode(int number, bool doReNumbering) { if (!doReNumbering) { return number; } else { int s = s_newNumber.size(); if (number>=s) { int ns=0; ns = s * 3 / 2 + 5; // new size if (number>=ns) // number still doesn't fit { ns = number * 3 / 2 + 5; } s_newNumber.resize(ns); for (int i=s;i { public: DotNodeList() : QList() {} ~DotNodeList() {} int compareItems(GCI item1,GCI item2) { return stricmp(((DotNode *)item1)->m_label,((DotNode *)item2)->m_label); } }; //-------------------------------------------------------------------- DotRunner::DotRunner(const char *file) : m_file(file) { m_jobs.setAutoDelete(TRUE); } void DotRunner::addJob(const char *format,const char *output) { QCString args = QCString("-T")+format+" -o \""+output+"\""; m_jobs.append(new QCString(args)); } bool DotRunner::run() { int exitCode=0; static QCString dotExe = Config_getString("DOT_PATH")+"dot"; QCString dotArgs; QListIterator li(m_jobs); QCString *s; if (Config_getBool("DOT_MULTI_TARGETS")) { dotArgs="\""+m_file+"\""; for (li.toFirst();(s=li.current());++li) { dotArgs+=' '; dotArgs+=*s; } if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0) { goto error; } } else { for (li.toFirst();(s=li.current());++li) { dotArgs="\""+m_file+"\" "+*s; if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0) { goto error; } } } return TRUE; error: err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n", exitCode,dotExe.data(),dotArgs.data()); return FALSE; } //-------------------------------------------------------------------- /*! helper function that deletes all nodes in a connected graph, given * one of the graph's nodes */ static void deleteNodes(DotNode *node,SDict *skipNodes=0) { //printf("deleteNodes skipNodes=%p\n",skipNodes); static DotNodeList deletedNodes; deletedNodes.setAutoDelete(TRUE); node->deleteNode(deletedNodes,skipNodes); // collect nodes to be deleted. deletedNodes.clear(); // actually remove the nodes. } DotNode::DotNode(int n,const char *lab,const char *tip, const char *url, bool isRoot,ClassDef *cd) : m_subgraphId(-1) , m_number(n) , m_label(lab) , m_tooltip(tip) , m_url(url) , m_parents(0) , m_children(0) , m_edgeInfo(0) , m_deleted(FALSE) , m_written(FALSE) , m_hasDoc(FALSE) , m_isRoot(isRoot) , m_classDef(cd) , m_visible(FALSE) , m_truncated(Unknown) , m_distance(1000) { } DotNode::~DotNode() { delete m_children; delete m_parents; delete m_edgeInfo; } void DotNode::addChild(DotNode *n, int edgeColor, int edgeStyle, const char *edgeLab, const char *edgeURL, int edgeLabCol ) { if (m_children==0) { m_children = new QList; m_edgeInfo = new QList; m_edgeInfo->setAutoDelete(TRUE); } m_children->append(n); EdgeInfo *ei = new EdgeInfo; ei->m_color = edgeColor; ei->m_style = edgeStyle; ei->m_label = edgeLab; ei->m_url = edgeURL; if (edgeLabCol==-1) ei->m_labColor=edgeColor; else ei->m_labColor=edgeLabCol; m_edgeInfo->append(ei); } void DotNode::addParent(DotNode *n) { if (m_parents==0) { m_parents = new QList; } m_parents->append(n); } void DotNode::removeChild(DotNode *n) { if (m_children) m_children->remove(n); } void DotNode::removeParent(DotNode *n) { if (m_parents) m_parents->remove(n); } void DotNode::deleteNode(DotNodeList &deletedList,SDict *skipNodes) { if (m_deleted) return; // avoid recursive loops in case the graph has cycles m_deleted=TRUE; if (m_parents!=0) // delete all parent nodes of this node { QListIterator dnlip(*m_parents); DotNode *pn; for (dnlip.toFirst();(pn=dnlip.current());++dnlip) { //pn->removeChild(this); pn->deleteNode(deletedList,skipNodes); } } if (m_children!=0) // delete all child nodes of this node { QListIterator dnlic(*m_children); DotNode *cn; for (dnlic.toFirst();(cn=dnlic.current());++dnlic) { //cn->removeParent(this); cn->deleteNode(deletedList,skipNodes); } } // add this node to the list of deleted nodes. //printf("skipNodes=%p find(%p)=%p\n",skipNodes,this,skipNodes ? skipNodes->find((int)this) : 0); if (skipNodes==0 || skipNodes->find((char*)this)==0) { //printf("deleting\n"); deletedList.append(this); } } void DotNode::setDistance(int distance) { if (distance': result+="\\>"; break; case '|': result+="\\|"; break; case '{': result+="\\{"; break; case '}': result+="\\}"; break; case '"': result+="\\\""; break; default: result+=c; break; } } return result; } static QCString escapeTooltip(const QCString &tooltip) { QCString result; const char *p=tooltip.data(); if (p==0) return result; char c; while ((c=*p++)) { switch(c) { case '\\': result+="\\\\"; break; default: result+=c; break; } } return result; } static void writeBoxMemberList(QTextStream &t,char prot,MemberList *ml,ClassDef *scope) { if (ml) { MemberListIterator mlia(*ml); MemberDef *mma; for (mlia.toFirst();(mma = mlia.current());++mlia) { if (mma->getClassDef() == scope) { t << prot << " " << convertLabel(mma->name()); if (!mma->isObjCMethod() && (mma->isFunction() || mma->isSlot() || mma->isSignal())) t << "()"; t << "\\l"; } } // write member groups within the memberlist MemberGroupList *mgl = ml->getMemberGroupList(); if (mgl) { MemberGroupListIterator mgli(*mgl); MemberGroup *mg; for (mgli.toFirst();(mg=mgli.current());++mgli) { if (mg->members()) { writeBoxMemberList(t,prot,mg->members(),scope); } } } } } void DotNode::writeBox(QTextStream &t, GraphType gt, GraphOutputFormat /*format*/, bool hasNonReachableChildren, bool reNumber) { const char *labCol = m_url.isEmpty() ? "grey75" : // non link ( (hasNonReachableChildren) ? "red" : "black" ); t << " Node" << reNumberNode(m_number,reNumber) << " [label=\""; if (m_classDef && Config_getBool("UML_LOOK") && (gt==Inheritance || gt==Collaboration)) { t << "{" << convertLabel(m_label); t << "\\n|"; writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubAttribs),m_classDef); writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticAttribs),m_classDef); writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::properties),m_classDef); writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacAttribs),m_classDef); writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticAttribs),m_classDef); writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proAttribs),m_classDef); writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticAttribs),m_classDef); writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priAttribs),m_classDef); writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticAttribs),m_classDef); t << "|"; writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubMethods),m_classDef); writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticMethods),m_classDef); writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubSlots),m_classDef); writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacMethods),m_classDef); writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticMethods),m_classDef); writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proMethods),m_classDef); writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticMethods),m_classDef); writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proSlots),m_classDef); writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priMethods),m_classDef); writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticMethods),m_classDef); writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priSlots),m_classDef); if (m_classDef->getMemberGroupSDict()) { MemberGroupSDict::Iterator mgdi(*m_classDef->getMemberGroupSDict()); MemberGroup *mg; for (mgdi.toFirst();(mg=mgdi.current());++mgdi) { if (mg->members()) { writeBoxMemberList(t,'*',mg->members(),m_classDef); } } } t << "}"; } else // standard look { t << convertLabel(m_label); } t << "\",height=0.2,width=0.4"; if (m_isRoot) { t << ",color=\"black\", fillcolor=\"grey75\", style=\"filled\" fontcolor=\"black\""; } else { if (!Config_getBool("DOT_TRANSPARENT")) { t << ",color=\"" << labCol << "\", fillcolor=\"white\", style=\"filled\""; } else { t << ",color=\"" << labCol << "\""; } if (!m_url.isEmpty()) { int anchorPos = m_url.findRev('#'); if (anchorPos==-1) { t << ",URL=\"" << m_url << Doxygen::htmlFileExtension << "\""; } else { t << ",URL=\"" << m_url.left(anchorPos) << Doxygen::htmlFileExtension << m_url.right(m_url.length()-anchorPos) << "\""; } } if (!m_tooltip.isEmpty()) { t << ",tooltip=\"" << escapeTooltip(m_tooltip) << "\""; } } t << "];" << endl; } void DotNode::writeArrow(QTextStream &t, GraphType gt, GraphOutputFormat format, DotNode *cn, EdgeInfo *ei, bool topDown, bool pointBack, bool reNumber ) { t << " Node"; if (topDown) t << reNumberNode(cn->number(),reNumber); else t << reNumberNode(m_number,reNumber); t << " -> Node"; if (topDown) t << reNumberNode(m_number,reNumber); else t << reNumberNode(cn->number(),reNumber); t << " ["; if (pointBack) t << "dir=back,"; t << "color=\"" << edgeColorMap[ei->m_color] << "\",fontsize=\"" << FONTSIZE << "\",style=\"" << edgeStyleMap[ei->m_style] << "\""; if (!ei->m_label.isEmpty()) { t << ",label=\"" << convertLabel(ei->m_label) << "\""; } if (Config_getBool("UML_LOOK") && arrowStyle[ei->m_color] && (gt==Inheritance || gt==Collaboration) ) { if (pointBack) t << ",arrowtail=\"" << arrowStyle[ei->m_color] << "\""; else t << ",arrowhead=\"" << arrowStyle[ei->m_color] << "\""; } if (format==BITMAP) t << ",fontname=\"" << FONTNAME << "\""; t << "];" << endl; } void DotNode::write(QTextStream &t, GraphType gt, GraphOutputFormat format, bool topDown, bool toChildren, bool backArrows, bool reNumber ) { //printf("DotNode::write(%d) name=%s this=%p written=%d\n",distance,m_label.data(),this,m_written); if (m_written) return; // node already written to the output if (!m_visible) return; // node is not visible writeBox(t,gt,format,m_truncated==Truncated,reNumber); m_written=TRUE; QList *nl = toChildren ? m_children : m_parents; if (nl) { if (toChildren) { QListIterator dnli1(*nl); QListIterator dnli2(*m_edgeInfo); DotNode *cn; for (dnli1.toFirst();(cn=dnli1.current());++dnli1,++dnli2) { if (cn->isVisible()) { //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",cn->label().data()); writeArrow(t,gt,format,cn,dnli2.current(),topDown,backArrows,reNumber); } cn->write(t,gt,format,topDown,toChildren,backArrows,reNumber); } } else // render parents { QListIterator dnli(*nl); DotNode *pn; for (dnli.toFirst();(pn=dnli.current());++dnli) { if (pn->isVisible()) { //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",pn->label().data()); writeArrow(t, gt, format, pn, pn->m_edgeInfo->at(pn->m_children->findRef(this)), FALSE, backArrows, reNumber ); } pn->write(t,gt,format,TRUE,FALSE,backArrows,reNumber); } } } //printf("end DotNode::write(%d) name=%s\n",distance,m_label.data()); } void DotNode::writeXML(QTextStream &t,bool isClassGraph) { t << " " << endl; t << " " << endl; if (!m_url.isEmpty()) { QCString url(m_url); char *refPtr = url.data(); char *urlPtr = strchr(url.data(),'$'); if (urlPtr) { *urlPtr++='\0'; t << " " << endl; } } if (m_children) { QListIterator nli(*m_children); QListIterator eli(*m_edgeInfo); DotNode *childNode; EdgeInfo *edgeInfo; for (;(childNode=nli.current());++nli,++eli) { edgeInfo=eli.current(); t << " m_number << "\" relation=\""; if (isClassGraph) { switch(edgeInfo->m_color) { case EdgeInfo::Blue: t << "public-inheritance"; break; case EdgeInfo::Green: t << "protected-inheritance"; break; case EdgeInfo::Red: t << "private-inheritance"; break; case EdgeInfo::Purple: t << "usage"; break; case EdgeInfo::Orange: t << "template-instance"; break; case EdgeInfo::Grey: ASSERT(0); break; } } else // include graph { t << "include"; } t << "\">" << endl; if (!edgeInfo->m_label.isEmpty()) { int p=0; int ni; while ((ni=edgeInfo->m_label.find('\n',p))!=-1) { t << " " << convertToXML(edgeInfo->m_label.mid(p,ni-p)) << "" << endl; p=ni+1; } t << " " << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p)) << "" << endl; } t << " " << endl; } } t << " " << endl; } void DotNode::writeDEF(QTextStream &t) { char* nodePrefix = " node-"; t << " node = {" << endl; t << nodePrefix << "id = " << m_number << ';' << endl; t << nodePrefix << "label = '" << m_label << "';" << endl; if (!m_url.isEmpty()) { QCString url(m_url); char *refPtr = url.data(); char *urlPtr = strchr(url.data(),'$'); if (urlPtr) { *urlPtr++='\0'; t << nodePrefix << "link = {" << endl << " " << nodePrefix << "link-id = '" << urlPtr << "';" << endl; if (*refPtr!='\0') { t << " " << nodePrefix << "link-external = '" << refPtr << "';" << endl; } t << " };" << endl; } } if (m_children) { QListIterator nli(*m_children); QListIterator eli(*m_edgeInfo); DotNode *childNode; EdgeInfo *edgeInfo; for (;(childNode=nli.current());++nli,++eli) { edgeInfo=eli.current(); t << " node-child = {" << endl; t << " child-id = '" << childNode->m_number << "';" << endl; t << " relation = "; switch(edgeInfo->m_color) { case EdgeInfo::Blue: t << "public-inheritance"; break; case EdgeInfo::Green: t << "protected-inheritance"; break; case EdgeInfo::Red: t << "private-inheritance"; break; case EdgeInfo::Purple: t << "usage"; break; case EdgeInfo::Orange: t << "template-instance"; break; case EdgeInfo::Grey: ASSERT(0); break; } t << ';' << endl; if (!edgeInfo->m_label.isEmpty()) { t << " edgelabel = <<_EnD_oF_dEf_TeXt_" << endl << edgeInfo->m_label << endl << "_EnD_oF_dEf_TeXt_;" << endl; } t << " }; /* node-child */" << endl; } /* for (;childNode...) */ } t << " }; /* node */" << endl; } void DotNode::clearWriteFlag() { m_written=FALSE; if (m_parents!=0) { QListIterator dnlip(*m_parents); DotNode *pn; for (dnlip.toFirst();(pn=dnlip.current());++dnlip) { if (pn->m_written) { pn->clearWriteFlag(); } } } if (m_children!=0) { QListIterator dnlic(*m_children); DotNode *cn; for (dnlic.toFirst();(cn=dnlic.current());++dnlic) { if (cn->m_written) { cn->clearWriteFlag(); } } } } void DotNode::colorConnectedNodes(int curColor) { if (m_children) { QListIterator dnlic(*m_children); DotNode *cn; for (dnlic.toFirst();(cn=dnlic.current());++dnlic) { if (cn->m_subgraphId==-1) // uncolored child node { cn->m_subgraphId=curColor; cn->markAsVisible(); cn->colorConnectedNodes(curColor); //printf("coloring node %s (%p): %d\n",cn->m_label.data(),cn,cn->m_subgraphId); } } } if (m_parents) { QListIterator dnlip(*m_parents); DotNode *pn; for (dnlip.toFirst();(pn=dnlip.current());++dnlip) { if (pn->m_subgraphId==-1) // uncolored parent node { pn->m_subgraphId=curColor; pn->markAsVisible(); pn->colorConnectedNodes(curColor); //printf("coloring node %s (%p): %d\n",pn->m_label.data(),pn,pn->m_subgraphId); } } } } const DotNode *DotNode::findDocNode() const { if (!m_url.isEmpty()) return this; //printf("findDocNode(): `%s'\n",m_label.data()); if (m_parents) { QListIterator dnli(*m_parents); DotNode *pn; for (dnli.toFirst();(pn=dnli.current());++dnli) { if (!pn->m_hasDoc) { pn->m_hasDoc=TRUE; const DotNode *dn = pn->findDocNode(); if (dn) return dn; } } } if (m_children) { QListIterator dnli(*m_children); DotNode *cn; for (dnli.toFirst();(cn=dnli.current());++dnli) { if (!cn->m_hasDoc) { cn->m_hasDoc=TRUE; const DotNode *dn = cn->findDocNode(); if (dn) return dn; } } } return 0; } //-------------------------------------------------------------------- int DotGfxHierarchyTable::m_curNodeNumber; void DotGfxHierarchyTable::writeGraph(QTextStream &out,const char *path) const { //printf("DotGfxHierarchyTable::writeGraph(%s)\n",name); //printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count()); if (m_rootSubgraphs->count()==0) return; QDir d(path); // store the original directory if (!d.exists()) { err("Error: Output dir %s does not exist!\n",path); exit(1); } QCString oldDir = convertToQCString(QDir::currentDirPath()); // go to the html output directory (i.e. path) QDir::setCurrent(d.absPath()); QDir thisDir; // put each connected subgraph of the hierarchy in a row of the HTML output out << "" << endl; QListIterator dnli(*m_rootSubgraphs); DotNode *n; int count=0; for (dnli.toFirst();(n=dnli.current());++dnli) { QCString baseName; QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); baseName.sprintf("inherit_graph_%d",count++); baseName = convertNameToFile(baseName); QCString imgName=baseName+"."+ imgExt; QCString mapName=baseName+".map"; QListIterator dnli2(*m_rootNodes); DotNode *node; // compute md5 checksum of the graph were are about to generate QString theGraph; QTextStream md5stream(&theGraph,IO_WriteOnly); md5stream.setEncoding(md5stream.UnicodeUTF8); writeGraphHeader(md5stream); md5stream << " rankdir=LR;" << endl; for (dnli2.toFirst();(node=dnli2.current());++dnli2) { if (node->m_subgraphId==n->m_subgraphId) { node->clearWriteFlag(); } } for (dnli2.toFirst();(node=dnli2.current());++dnli2) { if (node->m_subgraphId==n->m_subgraphId) { node->write(md5stream,DotNode::Hierarchy,BITMAP,FALSE,TRUE,TRUE,TRUE); } } writeGraphFooter(md5stream); resetReNumbering(); uchar md5_sig[16]; QCString sigStr(33); MD5Buffer((const unsigned char *)theGraph.ascii(),theGraph.length(),md5_sig); MD5SigToString(md5_sig,sigStr.data(),33); if (checkAndUpdateMd5Signature(baseName,sigStr) || !QFileInfo(mapName).exists()) { // image was new or has changed QCString dotName=baseName+".dot"; QFile f(dotName); if (!f.open(IO_WriteOnly)) return; QTextStream t(&f); t.setEncoding(t.UnicodeUTF8); t << theGraph; f.close(); resetReNumbering(); DotRunner dotRun(dotName); dotRun.addJob(imgExt,imgName); dotRun.addJob(MAP_CMD,mapName); if (!dotRun.run()) { out << "
" << endl; return; } checkDotResult(imgName); if (Config_getBool("DOT_CLEANUP")) thisDir.remove(dotName); } Doxygen::indexList.addImageFile(imgName); // write image and map in a table row QCString mapLabel = escapeCharsInString(n->m_label,FALSE); out << "\"\"" << endl; out << "" << endl; convertMapFile(out,mapName,""); out << "" << endl; //thisDir.remove(mapName); } out << "" << endl; QDir::setCurrent(oldDir); } void DotGfxHierarchyTable::addHierarchy(DotNode *n,ClassDef *cd,bool hideSuper) { //printf("addHierarchy `%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count()); if (cd->subClasses()) { BaseClassListIterator bcli(*cd->subClasses()); BaseClassDef *bcd; for ( ; (bcd=bcli.current()) ; ++bcli ) { ClassDef *bClass=bcd->classDef; //printf(" Trying sub class=`%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count()); if (bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses())) { DotNode *bn; //printf(" Node `%s' Found visible class=`%s'\n",n->m_label.data(), // bClass->name().data()); if ((bn=m_usedNodes->find(bClass->name()))) // node already present { if (n->m_children==0 || n->m_children->findRef(bn)==-1) // no arrow yet { n->addChild(bn,bcd->prot); bn->addParent(n); //printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n", // n->m_label.data(), // bn->m_label.data(), // bn->m_children ? bn->m_children->count() : 0, // bn->m_parents ? bn->m_parents->count() : 0 // ); } //else //{ // printf(" Class already has an arrow!\n"); //} } else { QCString tmp_url=""; if (bClass->isLinkable() && !bClass->isHidden()) { tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase(); } QCString tooltip = bClass->briefDescriptionAsTooltip(); bn = new DotNode(m_curNodeNumber++, bClass->displayName(), tooltip, tmp_url.data() ); n->addChild(bn,bcd->prot); bn->addParent(n); //printf(" Adding node %s to new base node %s (c=%d,p=%d)\n", // n->m_label.data(), // bn->m_label.data(), // bn->m_children ? bn->m_children->count() : 0, // bn->m_parents ? bn->m_parents->count() : 0 // ); //printf(" inserting %s (%p)\n",bClass->name().data(),bn); m_usedNodes->insert(bClass->name(),bn); // add node to the used list } if (!bClass->visited && !hideSuper && bClass->subClasses()) { bool wasVisited=bClass->visited; bClass->visited=TRUE; addHierarchy(bn,bClass,wasVisited); } } } } //printf("end addHierarchy\n"); } void DotGfxHierarchyTable::addClassList(ClassSDict *cl) { ClassSDict::Iterator cli(*cl); ClassDef *cd; for (cli.toLast();(cd=cli.current());--cli) { //printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count()); if (!hasVisibleRoot(cd->baseClasses()) && cd->isVisibleInHierarchy() ) // root node in the forest { QCString tmp_url=""; if (cd->isLinkable() && !cd->isHidden()) { tmp_url=cd->getReference()+"$"+cd->getOutputFileBase(); } //printf("Inserting root class %s\n",cd->name().data()); QCString tooltip = cd->briefDescriptionAsTooltip(); DotNode *n = new DotNode(m_curNodeNumber++, cd->displayName(), tooltip, tmp_url.data()); //m_usedNodes->clear(); m_usedNodes->insert(cd->name(),n); m_rootNodes->insert(0,n); if (!cd->visited && cd->subClasses()) { addHierarchy(n,cd,cd->visited); cd->visited=TRUE; } } } } DotGfxHierarchyTable::DotGfxHierarchyTable() { m_curNodeNumber=0; m_rootNodes = new QList; m_usedNodes = new QDict(1009); m_usedNodes->setAutoDelete(TRUE); m_rootSubgraphs = new DotNodeList; // build a graph with each class as a node and the inheritance relations // as edges initClassHierarchy(Doxygen::classSDict); initClassHierarchy(Doxygen::hiddenClasses); addClassList(Doxygen::classSDict); addClassList(Doxygen::hiddenClasses); // m_usedNodes now contains all nodes in the graph // color the graph into a set of independent subgraphs bool done=FALSE; int curColor=0; QListIterator dnli(*m_rootNodes); while (!done) // there are still nodes to color { DotNode *n; done=TRUE; // we are done unless there are still uncolored nodes for (dnli.toLast();(n=dnli.current());--dnli) { if (n->m_subgraphId==-1) // not yet colored { //printf("Starting at node %s (%p): %d\n",n->m_label.data(),n,curColor); done=FALSE; // still uncolored nodes n->m_subgraphId=curColor; n->markAsVisible(); n->colorConnectedNodes(curColor); curColor++; const DotNode *dn=n->findDocNode(); if (dn!=0) m_rootSubgraphs->inSort(dn); else m_rootSubgraphs->inSort(n); } } } //printf("Number of independent subgraphs: %d\n",curColor); //QListIterator dnli2(*m_rootSubgraphs); //DotNode *n; //for (dnli2.toFirst();(n=dnli2.current());++dnli2) //{ // printf("Node %s color=%d (c=%d,p=%d)\n", // n->m_label.data(),n->m_subgraphId, // n->m_children?n->m_children->count():0, // n->m_parents?n->m_parents->count():0); //} } DotGfxHierarchyTable::~DotGfxHierarchyTable() { //printf("DotGfxHierarchyTable::~DotGfxHierarchyTable\n"); //QDictIterator di(*m_usedNodes); //DotNode *n; //for (;(n=di.current());++di) //{ // printf("Node %p: %s\n",n,n->label().data()); //} delete m_rootNodes; delete m_usedNodes; delete m_rootSubgraphs; } //-------------------------------------------------------------------- int DotClassGraph::m_curNodeNumber = 0; void DotClassGraph::addClass(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) ? EdgeInfo::Dashed : EdgeInfo::Solid; QCString className; 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->m_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->m_label.data(),n->m_label.data()); } else // new class { QCString displayName=className; if (Config_getBool("HIDE_SCOPE_NAMES")) displayName=stripScope(displayName); QCString tmp_url; if (cd->isLinkable() && !cd->isHidden()) { tmp_url=cd->getReference()+"$"+cd->getOutputFileBase(); } QCString tooltip = cd->briefDescriptionAsTooltip(); bn = new DotNode(m_curNodeNumber++, 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->m_label.data(),cd->isHidden(),tmp_url.data()); buildGraph(cd,bn,base,distance+1); } } void DotClassGraph::determineTruncatedNodes(QList &queue,bool includeParents) { while (queue.count()>0) { DotNode *n = queue.take(0); if (n->isVisible() && n->isTruncated()==DotNode::Unknown) { bool truncated = FALSE; if (n->m_children) { QListIterator li(*n->m_children); DotNode *dn; for (li.toFirst();(dn=li.current());++li) { if (!dn->isVisible()) truncated = TRUE; else queue.append(dn); } } if (n->m_parents && includeParents) { QListIterator li(*n->m_parents); 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 childQueue; QList parentQueue; QArray childTreeWidth; QArray 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) { static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH"); if (childQueue.count()>0) { DotNode *n = childQueue.take(0); int distance = n->distance(); if (!n->isVisible() && distance0) { int oldSize=(int)childTreeWidth.size(); if (distance>oldSize) { childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance)); int i; for (i=oldSize;ilabel().length(); } n->markAsVisible(); maxNodes--; // add direct children if (n->m_children) { QListIterator li(*n->m_children); 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()distance(); if (distance>0) { int oldSize = (int)parentTreeWidth.size(); if (distance>oldSize) { parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance)); int i; for (i=oldSize;ilabel().length(); } n->markAsVisible(); maxNodes--; // add direct parents if (n->m_parents) { QListIterator li(*n->m_parents); DotNode *dn; for (li.toFirst();(dn=li.current());++li) { parentQueue.append(dn); } } } } } if (Config_getBool("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;imaxWidth) maxWidth=childTreeWidth.at(i); } for (i=0;imaxWidth) 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(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 == DotNode::Inheritance || m_graphType==DotNode::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 == DotNode::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 dvi(*ucd->accessors); const char *s; bool first=TRUE; int count=0; int maxLabels=10; for (;(s=dvi.currentKey()) && countclassDef->name().data(),ucd->templSpecifiers.data()); addClass(ucd->classDef,n,EdgeInfo::Purple,label,0, ucd->templSpecifiers,base,distance); } } } // ---- Add template instantiation relations static bool templateRelations = Config_getBool("TEMPLATE_RELATIONS"); if (templateRelations) { if (base) // template relations for base classes { ClassDef *templMaster=cd->templateMaster(); if (templMaster) { QDictIterator cli(*templMaster->getTemplateInstances()); 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 { QDict *templInstances = cd->getTemplateInstances(); if (templInstances) { QDictIterator cli(*templInstances); ClassDef *templInstance; for (;(templInstance=cli.current());++cli) { addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0, 0,FALSE,distance); } } } } } DotClassGraph::DotClassGraph(ClassDef *cd,DotNode::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(); } QCString className = cd->displayName(); QCString tooltip = cd->briefDescriptionAsTooltip(); m_startNode = new DotNode(m_curNodeNumber++, className, tooltip, tmp_url.data(), TRUE, // is a root node cd ); m_startNode->setDistance(0); m_usedNodes = new QDict(1009); m_usedNodes->insert(className,m_startNode); //printf("Root node %s\n",cd->name().data()); //if (m_recDepth>0) //{ buildGraph(cd,m_startNode,TRUE,1); if (t==DotNode::Inheritance) buildGraph(cd,m_startNode,FALSE,1); //} static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES"); //int directChildNodes = 1; //if (m_startNode->m_children!=0) // directChildNodes+=m_startNode->m_children->count(); //if (t==DotNode::Inheritance && m_startNode->m_parents!=0) // directChildNodes+=m_startNode->m_parents->count(); //if (directChildNodes>maxNodes) maxNodes=directChildNodes; //openNodeQueue.append(m_startNode); m_lrRank = determineVisibleNodes(m_startNode,maxNodes,t==DotNode::Inheritance); QList openNodeQueue; openNodeQueue.append(m_startNode); determineTruncatedNodes(openNodeQueue,t==DotNode::Inheritance); m_diskName = cd->getFileBase().copy(); } bool DotClassGraph::isTrivial() const { if (m_graphType==DotNode::Inheritance) return m_startNode->m_children==0 && m_startNode->m_parents==0; else return m_startNode->m_children==0; } bool DotClassGraph::isTooBig() const { static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES"); int numNodes = 0; numNodes+= m_startNode->m_children ? m_startNode->m_children->count() : 0; if (m_graphType==DotNode::Inheritance) { numNodes+= m_startNode->m_parents ? m_startNode->m_parents->count() : 0; } return numNodes>=maxNodes; } DotClassGraph::~DotClassGraph() { deleteNodes(m_startNode); delete m_usedNodes; } /*! Computes a 16 byte md5 checksum for a given dot graph. * The md5 checksum is returned as a 32 character ASCII string. */ QCString computeMd5Signature(DotNode *root, DotNode::GraphType gt, GraphOutputFormat format, bool lrRank, bool renderParents, bool backArrows, QCString &graphStr ) { bool reNumber=TRUE; //printf("computeMd5Signature\n"); QString buf; QTextStream md5stream(&buf,IO_WriteOnly); md5stream.setEncoding(md5stream.UnicodeUTF8); writeGraphHeader(md5stream); if (lrRank) { md5stream << " rankdir=LR;" << endl; } root->clearWriteFlag(); root->write(md5stream, gt, format, gt!=DotNode::CallGraph && gt!=DotNode::Dependency, TRUE, backArrows, reNumber); if (renderParents && root->m_parents) { QListIterator dnli(*root->m_parents); DotNode *pn; for (dnli.toFirst();(pn=dnli.current());++dnli) { if (pn->isVisible()) { root->writeArrow(md5stream, // stream gt, // graph type format, // output format pn, // child node pn->m_edgeInfo->at(pn->m_children->findRef(root)), // edge info FALSE, // topDown? backArrows, // point back? reNumber // renumber nodes ); } pn->write(md5stream, // stream gt, // graph type format, // output format TRUE, // topDown? FALSE, // toChildren? backArrows, // backward pointing arrows? reNumber // renumber nodes? ); } } writeGraphFooter(md5stream); uchar md5_sig[16]; QCString sigStr(33); MD5Buffer((const unsigned char *)buf.ascii(),buf.length(),md5_sig); MD5SigToString(md5_sig,sigStr.data(),33); if (reNumber) { resetReNumbering(); } graphStr=buf.ascii(); //printf("md5: %s | file: %s\n",sigStr,baseName.data()); return sigStr; } static bool updateDotGraph(DotNode *root, DotNode::GraphType gt, //QDir &thisDir, const QCString &baseName, GraphOutputFormat format, bool lrRank, bool renderParents, bool backArrows ) { QCString theGraph; // TODO: write graph to theGraph, then compute md5 checksum QCString md5 = computeMd5Signature( root,gt,format,lrRank,renderParents,backArrows,theGraph); if (checkAndUpdateMd5Signature(baseName,md5)) // graph needs to be regenerated { QFile f; f.setName(baseName+".dot"); if (f.open(IO_WriteOnly)) { QTextStream t(&f); t.setEncoding(t.UnicodeUTF8); t << theGraph; } return TRUE; } return FALSE; } QCString DotClassGraph::diskName() const { QCString result=m_diskName.copy(); switch (m_graphType) { case DotNode::Collaboration: result+="_coll_graph"; break; //case Interface: // result+="_intf_graph"; // break; case DotNode::Inheritance: result+="_inherit_graph"; break; default: ASSERT(0); break; } return result; } QCString DotClassGraph::writeGraph(QTextStream &out, GraphOutputFormat format, const char *path, const char *relPath, bool /*isTBRank*/, bool generateImageMap) const { QDir d(path); // store the original directory if (!d.exists()) { err("Error: Output dir %s does not exist!\n",path); exit(1); } QCString oldDir = convertToQCString(QDir::currentDirPath()); // go to the html output directory (i.e. path) QDir::setCurrent(d.absPath()); QDir thisDir; QCString baseName; QCString mapName; switch (m_graphType) { case DotNode::Collaboration: mapName="coll_map"; break; //case Interface: // mapName="intf_map"; // break; case DotNode::Inheritance: mapName="inherit_map"; break; default: ASSERT(0); break; } baseName = convertNameToFile(diskName()); QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); if (updateDotGraph(m_startNode, m_graphType, baseName, format, m_lrRank, //!isTBRank, m_graphType==DotNode::Inheritance, TRUE ) ) { if (format==BITMAP) // run dot to create a bitmap image { QCString dotArgs(maxCmdLine); QCString imgName = baseName+"."+imgExt; DotRunner dotRun(baseName+".dot"); dotRun.addJob(imgExt,imgName); if (generateImageMap) dotRun.addJob(MAP_CMD,baseName+".map"); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } checkDotResult(imgName); } else if (format==EPS) // run dot to create a .eps image { DotRunner dotRun(baseName+".dot"); dotRun.addJob("ps",baseName+".eps"); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } if (Config_getBool("USE_PDFLATEX")) { QCString epstopdfArgs(maxCmdLine); epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", baseName.data(),baseName.data()); if (portable_system("epstopdf",epstopdfArgs)!=0) { err("Error: Problems running epstopdf. Check your TeX installation!\n"); QDir::setCurrent(oldDir); return baseName; } } } if (Config_getBool("DOT_CLEANUP")) thisDir.remove(baseName+".dot"); } Doxygen::indexList.addImageFile(baseName+"."+imgExt); if (format==BITMAP && generateImageMap) // produce HTML to include the image { QCString mapLabel = escapeCharsInString(m_startNode->m_label,FALSE)+"_"+ escapeCharsInString(mapName,FALSE); out << "
\"";
" << endl; QString tmpstr; QTextOStream tmpout(&tmpstr); tmpout.setEncoding(tmpout.UnicodeUTF8); convertMapFile(tmpout,baseName+".map",relPath); if (!tmpstr.isEmpty()) { out << "" << endl; out << tmpstr; out << "" << endl; } //thisDir.remove(baseName+".map"); } else if (format==EPS) // produce tex to include the .eps image { int width=420,height=600; if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) { err("Error: Could not extract bounding box from .eps!\n"); QDir::setCurrent(oldDir); return baseName; } //printf("Got EPS size %d,%d\n",width,height); int maxWidth = 400; /* approx. page width in points, excl. margins */ int maxHeight = 400; /* approx. page height in points, excl. margins */ out << "\\nopagebreak\n" "\\begin{figure}[H]\n" "\\begin{center}\n" "\\leavevmode\n"; if (width>maxWidth) { out << "\\includegraphics[width=" << maxWidth << "pt]"; } else if (height>maxHeight) { out << "\\includegraphics[height=" << maxHeight << "pt]"; } else { out << "\\includegraphics[width=" << width << "pt]"; } out << "{" << baseName << "}\n" "\\end{center}\n" "\\end{figure}\n"; } QDir::setCurrent(oldDir); return baseName; } //-------------------------------------------------------------------- void DotClassGraph::writeXML(QTextStream &t) { QDictIterator dni(*m_usedNodes); DotNode *node; for (;(node=dni.current());++dni) { node->writeXML(t,TRUE); } } void DotClassGraph::writeDEF(QTextStream &t) { QDictIterator dni(*m_usedNodes); DotNode *node; for (;(node=dni.current());++dni) { node->writeDEF(t); } } //-------------------------------------------------------------------- int DotInclDepGraph::m_curNodeNumber = 0; void DotInclDepGraph::buildGraph(DotNode *n,FileDef *fd,int distance) { QList *includeFiles = m_inverse ? fd->includedByFileList() : fd->includeFileList(); if (includeFiles) { QListIterator ili(*includeFiles); IncludeInfo *ii; for (;(ii=ili.current());++ili) { FileDef *bfd = ii->fileDef; QCString in = ii->includeName; //printf(">>>> in=`%s' bfd=%p\n",ii->includeName.data(),bfd); bool doc=TRUE,src=FALSE; if (bfd) { in = bfd->absFilePath(); doc = bfd->isLinkable() && !bfd->isHidden(); src = bfd->generateSourceFile(); } if (doc || src || !Config_getBool("HIDE_UNDOC_RELATIONS")) { QCString url=""; if (bfd) url=bfd->getOutputFileBase().copy(); if (!doc && src) { url=bfd->getSourceFileBase(); } DotNode *bn = m_usedNodes->find(in); if (bn) // file is already a node in the graph { n->addChild(bn,0,0,0); bn->addParent(n); bn->setDistance(distance); } else { QCString tmp_url; QCString tooltip; if (bfd) { tmp_url=doc || src ? bfd->getReference()+"$"+url : QCString(); tooltip = bfd->briefDescriptionAsTooltip(); } bn = new DotNode( m_curNodeNumber++, // n ii->includeName, // label tooltip, // tip tmp_url, // url FALSE, // rootNode 0 // cd ); n->addChild(bn,0,0,0); bn->addParent(n); m_usedNodes->insert(in,bn); bn->setDistance(distance); if (bfd) buildGraph(bn,bfd,distance+1); } } } } } void DotInclDepGraph::determineVisibleNodes(QList &queue, int &maxNodes) { while (queue.count()>0 && maxNodes>0) { static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH"); DotNode *n = queue.take(0); if (!n->isVisible() && n->distance()markAsVisible(); maxNodes--; // add direct children if (n->m_children) { QListIterator li(*n->m_children); DotNode *dn; for (li.toFirst();(dn=li.current());++li) { queue.append(dn); } } } } } void DotInclDepGraph::determineTruncatedNodes(QList &queue) { while (queue.count()>0) { DotNode *n = queue.take(0); if (n->isVisible() && n->isTruncated()==DotNode::Unknown) { bool truncated = FALSE; if (n->m_children) { QListIterator li(*n->m_children); DotNode *dn; for (li.toFirst();(dn=li.current());++li) { if (!dn->isVisible()) truncated = TRUE; else queue.append(dn); } } n->markAsTruncated(truncated); } } } DotInclDepGraph::DotInclDepGraph(FileDef *fd,bool inverse) { m_maxDistance = 0; m_inverse = inverse; ASSERT(fd!=0); m_diskName = fd->getFileBase().copy(); QCString tmp_url=fd->getReference()+"$"+fd->getFileBase(); m_startNode = new DotNode(m_curNodeNumber++, fd->docName(), "", tmp_url.data(), TRUE // root node ); m_startNode->setDistance(0); m_usedNodes = new QDict(1009); m_usedNodes->insert(fd->absFilePath(),m_startNode); buildGraph(m_startNode,fd,1); static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES"); int maxNodes = nodes; //int directChildNodes = 1; //if (m_startNode->m_children!=0) // directChildNodes+=m_startNode->m_children->count(); //if (directChildNodes>maxNodes) maxNodes=directChildNodes; QList openNodeQueue; openNodeQueue.append(m_startNode); determineVisibleNodes(openNodeQueue,maxNodes); openNodeQueue.clear(); openNodeQueue.append(m_startNode); determineTruncatedNodes(openNodeQueue); } DotInclDepGraph::~DotInclDepGraph() { deleteNodes(m_startNode); delete m_usedNodes; } QCString DotInclDepGraph::diskName() const { QCString result=m_diskName.copy(); if (m_inverse) result+="_dep"; result+="_incl"; return convertNameToFile(result); } QCString DotInclDepGraph::writeGraph(QTextStream &out, GraphOutputFormat format, const char *path, const char *relPath, bool generateImageMap ) const { QDir d(path); // store the original directory if (!d.exists()) { err("Error: Output dir %s does not exist!\n",path); exit(1); } QCString oldDir = convertToQCString(QDir::currentDirPath()); // go to the html output directory (i.e. path) QDir::setCurrent(d.absPath()); QDir thisDir; QCString baseName=m_diskName; if (m_inverse) baseName+="_dep"; baseName+="_incl"; baseName=convertNameToFile(baseName); QCString mapName=escapeCharsInString(m_startNode->m_label,FALSE); if (m_inverse) mapName+="dep"; QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); if (updateDotGraph(m_startNode, DotNode::Dependency, baseName, format, FALSE, // lrRank FALSE, // renderParents m_inverse // backArrows ) ) { if (format==BITMAP) { // run dot to create a bitmap image QCString dotArgs(maxCmdLine); QCString imgName=baseName+"."+imgExt; DotRunner dotRun(baseName+".dot"); dotRun.addJob(imgExt,imgName); if (generateImageMap) dotRun.addJob(MAP_CMD,baseName+".map"); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } checkDotResult(imgName); } else if (format==EPS) { // run dot to create a .eps image DotRunner dotRun(baseName+".dot"); dotRun.addJob("ps",baseName+".eps"); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } if (Config_getBool("USE_PDFLATEX")) { QCString epstopdfArgs(maxCmdLine); epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", baseName.data(),baseName.data()); if (portable_system("epstopdf",epstopdfArgs)!=0) { err("Error: Problems running epstopdf. Check your TeX installation!\n"); QDir::setCurrent(oldDir); return baseName; } } } } Doxygen::indexList.addImageFile(baseName+"."+imgExt); if (format==BITMAP && generateImageMap) { out << "
\"\"/"; out << "
" << endl; QString tmpstr; QTextOStream tmpout(&tmpstr); tmpout.setEncoding(tmpout.UnicodeUTF8); convertMapFile(tmpout,baseName+".map",relPath); if (!tmpstr.isEmpty()) { out << "" << endl; out << tmpstr; out << "" << endl; } //thisDir.remove(baseName+".map"); } else if (format==EPS) { int width,height; if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) { err("Error: Could not extract bounding box from .eps!\n"); QDir::setCurrent(oldDir); return baseName; } int maxWidth = 420; /* approx. page width in points */ out << "\\nopagebreak\n" "\\begin{figure}[H]\n" "\\begin{center}\n" "\\leavevmode\n" "\\includegraphics[width=" << QMIN(width/2,maxWidth) << "pt]{" << baseName << "}\n" "\\end{center}\n" "\\end{figure}\n"; } if (Config_getBool("DOT_CLEANUP")) thisDir.remove(baseName+".dot"); QDir::setCurrent(oldDir); return baseName; } bool DotInclDepGraph::isTrivial() const { return m_startNode->m_children==0; } bool DotInclDepGraph::isTooBig() const { static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES"); int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0; return numNodes>=maxNodes; } void DotInclDepGraph::writeXML(QTextStream &t) { QDictIterator dni(*m_usedNodes); DotNode *node; for (;(node=dni.current());++dni) { node->writeXML(t,FALSE); } } //------------------------------------------------------------- int DotCallGraph::m_curNodeNumber = 0; void DotCallGraph::buildGraph(DotNode *n,MemberDef *md,int distance) { LockingPtr refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers(); if (!refs.isNull()) { MemberSDict::Iterator mri(*refs); MemberDef *rmd; for (;(rmd=mri.current());++mri) { if (rmd->isFunction() || rmd->isSlot() || rmd->isSignal()) { QCString uniqueId; uniqueId=rmd->getReference()+"$"+ rmd->getOutputFileBase()+"#"+rmd->anchor(); DotNode *bn = m_usedNodes->find(uniqueId); if (bn) // file is already a node in the graph { n->addChild(bn,0,0,0); bn->addParent(n); bn->setDistance(distance); } else { QCString name; if (Config_getBool("HIDE_SCOPE_NAMES")) { name = rmd->getOuterScope()==m_scope ? rmd->name() : rmd->qualifiedName(); } else { name = rmd->qualifiedName(); } QCString tooltip = rmd->briefDescriptionAsTooltip(); bn = new DotNode( m_curNodeNumber++, linkToText(name,FALSE), tooltip, uniqueId, 0 //distance ); n->addChild(bn,0,0,0); bn->addParent(n); bn->setDistance(distance); m_usedNodes->insert(uniqueId,bn); buildGraph(bn,rmd,distance+1); } } } } } void DotCallGraph::determineVisibleNodes(QList &queue, int &maxNodes) { while (queue.count()>0 && maxNodes>0) { static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH"); DotNode *n = queue.take(0); if (!n->isVisible() && n->distance()markAsVisible(); maxNodes--; // add direct children if (n->m_children) { QListIterator li(*n->m_children); DotNode *dn; for (li.toFirst();(dn=li.current());++li) { queue.append(dn); } } } } } void DotCallGraph::determineTruncatedNodes(QList &queue) { while (queue.count()>0) { DotNode *n = queue.take(0); if (n->isVisible() && n->isTruncated()==DotNode::Unknown) { bool truncated = FALSE; if (n->m_children) { QListIterator li(*n->m_children); DotNode *dn; for (li.toFirst();(dn=li.current());++li) { if (!dn->isVisible()) truncated = TRUE; else queue.append(dn); } } n->markAsTruncated(truncated); } } } DotCallGraph::DotCallGraph(MemberDef *md,bool inverse) { m_maxDistance = 0; m_inverse = inverse; m_diskName = md->getOutputFileBase()+"_"+md->anchor(); m_scope = md->getOuterScope(); QCString uniqueId; uniqueId = md->getReference()+"$"+ md->getOutputFileBase()+"#"+md->anchor(); QCString name; if (Config_getBool("HIDE_SCOPE_NAMES")) { name = md->name(); } else { name = md->qualifiedName(); } m_startNode = new DotNode(m_curNodeNumber++, linkToText(name,FALSE), "", uniqueId.data(), TRUE // root node ); m_startNode->setDistance(0); m_usedNodes = new QDict(1009); m_usedNodes->insert(uniqueId,m_startNode); buildGraph(m_startNode,md,1); static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES"); int maxNodes = nodes; //int directChildNodes = 1; //if (m_startNode->m_children!=0) // directChildNodes+=m_startNode->m_children->count(); //if (directChildNodes>maxNodes) maxNodes=directChildNodes; QList openNodeQueue; openNodeQueue.append(m_startNode); determineVisibleNodes(openNodeQueue,maxNodes); openNodeQueue.clear(); openNodeQueue.append(m_startNode); determineTruncatedNodes(openNodeQueue); } DotCallGraph::~DotCallGraph() { deleteNodes(m_startNode); delete m_usedNodes; } QCString DotCallGraph::writeGraph(QTextStream &out, GraphOutputFormat format, const char *path,const char *relPath,bool generateImageMap) const { QDir d(path); // store the original directory if (!d.exists()) { err("Error: Output dir %s does not exist!\n",path); exit(1); } QCString oldDir = convertToQCString(QDir::currentDirPath()); // go to the html output directory (i.e. path) QDir::setCurrent(d.absPath()); QDir thisDir; QCString baseName = m_diskName + (m_inverse ? "_icgraph" : "_cgraph"); QCString mapName=baseName; QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); if (updateDotGraph(m_startNode, DotNode::CallGraph, baseName, format, TRUE, // lrRank FALSE, // renderParents m_inverse // backArrows ) ) { if (format==BITMAP) { // run dot to create a bitmap image QCString dotArgs(maxCmdLine); QCString imgName=baseName+"."+imgExt; DotRunner dotRun(baseName+".dot"); dotRun.addJob(imgExt,imgName); if (generateImageMap) dotRun.addJob(MAP_CMD,baseName+".map"); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } checkDotResult(imgName); } else if (format==EPS) { // run dot to create a .eps image DotRunner dotRun(baseName+".dot"); dotRun.addJob("ps",baseName+".eps"); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } if (Config_getBool("USE_PDFLATEX")) { QCString epstopdfArgs(maxCmdLine); epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", baseName.data(),baseName.data()); if (portable_system("epstopdf",epstopdfArgs)!=0) { err("Error: Problems running epstopdf. Check your TeX installation!\n"); QDir::setCurrent(oldDir); return baseName; } } } } Doxygen::indexList.addImageFile(baseName+"."+imgExt); if (format==BITMAP && generateImageMap) { out << "
\"";"; out << "
" << endl; QString tmpstr; QTextOStream tmpout(&tmpstr); tmpout.setEncoding(tmpout.UnicodeUTF8); convertMapFile(tmpout,baseName+".map",relPath); if (!tmpstr.isEmpty()) { out << "" << endl; out << tmpstr; out << "" << endl; } //thisDir.remove(baseName+".map"); } else if (format==EPS) { int width,height; if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) { err("Error: Could not extract bounding box from .eps!\n"); QDir::setCurrent(oldDir); return baseName; } int maxWidth = 420; /* approx. page width in points */ out << "\\nopagebreak\n" "\\begin{figure}[H]\n" "\\begin{center}\n" "\\leavevmode\n" "\\includegraphics[width=" << QMIN(width/2,maxWidth) << "pt]{" << baseName << "}\n" "\\end{center}\n" "\\end{figure}\n"; } if (Config_getBool("DOT_CLEANUP")) thisDir.remove(baseName+".dot"); QDir::setCurrent(oldDir); return baseName; } bool DotCallGraph::isTrivial() const { return m_startNode->m_children==0; } bool DotCallGraph::isTooBig() const { static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES"); int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0; return numNodes>=maxNodes; } //------------------------------------------------------------- DotDirDeps::DotDirDeps(DirDef *dir) : m_dir(dir) { } DotDirDeps::~DotDirDeps() { } QCString DotDirDeps::writeGraph(QTextStream &out, GraphOutputFormat format, const char *path, const char *relPath, bool generateImageMap) const { QDir d(path); // store the original directory if (!d.exists()) { err("Error: Output dir %s does not exist!\n",path); exit(1); } QCString oldDir = convertToQCString(QDir::currentDirPath()); // go to the html output directory (i.e. path) QDir::setCurrent(d.absPath()); QDir thisDir; QCString baseName=m_dir->getOutputFileBase()+"_dep"; QCString mapName=escapeCharsInString(baseName,FALSE); QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); // todo: create check, update md5 checksum { QFile f(baseName+".dot"); if (!f.open(IO_WriteOnly)) { err("Cannot create file %s.dot for writing!\n",baseName.data()); } QTextStream t(&f); t.setEncoding(t.UnicodeUTF8); m_dir->writeDepGraph(t); f.close(); if (format==BITMAP) { // run dot to create a bitmap image QCString dotArgs(maxCmdLine); QCString imgName=baseName+"."+imgExt; DotRunner dotRun(baseName+".dot"); dotRun.addJob(imgExt,imgName); if (generateImageMap) dotRun.addJob(MAP_CMD,baseName+".map"); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } checkDotResult(imgName); } else if (format==EPS) { // run dot to create a .eps image DotRunner dotRun(baseName+".dot"); dotRun.addJob("ps",baseName+".eps"); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } if (Config_getBool("USE_PDFLATEX")) { QCString epstopdfArgs(maxCmdLine); epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", baseName.data(),baseName.data()); if (portable_system("epstopdf",epstopdfArgs)!=0) { err("Error: Problems running epstopdf. Check your TeX installation!\n"); QDir::setCurrent(oldDir); return baseName; } } } } Doxygen::indexList.addImageFile(baseName+"."+imgExt); if (format==BITMAP && generateImageMap) { out << "
\"";displayName()); out << "\"/>"; out << "
" << endl; QString tmpstr; QTextOStream tmpout(&tmpstr); tmpout.setEncoding(tmpout.UnicodeUTF8); convertMapFile(tmpout,baseName+".map",relPath,TRUE); if (!tmpstr.isEmpty()) { out << "" << endl; out << tmpstr; out << "" << endl; } else { //printf("Map is empty!\n"); } //thisDir.remove(baseName+".map"); } else if (format==EPS) { int width,height; if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) { err("Error: Could not extract bounding box from .eps!\n"); QDir::setCurrent(oldDir); return baseName; } int maxWidth = 420; /* approx. page width in points */ out << "\\nopagebreak\n" "\\begin{figure}[H]\n" "\\begin{center}\n" "\\leavevmode\n" "\\includegraphics[width=" << QMIN(width/2,maxWidth) << "pt]{" << baseName << "}\n" "\\end{center}\n" "\\end{figure}\n"; } if (Config_getBool("DOT_CLEANUP")) thisDir.remove(baseName+".dot"); QDir::setCurrent(oldDir); return baseName; } bool DotDirDeps::isTrivial() const { return m_dir->depGraphIsTrivial(); } //------------------------------------------------------------- void generateGraphLegend(const char *path) { QFile dotFile((QCString)path+"/graph_legend.dot"); if (!dotFile.open(IO_WriteOnly)) { err("Could not open file %s for writing\n", convertToQCString(dotFile.name()).data()); return; } QTextStream dotText(&dotFile); writeGraphHeader(dotText); dotText << " Node9 [shape=\"box\",label=\"Inherited\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",fillcolor=\"grey75\",style=\"filled\" fontcolor=\"black\"];\n"; dotText << " Node10 -> Node9 [dir=back,color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; dotText << " Node10 [shape=\"box\",label=\"PublicBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPublicBase" << Doxygen::htmlFileExtension << "\"];\n"; dotText << " Node11 -> Node10 [dir=back,color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; dotText << " Node11 [shape=\"box\",label=\"Truncated\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"red\",URL=\"$classTruncated" << Doxygen::htmlFileExtension << "\"];\n"; dotText << " Node13 -> Node9 [dir=back,color=\"darkgreen\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; dotText << " Node13 [shape=\"box\",label=\"ProtectedBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classProtectedBase" << Doxygen::htmlFileExtension << "\"];\n"; dotText << " Node14 -> Node9 [dir=back,color=\"firebrick4\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; dotText << " Node14 [shape=\"box\",label=\"PrivateBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPrivateBase" << Doxygen::htmlFileExtension << "\"];\n"; dotText << " Node15 -> Node9 [dir=back,color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; dotText << " Node15 [shape=\"box\",label=\"Undocumented\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"grey75\"];\n"; dotText << " Node16 -> Node9 [dir=back,color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; dotText << " Node16 [shape=\"box\",label=\"Templ< int >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n"; dotText << " Node17 -> Node16 [dir=back,color=\"orange\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"< int >\",fontname=\"" << FONTNAME << "\"];\n"; dotText << " Node17 [shape=\"box\",label=\"Templ< T >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n"; dotText << " Node18 -> Node9 [dir=back,color=\"darkorchid3\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"m_usedClass\",fontname=\"" << FONTNAME << "\"];\n"; dotText << " Node18 [shape=\"box\",label=\"Used\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classUsed" << Doxygen::htmlFileExtension << "\"];\n"; writeGraphFooter(dotText); dotFile.close(); QDir d(path); // store the original directory if (!d.exists()) { err("Error: Output dir %s does not exist!\n",path); exit(1); } QCString oldDir = convertToQCString(QDir::currentDirPath()); // go to the html output directory (i.e. path) QDir::setCurrent(d.absPath()); // run dot to generate the a bitmap image from the graph QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); QCString imgName = "graph_legend."+imgExt; DotRunner dotRun("graph_legend.dot"); dotRun.addJob(imgExt,imgName); if (!dotRun.run()) { QDir::setCurrent(oldDir); return; } checkDotResult(imgName); Doxygen::indexList.addImageFile(imgName); QDir::setCurrent(oldDir); } void writeDotGraphFromFile(const char *inFile,const char *outDir, const char *outFile,GraphOutputFormat format) { QCString absOutFile = outDir; absOutFile+=portable_pathSeparator(); absOutFile+=outFile; // chdir to the output dir, so dot can find the font file. QCString oldDir = convertToQCString(QDir::currentDirPath()); // go to the html output directory (i.e. path) QDir::setCurrent(outDir); //printf("Going to dir %s\n",QDir::currentDirPath().data()); QCString env = portable_getenv("DOTFONTPATH"); if (env==".") // this path was set by doxygen, so dot can find the FreeSans.ttf font, // for user defined graphs we use the default search path built into dot, // unless the user has set the DOTFONTPATH as well. { // temporarily remove the DOTFONTPATH environment variable // so dot will use the built-in search path. portable_unsetenv("DOTFONTPATH"); } QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); QCString imgName = (QCString)outFile+"."+imgExt; DotRunner dotRun(inFile); if (format==BITMAP) dotRun.addJob(imgExt,imgName); else // format==EPS dotRun.addJob("ps",QCString(outFile)+".eps"); if (!dotRun.run()) { QDir::setCurrent(oldDir); goto error; } // Added by Nils Strom if ( (format==EPS) && (Config_getBool("USE_PDFLATEX")) ) { QCString epstopdfArgs(maxCmdLine); epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", outFile,outFile); if (portable_system("epstopdf",epstopdfArgs)!=0) { err("Error: Problems running epstopdf. Check your TeX installation!\n"); } } if (format==BITMAP) checkDotResult(imgName); Doxygen::indexList.addImageFile(imgName); if (env==".") { // restore the DOTFONTPATH variable again portable_setenv("DOTFONTPATH",env); } error: QDir::setCurrent(oldDir); } /*! Marco Dalla Gasperina [marcodg@attbi.com] added this to allow * dotfiles to generate image maps. * \param inFile just the basename part of the filename * \param outDir output directory * \param relPath relative path the to root of the output dir * \param context the scope in which this graph is found (for resolving links) * \returns a string which is the HTML image map (without the \\) */ QString getDotImageMapFromFile(const QString& inFile, const QString& outDir, const QCString &relPath,const QString &context) { QString outFile = inFile + ".map"; // chdir to the output dir, so dot can find the font file. QCString oldDir = convertToQCString(QDir::currentDirPath()); // go to the html output directory (i.e. path) QDir::setCurrent(outDir); //printf("Going to dir %s\n",QDir::currentDirPath().data()); DotRunner dotRun(inFile); dotRun.addJob(MAP_CMD,outFile); if (!dotRun.run()) { QDir::setCurrent(oldDir); return ""; } QString result; QTextOStream tmpout(&result); tmpout.setEncoding(tmpout.UnicodeUTF8); convertMapFile(tmpout, outFile, relPath ,TRUE, context); QDir().remove(outFile); // printf("result=%s\n",result.data()); QDir::setCurrent(oldDir); return result; } // end MDG mods //------------------------------------------------------------- DotGroupCollaboration::DotGroupCollaboration(GroupDef* gd) { m_curNodeId = 0; QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase(); m_usedNodes = new QDict(1009); m_rootNode = new DotNode(m_curNodeId++, gd->groupTitle(), "", tmp_url, TRUE ); m_rootNode->markAsVisible(); m_usedNodes->insert(gd->name(), m_rootNode ); m_edges.setAutoDelete(TRUE); m_diskName = gd->getOutputFileBase(); buildGraph( gd ); } DotGroupCollaboration::~DotGroupCollaboration() { delete m_usedNodes; } void DotGroupCollaboration::buildGraph(GroupDef* gd) { QCString tmp_url; //=========================== // hierarchy. // Write parents LockingPtr groups = gd->partOfGroups(); if ( groups!=0 ) { GroupListIterator gli(*groups); GroupDef *d; for (gli.toFirst();(d=gli.current());++gli) { DotNode* nnode = m_usedNodes->find(d->name()); if ( !nnode ) { // add node tmp_url = d->getReference()+"$"+d->getOutputFileBase(); QCString tooltip = d->briefDescriptionAsTooltip(); nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_url ); nnode->markAsVisible(); m_usedNodes->insert(d->name(), nnode ); } tmp_url = ""; addEdge( nnode, m_rootNode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url ); } } // Add subgroups if ( gd->getSubGroups() && gd->getSubGroups()->count() ) { QListIterator defli(*gd->getSubGroups()); GroupDef *def; for (;(def=defli.current());++defli) { DotNode* nnode = m_usedNodes->find(def->name()); if ( !nnode ) { // add node tmp_url = def->getReference()+"$"+def->getOutputFileBase(); QCString tooltip = def->briefDescriptionAsTooltip(); nnode = new DotNode(m_curNodeId++, def->groupTitle(), tooltip, tmp_url ); nnode->markAsVisible(); m_usedNodes->insert(def->name(), nnode ); } tmp_url = ""; addEdge( m_rootNode, nnode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url ); } } //======================= // Write collaboration // Add members addMemberList( gd->getMemberList(MemberList::allMembersList) ); // Add classes if ( gd->getClasses() && gd->getClasses()->count() ) { ClassSDict::Iterator defli(*gd->getClasses()); ClassDef *def; for (;(def=defli.current());++defli) { tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; addCollaborationMember( def, tmp_url, DotGroupCollaboration::tclass ); } } // Add namespaces if ( gd->getNamespaces() && gd->getNamespaces()->count() ) { NamespaceSDict::Iterator defli(*gd->getNamespaces()); NamespaceDef *def; for (;(def=defli.current());++defli) { tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; addCollaborationMember( def, tmp_url, DotGroupCollaboration::tnamespace ); } } // Add files if ( gd->getFiles() && gd->getFiles()->count() ) { QListIterator defli(*gd->getFiles()); FileDef *def; for (;(def=defli.current());++defli) { tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; addCollaborationMember( def, tmp_url, DotGroupCollaboration::tfile ); } } // Add pages if ( gd->getPages() && gd->getPages()->count() ) { PageSDict::Iterator defli(*gd->getPages()); PageDef *def; for (;(def=defli.current());++defli) { tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; addCollaborationMember( def, tmp_url, DotGroupCollaboration::tpages ); } } // Add directories if ( gd->getDirs() && gd->getDirs()->count() ) { QListIterator defli(*gd->getDirs()); DirDef *def; for (;(def=defli.current());++defli) { tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; addCollaborationMember( def, tmp_url, DotGroupCollaboration::tdir ); } } } void DotGroupCollaboration::addMemberList( MemberList* ml ) { if ( !( ml && ml->count()) ) return; MemberListIterator defli(*ml); MemberDef *def; for (;(def=defli.current());++defli) { QCString tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension +"#"+def->anchor(); addCollaborationMember( def, tmp_url, DotGroupCollaboration::tmember ); } } DotGroupCollaboration::Edge* DotGroupCollaboration::addEdge( DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType, const QCString& _label, const QCString& _url ) { // search a existing link. QListIterator lli(m_edges); Edge* newEdge = 0; for ( lli.toFirst(); (newEdge=lli.current()); ++lli) { if ( newEdge->pNStart==_pNStart && newEdge->pNEnd==_pNEnd && newEdge->eType==_eType ) { // edge already found break; } } if ( newEdge==0 ) // new link { newEdge = new Edge(_pNStart,_pNEnd,_eType); m_edges.append( newEdge ); } if (!_label.isEmpty()) { newEdge->links.append(new Link(_label,_url)); } return newEdge; } void DotGroupCollaboration::addCollaborationMember( Definition* def, QCString& url, EdgeType eType ) { // Create group nodes if ( !def->partOfGroups() ) return; GroupListIterator gli(*def->partOfGroups()); GroupDef *d; QCString tmp_str; for (;(d=gli.current());++gli) { DotNode* nnode = m_usedNodes->find(d->name()); if ( nnode != m_rootNode ) { if ( nnode==0 ) { // add node tmp_str = d->getReference()+"$"+d->getOutputFileBase(); QCString tooltip = d->briefDescriptionAsTooltip(); nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_str ); nnode->markAsVisible(); m_usedNodes->insert(d->name(), nnode ); } tmp_str = def->qualifiedName(); addEdge( m_rootNode, nnode, eType, tmp_str, url ); } } } QCString DotGroupCollaboration::writeGraph( QTextStream &t, GraphOutputFormat format, const char *path, const char *relPath, bool writeImageMap) const { QDir d(path); // store the original directory if (!d.exists()) { err("Error: Output dir %s does not exist!\n",path); exit(1); } QCString oldDir = convertToQCString(QDir::currentDirPath()); // go to the output directory (i.e. path) QDir::setCurrent(d.absPath()); QDir thisDir; QCString baseName = m_diskName; QFile dotfile(baseName+".dot"); if (dotfile.open(IO_WriteOnly)) { QTextStream tdot(&dotfile); tdot.setEncoding(tdot.UnicodeUTF8); writeGraphHeader(tdot); // clean write flags QDictIterator dni(*m_usedNodes); DotNode *pn; for (dni.toFirst();(pn=dni.current());++dni) pn->clearWriteFlag(); // write other nodes. for (dni.toFirst();(pn=dni.current());++dni) { pn->write(tdot,DotNode::Inheritance,format,TRUE,FALSE,FALSE,FALSE); } // write edges QListIterator eli(m_edges); Edge* edge; for (eli.toFirst();(edge=eli.current());++eli) { edge->write( tdot ); } writeGraphFooter(tdot); dotfile.close(); } QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); if (format==BITMAP) // run dot to create a bitmap image { QCString dotArgs(maxCmdLine); QCString imgName = baseName+"."+imgExt; QCString mapName=baseName+".map"; DotRunner dotRun(baseName+".dot"); dotRun.addJob(imgExt,imgName); if (writeImageMap) dotRun.addJob(MAP_CMD,mapName); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } if (writeImageMap) { QCString mapLabel = escapeCharsInString(baseName,FALSE); t << "
\"\"" << endl; t << "" << endl; convertMapFile(t,mapName,relPath); t << "
" << endl; thisDir.remove(mapName); } } else if (format==EPS) { DotRunner dotRun(baseName+".dot"); dotRun.addJob("ps",baseName+".eps"); if (!dotRun.run()) { QDir::setCurrent(oldDir); return baseName; } if (Config_getBool("USE_PDFLATEX")) { QCString epstopdfArgs(maxCmdLine); epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", baseName.data(),baseName.data()); if (portable_system("epstopdf",epstopdfArgs)!=0) { err("Error: Problems running epstopdf. Check your TeX installation!\n"); QDir::setCurrent(oldDir); return baseName; } } int width,height; if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) { err("Error: Could not extract bounding box from .eps!\n"); QDir::setCurrent(oldDir); return baseName; } int maxWidth = 420; /* approx. page width in points */ t << "\\nopagebreak\n" "\\begin{figure}[H]\n" "\\begin{center}\n" "\\leavevmode\n" "\\includegraphics[width=" << QMIN(width/2,maxWidth) << "pt]{" << baseName << "}\n" "\\end{center}\n" "\\end{figure}\n"; } if (Config_getBool("DOT_CLEANUP")) { thisDir.remove(baseName+".dot"); } QDir::setCurrent(oldDir); return baseName; } void DotGroupCollaboration::Edge::write( QTextStream &t ) const { const char* linkTypeColor[] = { "darkorchid3" ,"orange" ,"blueviolet" ,"darkgreen" ,"firebrick4" ,"grey75" ,"midnightblue" }; QCString arrowStyle = "dir=\"none\", style=\"dashed\""; t << " Node" << pNStart->number(); t << "->"; t << "Node" << pNEnd->number(); t << " [shape=plaintext"; if (links.count()>0) // there are links { t << ", "; // HTML-like edge labels crash on my Mac with Graphviz 2.0! and // are not supported by older version of dot. // //t << label=<"; //QListIterator lli(links); //Link *link; //for( lli.toFirst(); (link=lli.current()); ++lli) //{ // t << "url.isEmpty() ) // t << " HREF=\"" << link->url << "\""; // t << ">" << link->label << ""; //} //t << "
>"; t << "label=\""; QListIterator lli(links); Link *link; bool first=TRUE; int count=0; const int maxLabels = 10; for( lli.toFirst(); (link=lli.current()) && countlabel); } if (count==maxLabels) t << "\\n..."; t << "\""; } switch( eType ) { case thierarchy : arrowStyle = "dir=\"back\", style=\"solid\""; default : t << ", color=\"" << linkTypeColor[(int)eType] << "\""; break; } t << ", " << arrowStyle; t << "];" << endl; } bool DotGroupCollaboration::isTrivial() const { return m_usedNodes->count() <= 1; } void DotGroupCollaboration::writeGraphHeader(QTextStream &t) const { t << "digraph structs" << endl; t << "{" << endl; if (Config_getBool("DOT_TRANSPARENT")) { t << " bgcolor=\"transparent\";" << endl; } t << " edge [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\"," "labelfontname=\"" << FONTNAME << "\",labelfontsize=\"" << FONTSIZE << "\"];\n"; t << " node [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\",shape=record];\n"; t << " rankdir=LR;\n"; } void writeDotDirDepGraph(QTextStream &t,DirDef *dd) { t << "digraph G {\n"; if (Config_getBool("DOT_TRANSPARENT")) { t << " bgcolor=transparent;\n"; } t << " compound=true\n"; t << " node [ fontsize=\"" << FONTSIZE << "\", fontname=\"" << FONTNAME << "\"];\n"; t << " edge [ labelfontsize=\"" << FONTSIZE << "\", labelfontname=\"" << FONTNAME << "\"];\n"; QDict dirsInGraph(257); dirsInGraph.insert(dd->getOutputFileBase(),dd); if (dd->parent()) { t << " subgraph cluster" << dd->parent()->getOutputFileBase() << " {\n"; t << " graph [ bgcolor=\"#ddddee\", pencolor=\"black\", label=\"" << dd->parent()->shortName() << "\" fontname=\"" << FONTNAME << "\", fontsize=\"" << FONTSIZE << "\", URL=\""; t << dd->parent()->getOutputFileBase() << Doxygen::htmlFileExtension; t << "\"]\n"; } if (dd->isCluster()) { t << " subgraph cluster" << dd->getOutputFileBase() << " {\n"; t << " graph [ bgcolor=\"#eeeeff\", pencolor=\"black\", label=\"\"" << " URL=\"" << dd->getOutputFileBase() << Doxygen::htmlFileExtension << "\"];\n"; t << " " << dd->getOutputFileBase() << " [shape=plaintext label=\"" << dd->shortName() << "\"];\n"; // add nodes for sub directories QListIterator sdi(dd->subDirs()); DirDef *sdir; for (sdi.toFirst();(sdir=sdi.current());++sdi) { t << " " << sdir->getOutputFileBase() << " [shape=box label=\"" << sdir->shortName() << "\""; if (sdir->isCluster()) { t << " color=\"red\""; } else { t << " color=\"black\""; } t << " fillcolor=\"white\" style=\"filled\""; t << " URL=\"" << sdir->getOutputFileBase() << Doxygen::htmlFileExtension << "\""; t << "];\n"; dirsInGraph.insert(sdir->getOutputFileBase(),sdir); } t << " }\n"; } else { t << " " << dd->getOutputFileBase() << " [shape=box, label=\"" << dd->shortName() << "\", style=\"filled\", fillcolor=\"#eeeeff\"," << " pencolor=\"black\", URL=\"" << dd->getOutputFileBase() << Doxygen::htmlFileExtension << "\"];\n"; } if (dd->parent()) { t << " }\n"; } // add nodes for other used directories QDictIterator udi(*dd->usedDirs()); UsedDir *udir; //printf("*** For dir %s\n",shortName().data()); for (udi.toFirst();(udir=udi.current());++udi) // for each used dir (=directly used or a parent of a directly used dir) { const DirDef *usedDir=udir->dir(); DirDef *dir=dd; while (dir) { //printf("*** check relation %s->%s same_parent=%d !%s->isParentOf(%s)=%d\n", // dir->shortName().data(),usedDir->shortName().data(), // dir->parent()==usedDir->parent(), // usedDir->shortName().data(), // shortName().data(), // !usedDir->isParentOf(this) // ); if (dir!=usedDir && dir->parent()==usedDir->parent() && !usedDir->isParentOf(dd)) // include if both have the same parent (or no parent) { t << " " << usedDir->getOutputFileBase() << " [shape=box label=\"" << usedDir->shortName() << "\""; if (usedDir->isCluster()) { if (!Config_getBool("DOT_TRANSPARENT")) { t << " fillcolor=\"white\" style=\"filled\""; } t << " color=\"red\""; } t << " URL=\"" << usedDir->getOutputFileBase() << Doxygen::htmlFileExtension << "\"];\n"; dirsInGraph.insert(usedDir->getOutputFileBase(),usedDir); break; } dir=dir->parent(); } } // add relations between all selected directories DirDef *dir; QDictIterator di(dirsInGraph); for (di.toFirst();(dir=di.current());++di) // foreach dir in the graph { QDictIterator udi(*dir->usedDirs()); UsedDir *udir; for (udi.toFirst();(udir=udi.current());++udi) // foreach used dir { const DirDef *usedDir=udir->dir(); if ((dir!=dd || !udir->inherited()) && // only show direct dependendies for this dir (usedDir!=dd || !udir->inherited()) && // only show direct dependendies for this dir !usedDir->isParentOf(dir) && // don't point to own parent dirsInGraph.find(usedDir->getOutputFileBase())) // only point to nodes that are in the graph { QCString relationName; relationName.sprintf("dir_%06d_%06d",dir->dirCount(),usedDir->dirCount()); if (Doxygen::dirRelations.find(relationName)==0) { // new relation Doxygen::dirRelations.append(relationName, new DirRelation(relationName,dir,udir)); } int nrefs = udir->filePairs().count(); t << " " << dir->getOutputFileBase() << "->" << usedDir->getOutputFileBase(); t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5"; t << " headhref=\"" << relationName << Doxygen::htmlFileExtension << "\"];\n"; } } } t << "}\n"; }