dot.cpp

Go to the documentation of this file.
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 }



Generated on Mon Mar 31 10:58:36 2008 by  doxygen 1.5.1