00001 /***************************************************************************** 00002 * 00003 * $Id: dot.cpp,v 1.20 2001/03/19 19:27:40 root Exp $ 00004 * 00005 * 00006 * Copyright (C) 1997-2008 by Dimitri van Heesch. 00007 * 00008 * Permission to use, copy, modify, and distribute this software and its 00009 * documentation under the terms of the GNU General Public License is hereby 00010 * granted. No representations are made about the suitability of this software 00011 * for any purpose. It is provided "as is" without express or implied warranty. 00012 * See the GNU General Public License for more details. 00013 * 00014 * Documents produced by Doxygen are derivative works derived from the 00015 * input used in their production; they are not affected by this license. 00016 * 00017 */ 00018 00019 #ifdef _WIN32 00020 #include <windows.h> 00021 #define BITMAP W_BITMAP 00022 #endif 00023 00024 #include <stdlib.h> 00025 00026 #include "dot.h" 00027 #include "doxygen.h" 00028 #include "message.h" 00029 #include "util.h" 00030 #include "config.h" 00031 #include "language.h" 00032 #include "defargs.h" 00033 #include "docparser.h" 00034 #include "debug.h" 00035 #include "pagedef.h" 00036 #include "portable.h" 00037 #include "dirdef.h" 00038 00039 #include <qdir.h> 00040 #include <qfile.h> 00041 #include <qtextstream.h> 00042 #include <md5.h> 00043 00044 #define MAP_CMD "cmap" 00045 00046 #define FONTNAME "FreeSans" 00047 00048 //-------------------------------------------------------------------- 00049 00050 static const int maxCmdLine = 40960; 00051 00053 static const char *edgeColorMap[] = 00054 { 00055 "midnightblue", // Public 00056 "darkgreen", // Protected 00057 "firebrick4", // Private 00058 "darkorchid3", // "use" relation 00059 "grey75", // Undocumented 00060 "orange" // template relation 00061 }; 00062 00063 static const char *arrowStyle[] = 00064 { 00065 "empty", // Public 00066 "empty", // Protected 00067 "empty", // Private 00068 "open", // "use" relation 00069 0, // Undocumented 00070 0 // template relation 00071 }; 00072 00073 static const char *edgeStyleMap[] = 00074 { 00075 "solid", // inheritance 00076 "dashed" // usage 00077 }; 00078 00079 static void writeGraphHeader(QTextStream &t) 00080 { 00081 t << "digraph G" << endl; 00082 t << "{" << endl; 00083 if (Config_getBool("DOT_TRANSPARENT")) 00084 { 00085 t << " bgcolor=\"transparent\";" << endl; 00086 } 00087 t << " edge [fontname=\"" << FONTNAME << "\",fontsize=10," 00088 "labelfontname=\"" << FONTNAME << "\",labelfontsize=10];\n"; 00089 t << " node [fontname=\"" << FONTNAME << "\",fontsize=10,shape=record];\n"; 00090 } 00091 00092 static void writeGraphFooter(QTextStream &t) 00093 { 00094 t << "}" << endl; 00095 } 00096 00108 static bool convertMapFile(QTextStream &t,const char *mapName, 00109 const QCString relPath, bool urlOnly=FALSE, 00110 const QString &context=QString()) 00111 { 00112 QFile f(mapName); 00113 if (!f.open(IO_ReadOnly)) 00114 { 00115 err("Error opening map file %s for inclusion in the docs!\n",mapName); 00116 return FALSE; 00117 } 00118 const int maxLineLen=10240; 00119 while (!f.atEnd()) // foreach line 00120 { 00121 QCString buf(maxLineLen); 00122 int numBytes = f.readLine(buf.data(),maxLineLen); 00123 buf[numBytes-1]='\0'; 00124 00125 // search for href="...", store ... part in link 00126 int indexS = buf.find("href=\""), indexE; 00127 if (indexS!=-1 && (indexE=buf.find('"',indexS+6))!=-1) 00128 { 00129 QCString link = buf.mid(indexS+6,indexE-indexS-6); 00130 QCString result; 00131 QCString *dest; 00132 if (urlOnly) // for user defined dot graphs 00133 { 00134 if (link.left(5)=="\\ref ") // \ref url 00135 { 00136 result="href=\""; 00137 // fake ref node to resolve the url 00138 DocRef *df = new DocRef( (DocNode*) 0, link.mid(5), context ); 00139 if (!df->ref().isEmpty()) 00140 { 00141 if ((dest=Doxygen::tagDestinationDict[df->ref()])) 00142 result += *dest + "/"; 00143 } 00144 else if (!relPath.isEmpty()) 00145 { 00146 result += relPath; 00147 } 00148 if (!df->file().isEmpty()) 00149 result += df->file().data() + Doxygen::htmlFileExtension; 00150 if (!df->anchor().isEmpty()) 00151 result += "#" + df->anchor(); 00152 delete df; 00153 result += "\""; 00154 } 00155 else 00156 { 00157 result = "href=\"" + link + "\""; 00158 } 00159 } 00160 else // ref$url (external ref via tag file), or $url (local ref) 00161 { 00162 int marker = link.find('$'); 00163 if (marker!=-1) 00164 { 00165 QCString ref = link.left(marker); 00166 QCString url = link.mid(marker+1); 00167 if (!ref.isEmpty()) 00168 { 00169 result = "doxygen=\"" + ref + ":"; 00170 if ((dest=Doxygen::tagDestinationDict[ref])) result += *dest + "/"; 00171 result += "\" "; 00172 } 00173 result+= "href=\""; 00174 if (!ref.isEmpty()) 00175 { 00176 if ((dest=Doxygen::tagDestinationDict[ref])) result += *dest + "/"; 00177 } 00178 else if (!relPath.isEmpty()) 00179 { 00180 result += relPath; 00181 } 00182 result+= url + "\""; 00183 } 00184 else // should not happen, but handle properly anyway 00185 { 00186 result = "href=\"" + link + "\""; 00187 } 00188 } 00189 QCString leftPart = buf.left(indexS); 00190 QCString rightPart = buf.mid(indexE+1); 00191 buf = leftPart + result + rightPart; 00192 } 00193 t << buf; 00194 } 00195 return TRUE; 00196 } 00197 00198 static QArray<int> s_newNumber; 00199 static int s_max_newNumber=0; 00200 00201 inline int reNumberNode(int number, bool doReNumbering) 00202 { 00203 if (!doReNumbering) 00204 { 00205 return number; 00206 } 00207 else 00208 { 00209 int s = s_newNumber.size(); 00210 if (number>=s) 00211 { 00212 int ns=0; 00213 ns = s * 3 / 2 + 5; // new size 00214 if (number>=ns) // number still doesn't fit 00215 { 00216 ns = number * 3 / 2 + 5; 00217 } 00218 s_newNumber.resize(ns); 00219 for (int i=s;i<ns;i++) // clear new part of the array 00220 { 00221 s_newNumber.at(i)=0; 00222 } 00223 } 00224 int i = s_newNumber.at(number); 00225 if (i == 0) // not yet mapped 00226 { 00227 i = ++s_max_newNumber; // start from 1 00228 s_newNumber.at(number) = i; 00229 } 00230 return i; 00231 } 00232 } 00233 00234 static void resetReNumbering() 00235 { 00236 s_max_newNumber=0; 00237 s_newNumber.resize(s_max_newNumber); 00238 } 00239 00240 static bool readBoundingBoxEPS(const char *fileName,int *width,int *height) 00241 { 00242 QCString bb("%%PageBoundingBox:"); 00243 QFile f(fileName); 00244 if (!f.open(IO_ReadOnly)) return FALSE; 00245 const int maxLineLen=1024; 00246 char buf[maxLineLen]; 00247 while (!f.atEnd()) 00248 { 00249 int numBytes = f.readLine(buf,maxLineLen-1); // read line 00250 buf[numBytes]='\0'; 00251 if (strncmp(buf,bb.data(),bb.length()-1)==0) // found PageBoundingBox string 00252 { 00253 int x,y; 00254 if (sscanf(buf+bb.length(),"%d %d %d %d",&x,&y,width,height)!=4) 00255 { 00256 return FALSE; 00257 } 00258 return TRUE; 00259 } 00260 } 00261 return FALSE; 00262 } 00263 00264 // since dot silently reproduces the input file when it does not 00265 // support the PNG format, we need to check the result. 00266 static void checkDotResult(const QCString &imgName) 00267 { 00268 if (Config_getEnum("DOT_IMAGE_FORMAT")=="png") 00269 { 00270 QFile f(imgName); 00271 if (f.open(IO_ReadOnly)) 00272 { 00273 char data[4]; 00274 if (f.readBlock(data,4)==4) 00275 { 00276 if (!(data[1]=='P' && data[2]=='N' && data[3]=='G')) 00277 { 00278 err("Error! Image `%s' produced by dot is not a valid PNG!\n" 00279 "You should either select a different format " 00280 "(DOT_IMAGE_FORMAT in the config file) or install a more " 00281 "recent version of graphviz (1.7+)\n",imgName.data() 00282 ); 00283 } 00284 } 00285 else 00286 { 00287 err("Error: Could not read image `%s' generated by dot!\n",imgName.data()); 00288 } 00289 } 00290 else 00291 { 00292 err("Error: Could not open image `%s' generated by dot!\n",imgName.data()); 00293 } 00294 } 00295 } 00296 00302 static bool checkAndUpdateMd5Signature(const QCString &baseName,const QCString &md5) 00303 { 00304 QFile f(baseName+".md5"); 00305 if (f.open(IO_ReadOnly)) 00306 { 00307 // read checksum 00308 QCString md5stored(33); 00309 int bytesRead=f.readBlock(md5stored.data(),32); 00310 md5stored[32]='\0'; 00311 // compare checksum 00312 if (bytesRead==32 && md5==md5stored) 00313 { 00314 // bail out if equal 00315 return FALSE; 00316 } 00317 } 00318 f.close(); 00319 // create checksum file 00320 if (f.open(IO_WriteOnly)) 00321 { 00322 f.writeBlock(md5.data(),32); 00323 f.close(); 00324 } 00325 return TRUE; 00326 } 00327 00328 //-------------------------------------------------------------------- 00329 00330 class DotNodeList : public QList<DotNode> 00331 { 00332 public: 00333 DotNodeList() : QList<DotNode>() {} 00334 ~DotNodeList() {} 00335 int compareItems(GCI item1,GCI item2) 00336 { 00337 return stricmp(((DotNode *)item1)->m_label,((DotNode *)item2)->m_label); 00338 } 00339 }; 00340 00341 //-------------------------------------------------------------------- 00342 00343 DotRunner::DotRunner(const char *file) : m_file(file) 00344 { 00345 m_jobs.setAutoDelete(TRUE); 00346 } 00347 00348 void DotRunner::addJob(const char *format,const char *output) 00349 { 00350 QCString args = QCString("-T")+format+" -o \""+output+"\""; 00351 m_jobs.append(new QCString(args)); 00352 } 00353 00354 bool DotRunner::run() 00355 { 00356 int exitCode=0; 00357 static QCString dotExe = Config_getString("DOT_PATH")+"dot"; 00358 QCString dotArgs; 00359 QListIterator<QCString> li(m_jobs); 00360 QCString *s; 00361 if (Config_getBool("DOT_MULTI_TARGETS")) 00362 { 00363 dotArgs="\""+m_file+"\""; 00364 for (li.toFirst();(s=li.current());++li) 00365 { 00366 dotArgs+=' '; 00367 dotArgs+=*s; 00368 } 00369 if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0) 00370 { 00371 goto error; 00372 } 00373 } 00374 else 00375 { 00376 for (li.toFirst();(s=li.current());++li) 00377 { 00378 dotArgs="\""+m_file+"\" "+*s; 00379 if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0) 00380 { 00381 goto error; 00382 } 00383 } 00384 } 00385 return TRUE; 00386 error: 00387 err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n", 00388 exitCode,dotExe.data(),dotArgs.data()); 00389 return FALSE; 00390 } 00391 00392 //-------------------------------------------------------------------- 00393 00394 00398 static void deleteNodes(DotNode *node,SDict<DotNode> *skipNodes=0) 00399 { 00400 //printf("deleteNodes skipNodes=%p\n",skipNodes); 00401 static DotNodeList deletedNodes; 00402 deletedNodes.setAutoDelete(TRUE); 00403 node->deleteNode(deletedNodes,skipNodes); // collect nodes to be deleted. 00404 deletedNodes.clear(); // actually remove the nodes. 00405 } 00406 00407 DotNode::DotNode(int n,const char *lab,const char *tip, const char *url, 00408 bool isRoot,ClassDef *cd) 00409 : m_subgraphId(-1) 00410 , m_number(n) 00411 , m_label(lab) 00412 , m_tooltip(tip) 00413 , m_url(url) 00414 , m_parents(0) 00415 , m_children(0) 00416 , m_edgeInfo(0) 00417 , m_deleted(FALSE) 00418 , m_written(FALSE) 00419 , m_hasDoc(FALSE) 00420 , m_isRoot(isRoot) 00421 , m_classDef(cd) 00422 , m_visible(FALSE) 00423 , m_truncated(Unknown) 00424 , m_distance(1000) 00425 { 00426 } 00427 00428 DotNode::~DotNode() 00429 { 00430 delete m_children; 00431 delete m_parents; 00432 delete m_edgeInfo; 00433 } 00434 00435 void DotNode::addChild(DotNode *n, 00436 int edgeColor, 00437 int edgeStyle, 00438 const char *edgeLab, 00439 const char *edgeURL, 00440 int edgeLabCol 00441 ) 00442 { 00443 if (m_children==0) 00444 { 00445 m_children = new QList<DotNode>; 00446 m_edgeInfo = new QList<EdgeInfo>; 00447 m_edgeInfo->setAutoDelete(TRUE); 00448 } 00449 m_children->append(n); 00450 EdgeInfo *ei = new EdgeInfo; 00451 ei->m_color = edgeColor; 00452 ei->m_style = edgeStyle; 00453 ei->m_label = edgeLab; 00454 ei->m_url = edgeURL; 00455 if (edgeLabCol==-1) 00456 ei->m_labColor=edgeColor; 00457 else 00458 ei->m_labColor=edgeLabCol; 00459 m_edgeInfo->append(ei); 00460 } 00461 00462 void DotNode::addParent(DotNode *n) 00463 { 00464 if (m_parents==0) 00465 { 00466 m_parents = new QList<DotNode>; 00467 } 00468 m_parents->append(n); 00469 } 00470 00471 void DotNode::removeChild(DotNode *n) 00472 { 00473 if (m_children) m_children->remove(n); 00474 } 00475 00476 void DotNode::removeParent(DotNode *n) 00477 { 00478 if (m_parents) m_parents->remove(n); 00479 } 00480 00481 void DotNode::deleteNode(DotNodeList &deletedList,SDict<DotNode> *skipNodes) 00482 { 00483 if (m_deleted) return; // avoid recursive loops in case the graph has cycles 00484 m_deleted=TRUE; 00485 if (m_parents!=0) // delete all parent nodes of this node 00486 { 00487 QListIterator<DotNode> dnlip(*m_parents); 00488 DotNode *pn; 00489 for (dnlip.toFirst();(pn=dnlip.current());++dnlip) 00490 { 00491 //pn->removeChild(this); 00492 pn->deleteNode(deletedList,skipNodes); 00493 } 00494 } 00495 if (m_children!=0) // delete all child nodes of this node 00496 { 00497 QListIterator<DotNode> dnlic(*m_children); 00498 DotNode *cn; 00499 for (dnlic.toFirst();(cn=dnlic.current());++dnlic) 00500 { 00501 //cn->removeParent(this); 00502 cn->deleteNode(deletedList,skipNodes); 00503 } 00504 } 00505 // add this node to the list of deleted nodes. 00506 //printf("skipNodes=%p find(%p)=%p\n",skipNodes,this,skipNodes ? skipNodes->find((int)this) : 0); 00507 if (skipNodes==0 || skipNodes->find((char*)this)==0) 00508 { 00509 //printf("deleting\n"); 00510 deletedList.append(this); 00511 } 00512 } 00513 00514 void DotNode::setDistance(int distance) 00515 { 00516 if (distance<m_distance) m_distance = distance; 00517 } 00518 00519 static QCString convertLabel(const QCString &l) 00520 { 00521 QCString result; 00522 const char *p=l.data(); 00523 if (p==0) return result; 00524 char c; 00525 while ((c=*p++)) 00526 { 00527 switch(c) 00528 { 00529 case '\\': result+="\\\\"; break; 00530 case '\n': result+="\\n"; break; 00531 case '<': result+="\\<"; break; 00532 case '>': result+="\\>"; break; 00533 case '|': result+="\\|"; break; 00534 case '{': result+="\\{"; break; 00535 case '}': result+="\\}"; break; 00536 case '"': result+="\\\""; break; 00537 default: result+=c; break; 00538 } 00539 } 00540 return result; 00541 } 00542 00543 static QCString escapeTooltip(const QCString &tooltip) 00544 { 00545 QCString result; 00546 const char *p=tooltip.data(); 00547 if (p==0) return result; 00548 char c; 00549 while ((c=*p++)) 00550 { 00551 switch(c) 00552 { 00553 case '\\': result+="\\\\"; break; 00554 default: result+=c; break; 00555 } 00556 } 00557 return result; 00558 } 00559 00560 static void writeBoxMemberList(QTextStream &t,char prot,MemberList *ml,ClassDef *scope) 00561 { 00562 if (ml) 00563 { 00564 MemberListIterator mlia(*ml); 00565 MemberDef *mma; 00566 for (mlia.toFirst();(mma = mlia.current());++mlia) 00567 { 00568 if (mma->getClassDef() == scope) 00569 { 00570 t << prot << " " << convertLabel(mma->name()); 00571 if (!mma->isObjCMethod() && mma->isFunction()) t << "()"; 00572 t << "\\l"; 00573 } 00574 } 00575 // write member groups within the memberlist 00576 MemberGroupList *mgl = ml->getMemberGroupList(); 00577 if (mgl) 00578 { 00579 MemberGroupListIterator mgli(*mgl); 00580 MemberGroup *mg; 00581 for (mgli.toFirst();(mg=mgli.current());++mgli) 00582 { 00583 if (mg->members()) 00584 { 00585 writeBoxMemberList(t,prot,mg->members(),scope); 00586 } 00587 } 00588 } 00589 } 00590 } 00591 00592 void DotNode::writeBox(QTextStream &t, 00593 GraphType gt, 00594 GraphOutputFormat /*format*/, 00595 bool hasNonReachableChildren, 00596 bool reNumber) 00597 { 00598 const char *labCol = 00599 m_url.isEmpty() ? "grey75" : // non link 00600 ( 00601 (hasNonReachableChildren) ? "red" : "black" 00602 ); 00603 t << " Node" << reNumberNode(m_number,reNumber) << " [label=\""; 00604 00605 if (m_classDef && Config_getBool("UML_LOOK") && 00606 (gt==Inheritance || gt==Collaboration)) 00607 { 00608 t << "{" << convertLabel(m_label); 00609 t << "\\n|"; 00610 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubAttribs),m_classDef); 00611 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticAttribs),m_classDef); 00612 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacAttribs),m_classDef); 00613 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticAttribs),m_classDef); 00614 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proAttribs),m_classDef); 00615 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticAttribs),m_classDef); 00616 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priAttribs),m_classDef); 00617 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticAttribs),m_classDef); 00618 t << "|"; 00619 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubMethods),m_classDef); 00620 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticMethods),m_classDef); 00621 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubSlots),m_classDef); 00622 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacMethods),m_classDef); 00623 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticMethods),m_classDef); 00624 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proMethods),m_classDef); 00625 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticMethods),m_classDef); 00626 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proSlots),m_classDef); 00627 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priMethods),m_classDef); 00628 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticMethods),m_classDef); 00629 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priSlots),m_classDef); 00630 if (m_classDef->getMemberGroupSDict()) 00631 { 00632 MemberGroupSDict::Iterator mgdi(*m_classDef->getMemberGroupSDict()); 00633 MemberGroup *mg; 00634 for (mgdi.toFirst();(mg=mgdi.current());++mgdi) 00635 { 00636 if (mg->members()) 00637 { 00638 writeBoxMemberList(t,'*',mg->members(),m_classDef); 00639 } 00640 } 00641 } 00642 t << "}"; 00643 } 00644 else // standard look 00645 { 00646 t << convertLabel(m_label); 00647 } 00648 t << "\",height=0.2,width=0.4"; 00649 if (m_isRoot) 00650 { 00651 t << ",color=\"black\", fillcolor=\"grey75\", style=\"filled\" fontcolor=\"black\""; 00652 } 00653 else 00654 { 00655 if (!Config_getBool("DOT_TRANSPARENT")) 00656 { 00657 t << ",color=\"" << labCol << "\", fillcolor=\"white\", style=\"filled\""; 00658 } 00659 else 00660 { 00661 t << ",color=\"" << labCol << "\""; 00662 } 00663 if (!m_url.isEmpty()) 00664 { 00665 int anchorPos = m_url.findRev('#'); 00666 if (anchorPos==-1) 00667 { 00668 t << ",URL=\"" << m_url << Doxygen::htmlFileExtension << "\""; 00669 } 00670 else 00671 { 00672 t << ",URL=\"" << m_url.left(anchorPos) << Doxygen::htmlFileExtension 00673 << m_url.right(m_url.length()-anchorPos) << "\""; 00674 } 00675 } 00676 if (!m_tooltip.isEmpty()) 00677 { 00678 t << ",tooltip=\"" << escapeTooltip(m_tooltip) << "\""; 00679 } 00680 } 00681 t << "];" << endl; 00682 } 00683 00684 void DotNode::writeArrow(QTextStream &t, 00685 GraphType gt, 00686 GraphOutputFormat format, 00687 DotNode *cn, 00688 EdgeInfo *ei, 00689 bool topDown, 00690 bool pointBack, 00691 bool reNumber 00692 ) 00693 { 00694 t << " Node"; 00695 if (topDown) 00696 t << reNumberNode(cn->number(),reNumber); 00697 else 00698 t << reNumberNode(m_number,reNumber); 00699 t << " -> Node"; 00700 if (topDown) 00701 t << reNumberNode(m_number,reNumber); 00702 else 00703 t << reNumberNode(cn->number(),reNumber); 00704 t << " ["; 00705 if (pointBack) t << "dir=back,"; 00706 t << "color=\"" << edgeColorMap[ei->m_color] 00707 << "\",fontsize=10,style=\"" << edgeStyleMap[ei->m_style] << "\""; 00708 if (!ei->m_label.isEmpty()) 00709 { 00710 t << ",label=\"" << convertLabel(ei->m_label) << "\""; 00711 } 00712 if (Config_getBool("UML_LOOK") && 00713 arrowStyle[ei->m_color] && 00714 (gt==Inheritance || gt==Collaboration) 00715 ) 00716 { 00717 if (pointBack) 00718 t << ",arrowtail=\"" << arrowStyle[ei->m_color] << "\""; 00719 else 00720 t << ",arrowhead=\"" << arrowStyle[ei->m_color] << "\""; 00721 } 00722 00723 if (format==BITMAP) t << ",fontname=\"" << FONTNAME << "\""; 00724 t << "];" << endl; 00725 } 00726 00727 void DotNode::write(QTextStream &t, 00728 GraphType gt, 00729 GraphOutputFormat format, 00730 bool topDown, 00731 bool toChildren, 00732 bool backArrows, 00733 bool reNumber 00734 ) 00735 { 00736 //printf("DotNode::write(%d) name=%s this=%p written=%d\n",distance,m_label.data(),this,m_written); 00737 if (m_written) return; // node already written to the output 00738 if (!m_visible) return; // node is not visible 00739 writeBox(t,gt,format,m_truncated==Truncated,reNumber); 00740 m_written=TRUE; 00741 QList<DotNode> *nl = toChildren ? m_children : m_parents; 00742 if (nl) 00743 { 00744 if (toChildren) 00745 { 00746 QListIterator<DotNode> dnli1(*nl); 00747 QListIterator<EdgeInfo> dnli2(*m_edgeInfo); 00748 DotNode *cn; 00749 for (dnli1.toFirst();(cn=dnli1.current());++dnli1,++dnli2) 00750 { 00751 if (cn->isVisible()) 00752 { 00753 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",cn->label().data()); 00754 writeArrow(t,gt,format,cn,dnli2.current(),topDown,backArrows,reNumber); 00755 } 00756 cn->write(t,gt,format,topDown,toChildren,backArrows,reNumber); 00757 } 00758 } 00759 else // render parents 00760 { 00761 QListIterator<DotNode> dnli(*nl); 00762 DotNode *pn; 00763 for (dnli.toFirst();(pn=dnli.current());++dnli) 00764 { 00765 if (pn->isVisible()) 00766 { 00767 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",pn->label().data()); 00768 writeArrow(t, 00769 gt, 00770 format, 00771 pn, 00772 pn->m_edgeInfo->at(pn->m_children->findRef(this)), 00773 FALSE, 00774 backArrows, 00775 reNumber 00776 ); 00777 } 00778 pn->write(t,gt,format,TRUE,FALSE,backArrows,reNumber); 00779 } 00780 } 00781 } 00782 //printf("end DotNode::write(%d) name=%s\n",distance,m_label.data()); 00783 } 00784 00785 void DotNode::writeXML(QTextStream &t,bool isClassGraph) 00786 { 00787 t << " <node id=\"" << m_number << "\">" << endl; 00788 t << " <label>" << convertToXML(m_label) << "</label>" << endl; 00789 if (!m_url.isEmpty()) 00790 { 00791 QCString url(m_url); 00792 char *refPtr = url.data(); 00793 char *urlPtr = strchr(url.data(),'$'); 00794 if (urlPtr) 00795 { 00796 *urlPtr++='\0'; 00797 t << " <link refid=\"" << convertToXML(urlPtr) << "\""; 00798 if (*refPtr!='\0') 00799 { 00800 t << " external=\"" << convertToXML(refPtr) << "\""; 00801 } 00802 t << "/>" << endl; 00803 } 00804 } 00805 if (m_children) 00806 { 00807 QListIterator<DotNode> nli(*m_children); 00808 QListIterator<EdgeInfo> eli(*m_edgeInfo); 00809 DotNode *childNode; 00810 EdgeInfo *edgeInfo; 00811 for (;(childNode=nli.current());++nli,++eli) 00812 { 00813 edgeInfo=eli.current(); 00814 t << " <childnode refid=\"" << childNode->m_number << "\" relation=\""; 00815 if (isClassGraph) 00816 { 00817 switch(edgeInfo->m_color) 00818 { 00819 case EdgeInfo::Blue: t << "public-inheritance"; break; 00820 case EdgeInfo::Green: t << "protected-inheritance"; break; 00821 case EdgeInfo::Red: t << "private-inheritance"; break; 00822 case EdgeInfo::Purple: t << "usage"; break; 00823 case EdgeInfo::Orange: t << "template-instance"; break; 00824 case EdgeInfo::Grey: ASSERT(0); break; 00825 } 00826 } 00827 else // include graph 00828 { 00829 t << "include"; 00830 } 00831 t << "\">" << endl; 00832 if (!edgeInfo->m_label.isEmpty()) 00833 { 00834 int p=0; 00835 int ni; 00836 while ((ni=edgeInfo->m_label.find("\\n",p))!=-1) 00837 { 00838 t << " <edgelabel>" 00839 << convertToXML(edgeInfo->m_label.mid(p,ni-p)) 00840 << "</edgelabel>" << endl; 00841 p=ni+2; 00842 } 00843 t << " <edgelabel>" 00844 << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p)) 00845 << "</edgelabel>" << endl; 00846 } 00847 t << " </childnode>" << endl; 00848 } 00849 } 00850 t << " </node>" << endl; 00851 } 00852 00853 00854 void DotNode::writeDEF(QTextStream &t) 00855 { 00856 char* nodePrefix = " node-"; 00857 00858 t << " node = {" << endl; 00859 t << nodePrefix << "id = " << m_number << ';' << endl; 00860 t << nodePrefix << "label = '" << m_label << "';" << endl; 00861 00862 if (!m_url.isEmpty()) 00863 { 00864 QCString url(m_url); 00865 char *refPtr = url.data(); 00866 char *urlPtr = strchr(url.data(),'$'); 00867 if (urlPtr) 00868 { 00869 *urlPtr++='\0'; 00870 t << nodePrefix << "link = {" << endl << " " 00871 << nodePrefix << "link-id = '" << urlPtr << "';" << endl; 00872 00873 if (*refPtr!='\0') 00874 { 00875 t << " " << nodePrefix << "link-external = '" 00876 << refPtr << "';" << endl; 00877 } 00878 t << " };" << endl; 00879 } 00880 } 00881 if (m_children) 00882 { 00883 QListIterator<DotNode> nli(*m_children); 00884 QListIterator<EdgeInfo> eli(*m_edgeInfo); 00885 DotNode *childNode; 00886 EdgeInfo *edgeInfo; 00887 for (;(childNode=nli.current());++nli,++eli) 00888 { 00889 edgeInfo=eli.current(); 00890 t << " node-child = {" << endl; 00891 t << " child-id = '" << childNode->m_number << "';" << endl; 00892 t << " relation = "; 00893 00894 switch(edgeInfo->m_color) 00895 { 00896 case EdgeInfo::Blue: t << "public-inheritance"; break; 00897 case EdgeInfo::Green: t << "protected-inheritance"; break; 00898 case EdgeInfo::Red: t << "private-inheritance"; break; 00899 case EdgeInfo::Purple: t << "usage"; break; 00900 case EdgeInfo::Orange: t << "template-instance"; break; 00901 case EdgeInfo::Grey: ASSERT(0); break; 00902 } 00903 t << ';' << endl; 00904 00905 if (!edgeInfo->m_label.isEmpty()) 00906 { 00907 t << " edgelabel = <<_EnD_oF_dEf_TeXt_" << endl 00908 << edgeInfo->m_label << endl 00909 << "_EnD_oF_dEf_TeXt_;" << endl; 00910 } 00911 t << " }; /* node-child */" << endl; 00912 } /* for (;childNode...) */ 00913 } 00914 t << " }; /* node */" << endl; 00915 } 00916 00917 00918 void DotNode::clearWriteFlag() 00919 { 00920 m_written=FALSE; 00921 if (m_parents!=0) 00922 { 00923 QListIterator<DotNode> dnlip(*m_parents); 00924 DotNode *pn; 00925 for (dnlip.toFirst();(pn=dnlip.current());++dnlip) 00926 { 00927 if (pn->m_written) 00928 { 00929 pn->clearWriteFlag(); 00930 } 00931 } 00932 } 00933 if (m_children!=0) 00934 { 00935 QListIterator<DotNode> dnlic(*m_children); 00936 DotNode *cn; 00937 for (dnlic.toFirst();(cn=dnlic.current());++dnlic) 00938 { 00939 if (cn->m_written) 00940 { 00941 cn->clearWriteFlag(); 00942 } 00943 } 00944 } 00945 } 00946 00947 void DotNode::colorConnectedNodes(int curColor) 00948 { 00949 if (m_children) 00950 { 00951 QListIterator<DotNode> dnlic(*m_children); 00952 DotNode *cn; 00953 for (dnlic.toFirst();(cn=dnlic.current());++dnlic) 00954 { 00955 if (cn->m_subgraphId==-1) // uncolored child node 00956 { 00957 cn->m_subgraphId=curColor; 00958 cn->markAsVisible(); 00959 cn->colorConnectedNodes(curColor); 00960 //printf("coloring node %s (%p): %d\n",cn->m_label.data(),cn,cn->m_subgraphId); 00961 } 00962 } 00963 } 00964 00965 if (m_parents) 00966 { 00967 QListIterator<DotNode> dnlip(*m_parents); 00968 DotNode *pn; 00969 for (dnlip.toFirst();(pn=dnlip.current());++dnlip) 00970 { 00971 if (pn->m_subgraphId==-1) // uncolored parent node 00972 { 00973 pn->m_subgraphId=curColor; 00974 pn->markAsVisible(); 00975 pn->colorConnectedNodes(curColor); 00976 //printf("coloring node %s (%p): %d\n",pn->m_label.data(),pn,pn->m_subgraphId); 00977 } 00978 } 00979 } 00980 } 00981 00982 const DotNode *DotNode::findDocNode() const 00983 { 00984 if (!m_url.isEmpty()) return this; 00985 //printf("findDocNode(): `%s'\n",m_label.data()); 00986 if (m_parents) 00987 { 00988 QListIterator<DotNode> dnli(*m_parents); 00989 DotNode *pn; 00990 for (dnli.toFirst();(pn=dnli.current());++dnli) 00991 { 00992 if (!pn->m_hasDoc) 00993 { 00994 pn->m_hasDoc=TRUE; 00995 const DotNode *dn = pn->findDocNode(); 00996 if (dn) return dn; 00997 } 00998 } 00999 } 01000 if (m_children) 01001 { 01002 QListIterator<DotNode> dnli(*m_children); 01003 DotNode *cn; 01004 for (dnli.toFirst();(cn=dnli.current());++dnli) 01005 { 01006 if (!cn->m_hasDoc) 01007 { 01008 cn->m_hasDoc=TRUE; 01009 const DotNode *dn = cn->findDocNode(); 01010 if (dn) return dn; 01011 } 01012 } 01013 } 01014 return 0; 01015 } 01016 01017 //-------------------------------------------------------------------- 01018 01019 int DotGfxHierarchyTable::m_curNodeNumber; 01020 01021 void DotGfxHierarchyTable::writeGraph(QTextStream &out,const char *path) const 01022 { 01023 //printf("DotGfxHierarchyTable::writeGraph(%s)\n",name); 01024 //printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count()); 01025 if (m_rootSubgraphs->count()==0) return; 01026 01027 QDir d(path); 01028 // store the original directory 01029 if (!d.exists()) 01030 { 01031 err("Error: Output dir %s does not exist!\n",path); exit(1); 01032 } 01033 QCString oldDir = convertToQCString(QDir::currentDirPath()); 01034 // go to the html output directory (i.e. path) 01035 QDir::setCurrent(d.absPath()); 01036 QDir thisDir; 01037 01038 // put each connected subgraph of the hierarchy in a row of the HTML output 01039 out << "<table border=\"0\" cellspacing=\"10\" cellpadding=\"0\">" << endl; 01040 01041 QListIterator<DotNode> dnli(*m_rootSubgraphs); 01042 DotNode *n; 01043 int count=0; 01044 for (dnli.toFirst();(n=dnli.current());++dnli) 01045 { 01046 QCString baseName; 01047 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); 01048 baseName.sprintf("inherit_graph_%d",count++); 01049 baseName = convertNameToFile(baseName); 01050 QCString imgName=baseName+"."+ imgExt; 01051 QCString mapName=baseName+".map"; 01052 QListIterator<DotNode> dnli2(*m_rootNodes); 01053 DotNode *node; 01054 01055 // compute md5 checksum of the graph were are about to generate 01056 QString theGraph; 01057 QTextStream md5stream(&theGraph,IO_WriteOnly); 01058 md5stream.setEncoding(md5stream.UnicodeUTF8); 01059 writeGraphHeader(md5stream); 01060 md5stream << " rankdir=LR;" << endl; 01061 for (dnli2.toFirst();(node=dnli2.current());++dnli2) 01062 { 01063 if (node->m_subgraphId==n->m_subgraphId) 01064 { 01065 node->clearWriteFlag(); 01066 } 01067 } 01068 for (dnli2.toFirst();(node=dnli2.current());++dnli2) 01069 { 01070 if (node->m_subgraphId==n->m_subgraphId) 01071 { 01072 node->write(md5stream,DotNode::Hierarchy,BITMAP,FALSE,TRUE,TRUE,TRUE); 01073 } 01074 } 01075 writeGraphFooter(md5stream); 01076 resetReNumbering(); 01077 uchar md5_sig[16]; 01078 QCString sigStr(33); 01079 MD5Buffer((const unsigned char *)theGraph.ascii(),theGraph.length(),md5_sig); 01080 MD5SigToString(md5_sig,sigStr.data(),33); 01081 if (checkAndUpdateMd5Signature(baseName,sigStr) || 01082 !QFileInfo(mapName).exists()) 01083 { 01084 // image was new or has changed 01085 QCString dotName=baseName+".dot"; 01086 QFile f(dotName); 01087 if (!f.open(IO_WriteOnly)) return; 01088 QTextStream t(&f); 01089 t.setEncoding(t.UnicodeUTF8); 01090 t << theGraph; 01091 f.close(); 01092 resetReNumbering(); 01093 01094 DotRunner dotRun(dotName); 01095 dotRun.addJob(imgExt,imgName); 01096 dotRun.addJob(MAP_CMD,mapName); 01097 if (!dotRun.run()) 01098 { 01099 out << "</table>" << endl; 01100 return; 01101 } 01102 01103 checkDotResult(imgName); 01104 if (Config_getBool("DOT_CLEANUP")) thisDir.remove(dotName); 01105 } 01106 // write image and map in a table row 01107 QCString mapLabel = convertNameToFile(n->m_label); 01108 out << "<tr><td><img src=\"" << imgName << "\" border=\"0\" alt=\"\" usemap=\"#" 01109 << mapLabel << "_map\">" << endl; 01110 out << "<map name=\"" << mapLabel << "_map\">" << endl; 01111 convertMapFile(out,mapName,""); 01112 out << "</map></td></tr>" << endl; 01113 //thisDir.remove(mapName); 01114 } 01115 out << "</table>" << endl; 01116 01117 QDir::setCurrent(oldDir); 01118 } 01119 01120 void DotGfxHierarchyTable::addHierarchy(DotNode *n,ClassDef *cd,bool hideSuper) 01121 { 01122 //printf("addHierarchy `%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count()); 01123 if (cd->subClasses()) 01124 { 01125 BaseClassListIterator bcli(*cd->subClasses()); 01126 BaseClassDef *bcd; 01127 for ( ; (bcd=bcli.current()) ; ++bcli ) 01128 { 01129 ClassDef *bClass=bcd->classDef; 01130 //printf(" Trying sub class=`%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count()); 01131 if (bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses())) 01132 { 01133 DotNode *bn; 01134 //printf(" Node `%s' Found visible class=`%s'\n",n->m_label.data(), 01135 // bClass->name().data()); 01136 if ((bn=m_usedNodes->find(bClass->name()))) // node already present 01137 { 01138 if (n->m_children==0 || n->m_children->findRef(bn)==-1) // no arrow yet 01139 { 01140 n->addChild(bn,bcd->prot); 01141 bn->addParent(n); 01142 //printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n", 01143 // n->m_label.data(), 01144 // bn->m_label.data(), 01145 // bn->m_children ? bn->m_children->count() : 0, 01146 // bn->m_parents ? bn->m_parents->count() : 0 01147 // ); 01148 } 01149 //else 01150 //{ 01151 // printf(" Class already has an arrow!\n"); 01152 //} 01153 } 01154 else 01155 { 01156 QCString tmp_url=""; 01157 if (bClass->isLinkable() && !bClass->isHidden()) 01158 { 01159 tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase(); 01160 } 01161 QCString tooltip = bClass->briefDescriptionAsTooltip(); 01162 bn = new DotNode(m_curNodeNumber++, 01163 bClass->displayName(), 01164 tooltip, 01165 tmp_url.data() 01166 ); 01167 n->addChild(bn,bcd->prot); 01168 bn->addParent(n); 01169 //printf(" Adding node %s to new base node %s (c=%d,p=%d)\n", 01170 // n->m_label.data(), 01171 // bn->m_label.data(), 01172 // bn->m_children ? bn->m_children->count() : 0, 01173 // bn->m_parents ? bn->m_parents->count() : 0 01174 // ); 01175 //printf(" inserting %s (%p)\n",bClass->name().data(),bn); 01176 m_usedNodes->insert(bClass->name(),bn); // add node to the used list 01177 } 01178 if (!bClass->visited && !hideSuper && bClass->subClasses()) 01179 { 01180 bool wasVisited=bClass->visited; 01181 bClass->visited=TRUE; 01182 addHierarchy(bn,bClass,wasVisited); 01183 } 01184 } 01185 } 01186 } 01187 //printf("end addHierarchy\n"); 01188 } 01189 01190 void DotGfxHierarchyTable::addClassList(ClassSDict *cl) 01191 { 01192 ClassSDict::Iterator cli(*cl); 01193 ClassDef *cd; 01194 for (cli.toLast();(cd=cli.current());--cli) 01195 { 01196 //printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count()); 01197 if (!hasVisibleRoot(cd->baseClasses()) && 01198 cd->isVisibleInHierarchy() 01199 ) // root node in the forest 01200 { 01201 QCString tmp_url=""; 01202 if (cd->isLinkable() && !cd->isHidden()) 01203 { 01204 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase(); 01205 } 01206 //printf("Inserting root class %s\n",cd->name().data()); 01207 QCString tooltip = cd->briefDescriptionAsTooltip(); 01208 DotNode *n = new DotNode(m_curNodeNumber++, 01209 cd->displayName(), 01210 tooltip, 01211 tmp_url.data()); 01212 01213 //m_usedNodes->clear(); 01214 m_usedNodes->insert(cd->name(),n); 01215 m_rootNodes->insert(0,n); 01216 if (!cd->visited && cd->subClasses()) 01217 { 01218 addHierarchy(n,cd,cd->visited); 01219 cd->visited=TRUE; 01220 } 01221 } 01222 } 01223 } 01224 01225 DotGfxHierarchyTable::DotGfxHierarchyTable() 01226 { 01227 m_curNodeNumber=0; 01228 m_rootNodes = new QList<DotNode>; 01229 m_usedNodes = new QDict<DotNode>(1009); 01230 m_usedNodes->setAutoDelete(TRUE); 01231 m_rootSubgraphs = new DotNodeList; 01232 01233 // build a graph with each class as a node and the inheritance relations 01234 // as edges 01235 initClassHierarchy(Doxygen::classSDict); 01236 initClassHierarchy(Doxygen::hiddenClasses); 01237 addClassList(Doxygen::classSDict); 01238 addClassList(Doxygen::hiddenClasses); 01239 // m_usedNodes now contains all nodes in the graph 01240 01241 // color the graph into a set of independent subgraphs 01242 bool done=FALSE; 01243 int curColor=0; 01244 QListIterator<DotNode> dnli(*m_rootNodes); 01245 while (!done) // there are still nodes to color 01246 { 01247 DotNode *n; 01248 done=TRUE; // we are done unless there are still uncolored nodes 01249 for (dnli.toLast();(n=dnli.current());--dnli) 01250 { 01251 if (n->m_subgraphId==-1) // not yet colored 01252 { 01253 //printf("Starting at node %s (%p): %d\n",n->m_label.data(),n,curColor); 01254 done=FALSE; // still uncolored nodes 01255 n->m_subgraphId=curColor; 01256 n->markAsVisible(); 01257 n->colorConnectedNodes(curColor); 01258 curColor++; 01259 const DotNode *dn=n->findDocNode(); 01260 if (dn!=0) 01261 m_rootSubgraphs->inSort(dn); 01262 else 01263 m_rootSubgraphs->inSort(n); 01264 } 01265 } 01266 } 01267 01268 //printf("Number of independent subgraphs: %d\n",curColor); 01269 //QListIterator<DotNode> dnli2(*m_rootSubgraphs); 01270 //DotNode *n; 01271 //for (dnli2.toFirst();(n=dnli2.current());++dnli2) 01272 //{ 01273 // printf("Node %s color=%d (c=%d,p=%d)\n", 01274 // n->m_label.data(),n->m_subgraphId, 01275 // n->m_children?n->m_children->count():0, 01276 // n->m_parents?n->m_parents->count():0); 01277 //} 01278 } 01279 01280 DotGfxHierarchyTable::~DotGfxHierarchyTable() 01281 { 01282 //printf("DotGfxHierarchyTable::~DotGfxHierarchyTable\n"); 01283 01284 //QDictIterator<DotNode> di(*m_usedNodes); 01285 //DotNode *n; 01286 //for (;(n=di.current());++di) 01287 //{ 01288 // printf("Node %p: %s\n",n,n->label().data()); 01289 //} 01290 01291 delete m_rootNodes; 01292 delete m_usedNodes; 01293 delete m_rootSubgraphs; 01294 } 01295 01296 //-------------------------------------------------------------------- 01297 01298 int DotClassGraph::m_curNodeNumber = 0; 01299 01300 void DotClassGraph::addClass(ClassDef *cd,DotNode *n,int prot, 01301 const char *label,const char *usedName,const char *templSpec,bool base,int distance) 01302 { 01303 if (Config_getBool("HIDE_UNDOC_CLASSES") && !cd->isLinkable()) return; 01304 01305 int edgeStyle = (label || prot==EdgeInfo::Orange) ? EdgeInfo::Dashed : EdgeInfo::Solid; 01306 QCString className; 01307 if (usedName) // name is a typedef 01308 { 01309 className=usedName; 01310 } 01311 else if (templSpec) // name has a template part 01312 { 01313 className=insertTemplateSpecifierInScope(cd->name(),templSpec); 01314 } 01315 else // just a normal name 01316 { 01317 className=cd->displayName(); 01318 } 01319 //printf("DotClassGraph::addClass(class=`%s',parent=%s,prot=%d,label=%s,dist=%d,usedName=%s,templSpec=%s,base=%d)\n", 01320 // className.data(),n->m_label.data(),prot,label,distance,usedName,templSpec,base); 01321 DotNode *bn = m_usedNodes->find(className); 01322 if (bn) // class already inserted 01323 { 01324 if (base) 01325 { 01326 n->addChild(bn,prot,edgeStyle,label); 01327 bn->addParent(n); 01328 } 01329 else 01330 { 01331 bn->addChild(n,prot,edgeStyle,label); 01332 n->addParent(bn); 01333 } 01334 bn->setDistance(distance); 01335 //printf(" add exiting node %s of %s\n",bn->m_label.data(),n->m_label.data()); 01336 } 01337 else // new class 01338 { 01339 QCString displayName=className; 01340 if (Config_getBool("HIDE_SCOPE_NAMES")) displayName=stripScope(displayName); 01341 QCString tmp_url; 01342 if (cd->isLinkable() && !cd->isHidden()) 01343 { 01344 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase(); 01345 } 01346 QCString tooltip = cd->briefDescriptionAsTooltip(); 01347 bn = new DotNode(m_curNodeNumber++, 01348 displayName, 01349 tooltip, 01350 tmp_url.data(), 01351 FALSE, // rootNode 01352 cd 01353 ); 01354 if (base) 01355 { 01356 n->addChild(bn,prot,edgeStyle,label); 01357 bn->addParent(n); 01358 } 01359 else 01360 { 01361 bn->addChild(n,prot,edgeStyle,label); 01362 n->addParent(bn); 01363 } 01364 bn->setDistance(distance); 01365 m_usedNodes->insert(className,bn); 01366 //printf(" add new child node `%s' to %s hidden=%d url=%s\n", 01367 // className.data(),n->m_label.data(),cd->isHidden(),tmp_url.data()); 01368 01369 buildGraph(cd,bn,base,distance+1); 01370 } 01371 } 01372 01373 void DotClassGraph::determineTruncatedNodes(QList<DotNode> &queue,bool includeParents) 01374 { 01375 while (queue.count()>0) 01376 { 01377 DotNode *n = queue.take(0); 01378 if (n->isVisible() && n->isTruncated()==DotNode::Unknown) 01379 { 01380 bool truncated = FALSE; 01381 if (n->m_children) 01382 { 01383 QListIterator<DotNode> li(*n->m_children); 01384 DotNode *dn; 01385 for (li.toFirst();(dn=li.current());++li) 01386 { 01387 if (!dn->isVisible()) 01388 truncated = TRUE; 01389 else 01390 queue.append(dn); 01391 } 01392 } 01393 if (n->m_parents && includeParents) 01394 { 01395 QListIterator<DotNode> li(*n->m_parents); 01396 DotNode *dn; 01397 for (li.toFirst();(dn=li.current());++li) 01398 { 01399 if (!dn->isVisible()) 01400 truncated = TRUE; 01401 else 01402 queue.append(dn); 01403 } 01404 } 01405 n->markAsTruncated(truncated); 01406 } 01407 } 01408 } 01409 01410 bool DotClassGraph::determineVisibleNodes(DotNode *rootNode, 01411 int maxNodes,bool includeParents) 01412 { 01413 QList<DotNode> childQueue; 01414 QList<DotNode> parentQueue; 01415 QArray<int> childTreeWidth; 01416 QArray<int> parentTreeWidth; 01417 childQueue.append(rootNode); 01418 if (includeParents) parentQueue.append(rootNode); 01419 bool firstNode=TRUE; // flag to force reprocessing rootNode in the parent loop 01420 // despite being marked visible in the child loop 01421 while ((childQueue.count()>0 || parentQueue.count()>0) && maxNodes>0) 01422 { 01423 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH"); 01424 if (childQueue.count()>0) 01425 { 01426 DotNode *n = childQueue.take(0); 01427 int distance = n->distance(); 01428 if (!n->isVisible() && distance<maxDistance) // not yet processed 01429 { 01430 if (distance>0) 01431 { 01432 int oldSize=(int)childTreeWidth.size(); 01433 if (distance>oldSize) 01434 { 01435 childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance)); 01436 int i; for (i=oldSize;i<distance;i++) childTreeWidth[i]=0; 01437 } 01438 childTreeWidth[distance-1]+=n->label().length(); 01439 } 01440 n->markAsVisible(); 01441 maxNodes--; 01442 // add direct children 01443 if (n->m_children) 01444 { 01445 QListIterator<DotNode> li(*n->m_children); 01446 DotNode *dn; 01447 for (li.toFirst();(dn=li.current());++li) 01448 { 01449 childQueue.append(dn); 01450 } 01451 } 01452 } 01453 } 01454 if (includeParents && parentQueue.count()>0) 01455 { 01456 DotNode *n = parentQueue.take(0); 01457 if ((!n->isVisible() || firstNode) && n->distance()<maxDistance) // not yet processed 01458 { 01459 firstNode=FALSE; 01460 int distance = n->distance(); 01461 if (distance>0) 01462 { 01463 int oldSize = (int)parentTreeWidth.size(); 01464 if (distance>oldSize) 01465 { 01466 parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance)); 01467 int i; for (i=oldSize;i<distance;i++) parentTreeWidth[i]=0; 01468 } 01469 parentTreeWidth[distance-1]+=n->label().length(); 01470 } 01471 n->markAsVisible(); 01472 maxNodes--; 01473 // add direct parents 01474 if (n->m_parents) 01475 { 01476 QListIterator<DotNode> li(*n->m_parents); 01477 DotNode *dn; 01478 for (li.toFirst();(dn=li.current());++li) 01479 { 01480 parentQueue.append(dn); 01481 } 01482 } 01483 } 01484 } 01485 } 01486 if (Config_getBool("UML_LOOK")) return FALSE; // UML graph are always top to bottom 01487 int maxWidth=0; 01488 int maxHeight=(int)QMAX(childTreeWidth.size(),parentTreeWidth.size()); 01489 uint i; 01490 for (i=0;i<childTreeWidth.size();i++) 01491 { 01492 if (childTreeWidth.at(i)>maxWidth) maxWidth=childTreeWidth.at(i); 01493 } 01494 for (i=0;i<parentTreeWidth.size();i++) 01495 { 01496 if (parentTreeWidth.at(i)>maxWidth) maxWidth=parentTreeWidth.at(i); 01497 } 01498 //printf("max tree width=%d, max tree height=%d\n",maxWidth,maxHeight); 01499 return maxWidth>80 && maxHeight<12; // used metric to decide to render the tree 01500 // from left to right instead of top to bottom, 01501 // with the idea to render very wide trees in 01502 // left to right order. 01503 } 01504 01505 void DotClassGraph::buildGraph(ClassDef *cd,DotNode *n,bool base,int distance) 01506 { 01507 //printf("DocClassGraph::buildGraph(%s,distance=%d,base=%d)\n", 01508 // cd->name().data(),distance,base); 01509 // ---- Add inheritance relations 01510 01511 if (m_graphType == DotNode::Inheritance || m_graphType==DotNode::Collaboration) 01512 { 01513 BaseClassList *bcl = base ? cd->baseClasses() : cd->subClasses(); 01514 if (bcl) 01515 { 01516 BaseClassListIterator bcli(*bcl); 01517 BaseClassDef *bcd; 01518 for ( ; (bcd=bcli.current()) ; ++bcli ) 01519 { 01520 //printf("-------- inheritance relation %s->%s templ=`%s'\n", 01521 // cd->name().data(),bcd->classDef->name().data(),bcd->templSpecifiers.data()); 01522 addClass(bcd->classDef,n,bcd->prot,0,bcd->usedName, 01523 bcd->templSpecifiers,base,distance); 01524 } 01525 } 01526 } 01527 if (m_graphType == DotNode::Collaboration) 01528 { 01529 // ---- Add usage relations 01530 01531 UsesClassDict *dict = 01532 base ? cd->usedImplementationClasses() : 01533 cd->usedByImplementationClasses() 01534 ; 01535 if (dict) 01536 { 01537 UsesClassDictIterator ucdi(*dict); 01538 UsesClassDef *ucd; 01539 for (;(ucd=ucdi.current());++ucdi) 01540 { 01541 QCString label; 01542 QDictIterator<void> dvi(*ucd->accessors); 01543 const char *s; 01544 bool first=TRUE; 01545 int count=0; 01546 int maxLabels=10; 01547 for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count) 01548 { 01549 if (first) 01550 { 01551 label=s; 01552 first=FALSE; 01553 } 01554 else 01555 { 01556 label+=QCString("\n")+s; 01557 } 01558 } 01559 if (count==maxLabels) label+="\n..."; 01560 //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data()); 01561 addClass(ucd->classDef,n,EdgeInfo::Purple,label,0, 01562 ucd->templSpecifiers,base,distance); 01563 } 01564 } 01565 } 01566 01567 // ---- Add template instantiation relations 01568 01569 static bool templateRelations = Config_getBool("TEMPLATE_RELATIONS"); 01570 if (templateRelations) 01571 { 01572 if (base) // template relations for base classes 01573 { 01574 ClassDef *templMaster=cd->templateMaster(); 01575 if (templMaster) 01576 { 01577 QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances()); 01578 ClassDef *templInstance; 01579 for (;(templInstance=cli.current());++cli) 01580 { 01581 if (templInstance==cd) 01582 { 01583 addClass(templMaster,n,EdgeInfo::Orange,cli.currentKey(),0, 01584 0,TRUE,distance); 01585 } 01586 } 01587 } 01588 } 01589 else // template relations for super classes 01590 { 01591 QDict<ClassDef> *templInstances = cd->getTemplateInstances(); 01592 if (templInstances) 01593 { 01594 QDictIterator<ClassDef> cli(*templInstances); 01595 ClassDef *templInstance; 01596 for (;(templInstance=cli.current());++cli) 01597 { 01598 addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0, 01599 0,FALSE,distance); 01600 } 01601 } 01602 } 01603 } 01604 } 01605 01606 DotClassGraph::DotClassGraph(ClassDef *cd,DotNode::GraphType t) 01607 { 01608 //printf("--------------- DotClassGraph::DotClassGraph `%s'\n",cd->displayName().data()); 01609 m_graphType = t; 01610 QCString tmp_url=""; 01611 if (cd->isLinkable() && !cd->isHidden()) 01612 { 01613 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase(); 01614 } 01615 QCString className = cd->displayName(); 01616 QCString tooltip = cd->briefDescriptionAsTooltip(); 01617 m_startNode = new DotNode(m_curNodeNumber++, 01618 className, 01619 tooltip, 01620 tmp_url.data(), 01621 TRUE, // is a root node 01622 cd 01623 ); 01624 m_startNode->setDistance(0); 01625 m_usedNodes = new QDict<DotNode>(1009); 01626 m_usedNodes->insert(className,m_startNode); 01627 01628 //printf("Root node %s\n",cd->name().data()); 01629 //if (m_recDepth>0) 01630 //{ 01631 buildGraph(cd,m_startNode,TRUE,1); 01632 if (t==DotNode::Inheritance) buildGraph(cd,m_startNode,FALSE,1); 01633 //} 01634 01635 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES"); 01636 //int directChildNodes = 1; 01637 //if (m_startNode->m_children!=0) 01638 // directChildNodes+=m_startNode->m_children->count(); 01639 //if (t==DotNode::Inheritance && m_startNode->m_parents!=0) 01640 // directChildNodes+=m_startNode->m_parents->count(); 01641 //if (directChildNodes>maxNodes) maxNodes=directChildNodes; 01642 //openNodeQueue.append(m_startNode); 01643 m_lrRank = determineVisibleNodes(m_startNode,maxNodes,t==DotNode::Inheritance); 01644 QList<DotNode> openNodeQueue; 01645 openNodeQueue.append(m_startNode); 01646 determineTruncatedNodes(openNodeQueue,t==DotNode::Inheritance); 01647 01648 m_diskName = cd->getFileBase().copy(); 01649 } 01650 01651 bool DotClassGraph::isTrivial() const 01652 { 01653 if (m_graphType==DotNode::Inheritance) 01654 return m_startNode->m_children==0 && m_startNode->m_parents==0; 01655 else 01656 return m_startNode->m_children==0; 01657 } 01658 01659 bool DotClassGraph::isTooBig() const 01660 { 01661 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES"); 01662 int numNodes = 0; 01663 numNodes+= m_startNode->m_children ? m_startNode->m_children->count() : 0; 01664 if (m_graphType==DotNode::Inheritance) 01665 { 01666 numNodes+= m_startNode->m_parents ? m_startNode->m_parents->count() : 0; 01667 } 01668 return numNodes>=maxNodes; 01669 } 01670 01671 DotClassGraph::~DotClassGraph() 01672 { 01673 deleteNodes(m_startNode); 01674 delete m_usedNodes; 01675 } 01676 01680 QCString computeMd5Signature(DotNode *root, 01681 DotNode::GraphType gt, 01682 GraphOutputFormat format, 01683 bool lrRank, 01684 bool renderParents, 01685 bool backArrows, 01686 QCString &graphStr 01687 ) 01688 { 01689 bool reNumber=TRUE; 01690 01691 //printf("computeMd5Signature\n"); 01692 QString buf; 01693 QTextStream md5stream(&buf,IO_WriteOnly); 01694 md5stream.setEncoding(md5stream.UnicodeUTF8); 01695 writeGraphHeader(md5stream); 01696 if (lrRank) 01697 { 01698 md5stream << " rankdir=LR;" << endl; 01699 } 01700 root->clearWriteFlag(); 01701 root->write(md5stream, 01702 gt, 01703 format, 01704 gt!=DotNode::CallGraph && gt!=DotNode::Dependency, 01705 TRUE, 01706 backArrows, 01707 reNumber); 01708 if (renderParents && root->m_parents) 01709 { 01710 QListIterator<DotNode> dnli(*root->m_parents); 01711 DotNode *pn; 01712 for (dnli.toFirst();(pn=dnli.current());++dnli) 01713 { 01714 if (pn->isVisible()) 01715 { 01716 root->writeArrow(md5stream, // stream 01717 gt, // graph type 01718 format, // output format 01719 pn, // child node 01720 pn->m_edgeInfo->at(pn->m_children->findRef(root)), // edge info 01721 FALSE, // topDown? 01722 backArrows, // point back? 01723 reNumber // renumber nodes 01724 ); 01725 } 01726 pn->write(md5stream, // stream 01727 gt, // graph type 01728 format, // output format 01729 TRUE, // topDown? 01730 FALSE, // toChildren? 01731 backArrows, // backward pointing arrows? 01732 reNumber // renumber nodes? 01733 ); 01734 } 01735 } 01736 writeGraphFooter(md5stream); 01737 uchar md5_sig[16]; 01738 QCString sigStr(33); 01739 MD5Buffer((const unsigned char *)buf.ascii(),buf.length(),md5_sig); 01740 MD5SigToString(md5_sig,sigStr.data(),33); 01741 if (reNumber) 01742 { 01743 resetReNumbering(); 01744 } 01745 graphStr=buf.ascii(); 01746 //printf("md5: %s | file: %s\n",sigStr,baseName.data()); 01747 return sigStr; 01748 } 01749 01750 static bool updateDotGraph(DotNode *root, 01751 DotNode::GraphType gt, 01752 //QDir &thisDir, 01753 const QCString &baseName, 01754 GraphOutputFormat format, 01755 bool lrRank, 01756 bool renderParents, 01757 bool backArrows 01758 ) 01759 { 01760 QCString theGraph; 01761 // TODO: write graph to theGraph, then compute md5 checksum 01762 QCString md5 = computeMd5Signature( 01763 root,gt,format,lrRank,renderParents,backArrows,theGraph); 01764 if (checkAndUpdateMd5Signature(baseName,md5)) // graph needs to be regenerated 01765 { 01766 QFile f; 01767 f.setName(baseName+".dot"); 01768 if (f.open(IO_WriteOnly)) 01769 { 01770 QTextStream t(&f); 01771 t.setEncoding(t.UnicodeUTF8); 01772 t << theGraph; 01773 } 01774 return TRUE; 01775 } 01776 return FALSE; 01777 } 01778 01779 QCString DotClassGraph::diskName() const 01780 { 01781 QCString result=m_diskName.copy(); 01782 switch (m_graphType) 01783 { 01784 case DotNode::Collaboration: 01785 result+="_coll_graph"; 01786 break; 01787 //case Interface: 01788 // result+="_intf_graph"; 01789 // break; 01790 case DotNode::Inheritance: 01791 result+="_inherit_graph"; 01792 break; 01793 default: 01794 ASSERT(0); 01795 break; 01796 } 01797 return result; 01798 } 01799 01800 QCString DotClassGraph::writeGraph(QTextStream &out, 01801 GraphOutputFormat format, 01802 const char *path, 01803 const char *relPath, 01804 bool /*isTBRank*/, 01805 bool generateImageMap) const 01806 { 01807 QDir d(path); 01808 // store the original directory 01809 if (!d.exists()) 01810 { 01811 err("Error: Output dir %s does not exist!\n",path); exit(1); 01812 } 01813 QCString oldDir = convertToQCString(QDir::currentDirPath()); 01814 // go to the html output directory (i.e. path) 01815 QDir::setCurrent(d.absPath()); 01816 QDir thisDir; 01817 01818 QCString baseName; 01819 QCString mapName; 01820 switch (m_graphType) 01821 { 01822 case DotNode::Collaboration: 01823 mapName="coll_map"; 01824 break; 01825 //case Interface: 01826 // mapName="intf_map"; 01827 // break; 01828 case DotNode::Inheritance: 01829 mapName="inherit_map"; 01830 break; 01831 default: 01832 ASSERT(0); 01833 break; 01834 } 01835 baseName = convertNameToFile(diskName()); 01836 01837 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); 01838 01839 if (updateDotGraph(m_startNode, 01840 m_graphType, 01841 baseName, 01842 format, 01843 m_lrRank, 01844 m_graphType==DotNode::Inheritance, 01845 TRUE 01846 ) 01847 ) 01848 { 01849 if (format==BITMAP) // run dot to create a bitmap image 01850 { 01851 QCString dotArgs(maxCmdLine); 01852 QCString imgName = baseName+"."+imgExt; 01853 01854 DotRunner dotRun(baseName+".dot"); 01855 dotRun.addJob(imgExt,imgName); 01856 if (generateImageMap) dotRun.addJob(MAP_CMD,baseName+".map"); 01857 if (!dotRun.run()) 01858 { 01859 QDir::setCurrent(oldDir); 01860 return baseName; 01861 } 01862 01863 checkDotResult(imgName); 01864 } 01865 else if (format==EPS) // run dot to create a .eps image 01866 { 01867 DotRunner dotRun(baseName+".dot"); 01868 dotRun.addJob("ps",baseName+".eps"); 01869 if (!dotRun.run()) 01870 { 01871 QDir::setCurrent(oldDir); 01872 return baseName; 01873 } 01874 01875 if (Config_getBool("USE_PDFLATEX")) 01876 { 01877 QCString epstopdfArgs(maxCmdLine); 01878 epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", 01879 baseName.data(),baseName.data()); 01880 if (portable_system("epstopdf",epstopdfArgs)!=0) 01881 { 01882 err("Error: Problems running epstopdf. Check your TeX installation!\n"); 01883 QDir::setCurrent(oldDir); 01884 return baseName; 01885 } 01886 } 01887 01888 } 01889 if (Config_getBool("DOT_CLEANUP")) thisDir.remove(baseName+".dot"); 01890 } 01891 01892 if (format==BITMAP && generateImageMap) // produce HTML to include the image 01893 { 01894 QCString mapLabel = convertNameToFile(m_startNode->m_label+"_"+mapName); 01895 out << "<p><center><img src=\"" << relPath << baseName << "." 01896 << imgExt << "\" border=\"0\" usemap=\"#" 01897 << mapLabel << "\" alt=\""; 01898 switch (m_graphType) 01899 { 01900 case DotNode::Collaboration: 01901 out << "Collaboration graph"; 01902 break; 01903 case DotNode::Inheritance: 01904 out << "Inheritance graph"; 01905 break; 01906 default: 01907 ASSERT(0); 01908 break; 01909 } 01910 out << "\"></center>" << endl; 01911 QString tmpstr; 01912 QTextOStream tmpout(&tmpstr); 01913 tmpout.setEncoding(tmpout.UnicodeUTF8); 01914 convertMapFile(tmpout,baseName+".map",relPath); 01915 if (!tmpstr.isEmpty()) 01916 { 01917 out << "<map name=\"" << mapLabel << "\">" << endl; 01918 out << tmpstr; 01919 out << "</map>" << endl; 01920 } 01921 //thisDir.remove(baseName+".map"); 01922 } 01923 else if (format==EPS) // produce tex to include the .eps image 01924 { 01925 int width=420,height=600; 01926 if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) 01927 { 01928 err("Error: Could not extract bounding box from .eps!\n"); 01929 QDir::setCurrent(oldDir); 01930 return baseName; 01931 } 01932 //printf("Got EPS size %d,%d\n",width,height); 01933 int maxWidth = 400; /* approx. page width in points, excl. margins */ 01934 int maxHeight = 400; /* approx. page height in points, excl. margins */ 01935 out << "\\nopagebreak\n" 01936 "\\begin{figure}[H]\n" 01937 "\\begin{center}\n" 01938 "\\leavevmode\n"; 01939 if (width>maxWidth) 01940 { 01941 out << "\\includegraphics[width=" << maxWidth << "pt]"; 01942 } 01943 else if (height>maxHeight) 01944 { 01945 out << "\\includegraphics[height=" << maxHeight << "pt]"; 01946 } 01947 else 01948 { 01949 out << "\\includegraphics[width=" << width/2 << "pt]"; 01950 } 01951 out << "{" << baseName << "}\n" 01952 "\\end{center}\n" 01953 "\\end{figure}\n"; 01954 } 01955 01956 QDir::setCurrent(oldDir); 01957 return baseName; 01958 } 01959 01960 //-------------------------------------------------------------------- 01961 01962 void DotClassGraph::writeXML(QTextStream &t) 01963 { 01964 QDictIterator<DotNode> dni(*m_usedNodes); 01965 DotNode *node; 01966 for (;(node=dni.current());++dni) 01967 { 01968 node->writeXML(t,TRUE); 01969 } 01970 } 01971 01972 void DotClassGraph::writeDEF(QTextStream &t) 01973 { 01974 QDictIterator<DotNode> dni(*m_usedNodes); 01975 DotNode *node; 01976 for (;(node=dni.current());++dni) 01977 { 01978 node->writeDEF(t); 01979 } 01980 } 01981 01982 //-------------------------------------------------------------------- 01983 01984 int DotInclDepGraph::m_curNodeNumber = 0; 01985 01986 void DotInclDepGraph::buildGraph(DotNode *n,FileDef *fd,int distance) 01987 { 01988 QList<IncludeInfo> *includeFiles = 01989 m_inverse ? fd->includedByFileList() : fd->includeFileList(); 01990 if (includeFiles) 01991 { 01992 QListIterator<IncludeInfo> ili(*includeFiles); 01993 IncludeInfo *ii; 01994 for (;(ii=ili.current());++ili) 01995 { 01996 FileDef *bfd = ii->fileDef; 01997 QCString in = ii->includeName; 01998 //printf(">>>> in=`%s' bfd=%p\n",ii->includeName.data(),bfd); 01999 bool doc=TRUE,src=FALSE; 02000 if (bfd) 02001 { 02002 in = bfd->absFilePath(); 02003 doc = bfd->isLinkable() && !bfd->isHidden(); 02004 src = bfd->generateSourceFile(); 02005 } 02006 if (doc || src || !Config_getBool("HIDE_UNDOC_RELATIONS")) 02007 { 02008 QCString url=""; 02009 if (bfd) url=bfd->getOutputFileBase().copy(); 02010 if (!doc && src) 02011 { 02012 url=bfd->getSourceFileBase(); 02013 } 02014 DotNode *bn = m_usedNodes->find(in); 02015 if (bn) // file is already a node in the graph 02016 { 02017 n->addChild(bn,0,0,0); 02018 bn->addParent(n); 02019 bn->setDistance(distance); 02020 } 02021 else 02022 { 02023 QCString tmp_url; 02024 QCString tooltip; 02025 if (bfd) 02026 { 02027 tmp_url=doc || src ? bfd->getReference()+"$"+url : QCString(); 02028 tooltip = bfd->briefDescriptionAsTooltip(); 02029 } 02030 bn = new DotNode( 02031 m_curNodeNumber++, // n 02032 ii->includeName, // label 02033 tooltip, // tip 02034 tmp_url, // url 02035 FALSE, // rootNode 02036 0 // cd 02037 ); 02038 n->addChild(bn,0,0,0); 02039 bn->addParent(n); 02040 m_usedNodes->insert(in,bn); 02041 bn->setDistance(distance); 02042 02043 if (bfd) buildGraph(bn,bfd,distance+1); 02044 } 02045 } 02046 } 02047 } 02048 } 02049 02050 void DotInclDepGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes) 02051 { 02052 while (queue.count()>0 && maxNodes>0) 02053 { 02054 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH"); 02055 DotNode *n = queue.take(0); 02056 if (!n->isVisible() && n->distance()<maxDistance) // not yet processed 02057 { 02058 n->markAsVisible(); 02059 maxNodes--; 02060 // add direct children 02061 if (n->m_children) 02062 { 02063 QListIterator<DotNode> li(*n->m_children); 02064 DotNode *dn; 02065 for (li.toFirst();(dn=li.current());++li) 02066 { 02067 queue.append(dn); 02068 } 02069 } 02070 } 02071 } 02072 } 02073 02074 void DotInclDepGraph::determineTruncatedNodes(QList<DotNode> &queue) 02075 { 02076 while (queue.count()>0) 02077 { 02078 DotNode *n = queue.take(0); 02079 if (n->isVisible() && n->isTruncated()==DotNode::Unknown) 02080 { 02081 bool truncated = FALSE; 02082 if (n->m_children) 02083 { 02084 QListIterator<DotNode> li(*n->m_children); 02085 DotNode *dn; 02086 for (li.toFirst();(dn=li.current());++li) 02087 { 02088 if (!dn->isVisible()) 02089 truncated = TRUE; 02090 else 02091 queue.append(dn); 02092 } 02093 } 02094 n->markAsTruncated(truncated); 02095 } 02096 } 02097 } 02098 02099 02100 DotInclDepGraph::DotInclDepGraph(FileDef *fd,bool inverse) 02101 { 02102 m_maxDistance = 0; 02103 m_inverse = inverse; 02104 ASSERT(fd!=0); 02105 m_diskName = fd->getFileBase().copy(); 02106 QCString tmp_url=fd->getReference()+"$"+fd->getFileBase(); 02107 m_startNode = new DotNode(m_curNodeNumber++, 02108 fd->docName(), 02109 "", 02110 tmp_url.data(), 02111 TRUE // root node 02112 ); 02113 m_startNode->setDistance(0); 02114 m_usedNodes = new QDict<DotNode>(1009); 02115 m_usedNodes->insert(fd->absFilePath(),m_startNode); 02116 buildGraph(m_startNode,fd,1); 02117 02118 static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES"); 02119 int maxNodes = nodes; 02120 //int directChildNodes = 1; 02121 //if (m_startNode->m_children!=0) 02122 // directChildNodes+=m_startNode->m_children->count(); 02123 //if (directChildNodes>maxNodes) maxNodes=directChildNodes; 02124 QList<DotNode> openNodeQueue; 02125 openNodeQueue.append(m_startNode); 02126 determineVisibleNodes(openNodeQueue,maxNodes); 02127 openNodeQueue.clear(); 02128 openNodeQueue.append(m_startNode); 02129 determineTruncatedNodes(openNodeQueue); 02130 } 02131 02132 DotInclDepGraph::~DotInclDepGraph() 02133 { 02134 deleteNodes(m_startNode); 02135 delete m_usedNodes; 02136 } 02137 02138 QCString DotInclDepGraph::diskName() const 02139 { 02140 QCString result=m_diskName.copy(); 02141 if (m_inverse) result+="_dep"; 02142 result+="_incl"; 02143 return convertNameToFile(result); 02144 } 02145 02146 QCString DotInclDepGraph::writeGraph(QTextStream &out, 02147 GraphOutputFormat format, 02148 const char *path, 02149 const char *relPath, 02150 bool generateImageMap 02151 ) const 02152 { 02153 QDir d(path); 02154 // store the original directory 02155 if (!d.exists()) 02156 { 02157 err("Error: Output dir %s does not exist!\n",path); exit(1); 02158 } 02159 QCString oldDir = convertToQCString(QDir::currentDirPath()); 02160 // go to the html output directory (i.e. path) 02161 QDir::setCurrent(d.absPath()); 02162 QDir thisDir; 02163 02164 QCString baseName=m_diskName; 02165 if (m_inverse) baseName+="_dep"; 02166 baseName+="_incl"; 02167 baseName=convertNameToFile(baseName); 02168 QCString mapName=m_startNode->m_label.copy(); 02169 if (m_inverse) mapName+="dep"; 02170 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); 02171 02172 if (updateDotGraph(m_startNode, 02173 DotNode::Dependency, 02174 baseName, 02175 format, 02176 FALSE, // lrRank 02177 FALSE, // renderParents 02178 m_inverse // backArrows 02179 ) 02180 ) 02181 { 02182 if (format==BITMAP) 02183 { 02184 // run dot to create a bitmap image 02185 QCString dotArgs(maxCmdLine); 02186 QCString imgName=baseName+"."+imgExt; 02187 DotRunner dotRun(baseName+".dot"); 02188 dotRun.addJob(imgExt,imgName); 02189 if (generateImageMap) dotRun.addJob(MAP_CMD,baseName+".map"); 02190 if (!dotRun.run()) 02191 { 02192 QDir::setCurrent(oldDir); 02193 return baseName; 02194 } 02195 checkDotResult(imgName); 02196 } 02197 else if (format==EPS) 02198 { 02199 // run dot to create a .eps image 02200 DotRunner dotRun(baseName+".dot"); 02201 dotRun.addJob("ps",baseName+".eps"); 02202 if (!dotRun.run()) 02203 { 02204 QDir::setCurrent(oldDir); 02205 return baseName; 02206 } 02207 if (Config_getBool("USE_PDFLATEX")) 02208 { 02209 QCString epstopdfArgs(maxCmdLine); 02210 epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", 02211 baseName.data(),baseName.data()); 02212 if (portable_system("epstopdf",epstopdfArgs)!=0) 02213 { 02214 err("Error: Problems running epstopdf. Check your TeX installation!\n"); 02215 QDir::setCurrent(oldDir); 02216 return baseName; 02217 } 02218 } 02219 } 02220 } 02221 02222 if (format==BITMAP && generateImageMap) 02223 { 02224 out << "<p><center><img src=\"" << relPath << baseName << "." 02225 << imgExt << "\" border=\"0\" usemap=\"#" 02226 << mapName << "_map\" alt=\""; 02227 out << "\">"; 02228 out << "</center>" << endl; 02229 QString tmpstr; 02230 QTextOStream tmpout(&tmpstr); 02231 tmpout.setEncoding(tmpout.UnicodeUTF8); 02232 convertMapFile(tmpout,baseName+".map",relPath); 02233 if (!tmpstr.isEmpty()) 02234 { 02235 out << "<map name=\"" << mapName << "_map\">" << endl; 02236 out << tmpstr; 02237 out << "</map>" << endl; 02238 } 02239 //thisDir.remove(baseName+".map"); 02240 } 02241 else if (format==EPS) 02242 { 02243 int width,height; 02244 if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) 02245 { 02246 err("Error: Could not extract bounding box from .eps!\n"); 02247 QDir::setCurrent(oldDir); 02248 return baseName; 02249 } 02250 int maxWidth = 420; /* approx. page width in points */ 02251 02252 out << "\\nopagebreak\n" 02253 "\\begin{figure}[H]\n" 02254 "\\begin{center}\n" 02255 "\\leavevmode\n" 02256 "\\includegraphics[width=" << QMIN(width/2,maxWidth) 02257 << "pt]{" << baseName << "}\n" 02258 "\\end{center}\n" 02259 "\\end{figure}\n"; 02260 } 02261 02262 if (Config_getBool("DOT_CLEANUP")) thisDir.remove(baseName+".dot"); 02263 02264 QDir::setCurrent(oldDir); 02265 return baseName; 02266 } 02267 02268 bool DotInclDepGraph::isTrivial() const 02269 { 02270 return m_startNode->m_children==0; 02271 } 02272 02273 bool DotInclDepGraph::isTooBig() const 02274 { 02275 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES"); 02276 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0; 02277 return numNodes>=maxNodes; 02278 } 02279 02280 void DotInclDepGraph::writeXML(QTextStream &t) 02281 { 02282 QDictIterator<DotNode> dni(*m_usedNodes); 02283 DotNode *node; 02284 for (;(node=dni.current());++dni) 02285 { 02286 node->writeXML(t,FALSE); 02287 } 02288 } 02289 02290 //------------------------------------------------------------- 02291 02292 int DotCallGraph::m_curNodeNumber = 0; 02293 02294 void DotCallGraph::buildGraph(DotNode *n,MemberDef *md,int distance) 02295 { 02296 LockingPtr<MemberSDict> refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers(); 02297 if (!refs.isNull()) 02298 { 02299 MemberSDict::Iterator mri(*refs); 02300 MemberDef *rmd; 02301 for (;(rmd=mri.current());++mri) 02302 { 02303 if (rmd->isFunction()) 02304 { 02305 QCString uniqueId; 02306 uniqueId=rmd->getReference()+"$"+ 02307 rmd->getOutputFileBase()+"#"+rmd->anchor(); 02308 DotNode *bn = m_usedNodes->find(uniqueId); 02309 if (bn) // file is already a node in the graph 02310 { 02311 n->addChild(bn,0,0,0); 02312 bn->addParent(n); 02313 bn->setDistance(distance); 02314 } 02315 else 02316 { 02317 QCString name; 02318 if (Config_getBool("HIDE_SCOPE_NAMES")) 02319 { 02320 name = rmd->getOuterScope()==m_scope ? 02321 rmd->name() : rmd->qualifiedName(); 02322 } 02323 else 02324 { 02325 name = rmd->qualifiedName(); 02326 } 02327 QCString tooltip = rmd->briefDescriptionAsTooltip(); 02328 bn = new DotNode( 02329 m_curNodeNumber++, 02330 linkToText(name,FALSE), 02331 tooltip, 02332 uniqueId, 02333 0 //distance 02334 ); 02335 n->addChild(bn,0,0,0); 02336 bn->addParent(n); 02337 bn->setDistance(distance); 02338 m_usedNodes->insert(uniqueId,bn); 02339 02340 buildGraph(bn,rmd,distance+1); 02341 } 02342 } 02343 } 02344 } 02345 } 02346 02347 void DotCallGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes) 02348 { 02349 while (queue.count()>0 && maxNodes>0) 02350 { 02351 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH"); 02352 DotNode *n = queue.take(0); 02353 if (!n->isVisible() && n->distance()<maxDistance) // not yet processed 02354 { 02355 n->markAsVisible(); 02356 maxNodes--; 02357 // add direct children 02358 if (n->m_children) 02359 { 02360 QListIterator<DotNode> li(*n->m_children); 02361 DotNode *dn; 02362 for (li.toFirst();(dn=li.current());++li) 02363 { 02364 queue.append(dn); 02365 } 02366 } 02367 } 02368 } 02369 } 02370 02371 void DotCallGraph::determineTruncatedNodes(QList<DotNode> &queue) 02372 { 02373 while (queue.count()>0) 02374 { 02375 DotNode *n = queue.take(0); 02376 if (n->isVisible() && n->isTruncated()==DotNode::Unknown) 02377 { 02378 bool truncated = FALSE; 02379 if (n->m_children) 02380 { 02381 QListIterator<DotNode> li(*n->m_children); 02382 DotNode *dn; 02383 for (li.toFirst();(dn=li.current());++li) 02384 { 02385 if (!dn->isVisible()) 02386 truncated = TRUE; 02387 else 02388 queue.append(dn); 02389 } 02390 } 02391 n->markAsTruncated(truncated); 02392 } 02393 } 02394 } 02395 02396 02397 02398 DotCallGraph::DotCallGraph(MemberDef *md,bool inverse) 02399 { 02400 m_maxDistance = 0; 02401 m_inverse = inverse; 02402 m_diskName = md->getOutputFileBase()+"_"+md->anchor(); 02403 m_scope = md->getOuterScope(); 02404 QCString uniqueId; 02405 uniqueId = md->getReference()+"$"+ 02406 md->getOutputFileBase()+"#"+md->anchor(); 02407 QCString name; 02408 if (Config_getBool("HIDE_SCOPE_NAMES")) 02409 { 02410 name = md->name(); 02411 } 02412 else 02413 { 02414 name = md->qualifiedName(); 02415 } 02416 m_startNode = new DotNode(m_curNodeNumber++, 02417 linkToText(name,FALSE), 02418 "", 02419 uniqueId.data(), 02420 TRUE // root node 02421 ); 02422 m_startNode->setDistance(0); 02423 m_usedNodes = new QDict<DotNode>(1009); 02424 m_usedNodes->insert(uniqueId,m_startNode); 02425 buildGraph(m_startNode,md,1); 02426 02427 static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES"); 02428 int maxNodes = nodes; 02429 //int directChildNodes = 1; 02430 //if (m_startNode->m_children!=0) 02431 // directChildNodes+=m_startNode->m_children->count(); 02432 //if (directChildNodes>maxNodes) maxNodes=directChildNodes; 02433 QList<DotNode> openNodeQueue; 02434 openNodeQueue.append(m_startNode); 02435 determineVisibleNodes(openNodeQueue,maxNodes); 02436 openNodeQueue.clear(); 02437 openNodeQueue.append(m_startNode); 02438 determineTruncatedNodes(openNodeQueue); 02439 } 02440 02441 DotCallGraph::~DotCallGraph() 02442 { 02443 deleteNodes(m_startNode); 02444 delete m_usedNodes; 02445 } 02446 02447 QCString DotCallGraph::writeGraph(QTextStream &out, GraphOutputFormat format, 02448 const char *path,const char *relPath,bool generateImageMap) const 02449 { 02450 QDir d(path); 02451 // store the original directory 02452 if (!d.exists()) 02453 { 02454 err("Error: Output dir %s does not exist!\n",path); exit(1); 02455 } 02456 QCString oldDir = convertToQCString(QDir::currentDirPath()); 02457 // go to the html output directory (i.e. path) 02458 QDir::setCurrent(d.absPath()); 02459 QDir thisDir; 02460 02461 QCString baseName = m_diskName + (m_inverse ? "_icgraph" : "_cgraph"); 02462 QCString mapName=baseName; 02463 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); 02464 02465 if (updateDotGraph(m_startNode, 02466 DotNode::CallGraph, 02467 baseName, 02468 format, 02469 TRUE, // lrRank 02470 FALSE, // renderParents 02471 m_inverse // backArrows 02472 ) 02473 ) 02474 { 02475 if (format==BITMAP) 02476 { 02477 // run dot to create a bitmap image 02478 QCString dotArgs(maxCmdLine); 02479 QCString imgName=baseName+"."+imgExt; 02480 DotRunner dotRun(baseName+".dot"); 02481 dotRun.addJob(imgExt,imgName); 02482 if (generateImageMap) dotRun.addJob(MAP_CMD,baseName+".map"); 02483 if (!dotRun.run()) 02484 { 02485 QDir::setCurrent(oldDir); 02486 return baseName; 02487 } 02488 checkDotResult(imgName); 02489 } 02490 else if (format==EPS) 02491 { 02492 // run dot to create a .eps image 02493 DotRunner dotRun(baseName+".dot"); 02494 dotRun.addJob("ps",baseName+".eps"); 02495 if (!dotRun.run()) 02496 { 02497 QDir::setCurrent(oldDir); 02498 return baseName; 02499 } 02500 if (Config_getBool("USE_PDFLATEX")) 02501 { 02502 QCString epstopdfArgs(maxCmdLine); 02503 epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", 02504 baseName.data(),baseName.data()); 02505 if (portable_system("epstopdf",epstopdfArgs)!=0) 02506 { 02507 err("Error: Problems running epstopdf. Check your TeX installation!\n"); 02508 QDir::setCurrent(oldDir); 02509 return baseName; 02510 } 02511 } 02512 } 02513 } 02514 02515 if (format==BITMAP && generateImageMap) 02516 { 02517 out << "<p><center><img src=\"" << relPath << baseName << "." 02518 << imgExt << "\" border=\"0\" usemap=\"#" 02519 << mapName << "_map\" alt=\""; 02520 out << "\">"; 02521 out << "</center>" << endl; 02522 QString tmpstr; 02523 QTextOStream tmpout(&tmpstr); 02524 tmpout.setEncoding(tmpout.UnicodeUTF8); 02525 convertMapFile(tmpout,baseName+".map",relPath); 02526 if (!tmpstr.isEmpty()) 02527 { 02528 out << "<map name=\"" << mapName << "_map\">" << endl; 02529 out << tmpstr; 02530 out << "</map>" << endl; 02531 } 02532 //thisDir.remove(baseName+".map"); 02533 } 02534 else if (format==EPS) 02535 { 02536 int width,height; 02537 if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) 02538 { 02539 err("Error: Could not extract bounding box from .eps!\n"); 02540 QDir::setCurrent(oldDir); 02541 return baseName; 02542 } 02543 int maxWidth = 420; /* approx. page width in points */ 02544 02545 out << "\\nopagebreak\n" 02546 "\\begin{figure}[H]\n" 02547 "\\begin{center}\n" 02548 "\\leavevmode\n" 02549 "\\includegraphics[width=" << QMIN(width/2,maxWidth) 02550 << "pt]{" << baseName << "}\n" 02551 "\\end{center}\n" 02552 "\\end{figure}\n"; 02553 } 02554 02555 if (Config_getBool("DOT_CLEANUP")) thisDir.remove(baseName+".dot"); 02556 02557 QDir::setCurrent(oldDir); 02558 return baseName; 02559 } 02560 02561 bool DotCallGraph::isTrivial() const 02562 { 02563 return m_startNode->m_children==0; 02564 } 02565 02566 bool DotCallGraph::isTooBig() const 02567 { 02568 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES"); 02569 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0; 02570 return numNodes>=maxNodes; 02571 } 02572 02573 //------------------------------------------------------------- 02574 02575 DotDirDeps::DotDirDeps(DirDef *dir) : m_dir(dir) 02576 { 02577 } 02578 02579 DotDirDeps::~DotDirDeps() 02580 { 02581 } 02582 02583 QCString DotDirDeps::writeGraph(QTextStream &out, 02584 GraphOutputFormat format, 02585 const char *path, 02586 const char *relPath, 02587 bool generateImageMap) const 02588 { 02589 QDir d(path); 02590 // store the original directory 02591 if (!d.exists()) 02592 { 02593 err("Error: Output dir %s does not exist!\n",path); exit(1); 02594 } 02595 QCString oldDir = convertToQCString(QDir::currentDirPath()); 02596 // go to the html output directory (i.e. path) 02597 QDir::setCurrent(d.absPath()); 02598 QDir thisDir; 02599 02600 QCString baseName=m_dir->getOutputFileBase()+"_dep"; 02601 QCString mapName=baseName; 02602 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); 02603 02604 // todo: create check, update md5 checksum 02605 { 02606 QFile f(baseName+".dot"); 02607 if (!f.open(IO_WriteOnly)) 02608 { 02609 err("Cannot create file %s.dot for writing!\n",baseName.data()); 02610 } 02611 QTextStream t(&f); 02612 t.setEncoding(t.UnicodeUTF8); 02613 m_dir->writeDepGraph(t); 02614 f.close(); 02615 02616 if (format==BITMAP) 02617 { 02618 // run dot to create a bitmap image 02619 QCString dotArgs(maxCmdLine); 02620 QCString imgName=baseName+"."+imgExt; 02621 DotRunner dotRun(baseName+".dot"); 02622 dotRun.addJob(imgExt,imgName); 02623 if (generateImageMap) dotRun.addJob(MAP_CMD,baseName+".map"); 02624 if (!dotRun.run()) 02625 { 02626 QDir::setCurrent(oldDir); 02627 return baseName; 02628 } 02629 checkDotResult(imgName); 02630 } 02631 else if (format==EPS) 02632 { 02633 // run dot to create a .eps image 02634 DotRunner dotRun(baseName+".dot"); 02635 dotRun.addJob("ps",baseName+".eps"); 02636 if (!dotRun.run()) 02637 { 02638 QDir::setCurrent(oldDir); 02639 return baseName; 02640 } 02641 if (Config_getBool("USE_PDFLATEX")) 02642 { 02643 QCString epstopdfArgs(maxCmdLine); 02644 epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", 02645 baseName.data(),baseName.data()); 02646 if (portable_system("epstopdf",epstopdfArgs)!=0) 02647 { 02648 err("Error: Problems running epstopdf. Check your TeX installation!\n"); 02649 QDir::setCurrent(oldDir); 02650 return baseName; 02651 } 02652 } 02653 } 02654 } 02655 02656 if (format==BITMAP && generateImageMap) 02657 { 02658 out << "<p><center><img src=\"" << relPath << baseName << "." 02659 << imgExt << "\" border=\"0\" usemap=\"#" 02660 << mapName << "_map\" alt=\""; 02661 out << m_dir->displayName(); 02662 out << "\">"; 02663 out << "</center>" << endl; 02664 QString tmpstr; 02665 QTextOStream tmpout(&tmpstr); 02666 tmpout.setEncoding(tmpout.UnicodeUTF8); 02667 convertMapFile(tmpout,baseName+".map",relPath,TRUE); 02668 if (!tmpstr.isEmpty()) 02669 { 02670 out << "<map name=\"" << mapName << "_map\">" << endl; 02671 out << tmpstr; 02672 out << "</map>" << endl; 02673 } 02674 else 02675 { 02676 //printf("Map is empty!\n"); 02677 } 02678 //thisDir.remove(baseName+".map"); 02679 } 02680 else if (format==EPS) 02681 { 02682 int width,height; 02683 if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) 02684 { 02685 err("Error: Could not extract bounding box from .eps!\n"); 02686 QDir::setCurrent(oldDir); 02687 return baseName; 02688 } 02689 int maxWidth = 420; /* approx. page width in points */ 02690 02691 out << "\\nopagebreak\n" 02692 "\\begin{figure}[H]\n" 02693 "\\begin{center}\n" 02694 "\\leavevmode\n" 02695 "\\includegraphics[width=" << QMIN(width/2,maxWidth) 02696 << "pt]{" << baseName << "}\n" 02697 "\\end{center}\n" 02698 "\\end{figure}\n"; 02699 } 02700 02701 if (Config_getBool("DOT_CLEANUP")) thisDir.remove(baseName+".dot"); 02702 02703 QDir::setCurrent(oldDir); 02704 return baseName; 02705 } 02706 02707 bool DotDirDeps::isTrivial() const 02708 { 02709 return m_dir->depGraphIsTrivial(); 02710 } 02711 02712 //------------------------------------------------------------- 02713 02714 void generateGraphLegend(const char *path) 02715 { 02716 QFile dotFile((QCString)path+"/graph_legend.dot"); 02717 if (!dotFile.open(IO_WriteOnly)) 02718 { 02719 err("Could not open file %s for writing\n", 02720 convertToQCString(dotFile.name()).data()); 02721 return; 02722 } 02723 QTextStream dotText(&dotFile); 02724 writeGraphHeader(dotText); 02725 dotText << " Node9 [shape=\"box\",label=\"Inherited\",fontsize=10,height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",fillcolor=\"grey75\",style=\"filled\" fontcolor=\"black\"];\n"; 02726 dotText << " Node10 -> Node9 [dir=back,color=\"midnightblue\",fontsize=10,style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; 02727 dotText << " Node10 [shape=\"box\",label=\"PublicBase\",fontsize=10,height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPublicBase" << Doxygen::htmlFileExtension << "\"];\n"; 02728 dotText << " Node11 -> Node10 [dir=back,color=\"midnightblue\",fontsize=10,style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; 02729 dotText << " Node11 [shape=\"box\",label=\"Truncated\",fontsize=10,height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"red\",URL=\"$classTruncated" << Doxygen::htmlFileExtension << "\"];\n"; 02730 dotText << " Node13 -> Node9 [dir=back,color=\"darkgreen\",fontsize=10,style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; 02731 dotText << " Node13 [shape=\"box\",label=\"ProtectedBase\",fontsize=10,height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classProtectedBase" << Doxygen::htmlFileExtension << "\"];\n"; 02732 dotText << " Node14 -> Node9 [dir=back,color=\"firebrick4\",fontsize=10,style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; 02733 dotText << " Node14 [shape=\"box\",label=\"PrivateBase\",fontsize=10,height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPrivateBase" << Doxygen::htmlFileExtension << "\"];\n"; 02734 dotText << " Node15 -> Node9 [dir=back,color=\"midnightblue\",fontsize=10,style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; 02735 dotText << " Node15 [shape=\"box\",label=\"Undocumented\",fontsize=10,height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"grey75\"];\n"; 02736 dotText << " Node16 -> Node9 [dir=back,color=\"midnightblue\",fontsize=10,style=\"solid\",fontname=\"" << FONTNAME << "\"];\n"; 02737 dotText << " Node16 [shape=\"box\",label=\"Templ< int >\",fontsize=10,height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n"; 02738 dotText << " Node17 -> Node16 [dir=back,color=\"orange\",fontsize=10,style=\"dashed\",label=\"< int >\",fontname=\"" << FONTNAME << "\"];\n"; 02739 dotText << " Node17 [shape=\"box\",label=\"Templ< T >\",fontsize=10,height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n"; 02740 dotText << " Node18 -> Node9 [dir=back,color=\"darkorchid3\",fontsize=10,style=\"dashed\",label=\"m_usedClass\",fontname=\"" << FONTNAME << "\"];\n"; 02741 dotText << " Node18 [shape=\"box\",label=\"Used\",fontsize=10,height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classUsed" << Doxygen::htmlFileExtension << "\"];\n"; 02742 writeGraphFooter(dotText); 02743 dotFile.close(); 02744 02745 QDir d(path); 02746 // store the original directory 02747 if (!d.exists()) 02748 { 02749 err("Error: Output dir %s does not exist!\n",path); exit(1); 02750 } 02751 QCString oldDir = convertToQCString(QDir::currentDirPath()); 02752 // go to the html output directory (i.e. path) 02753 QDir::setCurrent(d.absPath()); 02754 02755 // run dot to generate the a bitmap image from the graph 02756 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); 02757 QCString imgName = "graph_legend."+imgExt; 02758 02759 DotRunner dotRun("graph_legend.dot"); 02760 dotRun.addJob(imgExt,imgName); 02761 if (!dotRun.run()) 02762 { 02763 QDir::setCurrent(oldDir); 02764 return; 02765 } 02766 checkDotResult(imgName); 02767 QDir::setCurrent(oldDir); 02768 } 02769 02770 void writeDotGraphFromFile(const char *inFile,const char *outDir, 02771 const char *outFile,GraphOutputFormat format) 02772 { 02773 QCString absOutFile = outDir; 02774 absOutFile+=portable_pathSeparator(); 02775 absOutFile+=outFile; 02776 02777 // chdir to the output dir, so dot can find the font file. 02778 QCString oldDir = convertToQCString(QDir::currentDirPath()); 02779 // go to the html output directory (i.e. path) 02780 QDir::setCurrent(outDir); 02781 //printf("Going to dir %s\n",QDir::currentDirPath().data()); 02782 02783 QCString env = portable_getenv("DOTFONTPATH"); 02784 if (env==".") // this path was set by doxygen, so dot can find the FreeSans.ttf font, 02785 // for user defined graphs we use the default search path built into dot, 02786 // unless the user has set the DOTFONTPATH as well. 02787 { 02788 // temporarily remove the DOTFONTPATH environment variable 02789 // so dot will use the built-in search path. 02790 portable_unsetenv("DOTFONTPATH"); 02791 } 02792 02793 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); 02794 QCString imgName = (QCString)outFile+"."+imgExt; 02795 02796 DotRunner dotRun(inFile); 02797 if (format==BITMAP) 02798 dotRun.addJob(imgExt,imgName); 02799 else // format==EPS 02800 dotRun.addJob("ps",QCString(outFile)+".eps"); 02801 if (!dotRun.run()) 02802 { 02803 QDir::setCurrent(oldDir); 02804 goto error; 02805 } 02806 // Added by Nils Strom 02807 if ( (format==EPS) && (Config_getBool("USE_PDFLATEX")) ) 02808 { 02809 QCString epstopdfArgs(maxCmdLine); 02810 epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", 02811 outFile,outFile); 02812 if (portable_system("epstopdf",epstopdfArgs)!=0) 02813 { 02814 err("Error: Problems running epstopdf. Check your TeX installation!\n"); 02815 } 02816 } 02817 02818 if (format==BITMAP) checkDotResult(imgName); 02819 02820 if (env==".") 02821 { 02822 // restore the DOTFONTPATH variable again 02823 portable_setenv("DOTFONTPATH",env); 02824 } 02825 02826 error: 02827 QDir::setCurrent(oldDir); 02828 } 02829 02830 02839 QString getDotImageMapFromFile(const QString& inFile, const QString& outDir, 02840 const QCString &relPath,const QString &context) 02841 { 02842 QString outFile = inFile + ".map"; 02843 02844 // chdir to the output dir, so dot can find the font file. 02845 QCString oldDir = convertToQCString(QDir::currentDirPath()); 02846 // go to the html output directory (i.e. path) 02847 QDir::setCurrent(outDir); 02848 //printf("Going to dir %s\n",QDir::currentDirPath().data()); 02849 02850 DotRunner dotRun(inFile); 02851 dotRun.addJob(MAP_CMD,outFile); 02852 if (!dotRun.run()) 02853 { 02854 QDir::setCurrent(oldDir); 02855 return ""; 02856 } 02857 02858 QString result; 02859 QTextOStream tmpout(&result); 02860 tmpout.setEncoding(tmpout.UnicodeUTF8); 02861 convertMapFile(tmpout, outFile, relPath ,TRUE, context); 02862 QDir().remove(outFile); 02863 // printf("result=%s\n",result.data()); 02864 02865 QDir::setCurrent(oldDir); 02866 return result; 02867 } 02868 // end MDG mods 02869 02870 //------------------------------------------------------------- 02871 02872 DotGroupCollaboration::DotGroupCollaboration(GroupDef* gd) 02873 { 02874 m_curNodeId = 0; 02875 QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase(); 02876 m_usedNodes = new QDict<DotNode>(1009); 02877 m_rootNode = new DotNode(m_curNodeId++, gd->groupTitle(), "", tmp_url, TRUE ); 02878 m_rootNode->markAsVisible(); 02879 m_usedNodes->insert(gd->name(), m_rootNode ); 02880 m_edges.setAutoDelete(TRUE); 02881 02882 m_diskName = gd->getOutputFileBase(); 02883 02884 buildGraph( gd ); 02885 } 02886 02887 DotGroupCollaboration::~DotGroupCollaboration() 02888 { 02889 delete m_usedNodes; 02890 } 02891 02892 void DotGroupCollaboration::buildGraph(GroupDef* gd) 02893 { 02894 QCString tmp_url; 02895 //=========================== 02896 // hierarchy. 02897 02898 // Write parents 02899 LockingPtr<GroupList> groups = gd->partOfGroups(); 02900 if ( groups!=0 ) 02901 { 02902 GroupListIterator gli(*groups); 02903 GroupDef *d; 02904 for (gli.toFirst();(d=gli.current());++gli) 02905 { 02906 DotNode* nnode = m_usedNodes->find(d->name()); 02907 if ( !nnode ) 02908 { // add node 02909 tmp_url = d->getReference()+"$"+d->getOutputFileBase(); 02910 QCString tooltip = d->briefDescriptionAsTooltip(); 02911 nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_url ); 02912 nnode->markAsVisible(); 02913 m_usedNodes->insert(d->name(), nnode ); 02914 } 02915 tmp_url = ""; 02916 addEdge( nnode, m_rootNode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url ); 02917 } 02918 } 02919 02920 // Add subgroups 02921 if ( gd->getSubGroups() && gd->getSubGroups()->count() ) 02922 { 02923 QListIterator<GroupDef> defli(*gd->getSubGroups()); 02924 GroupDef *def; 02925 for (;(def=defli.current());++defli) 02926 { 02927 DotNode* nnode = m_usedNodes->find(def->name()); 02928 if ( !nnode ) 02929 { // add node 02930 tmp_url = def->getReference()+"$"+def->getOutputFileBase(); 02931 QCString tooltip = def->briefDescriptionAsTooltip(); 02932 nnode = new DotNode(m_curNodeId++, def->groupTitle(), tooltip, tmp_url ); 02933 nnode->markAsVisible(); 02934 m_usedNodes->insert(def->name(), nnode ); 02935 } 02936 tmp_url = ""; 02937 addEdge( m_rootNode, nnode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url ); 02938 } 02939 } 02940 02941 //======================= 02942 // Write collaboration 02943 02944 // Add members 02945 addMemberList( gd->getMemberList(MemberList::allMembersList) ); 02946 02947 // Add classes 02948 if ( gd->getClasses() && gd->getClasses()->count() ) 02949 { 02950 ClassSDict::Iterator defli(*gd->getClasses()); 02951 ClassDef *def; 02952 for (;(def=defli.current());++defli) 02953 { 02954 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; 02955 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tclass ); 02956 } 02957 } 02958 02959 // Add namespaces 02960 if ( gd->getNamespaces() && gd->getNamespaces()->count() ) 02961 { 02962 NamespaceSDict::Iterator defli(*gd->getNamespaces()); 02963 NamespaceDef *def; 02964 for (;(def=defli.current());++defli) 02965 { 02966 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; 02967 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tnamespace ); 02968 } 02969 } 02970 02971 // Add files 02972 if ( gd->getFiles() && gd->getFiles()->count() ) 02973 { 02974 QListIterator<FileDef> defli(*gd->getFiles()); 02975 FileDef *def; 02976 for (;(def=defli.current());++defli) 02977 { 02978 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; 02979 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tfile ); 02980 } 02981 } 02982 02983 // Add pages 02984 if ( gd->getPages() && gd->getPages()->count() ) 02985 { 02986 PageSDict::Iterator defli(*gd->getPages()); 02987 PageDef *def; 02988 for (;(def=defli.current());++defli) 02989 { 02990 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; 02991 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tpages ); 02992 } 02993 } 02994 02995 // Add directories 02996 if ( gd->getDirs() && gd->getDirs()->count() ) 02997 { 02998 QListIterator<DirDef> defli(*gd->getDirs()); 02999 DirDef *def; 03000 for (;(def=defli.current());++defli) 03001 { 03002 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension; 03003 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tdir ); 03004 } 03005 } 03006 } 03007 03008 void DotGroupCollaboration::addMemberList( MemberList* ml ) 03009 { 03010 if ( !( ml && ml->count()) ) return; 03011 MemberListIterator defli(*ml); 03012 MemberDef *def; 03013 for (;(def=defli.current());++defli) 03014 { 03015 QCString tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension 03016 +"#"+def->anchor(); 03017 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tmember ); 03018 } 03019 } 03020 03021 DotGroupCollaboration::Edge* DotGroupCollaboration::addEdge( 03022 DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType, 03023 const QCString& _label, const QCString& _url ) 03024 { 03025 // search a existing link. 03026 QListIterator<Edge> lli(m_edges); 03027 Edge* newEdge = 0; 03028 for ( lli.toFirst(); (newEdge=lli.current()); ++lli) 03029 { 03030 if ( newEdge->pNStart==_pNStart && 03031 newEdge->pNEnd==_pNEnd && 03032 newEdge->eType==_eType 03033 ) 03034 { // edge already found 03035 break; 03036 } 03037 } 03038 if ( newEdge==0 ) // new link 03039 { 03040 newEdge = new Edge(_pNStart,_pNEnd,_eType); 03041 m_edges.append( newEdge ); 03042 } 03043 03044 if (!_label.isEmpty()) 03045 { 03046 newEdge->links.append(new Link(_label,_url)); 03047 } 03048 03049 return newEdge; 03050 } 03051 03052 void DotGroupCollaboration::addCollaborationMember( 03053 Definition* def, QCString& url, EdgeType eType ) 03054 { 03055 // Create group nodes 03056 if ( !def->partOfGroups() ) 03057 return; 03058 GroupListIterator gli(*def->partOfGroups()); 03059 GroupDef *d; 03060 QCString tmp_str; 03061 for (;(d=gli.current());++gli) 03062 { 03063 DotNode* nnode = m_usedNodes->find(d->name()); 03064 if ( nnode != m_rootNode ) 03065 { 03066 if ( nnode==0 ) 03067 { // add node 03068 tmp_str = d->getReference()+"$"+d->getOutputFileBase(); 03069 QCString tooltip = d->briefDescriptionAsTooltip(); 03070 nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_str ); 03071 nnode->markAsVisible(); 03072 m_usedNodes->insert(d->name(), nnode ); 03073 } 03074 tmp_str = def->qualifiedName(); 03075 addEdge( m_rootNode, nnode, eType, tmp_str, url ); 03076 } 03077 } 03078 } 03079 03080 03081 QCString DotGroupCollaboration::writeGraph( QTextStream &t, GraphOutputFormat format, 03082 const char *path, const char *relPath, 03083 bool writeImageMap) const 03084 { 03085 QDir d(path); 03086 // store the original directory 03087 if (!d.exists()) 03088 { 03089 err("Error: Output dir %s does not exist!\n",path); 03090 exit(1); 03091 } 03092 QCString oldDir = convertToQCString(QDir::currentDirPath()); 03093 // go to the output directory (i.e. path) 03094 QDir::setCurrent(d.absPath()); 03095 QDir thisDir; 03096 QCString baseName = m_diskName; 03097 03098 QFile dotfile(baseName+".dot"); 03099 if (dotfile.open(IO_WriteOnly)) 03100 { 03101 QTextStream tdot(&dotfile); 03102 tdot.setEncoding(tdot.UnicodeUTF8); 03103 writeGraphHeader(tdot); 03104 03105 // clean write flags 03106 QDictIterator<DotNode> dni(*m_usedNodes); 03107 DotNode *pn; 03108 for (dni.toFirst();(pn=dni.current());++dni) 03109 pn->clearWriteFlag(); 03110 03111 // write other nodes. 03112 for (dni.toFirst();(pn=dni.current());++dni) 03113 { 03114 pn->write(tdot,DotNode::Inheritance,format,TRUE,FALSE,FALSE,FALSE); 03115 } 03116 03117 // write edges 03118 QListIterator<Edge> eli(m_edges); 03119 Edge* edge; 03120 for (eli.toFirst();(edge=eli.current());++eli) 03121 { 03122 edge->write( tdot ); 03123 } 03124 03125 writeGraphFooter(tdot); 03126 dotfile.close(); 03127 } 03128 03129 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); 03130 if (format==BITMAP) // run dot to create a bitmap image 03131 { 03132 QCString dotArgs(maxCmdLine); 03133 QCString imgName = baseName+"."+imgExt; 03134 QCString mapName=baseName+".map"; 03135 03136 DotRunner dotRun(baseName+".dot"); 03137 dotRun.addJob(imgExt,imgName); 03138 if (writeImageMap) dotRun.addJob(MAP_CMD,mapName); 03139 if (!dotRun.run()) 03140 { 03141 QDir::setCurrent(oldDir); 03142 return baseName; 03143 } 03144 03145 if (writeImageMap) 03146 { 03147 QCString mapLabel = convertNameToFile(baseName); 03148 t << "<center><table><tr><td><img src=\"" << relPath << imgName 03149 << "\" border=\"0\" alt=\"\" usemap=\"#" 03150 << mapLabel << "_map\">" << endl; 03151 t << "<map name=\"" << mapLabel << "_map\">" << endl; 03152 convertMapFile(t,mapName,relPath); 03153 t << "</map></td></tr></table></center>" << endl; 03154 thisDir.remove(mapName); 03155 } 03156 } 03157 else if (format==EPS) 03158 { 03159 DotRunner dotRun(baseName+".dot"); 03160 dotRun.addJob("ps",baseName+".eps"); 03161 if (!dotRun.run()) 03162 { 03163 QDir::setCurrent(oldDir); 03164 return baseName; 03165 } 03166 03167 if (Config_getBool("USE_PDFLATEX")) 03168 { 03169 QCString epstopdfArgs(maxCmdLine); 03170 epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"", 03171 baseName.data(),baseName.data()); 03172 if (portable_system("epstopdf",epstopdfArgs)!=0) 03173 { 03174 err("Error: Problems running epstopdf. Check your TeX installation!\n"); 03175 QDir::setCurrent(oldDir); 03176 return baseName; 03177 } 03178 } 03179 int width,height; 03180 if (!readBoundingBoxEPS(baseName+".eps",&width,&height)) 03181 { 03182 err("Error: Could not extract bounding box from .eps!\n"); 03183 QDir::setCurrent(oldDir); 03184 return baseName; 03185 } 03186 int maxWidth = 420; /* approx. page width in points */ 03187 t << "\\nopagebreak\n" 03188 "\\begin{figure}[H]\n" 03189 "\\begin{center}\n" 03190 "\\leavevmode\n" 03191 "\\includegraphics[width=" << QMIN(width/2,maxWidth) 03192 << "pt]{" << baseName << "}\n" 03193 "\\end{center}\n" 03194 "\\end{figure}\n"; 03195 } 03196 if (Config_getBool("DOT_CLEANUP")) 03197 { 03198 thisDir.remove(baseName+".dot"); 03199 } 03200 03201 QDir::setCurrent(oldDir); 03202 03203 return baseName; 03204 } 03205 03206 void DotGroupCollaboration::Edge::write( QTextStream &t ) const 03207 { 03208 const char* linkTypeColor[] = { 03209 "darkorchid3" 03210 ,"orange" 03211 ,"blueviolet" 03212 ,"darkgreen" 03213 ,"firebrick4" 03214 ,"grey75" 03215 ,"midnightblue" 03216 }; 03217 QCString arrowStyle = "dir=\"none\", style=\"dashed\""; 03218 t << " Node" << pNStart->number(); 03219 t << "->"; 03220 t << "Node" << pNEnd->number(); 03221 03222 t << " [shape=plaintext"; 03223 if (links.count()>0) // there are links 03224 { 03225 t << ", "; 03226 // HTML-like edge labels crash on my Mac with Graphviz 2.0! and 03227 // are not supported by older version of dot. 03228 // 03229 //t << label=<<TABLE BORDER=\"0\" CELLBORDER=\"0\">"; 03230 //QListIterator<Link> lli(links); 03231 //Link *link; 03232 //for( lli.toFirst(); (link=lli.current()); ++lli) 03233 //{ 03234 // t << "<TR><TD"; 03235 // if ( !link->url.isEmpty() ) 03236 // t << " HREF=\"" << link->url << "\""; 03237 // t << ">" << link->label << "</TD></TR>"; 03238 //} 03239 //t << "</TABLE>>"; 03240 03241 t << "label=\""; 03242 QListIterator<Link> lli(links); 03243 Link *link; 03244 bool first=TRUE; 03245 int count=0; 03246 const int maxLabels = 10; 03247 for( lli.toFirst(); (link=lli.current()) && count<maxLabels; ++lli,++count) 03248 { 03249 if (first) first=FALSE; else t << "\\n"; 03250 t << convertLabel(link->label); 03251 } 03252 if (count==maxLabels) t << "\\n..."; 03253 t << "\""; 03254 03255 } 03256 switch( eType ) 03257 { 03258 case thierarchy : 03259 arrowStyle = "dir=\"back\", style=\"solid\""; 03260 default : 03261 t << ", color=\"" << linkTypeColor[(int)eType] << "\""; 03262 break; 03263 } 03264 t << ", " << arrowStyle; 03265 t << "];" << endl; 03266 } 03267 03268 bool DotGroupCollaboration::isTrivial() const 03269 { 03270 return m_usedNodes->count() <= 1; 03271 } 03272 03273 void DotGroupCollaboration::writeGraphHeader(QTextStream &t) const 03274 { 03275 t << "digraph structs" << endl; 03276 t << "{" << endl; 03277 if (Config_getBool("DOT_TRANSPARENT")) 03278 { 03279 t << " bgcolor=\"transparent\";" << endl; 03280 } 03281 t << " edge [fontname=\"" << FONTNAME << "\",fontsize=8," 03282 "labelfontname=\"" << FONTNAME << "\",labelfontsize=8];\n"; 03283 t << " node [fontname=\"" << FONTNAME << "\",fontsize=10,shape=record];\n"; 03284 t << " rankdir=LR;\n"; 03285 } 03286 03287 void writeDotDirDepGraph(QTextStream &t,DirDef *dd) 03288 { 03289 t << "digraph G {\n"; 03290 if (Config_getBool("DOT_TRANSPARENT")) 03291 { 03292 t << " bgcolor=transparent;\n"; 03293 } 03294 t << " compound=true\n"; 03295 t << " node [ fontsize=10, fontname=\"" << FONTNAME << "\"];\n"; 03296 t << " edge [ labelfontsize=9, labelfontname=\"" << FONTNAME << "\"];\n"; 03297 03298 QDict<DirDef> dirsInGraph(257); 03299 03300 dirsInGraph.insert(dd->getOutputFileBase(),dd); 03301 if (dd->parent()) 03302 { 03303 t << " subgraph cluster" << dd->parent()->getOutputFileBase() << " {\n"; 03304 t << " graph [ bgcolor=\"#ddddee\", pencolor=\"black\", label=\"" 03305 << dd->parent()->shortName() 03306 << "\" fontname=\"" << FONTNAME << "\", fontsize=10, URL=\""; 03307 t << dd->parent()->getOutputFileBase() << Doxygen::htmlFileExtension; 03308 t << "\"]\n"; 03309 } 03310 if (dd->isCluster()) 03311 { 03312 t << " subgraph cluster" << dd->getOutputFileBase() << " {\n"; 03313 t << " graph [ bgcolor=\"#eeeeff\", pencolor=\"black\", label=\"\"" 03314 << " URL=\"" << dd->getOutputFileBase() << Doxygen::htmlFileExtension 03315 << "\"];\n"; 03316 t << " " << dd->getOutputFileBase() << " [shape=plaintext label=\"" 03317 << dd->shortName() << "\"];\n"; 03318 03319 // add nodes for sub directories 03320 QListIterator<DirDef> sdi(dd->subDirs()); 03321 DirDef *sdir; 03322 for (sdi.toFirst();(sdir=sdi.current());++sdi) 03323 { 03324 t << " " << sdir->getOutputFileBase() << " [shape=box label=\"" 03325 << sdir->shortName() << "\""; 03326 if (sdir->isCluster()) 03327 { 03328 t << " color=\"red\""; 03329 } 03330 else 03331 { 03332 t << " color=\"black\""; 03333 } 03334 t << " fillcolor=\"white\" style=\"filled\""; 03335 t << " URL=\"" << sdir->getOutputFileBase() 03336 << Doxygen::htmlFileExtension << "\""; 03337 t << "];\n"; 03338 dirsInGraph.insert(sdir->getOutputFileBase(),sdir); 03339 } 03340 t << " }\n"; 03341 } 03342 else 03343 { 03344 t << " " << dd->getOutputFileBase() << " [shape=box, label=\"" 03345 << dd->shortName() << "\", style=\"filled\", fillcolor=\"#eeeeff\"," 03346 << " pencolor=\"black\", URL=\"" << dd->getOutputFileBase() 03347 << Doxygen::htmlFileExtension << "\"];\n"; 03348 } 03349 if (dd->parent()) 03350 { 03351 t << " }\n"; 03352 } 03353 03354 // add nodes for other used directories 03355 QDictIterator<UsedDir> udi(*dd->usedDirs()); 03356 UsedDir *udir; 03357 //printf("*** For dir %s\n",shortName().data()); 03358 for (udi.toFirst();(udir=udi.current());++udi) 03359 // for each used dir (=directly used or a parent of a directly used dir) 03360 { 03361 const DirDef *usedDir=udir->dir(); 03362 DirDef *dir=dd; 03363 while (dir) 03364 { 03365 //printf("*** check relation %s->%s same_parent=%d !%s->isParentOf(%s)=%d\n", 03366 // dir->shortName().data(),usedDir->shortName().data(), 03367 // dir->parent()==usedDir->parent(), 03368 // usedDir->shortName().data(), 03369 // shortName().data(), 03370 // !usedDir->isParentOf(this) 03371 // ); 03372 if (dir!=usedDir && dir->parent()==usedDir->parent() && 03373 !usedDir->isParentOf(dd)) 03374 // include if both have the same parent (or no parent) 03375 { 03376 t << " " << usedDir->getOutputFileBase() << " [shape=box label=\"" 03377 << usedDir->shortName() << "\""; 03378 if (usedDir->isCluster()) 03379 { 03380 if (!Config_getBool("DOT_TRANSPARENT")) 03381 { 03382 t << " fillcolor=\"white\" style=\"filled\""; 03383 } 03384 t << " color=\"red\""; 03385 } 03386 t << " URL=\"" << usedDir->getOutputFileBase() 03387 << Doxygen::htmlFileExtension << "\"];\n"; 03388 dirsInGraph.insert(usedDir->getOutputFileBase(),usedDir); 03389 break; 03390 } 03391 dir=dir->parent(); 03392 } 03393 } 03394 03395 // add relations between all selected directories 03396 DirDef *dir; 03397 QDictIterator<DirDef> di(dirsInGraph); 03398 for (di.toFirst();(dir=di.current());++di) // foreach dir in the graph 03399 { 03400 QDictIterator<UsedDir> udi(*dir->usedDirs()); 03401 UsedDir *udir; 03402 for (udi.toFirst();(udir=udi.current());++udi) // foreach used dir 03403 { 03404 const DirDef *usedDir=udir->dir(); 03405 if ((dir!=dd || !udir->inherited()) && // only show direct dependendies for this dir 03406 (usedDir!=dd || !udir->inherited()) && // only show direct dependendies for this dir 03407 !usedDir->isParentOf(dir) && // don't point to own parent 03408 dirsInGraph.find(usedDir->getOutputFileBase())) // only point to nodes that are in the graph 03409 { 03410 QCString relationName; 03411 relationName.sprintf("dir_%06d_%06d",dir->dirCount(),usedDir->dirCount()); 03412 if (Doxygen::dirRelations.find(relationName)==0) 03413 { 03414 // new relation 03415 Doxygen::dirRelations.append(relationName, 03416 new DirRelation(relationName,dir,udir)); 03417 } 03418 int nrefs = udir->filePairs().count(); 03419 t << " " << dir->getOutputFileBase() << "->" 03420 << usedDir->getOutputFileBase(); 03421 t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5"; 03422 t << " headhref=\"" << relationName << Doxygen::htmlFileExtension 03423 << "\"];\n"; 03424 } 03425 } 03426 } 03427 03428 t << "}\n"; 03429 }