1 /*****************************************************************************
6 * Copyright (C) 1997-2015 by Dimitri van Heesch.
8 * Permission to use, copy, modify, and distribute this software and its
9 * documentation under the terms of the GNU General Public License is hereby
10 * granted. No representations are made about the suitability of this software
11 * for any purpose. It is provided "as is" without express or implied warranty.
12 * See the GNU General Public License for more details.
14 * Documents produced by Doxygen are derivative works derived from the
15 * input used in their production; they are not affected by this license.
26 #include <qwaitcondition.h>
36 #include "docparser.h"
41 #include "vhdldocgen.h"
42 #include "ftextstream.h"
44 #include "memberlist.h"
46 #include "classlist.h"
48 #include "namespacedef.h"
49 #include "memberdef.h"
50 #include "membergroup.h"
52 #define MAP_CMD "cmapx"
54 //#define FONTNAME "Helvetica"
55 #define FONTNAME getDotFontName()
56 #define FONTSIZE getDotFontSize()
58 //--------------------------------------------------------------------
60 static const char svgZoomHeader[] =
61 "<svg id=\"main\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:space=\"preserve\" onload=\"init(evt)\">\n"
62 "<style type=\"text/css\"><![CDATA[\n"
63 ".edge:hover path { stroke: red; }\n"
64 ".edge:hover polygon { stroke: red; fill: red; }\n"
66 "<script type=\"text/javascript\"><![CDATA[\n"
67 "var edges = document.getElementsByTagName('g');\n"
68 "if (edges && edges.length) {\n"
69 " for (var i=0;i<edges.length;i++) {\n"
70 " if (edges[i].id.substr(0,4)=='edge') {\n"
71 " edges[i].setAttribute('class','edge');\n"
77 " <circle id=\"rim\" cx=\"0\" cy=\"0\" r=\"7\"/>\n"
78 " <circle id=\"rim2\" cx=\"0\" cy=\"0\" r=\"3.5\"/>\n"
79 " <g id=\"zoomPlus\">\n"
80 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
81 " <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomplus.mouseover\" end=\"zoomplus.mouseout\"/>\n"
83 " <path d=\"M-4,0h8M0,-4v8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
85 " <g id=\"zoomMin\">\n"
86 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
87 " <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomminus.mouseover\" end=\"zoomminus.mouseout\"/>\n"
89 " <path d=\"M-4,0h8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
91 " <g id=\"dirArrow\">\n"
92 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
94 " <g id=\"resetDef\">\n"
95 " <use xlink:href=\"#rim2\" fill=\"#404040\">\n"
96 " <set attributeName=\"fill\" to=\"#808080\" begin=\"reset.mouseover\" end=\"reset.mouseout\"/>\n"
101 "<script type=\"text/javascript\">\n"
104 static const char svgZoomFooter[] =
106 " <g id=\"navigator\" transform=\"translate(0 0)\" fill=\"#404254\">\n"
107 " <rect fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\".5\" x=\"0\" y=\"0\" width=\"60\" height=\"60\"/>\n"
109 " <use id=\"zoomplus\" xlink:href=\"#zoomPlus\" x=\"17\" y=\"9\" onmousedown=\"handleZoom(evt,'in')\"/>\n"
111 " <use id=\"zoomminus\" xlink:href=\"#zoomMin\" x=\"42\" y=\"9\" onmousedown=\"handleZoom(evt,'out')\"/>\n"
113 " <use id=\"reset\" xlink:href=\"#resetDef\" x=\"30\" y=\"36\" onmousedown=\"handleReset()\"/>\n"
115 " <g id=\"arrowUp\" xlink:href=\"#dirArrow\" transform=\"translate(30 24)\" onmousedown=\"handlePan(0,-1)\">\n"
116 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
117 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowUp.mouseover\" end=\"arrowUp.mouseout\"/>\n"
119 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
122 " <g id=\"arrowRight\" xlink:href=\"#dirArrow\" transform=\"rotate(90) translate(36 -43)\" onmousedown=\"handlePan(1,0)\">\n"
123 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
124 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowRight.mouseover\" end=\"arrowRight.mouseout\"/>\n"
126 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
129 " <g id=\"arrowDown\" xlink:href=\"#dirArrow\" transform=\"rotate(180) translate(-30 -48)\" onmousedown=\"handlePan(0,1)\">\n"
130 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
131 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowDown.mouseover\" end=\"arrowDown.mouseout\"/>\n"
133 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
136 " <g id=\"arrowLeft\" xlink:href=\"#dirArrow\" transform=\"rotate(270) translate(-36 17)\" onmousedown=\"handlePan(-1,0)\">\n"
137 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
138 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowLeft.mouseover\" end=\"arrowLeft.mouseout\"/>\n"
140 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
143 // link to original SVG
144 " <svg viewBox=\"0 0 15 15\" width=\"100%\" height=\"30px\" preserveAspectRatio=\"xMaxYMin meet\">\n"
145 " <g id=\"arrow_out\" transform=\"scale(0.3 0.3)\">\n"
146 " <a xlink:href=\"$orgname\" target=\"_base\">\n"
147 " <rect id=\"button\" ry=\"5\" rx=\"5\" y=\"6\" x=\"6\" height=\"38\" width=\"38\"\n"
148 " fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\"1.0\"/>\n"
149 " <path id=\"arrow\"\n"
150 " d=\"M 11.500037,31.436501 C 11.940474,20.09759 22.043105,11.32322 32.158766,21.979434 L 37.068811,17.246167 C 37.068811,17.246167 37.088388,32 37.088388,32 L 22.160133,31.978069 C 22.160133,31.978069 26.997745,27.140456 26.997745,27.140456 C 18.528582,18.264221 13.291696,25.230495 11.500037,31.436501 z\"\n"
151 " style=\"fill:#404040;\"/>\n"
158 //--------------------------------------------------------------------
160 /*! mapping from protection levels to color names */
161 static const char *normalEdgeColorMap[] =
163 "midnightblue", // Public
164 "darkgreen", // Protected
165 "firebrick4", // Private
166 "darkorchid3", // "use" relation
167 "grey75", // Undocumented
168 "orange", // template relation
169 "orange" // type constraint
172 static const char *normalArrowStyleMap[] =
175 "empty", // Protected
177 "open", // "use" relation
179 0 // template relation
182 static const char *normalEdgeStyleMap[] =
184 "solid", // inheritance
188 static const char *umlEdgeColorMap[] =
190 "midnightblue", // Public
191 "darkgreen", // Protected
192 "firebrick4", // Private
193 "grey25", // "use" relation
194 "grey75", // Undocumented
195 "orange", // template relation
196 "orange" // type constraint
199 static const char *umlArrowStyleMap[] =
202 "onormal", // Protected
203 "onormal", // Private
204 "odiamond", // "use" relation
206 0 // template relation
209 static const char *umlEdgeStyleMap[] =
211 "solid", // inheritance
215 /** Helper struct holding the properties of a edge in a dot graph. */
216 struct EdgeProperties
218 const char * const *edgeColorMap;
219 const char * const *arrowStyleMap;
220 const char * const *edgeStyleMap;
223 static EdgeProperties normalEdgeProps =
225 normalEdgeColorMap, normalArrowStyleMap, normalEdgeStyleMap
228 static EdgeProperties umlEdgeProps =
230 umlEdgeColorMap, umlArrowStyleMap, umlEdgeStyleMap
234 static QCString getDotFontName()
236 static QCString dotFontName = Config_getString(DOT_FONTNAME);
237 if (dotFontName.isEmpty())
239 //dotFontName="FreeSans.ttf";
240 dotFontName="Helvetica";
245 static int getDotFontSize()
247 static int dotFontSize = Config_getInt(DOT_FONTSIZE);
248 if (dotFontSize<4) dotFontSize=4;
252 static void writeGraphHeader(FTextStream &t,const QCString &title=QCString())
254 static bool interactiveSVG = Config_getBool(INTERACTIVE_SVG);
258 t << "\"Dot Graph\"";
262 t << "\"" << convertToXML(title) << "\"";
264 t << endl << "{" << endl;
265 if (interactiveSVG) // insert a comment to force regeneration when this
268 t << " // INTERACTIVE_SVG=YES\n";
270 t << " // LATEX_PDF_SIZE\n"; // write placeholder for LaTeX PDF bounding box size repacement
271 if (Config_getBool(DOT_TRANSPARENT))
273 t << " bgcolor=\"transparent\";" << endl;
275 t << " edge [fontname=\"" << FONTNAME << "\","
276 "fontsize=\"" << FONTSIZE << "\","
277 "labelfontname=\"" << FONTNAME << "\","
278 "labelfontsize=\"" << FONTSIZE << "\"];\n";
279 t << " node [fontname=\"" << FONTNAME << "\","
280 "fontsize=\"" << FONTSIZE << "\",shape=record];\n";
283 static void writeGraphFooter(FTextStream &t)
288 static QCString replaceRef(const QCString &buf,const QCString relPath,
289 bool urlOnly,const QCString &context,const QCString &target=QCString())
291 // search for href="...", store ... part in link
292 QCString href = "href";
293 //bool isXLink=FALSE;
295 int indexS = buf.find("href=\""), indexE;
296 bool setTarget = FALSE;
297 if (indexS>5 && buf.find("xlink:href=\"")!=-1) // XLink href (for SVG)
301 href.prepend("xlink:");
304 if (indexS>=0 && (indexE=buf.find('"',indexS+len))!=-1)
306 QCString link = buf.mid(indexS+len,indexE-indexS-len);
308 if (urlOnly) // for user defined dot graphs
310 if (link.left(5)=="\\ref " || link.left(5)=="@ref ") // \ref url
313 // fake ref node to resolve the url
314 DocRef *df = new DocRef( (DocNode*) 0, link.mid(5), context );
315 result+=externalRef(relPath,df->ref(),TRUE);
316 if (!df->file().isEmpty())
317 result += df->file().data() + Doxygen::htmlFileExtension;
318 if (!df->anchor().isEmpty())
319 result += "#" + df->anchor();
325 result = href+"=\"" + link + "\"";
328 else // ref$url (external ref via tag file), or $url (local ref)
330 int marker = link.find('$');
333 QCString ref = link.left(marker);
334 QCString url = link.mid(marker+1);
337 result = externalLinkTarget();
338 if (result != "") setTarget = TRUE;
339 result += externalRef(relPath,ref,FALSE);
342 result+=externalRef(relPath,ref,TRUE);
345 else // should not happen, but handle properly anyway
347 result = href+"=\"" + link + "\"";
350 if (!target.isEmpty() && !setTarget)
352 result+=" target=\""+target+"\"";
354 QCString leftPart = buf.left(indexS);
355 QCString rightPart = buf.mid(indexE+1);
356 return leftPart + result + rightPart;
364 /*! converts the rectangles in a client site image map into a stream
365 * \param t the stream to which the result is written.
366 * \param mapName the name of the map file.
367 * \param relPath the relative path to the root of the output directory
368 * (used in case CREATE_SUBDIRS is enabled).
369 * \param urlOnly if FALSE the url field in the map contains an external
370 * references followed by a $ and then the URL.
371 * \param context the context (file, class, or namespace) in which the
373 * \returns TRUE if successful.
375 static bool convertMapFile(FTextStream &t,const char *mapName,
376 const QCString relPath, bool urlOnly=FALSE,
377 const QCString &context=QCString())
380 static QRegExp re("id=\"node[0-9]*\"");
381 if (!f.open(IO_ReadOnly))
383 err("problems opening map file %s for inclusion in the docs!\n"
384 "If you installed Graphviz/dot after a previous failing run, \n"
385 "try deleting the output directory and rerun doxygen.\n",mapName);
388 const int maxLineLen=10240;
389 while (!f.atEnd()) // foreach line
391 QCString buf(maxLineLen);
392 int numBytes = f.readLine(buf.rawData(),maxLineLen);
395 buf.resize(numBytes+1);
397 if (buf.left(5)=="<area")
399 t << replaceRef(buf,relPath,urlOnly,context).replace(re,"");
406 static QCString g_dotFontPath;
408 static void setDotFontPath(const char *path)
410 ASSERT(g_dotFontPath.isEmpty());
411 g_dotFontPath = portable_getenv("DOTFONTPATH");
412 QCString newFontPath = Config_getString(DOT_FONTPATH);
413 QCString spath = path;
414 if (!newFontPath.isEmpty() && !spath.isEmpty())
416 newFontPath.prepend(spath+portable_pathListSeparator());
418 else if (newFontPath.isEmpty() && !spath.isEmpty())
424 portable_unsetenv("DOTFONTPATH");
427 portable_setenv("DOTFONTPATH",newFontPath);
430 static void unsetDotFontPath()
432 if (g_dotFontPath.isEmpty())
434 portable_unsetenv("DOTFONTPATH");
438 portable_setenv("DOTFONTPATH",g_dotFontPath);
443 static bool resetPDFSize(const int width,const int height, const char *base)
445 QString tmpName = QString::fromUtf8(QCString(base)+".tmp");
446 QString patchFile = QString::fromUtf8(QCString(base)+".dot");
447 if (!QDir::current().rename(patchFile,tmpName))
449 err("Failed to rename file %s to %s!\n",patchFile.data(),tmpName.data());
454 if (!fi.open(IO_ReadOnly))
456 err("problem opening file %s for patching!\n",tmpName.data());
457 QDir::current().rename(tmpName,patchFile);
460 if (!fo.open(IO_WriteOnly))
462 err("problem opening file %s for patching!\n",patchFile.data());
463 QDir::current().rename(tmpName,patchFile);
468 const int maxLineLen=100*1024;
469 while (!fi.atEnd()) // foreach line
471 QCString line(maxLineLen);
472 int numBytes = fi.readLine(line.rawData(),maxLineLen);
477 line.resize(numBytes+1);
478 if (line.find("LATEX_PDF_SIZE") != -1)
480 double scale = (width > height ? width : height)/double(MAX_LATEX_GRAPH_INCH);
481 t << " size=\""<<width/scale << "," <<height/scale <<"\";\n";
488 // remove temporary file
489 QDir::current().remove(tmpName);
492 static bool readBoundingBox(const char *fileName,int *width,int *height,bool isEps)
494 QCString bb = isEps ? QCString("%%PageBoundingBox:") : QCString("/MediaBox [");
496 if (!f.open(IO_ReadOnly|IO_Raw))
498 //printf("readBoundingBox: could not open %s\n",fileName);
501 const int maxLineLen=1024;
502 char buf[maxLineLen];
505 int numBytes = f.readLine(buf,maxLineLen-1); // read line
509 const char *p = strstr(buf,bb);
510 if (p) // found PageBoundingBox or /MediaBox string
513 if (sscanf(p+bb.length(),"%d %d %d %d",&x,&y,width,height)!=4)
515 //printf("readBoundingBox sscanf fail\n");
523 //printf("Read error %d!\n",numBytes);
527 err("Failed to extract bounding box from generated diagram file %s\n",fileName);
531 static bool writeVecGfxFigure(FTextStream &out,const QCString &baseName,
532 const QCString &figureName)
534 int width=400,height=550;
535 static bool usePdfLatex = Config_getBool(USE_PDFLATEX);
538 if (!readBoundingBox(figureName+".pdf",&width,&height,FALSE))
540 //printf("writeVecGfxFigure()=0\n");
546 if (!readBoundingBox(figureName+".eps",&width,&height,TRUE))
548 //printf("writeVecGfxFigure()=0\n");
552 //printf("Got PDF/EPS size %d,%d\n",width,height);
553 int maxWidth = 350; /* approx. page width in points, excl. margins */
554 int maxHeight = 550; /* approx. page height in points, excl. margins */
555 out << "\\nopagebreak\n"
556 "\\begin{figure}[H]\n"
559 if (width>maxWidth || height>maxHeight) // figure too big for page
561 // c*width/maxWidth > c*height/maxHeight, where c=maxWidth*maxHeight>0
562 if (width*maxHeight>height*maxWidth)
564 out << "\\includegraphics[width=" << maxWidth << "pt]";
568 out << "\\includegraphics[height=" << maxHeight << "pt]";
573 out << "\\includegraphics[width=" << width << "pt]";
576 out << "{" << baseName << "}\n"
580 //printf("writeVecGfxFigure()=1\n");
584 // extract size from a dot generated SVG file
585 static bool readSVGSize(const QCString &fileName,int *width,int *height)
589 if (!f.open(IO_ReadOnly))
593 const int maxLineLen=4096;
594 char buf[maxLineLen];
595 while (!f.atEnd() && !found)
597 int numBytes = f.readLine(buf,maxLineLen-1); // read line
601 if (qstrncmp(buf,"<!--zoomable ",13)==0)
605 sscanf(buf,"<!--zoomable %d",height);
606 //printf("Found zoomable for %s!\n",fileName.data());
609 else if (sscanf(buf,"<svg width=\"%dpt\" height=\"%dpt\"",width,height)==2)
611 //printf("Found fixed size %dx%d for %s!\n",*width,*height,fileName.data());
617 //printf("Read error %d!\n",numBytes);
624 static void writeSVGNotSupported(FTextStream &out)
626 out << "<p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p>";
629 // check if a reference to a SVG figure can be written and does so if possible.
630 // return FALSE if not possible (for instance because the SVG file is not yet generated).
631 static bool writeSVGFigureLink(FTextStream &out,const QCString &relPath,
632 const QCString &baseName,const QCString &absImgName)
634 int width=600,height=600;
635 if (!readSVGSize(absImgName,&width,&height))
644 height+=300; // add some extra space for zooming
645 if (height>600) height=600; // clip to maximum height of 600 pixels
646 out << "<div class=\"zoom\">";
647 //out << "<object type=\"image/svg+xml\" data=\""
648 //out << "<embed type=\"image/svg+xml\" src=\""
649 out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
650 << relPath << baseName << ".svg\" width=\"100%\" height=\"" << height << "\">";
654 //out << "<object type=\"image/svg+xml\" data=\""
655 //out << "<embed type=\"image/svg+xml\" src=\""
656 out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
657 << relPath << baseName << ".svg\" width=\""
658 << ((width*96+48)/72) << "\" height=\""
659 << ((height*96+48)/72) << "\">";
661 writeSVGNotSupported(out);
662 //out << "</object>";
673 // since dot silently reproduces the input file when it does not
674 // support the PNG format, we need to check the result.
675 static void checkDotResult(const char *imgExt, const char *imgName)
677 if (qstrcmp(imgExt,"png")==0)
679 FILE *f = portable_fopen(imgName,"rb");
683 if (fread(data,1,4,f)==4)
685 if (!(data[1]=='P' && data[2]=='N' && data[3]=='G'))
687 err("Image `%s' produced by dot is not a valid PNG!\n"
688 "You should either select a different format "
689 "(DOT_IMAGE_FORMAT in the config file) or install a more "
690 "recent version of graphviz (1.7+)\n",imgName
696 err("Could not read image `%s' generated by dot!\n",imgName);
702 err("Could not open image `%s' generated by dot!\n",imgName);
707 static bool insertMapFile(FTextStream &out,const QCString &mapFile,
708 const QCString &relPath,const QCString &mapLabel)
710 QFileInfo fi(mapFile);
711 if (fi.exists() && fi.size()>0) // reuse existing map file
714 FTextStream tmpout(&tmpstr);
715 convertMapFile(tmpout,mapFile,relPath);
716 if (!tmpstr.isEmpty())
718 out << "<map name=\"" << mapLabel << "\" id=\"" << mapLabel << "\">" << endl;
720 out << "</map>" << endl;
724 return FALSE; // no map file yet, need to generate it
727 static void removeDotGraph(const QCString &dotName)
729 static bool dotCleanUp = Config_getBool(DOT_CLEANUP);
739 /*! Checks if a file "baseName".md5 exists. If so the contents
740 * are compared with \a md5. If equal FALSE is returned. If the .md5
741 * file does not exist or its contents are not equal to \a md5,
742 * a new .md5 is generated with the \a md5 string as contents.
744 static bool checkAndUpdateMd5Signature(const QCString &baseName,
747 QFile f(baseName+".md5");
748 if (f.open(IO_ReadOnly))
751 QCString md5stored(33);
752 int bytesRead=f.readBlock(md5stored.rawData(),32);
755 if (bytesRead==32 && md5==md5stored)
762 // create checksum file
763 if (f.open(IO_WriteOnly))
765 f.writeBlock(md5.data(),32);
771 static bool checkDeliverables(const QCString &file1,
772 const QCString &file2=QCString())
776 if (!file1.isEmpty())
779 file1Ok = (fi.exists() && fi.size()>0);
781 if (!file2.isEmpty())
784 file2Ok = (fi.exists() && fi.size()>0);
786 return file1Ok && file2Ok;
789 //--------------------------------------------------------------------
791 inline int DotNode::findParent( DotNode *n )
793 if ( !m_parents ) return -1;
794 return m_parents->find(n);
797 //--------------------------------------------------------------------
799 int DotNodeList::compareValues(const DotNode *n1,const DotNode *n2) const
801 return qstricmp(n1->m_label,n2->m_label);
804 //--------------------------------------------------------------------
806 DotRunner::DotRunner(const QCString &file,const QCString &path,
807 bool checkResult,const QCString &imageName)
808 : m_dotExe(Config_getString(DOT_PATH)+"dot"),
809 m_file(file), m_path(path),
810 m_checkResult(checkResult), m_imageName(imageName),
811 m_imgExt(getDotImageExtension())
813 static bool dotCleanUp = Config_getBool(DOT_CLEANUP);
814 static bool dotMultiTargets = Config_getBool(DOT_MULTI_TARGETS);
815 m_cleanUp = dotCleanUp;
816 m_multiTargets = dotMultiTargets;
817 m_jobs.setAutoDelete(TRUE);
820 void DotRunner::addJob(const char *format,const char *output, const char *base)
822 QCString args = QCString("-T")+format+" -o \""+output+"\"";
823 m_jobs.append(new DotConstString(args, base));
826 void DotRunner::addPostProcessing(const char *cmd,const char *args)
829 m_postArgs.set(args);
832 bool DotRunner::run()
835 int width=0,height=0;
838 QListIterator<DotConstString> li(m_jobs);
842 dotArgs=QCString("\"")+m_file.data()+"\"";
843 for (li.toFirst();(s=li.current());++li)
848 if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0) goto error;
849 dotArgs=QCString("\"")+m_file.data()+"\"";
851 for (li.toFirst();(s=li.current());++li)
855 if (!readBoundingBox(QCString(s->pdfData())+".pdf",&width,&height,FALSE)) goto error;
856 if ((width > MAX_LATEX_GRAPH_SIZE) || (height > MAX_LATEX_GRAPH_SIZE))
858 if (!resetPDFSize(width,height,s->pdfData())) goto error;
867 if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0) goto error;
872 for (li.toFirst();(s=li.current());++li)
874 dotArgs=QCString("\"")+m_file.data()+"\" "+s->data();
875 if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0) goto error;
878 if (!readBoundingBox(QCString(s->pdfData())+".pdf",&width,&height,FALSE)) goto error;
879 if ((width > MAX_LATEX_GRAPH_SIZE) || (height > MAX_LATEX_GRAPH_SIZE))
881 if (!resetPDFSize(width,height,s->pdfData())) goto error;
882 if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0) goto error;
887 if (!m_postCmd.isEmpty() && portable_system(m_postCmd.data(),m_postArgs.data())!=0)
889 err("Problems running '%s' as a post-processing step for dot output\n",m_postCmd.data());
894 checkDotResult(m_imgExt.data(),m_imageName.data());
898 //printf("removing dot file %s\n",m_file.data());
899 //QDir(path).remove(file);
900 m_cleanupItem.file.set(m_file.data());
901 m_cleanupItem.path.set(m_path.data());
905 err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n",
906 exitCode,m_dotExe.data(),dotArgs.data());
910 //--------------------------------------------------------------------
912 DotFilePatcher::DotFilePatcher(const char *patchFile)
913 : m_patchFile(patchFile)
915 m_maps.setAutoDelete(TRUE);
918 QCString DotFilePatcher::file() const
923 int DotFilePatcher::addMap(const QCString &mapFile,const QCString &relPath,
924 bool urlOnly,const QCString &context,const QCString &label)
926 int id = m_maps.count();
928 map->mapFile = mapFile;
929 map->relPath = relPath;
930 map->urlOnly = urlOnly;
931 map->context = context;
933 map->zoomable = FALSE;
939 int DotFilePatcher::addFigure(const QCString &baseName,
940 const QCString &figureName,bool heightCheck)
942 int id = m_maps.count();
944 map->mapFile = figureName;
945 map->urlOnly = heightCheck;
946 map->label = baseName;
947 map->zoomable = FALSE;
953 int DotFilePatcher::addSVGConversion(const QCString &relPath,bool urlOnly,
954 const QCString &context,bool zoomable,
957 int id = m_maps.count();
959 map->relPath = relPath;
960 map->urlOnly = urlOnly;
961 map->context = context;
962 map->zoomable = zoomable;
963 map->graphId = graphId;
968 int DotFilePatcher::addSVGObject(const QCString &baseName,
969 const QCString &absImgName,
970 const QCString &relPath)
972 int id = m_maps.count();
974 map->mapFile = absImgName;
975 map->relPath = relPath;
976 map->label = baseName;
977 map->zoomable = FALSE;
983 bool DotFilePatcher::run()
985 //printf("DotFilePatcher::run(): %s\n",m_patchFile.data());
986 static bool interactiveSVG = Config_getBool(INTERACTIVE_SVG);
987 bool isSVGFile = m_patchFile.right(4)==".svg";
992 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
993 interactiveSVG = interactiveSVG && map->zoomable;
994 graphId = map->graphId;
995 relPath = map->relPath;
996 //printf("DotFilePatcher::addSVGConversion: file=%s zoomable=%d\n",
997 // m_patchFile.data(),map->zoomable);
999 QString tmpName = QString::fromUtf8(m_patchFile+".tmp");
1000 QString patchFile = QString::fromUtf8(m_patchFile);
1001 if (!QDir::current().rename(patchFile,tmpName))
1003 err("Failed to rename file %s to %s!\n",m_patchFile.data(),tmpName.data());
1007 QFile fo(patchFile);
1008 if (!fi.open(IO_ReadOnly))
1010 err("problem opening file %s for patching!\n",tmpName.data());
1011 QDir::current().rename(tmpName,patchFile);
1014 if (!fo.open(IO_WriteOnly))
1016 err("problem opening file %s for patching!\n",m_patchFile.data());
1017 QDir::current().rename(tmpName,patchFile);
1021 const int maxLineLen=100*1024;
1024 bool insideHeader=FALSE;
1025 bool replacedHeader=FALSE;
1026 bool foundSize=FALSE;
1027 while (!fi.atEnd()) // foreach line
1029 QCString line(maxLineLen);
1030 int numBytes = fi.readLine(line.rawData(),maxLineLen);
1035 line.resize(numBytes+1);
1037 //printf("line=[%s]\n",line.stripWhiteSpace().data());
1039 ASSERT(numBytes<maxLineLen);
1044 if (line.find("<svg")!=-1 && !replacedHeader)
1047 count = sscanf(line.data(),"<svg width=\"%dpt\" height=\"%dpt\"",&width,&height);
1048 //printf("width=%d height=%d\n",width,height);
1049 foundSize = count==2 && (width>500 || height>450);
1050 if (foundSize) insideHeader=TRUE;
1052 else if (insideHeader && !replacedHeader && line.find("<title>")!=-1)
1056 // insert special replacement header for interactive SVGs
1057 t << "<!--zoomable " << height << " -->\n";
1059 t << "var viewWidth = " << width << ";\n";
1060 t << "var viewHeight = " << height << ";\n";
1063 t << "var sectionId = 'dynsection-" << graphId << "';\n";
1066 t << "<script xlink:href=\"" << relPath << "svgpan.js\"/>\n";
1067 t << "<svg id=\"graph\" class=\"graph\">\n";
1068 t << "<g id=\"viewport\">\n";
1071 replacedHeader=TRUE;
1074 if (!insideHeader || !foundSize) // copy SVG and replace refs,
1075 // unless we are inside the header of the SVG.
1076 // Then we replace it with another header.
1078 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
1079 t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
1082 else if ((i=line.find("<!-- SVG"))!=-1 || (i=line.find("[!-- SVG"))!=-1)
1084 //printf("Found marker at %d\n",i);
1087 int n = sscanf(line.data()+i+1,"!-- SVG %d",&mapId);
1088 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1090 int e = QMAX(line.find("--]"),line.find("-->"));
1091 Map *map = m_maps.at(mapId);
1092 //printf("DotFilePatcher::writeSVGFigure: file=%s zoomable=%d\n",
1093 // m_patchFile.data(),map->zoomable);
1094 if (!writeSVGFigureLink(t,map->relPath,map->label,map->mapFile))
1096 err("Problem extracting size from SVG file %s\n",map->mapFile.data());
1098 if (e!=-1) t << line.mid(e+3);
1100 else // error invalid map id!
1102 err("Found invalid SVG id in file %s!\n",m_patchFile.data());
1106 else if ((i=line.find("<!-- MAP"))!=-1)
1110 int n = sscanf(line.data()+i,"<!-- MAP %d",&mapId);
1111 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1114 FTextStream tt(&result);
1115 Map *map = m_maps.at(mapId);
1116 //printf("patching MAP %d in file %s with contents of %s\n",
1117 // mapId,m_patchFile.data(),map->mapFile.data());
1118 convertMapFile(tt,map->mapFile,map->relPath,map->urlOnly,map->context);
1119 if (!result.isEmpty())
1121 t << "<map name=\"" << map->label << "\" id=\"" << map->label << "\">" << endl;
1123 t << "</map>" << endl;
1126 else // error invalid map id!
1128 err("Found invalid MAP id in file %s!\n",m_patchFile.data());
1132 else if ((i=line.find("% FIG"))!=-1)
1135 int n = sscanf(line.data()+i+2,"FIG %d",&mapId);
1136 //printf("line='%s' n=%d\n",line.data()+i,n);
1137 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1139 Map *map = m_maps.at(mapId);
1140 //printf("patching FIG %d in file %s with contents of %s\n",
1141 // mapId,m_patchFile.data(),map->mapFile.data());
1142 if (!writeVecGfxFigure(t,map->label,map->mapFile))
1144 err("problem writing FIG %d figure!\n",mapId);
1148 else // error invalid map id!
1150 err("Found invalid bounding FIG %d in file %s!\n",mapId,m_patchFile.data());
1161 if (isSVGFile && interactiveSVG && replacedHeader)
1163 QCString orgName=m_patchFile.left(m_patchFile.length()-4)+"_org.svg";
1164 t << substitute(svgZoomFooter,"$orgname",stripPath(orgName));
1166 // keep original SVG file so we can refer to it, we do need to replace
1167 // dummy link by real ones
1170 if (!fi.open(IO_ReadOnly))
1172 err("problem opening file %s for reading!\n",tmpName.data());
1175 if (!fo.open(IO_WriteOnly))
1177 err("problem opening file %s for writing!\n",orgName.data());
1181 while (!fi.atEnd()) // foreach line
1183 QCString line(maxLineLen);
1184 int numBytes = fi.readLine(line.rawData(),maxLineLen);
1189 line.resize(numBytes+1);
1190 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
1191 t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
1196 // remove temporary file
1197 QDir::current().remove(tmpName);
1201 //--------------------------------------------------------------------
1203 void DotRunnerQueue::enqueue(DotRunner *runner)
1205 QMutexLocker locker(&m_mutex);
1206 m_queue.enqueue(runner);
1207 m_bufferNotEmpty.wakeAll();
1210 DotRunner *DotRunnerQueue::dequeue()
1212 QMutexLocker locker(&m_mutex);
1213 while (m_queue.isEmpty())
1215 // wait until something is added to the queue
1216 m_bufferNotEmpty.wait(&m_mutex);
1218 DotRunner *result = m_queue.dequeue();
1222 uint DotRunnerQueue::count() const
1224 QMutexLocker locker(&m_mutex);
1225 return m_queue.count();
1228 //--------------------------------------------------------------------
1230 DotWorkerThread::DotWorkerThread(DotRunnerQueue *queue)
1233 m_cleanupItems.setAutoDelete(TRUE);
1236 void DotWorkerThread::run()
1239 while ((runner=m_queue->dequeue()))
1242 const DotRunner::CleanupItem &cleanup = runner->cleanup();
1243 if (!cleanup.file.isEmpty())
1245 m_cleanupItems.append(new DotRunner::CleanupItem(cleanup));
1250 void DotWorkerThread::cleanup()
1252 QListIterator<DotRunner::CleanupItem> it(m_cleanupItems);
1253 DotRunner::CleanupItem *ci;
1254 for (;(ci=it.current());++it)
1256 QDir(ci->path.data()).remove(ci->file.data());
1260 //--------------------------------------------------------------------
1262 DotManager *DotManager::m_theInstance = 0;
1264 DotManager *DotManager::instance()
1268 m_theInstance = new DotManager;
1270 return m_theInstance;
1273 DotManager::DotManager() : m_dotMaps(1009)
1275 m_dotRuns.setAutoDelete(TRUE);
1276 m_dotMaps.setAutoDelete(TRUE);
1277 m_queue = new DotRunnerQueue;
1279 int numThreads = QMIN(32,Config_getInt(DOT_NUM_THREADS));
1282 if (numThreads==0) numThreads = QMAX(2,QThread::idealThreadCount()+1);
1283 for (i=0;i<numThreads;i++)
1285 DotWorkerThread *thread = new DotWorkerThread(m_queue);
1287 if (thread->isRunning())
1289 m_workers.append(thread);
1291 else // no more threads available!
1296 ASSERT(m_workers.count()>0);
1300 DotManager::~DotManager()
1305 void DotManager::addRun(DotRunner *run)
1307 m_dotRuns.append(run);
1310 int DotManager::addMap(const QCString &file,const QCString &mapFile,
1311 const QCString &relPath,bool urlOnly,const QCString &context,
1312 const QCString &label)
1314 DotFilePatcher *map = m_dotMaps.find(file);
1317 map = new DotFilePatcher(file);
1318 m_dotMaps.append(file,map);
1320 return map->addMap(mapFile,relPath,urlOnly,context,label);
1323 int DotManager::addFigure(const QCString &file,const QCString &baseName,
1324 const QCString &figureName,bool heightCheck)
1326 DotFilePatcher *map = m_dotMaps.find(file);
1329 map = new DotFilePatcher(file);
1330 m_dotMaps.append(file,map);
1332 return map->addFigure(baseName,figureName,heightCheck);
1335 int DotManager::addSVGConversion(const QCString &file,const QCString &relPath,
1336 bool urlOnly,const QCString &context,bool zoomable,
1339 DotFilePatcher *map = m_dotMaps.find(file);
1342 map = new DotFilePatcher(file);
1343 m_dotMaps.append(file,map);
1345 return map->addSVGConversion(relPath,urlOnly,context,zoomable,graphId);
1348 int DotManager::addSVGObject(const QCString &file,const QCString &baseName,
1349 const QCString &absImgName,const QCString &relPath)
1351 DotFilePatcher *map = m_dotMaps.find(file);
1354 map = new DotFilePatcher(file);
1355 m_dotMaps.append(file,map);
1357 return map->addSVGObject(baseName,absImgName,relPath);
1360 bool DotManager::run()
1362 uint numDotRuns = m_dotRuns.count();
1363 uint numDotMaps = m_dotMaps.count();
1364 if (numDotRuns+numDotMaps>1)
1366 if (m_workers.count()==0)
1368 msg("Generating dot graphs in single threaded mode...\n");
1372 msg("Generating dot graphs using %d parallel threads...\n",QMIN(numDotRuns+numDotMaps,m_workers.count()));
1376 QListIterator<DotRunner> li(m_dotRuns);
1379 if (Config_getBool(GENERATE_HTML))
1381 setDotFontPath(Config_getString(HTML_OUTPUT));
1384 else if (Config_getBool(GENERATE_LATEX))
1386 setDotFontPath(Config_getString(LATEX_OUTPUT));
1389 else if (Config_getBool(GENERATE_RTF))
1391 setDotFontPath(Config_getString(RTF_OUTPUT));
1394 else if (Config_getBool(GENERATE_DOCBOOK))
1396 setDotFontPath(Config_getString(DOCBOOK_OUTPUT));
1399 portable_sysTimerStart();
1400 // fill work queue with dot operations
1403 if (m_workers.count()==0) // no threads to work with
1405 for (li.toFirst();(dr=li.current());++li)
1407 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1412 else // use multiple threads to run instances of dot in parallel
1414 for (li.toFirst();(dr=li.current());++li)
1416 m_queue->enqueue(dr);
1418 // wait for the queue to become empty
1419 while ((i=m_queue->count())>0)
1424 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1427 portable_sleep(100);
1429 while ((int)numDotRuns>=prev)
1431 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1434 // signal the workers we are done
1435 for (i=0;i<(int)m_workers.count();i++)
1437 m_queue->enqueue(0); // add terminator for each worker
1439 // wait for the workers to finish
1440 for (i=0;i<(int)m_workers.count();i++)
1442 m_workers.at(i)->wait();
1444 // clean up dot files from main thread
1445 for (i=0;i<(int)m_workers.count();i++)
1447 m_workers.at(i)->cleanup();
1450 portable_sysTimerStop();
1456 // patch the output file and insert the maps and figures
1458 SDict<DotFilePatcher>::Iterator di(m_dotMaps);
1459 DotFilePatcher *map;
1460 // since patching the svg files may involve patching the header of the SVG
1461 // (for zoomable SVGs), and patching the .html files requires reading that
1462 // header after the SVG is patched, we first process the .svg files and
1463 // then the other files.
1464 for (di.toFirst();(map=di.current());++di)
1466 if (map->file().right(4)==".svg")
1468 msg("Patching output file %d/%d\n",i,numDotMaps);
1469 if (!map->run()) return FALSE;
1473 for (di.toFirst();(map=di.current());++di)
1475 if (map->file().right(4)!=".svg")
1477 msg("Patching output file %d/%d\n",i,numDotMaps);
1478 if (!map->run()) return FALSE;
1485 //--------------------------------------------------------------------
1488 /*! helper function that deletes all nodes in a connected graph, given
1489 * one of the graph's nodes
1491 static void deleteNodes(DotNode *node,SDict<DotNode> *skipNodes=0)
1493 //printf("deleteNodes skipNodes=%p\n",skipNodes);
1494 static DotNodeList deletedNodes;
1495 deletedNodes.setAutoDelete(TRUE);
1496 node->deleteNode(deletedNodes,skipNodes); // collect nodes to be deleted.
1497 deletedNodes.clear(); // actually remove the nodes.
1500 DotNode::DotNode(int n,const char *lab,const char *tip, const char *url,
1501 bool isRoot,ClassDef *cd)
1516 , m_truncated(Unknown)
1518 , m_renumbered(false)
1529 void DotNode::addChild(DotNode *n,
1532 const char *edgeLab,
1533 const char *edgeURL,
1539 m_children = new QList<DotNode>;
1540 m_edgeInfo = new QList<EdgeInfo>;
1541 m_edgeInfo->setAutoDelete(TRUE);
1543 m_children->append(n);
1544 EdgeInfo *ei = new EdgeInfo;
1545 ei->m_color = edgeColor;
1546 ei->m_style = edgeStyle;
1547 ei->m_label = edgeLab;
1548 ei->m_url = edgeURL;
1550 ei->m_labColor=edgeColor;
1552 ei->m_labColor=edgeLabCol;
1553 m_edgeInfo->append(ei);
1556 void DotNode::addParent(DotNode *n)
1560 m_parents = new QList<DotNode>;
1562 m_parents->append(n);
1565 void DotNode::removeChild(DotNode *n)
1567 if (m_children) m_children->remove(n);
1570 void DotNode::removeParent(DotNode *n)
1572 if (m_parents) m_parents->remove(n);
1575 void DotNode::deleteNode(DotNodeList &deletedList,SDict<DotNode> *skipNodes)
1577 if (m_deleted) return; // avoid recursive loops in case the graph has cycles
1579 if (m_parents!=0) // delete all parent nodes of this node
1581 QListIterator<DotNode> dnlip(*m_parents);
1583 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
1585 //pn->removeChild(this);
1586 pn->deleteNode(deletedList,skipNodes);
1589 if (m_children!=0) // delete all child nodes of this node
1591 QListIterator<DotNode> dnlic(*m_children);
1593 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
1595 //cn->removeParent(this);
1596 cn->deleteNode(deletedList,skipNodes);
1599 // add this node to the list of deleted nodes.
1600 //printf("skipNodes=%p find(%p)=%p\n",skipNodes,this,skipNodes ? skipNodes->find((int)this) : 0);
1601 if (skipNodes==0 || skipNodes->find((char*)this)==0)
1603 //printf("deleting\n");
1604 deletedList.append(this);
1608 void DotNode::setDistance(int distance)
1610 if (distance<m_distance) m_distance = distance;
1613 static QCString convertLabel(const QCString &l)
1615 QString bBefore("\\_/<({[: =-+@%#~?$"); // break before character set
1616 QString bAfter(">]),:;|"); // break after character set
1618 if (p.isEmpty()) return QCString();
1625 int foldLen=17; // ideal text length
1626 while (idx < p.length())
1629 QString replacement;
1632 case '\\': replacement="\\\\"; break;
1633 case '\n': replacement="\\n"; break;
1634 case '<': replacement="\\<"; break;
1635 case '>': replacement="\\>"; break;
1636 case '|': replacement="\\|"; break;
1637 case '{': replacement="\\{"; break;
1638 case '}': replacement="\\}"; break;
1639 case '"': replacement="\\\""; break;
1640 default: replacement=c; break;
1642 // Some heuristics to insert newlines to prevent too long
1643 // boxes and at the same time prevent ugly breaks
1646 result+=replacement;
1647 foldLen = (3*foldLen+sinceLast+2)/4;
1650 else if ((pc!=':' || c!=':') && charsLeft>foldLen/3 && sinceLast>foldLen && bBefore.contains(c))
1653 result+=replacement;
1654 foldLen = (foldLen+sinceLast+1)/2;
1657 else if (charsLeft>1+foldLen/4 && sinceLast>foldLen+foldLen/3 &&
1658 !isupper(c) && p[idx].category()==QChar::Letter_Uppercase)
1660 result+=replacement;
1662 foldLen = (foldLen+sinceLast+1)/2;
1665 else if (charsLeft>foldLen/3 && sinceLast>foldLen && bAfter.contains(c) && (c!=':' || p[idx]!=':'))
1667 result+=replacement;
1669 foldLen = (foldLen+sinceLast+1)/2;
1674 result+=replacement;
1680 return result.utf8();
1683 static QCString escapeTooltip(const QCString &tooltip)
1686 const char *p=tooltip.data();
1687 if (p==0) return result;
1693 case '"': result+="\\\""; break;
1694 default: result+=c; break;
1700 static void writeBoxMemberList(FTextStream &t,
1701 char prot,MemberList *ml,ClassDef *scope,
1702 bool isStatic=FALSE,const QDict<void> *skipNames=0)
1707 MemberListIterator mlia(*ml);
1710 for (mlia.toFirst();(mma = mlia.current());++mlia)
1712 if (mma->getClassDef()==scope &&
1713 (skipNames==0 || skipNames->find(mma->name())==0))
1720 for (mlia.toFirst();(mma = mlia.current());++mlia)
1722 if (mma->getClassDef() == scope &&
1723 (skipNames==0 || skipNames->find(mma->name())==0))
1725 static int limit = Config_getInt(UML_LIMIT_NUM_FIELDS);
1726 if (limit>0 && (totalCount>limit*3/2 && count>=limit))
1728 t << theTranslator->trAndMore(QCString().sprintf("%d",totalCount-count)) << "\\l";
1734 t << convertLabel(mma->name());
1735 if (!mma->isObjCMethod() &&
1736 (mma->isFunction() || mma->isSlot() || mma->isSignal())) t << "()";
1742 // write member groups within the memberlist
1743 MemberGroupList *mgl = ml->getMemberGroupList();
1746 MemberGroupListIterator mgli(*mgl);
1748 for (mgli.toFirst();(mg=mgli.current());++mgli)
1752 writeBoxMemberList(t,prot,mg->members(),scope,isStatic,skipNames);
1759 static QCString stripProtectionPrefix(const QCString &s)
1761 if (!s.isEmpty() && (s[0]=='-' || s[0]=='+' || s[0]=='~' || s[0]=='#'))
1771 void DotNode::writeBox(FTextStream &t,
1773 GraphOutputFormat /*format*/,
1774 bool hasNonReachableChildren
1777 const char *labCol =
1778 m_url.isEmpty() ? "grey75" : // non link
1780 (hasNonReachableChildren) ? "red" : "black"
1782 t << " Node" << m_number << " [label=\"";
1783 static bool umlLook = Config_getBool(UML_LOOK);
1785 if (m_classDef && umlLook && (gt==Inheritance || gt==Collaboration))
1787 // add names shown as relations to a dictionary, so we don't show
1788 // them as attributes as well
1789 QDict<void> arrowNames(17);
1793 QListIterator<EdgeInfo> li(*m_edgeInfo);
1795 for (li.toFirst();(ei=li.current());++li)
1797 if (!ei->m_label.isEmpty()) // labels joined by \n
1799 int li=ei->m_label.find('\n');
1802 while ((li=ei->m_label.find('\n',p))!=-1)
1804 lab = stripProtectionPrefix(ei->m_label.mid(p,li-p));
1805 arrowNames.insert(lab,(void*)0x8);
1808 lab = stripProtectionPrefix(ei->m_label.right(ei->m_label.length()-p));
1809 arrowNames.insert(lab,(void*)0x8);
1814 //printf("DotNode::writeBox for %s\n",m_classDef->name().data());
1815 static bool extractPrivate = Config_getBool(EXTRACT_PRIVATE);
1816 t << "{" << convertLabel(m_label);
1818 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubAttribs),m_classDef,FALSE,&arrowNames);
1819 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubStaticAttribs),m_classDef,TRUE,&arrowNames);
1820 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_properties),m_classDef,FALSE,&arrowNames);
1821 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacAttribs),m_classDef,FALSE,&arrowNames);
1822 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacStaticAttribs),m_classDef,TRUE,&arrowNames);
1823 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proAttribs),m_classDef,FALSE,&arrowNames);
1824 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proStaticAttribs),m_classDef,TRUE,&arrowNames);
1827 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priAttribs),m_classDef,FALSE,&arrowNames);
1828 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priStaticAttribs),m_classDef,TRUE,&arrowNames);
1831 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubMethods),m_classDef);
1832 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubStaticMethods),m_classDef,TRUE);
1833 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubSlots),m_classDef);
1834 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacMethods),m_classDef);
1835 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacStaticMethods),m_classDef,TRUE);
1836 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proMethods),m_classDef);
1837 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proStaticMethods),m_classDef,TRUE);
1838 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proSlots),m_classDef);
1841 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priMethods),m_classDef);
1842 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priStaticMethods),m_classDef,TRUE);
1843 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priSlots),m_classDef);
1845 if (m_classDef->getLanguage()!=SrcLangExt_Fortran &&
1846 m_classDef->getMemberGroupSDict())
1848 MemberGroupSDict::Iterator mgdi(*m_classDef->getMemberGroupSDict());
1850 for (mgdi.toFirst();(mg=mgdi.current());++mgdi)
1854 writeBoxMemberList(t,'*',mg->members(),m_classDef,FALSE,&arrowNames);
1860 else // standard look
1862 t << convertLabel(m_label);
1864 t << "\",height=0.2,width=0.4";
1867 t << ",color=\"black\", fillcolor=\"grey75\", style=\"filled\", fontcolor=\"black\"";
1871 static bool dotTransparent = Config_getBool(DOT_TRANSPARENT);
1872 if (!dotTransparent)
1874 t << ",color=\"" << labCol << "\", fillcolor=\"";
1876 t << "\", style=\"filled\"";
1880 t << ",color=\"" << labCol << "\"";
1882 if (!m_url.isEmpty())
1884 int anchorPos = m_url.findRev('#');
1887 t << ",URL=\"" << m_url << Doxygen::htmlFileExtension << "\"";
1891 t << ",URL=\"" << m_url.left(anchorPos) << Doxygen::htmlFileExtension
1892 << m_url.right(m_url.length()-anchorPos) << "\"";
1896 if (!m_tooltip.isEmpty())
1898 t << ",tooltip=\"" << escapeTooltip(m_tooltip) << "\"";
1902 t << ",tooltip=\" \""; // space in tooltip is required otherwise still something like 'Node0' is used
1907 void DotNode::writeArrow(FTextStream &t,
1909 GraphOutputFormat format,
1928 static bool umlLook = Config_getBool(UML_LOOK);
1929 const EdgeProperties *eProps = umlLook ? ¨EdgeProps : &normalEdgeProps;
1930 QCString aStyle = eProps->arrowStyleMap[ei->m_color];
1931 bool umlUseArrow = aStyle=="odiamond";
1933 if (pointBack && !umlUseArrow) t << "dir=\"back\",";
1934 t << "color=\"" << eProps->edgeColorMap[ei->m_color]
1935 << "\",fontsize=\"" << FONTSIZE << "\",";
1936 t << "style=\"" << eProps->edgeStyleMap[ei->m_style] << "\"";
1937 if (!ei->m_label.isEmpty())
1939 t << ",label=\" " << convertLabel(ei->m_label) << "\" ";
1942 eProps->arrowStyleMap[ei->m_color] &&
1943 (gt==Inheritance || gt==Collaboration)
1946 bool rev = pointBack;
1947 if (umlUseArrow) rev=!rev; // UML use relates has arrow on the start side
1949 t << ",arrowtail=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
1951 t << ",arrowhead=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
1954 if (format==GOF_BITMAP) t << ",fontname=\"" << FONTNAME << "\"";
1958 void DotNode::write(FTextStream &t,
1960 GraphOutputFormat format,
1966 //printf("DotNode::write(%d) name=%s this=%p written=%d visible=%d\n",m_distance,m_label.data(),this,m_written,m_visible);
1967 if (m_written) return; // node already written to the output
1968 if (!m_visible) return; // node is not visible
1969 writeBox(t,gt,format,m_truncated==Truncated);
1971 QList<DotNode> *nl = toChildren ? m_children : m_parents;
1976 QListIterator<DotNode> dnli1(*nl);
1977 QListIterator<EdgeInfo> dnli2(*m_edgeInfo);
1979 for (dnli1.toFirst();(cn=dnli1.current());++dnli1,++dnli2)
1981 if (cn->isVisible())
1983 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",cn->label().data());
1984 writeArrow(t,gt,format,cn,dnli2.current(),topDown,backArrows);
1986 cn->write(t,gt,format,topDown,toChildren,backArrows);
1989 else // render parents
1991 QListIterator<DotNode> dnli(*nl);
1993 for (dnli.toFirst();(pn=dnli.current());++dnli)
1995 if (pn->isVisible())
1997 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",pn->label().data());
2002 pn->m_edgeInfo->at(pn->m_children->findRef(this)),
2007 pn->write(t,gt,format,TRUE,FALSE,backArrows);
2011 //printf("end DotNode::write(%d) name=%s\n",distance,m_label.data());
2014 void DotNode::writeXML(FTextStream &t,bool isClassGraph)
2016 t << " <node id=\"" << m_number << "\">" << endl;
2017 t << " <label>" << convertToXML(m_label) << "</label>" << endl;
2018 if (!m_url.isEmpty())
2020 QCString url(m_url);
2021 const char *refPtr = url.data();
2022 char *urlPtr = strchr(url.rawData(),'$');
2026 t << " <link refid=\"" << convertToXML(urlPtr) << "\"";
2029 t << " external=\"" << convertToXML(refPtr) << "\"";
2036 QListIterator<DotNode> nli(*m_children);
2037 QListIterator<EdgeInfo> eli(*m_edgeInfo);
2040 for (;(childNode=nli.current());++nli,++eli)
2042 edgeInfo=eli.current();
2043 t << " <childnode refid=\"" << childNode->m_number << "\" relation=\"";
2046 switch(edgeInfo->m_color)
2048 case EdgeInfo::Blue: t << "public-inheritance"; break;
2049 case EdgeInfo::Green: t << "protected-inheritance"; break;
2050 case EdgeInfo::Red: t << "private-inheritance"; break;
2051 case EdgeInfo::Purple: t << "usage"; break;
2052 case EdgeInfo::Orange: t << "template-instance"; break;
2053 case EdgeInfo::Orange2: t << "type-constraint"; break;
2054 case EdgeInfo::Grey: ASSERT(0); break;
2057 else // include graph
2062 if (!edgeInfo->m_label.isEmpty())
2066 while ((ni=edgeInfo->m_label.find('\n',p))!=-1)
2069 << convertToXML(edgeInfo->m_label.mid(p,ni-p))
2070 << "</edgelabel>" << endl;
2074 << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p))
2075 << "</edgelabel>" << endl;
2077 t << " </childnode>" << endl;
2080 t << " </node>" << endl;
2083 void DotNode::writeDocbook(FTextStream &t,bool isClassGraph)
2085 t << " <node id=\"" << m_number << "\">" << endl;
2086 t << " <label>" << convertToXML(m_label) << "</label>" << endl;
2087 if (!m_url.isEmpty())
2089 QCString url(m_url);
2090 const char *refPtr = url.data();
2091 char *urlPtr = strchr(url.rawData(),'$');
2095 t << " <link refid=\"" << convertToXML(urlPtr) << "\"";
2098 t << " external=\"" << convertToXML(refPtr) << "\"";
2105 QListIterator<DotNode> nli(*m_children);
2106 QListIterator<EdgeInfo> eli(*m_edgeInfo);
2109 for (;(childNode=nli.current());++nli,++eli)
2111 edgeInfo=eli.current();
2112 t << " <childnode refid=\"" << childNode->m_number << "\" relation=\"";
2115 switch(edgeInfo->m_color)
2117 case EdgeInfo::Blue: t << "public-inheritance"; break;
2118 case EdgeInfo::Green: t << "protected-inheritance"; break;
2119 case EdgeInfo::Red: t << "private-inheritance"; break;
2120 case EdgeInfo::Purple: t << "usage"; break;
2121 case EdgeInfo::Orange: t << "template-instance"; break;
2122 case EdgeInfo::Orange2: t << "type-constraint"; break;
2123 case EdgeInfo::Grey: ASSERT(0); break;
2126 else // include graph
2131 if (!edgeInfo->m_label.isEmpty())
2135 while ((ni=edgeInfo->m_label.find('\n',p))!=-1)
2138 << convertToXML(edgeInfo->m_label.mid(p,ni-p))
2139 << "</edgelabel>" << endl;
2143 << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p))
2144 << "</edgelabel>" << endl;
2146 t << " </childnode>" << endl;
2149 t << " </node>" << endl;
2153 void DotNode::writeDEF(FTextStream &t)
2155 const char* nodePrefix = " node-";
2157 t << " node = {" << endl;
2158 t << nodePrefix << "id = " << m_number << ';' << endl;
2159 t << nodePrefix << "label = '" << m_label << "';" << endl;
2161 if (!m_url.isEmpty())
2163 QCString url(m_url);
2164 const char *refPtr = url.data();
2165 char *urlPtr = strchr(url.rawData(),'$');
2169 t << nodePrefix << "link = {" << endl << " "
2170 << nodePrefix << "link-id = '" << urlPtr << "';" << endl;
2174 t << " " << nodePrefix << "link-external = '"
2175 << refPtr << "';" << endl;
2182 QListIterator<DotNode> nli(*m_children);
2183 QListIterator<EdgeInfo> eli(*m_edgeInfo);
2186 for (;(childNode=nli.current());++nli,++eli)
2188 edgeInfo=eli.current();
2189 t << " node-child = {" << endl;
2190 t << " child-id = '" << childNode->m_number << "';" << endl;
2191 t << " relation = ";
2193 switch(edgeInfo->m_color)
2195 case EdgeInfo::Blue: t << "public-inheritance"; break;
2196 case EdgeInfo::Green: t << "protected-inheritance"; break;
2197 case EdgeInfo::Red: t << "private-inheritance"; break;
2198 case EdgeInfo::Purple: t << "usage"; break;
2199 case EdgeInfo::Orange: t << "template-instance"; break;
2200 case EdgeInfo::Orange2: t << "type-constraint"; break;
2201 case EdgeInfo::Grey: ASSERT(0); break;
2205 if (!edgeInfo->m_label.isEmpty())
2207 t << " edgelabel = <<_EnD_oF_dEf_TeXt_" << endl
2208 << edgeInfo->m_label << endl
2209 << "_EnD_oF_dEf_TeXt_;" << endl;
2211 t << " }; /* node-child */" << endl;
2212 } /* for (;childNode...) */
2214 t << " }; /* node */" << endl;
2218 void DotNode::clearWriteFlag()
2223 QListIterator<DotNode> dnlip(*m_parents);
2225 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
2229 pn->clearWriteFlag();
2235 QListIterator<DotNode> dnlic(*m_children);
2237 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2241 cn->clearWriteFlag();
2247 void DotNode::colorConnectedNodes(int curColor)
2251 QListIterator<DotNode> dnlic(*m_children);
2253 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2255 if (cn->m_subgraphId==-1) // uncolored child node
2257 cn->m_subgraphId=curColor;
2258 cn->markAsVisible();
2259 cn->colorConnectedNodes(curColor);
2260 //printf("coloring node %s (%p): %d\n",cn->m_label.data(),cn,cn->m_subgraphId);
2267 QListIterator<DotNode> dnlip(*m_parents);
2269 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
2271 if (pn->m_subgraphId==-1) // uncolored parent node
2273 pn->m_subgraphId=curColor;
2274 pn->markAsVisible();
2275 pn->colorConnectedNodes(curColor);
2276 //printf("coloring node %s (%p): %d\n",pn->m_label.data(),pn,pn->m_subgraphId);
2282 void DotNode::renumberNodes(int &number)
2284 m_number = number++;
2287 QListIterator<DotNode> dnlic(*m_children);
2289 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2291 if (!cn->m_renumbered)
2293 cn->m_renumbered = true;
2294 cn->renumberNodes(number);
2300 const DotNode *DotNode::findDocNode() const
2302 if (!m_url.isEmpty()) return this;
2303 //printf("findDocNode(): `%s'\n",m_label.data());
2306 QListIterator<DotNode> dnli(*m_parents);
2308 for (dnli.toFirst();(pn=dnli.current());++dnli)
2313 const DotNode *dn = pn->findDocNode();
2320 QListIterator<DotNode> dnli(*m_children);
2322 for (dnli.toFirst();(cn=dnli.current());++dnli)
2327 const DotNode *dn = cn->findDocNode();
2335 //--------------------------------------------------------------------
2337 void DotGfxHierarchyTable::createGraph(DotNode *n,FTextStream &out,
2338 const char *path,const char *fileName,int id) const
2342 QCString imgExt = getDotImageExtension();
2343 QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
2344 if (m_prefix.isEmpty())
2345 baseName.sprintf("inherit_graph_%d",id);
2347 baseName.sprintf("%sinherit_graph_%d",m_prefix.data(),id);
2348 QCString imgName = baseName+"."+ imgExt;
2349 QCString mapName = baseName+".map";
2350 QCString absImgName = QCString(d.absPath().data())+"/"+imgName;
2351 QCString absMapName = QCString(d.absPath().data())+"/"+mapName;
2352 QCString absBaseName = QCString(d.absPath().data())+"/"+baseName;
2353 QListIterator<DotNode> dnli2(*m_rootNodes);
2356 // compute md5 checksum of the graph were are about to generate
2358 FTextStream md5stream(&theGraph);
2359 writeGraphHeader(md5stream,theTranslator->trGraphicalHierarchy());
2360 md5stream << " rankdir=\"LR\";" << endl;
2361 for (dnli2.toFirst();(node=dnli2.current());++dnli2)
2363 if (node->m_subgraphId==n->m_subgraphId)
2365 node->clearWriteFlag();
2368 for (dnli2.toFirst();(node=dnli2.current());++dnli2)
2370 if (node->m_subgraphId==n->m_subgraphId)
2372 node->write(md5stream,DotNode::Hierarchy,GOF_BITMAP,FALSE,TRUE,TRUE);
2375 writeGraphFooter(md5stream);
2377 QCString sigStr(33);
2378 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
2379 MD5SigToString(md5_sig,sigStr.rawData(),33);
2380 bool regenerate=FALSE;
2381 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
2382 !checkDeliverables(absImgName,absMapName))
2385 // image was new or has changed
2386 QCString dotName=absBaseName+".dot";
2388 if (!f.open(IO_WriteOnly)) return;
2393 DotRunner *dotRun = new DotRunner(dotName,d.absPath().data(),TRUE,absImgName);
2394 dotRun->addJob(imgFmt,absImgName);
2395 dotRun->addJob(MAP_CMD,absMapName);
2396 DotManager::instance()->addRun(dotRun);
2400 removeDotGraph(absBaseName+".dot");
2402 Doxygen::indexList->addImageFile(imgName);
2403 // write image and map in a table row
2404 QCString mapLabel = escapeCharsInString(n->m_label,FALSE);
2405 if (imgExt=="svg") // vector graphics
2407 if (regenerate || !writeSVGFigureLink(out,QCString(),baseName,absImgName))
2411 DotManager::instance()->addSVGConversion(absImgName,QCString(),
2412 FALSE,QCString(),FALSE,0);
2414 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,
2415 absImgName,QCString());
2416 out << "<!-- SVG " << mapId << " -->" << endl;
2419 else // normal bitmap
2421 out << "<img src=\"" << imgName << "\" border=\"0\" alt=\"\" usemap=\"#"
2422 << mapLabel << "\"/>" << endl;
2424 if (regenerate || !insertMapFile(out,absMapName,QCString(),mapLabel))
2426 int mapId = DotManager::instance()->addMap(fileName,absMapName,QCString(),
2427 FALSE,QCString(),mapLabel);
2428 out << "<!-- MAP " << mapId << " -->" << endl;
2433 void DotGfxHierarchyTable::writeGraph(FTextStream &out,
2434 const char *path,const char *fileName) const
2436 //printf("DotGfxHierarchyTable::writeGraph(%s)\n",name);
2437 //printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count());
2439 if (m_rootSubgraphs->count()==0) return;
2442 // store the original directory
2445 err("Output dir %s does not exist!\n",path); exit(1);
2448 // put each connected subgraph of the hierarchy in a row of the HTML output
2449 out << "<table border=\"0\" cellspacing=\"10\" cellpadding=\"0\">" << endl;
2451 QListIterator<DotNode> dnli(*m_rootSubgraphs);
2454 for (dnli.toFirst();(n=dnli.current());++dnli)
2457 createGraph(n,out,path,fileName,count++);
2458 out << "</td></tr>" << endl;
2460 out << "</table>" << endl;
2463 void DotGfxHierarchyTable::addHierarchy(DotNode *n,ClassDef *cd,bool hideSuper)
2465 //printf("addHierarchy `%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count());
2466 if (cd->subClasses())
2468 BaseClassListIterator bcli(*cd->subClasses());
2470 for ( ; (bcd=bcli.current()) ; ++bcli )
2472 ClassDef *bClass=bcd->classDef;
2473 //printf(" Trying sub class=`%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count());
2474 if (bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses()))
2477 //printf(" Node `%s' Found visible class=`%s'\n",n->m_label.data(),
2478 // bClass->name().data());
2479 if ((bn=m_usedNodes->find(bClass->name()))) // node already present
2481 if (n->m_children==0 || n->m_children->findRef(bn)==-1) // no arrow yet
2483 n->addChild(bn,bcd->prot);
2485 //printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n",
2486 // n->m_label.data(),
2487 // bn->m_label.data(),
2488 // bn->m_children ? bn->m_children->count() : 0,
2489 // bn->m_parents ? bn->m_parents->count() : 0
2494 // printf(" Class already has an arrow!\n");
2499 QCString tmp_url="";
2500 if (bClass->isLinkable() && !bClass->isHidden())
2502 tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase();
2503 if (!bClass->anchor().isEmpty())
2505 tmp_url+="#"+bClass->anchor();
2508 QCString tooltip = bClass->briefDescriptionAsTooltip();
2509 bn = new DotNode(m_curNodeNumber++,
2510 bClass->displayName(),
2514 n->addChild(bn,bcd->prot);
2516 //printf(" Adding node %s to new base node %s (c=%d,p=%d)\n",
2517 // n->m_label.data(),
2518 // bn->m_label.data(),
2519 // bn->m_children ? bn->m_children->count() : 0,
2520 // bn->m_parents ? bn->m_parents->count() : 0
2522 //printf(" inserting %s (%p)\n",bClass->name().data(),bn);
2523 m_usedNodes->insert(bClass->name(),bn); // add node to the used list
2525 if (!bClass->visited && !hideSuper && bClass->subClasses())
2527 bool wasVisited=bClass->visited;
2528 bClass->visited=TRUE;
2529 addHierarchy(bn,bClass,wasVisited);
2534 //printf("end addHierarchy\n");
2537 void DotGfxHierarchyTable::addClassList(ClassSDict *cl)
2539 static bool sliceOpt = Config_getBool(OPTIMIZE_OUTPUT_SLICE);
2540 ClassSDict::Iterator cli(*cl);
2542 for (cli.toLast();(cd=cli.current());--cli)
2544 //printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count());
2545 if (cd->getLanguage()==SrcLangExt_VHDL &&
2546 (VhdlDocGen::VhdlClasses)cd->protection()!=VhdlDocGen::ENTITYCLASS
2551 if (sliceOpt && cd->compoundType() != m_classType)
2555 if (!hasVisibleRoot(cd->baseClasses()) &&
2556 cd->isVisibleInHierarchy()
2557 ) // root node in the forest
2559 QCString tmp_url="";
2560 if (cd->isLinkable() && !cd->isHidden())
2562 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2563 if (!cd->anchor().isEmpty())
2565 tmp_url+="#"+cd->anchor();
2568 //printf("Inserting root class %s\n",cd->name().data());
2569 QCString tooltip = cd->briefDescriptionAsTooltip();
2570 DotNode *n = new DotNode(m_curNodeNumber++,
2575 //m_usedNodes->clear();
2576 m_usedNodes->insert(cd->name(),n);
2577 m_rootNodes->insert(0,n);
2578 if (!cd->visited && cd->subClasses())
2580 addHierarchy(n,cd,cd->visited);
2587 DotGfxHierarchyTable::DotGfxHierarchyTable(const char *prefix,ClassDef::CompoundType ct)
2590 , m_curNodeNumber(1)
2592 m_rootNodes = new QList<DotNode>;
2593 m_usedNodes = new QDict<DotNode>(1009);
2594 m_usedNodes->setAutoDelete(TRUE);
2595 m_rootSubgraphs = new DotNodeList;
2597 // build a graph with each class as a node and the inheritance relations
2599 initClassHierarchy(Doxygen::classSDict);
2600 initClassHierarchy(Doxygen::hiddenClasses);
2601 addClassList(Doxygen::classSDict);
2602 addClassList(Doxygen::hiddenClasses);
2603 // m_usedNodes now contains all nodes in the graph
2605 // color the graph into a set of independent subgraphs
2608 QListIterator<DotNode> dnli(*m_rootNodes);
2609 while (!done) // there are still nodes to color
2612 done=TRUE; // we are done unless there are still uncolored nodes
2613 for (dnli.toLast();(n=dnli.current());--dnli)
2615 if (n->m_subgraphId==-1) // not yet colored
2617 //printf("Starting at node %s (%p): %d\n",n->m_label.data(),n,curColor);
2618 done=FALSE; // still uncolored nodes
2619 n->m_subgraphId=curColor;
2621 n->colorConnectedNodes(curColor);
2623 const DotNode *dn=n->findDocNode();
2625 m_rootSubgraphs->inSort(dn);
2627 m_rootSubgraphs->inSort(n);
2632 //printf("Number of independent subgraphs: %d\n",curColor);
2633 QListIterator<DotNode> dnli2(*m_rootSubgraphs);
2635 for (dnli2.toFirst();(n=dnli2.current());++dnli2)
2637 //printf("Node %s color=%d (c=%d,p=%d)\n",
2638 // n->m_label.data(),n->m_subgraphId,
2639 // n->m_children?n->m_children->count():0,
2640 // n->m_parents?n->m_parents->count():0);
2642 n->renumberNodes(number);
2646 DotGfxHierarchyTable::~DotGfxHierarchyTable()
2648 //printf("DotGfxHierarchyTable::~DotGfxHierarchyTable\n");
2650 //QDictIterator<DotNode> di(*m_usedNodes);
2652 //for (;(n=di.current());++di)
2654 // printf("Node %p: %s\n",n,n->label().data());
2659 delete m_rootSubgraphs;
2662 //--------------------------------------------------------------------
2664 void DotClassGraph::addClass(ClassDef *cd,DotNode *n,int prot,
2665 const char *label,const char *usedName,const char *templSpec,bool base,int distance)
2667 if (Config_getBool(HIDE_UNDOC_CLASSES) && !cd->isLinkable()) return;
2669 int edgeStyle = (label || prot==EdgeInfo::Orange || prot==EdgeInfo::Orange2) ? EdgeInfo::Dashed : EdgeInfo::Solid;
2671 if (cd->isAnonymous())
2673 className="anonymous:";
2676 else if (usedName) // name is a typedef
2680 else if (templSpec) // name has a template part
2682 className=insertTemplateSpecifierInScope(cd->name(),templSpec);
2684 else // just a normal name
2686 className=cd->displayName();
2688 //printf("DotClassGraph::addClass(class=`%s',parent=%s,prot=%d,label=%s,dist=%d,usedName=%s,templSpec=%s,base=%d)\n",
2689 // className.data(),n->m_label.data(),prot,label,distance,usedName,templSpec,base);
2690 DotNode *bn = m_usedNodes->find(className);
2691 if (bn) // class already inserted
2695 n->addChild(bn,prot,edgeStyle,label);
2700 bn->addChild(n,prot,edgeStyle,label);
2703 bn->setDistance(distance);
2704 //printf(" add exiting node %s of %s\n",bn->m_label.data(),n->m_label.data());
2708 QCString displayName=className;
2709 if (Config_getBool(HIDE_SCOPE_NAMES)) displayName=stripScope(displayName);
2711 if (cd->isLinkable() && !cd->isHidden())
2713 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2714 if (!cd->anchor().isEmpty())
2716 tmp_url+="#"+cd->anchor();
2719 QCString tooltip = cd->briefDescriptionAsTooltip();
2720 bn = new DotNode(m_curNodeNumber++,
2729 n->addChild(bn,prot,edgeStyle,label);
2734 bn->addChild(n,prot,edgeStyle,label);
2737 bn->setDistance(distance);
2738 m_usedNodes->insert(className,bn);
2739 //printf(" add new child node `%s' to %s hidden=%d url=%s\n",
2740 // className.data(),n->m_label.data(),cd->isHidden(),tmp_url.data());
2742 buildGraph(cd,bn,base,distance+1);
2746 void DotClassGraph::determineTruncatedNodes(QList<DotNode> &queue,bool includeParents)
2748 while (queue.count()>0)
2750 DotNode *n = queue.take(0);
2751 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
2753 bool truncated = FALSE;
2756 QListIterator<DotNode> li(*n->m_children);
2758 for (li.toFirst();(dn=li.current());++li)
2760 if (!dn->isVisible())
2766 if (n->m_parents && includeParents)
2768 QListIterator<DotNode> li(*n->m_parents);
2770 for (li.toFirst();(dn=li.current());++li)
2772 if (!dn->isVisible())
2778 n->markAsTruncated(truncated);
2783 bool DotClassGraph::determineVisibleNodes(DotNode *rootNode,
2784 int maxNodes,bool includeParents)
2786 QList<DotNode> childQueue;
2787 QList<DotNode> parentQueue;
2788 QArray<int> childTreeWidth;
2789 QArray<int> parentTreeWidth;
2790 childQueue.append(rootNode);
2791 if (includeParents) parentQueue.append(rootNode);
2792 bool firstNode=TRUE; // flag to force reprocessing rootNode in the parent loop
2793 // despite being marked visible in the child loop
2794 while ((childQueue.count()>0 || parentQueue.count()>0) && maxNodes>0)
2796 static int maxDistance = Config_getInt(MAX_DOT_GRAPH_DEPTH);
2797 if (childQueue.count()>0)
2799 DotNode *n = childQueue.take(0);
2800 int distance = n->distance();
2801 if (!n->isVisible() && distance<=maxDistance) // not yet processed
2805 int oldSize=(int)childTreeWidth.size();
2806 if (distance>oldSize)
2808 childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance));
2809 int i; for (i=oldSize;i<distance;i++) childTreeWidth[i]=0;
2811 childTreeWidth[distance-1]+=n->label().length();
2815 // add direct children
2818 QListIterator<DotNode> li(*n->m_children);
2820 for (li.toFirst();(dn=li.current());++li)
2822 childQueue.append(dn);
2827 if (includeParents && parentQueue.count()>0)
2829 DotNode *n = parentQueue.take(0);
2830 if ((!n->isVisible() || firstNode) && n->distance()<=maxDistance) // not yet processed
2833 int distance = n->distance();
2836 int oldSize = (int)parentTreeWidth.size();
2837 if (distance>oldSize)
2839 parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance));
2840 int i; for (i=oldSize;i<distance;i++) parentTreeWidth[i]=0;
2842 parentTreeWidth[distance-1]+=n->label().length();
2846 // add direct parents
2849 QListIterator<DotNode> li(*n->m_parents);
2851 for (li.toFirst();(dn=li.current());++li)
2853 parentQueue.append(dn);
2859 if (Config_getBool(UML_LOOK)) return FALSE; // UML graph are always top to bottom
2861 int maxHeight=(int)QMAX(childTreeWidth.size(),parentTreeWidth.size());
2863 for (i=0;i<childTreeWidth.size();i++)
2865 if (childTreeWidth.at(i)>maxWidth) maxWidth=childTreeWidth.at(i);
2867 for (i=0;i<parentTreeWidth.size();i++)
2869 if (parentTreeWidth.at(i)>maxWidth) maxWidth=parentTreeWidth.at(i);
2871 //printf("max tree width=%d, max tree height=%d\n",maxWidth,maxHeight);
2872 return maxWidth>80 && maxHeight<12; // used metric to decide to render the tree
2873 // from left to right instead of top to bottom,
2874 // with the idea to render very wide trees in
2875 // left to right order.
2878 void DotClassGraph::buildGraph(ClassDef *cd,DotNode *n,bool base,int distance)
2880 static bool templateRelations = Config_getBool(TEMPLATE_RELATIONS);
2881 //printf("DocClassGraph::buildGraph(%s,distance=%d,base=%d)\n",
2882 // cd->name().data(),distance,base);
2883 // ---- Add inheritance relations
2885 if (m_graphType == DotNode::Inheritance || m_graphType==DotNode::Collaboration)
2887 BaseClassList *bcl = base ? cd->baseClasses() : cd->subClasses();
2890 BaseClassListIterator bcli(*bcl);
2892 for ( ; (bcd=bcli.current()) ; ++bcli )
2894 //printf("-------- inheritance relation %s->%s templ=`%s'\n",
2895 // cd->name().data(),bcd->classDef->name().data(),bcd->templSpecifiers.data());
2896 addClass(bcd->classDef,n,bcd->prot,0,bcd->usedName,
2897 bcd->templSpecifiers,base,distance);
2901 if (m_graphType == DotNode::Collaboration)
2903 // ---- Add usage relations
2905 UsesClassDict *dict =
2906 base ? cd->usedImplementationClasses() :
2907 cd->usedByImplementationClasses()
2911 UsesClassDictIterator ucdi(*dict);
2913 for (;(ucd=ucdi.current());++ucdi)
2916 QDictIterator<void> dvi(*ucd->accessors);
2921 for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count)
2930 label+=QCString("\n")+s;
2933 if (count==maxLabels) label+="\n...";
2934 //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data());
2935 addClass(ucd->classDef,n,EdgeInfo::Purple,label,0,
2936 ucd->templSpecifiers,base,distance);
2940 if (templateRelations && base)
2942 ConstraintClassDict *dict = cd->templateTypeConstraints();
2945 ConstraintClassDictIterator ccdi(*dict);
2946 ConstraintClassDef *ccd;
2947 for (;(ccd=ccdi.current());++ccdi)
2950 QDictIterator<void> dvi(*ccd->accessors);
2955 for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count)
2964 label+=QCString("\n")+s;
2967 if (count==maxLabels) label+="\n...";
2968 //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data());
2969 addClass(ccd->classDef,n,EdgeInfo::Orange2,label,0,
2975 // ---- Add template instantiation relations
2977 if (templateRelations)
2979 if (base) // template relations for base classes
2981 ClassDef *templMaster=cd->templateMaster();
2984 QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances());
2985 ClassDef *templInstance;
2986 for (;(templInstance=cli.current());++cli)
2988 if (templInstance==cd)
2990 addClass(templMaster,n,EdgeInfo::Orange,cli.currentKey(),0,
2996 else // template relations for super classes
2998 QDict<ClassDef> *templInstances = cd->getTemplateInstances();
3001 QDictIterator<ClassDef> cli(*templInstances);
3002 ClassDef *templInstance;
3003 for (;(templInstance=cli.current());++cli)
3005 addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0,
3013 int DotClassGraph::m_curNodeNumber = 0;
3015 void DotClassGraph::resetNumbering()
3017 m_curNodeNumber = 0;
3020 DotClassGraph::DotClassGraph(ClassDef *cd,DotNode::GraphType t)
3022 //printf("--------------- DotClassGraph::DotClassGraph `%s'\n",cd->displayName().data());
3024 QCString tmp_url="";
3025 if (cd->isLinkable() && !cd->isHidden())
3027 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
3028 if (!cd->anchor().isEmpty())
3030 tmp_url+="#"+cd->anchor();
3033 QCString className = cd->displayName();
3034 QCString tooltip = cd->briefDescriptionAsTooltip();
3035 m_startNode = new DotNode(m_curNodeNumber++,
3039 TRUE, // is a root node
3042 m_startNode->setDistance(0);
3043 m_usedNodes = new QDict<DotNode>(1009);
3044 m_usedNodes->insert(className,m_startNode);
3046 //printf("Root node %s\n",cd->name().data());
3049 buildGraph(cd,m_startNode,TRUE,1);
3050 if (t==DotNode::Inheritance) buildGraph(cd,m_startNode,FALSE,1);
3053 static int maxNodes = Config_getInt(DOT_GRAPH_MAX_NODES);
3054 //int directChildNodes = 1;
3055 //if (m_startNode->m_children!=0)
3056 // directChildNodes+=m_startNode->m_children->count();
3057 //if (t==DotNode::Inheritance && m_startNode->m_parents!=0)
3058 // directChildNodes+=m_startNode->m_parents->count();
3059 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3060 //openNodeQueue.append(m_startNode);
3061 m_lrRank = determineVisibleNodes(m_startNode,maxNodes,t==DotNode::Inheritance);
3062 QList<DotNode> openNodeQueue;
3063 openNodeQueue.append(m_startNode);
3064 determineTruncatedNodes(openNodeQueue,t==DotNode::Inheritance);
3066 m_collabFileName = cd->collaborationGraphFileName();
3067 m_inheritFileName = cd->inheritanceGraphFileName();
3070 bool DotClassGraph::isTrivial() const
3072 static bool umlLook = Config_getBool(UML_LOOK);
3073 if (m_graphType==DotNode::Inheritance)
3074 return m_startNode->m_children==0 && m_startNode->m_parents==0;
3076 return !umlLook && m_startNode->m_children==0;
3079 bool DotClassGraph::isTooBig() const
3081 static int maxNodes = Config_getInt(DOT_GRAPH_MAX_NODES);
3083 numNodes+= m_startNode->m_children ? m_startNode->m_children->count() : 0;
3084 if (m_graphType==DotNode::Inheritance)
3086 numNodes+= m_startNode->m_parents ? m_startNode->m_parents->count() : 0;
3088 return numNodes>=maxNodes;
3091 DotClassGraph::~DotClassGraph()
3093 deleteNodes(m_startNode);
3097 /*! Computes a 16 byte md5 checksum for a given dot graph.
3098 * The md5 checksum is returned as a 32 character ASCII string.
3100 QCString computeMd5Signature(DotNode *root,
3101 DotNode::GraphType gt,
3102 GraphOutputFormat format,
3103 const QCString &rank, // either "LR", "RL", or ""
3106 const QCString &title,
3110 //printf("computeMd5Signature\n");
3112 FTextStream md5stream(&buf);
3113 writeGraphHeader(md5stream,title);
3114 if (!rank.isEmpty())
3116 md5stream << " rankdir=\"" << rank << "\";" << endl;
3118 root->clearWriteFlag();
3119 root->write(md5stream,
3122 gt!=DotNode::CallGraph && gt!=DotNode::Dependency,
3125 if (renderParents && root->m_parents)
3127 QListIterator<DotNode> dnli(*root->m_parents);
3129 for (dnli.toFirst();(pn=dnli.current());++dnli)
3131 if (pn->isVisible())
3133 root->writeArrow(md5stream, // stream
3135 format, // output format
3137 pn->m_edgeInfo->at(pn->m_children->findRef(root)), // edge info
3139 backArrows // point back?
3142 pn->write(md5stream, // stream
3144 format, // output format
3146 FALSE, // toChildren?
3147 backArrows // backward pointing arrows?
3151 writeGraphFooter(md5stream);
3153 QCString sigStr(33);
3154 MD5Buffer((const unsigned char *)buf.data(),buf.length(),md5_sig);
3155 MD5SigToString(md5_sig,sigStr.rawData(),33);
3156 graphStr=buf.data();
3157 //printf("md5: %s | file: %s\n",sigStr,baseName.data());
3161 static bool updateDotGraph(DotNode *root,
3162 DotNode::GraphType gt,
3163 const QCString &baseName,
3164 GraphOutputFormat format,
3165 const QCString &rank,
3168 const QCString &title=QCString()
3172 // TODO: write graph to theGraph, then compute md5 checksum
3173 QCString md5 = computeMd5Signature(
3174 root,gt,format,rank,renderParents,
3175 backArrows,title,theGraph);
3176 QFile f(baseName+".dot");
3177 if (f.open(IO_WriteOnly))
3182 return checkAndUpdateMd5Signature(baseName,md5); // graph needs to be regenerated
3185 QCString DotClassGraph::writeGraph(FTextStream &out,
3186 GraphOutputFormat graphFormat,
3187 EmbeddedOutputFormat textFormat,
3189 const char *fileName,
3190 const char *relPath,
3192 bool generateImageMap,
3196 // store the original directory
3199 err("Output dir %s does not exist!\n",path); exit(1);
3201 static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
3205 switch (m_graphType)
3207 case DotNode::Collaboration:
3209 baseName=m_collabFileName;
3211 case DotNode::Inheritance:
3212 mapName="inherit_map";
3213 baseName=m_inheritFileName;
3220 // derive target file names from baseName
3221 QCString imgExt = getDotImageExtension();
3222 QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
3223 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3224 QCString absDotName = absBaseName+".dot";
3225 QCString absMapName = absBaseName+".map";
3226 QCString absPdfName = absBaseName+".pdf";
3227 QCString absEpsName = absBaseName+".eps";
3228 QCString absImgName = absBaseName+"."+imgExt;
3230 bool regenerate = FALSE;
3231 if (updateDotGraph(m_startNode,
3235 m_lrRank ? "LR" : "",
3236 m_graphType==DotNode::Inheritance,
3238 m_startNode->label()
3240 !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
3241 usePDFLatex ? absPdfName : absEpsName,
3242 graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString())
3246 if (graphFormat==GOF_BITMAP) // run dot to create a bitmap image
3248 DotRunner *dotRun = new DotRunner(absDotName,
3249 d.absPath().data(),TRUE,absImgName);
3250 dotRun->addJob(imgFmt,absImgName);
3251 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3252 DotManager::instance()->addRun(dotRun);
3255 else if (graphFormat==GOF_EPS) // run dot to create a .eps image
3257 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3260 dotRun->addJob("pdf",absPdfName,absBaseName);
3264 dotRun->addJob("ps",absEpsName);
3266 DotManager::instance()->addRun(dotRun);
3269 Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3271 if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
3273 out << "<para>" << endl;
3274 out << " <informalfigure>" << endl;
3275 out << " <mediaobject>" << endl;
3276 out << " <imageobject>" << endl;
3277 out << " <imagedata";
3278 out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
3279 out << "</imagedata>" << endl;
3280 out << " </imageobject>" << endl;
3281 out << " </mediaobject>" << endl;
3282 out << " </informalfigure>" << endl;
3283 out << "</para>" << endl;
3285 else if (graphFormat==GOF_BITMAP && generateImageMap) // produce HTML to include the image
3287 QCString mapLabel = escapeCharsInString(m_startNode->m_label,FALSE)+"_"+
3288 escapeCharsInString(mapName,FALSE);
3289 if (imgExt=="svg") // add link to SVG file without map file
3291 out << "<div class=\"center\">";
3292 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3296 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3298 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3299 out << "<!-- SVG " << mapId << " -->" << endl;
3301 out << "</div>" << endl;
3303 else // add link to bitmap file with image map
3305 out << "<div class=\"center\">";
3306 out << "<img src=\"" << relPath << baseName << "."
3307 << imgExt << "\" border=\"0\" usemap=\"#"
3308 << mapLabel << "\" alt=\"";
3309 switch (m_graphType)
3311 case DotNode::Collaboration:
3312 out << "Collaboration graph";
3314 case DotNode::Inheritance:
3315 out << "Inheritance graph";
3322 out << "</div>" << endl;
3323 if (regenerate || !insertMapFile(out,absMapName,relPath,mapLabel))
3325 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3326 FALSE,QCString(),mapLabel);
3327 out << "<!-- MAP " << mapId << " -->" << endl;
3331 else if (graphFormat==GOF_EPS) // produce tex to include the .eps image
3333 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3335 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE /*TRUE*/);
3336 out << endl << "% FIG " << figId << endl;
3339 if (!regenerate) removeDotGraph(absDotName);
3344 //--------------------------------------------------------------------
3346 void DotClassGraph::writeXML(FTextStream &t)
3348 QDictIterator<DotNode> dni(*m_usedNodes);
3350 for (;(node=dni.current());++dni)
3352 node->writeXML(t,TRUE);
3356 void DotClassGraph::writeDocbook(FTextStream &t)
3358 QDictIterator<DotNode> dni(*m_usedNodes);
3360 for (;(node=dni.current());++dni)
3362 node->writeDocbook(t,TRUE);
3366 void DotClassGraph::writeDEF(FTextStream &t)
3368 QDictIterator<DotNode> dni(*m_usedNodes);
3370 for (;(node=dni.current());++dni)
3376 //--------------------------------------------------------------------
3378 void DotInclDepGraph::buildGraph(DotNode *n,FileDef *fd,int distance)
3380 QList<IncludeInfo> *includeFiles =
3381 m_inverse ? fd->includedByFileList() : fd->includeFileList();
3384 QListIterator<IncludeInfo> ili(*includeFiles);
3386 for (;(ii=ili.current());++ili)
3388 FileDef *bfd = ii->fileDef;
3389 QCString in = ii->includeName;
3390 //printf(">>>> in=`%s' bfd=%p\n",ii->includeName.data(),bfd);
3391 bool doc=TRUE,src=FALSE;
3394 in = bfd->absFilePath();
3395 doc = bfd->isLinkable() && !bfd->isHidden();
3396 src = bfd->generateSourceFile();
3398 if (doc || src || !Config_getBool(HIDE_UNDOC_RELATIONS))
3401 if (bfd) url=bfd->getOutputFileBase().copy();
3404 url=bfd->getSourceFileBase();
3406 DotNode *bn = m_usedNodes->find(in);
3407 if (bn) // file is already a node in the graph
3409 n->addChild(bn,0,0,0);
3411 bn->setDistance(distance);
3419 tmp_url=doc || src ? bfd->getReference()+"$"+url : QCString();
3420 tooltip = bfd->briefDescriptionAsTooltip();
3423 m_curNodeNumber++, // n
3424 ii->includeName, // label
3430 n->addChild(bn,0,0,0);
3432 m_usedNodes->insert(in,bn);
3433 bn->setDistance(distance);
3435 if (bfd) buildGraph(bn,bfd,distance+1);
3442 void DotInclDepGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
3444 while (queue.count()>0 && maxNodes>0)
3446 static int maxDistance = Config_getInt(MAX_DOT_GRAPH_DEPTH);
3447 DotNode *n = queue.take(0);
3448 if (!n->isVisible() && n->distance()<=maxDistance) // not yet processed
3452 // add direct children
3455 QListIterator<DotNode> li(*n->m_children);
3457 for (li.toFirst();(dn=li.current());++li)
3466 void DotInclDepGraph::determineTruncatedNodes(QList<DotNode> &queue)
3468 while (queue.count()>0)
3470 DotNode *n = queue.take(0);
3471 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
3473 bool truncated = FALSE;
3476 QListIterator<DotNode> li(*n->m_children);
3478 for (li.toFirst();(dn=li.current());++li)
3480 if (!dn->isVisible())
3486 n->markAsTruncated(truncated);
3491 int DotInclDepGraph::m_curNodeNumber = 0;
3493 void DotInclDepGraph::resetNumbering()
3495 m_curNodeNumber = 0;
3498 DotInclDepGraph::DotInclDepGraph(FileDef *fd,bool inverse)
3500 m_inverse = inverse;
3502 m_inclDepFileName = fd->includeDependencyGraphFileName();
3503 m_inclByDepFileName = fd->includedByDependencyGraphFileName();
3504 QCString tmp_url=fd->getReference()+"$"+fd->getOutputFileBase();
3505 QCString tooltip = fd->briefDescriptionAsTooltip();
3506 m_startNode = new DotNode(m_curNodeNumber++,
3512 m_startNode->setDistance(0);
3513 m_usedNodes = new QDict<DotNode>(1009);
3514 m_usedNodes->insert(fd->absFilePath(),m_startNode);
3515 buildGraph(m_startNode,fd,1);
3517 static int nodes = Config_getInt(DOT_GRAPH_MAX_NODES);
3518 int maxNodes = nodes;
3519 //int directChildNodes = 1;
3520 //if (m_startNode->m_children!=0)
3521 // directChildNodes+=m_startNode->m_children->count();
3522 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3523 QList<DotNode> openNodeQueue;
3524 openNodeQueue.append(m_startNode);
3525 determineVisibleNodes(openNodeQueue,maxNodes);
3526 openNodeQueue.clear();
3527 openNodeQueue.append(m_startNode);
3528 determineTruncatedNodes(openNodeQueue);
3531 DotInclDepGraph::~DotInclDepGraph()
3533 deleteNodes(m_startNode);
3537 QCString DotInclDepGraph::writeGraph(FTextStream &out,
3538 GraphOutputFormat graphFormat,
3539 EmbeddedOutputFormat textFormat,
3541 const char *fileName,
3542 const char *relPath,
3543 bool generateImageMap,
3548 // store the original directory
3551 err("Output dir %s does not exist!\n",path); exit(1);
3553 static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
3558 baseName=m_inclByDepFileName;
3562 baseName=m_inclDepFileName;
3564 QCString mapName=escapeCharsInString(m_startNode->m_label,FALSE);
3565 if (m_inverse) mapName+="dep";
3567 QCString imgExt = getDotImageExtension();
3568 QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
3569 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3570 QCString absDotName = absBaseName+".dot";
3571 QCString absMapName = absBaseName+".map";
3572 QCString absPdfName = absBaseName+".pdf";
3573 QCString absEpsName = absBaseName+".eps";
3574 QCString absImgName = absBaseName+"."+imgExt;
3576 bool regenerate = FALSE;
3577 if (updateDotGraph(m_startNode,
3578 DotNode::Dependency,
3582 FALSE, // renderParents
3583 m_inverse, // backArrows
3584 m_startNode->label()
3586 !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
3587 usePDFLatex ? absPdfName : absEpsName,
3588 graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString())
3592 if (graphFormat==GOF_BITMAP)
3594 // run dot to create a bitmap image
3595 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3596 dotRun->addJob(imgFmt,absImgName);
3597 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3598 DotManager::instance()->addRun(dotRun);
3600 else if (graphFormat==GOF_EPS)
3602 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3605 dotRun->addJob("pdf",absPdfName,absBaseName);
3609 dotRun->addJob("ps",absEpsName);
3611 DotManager::instance()->addRun(dotRun);
3614 Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3616 if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
3618 out << "<para>" << endl;
3619 out << " <informalfigure>" << endl;
3620 out << " <mediaobject>" << endl;
3621 out << " <imageobject>" << endl;
3622 out << " <imagedata";
3623 out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
3624 out << "</imagedata>" << endl;
3625 out << " </imageobject>" << endl;
3626 out << " </mediaobject>" << endl;
3627 out << " </informalfigure>" << endl;
3628 out << "</para>" << endl;
3630 else if (graphFormat==GOF_BITMAP && generateImageMap)
3632 if (imgExt=="svg") // Scalable vector graphics
3634 out << "<div class=\"center\">";
3635 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3639 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3641 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3642 out << "<!-- SVG " << mapId << " -->" << endl;
3644 out << "</div>" << endl;
3646 else // bitmap graphics
3648 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "." << imgExt << "\" border=\"0\" usemap=\"#" << mapName << "\" alt=\"\"/>";
3649 out << "</div>" << endl;
3651 QCString absMapName = absBaseName+".map";
3652 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3654 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3655 FALSE,QCString(),mapName);
3656 out << "<!-- MAP " << mapId << " -->" << endl;
3660 else if (graphFormat==GOF_EPS) // encapsulated postscript
3662 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3664 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3665 out << endl << "% FIG " << figId << endl;
3668 if (!regenerate) removeDotGraph(absDotName);
3673 bool DotInclDepGraph::isTrivial() const
3675 return m_startNode->m_children==0;
3678 bool DotInclDepGraph::isTooBig() const
3680 static int maxNodes = Config_getInt(DOT_GRAPH_MAX_NODES);
3681 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
3682 return numNodes>=maxNodes;
3685 void DotInclDepGraph::writeXML(FTextStream &t)
3687 QDictIterator<DotNode> dni(*m_usedNodes);
3689 for (;(node=dni.current());++dni)
3691 node->writeXML(t,FALSE);
3695 void DotInclDepGraph::writeDocbook(FTextStream &t)
3697 QDictIterator<DotNode> dni(*m_usedNodes);
3699 for (;(node=dni.current());++dni)
3701 node->writeDocbook(t,FALSE);
3705 //-------------------------------------------------------------
3707 void DotCallGraph::buildGraph(DotNode *n,MemberDef *md,int distance)
3709 MemberSDict *refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers();
3712 MemberSDict::Iterator mri(*refs);
3714 for (;(rmd=mri.current());++mri)
3716 if (rmd->showInCallGraph())
3719 uniqueId=rmd->getReference()+"$"+
3720 rmd->getOutputFileBase()+"#"+rmd->anchor();
3721 DotNode *bn = m_usedNodes->find(uniqueId);
3722 if (bn) // file is already a node in the graph
3724 n->addChild(bn,0,0,0);
3726 bn->setDistance(distance);
3731 if (Config_getBool(HIDE_SCOPE_NAMES))
3733 name = rmd->getOuterScope()==m_scope ?
3734 rmd->name() : rmd->qualifiedName();
3738 name = rmd->qualifiedName();
3740 QCString tooltip = rmd->briefDescriptionAsTooltip();
3743 linkToText(rmd->getLanguage(),name,FALSE),
3748 n->addChild(bn,0,0,0);
3750 bn->setDistance(distance);
3751 m_usedNodes->insert(uniqueId,bn);
3753 buildGraph(bn,rmd,distance+1);
3760 void DotCallGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
3762 while (queue.count()>0 && maxNodes>0)
3764 static int maxDistance = Config_getInt(MAX_DOT_GRAPH_DEPTH);
3765 DotNode *n = queue.take(0);
3766 if (!n->isVisible() && n->distance()<=maxDistance) // not yet processed
3770 // add direct children
3773 QListIterator<DotNode> li(*n->m_children);
3775 for (li.toFirst();(dn=li.current());++li)
3784 void DotCallGraph::determineTruncatedNodes(QList<DotNode> &queue)
3786 while (queue.count()>0)
3788 DotNode *n = queue.take(0);
3789 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
3791 bool truncated = FALSE;
3794 QListIterator<DotNode> li(*n->m_children);
3796 for (li.toFirst();(dn=li.current());++li)
3798 if (!dn->isVisible())
3804 n->markAsTruncated(truncated);
3809 int DotCallGraph::m_curNodeNumber = 0;
3811 void DotCallGraph::resetNumbering()
3813 m_curNodeNumber = 0;
3816 DotCallGraph::DotCallGraph(MemberDef *md,bool inverse)
3818 m_inverse = inverse;
3819 m_diskName = md->getOutputFileBase()+"_"+md->anchor();
3820 m_scope = md->getOuterScope();
3822 uniqueId = md->getReference()+"$"+
3823 md->getOutputFileBase()+"#"+md->anchor();
3825 if (Config_getBool(HIDE_SCOPE_NAMES))
3831 name = md->qualifiedName();
3833 QCString tooltip = md->briefDescriptionAsTooltip();
3834 m_startNode = new DotNode(m_curNodeNumber++,
3835 linkToText(md->getLanguage(),name,FALSE),
3840 m_startNode->setDistance(0);
3841 m_usedNodes = new QDict<DotNode>(1009);
3842 m_usedNodes->insert(uniqueId,m_startNode);
3843 buildGraph(m_startNode,md,1);
3845 static int nodes = Config_getInt(DOT_GRAPH_MAX_NODES);
3846 int maxNodes = nodes;
3847 //int directChildNodes = 1;
3848 //if (m_startNode->m_children!=0)
3849 // directChildNodes+=m_startNode->m_children->count();
3850 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3851 QList<DotNode> openNodeQueue;
3852 openNodeQueue.append(m_startNode);
3853 determineVisibleNodes(openNodeQueue,maxNodes);
3854 openNodeQueue.clear();
3855 openNodeQueue.append(m_startNode);
3856 determineTruncatedNodes(openNodeQueue);
3859 DotCallGraph::~DotCallGraph()
3861 deleteNodes(m_startNode);
3865 QCString DotCallGraph::writeGraph(FTextStream &out, GraphOutputFormat graphFormat,
3866 EmbeddedOutputFormat textFormat,
3867 const char *path,const char *fileName,
3868 const char *relPath,bool generateImageMap,int
3872 // store the original directory
3875 err("Output dir %s does not exist!\n",path); exit(1);
3877 static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
3879 QCString baseName = m_diskName + (m_inverse ? "_icgraph" : "_cgraph");
3880 QCString mapName = baseName;
3882 QCString imgExt = getDotImageExtension();
3883 QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
3884 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3885 QCString absDotName = absBaseName+".dot";
3886 QCString absMapName = absBaseName+".map";
3887 QCString absPdfName = absBaseName+".pdf";
3888 QCString absEpsName = absBaseName+".eps";
3889 QCString absImgName = absBaseName+"."+imgExt;
3891 bool regenerate = FALSE;
3893 if (updateDotGraph(m_startNode,
3897 m_inverse ? "RL" : "LR", // lrRank
3898 FALSE, // renderParents
3899 m_inverse, // backArrows
3900 m_startNode->label()
3902 !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
3903 usePDFLatex ? absPdfName : absEpsName,
3904 graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString())
3908 if (graphFormat==GOF_BITMAP)
3910 // run dot to create a bitmap image
3911 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3912 dotRun->addJob(imgFmt,absImgName);
3913 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3914 DotManager::instance()->addRun(dotRun);
3917 else if (graphFormat==GOF_EPS)
3919 // run dot to create a .eps image
3920 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3923 dotRun->addJob("pdf",absPdfName,absBaseName);
3927 dotRun->addJob("ps",absEpsName);
3929 DotManager::instance()->addRun(dotRun);
3933 Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3935 if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
3937 out << "<para>" << endl;
3938 out << " <informalfigure>" << endl;
3939 out << " <mediaobject>" << endl;
3940 out << " <imageobject>" << endl;
3941 out << " <imagedata";
3942 out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
3943 out << "</imagedata>" << endl;
3944 out << " </imageobject>" << endl;
3945 out << " </mediaobject>" << endl;
3946 out << " </informalfigure>" << endl;
3947 out << "</para>" << endl;
3949 else if (graphFormat==GOF_BITMAP && generateImageMap)
3951 if (imgExt=="svg") // Scalable vector graphics
3953 out << "<div class=\"center\">";
3954 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3958 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3960 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3961 out << "<!-- SVG " << mapId << " -->" << endl;
3963 out << "</div>" << endl;
3965 else // bitmap graphics
3967 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3968 << imgExt << "\" border=\"0\" usemap=\"#"
3969 << mapName << "\" alt=\"";
3971 out << "</div>" << endl;
3973 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3975 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3976 FALSE,QCString(),mapName);
3977 out << "<!-- MAP " << mapId << " -->" << endl;
3981 else if (graphFormat==GOF_EPS) // encapsulated postscript
3983 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3985 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3986 out << endl << "% FIG " << figId << endl;
3989 if (!regenerate) removeDotGraph(absDotName);
3994 bool DotCallGraph::isTrivial() const
3996 return m_startNode->m_children==0;
3999 bool DotCallGraph::isTooBig() const
4001 static int maxNodes = Config_getInt(DOT_GRAPH_MAX_NODES);
4002 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
4003 return numNodes>=maxNodes;
4006 //-------------------------------------------------------------
4007 static void writeDotDirDepGraph(FTextStream &t,DirDef *dd,bool linkRelations);
4009 DotDirDeps::DotDirDeps(DirDef *dir) : m_dir(dir)
4013 DotDirDeps::~DotDirDeps()
4017 QCString DotDirDeps::writeGraph(FTextStream &out,
4018 GraphOutputFormat graphFormat,
4019 EmbeddedOutputFormat textFormat,
4021 const char *fileName,
4022 const char *relPath,
4023 bool generateImageMap,
4025 bool linkRelations) const
4028 // store the original directory
4031 err("Output dir %s does not exist!\n",path); exit(1);
4033 static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
4035 QCString baseName=m_dir->getOutputFileBase()+"_dep";
4036 QCString mapName=escapeCharsInString(baseName,FALSE);
4038 QCString imgExt = getDotImageExtension();
4039 QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
4040 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
4041 QCString absDotName = absBaseName+".dot";
4042 QCString absMapName = absBaseName+".map";
4043 QCString absPdfName = absBaseName+".pdf";
4044 QCString absEpsName = absBaseName+".eps";
4045 QCString absImgName = absBaseName+"."+imgExt;
4047 // compute md5 checksum of the graph were are about to generate
4049 FTextStream md5stream(&theGraph);
4050 //m_dir->writeDepGraph(md5stream);
4051 writeDotDirDepGraph(md5stream,m_dir,linkRelations);
4053 QCString sigStr(33);
4054 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
4055 MD5SigToString(md5_sig,sigStr.rawData(),33);
4056 bool regenerate=FALSE;
4057 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
4058 !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
4059 usePDFLatex ? absPdfName : absEpsName,
4060 graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString())
4065 QFile f(absDotName);
4066 if (!f.open(IO_WriteOnly))
4068 err("Cannot create file %s.dot for writing!\n",baseName.data());
4071 t << theGraph.data();
4074 if (graphFormat==GOF_BITMAP)
4076 // run dot to create a bitmap image
4077 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
4078 dotRun->addJob(imgFmt,absImgName);
4079 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
4080 DotManager::instance()->addRun(dotRun);
4082 else if (graphFormat==GOF_EPS)
4084 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
4087 dotRun->addJob("pdf",absPdfName,absBaseName);
4091 dotRun->addJob("ps",absEpsName);
4093 DotManager::instance()->addRun(dotRun);
4096 Doxygen::indexList->addImageFile(baseName+"."+imgExt);
4098 if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
4100 out << "<para>" << endl;
4101 out << " <informalfigure>" << endl;
4102 out << " <mediaobject>" << endl;
4103 out << " <imageobject>" << endl;
4104 out << " <imagedata";
4105 out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
4106 out << "</imagedata>" << endl;
4107 out << " </imageobject>" << endl;
4108 out << " </mediaobject>" << endl;
4109 out << " </informalfigure>" << endl;
4110 out << "</para>" << endl;
4112 else if (graphFormat==GOF_BITMAP && generateImageMap)
4114 if (imgExt=="svg") // Scalable vector graphics
4116 out << "<div class=\"center\">";
4117 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
4121 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
4123 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
4124 out << "<!-- SVG " << mapId << " -->" << endl;
4126 out << "</div>" << endl;
4128 else // bitmap graphics
4130 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
4131 << imgExt << "\" border=\"0\" usemap=\"#"
4132 << mapName << "\" alt=\"";
4133 out << convertToXML(m_dir->displayName());
4135 out << "</div>" << endl;
4137 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
4139 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
4140 TRUE,QCString(),mapName);
4141 out << "<!-- MAP " << mapId << " -->" << endl;
4145 else if (graphFormat==GOF_EPS)
4147 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
4149 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
4150 out << endl << "% FIG " << figId << endl;
4153 if (!regenerate) removeDotGraph(absDotName);
4158 bool DotDirDeps::isTrivial() const
4160 return m_dir->depGraphIsTrivial();
4163 //-------------------------------------------------------------
4165 void generateGraphLegend(const char *path)
4168 // store the original directory
4171 err("Output dir %s does not exist!\n",path); exit(1);
4175 FTextStream md5stream(&theGraph);
4176 writeGraphHeader(md5stream,theTranslator->trLegendTitle());
4177 md5stream << " Node9 [shape=\"box\",label=\"Inherited\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",fillcolor=\"grey75\",style=\"filled\" fontcolor=\"black\"];\n";
4178 md5stream << " Node10 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4179 md5stream << " Node10 [shape=\"box\",label=\"PublicBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPublicBase" << Doxygen::htmlFileExtension << "\"];\n";
4180 md5stream << " Node11 -> Node10 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4181 md5stream << " Node11 [shape=\"box\",label=\"Truncated\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"red\",URL=\"$classTruncated" << Doxygen::htmlFileExtension << "\"];\n";
4182 md5stream << " Node13 -> Node9 [dir=\"back\",color=\"darkgreen\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4183 md5stream << " Node13 [shape=\"box\",label=\"ProtectedBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classProtectedBase" << Doxygen::htmlFileExtension << "\"];\n";
4184 md5stream << " Node14 -> Node9 [dir=\"back\",color=\"firebrick4\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4185 md5stream << " Node14 [shape=\"box\",label=\"PrivateBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPrivateBase" << Doxygen::htmlFileExtension << "\"];\n";
4186 md5stream << " Node15 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4187 md5stream << " Node15 [shape=\"box\",label=\"Undocumented\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"grey75\"];\n";
4188 md5stream << " Node16 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4189 md5stream << " Node16 [shape=\"box\",label=\"Templ< int >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
4190 md5stream << " Node17 -> Node16 [dir=\"back\",color=\"orange\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"< int >\",fontname=\"" << FONTNAME << "\"];\n";
4191 md5stream << " Node17 [shape=\"box\",label=\"Templ< T >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
4192 md5stream << " Node18 -> Node9 [dir=\"back\",color=\"darkorchid3\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"m_usedClass\",fontname=\"" << FONTNAME << "\"];\n";
4193 md5stream << " Node18 [shape=\"box\",label=\"Used\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classUsed" << Doxygen::htmlFileExtension << "\"];\n";
4194 writeGraphFooter(md5stream);
4196 QCString sigStr(33);
4197 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
4198 MD5SigToString(md5_sig,sigStr.rawData(),33);
4199 QCString absBaseName = (QCString)path+"/graph_legend";
4200 QCString absDotName = absBaseName+".dot";
4201 QCString imgExt = getDotImageExtension();
4202 QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
4203 QCString imgName = "graph_legend."+imgExt;
4204 QCString absImgName = absBaseName+"."+imgExt;
4205 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
4206 !checkDeliverables(absImgName))
4208 QFile dotFile(absDotName);
4209 if (!dotFile.open(IO_WriteOnly))
4211 err("Could not open file %s for writing\n",dotFile.name().data());
4215 FTextStream dotText(&dotFile);
4216 dotText << theGraph;
4219 // run dot to generate the a bitmap image from the graph
4221 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
4222 dotRun->addJob(imgFmt,absImgName);
4223 DotManager::instance()->addRun(dotRun);
4227 removeDotGraph(absDotName);
4229 Doxygen::indexList->addImageFile(imgName);
4233 DotManager::instance()->addSVGObject(
4234 absBaseName+Config_getString(HTML_FILE_EXTENSION),
4236 absImgName,QCString());
4241 void writeDotGraphFromFile(const char *inFile,const char *outDir,
4242 const char *outFile,GraphOutputFormat format)
4247 err("Output dir %s does not exist!\n",outDir); exit(1);
4250 QCString imgExt = getDotImageExtension();
4251 QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
4252 QCString imgName = (QCString)outFile+"."+imgExt;
4253 QCString absImgName = d.absPath().utf8()+"/"+imgName;
4254 QCString absOutFile = d.absPath().utf8()+"/"+outFile;
4256 DotRunner dotRun(inFile,d.absPath().data(),FALSE,absImgName);
4257 if (format==GOF_BITMAP)
4258 dotRun.addJob(imgFmt,absImgName);
4259 else // format==GOF_EPS
4261 if (Config_getBool(USE_PDFLATEX))
4263 dotRun.addJob("pdf",absOutFile+".pdf",absOutFile);
4267 dotRun.addJob("ps",absOutFile+".eps");
4271 dotRun.preventCleanUp();
4277 if (format==GOF_BITMAP) checkDotResult(getDotImageExtension(),absImgName);
4279 Doxygen::indexList->addImageFile(imgName);
4284 /*! Writes user defined image map to the output.
4285 * \param t text stream to write to
4286 * \param inFile just the basename part of the filename
4287 * \param outDir output directory
4288 * \param relPath relative path the to root of the output dir
4289 * \param baseName the base name of the output files
4290 * \param context the scope in which this graph is found (for resolving links)
4291 * \param graphId a unique id for this graph, use for dynamic sections
4293 void writeDotImageMapFromFile(FTextStream &t,
4294 const QCString &inFile, const QCString &outDir,
4295 const QCString &relPath, const QCString &baseName,
4296 const QCString &context,int graphId)
4302 err("Output dir %s does not exist!\n",outDir.data()); exit(1);
4305 QCString mapName = baseName+".map";
4306 QCString imgExt = getDotImageExtension();
4307 QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
4308 QCString imgName = baseName+"."+imgExt;
4309 QCString absOutFile = d.absPath().utf8()+"/"+mapName;
4311 DotRunner dotRun(inFile,d.absPath().data(),FALSE);
4312 dotRun.addJob(MAP_CMD,absOutFile);
4313 dotRun.preventCleanUp();
4319 if (imgExt=="svg") // vector graphics
4321 //writeSVGFigureLink(t,relPath,inFile,inFile+".svg");
4322 //DotFilePatcher patcher(inFile+".svg");
4323 QCString svgName=outDir+"/"+baseName+".svg";
4324 writeSVGFigureLink(t,relPath,baseName,svgName);
4325 DotFilePatcher patcher(svgName);
4326 patcher.addSVGConversion(relPath,TRUE,context,TRUE,graphId);
4329 else // bitmap graphics
4332 FTextStream tt(&result);
4334 t << "<img src=\"" << relPath << imgName << "\" alt=\""
4335 << imgName << "\" border=\"0\" usemap=\"#" << mapName << "\"/>" << endl;
4336 convertMapFile(tt, absOutFile, relPath ,TRUE, context);
4337 if (!result.isEmpty())
4339 t << "<map name=\"" << mapName << "\" id=\"" << mapName << "\">";
4341 t << "</map>" << endl;
4344 d.remove(absOutFile);
4347 //-------------------------------------------------------------
4349 int DotGroupCollaboration::m_curNodeNumber = 0;
4351 void DotGroupCollaboration::resetNumbering()
4353 m_curNodeNumber = 0;
4356 DotGroupCollaboration::DotGroupCollaboration(GroupDef* gd)
4358 QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase();
4359 m_usedNodes = new QDict<DotNode>(1009);
4360 QCString tooltip = gd->briefDescriptionAsTooltip();
4361 m_rootNode = new DotNode(m_curNodeNumber++, gd->groupTitle(), tooltip, tmp_url, TRUE );
4362 m_rootNode->markAsVisible();
4363 m_usedNodes->insert(gd->name(), m_rootNode );
4364 m_edges.setAutoDelete(TRUE);
4366 m_diskName = gd->getOutputFileBase();
4371 DotGroupCollaboration::~DotGroupCollaboration()
4376 void DotGroupCollaboration::buildGraph(GroupDef* gd)
4379 //===========================
4383 GroupList *groups = gd->partOfGroups();
4386 GroupListIterator gli(*groups);
4388 for (gli.toFirst();(d=gli.current());++gli)
4390 DotNode* nnode = m_usedNodes->find(d->name());
4393 tmp_url = d->getReference()+"$"+d->getOutputFileBase();
4394 QCString tooltip = d->briefDescriptionAsTooltip();
4395 nnode = new DotNode(m_curNodeNumber++, d->groupTitle(), tooltip, tmp_url );
4396 nnode->markAsVisible();
4397 m_usedNodes->insert(d->name(), nnode );
4400 addEdge( nnode, m_rootNode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
4405 if ( gd->getSubGroups() && gd->getSubGroups()->count() )
4407 QListIterator<GroupDef> defli(*gd->getSubGroups());
4409 for (;(def=defli.current());++defli)
4411 DotNode* nnode = m_usedNodes->find(def->name());
4414 tmp_url = def->getReference()+"$"+def->getOutputFileBase();
4415 QCString tooltip = def->briefDescriptionAsTooltip();
4416 nnode = new DotNode(m_curNodeNumber++, def->groupTitle(), tooltip, tmp_url );
4417 nnode->markAsVisible();
4418 m_usedNodes->insert(def->name(), nnode );
4421 addEdge( m_rootNode, nnode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
4425 //=======================
4426 // Write collaboration
4429 addMemberList( gd->getMemberList(MemberListType_allMembersList) );
4432 if ( gd->getClasses() && gd->getClasses()->count() )
4434 ClassSDict::Iterator defli(*gd->getClasses());
4436 for (;(def=defli.current());++defli)
4438 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4439 if (!def->anchor().isEmpty())
4441 tmp_url+="#"+def->anchor();
4443 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tclass );
4448 if ( gd->getNamespaces() && gd->getNamespaces()->count() )
4450 NamespaceSDict::Iterator defli(*gd->getNamespaces());
4452 for (;(def=defli.current());++defli)
4454 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4455 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tnamespace );
4460 if ( gd->getFiles() && gd->getFiles()->count() )
4462 QListIterator<FileDef> defli(*gd->getFiles());
4464 for (;(def=defli.current());++defli)
4466 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4467 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tfile );
4472 if ( gd->getPages() && gd->getPages()->count() )
4474 PageSDict::Iterator defli(*gd->getPages());
4476 for (;(def=defli.current());++defli)
4478 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4479 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tpages );
4484 if ( gd->getDirs() && gd->getDirs()->count() )
4486 QListIterator<DirDef> defli(*gd->getDirs());
4488 for (;(def=defli.current());++defli)
4490 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4491 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tdir );
4496 void DotGroupCollaboration::addMemberList( MemberList* ml )
4498 if ( !( ml && ml->count()) ) return;
4499 MemberListIterator defli(*ml);
4501 for (;(def=defli.current());++defli)
4503 QCString tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension
4505 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tmember );
4509 DotGroupCollaboration::Edge* DotGroupCollaboration::addEdge(
4510 DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType,
4511 const QCString& _label, const QCString& _url )
4513 // search a existing link.
4514 QListIterator<Edge> lli(m_edges);
4516 for ( lli.toFirst(); (newEdge=lli.current()); ++lli)
4518 if ( newEdge->pNStart==_pNStart &&
4519 newEdge->pNEnd==_pNEnd &&
4520 newEdge->eType==_eType
4522 { // edge already found
4526 if ( newEdge==0 ) // new link
4528 newEdge = new Edge(_pNStart,_pNEnd,_eType);
4529 m_edges.append( newEdge );
4532 if (!_label.isEmpty())
4534 newEdge->links.append(new Link(_label,_url));
4540 void DotGroupCollaboration::addCollaborationMember(
4541 Definition* def, QCString& url, EdgeType eType )
4543 // Create group nodes
4544 if ( !def->partOfGroups() )
4546 GroupListIterator gli(*def->partOfGroups());
4549 for (;(d=gli.current());++gli)
4551 DotNode* nnode = m_usedNodes->find(d->name());
4552 if ( nnode != m_rootNode )
4556 tmp_str = d->getReference()+"$"+d->getOutputFileBase();
4557 QCString tooltip = d->briefDescriptionAsTooltip();
4558 nnode = new DotNode(m_curNodeNumber++, d->groupTitle(), tooltip, tmp_str );
4559 nnode->markAsVisible();
4560 m_usedNodes->insert(d->name(), nnode );
4562 tmp_str = def->qualifiedName();
4563 addEdge( m_rootNode, nnode, eType, tmp_str, url );
4569 QCString DotGroupCollaboration::writeGraph( FTextStream &t,
4570 GraphOutputFormat graphFormat, EmbeddedOutputFormat textFormat,
4571 const char *path, const char *fileName, const char *relPath,
4572 bool writeImageMap,int graphId) const
4575 // store the original directory
4578 err("Output dir %s does not exist!\n",path); exit(1);
4580 static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
4583 FTextStream md5stream(&theGraph);
4584 writeGraphHeader(md5stream,m_rootNode->label());
4586 // clean write flags
4587 QDictIterator<DotNode> dni(*m_usedNodes);
4589 for (dni.toFirst();(pn=dni.current());++dni)
4591 pn->clearWriteFlag();
4594 // write other nodes.
4595 for (dni.toFirst();(pn=dni.current());++dni)
4597 pn->write(md5stream,DotNode::Inheritance,graphFormat,TRUE,FALSE,FALSE);
4601 QListIterator<Edge> eli(m_edges);
4603 for (eli.toFirst();(edge=eli.current());++eli)
4605 edge->write( md5stream );
4608 writeGraphFooter(md5stream);
4610 QCString sigStr(33);
4611 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
4612 MD5SigToString(md5_sig,sigStr.rawData(),33);
4613 QCString imgExt = getDotImageExtension();
4614 QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
4615 QCString baseName = m_diskName;
4616 QCString imgName = baseName+"."+imgExt;
4617 QCString absPath = d.absPath().data();
4618 QCString absBaseName = absPath+"/"+baseName;
4619 QCString absDotName = absBaseName+".dot";
4620 QCString absImgName = absBaseName+"."+imgExt;
4621 QCString absMapName = absBaseName+".map";
4622 QCString absPdfName = absBaseName+".pdf";
4623 QCString absEpsName = absBaseName+".eps";
4624 bool regenerate=FALSE;
4625 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
4626 !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
4627 usePDFLatex ? absPdfName : absEpsName,
4628 graphFormat==GOF_BITMAP /*&& generateImageMap*/ ? absMapName : QCString())
4633 QFile dotfile(absDotName);
4634 if (dotfile.open(IO_WriteOnly))
4636 FTextStream tdot(&dotfile);
4641 if (graphFormat==GOF_BITMAP) // run dot to create a bitmap image
4643 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
4644 dotRun->addJob(imgFmt,absImgName);
4645 if (writeImageMap) dotRun->addJob(MAP_CMD,absMapName);
4646 DotManager::instance()->addRun(dotRun);
4649 else if (graphFormat==GOF_EPS)
4651 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
4654 dotRun->addJob("pdf",absPdfName,absBaseName);
4658 dotRun->addJob("ps",absEpsName);
4660 DotManager::instance()->addRun(dotRun);
4664 if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
4666 t << "<para>" << endl;
4667 t << " <informalfigure>" << endl;
4668 t << " <mediaobject>" << endl;
4669 t << " <imageobject>" << endl;
4671 t << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
4672 t << "</imagedata>" << endl;
4673 t << " </imageobject>" << endl;
4674 t << " </mediaobject>" << endl;
4675 t << " </informalfigure>" << endl;
4676 t << "</para>" << endl;
4678 else if (graphFormat==GOF_BITMAP && writeImageMap)
4680 QCString mapLabel = escapeCharsInString(baseName,FALSE);
4681 t << "<center><table><tr><td>";
4685 t << "<div class=\"center\">";
4686 if (regenerate || !writeSVGFigureLink(t,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
4690 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
4692 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
4693 t << "<!-- SVG " << mapId << " -->" << endl;
4695 t << "</div>" << endl;
4699 t << "<img src=\"" << relPath << imgName
4700 << "\" border=\"0\" alt=\"\" usemap=\"#"
4701 << mapLabel << "\"/>" << endl;
4702 if (regenerate || !insertMapFile(t,absMapName,relPath,mapLabel))
4704 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
4705 FALSE,QCString(),mapLabel);
4706 t << "<!-- MAP " << mapId << " -->" << endl;
4709 t << "</td></tr></table></center>" << endl;
4711 else if (graphFormat==GOF_EPS)
4713 if (regenerate || !writeVecGfxFigure(t,baseName,absBaseName))
4715 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
4716 t << endl << "% FIG " << figId << endl;
4719 if (!regenerate) removeDotGraph(absDotName);
4724 void DotGroupCollaboration::Edge::write( FTextStream &t ) const
4726 const char* linkTypeColor[] = {
4735 QCString arrowStyle = "dir=\"none\", style=\"dashed\"";
4736 t << " Node" << pNStart->number();
4738 t << "Node" << pNEnd->number();
4740 t << " [shape=plaintext";
4741 if (links.count()>0) // there are links
4744 // HTML-like edge labels crash on my Mac with Graphviz 2.0! and
4745 // are not supported by older version of dot.
4747 //t << label=<<TABLE BORDER=\"0\" CELLBORDER=\"0\">";
4748 //QListIterator<Link> lli(links);
4750 //for( lli.toFirst(); (link=lli.current()); ++lli)
4753 // if ( !link->url.isEmpty() )
4754 // t << " HREF=\"" << link->url << "\"";
4755 // t << ">" << link->label << "</TD></TR>";
4760 QListIterator<Link> lli(links);
4764 const int maxLabels = 10;
4765 for( lli.toFirst(); (link=lli.current()) && count<maxLabels; ++lli,++count)
4767 if (first) first=FALSE; else t << "\\n";
4768 t << convertLabel(link->label);
4770 if (count==maxLabels) t << "\\n...";
4777 arrowStyle = "dir=\"back\", style=\"solid\"";
4780 t << ", color=\"" << linkTypeColor[(int)eType] << "\"";
4783 t << ", " << arrowStyle;
4787 bool DotGroupCollaboration::isTrivial() const
4789 return m_usedNodes->count() <= 1;
4792 void DotGroupCollaboration::writeGraphHeader(FTextStream &t,
4793 const QCString &title) const
4796 if (title.isEmpty())
4798 t << "\"Dot Graph\"";
4802 t << "\"" << convertToXML(title) << "\"";
4806 if (Config_getBool(DOT_TRANSPARENT))
4808 t << " bgcolor=\"transparent\";" << endl;
4810 t << " edge [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\","
4811 "labelfontname=\"" << FONTNAME << "\",labelfontsize=\"" << FONTSIZE << "\"];\n";
4812 t << " node [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\",shape=box];\n";
4813 t << " rankdir=LR;\n";
4816 void writeDotDirDepGraph(FTextStream &t,DirDef *dd,bool linkRelations)
4818 t << "digraph \"" << dd->displayName() << "\" {\n";
4819 if (Config_getBool(DOT_TRANSPARENT))
4821 t << " bgcolor=transparent;\n";
4823 t << " compound=true\n";
4824 t << " node [ fontsize=\"" << FONTSIZE << "\", fontname=\"" << FONTNAME << "\"];\n";
4825 t << " edge [ labelfontsize=\"" << FONTSIZE << "\", labelfontname=\"" << FONTNAME << "\"];\n";
4827 QDict<DirDef> dirsInGraph(257);
4829 dirsInGraph.insert(dd->getOutputFileBase(),dd);
4832 t << " subgraph cluster" << dd->parent()->getOutputFileBase() << " {\n";
4833 t << " graph [ bgcolor=\"#ddddee\", pencolor=\"black\", label=\""
4834 << dd->parent()->shortName()
4835 << "\" fontname=\"" << FONTNAME << "\", fontsize=\"" << FONTSIZE << "\", URL=\"";
4836 t << dd->parent()->getOutputFileBase() << Doxygen::htmlFileExtension;
4839 if (dd->isCluster())
4841 t << " subgraph cluster" << dd->getOutputFileBase() << " {\n";
4842 t << " graph [ bgcolor=\"#eeeeff\", pencolor=\"black\", label=\"\""
4843 << " URL=\"" << dd->getOutputFileBase() << Doxygen::htmlFileExtension
4845 t << " " << dd->getOutputFileBase() << " [shape=plaintext label=\""
4846 << dd->shortName() << "\"];\n";
4848 // add nodes for sub directories
4849 QListIterator<DirDef> sdi(dd->subDirs());
4851 for (sdi.toFirst();(sdir=sdi.current());++sdi)
4853 t << " " << sdir->getOutputFileBase() << " [shape=box label=\""
4854 << sdir->shortName() << "\"";
4855 if (sdir->isCluster())
4857 t << " color=\"red\"";
4861 t << " color=\"black\"";
4863 t << " fillcolor=\"white\" style=\"filled\"";
4864 t << " URL=\"" << sdir->getOutputFileBase()
4865 << Doxygen::htmlFileExtension << "\"";
4867 dirsInGraph.insert(sdir->getOutputFileBase(),sdir);
4873 t << " " << dd->getOutputFileBase() << " [shape=box, label=\""
4874 << dd->shortName() << "\", style=\"filled\", fillcolor=\"#eeeeff\","
4875 << " pencolor=\"black\", URL=\"" << dd->getOutputFileBase()
4876 << Doxygen::htmlFileExtension << "\"];\n";
4883 // add nodes for other used directories
4884 QDictIterator<UsedDir> udi(*dd->usedDirs());
4886 //printf("*** For dir %s\n",shortName().data());
4887 for (udi.toFirst();(udir=udi.current());++udi)
4888 // for each used dir (=directly used or a parent of a directly used dir)
4890 const DirDef *usedDir=udir->dir();
4894 //printf("*** check relation %s->%s same_parent=%d !%s->isParentOf(%s)=%d\n",
4895 // dir->shortName().data(),usedDir->shortName().data(),
4896 // dir->parent()==usedDir->parent(),
4897 // usedDir->shortName().data(),
4898 // shortName().data(),
4899 // !usedDir->isParentOf(this)
4901 if (dir!=usedDir && dir->parent()==usedDir->parent() &&
4902 !usedDir->isParentOf(dd))
4903 // include if both have the same parent (or no parent)
4905 t << " " << usedDir->getOutputFileBase() << " [shape=box label=\""
4906 << usedDir->shortName() << "\"";
4907 if (usedDir->isCluster())
4909 if (!Config_getBool(DOT_TRANSPARENT))
4911 t << " fillcolor=\"white\" style=\"filled\"";
4913 t << " color=\"red\"";
4915 t << " URL=\"" << usedDir->getOutputFileBase()
4916 << Doxygen::htmlFileExtension << "\"];\n";
4917 dirsInGraph.insert(usedDir->getOutputFileBase(),usedDir);
4924 // add relations between all selected directories
4926 QDictIterator<DirDef> di(dirsInGraph);
4927 for (di.toFirst();(dir=di.current());++di) // foreach dir in the graph
4929 QDictIterator<UsedDir> udi(*dir->usedDirs());
4931 for (udi.toFirst();(udir=udi.current());++udi) // foreach used dir
4933 const DirDef *usedDir=udir->dir();
4934 if ((dir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4935 (usedDir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4936 !usedDir->isParentOf(dir) && // don't point to own parent
4937 dirsInGraph.find(usedDir->getOutputFileBase())) // only point to nodes that are in the graph
4939 QCString relationName;
4940 relationName.sprintf("dir_%06d_%06d",dir->dirCount(),usedDir->dirCount());
4941 if (Doxygen::dirRelations.find(relationName)==0)
4944 Doxygen::dirRelations.append(relationName,
4945 new DirRelation(relationName,dir,udir));
4947 int nrefs = udir->filePairs().count();
4948 t << " " << dir->getOutputFileBase() << "->"
4949 << usedDir->getOutputFileBase();
4950 t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5";
4953 t << " headhref=\"" << relationName << Doxygen::htmlFileExtension << "\"";
4963 void resetDotNodeNumbering()
4965 DotClassGraph::resetNumbering();
4966 DotInclDepGraph::resetNumbering();
4967 DotCallGraph::resetNumbering();
4968 DotGroupCollaboration::resetNumbering();