1 /*****************************************************************************
6 * Copyright (C) 1997-2014 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.
21 #define BITMAP W_BITMAP
31 #include <qwaitcondition.h>
40 #include "docparser.h"
45 #include "vhdldocgen.h"
46 #include "ftextstream.h"
48 #include "memberlist.h"
50 #include "classlist.h"
52 #include "namespacedef.h"
53 #include "memberdef.h"
54 #include "membergroup.h"
56 #define MAP_CMD "cmapx"
58 //#define FONTNAME "Helvetica"
59 #define FONTNAME getDotFontName()
60 #define FONTSIZE getDotFontSize()
62 //--------------------------------------------------------------------
64 static const char svgZoomHeader[] =
65 "<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"
66 "<style type=\"text/css\"><![CDATA[\n"
67 ".edge:hover path { stroke: red; }\n"
68 ".edge:hover polygon { stroke: red; fill: red; }\n"
70 "<script type=\"text/javascript\"><![CDATA[\n"
71 "var edges = document.getElementsByTagName('g');\n"
72 "if (edges && edges.length) {\n"
73 " for (var i=0;i<edges.length;i++) {\n"
74 " if (edges[i].id.substr(0,4)=='edge') {\n"
75 " edges[i].setAttribute('class','edge');\n"
81 " <circle id=\"rim\" cx=\"0\" cy=\"0\" r=\"7\"/>\n"
82 " <circle id=\"rim2\" cx=\"0\" cy=\"0\" r=\"3.5\"/>\n"
83 " <g id=\"zoomPlus\">\n"
84 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
85 " <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomplus.mouseover\" end=\"zoomplus.mouseout\"/>\n"
87 " <path d=\"M-4,0h8M0,-4v8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
89 " <g id=\"zoomMin\">\n"
90 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
91 " <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomminus.mouseover\" end=\"zoomminus.mouseout\"/>\n"
93 " <path d=\"M-4,0h8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
95 " <g id=\"dirArrow\">\n"
96 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
98 " <g id=\"resetDef\">\n"
99 " <use xlink:href=\"#rim2\" fill=\"#404040\">\n"
100 " <set attributeName=\"fill\" to=\"#808080\" begin=\"reset.mouseover\" end=\"reset.mouseout\"/>\n"
105 "<script type=\"text/javascript\">\n"
108 static const char svgZoomFooter[] =
110 " <g id=\"navigator\" transform=\"translate(0 0)\" fill=\"#404254\">\n"
111 " <rect fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\".5\" x=\"0\" y=\"0\" width=\"60\" height=\"60\"/>\n"
113 " <use id=\"zoomplus\" xlink:href=\"#zoomPlus\" x=\"17\" y=\"9\" onmousedown=\"handleZoom(evt,'in')\"/>\n"
115 " <use id=\"zoomminus\" xlink:href=\"#zoomMin\" x=\"42\" y=\"9\" onmousedown=\"handleZoom(evt,'out')\"/>\n"
117 " <use id=\"reset\" xlink:href=\"#resetDef\" x=\"30\" y=\"36\" onmousedown=\"handleReset()\"/>\n"
119 " <g id=\"arrowUp\" xlink:href=\"#dirArrow\" transform=\"translate(30 24)\" onmousedown=\"handlePan(0,-1)\">\n"
120 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
121 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowUp.mouseover\" end=\"arrowUp.mouseout\"/>\n"
123 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
126 " <g id=\"arrowRight\" xlink:href=\"#dirArrow\" transform=\"rotate(90) translate(36 -43)\" onmousedown=\"handlePan(1,0)\">\n"
127 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
128 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowRight.mouseover\" end=\"arrowRight.mouseout\"/>\n"
130 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
133 " <g id=\"arrowDown\" xlink:href=\"#dirArrow\" transform=\"rotate(180) translate(-30 -48)\" onmousedown=\"handlePan(0,1)\">\n"
134 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
135 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowDown.mouseover\" end=\"arrowDown.mouseout\"/>\n"
137 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
140 " <g id=\"arrowLeft\" xlink:href=\"#dirArrow\" transform=\"rotate(270) translate(-36 17)\" onmousedown=\"handlePan(-1,0)\">\n"
141 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
142 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowLeft.mouseover\" end=\"arrowLeft.mouseout\"/>\n"
144 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
147 // link to orginial SVG
148 " <svg viewBox=\"0 0 15 15\" width=\"100%\" height=\"30px\" preserveAspectRatio=\"xMaxYMin meet\">\n"
149 " <g id=\"arrow_out\" transform=\"scale(0.3 0.3)\">\n"
150 " <a xlink:href=\"$orgname\" target=\"_base\">\n"
151 " <rect id=\"button\" ry=\"5\" rx=\"5\" y=\"6\" x=\"6\" height=\"38\" width=\"38\"\n"
152 " fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\"1.0\"/>\n"
153 " <path id=\"arrow\"\n"
154 " 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"
155 " style=\"fill:#404040;\"/>\n"
162 //--------------------------------------------------------------------
164 static const int maxCmdLine = 40960;
166 /*! mapping from protection levels to color names */
167 static const char *normalEdgeColorMap[] =
169 "midnightblue", // Public
170 "darkgreen", // Protected
171 "firebrick4", // Private
172 "darkorchid3", // "use" relation
173 "grey75", // Undocumented
174 "orange" // template relation
177 static const char *normalArrowStyleMap[] =
180 "empty", // Protected
182 "open", // "use" relation
184 0 // template relation
187 static const char *normalEdgeStyleMap[] =
189 "solid", // inheritance
193 static const char *umlEdgeColorMap[] =
195 "midnightblue", // Public
196 "darkgreen", // Protected
197 "firebrick4", // Private
198 "grey25", // "use" relation
199 "grey75", // Undocumented
200 "orange" // template relation
203 static const char *umlArrowStyleMap[] =
206 "onormal", // Protected
207 "onormal", // Private
208 "odiamond", // "use" relation
210 0 // template relation
213 static const char *umlEdgeStyleMap[] =
215 "solid", // inheritance
219 /** Helper struct holding the properties of a edge in a dot graph. */
220 struct EdgeProperties
222 const char * const *edgeColorMap;
223 const char * const *arrowStyleMap;
224 const char * const *edgeStyleMap;
227 static EdgeProperties normalEdgeProps =
229 normalEdgeColorMap, normalArrowStyleMap, normalEdgeStyleMap
232 static EdgeProperties umlEdgeProps =
234 umlEdgeColorMap, umlArrowStyleMap, umlEdgeStyleMap
238 static QCString getDotFontName()
240 static QCString dotFontName = Config_getString("DOT_FONTNAME");
241 if (dotFontName.isEmpty())
243 //dotFontName="FreeSans.ttf";
244 dotFontName="Helvetica";
249 static int getDotFontSize()
251 static int dotFontSize = Config_getInt("DOT_FONTSIZE");
252 if (dotFontSize<4) dotFontSize=4;
256 static void writeGraphHeader(FTextStream &t,const QCString &title=QCString())
258 static bool interactiveSVG = Config_getBool("INTERACTIVE_SVG");
262 t << "\"Dot Graph\"";
266 t << "\"" << convertToXML(title) << "\"";
268 t << endl << "{" << endl;
269 if (interactiveSVG) // insert a comment to force regeneration when this
272 t << " // INTERACTIVE_SVG=YES\n";
274 if (Config_getBool("DOT_TRANSPARENT"))
276 t << " bgcolor=\"transparent\";" << endl;
278 t << " edge [fontname=\"" << FONTNAME << "\","
279 "fontsize=\"" << FONTSIZE << "\","
280 "labelfontname=\"" << FONTNAME << "\","
281 "labelfontsize=\"" << FONTSIZE << "\"];\n";
282 t << " node [fontname=\"" << FONTNAME << "\","
283 "fontsize=\"" << FONTSIZE << "\",shape=record];\n";
286 static void writeGraphFooter(FTextStream &t)
291 static QCString replaceRef(const QCString &buf,const QCString relPath,
292 bool urlOnly,const QCString &context,const QCString &target=QCString())
294 // search for href="...", store ... part in link
295 QCString href = "href";
296 //bool isXLink=FALSE;
298 int indexS = buf.find("href=\""), indexE;
299 if (indexS>5 && buf.find("xlink:href=\"")!=-1) // XLink href (for SVG)
303 href.prepend("xlink:");
306 if (indexS>=0 && (indexE=buf.find('"',indexS+len))!=-1)
308 QCString link = buf.mid(indexS+len,indexE-indexS-len);
310 if (urlOnly) // for user defined dot graphs
312 if (link.left(5)=="\\ref " || link.left(5)=="@ref ") // \ref url
315 // fake ref node to resolve the url
316 DocRef *df = new DocRef( (DocNode*) 0, link.mid(5), context );
317 result+=externalRef(relPath,df->ref(),TRUE);
318 if (!df->file().isEmpty())
319 result += df->file().data() + Doxygen::htmlFileExtension;
320 if (!df->anchor().isEmpty())
321 result += "#" + df->anchor();
327 result = href+"=\"" + link + "\"";
330 else // ref$url (external ref via tag file), or $url (local ref)
332 int marker = link.find('$');
335 QCString ref = link.left(marker);
336 QCString url = link.mid(marker+1);
339 result = externalLinkTarget() + 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())
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 if (!f.open(IO_ReadOnly))
382 err("problems opening map file %s for inclusion in the docs!\n"
383 "If you installed Graphviz/dot after a previous failing run, \n"
384 "try deleting the output directory and rerun doxygen.\n",mapName);
387 const int maxLineLen=10240;
388 while (!f.atEnd()) // foreach line
390 QCString buf(maxLineLen);
391 int numBytes = f.readLine(buf.data(),maxLineLen);
392 buf[numBytes-1]='\0';
394 if (buf.left(5)=="<area")
396 t << replaceRef(buf,relPath,urlOnly,context);
402 static QArray<int> s_newNumber;
403 static int s_max_newNumber=0;
405 inline int reNumberNode(int number, bool doReNumbering)
413 int s = s_newNumber.size();
417 ns = s * 3 / 2 + 5; // new size
418 if (number>=ns) // number still doesn't fit
420 ns = number * 3 / 2 + 5;
422 s_newNumber.resize(ns);
423 for (int i=s;i<ns;i++) // clear new part of the array
428 int i = s_newNumber.at(number);
429 if (i == 0) // not yet mapped
431 i = ++s_max_newNumber; // start from 1
432 s_newNumber.at(number) = i;
438 static void resetReNumbering()
441 s_newNumber.resize(s_max_newNumber);
444 static QCString g_dotFontPath;
446 static void setDotFontPath(const char *path)
448 ASSERT(g_dotFontPath.isEmpty());
449 g_dotFontPath = portable_getenv("DOTFONTPATH");
450 QCString newFontPath = Config_getString("DOT_FONTPATH");
451 QCString spath = path;
452 if (!newFontPath.isEmpty() && !spath.isEmpty())
454 newFontPath.prepend(spath+portable_pathListSeparator());
456 else if (newFontPath.isEmpty() && !spath.isEmpty())
462 portable_unsetenv("DOTFONTPATH");
465 portable_setenv("DOTFONTPATH",newFontPath);
468 static void unsetDotFontPath()
470 if (g_dotFontPath.isEmpty())
472 portable_unsetenv("DOTFONTPATH");
476 portable_setenv("DOTFONTPATH",g_dotFontPath);
481 static bool readBoundingBox(const char *fileName,int *width,int *height,bool isEps)
483 QCString bb = isEps ? QCString("%%PageBoundingBox:") : QCString("/MediaBox [");
485 if (!f.open(IO_ReadOnly|IO_Raw))
487 //printf("readBoundingBox: could not open %s\n",fileName);
490 const int maxLineLen=1024;
491 char buf[maxLineLen];
494 int numBytes = f.readLine(buf,maxLineLen-1); // read line
498 const char *p = strstr(buf,bb);
499 if (p) // found PageBoundingBox or /MediaBox string
502 if (sscanf(p+bb.length(),"%d %d %d %d",&x,&y,width,height)!=4)
504 //printf("readBoundingBox sscanf fail\n");
512 //printf("Read error %d!\n",numBytes);
516 err("Failed to extract bounding box from generated diagram file %s\n",fileName);
520 static bool writeVecGfxFigure(FTextStream &out,const QCString &baseName,
521 const QCString &figureName)
523 int width=400,height=550;
524 static bool usePdfLatex = Config_getBool("USE_PDFLATEX");
527 if (!readBoundingBox(figureName+".pdf",&width,&height,FALSE))
529 //printf("writeVecGfxFigure()=0\n");
535 if (!readBoundingBox(figureName+".eps",&width,&height,TRUE))
537 //printf("writeVecGfxFigure()=0\n");
541 //printf("Got PDF/EPS size %d,%d\n",width,height);
542 int maxWidth = 350; /* approx. page width in points, excl. margins */
543 int maxHeight = 550; /* approx. page height in points, excl. margins */
544 out << "\\nopagebreak\n"
545 "\\begin{figure}[H]\n"
548 if (width>maxWidth || height>maxHeight) // figure too big for page
550 // c*width/maxWidth > c*height/maxHeight, where c=maxWidth*maxHeight>0
551 if (width*maxHeight>height*maxWidth)
553 out << "\\includegraphics[width=" << maxWidth << "pt]";
557 out << "\\includegraphics[height=" << maxHeight << "pt]";
562 out << "\\includegraphics[width=" << width << "pt]";
565 out << "{" << baseName << "}\n"
569 //printf("writeVecGfxFigure()=1\n");
573 // extract size from a dot generated SVG file
574 static bool readSVGSize(const QCString &fileName,int *width,int *height)
578 if (!f.open(IO_ReadOnly))
582 const int maxLineLen=4096;
583 char buf[maxLineLen];
584 while (!f.atEnd() && !found)
586 int numBytes = f.readLine(buf,maxLineLen-1); // read line
590 if (qstrncmp(buf,"<!--zoomable ",13)==0)
594 sscanf(buf,"<!--zoomable %d",height);
595 //printf("Found zoomable for %s!\n",fileName.data());
598 else if (sscanf(buf,"<svg width=\"%dpt\" height=\"%dpt\"",width,height)==2)
600 //printf("Found fixed size %dx%d for %s!\n",*width,*height,fileName.data());
606 //printf("Read error %d!\n",numBytes);
613 static void writeSVGNotSupported(FTextStream &out)
615 out << "<p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p>";
618 // check if a reference to a SVG figure can be written and does so if possible.
619 // return FALSE if not possible (for instance because the SVG file is not yet generated).
620 static bool writeSVGFigureLink(FTextStream &out,const QCString &relPath,
621 const QCString &baseName,const QCString &absImgName)
623 int width=600,height=600;
624 if (!readSVGSize(absImgName,&width,&height))
633 height+=300; // add some extra space for zooming
634 if (height>600) height=600; // clip to maximum height of 600 pixels
635 out << "<div class=\"zoom\">";
636 //out << "<object type=\"image/svg+xml\" data=\""
637 //out << "<embed type=\"image/svg+xml\" src=\""
638 out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
639 << relPath << baseName << ".svg\" width=\"100%\" height=\"" << height << "\">";
643 //out << "<object type=\"image/svg+xml\" data=\""
644 //out << "<embed type=\"image/svg+xml\" src=\""
645 out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
646 << relPath << baseName << ".svg\" width=\""
647 << ((width*96+48)/72) << "\" height=\""
648 << ((height*96+48)/72) << "\">";
650 writeSVGNotSupported(out);
651 //out << "</object>";
662 // since dot silently reproduces the input file when it does not
663 // support the PNG format, we need to check the result.
664 static void checkDotResult(const QCString &imgName)
666 if (Config_getEnum("DOT_IMAGE_FORMAT")=="png")
668 FILE *f = portable_fopen(imgName,"rb");
672 if (fread(data,1,4,f)==4)
674 if (!(data[1]=='P' && data[2]=='N' && data[3]=='G'))
676 err("Image `%s' produced by dot is not a valid PNG!\n"
677 "You should either select a different format "
678 "(DOT_IMAGE_FORMAT in the config file) or install a more "
679 "recent version of graphviz (1.7+)\n",imgName.data()
685 err("Could not read image `%s' generated by dot!\n",imgName.data());
691 err("Could not open image `%s' generated by dot!\n",imgName.data());
696 static bool insertMapFile(FTextStream &out,const QCString &mapFile,
697 const QCString &relPath,const QCString &mapLabel)
699 QFileInfo fi(mapFile);
700 if (fi.exists() && fi.size()>0) // reuse existing map file
703 FTextStream tmpout(&tmpstr);
704 convertMapFile(tmpout,mapFile,relPath);
705 if (!tmpstr.isEmpty())
707 out << "<map name=\"" << mapLabel << "\" id=\"" << mapLabel << "\">" << endl;
709 out << "</map>" << endl;
713 return FALSE; // no map file yet, need to generate it
716 static void removeDotGraph(const QCString &dotName)
718 static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
728 /*! Checks if a file "baseName".md5 exists. If so the contents
729 * are compared with \a md5. If equal FALSE is returned. If the .md5
730 * file does not exist or its contents are not equal to \a md5,
731 * a new .md5 is generated with the \a md5 string as contents.
733 static bool checkAndUpdateMd5Signature(const QCString &baseName,
736 QFile f(baseName+".md5");
737 if (f.open(IO_ReadOnly))
740 QCString md5stored(33);
741 int bytesRead=f.readBlock(md5stored.data(),32);
744 if (bytesRead==32 && md5==md5stored)
751 // create checksum file
752 if (f.open(IO_WriteOnly))
754 f.writeBlock(md5.data(),32);
760 static bool checkDeliverables(const QCString &file1,
761 const QCString &file2=QCString())
765 if (!file1.isEmpty())
768 file1Ok = (fi.exists() && fi.size()>0);
770 if (!file2.isEmpty())
773 file2Ok = (fi.exists() && fi.size()>0);
775 return file1Ok && file2Ok;
778 //--------------------------------------------------------------------
780 /** Class representing a list of DotNode objects. */
781 class DotNodeList : public QList<DotNode>
784 DotNodeList() : QList<DotNode>() {}
787 int compareValues(const DotNode *n1,const DotNode *n2) const
789 return qstricmp(n1->m_label,n2->m_label);
793 //--------------------------------------------------------------------
795 DotRunner::DotRunner(const QCString &file,const QCString &path,
796 bool checkResult,const QCString &imageName)
797 : m_file(file), m_path(path),
798 m_checkResult(checkResult), m_imageName(imageName)
800 static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
801 m_cleanUp = dotCleanUp;
802 m_jobs.setAutoDelete(TRUE);
805 void DotRunner::addJob(const char *format,const char *output)
807 QCString args = QCString("-T")+format+" -o \""+output+"\"";
808 m_jobs.append(new QCString(args));
811 void DotRunner::addPostProcessing(const char *cmd,const char *args)
817 bool DotRunner::run()
820 QCString dotExe = Config_getString("DOT_PATH")+"dot";
821 bool multiTargets = Config_getBool("DOT_MULTI_TARGETS");
823 QListIterator<QCString> li(m_jobs);
825 QCString file = m_file;
826 QCString path = m_path;
827 QCString imageName = m_imageName;
828 QCString postCmd = m_postCmd;
829 QCString postArgs = m_postArgs;
830 bool checkResult = m_checkResult;
831 bool cleanUp = m_cleanUp;
834 dotArgs="\""+file+"\"";
835 for (li.toFirst();(s=li.current());++li)
840 if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
847 for (li.toFirst();(s=li.current());++li)
849 dotArgs="\""+file+"\" "+*s;
850 if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
856 if (!postCmd.isEmpty() && portable_system(postCmd,postArgs)!=0)
858 err("Problems running '%s' as a post-processing step for dot output\n",m_postCmd.data());
861 if (checkResult) checkDotResult(imageName);
864 //printf("removing dot file %s\n",m_file.data());
865 //QDir(path).remove(file);
866 m_cleanupItem.file = file;
867 m_cleanupItem.path = path;
871 err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n",
872 exitCode,dotExe.data(),dotArgs.data());
876 //--------------------------------------------------------------------
878 DotFilePatcher::DotFilePatcher(const char *patchFile)
879 : m_patchFile(patchFile)
881 m_maps.setAutoDelete(TRUE);
884 QCString DotFilePatcher::file() const
889 int DotFilePatcher::addMap(const QCString &mapFile,const QCString &relPath,
890 bool urlOnly,const QCString &context,const QCString &label)
892 int id = m_maps.count();
894 map->mapFile = mapFile;
895 map->relPath = relPath;
896 map->urlOnly = urlOnly;
897 map->context = context;
899 map->zoomable = FALSE;
905 int DotFilePatcher::addFigure(const QCString &baseName,
906 const QCString &figureName,bool heightCheck)
908 int id = m_maps.count();
910 map->mapFile = figureName;
911 map->urlOnly = heightCheck;
912 map->label = baseName;
913 map->zoomable = FALSE;
919 int DotFilePatcher::addSVGConversion(const QCString &relPath,bool urlOnly,
920 const QCString &context,bool zoomable,
923 int id = m_maps.count();
925 map->relPath = relPath;
926 map->urlOnly = urlOnly;
927 map->context = context;
928 map->zoomable = zoomable;
929 map->graphId = graphId;
934 int DotFilePatcher::addSVGObject(const QCString &baseName,
935 const QCString &absImgName,
936 const QCString &relPath)
938 int id = m_maps.count();
940 map->mapFile = absImgName;
941 map->relPath = relPath;
942 map->label = baseName;
943 map->zoomable = FALSE;
949 bool DotFilePatcher::run()
951 //printf("DotFilePatcher::run(): %s\n",m_patchFile.data());
952 static bool interactiveSVG = Config_getBool("INTERACTIVE_SVG");
953 bool isSVGFile = m_patchFile.right(4)==".svg";
958 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
959 interactiveSVG = interactiveSVG && map->zoomable;
960 graphId = map->graphId;
961 relPath = map->relPath;
962 //printf("DotFilePatcher::addSVGConversion: file=%s zoomable=%d\n",
963 // m_patchFile.data(),map->zoomable);
965 QString tmpName = QString::fromUtf8(m_patchFile+".tmp");
966 QString patchFile = QString::fromUtf8(m_patchFile);
967 if (!QDir::current().rename(patchFile,tmpName))
969 err("Failed to rename file %s to %s!\n",m_patchFile.data(),tmpName.data());
974 if (!fi.open(IO_ReadOnly))
976 err("problem opening file %s for patching!\n",tmpName.data());
977 QDir::current().rename(tmpName,patchFile);
980 if (!fo.open(IO_WriteOnly))
982 err("problem opening file %s for patching!\n",m_patchFile.data());
983 QDir::current().rename(tmpName,patchFile);
987 const int maxLineLen=100*1024;
990 bool insideHeader=FALSE;
991 bool replacedHeader=FALSE;
992 bool foundSize=FALSE;
993 while (!fi.atEnd()) // foreach line
995 QCString line(maxLineLen);
996 int numBytes = fi.readLine(line.data(),maxLineLen);
1002 //printf("line=[%s]\n",line.stripWhiteSpace().data());
1004 ASSERT(numBytes<maxLineLen);
1009 if (line.find("<svg")!=-1 && !replacedHeader)
1012 count = sscanf(line.data(),"<svg width=\"%dpt\" height=\"%dpt\"",&width,&height);
1013 //printf("width=%d height=%d\n",width,height);
1014 foundSize = count==2 && (width>500 || height>450);
1015 if (foundSize) insideHeader=TRUE;
1017 else if (insideHeader && !replacedHeader && line.find("<title>")!=-1)
1021 // insert special replacement header for interactive SVGs
1022 t << "<!--zoomable " << height << " -->\n";
1024 t << "var viewWidth = " << width << ";\n";
1025 t << "var viewHeight = " << height << ";\n";
1028 t << "var sectionId = 'dynsection-" << graphId << "';\n";
1031 t << "<script xlink:href=\"" << relPath << "svgpan.js\"/>\n";
1032 t << "<svg id=\"graph\" class=\"graph\">\n";
1033 t << "<g id=\"viewport\">\n";
1036 replacedHeader=TRUE;
1039 if (!insideHeader || !foundSize) // copy SVG and replace refs,
1040 // unless we are inside the header of the SVG.
1041 // Then we replace it with another header.
1043 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
1044 t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
1047 else if ((i=line.find("<!-- SVG"))!=-1 || (i=line.find("[!-- SVG"))!=-1)
1049 //printf("Found marker at %d\n",i);
1052 int n = sscanf(line.data()+i+1,"!-- SVG %d",&mapId);
1053 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1055 int e = QMAX(line.find("--]"),line.find("-->"));
1056 Map *map = m_maps.at(mapId);
1057 //printf("DotFilePatcher::writeSVGFigure: file=%s zoomable=%d\n",
1058 // m_patchFile.data(),map->zoomable);
1059 if (!writeSVGFigureLink(t,map->relPath,map->label,map->mapFile))
1061 err("Problem extracting size from SVG file %s\n",map->mapFile.data());
1063 if (e!=-1) t << line.mid(e+3);
1065 else // error invalid map id!
1067 err("Found invalid SVG id in file %s!\n",m_patchFile.data());
1071 else if ((i=line.find("<!-- MAP"))!=-1)
1075 int n = sscanf(line.data()+i,"<!-- MAP %d",&mapId);
1076 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1078 Map *map = m_maps.at(mapId);
1079 //printf("patching MAP %d in file %s with contents of %s\n",
1080 // mapId,m_patchFile.data(),map->mapFile.data());
1081 t << "<map name=\"" << map->label << "\" id=\"" << map->label << "\">" << endl;
1082 convertMapFile(t,map->mapFile,map->relPath,map->urlOnly,map->context);
1083 t << "</map>" << endl;
1085 else // error invalid map id!
1087 err("Found invalid MAP id in file %s!\n",m_patchFile.data());
1091 else if ((i=line.find("% FIG"))!=-1)
1094 int n = sscanf(line.data()+i+2,"FIG %d",&mapId);
1095 //printf("line='%s' n=%d\n",line.data()+i,n);
1096 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1098 Map *map = m_maps.at(mapId);
1099 //printf("patching FIG %d in file %s with contents of %s\n",
1100 // mapId,m_patchFile.data(),map->mapFile.data());
1101 writeVecGfxFigure(t,map->label,map->mapFile);
1103 else // error invalid map id!
1105 err("Found invalid bounding FIG %d in file %s!\n",mapId,m_patchFile.data());
1116 if (isSVGFile && interactiveSVG && replacedHeader)
1118 QCString orgName=m_patchFile.left(m_patchFile.length()-4)+"_org.svg";
1119 t << substitute(svgZoomFooter,"$orgname",stripPath(orgName));
1121 // keep original SVG file so we can refer to it, we do need to replace
1122 // dummy link by real ones
1125 if (!fi.open(IO_ReadOnly))
1127 err("problem opening file %s for reading!\n",tmpName.data());
1130 if (!fo.open(IO_WriteOnly))
1132 err("problem opening file %s for writing!\n",orgName.data());
1136 while (!fi.atEnd()) // foreach line
1138 QCString line(maxLineLen);
1139 int numBytes = fi.readLine(line.data(),maxLineLen);
1144 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
1145 t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
1150 // remove temporary file
1151 QDir::current().remove(tmpName);
1155 //--------------------------------------------------------------------
1157 void DotRunnerQueue::enqueue(DotRunner *runner)
1159 QMutexLocker locker(&m_mutex);
1160 m_queue.enqueue(runner);
1161 m_bufferNotEmpty.wakeAll();
1164 DotRunner *DotRunnerQueue::dequeue()
1166 QMutexLocker locker(&m_mutex);
1167 while (m_queue.isEmpty())
1169 // wait until something is added to the queue
1170 m_bufferNotEmpty.wait(&m_mutex);
1172 DotRunner *result = m_queue.dequeue();
1176 uint DotRunnerQueue::count() const
1178 QMutexLocker locker(&m_mutex);
1179 return m_queue.count();
1182 //--------------------------------------------------------------------
1184 DotWorkerThread::DotWorkerThread(DotRunnerQueue *queue)
1187 m_cleanupItems.setAutoDelete(TRUE);
1190 void DotWorkerThread::run()
1193 while ((runner=m_queue->dequeue()))
1196 DotRunner::CleanupItem cleanup = runner->cleanup();
1197 if (!cleanup.file.isEmpty())
1199 m_cleanupItems.append(new DotRunner::CleanupItem(cleanup));
1204 void DotWorkerThread::cleanup()
1206 QListIterator<DotRunner::CleanupItem> it(m_cleanupItems);
1207 DotRunner::CleanupItem *ci;
1208 for (;(ci=it.current());++it)
1210 QDir(ci->path).remove(ci->file);
1214 //--------------------------------------------------------------------
1216 DotManager *DotManager::m_theInstance = 0;
1218 DotManager *DotManager::instance()
1222 m_theInstance = new DotManager;
1224 return m_theInstance;
1227 DotManager::DotManager() : m_dotMaps(1009)
1229 m_dotRuns.setAutoDelete(TRUE);
1230 m_dotMaps.setAutoDelete(TRUE);
1231 m_queue = new DotRunnerQueue;
1233 int numThreads = QMIN(32,Config_getInt("DOT_NUM_THREADS"));
1236 if (numThreads==0) numThreads = QMAX(2,QThread::idealThreadCount()+1);
1237 for (i=0;i<numThreads;i++)
1239 DotWorkerThread *thread = new DotWorkerThread(m_queue);
1241 if (thread->isRunning())
1243 m_workers.append(thread);
1245 else // no more threads available!
1250 ASSERT(m_workers.count()>0);
1254 DotManager::~DotManager()
1259 void DotManager::addRun(DotRunner *run)
1261 m_dotRuns.append(run);
1264 int DotManager::addMap(const QCString &file,const QCString &mapFile,
1265 const QCString &relPath,bool urlOnly,const QCString &context,
1266 const QCString &label)
1268 DotFilePatcher *map = m_dotMaps.find(file);
1271 map = new DotFilePatcher(file);
1272 m_dotMaps.append(file,map);
1274 return map->addMap(mapFile,relPath,urlOnly,context,label);
1277 int DotManager::addFigure(const QCString &file,const QCString &baseName,
1278 const QCString &figureName,bool heightCheck)
1280 DotFilePatcher *map = m_dotMaps.find(file);
1283 map = new DotFilePatcher(file);
1284 m_dotMaps.append(file,map);
1286 return map->addFigure(baseName,figureName,heightCheck);
1289 int DotManager::addSVGConversion(const QCString &file,const QCString &relPath,
1290 bool urlOnly,const QCString &context,bool zoomable,
1293 DotFilePatcher *map = m_dotMaps.find(file);
1296 map = new DotFilePatcher(file);
1297 m_dotMaps.append(file,map);
1299 return map->addSVGConversion(relPath,urlOnly,context,zoomable,graphId);
1302 int DotManager::addSVGObject(const QCString &file,const QCString &baseName,
1303 const QCString &absImgName,const QCString &relPath)
1305 DotFilePatcher *map = m_dotMaps.find(file);
1308 map = new DotFilePatcher(file);
1309 m_dotMaps.append(file,map);
1311 return map->addSVGObject(baseName,absImgName,relPath);
1314 bool DotManager::run()
1316 uint numDotRuns = m_dotRuns.count();
1317 uint numDotMaps = m_dotMaps.count();
1318 if (numDotRuns+numDotMaps>1)
1320 if (m_workers.count()==0)
1322 msg("Generating dot graphs in single threaded mode...\n");
1326 msg("Generating dot graphs using %d parallel threads...\n",QMIN(numDotRuns+numDotMaps,m_workers.count()));
1330 QListIterator<DotRunner> li(m_dotRuns);
1333 if (Config_getBool("GENERATE_HTML"))
1335 setDotFontPath(Config_getString("HTML_OUTPUT"));
1338 else if (Config_getBool("GENERATE_LATEX"))
1340 setDotFontPath(Config_getString("LATEX_OUTPUT"));
1343 else if (Config_getBool("GENERATE_RTF"))
1345 setDotFontPath(Config_getString("RTF_OUTPUT"));
1348 portable_sysTimerStart();
1349 // fill work queue with dot operations
1352 if (m_workers.count()==0) // no threads to work with
1354 for (li.toFirst();(dr=li.current());++li)
1356 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1361 else // use multiple threads to run instances of dot in parallel
1363 for (li.toFirst();(dr=li.current());++li)
1365 m_queue->enqueue(dr);
1367 // wait for the queue to become empty
1368 while ((i=m_queue->count())>0)
1373 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1376 portable_sleep(100);
1378 while ((int)numDotRuns>=prev)
1380 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1383 // signal the workers we are done
1384 for (i=0;i<(int)m_workers.count();i++)
1386 m_queue->enqueue(0); // add terminator for each worker
1388 // wait for the workers to finish
1389 for (i=0;i<(int)m_workers.count();i++)
1391 m_workers.at(i)->wait();
1393 // clean up dot files from main thread
1394 for (i=0;i<(int)m_workers.count();i++)
1396 m_workers.at(i)->cleanup();
1399 portable_sysTimerStop();
1405 // patch the output file and insert the maps and figures
1407 SDict<DotFilePatcher>::Iterator di(m_dotMaps);
1408 DotFilePatcher *map;
1409 // since patching the svg files may involve patching the header of the SVG
1410 // (for zoomable SVGs), and patching the .html files requires reading that
1411 // header after the SVG is patched, we first process the .svg files and
1412 // then the other files.
1413 for (di.toFirst();(map=di.current());++di)
1415 if (map->file().right(4)==".svg")
1417 msg("Patching output file %d/%d\n",i,numDotMaps);
1418 if (!map->run()) return FALSE;
1422 for (di.toFirst();(map=di.current());++di)
1424 if (map->file().right(4)!=".svg")
1426 msg("Patching output file %d/%d\n",i,numDotMaps);
1427 if (!map->run()) return FALSE;
1434 //--------------------------------------------------------------------
1437 /*! helper function that deletes all nodes in a connected graph, given
1438 * one of the graph's nodes
1440 static void deleteNodes(DotNode *node,SDict<DotNode> *skipNodes=0)
1442 //printf("deleteNodes skipNodes=%p\n",skipNodes);
1443 static DotNodeList deletedNodes;
1444 deletedNodes.setAutoDelete(TRUE);
1445 node->deleteNode(deletedNodes,skipNodes); // collect nodes to be deleted.
1446 deletedNodes.clear(); // actually remove the nodes.
1449 DotNode::DotNode(int n,const char *lab,const char *tip, const char *url,
1450 bool isRoot,ClassDef *cd)
1465 , m_truncated(Unknown)
1477 void DotNode::addChild(DotNode *n,
1480 const char *edgeLab,
1481 const char *edgeURL,
1487 m_children = new QList<DotNode>;
1488 m_edgeInfo = new QList<EdgeInfo>;
1489 m_edgeInfo->setAutoDelete(TRUE);
1491 m_children->append(n);
1492 EdgeInfo *ei = new EdgeInfo;
1493 ei->m_color = edgeColor;
1494 ei->m_style = edgeStyle;
1495 ei->m_label = edgeLab;
1496 ei->m_url = edgeURL;
1498 ei->m_labColor=edgeColor;
1500 ei->m_labColor=edgeLabCol;
1501 m_edgeInfo->append(ei);
1504 void DotNode::addParent(DotNode *n)
1508 m_parents = new QList<DotNode>;
1510 m_parents->append(n);
1513 void DotNode::removeChild(DotNode *n)
1515 if (m_children) m_children->remove(n);
1518 void DotNode::removeParent(DotNode *n)
1520 if (m_parents) m_parents->remove(n);
1523 void DotNode::deleteNode(DotNodeList &deletedList,SDict<DotNode> *skipNodes)
1525 if (m_deleted) return; // avoid recursive loops in case the graph has cycles
1527 if (m_parents!=0) // delete all parent nodes of this node
1529 QListIterator<DotNode> dnlip(*m_parents);
1531 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
1533 //pn->removeChild(this);
1534 pn->deleteNode(deletedList,skipNodes);
1537 if (m_children!=0) // delete all child nodes of this node
1539 QListIterator<DotNode> dnlic(*m_children);
1541 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
1543 //cn->removeParent(this);
1544 cn->deleteNode(deletedList,skipNodes);
1547 // add this node to the list of deleted nodes.
1548 //printf("skipNodes=%p find(%p)=%p\n",skipNodes,this,skipNodes ? skipNodes->find((int)this) : 0);
1549 if (skipNodes==0 || skipNodes->find((char*)this)==0)
1551 //printf("deleting\n");
1552 deletedList.append(this);
1556 void DotNode::setDistance(int distance)
1558 if (distance<m_distance) m_distance = distance;
1561 static QCString convertLabel(const QCString &l)
1564 QCString bBefore("\\_/<({[: =-+@%#~?$"); // break before character set
1565 QCString bAfter(">]),:;|"); // break after character set
1566 const char *p=l.data();
1567 if (p==0) return result;
1574 int foldLen=17; // ideal text length
1577 QCString replacement;
1580 case '\\': replacement="\\\\"; break;
1581 case '\n': replacement="\\n"; break;
1582 case '<': replacement="\\<"; break;
1583 case '>': replacement="\\>"; break;
1584 case '|': replacement="\\|"; break;
1585 case '{': replacement="\\{"; break;
1586 case '}': replacement="\\}"; break;
1587 case '"': replacement="\\\""; break;
1588 default: cs[0]=c; replacement=cs; break;
1590 // Some heuristics to insert newlines to prevent too long
1591 // boxes and at the same time prevent ugly breaks
1594 result+=replacement;
1595 foldLen = (3*foldLen+sinceLast+2)/4;
1598 else if ((pc!=':' || c!=':') && charsLeft>foldLen/3 && sinceLast>foldLen && bBefore.contains(c))
1601 result+=replacement;
1602 foldLen = (foldLen+sinceLast+1)/2;
1605 else if (charsLeft>1+foldLen/4 && sinceLast>foldLen+foldLen/3 &&
1606 !isupper(c) && isupper(*p))
1608 result+=replacement;
1610 foldLen = (foldLen+sinceLast+1)/2;
1613 else if (charsLeft>foldLen/3 && sinceLast>foldLen && bAfter.contains(c) && (c!=':' || *p!=':'))
1615 result+=replacement;
1617 foldLen = (foldLen+sinceLast+1)/2;
1622 result+=replacement;
1631 static QCString escapeTooltip(const QCString &tooltip)
1634 const char *p=tooltip.data();
1635 if (p==0) return result;
1641 case '"': result+="\\\""; break;
1642 default: result+=c; break;
1648 static void writeBoxMemberList(FTextStream &t,
1649 char prot,MemberList *ml,ClassDef *scope,
1650 bool isStatic=FALSE,const QDict<void> *skipNames=0)
1655 MemberListIterator mlia(*ml);
1658 for (mlia.toFirst();(mma = mlia.current());++mlia)
1660 if (mma->getClassDef()==scope &&
1661 (skipNames==0 || skipNames->find(mma->name())==0))
1668 for (mlia.toFirst();(mma = mlia.current());++mlia)
1670 if (mma->getClassDef() == scope &&
1671 (skipNames==0 || skipNames->find(mma->name())==0))
1673 static int limit = Config_getInt("UML_LIMIT_NUM_FIELDS");
1674 if (limit>0 && (totalCount>limit*3/2 && count>=limit))
1676 t << theTranslator->trAndMore(QCString().sprintf("%d",totalCount-count)) << "\\l";
1682 t << convertLabel(mma->name());
1683 if (!mma->isObjCMethod() &&
1684 (mma->isFunction() || mma->isSlot() || mma->isSignal())) t << "()";
1690 // write member groups within the memberlist
1691 MemberGroupList *mgl = ml->getMemberGroupList();
1694 MemberGroupListIterator mgli(*mgl);
1696 for (mgli.toFirst();(mg=mgli.current());++mgli)
1700 writeBoxMemberList(t,prot,mg->members(),scope,isStatic,skipNames);
1707 static QCString stripProtectionPrefix(const QCString &s)
1709 if (!s.isEmpty() && (s[0]=='-' || s[0]=='+' || s[0]=='~' || s[0]=='#'))
1719 void DotNode::writeBox(FTextStream &t,
1721 GraphOutputFormat /*format*/,
1722 bool hasNonReachableChildren,
1725 const char *labCol =
1726 m_url.isEmpty() ? "grey75" : // non link
1728 (hasNonReachableChildren) ? "red" : "black"
1730 t << " Node" << reNumberNode(m_number,reNumber) << " [label=\"";
1731 static bool umlLook = Config_getBool("UML_LOOK");
1733 if (m_classDef && umlLook && (gt==Inheritance || gt==Collaboration))
1735 // add names shown as relations to a dictionary, so we don't show
1736 // them as attributes as well
1737 QDict<void> arrowNames(17);
1741 QListIterator<EdgeInfo> li(*m_edgeInfo);
1743 for (li.toFirst();(ei=li.current());++li)
1745 if (!ei->m_label.isEmpty()) // labels joined by \n
1747 int li=ei->m_label.find('\n');
1750 while ((li=ei->m_label.find('\n',p))!=-1)
1752 lab = stripProtectionPrefix(ei->m_label.mid(p,li-p));
1753 arrowNames.insert(lab,(void*)0x8);
1756 lab = stripProtectionPrefix(ei->m_label.right(ei->m_label.length()-p));
1757 arrowNames.insert(lab,(void*)0x8);
1762 //printf("DotNode::writeBox for %s\n",m_classDef->name().data());
1763 static bool extractPrivate = Config_getBool("EXTRACT_PRIVATE");
1764 t << "{" << convertLabel(m_label);
1766 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubAttribs),m_classDef,FALSE,&arrowNames);
1767 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubStaticAttribs),m_classDef,TRUE,&arrowNames);
1768 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_properties),m_classDef,FALSE,&arrowNames);
1769 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacAttribs),m_classDef,FALSE,&arrowNames);
1770 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacStaticAttribs),m_classDef,TRUE,&arrowNames);
1771 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proAttribs),m_classDef,FALSE,&arrowNames);
1772 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proStaticAttribs),m_classDef,TRUE,&arrowNames);
1775 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priAttribs),m_classDef,FALSE,&arrowNames);
1776 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priStaticAttribs),m_classDef,TRUE,&arrowNames);
1779 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubMethods),m_classDef);
1780 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubStaticMethods),m_classDef,TRUE);
1781 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubSlots),m_classDef);
1782 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacMethods),m_classDef);
1783 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacStaticMethods),m_classDef,TRUE);
1784 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proMethods),m_classDef);
1785 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proStaticMethods),m_classDef,TRUE);
1786 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proSlots),m_classDef);
1789 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priMethods),m_classDef);
1790 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priStaticMethods),m_classDef,TRUE);
1791 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priSlots),m_classDef);
1793 if (m_classDef->getLanguage()!=SrcLangExt_Fortran &&
1794 m_classDef->getMemberGroupSDict())
1796 MemberGroupSDict::Iterator mgdi(*m_classDef->getMemberGroupSDict());
1798 for (mgdi.toFirst();(mg=mgdi.current());++mgdi)
1802 writeBoxMemberList(t,'*',mg->members(),m_classDef,FALSE,&arrowNames);
1808 else // standard look
1810 t << convertLabel(m_label);
1812 t << "\",height=0.2,width=0.4";
1815 t << ",color=\"black\", fillcolor=\"grey75\", style=\"filled\" fontcolor=\"black\"";
1819 static bool dotTransparent = Config_getBool("DOT_TRANSPARENT");
1820 if (!dotTransparent)
1822 t << ",color=\"" << labCol << "\", fillcolor=\"";
1824 t << "\", style=\"filled\"";
1828 t << ",color=\"" << labCol << "\"";
1830 if (!m_url.isEmpty())
1832 int anchorPos = m_url.findRev('#');
1835 t << ",URL=\"" << m_url << Doxygen::htmlFileExtension << "\"";
1839 t << ",URL=\"" << m_url.left(anchorPos) << Doxygen::htmlFileExtension
1840 << m_url.right(m_url.length()-anchorPos) << "\"";
1843 if (!m_tooltip.isEmpty())
1845 t << ",tooltip=\"" << escapeTooltip(m_tooltip) << "\"";
1851 void DotNode::writeArrow(FTextStream &t,
1853 GraphOutputFormat format,
1863 t << reNumberNode(cn->number(),reNumber);
1865 t << reNumberNode(m_number,reNumber);
1868 t << reNumberNode(m_number,reNumber);
1870 t << reNumberNode(cn->number(),reNumber);
1873 static bool umlLook = Config_getBool("UML_LOOK");
1874 const EdgeProperties *eProps = umlLook ? ¨EdgeProps : &normalEdgeProps;
1875 QCString aStyle = eProps->arrowStyleMap[ei->m_color];
1876 bool umlUseArrow = aStyle=="odiamond";
1878 if (pointBack && !umlUseArrow) t << "dir=\"back\",";
1879 t << "color=\"" << eProps->edgeColorMap[ei->m_color]
1880 << "\",fontsize=\"" << FONTSIZE << "\",";
1881 t << "style=\"" << eProps->edgeStyleMap[ei->m_style] << "\"";
1882 if (!ei->m_label.isEmpty())
1884 t << ",label=\" " << convertLabel(ei->m_label) << "\" ";
1887 eProps->arrowStyleMap[ei->m_color] &&
1888 (gt==Inheritance || gt==Collaboration)
1891 bool rev = pointBack;
1892 if (umlUseArrow) rev=!rev; // UML use relates has arrow on the start side
1894 t << ",arrowtail=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
1896 t << ",arrowhead=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
1899 if (format==BITMAP) t << ",fontname=\"" << FONTNAME << "\"";
1903 void DotNode::write(FTextStream &t,
1905 GraphOutputFormat format,
1912 //printf("DotNode::write(%d) name=%s this=%p written=%d\n",distance,m_label.data(),this,m_written);
1913 if (m_written) return; // node already written to the output
1914 if (!m_visible) return; // node is not visible
1915 writeBox(t,gt,format,m_truncated==Truncated,reNumber);
1917 QList<DotNode> *nl = toChildren ? m_children : m_parents;
1922 QListIterator<DotNode> dnli1(*nl);
1923 QListIterator<EdgeInfo> dnli2(*m_edgeInfo);
1925 for (dnli1.toFirst();(cn=dnli1.current());++dnli1,++dnli2)
1927 if (cn->isVisible())
1929 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",cn->label().data());
1930 writeArrow(t,gt,format,cn,dnli2.current(),topDown,backArrows,reNumber);
1932 cn->write(t,gt,format,topDown,toChildren,backArrows,reNumber);
1935 else // render parents
1937 QListIterator<DotNode> dnli(*nl);
1939 for (dnli.toFirst();(pn=dnli.current());++dnli)
1941 if (pn->isVisible())
1943 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",pn->label().data());
1948 pn->m_edgeInfo->at(pn->m_children->findRef(this)),
1954 pn->write(t,gt,format,TRUE,FALSE,backArrows,reNumber);
1958 //printf("end DotNode::write(%d) name=%s\n",distance,m_label.data());
1961 void DotNode::writeXML(FTextStream &t,bool isClassGraph)
1963 t << " <node id=\"" << m_number << "\">" << endl;
1964 t << " <label>" << convertToXML(m_label) << "</label>" << endl;
1965 if (!m_url.isEmpty())
1967 QCString url(m_url);
1968 char *refPtr = url.data();
1969 char *urlPtr = strchr(url.data(),'$');
1973 t << " <link refid=\"" << convertToXML(urlPtr) << "\"";
1976 t << " external=\"" << convertToXML(refPtr) << "\"";
1983 QListIterator<DotNode> nli(*m_children);
1984 QListIterator<EdgeInfo> eli(*m_edgeInfo);
1987 for (;(childNode=nli.current());++nli,++eli)
1989 edgeInfo=eli.current();
1990 t << " <childnode refid=\"" << childNode->m_number << "\" relation=\"";
1993 switch(edgeInfo->m_color)
1995 case EdgeInfo::Blue: t << "public-inheritance"; break;
1996 case EdgeInfo::Green: t << "protected-inheritance"; break;
1997 case EdgeInfo::Red: t << "private-inheritance"; break;
1998 case EdgeInfo::Purple: t << "usage"; break;
1999 case EdgeInfo::Orange: t << "template-instance"; break;
2000 case EdgeInfo::Grey: ASSERT(0); break;
2003 else // include graph
2008 if (!edgeInfo->m_label.isEmpty())
2012 while ((ni=edgeInfo->m_label.find('\n',p))!=-1)
2015 << convertToXML(edgeInfo->m_label.mid(p,ni-p))
2016 << "</edgelabel>" << endl;
2020 << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p))
2021 << "</edgelabel>" << endl;
2023 t << " </childnode>" << endl;
2026 t << " </node>" << endl;
2029 void DotNode::writeDocbook(FTextStream &t,bool isClassGraph)
2031 t << " <node id=\"" << m_number << "\">" << endl;
2032 t << " <label>" << convertToXML(m_label) << "</label>" << endl;
2033 if (!m_url.isEmpty())
2035 QCString url(m_url);
2036 char *refPtr = url.data();
2037 char *urlPtr = strchr(url.data(),'$');
2041 t << " <link refid=\"" << convertToXML(urlPtr) << "\"";
2044 t << " external=\"" << convertToXML(refPtr) << "\"";
2051 QListIterator<DotNode> nli(*m_children);
2052 QListIterator<EdgeInfo> eli(*m_edgeInfo);
2055 for (;(childNode=nli.current());++nli,++eli)
2057 edgeInfo=eli.current();
2058 t << " <childnode refid=\"" << childNode->m_number << "\" relation=\"";
2061 switch(edgeInfo->m_color)
2063 case EdgeInfo::Blue: t << "public-inheritance"; break;
2064 case EdgeInfo::Green: t << "protected-inheritance"; break;
2065 case EdgeInfo::Red: t << "private-inheritance"; break;
2066 case EdgeInfo::Purple: t << "usage"; break;
2067 case EdgeInfo::Orange: t << "template-instance"; break;
2068 case EdgeInfo::Grey: ASSERT(0); break;
2071 else // include graph
2076 if (!edgeInfo->m_label.isEmpty())
2080 while ((ni=edgeInfo->m_label.find('\n',p))!=-1)
2083 << convertToXML(edgeInfo->m_label.mid(p,ni-p))
2084 << "</edgelabel>" << endl;
2088 << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p))
2089 << "</edgelabel>" << endl;
2091 t << " </childnode>" << endl;
2094 t << " </node>" << endl;
2098 void DotNode::writeDEF(FTextStream &t)
2100 const char* nodePrefix = " node-";
2102 t << " node = {" << endl;
2103 t << nodePrefix << "id = " << m_number << ';' << endl;
2104 t << nodePrefix << "label = '" << m_label << "';" << endl;
2106 if (!m_url.isEmpty())
2108 QCString url(m_url);
2109 char *refPtr = url.data();
2110 char *urlPtr = strchr(url.data(),'$');
2114 t << nodePrefix << "link = {" << endl << " "
2115 << nodePrefix << "link-id = '" << urlPtr << "';" << endl;
2119 t << " " << nodePrefix << "link-external = '"
2120 << refPtr << "';" << endl;
2127 QListIterator<DotNode> nli(*m_children);
2128 QListIterator<EdgeInfo> eli(*m_edgeInfo);
2131 for (;(childNode=nli.current());++nli,++eli)
2133 edgeInfo=eli.current();
2134 t << " node-child = {" << endl;
2135 t << " child-id = '" << childNode->m_number << "';" << endl;
2136 t << " relation = ";
2138 switch(edgeInfo->m_color)
2140 case EdgeInfo::Blue: t << "public-inheritance"; break;
2141 case EdgeInfo::Green: t << "protected-inheritance"; break;
2142 case EdgeInfo::Red: t << "private-inheritance"; break;
2143 case EdgeInfo::Purple: t << "usage"; break;
2144 case EdgeInfo::Orange: t << "template-instance"; break;
2145 case EdgeInfo::Grey: ASSERT(0); break;
2149 if (!edgeInfo->m_label.isEmpty())
2151 t << " edgelabel = <<_EnD_oF_dEf_TeXt_" << endl
2152 << edgeInfo->m_label << endl
2153 << "_EnD_oF_dEf_TeXt_;" << endl;
2155 t << " }; /* node-child */" << endl;
2156 } /* for (;childNode...) */
2158 t << " }; /* node */" << endl;
2162 void DotNode::clearWriteFlag()
2167 QListIterator<DotNode> dnlip(*m_parents);
2169 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
2173 pn->clearWriteFlag();
2179 QListIterator<DotNode> dnlic(*m_children);
2181 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2185 cn->clearWriteFlag();
2191 void DotNode::colorConnectedNodes(int curColor)
2195 QListIterator<DotNode> dnlic(*m_children);
2197 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2199 if (cn->m_subgraphId==-1) // uncolored child node
2201 cn->m_subgraphId=curColor;
2202 cn->markAsVisible();
2203 cn->colorConnectedNodes(curColor);
2204 //printf("coloring node %s (%p): %d\n",cn->m_label.data(),cn,cn->m_subgraphId);
2211 QListIterator<DotNode> dnlip(*m_parents);
2213 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
2215 if (pn->m_subgraphId==-1) // uncolored parent node
2217 pn->m_subgraphId=curColor;
2218 pn->markAsVisible();
2219 pn->colorConnectedNodes(curColor);
2220 //printf("coloring node %s (%p): %d\n",pn->m_label.data(),pn,pn->m_subgraphId);
2226 const DotNode *DotNode::findDocNode() const
2228 if (!m_url.isEmpty()) return this;
2229 //printf("findDocNode(): `%s'\n",m_label.data());
2232 QListIterator<DotNode> dnli(*m_parents);
2234 for (dnli.toFirst();(pn=dnli.current());++dnli)
2239 const DotNode *dn = pn->findDocNode();
2246 QListIterator<DotNode> dnli(*m_children);
2248 for (dnli.toFirst();(cn=dnli.current());++dnli)
2253 const DotNode *dn = cn->findDocNode();
2261 //--------------------------------------------------------------------
2263 int DotGfxHierarchyTable::m_curNodeNumber;
2265 void DotGfxHierarchyTable::writeGraph(FTextStream &out,
2266 const char *path,const char *fileName) const
2268 //printf("DotGfxHierarchyTable::writeGraph(%s)\n",name);
2269 //printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count());
2271 if (m_rootSubgraphs->count()==0) return;
2274 // store the original directory
2277 err("Output dir %s does not exist!\n",path); exit(1);
2280 // put each connected subgraph of the hierarchy in a row of the HTML output
2281 out << "<table border=\"0\" cellspacing=\"10\" cellpadding=\"0\">" << endl;
2283 QListIterator<DotNode> dnli(*m_rootSubgraphs);
2286 for (dnli.toFirst();(n=dnli.current());++dnli)
2289 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
2290 baseName.sprintf("inherit_graph_%d",count++);
2291 //baseName = convertNameToFile(baseName);
2292 QCString imgName = baseName+"."+ imgExt;
2293 QCString mapName = baseName+".map";
2294 QCString absImgName = QCString(d.absPath().data())+"/"+imgName;
2295 QCString absMapName = QCString(d.absPath().data())+"/"+mapName;
2296 QCString absBaseName = QCString(d.absPath().data())+"/"+baseName;
2297 QListIterator<DotNode> dnli2(*m_rootNodes);
2300 // compute md5 checksum of the graph were are about to generate
2302 FTextStream md5stream(&theGraph);
2303 writeGraphHeader(md5stream,theTranslator->trGraphicalHierarchy());
2304 md5stream << " rankdir=\"LR\";" << endl;
2305 for (dnli2.toFirst();(node=dnli2.current());++dnli2)
2307 if (node->m_subgraphId==n->m_subgraphId)
2309 node->clearWriteFlag();
2312 for (dnli2.toFirst();(node=dnli2.current());++dnli2)
2314 if (node->m_subgraphId==n->m_subgraphId)
2316 node->write(md5stream,DotNode::Hierarchy,BITMAP,FALSE,TRUE,TRUE,TRUE);
2319 writeGraphFooter(md5stream);
2322 QCString sigStr(33);
2323 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
2324 MD5SigToString(md5_sig,sigStr.data(),33);
2325 bool regenerate=FALSE;
2326 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
2327 !checkDeliverables(absImgName,absMapName))
2330 // image was new or has changed
2331 QCString dotName=absBaseName+".dot";
2333 if (!f.open(IO_WriteOnly)) return;
2339 DotRunner *dotRun = new DotRunner(dotName,d.absPath().data(),TRUE,absImgName);
2340 dotRun->addJob(imgExt,absImgName);
2341 dotRun->addJob(MAP_CMD,absMapName);
2342 DotManager::instance()->addRun(dotRun);
2346 removeDotGraph(absBaseName+".dot");
2348 Doxygen::indexList->addImageFile(imgName);
2349 // write image and map in a table row
2350 QCString mapLabel = escapeCharsInString(n->m_label,FALSE);
2352 if (imgExt=="svg") // vector graphics
2354 if (regenerate || !writeSVGFigureLink(out,QCString(),baseName,absImgName))
2358 DotManager::instance()->addSVGConversion(absImgName,QCString(),
2359 FALSE,QCString(),FALSE,0);
2361 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,
2362 absImgName,QCString());
2363 out << "<!-- SVG " << mapId << " -->" << endl;
2366 else // normal bitmap
2368 out << "<img src=\"" << imgName << "\" border=\"0\" alt=\"\" usemap=\"#"
2369 << mapLabel << "\"/>" << endl;
2371 if (regenerate || !insertMapFile(out,absMapName,QCString(),mapLabel))
2373 int mapId = DotManager::instance()->addMap(fileName,absMapName,QCString(),
2374 FALSE,QCString(),mapLabel);
2375 out << "<!-- MAP " << mapId << " -->" << endl;
2379 out << "</td></tr>" << endl;
2381 out << "</table>" << endl;
2384 void DotGfxHierarchyTable::addHierarchy(DotNode *n,ClassDef *cd,bool hideSuper)
2386 //printf("addHierarchy `%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count());
2387 if (cd->subClasses())
2389 BaseClassListIterator bcli(*cd->subClasses());
2391 for ( ; (bcd=bcli.current()) ; ++bcli )
2393 ClassDef *bClass=bcd->classDef;
2394 //printf(" Trying sub class=`%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count());
2395 if (bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses()))
2398 //printf(" Node `%s' Found visible class=`%s'\n",n->m_label.data(),
2399 // bClass->name().data());
2400 if ((bn=m_usedNodes->find(bClass->name()))) // node already present
2402 if (n->m_children==0 || n->m_children->findRef(bn)==-1) // no arrow yet
2404 n->addChild(bn,bcd->prot);
2406 //printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n",
2407 // n->m_label.data(),
2408 // bn->m_label.data(),
2409 // bn->m_children ? bn->m_children->count() : 0,
2410 // bn->m_parents ? bn->m_parents->count() : 0
2415 // printf(" Class already has an arrow!\n");
2420 QCString tmp_url="";
2421 if (bClass->isLinkable() && !bClass->isHidden())
2423 tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase();
2424 if (!bClass->anchor().isEmpty())
2426 tmp_url+="#"+bClass->anchor();
2429 QCString tooltip = bClass->briefDescriptionAsTooltip();
2430 bn = new DotNode(m_curNodeNumber++,
2431 bClass->displayName(),
2435 n->addChild(bn,bcd->prot);
2437 //printf(" Adding node %s to new base node %s (c=%d,p=%d)\n",
2438 // n->m_label.data(),
2439 // bn->m_label.data(),
2440 // bn->m_children ? bn->m_children->count() : 0,
2441 // bn->m_parents ? bn->m_parents->count() : 0
2443 //printf(" inserting %s (%p)\n",bClass->name().data(),bn);
2444 m_usedNodes->insert(bClass->name(),bn); // add node to the used list
2446 if (!bClass->visited && !hideSuper && bClass->subClasses())
2448 bool wasVisited=bClass->visited;
2449 bClass->visited=TRUE;
2450 addHierarchy(bn,bClass,wasVisited);
2455 //printf("end addHierarchy\n");
2458 void DotGfxHierarchyTable::addClassList(ClassSDict *cl)
2460 ClassSDict::Iterator cli(*cl);
2462 for (cli.toLast();(cd=cli.current());--cli)
2464 //printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count());
2465 if (cd->getLanguage()==SrcLangExt_VHDL &&
2466 (VhdlDocGen::VhdlClasses)cd->protection()!=VhdlDocGen::ENTITYCLASS
2471 if (!hasVisibleRoot(cd->baseClasses()) &&
2472 cd->isVisibleInHierarchy()
2473 ) // root node in the forest
2475 QCString tmp_url="";
2476 if (cd->isLinkable() && !cd->isHidden())
2478 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2479 if (!cd->anchor().isEmpty())
2481 tmp_url+="#"+cd->anchor();
2484 //printf("Inserting root class %s\n",cd->name().data());
2485 QCString tooltip = cd->briefDescriptionAsTooltip();
2486 DotNode *n = new DotNode(m_curNodeNumber++,
2491 //m_usedNodes->clear();
2492 m_usedNodes->insert(cd->name(),n);
2493 m_rootNodes->insert(0,n);
2494 if (!cd->visited && cd->subClasses())
2496 addHierarchy(n,cd,cd->visited);
2503 DotGfxHierarchyTable::DotGfxHierarchyTable()
2506 m_rootNodes = new QList<DotNode>;
2507 m_usedNodes = new QDict<DotNode>(1009);
2508 m_usedNodes->setAutoDelete(TRUE);
2509 m_rootSubgraphs = new DotNodeList;
2511 // build a graph with each class as a node and the inheritance relations
2513 initClassHierarchy(Doxygen::classSDict);
2514 initClassHierarchy(Doxygen::hiddenClasses);
2515 addClassList(Doxygen::classSDict);
2516 addClassList(Doxygen::hiddenClasses);
2517 // m_usedNodes now contains all nodes in the graph
2519 // color the graph into a set of independent subgraphs
2522 QListIterator<DotNode> dnli(*m_rootNodes);
2523 while (!done) // there are still nodes to color
2526 done=TRUE; // we are done unless there are still uncolored nodes
2527 for (dnli.toLast();(n=dnli.current());--dnli)
2529 if (n->m_subgraphId==-1) // not yet colored
2531 //printf("Starting at node %s (%p): %d\n",n->m_label.data(),n,curColor);
2532 done=FALSE; // still uncolored nodes
2533 n->m_subgraphId=curColor;
2535 n->colorConnectedNodes(curColor);
2537 const DotNode *dn=n->findDocNode();
2539 m_rootSubgraphs->inSort(dn);
2541 m_rootSubgraphs->inSort(n);
2546 //printf("Number of independent subgraphs: %d\n",curColor);
2547 //QListIterator<DotNode> dnli2(*m_rootSubgraphs);
2549 //for (dnli2.toFirst();(n=dnli2.current());++dnli2)
2551 // printf("Node %s color=%d (c=%d,p=%d)\n",
2552 // n->m_label.data(),n->m_subgraphId,
2553 // n->m_children?n->m_children->count():0,
2554 // n->m_parents?n->m_parents->count():0);
2558 DotGfxHierarchyTable::~DotGfxHierarchyTable()
2560 //printf("DotGfxHierarchyTable::~DotGfxHierarchyTable\n");
2562 //QDictIterator<DotNode> di(*m_usedNodes);
2564 //for (;(n=di.current());++di)
2566 // printf("Node %p: %s\n",n,n->label().data());
2571 delete m_rootSubgraphs;
2574 //--------------------------------------------------------------------
2576 int DotClassGraph::m_curNodeNumber = 0;
2578 void DotClassGraph::addClass(ClassDef *cd,DotNode *n,int prot,
2579 const char *label,const char *usedName,const char *templSpec,bool base,int distance)
2581 if (Config_getBool("HIDE_UNDOC_CLASSES") && !cd->isLinkable()) return;
2583 int edgeStyle = (label || prot==EdgeInfo::Orange) ? EdgeInfo::Dashed : EdgeInfo::Solid;
2585 if (usedName) // name is a typedef
2589 else if (templSpec) // name has a template part
2591 className=insertTemplateSpecifierInScope(cd->name(),templSpec);
2593 else // just a normal name
2595 className=cd->displayName();
2597 //printf("DotClassGraph::addClass(class=`%s',parent=%s,prot=%d,label=%s,dist=%d,usedName=%s,templSpec=%s,base=%d)\n",
2598 // className.data(),n->m_label.data(),prot,label,distance,usedName,templSpec,base);
2599 DotNode *bn = m_usedNodes->find(className);
2600 if (bn) // class already inserted
2604 n->addChild(bn,prot,edgeStyle,label);
2609 bn->addChild(n,prot,edgeStyle,label);
2612 bn->setDistance(distance);
2613 //printf(" add exiting node %s of %s\n",bn->m_label.data(),n->m_label.data());
2617 QCString displayName=className;
2618 if (Config_getBool("HIDE_SCOPE_NAMES")) displayName=stripScope(displayName);
2620 if (cd->isLinkable() && !cd->isHidden())
2622 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2623 if (!cd->anchor().isEmpty())
2625 tmp_url+="#"+cd->anchor();
2628 QCString tooltip = cd->briefDescriptionAsTooltip();
2629 bn = new DotNode(m_curNodeNumber++,
2638 n->addChild(bn,prot,edgeStyle,label);
2643 bn->addChild(n,prot,edgeStyle,label);
2646 bn->setDistance(distance);
2647 m_usedNodes->insert(className,bn);
2648 //printf(" add new child node `%s' to %s hidden=%d url=%s\n",
2649 // className.data(),n->m_label.data(),cd->isHidden(),tmp_url.data());
2651 buildGraph(cd,bn,base,distance+1);
2655 void DotClassGraph::determineTruncatedNodes(QList<DotNode> &queue,bool includeParents)
2657 while (queue.count()>0)
2659 DotNode *n = queue.take(0);
2660 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
2662 bool truncated = FALSE;
2665 QListIterator<DotNode> li(*n->m_children);
2667 for (li.toFirst();(dn=li.current());++li)
2669 if (!dn->isVisible())
2675 if (n->m_parents && includeParents)
2677 QListIterator<DotNode> li(*n->m_parents);
2679 for (li.toFirst();(dn=li.current());++li)
2681 if (!dn->isVisible())
2687 n->markAsTruncated(truncated);
2692 bool DotClassGraph::determineVisibleNodes(DotNode *rootNode,
2693 int maxNodes,bool includeParents)
2695 QList<DotNode> childQueue;
2696 QList<DotNode> parentQueue;
2697 QArray<int> childTreeWidth;
2698 QArray<int> parentTreeWidth;
2699 childQueue.append(rootNode);
2700 if (includeParents) parentQueue.append(rootNode);
2701 bool firstNode=TRUE; // flag to force reprocessing rootNode in the parent loop
2702 // despite being marked visible in the child loop
2703 while ((childQueue.count()>0 || parentQueue.count()>0) && maxNodes>0)
2705 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
2706 if (childQueue.count()>0)
2708 DotNode *n = childQueue.take(0);
2709 int distance = n->distance();
2710 if (!n->isVisible() && distance<=maxDistance) // not yet processed
2714 int oldSize=(int)childTreeWidth.size();
2715 if (distance>oldSize)
2717 childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance));
2718 int i; for (i=oldSize;i<distance;i++) childTreeWidth[i]=0;
2720 childTreeWidth[distance-1]+=n->label().length();
2724 // add direct children
2727 QListIterator<DotNode> li(*n->m_children);
2729 for (li.toFirst();(dn=li.current());++li)
2731 childQueue.append(dn);
2736 if (includeParents && parentQueue.count()>0)
2738 DotNode *n = parentQueue.take(0);
2739 if ((!n->isVisible() || firstNode) && n->distance()<=maxDistance) // not yet processed
2742 int distance = n->distance();
2745 int oldSize = (int)parentTreeWidth.size();
2746 if (distance>oldSize)
2748 parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance));
2749 int i; for (i=oldSize;i<distance;i++) parentTreeWidth[i]=0;
2751 parentTreeWidth[distance-1]+=n->label().length();
2755 // add direct parents
2758 QListIterator<DotNode> li(*n->m_parents);
2760 for (li.toFirst();(dn=li.current());++li)
2762 parentQueue.append(dn);
2768 if (Config_getBool("UML_LOOK")) return FALSE; // UML graph are always top to bottom
2770 int maxHeight=(int)QMAX(childTreeWidth.size(),parentTreeWidth.size());
2772 for (i=0;i<childTreeWidth.size();i++)
2774 if (childTreeWidth.at(i)>maxWidth) maxWidth=childTreeWidth.at(i);
2776 for (i=0;i<parentTreeWidth.size();i++)
2778 if (parentTreeWidth.at(i)>maxWidth) maxWidth=parentTreeWidth.at(i);
2780 //printf("max tree width=%d, max tree height=%d\n",maxWidth,maxHeight);
2781 return maxWidth>80 && maxHeight<12; // used metric to decide to render the tree
2782 // from left to right instead of top to bottom,
2783 // with the idea to render very wide trees in
2784 // left to right order.
2787 void DotClassGraph::buildGraph(ClassDef *cd,DotNode *n,bool base,int distance)
2789 //printf("DocClassGraph::buildGraph(%s,distance=%d,base=%d)\n",
2790 // cd->name().data(),distance,base);
2791 // ---- Add inheritance relations
2793 if (m_graphType == DotNode::Inheritance || m_graphType==DotNode::Collaboration)
2795 BaseClassList *bcl = base ? cd->baseClasses() : cd->subClasses();
2798 BaseClassListIterator bcli(*bcl);
2800 for ( ; (bcd=bcli.current()) ; ++bcli )
2802 //printf("-------- inheritance relation %s->%s templ=`%s'\n",
2803 // cd->name().data(),bcd->classDef->name().data(),bcd->templSpecifiers.data());
2804 addClass(bcd->classDef,n,bcd->prot,0,bcd->usedName,
2805 bcd->templSpecifiers,base,distance);
2809 if (m_graphType == DotNode::Collaboration)
2811 // ---- Add usage relations
2813 UsesClassDict *dict =
2814 base ? cd->usedImplementationClasses() :
2815 cd->usedByImplementationClasses()
2819 UsesClassDictIterator ucdi(*dict);
2821 for (;(ucd=ucdi.current());++ucdi)
2824 QDictIterator<void> dvi(*ucd->accessors);
2829 for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count)
2838 label+=QCString("\n")+s;
2841 if (count==maxLabels) label+="\n...";
2842 //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data());
2843 addClass(ucd->classDef,n,EdgeInfo::Purple,label,0,
2844 ucd->templSpecifiers,base,distance);
2849 // ---- Add template instantiation relations
2851 static bool templateRelations = Config_getBool("TEMPLATE_RELATIONS");
2852 if (templateRelations)
2854 if (base) // template relations for base classes
2856 ClassDef *templMaster=cd->templateMaster();
2859 QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances());
2860 ClassDef *templInstance;
2861 for (;(templInstance=cli.current());++cli)
2863 if (templInstance==cd)
2865 addClass(templMaster,n,EdgeInfo::Orange,cli.currentKey(),0,
2871 else // template relations for super classes
2873 QDict<ClassDef> *templInstances = cd->getTemplateInstances();
2876 QDictIterator<ClassDef> cli(*templInstances);
2877 ClassDef *templInstance;
2878 for (;(templInstance=cli.current());++cli)
2880 addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0,
2888 DotClassGraph::DotClassGraph(ClassDef *cd,DotNode::GraphType t)
2890 //printf("--------------- DotClassGraph::DotClassGraph `%s'\n",cd->displayName().data());
2892 QCString tmp_url="";
2893 if (cd->isLinkable() && !cd->isHidden())
2895 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2896 if (!cd->anchor().isEmpty())
2898 tmp_url+="#"+cd->anchor();
2901 QCString className = cd->displayName();
2902 QCString tooltip = cd->briefDescriptionAsTooltip();
2903 m_startNode = new DotNode(m_curNodeNumber++,
2907 TRUE, // is a root node
2910 m_startNode->setDistance(0);
2911 m_usedNodes = new QDict<DotNode>(1009);
2912 m_usedNodes->insert(className,m_startNode);
2914 //printf("Root node %s\n",cd->name().data());
2917 buildGraph(cd,m_startNode,TRUE,1);
2918 if (t==DotNode::Inheritance) buildGraph(cd,m_startNode,FALSE,1);
2921 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
2922 //int directChildNodes = 1;
2923 //if (m_startNode->m_children!=0)
2924 // directChildNodes+=m_startNode->m_children->count();
2925 //if (t==DotNode::Inheritance && m_startNode->m_parents!=0)
2926 // directChildNodes+=m_startNode->m_parents->count();
2927 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
2928 //openNodeQueue.append(m_startNode);
2929 m_lrRank = determineVisibleNodes(m_startNode,maxNodes,t==DotNode::Inheritance);
2930 QList<DotNode> openNodeQueue;
2931 openNodeQueue.append(m_startNode);
2932 determineTruncatedNodes(openNodeQueue,t==DotNode::Inheritance);
2934 m_diskName = cd->getFileBase().copy();
2937 bool DotClassGraph::isTrivial() const
2939 static bool umlLook = Config_getBool("UML_LOOK");
2940 if (m_graphType==DotNode::Inheritance)
2941 return m_startNode->m_children==0 && m_startNode->m_parents==0;
2943 return !umlLook && m_startNode->m_children==0;
2946 bool DotClassGraph::isTooBig() const
2948 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
2950 numNodes+= m_startNode->m_children ? m_startNode->m_children->count() : 0;
2951 if (m_graphType==DotNode::Inheritance)
2953 numNodes+= m_startNode->m_parents ? m_startNode->m_parents->count() : 0;
2955 return numNodes>=maxNodes;
2958 DotClassGraph::~DotClassGraph()
2960 deleteNodes(m_startNode);
2964 /*! Computes a 16 byte md5 checksum for a given dot graph.
2965 * The md5 checksum is returned as a 32 character ASCII string.
2967 QCString computeMd5Signature(DotNode *root,
2968 DotNode::GraphType gt,
2969 GraphOutputFormat format,
2973 const QCString &title,
2979 //printf("computeMd5Signature\n");
2981 FTextStream md5stream(&buf);
2982 writeGraphHeader(md5stream,title);
2985 md5stream << " rankdir=\"LR\";" << endl;
2987 root->clearWriteFlag();
2988 root->write(md5stream,
2991 gt!=DotNode::CallGraph && gt!=DotNode::Dependency,
2995 if (renderParents && root->m_parents)
2997 QListIterator<DotNode> dnli(*root->m_parents);
2999 for (dnli.toFirst();(pn=dnli.current());++dnli)
3001 if (pn->isVisible())
3003 root->writeArrow(md5stream, // stream
3005 format, // output format
3007 pn->m_edgeInfo->at(pn->m_children->findRef(root)), // edge info
3009 backArrows, // point back?
3010 reNumber // renumber nodes
3013 pn->write(md5stream, // stream
3015 format, // output format
3017 FALSE, // toChildren?
3018 backArrows, // backward pointing arrows?
3019 reNumber // renumber nodes?
3023 writeGraphFooter(md5stream);
3025 QCString sigStr(33);
3026 MD5Buffer((const unsigned char *)buf.data(),buf.length(),md5_sig);
3027 MD5SigToString(md5_sig,sigStr.data(),33);
3032 graphStr=buf.data();
3033 //printf("md5: %s | file: %s\n",sigStr,baseName.data());
3037 static bool updateDotGraph(DotNode *root,
3038 DotNode::GraphType gt,
3039 const QCString &baseName,
3040 GraphOutputFormat format,
3044 const QCString &title=QCString()
3048 // TODO: write graph to theGraph, then compute md5 checksum
3049 QCString md5 = computeMd5Signature(
3050 root,gt,format,lrRank,renderParents,
3051 backArrows,title,theGraph);
3052 QFile f(baseName+".dot");
3053 if (f.open(IO_WriteOnly))
3058 return checkAndUpdateMd5Signature(baseName,md5); // graph needs to be regenerated
3061 QCString DotClassGraph::diskName() const
3063 QCString result=m_diskName.copy();
3064 switch (m_graphType)
3066 case DotNode::Collaboration:
3067 result+="_coll_graph";
3070 // result+="_intf_graph";
3072 case DotNode::Inheritance:
3073 result+="_inherit_graph";
3082 QCString DotClassGraph::writeGraph(FTextStream &out,
3083 GraphOutputFormat format,
3085 const char *fileName,
3086 const char *relPath,
3088 bool generateImageMap,
3092 // store the original directory
3095 err("Output dir %s does not exist!\n",path); exit(1);
3097 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3101 switch (m_graphType)
3103 case DotNode::Collaboration:
3107 // mapName="intf_map";
3109 case DotNode::Inheritance:
3110 mapName="inherit_map";
3116 baseName = convertNameToFile(diskName());
3118 // derive target file names from baseName
3119 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3120 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3121 QCString absDotName = absBaseName+".dot";
3122 QCString absMapName = absBaseName+".map";
3123 QCString absPdfName = absBaseName+".pdf";
3124 QCString absEpsName = absBaseName+".eps";
3125 QCString absImgName = absBaseName+"."+imgExt;
3127 bool regenerate = FALSE;
3128 if (updateDotGraph(m_startNode,
3133 m_graphType==DotNode::Inheritance,
3135 m_startNode->label()
3137 !checkDeliverables(format==BITMAP ? absImgName :
3138 usePDFLatex ? absPdfName : absEpsName,
3139 format==BITMAP && generateImageMap ? absMapName : QCString())
3143 if (format==BITMAP) // run dot to create a bitmap image
3145 QCString dotArgs(maxCmdLine);
3147 DotRunner *dotRun = new DotRunner(absDotName,
3148 d.absPath().data(),TRUE,absImgName);
3149 dotRun->addJob(imgExt,absImgName);
3150 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3151 DotManager::instance()->addRun(dotRun);
3154 else if (format==EPS) // run dot to create a .eps image
3156 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3159 dotRun->addJob("pdf",absPdfName);
3163 dotRun->addJob("ps",absEpsName);
3165 DotManager::instance()->addRun(dotRun);
3168 Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3170 if (format==BITMAP && generateImageMap) // produce HTML to include the image
3172 QCString mapLabel = escapeCharsInString(m_startNode->m_label,FALSE)+"_"+
3173 escapeCharsInString(mapName,FALSE);
3174 if (imgExt=="svg") // add link to SVG file without map file
3176 out << "<div class=\"center\">";
3177 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3181 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3183 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3184 out << "<!-- SVG " << mapId << " -->" << endl;
3186 out << "</div>" << endl;
3188 else // add link to bitmap file with image map
3190 out << "<div class=\"center\">";
3191 out << "<img src=\"" << relPath << baseName << "."
3192 << imgExt << "\" border=\"0\" usemap=\"#"
3193 << mapLabel << "\" alt=\"";
3194 switch (m_graphType)
3196 case DotNode::Collaboration:
3197 out << "Collaboration graph";
3199 case DotNode::Inheritance:
3200 out << "Inheritance graph";
3207 out << "</div>" << endl;
3209 if (regenerate || !insertMapFile(out,absMapName,relPath,mapLabel))
3211 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3212 FALSE,QCString(),mapLabel);
3213 out << "<!-- MAP " << mapId << " -->" << endl;
3217 else if (format==EPS) // produce tex to include the .eps image
3219 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3221 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE /*TRUE*/);
3222 out << endl << "% FIG " << figId << endl;
3225 if (!regenerate) removeDotGraph(absDotName);
3230 //--------------------------------------------------------------------
3232 void DotClassGraph::writeXML(FTextStream &t)
3234 QDictIterator<DotNode> dni(*m_usedNodes);
3236 for (;(node=dni.current());++dni)
3238 node->writeXML(t,TRUE);
3242 void DotClassGraph::writeDocbook(FTextStream &t)
3244 QDictIterator<DotNode> dni(*m_usedNodes);
3246 for (;(node=dni.current());++dni)
3248 node->writeDocbook(t,TRUE);
3252 void DotClassGraph::writeDEF(FTextStream &t)
3254 QDictIterator<DotNode> dni(*m_usedNodes);
3256 for (;(node=dni.current());++dni)
3262 //--------------------------------------------------------------------
3264 int DotInclDepGraph::m_curNodeNumber = 0;
3266 void DotInclDepGraph::buildGraph(DotNode *n,FileDef *fd,int distance)
3268 QList<IncludeInfo> *includeFiles =
3269 m_inverse ? fd->includedByFileList() : fd->includeFileList();
3272 QListIterator<IncludeInfo> ili(*includeFiles);
3274 for (;(ii=ili.current());++ili)
3276 FileDef *bfd = ii->fileDef;
3277 QCString in = ii->includeName;
3278 //printf(">>>> in=`%s' bfd=%p\n",ii->includeName.data(),bfd);
3279 bool doc=TRUE,src=FALSE;
3282 in = bfd->absFilePath();
3283 doc = bfd->isLinkable() && !bfd->isHidden();
3284 src = bfd->generateSourceFile();
3286 if (doc || src || !Config_getBool("HIDE_UNDOC_RELATIONS"))
3289 if (bfd) url=bfd->getOutputFileBase().copy();
3292 url=bfd->getSourceFileBase();
3294 DotNode *bn = m_usedNodes->find(in);
3295 if (bn) // file is already a node in the graph
3297 n->addChild(bn,0,0,0);
3299 bn->setDistance(distance);
3307 tmp_url=doc || src ? bfd->getReference()+"$"+url : QCString();
3308 tooltip = bfd->briefDescriptionAsTooltip();
3311 m_curNodeNumber++, // n
3312 ii->includeName, // label
3318 n->addChild(bn,0,0,0);
3320 m_usedNodes->insert(in,bn);
3321 bn->setDistance(distance);
3323 if (bfd) buildGraph(bn,bfd,distance+1);
3330 void DotInclDepGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
3332 while (queue.count()>0 && maxNodes>0)
3334 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
3335 DotNode *n = queue.take(0);
3336 if (!n->isVisible() && n->distance()<=maxDistance) // not yet processed
3340 // add direct children
3343 QListIterator<DotNode> li(*n->m_children);
3345 for (li.toFirst();(dn=li.current());++li)
3354 void DotInclDepGraph::determineTruncatedNodes(QList<DotNode> &queue)
3356 while (queue.count()>0)
3358 DotNode *n = queue.take(0);
3359 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
3361 bool truncated = FALSE;
3364 QListIterator<DotNode> li(*n->m_children);
3366 for (li.toFirst();(dn=li.current());++li)
3368 if (!dn->isVisible())
3374 n->markAsTruncated(truncated);
3380 DotInclDepGraph::DotInclDepGraph(FileDef *fd,bool inverse)
3382 m_inverse = inverse;
3384 m_diskName = fd->getFileBase().copy();
3385 QCString tmp_url=fd->getReference()+"$"+fd->getFileBase();
3386 m_startNode = new DotNode(m_curNodeNumber++,
3392 m_startNode->setDistance(0);
3393 m_usedNodes = new QDict<DotNode>(1009);
3394 m_usedNodes->insert(fd->absFilePath(),m_startNode);
3395 buildGraph(m_startNode,fd,1);
3397 static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3398 int maxNodes = nodes;
3399 //int directChildNodes = 1;
3400 //if (m_startNode->m_children!=0)
3401 // directChildNodes+=m_startNode->m_children->count();
3402 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3403 QList<DotNode> openNodeQueue;
3404 openNodeQueue.append(m_startNode);
3405 determineVisibleNodes(openNodeQueue,maxNodes);
3406 openNodeQueue.clear();
3407 openNodeQueue.append(m_startNode);
3408 determineTruncatedNodes(openNodeQueue);
3411 DotInclDepGraph::~DotInclDepGraph()
3413 deleteNodes(m_startNode);
3417 QCString DotInclDepGraph::diskName() const
3419 QCString result=m_diskName.copy();
3420 if (m_inverse) result+="_dep";
3422 return convertNameToFile(result);
3425 QCString DotInclDepGraph::writeGraph(FTextStream &out,
3426 GraphOutputFormat format,
3428 const char *fileName,
3429 const char *relPath,
3430 bool generateImageMap,
3435 // store the original directory
3438 err("Output dir %s does not exist!\n",path); exit(1);
3440 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3442 QCString baseName=m_diskName;
3443 if (m_inverse) baseName+="_dep";
3445 baseName=convertNameToFile(baseName);
3446 QCString mapName=escapeCharsInString(m_startNode->m_label,FALSE);
3447 if (m_inverse) mapName+="dep";
3449 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3450 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3451 QCString absDotName = absBaseName+".dot";
3452 QCString absMapName = absBaseName+".map";
3453 QCString absPdfName = absBaseName+".pdf";
3454 QCString absEpsName = absBaseName+".eps";
3455 QCString absImgName = absBaseName+"."+imgExt;
3457 bool regenerate = FALSE;
3458 if (updateDotGraph(m_startNode,
3459 DotNode::Dependency,
3463 FALSE, // renderParents
3464 m_inverse, // backArrows
3465 m_startNode->label()
3467 !checkDeliverables(format==BITMAP ? absImgName :
3468 usePDFLatex ? absPdfName : absEpsName,
3469 format==BITMAP && generateImageMap ? absMapName : QCString())
3475 // run dot to create a bitmap image
3476 QCString dotArgs(maxCmdLine);
3477 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3478 dotRun->addJob(imgExt,absImgName);
3479 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3480 DotManager::instance()->addRun(dotRun);
3482 else if (format==EPS)
3484 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3487 dotRun->addJob("pdf",absPdfName);
3491 dotRun->addJob("ps",absEpsName);
3493 DotManager::instance()->addRun(dotRun);
3497 Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3499 if (format==BITMAP && generateImageMap)
3501 if (imgExt=="svg") // Scalable vector graphics
3503 out << "<div class=\"center\">";
3504 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3508 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3510 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3511 out << "<!-- SVG " << mapId << " -->" << endl;
3513 out << "</div>" << endl;
3515 else // bitmap graphics
3517 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3518 << imgExt << "\" border=\"0\" usemap=\"#"
3519 << mapName << "\" alt=\"\"/>";
3520 out << "</div>" << endl;
3522 QCString absMapName = absBaseName+".map";
3523 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3525 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3526 FALSE,QCString(),mapName);
3527 out << "<!-- MAP " << mapId << " -->" << endl;
3531 else if (format==EPS) // encapsulated postscript
3533 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3535 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3536 out << endl << "% FIG " << figId << endl;
3539 if (!regenerate) removeDotGraph(absDotName);
3544 bool DotInclDepGraph::isTrivial() const
3546 return m_startNode->m_children==0;
3549 bool DotInclDepGraph::isTooBig() const
3551 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3552 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
3553 return numNodes>=maxNodes;
3556 void DotInclDepGraph::writeXML(FTextStream &t)
3558 QDictIterator<DotNode> dni(*m_usedNodes);
3560 for (;(node=dni.current());++dni)
3562 node->writeXML(t,FALSE);
3566 void DotInclDepGraph::writeDocbook(FTextStream &t)
3568 QDictIterator<DotNode> dni(*m_usedNodes);
3570 for (;(node=dni.current());++dni)
3572 node->writeDocbook(t,FALSE);
3576 //-------------------------------------------------------------
3578 int DotCallGraph::m_curNodeNumber = 0;
3580 void DotCallGraph::buildGraph(DotNode *n,MemberDef *md,int distance)
3582 MemberSDict *refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers();
3585 MemberSDict::Iterator mri(*refs);
3587 for (;(rmd=mri.current());++mri)
3589 if (rmd->showInCallGraph())
3592 uniqueId=rmd->getReference()+"$"+
3593 rmd->getOutputFileBase()+"#"+rmd->anchor();
3594 DotNode *bn = m_usedNodes->find(uniqueId);
3595 if (bn) // file is already a node in the graph
3597 n->addChild(bn,0,0,0);
3599 bn->setDistance(distance);
3604 if (Config_getBool("HIDE_SCOPE_NAMES"))
3606 name = rmd->getOuterScope()==m_scope ?
3607 rmd->name() : rmd->qualifiedName();
3611 name = rmd->qualifiedName();
3613 QCString tooltip = rmd->briefDescriptionAsTooltip();
3616 linkToText(rmd->getLanguage(),name,FALSE),
3621 n->addChild(bn,0,0,0);
3623 bn->setDistance(distance);
3624 m_usedNodes->insert(uniqueId,bn);
3626 buildGraph(bn,rmd,distance+1);
3633 void DotCallGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
3635 while (queue.count()>0 && maxNodes>0)
3637 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
3638 DotNode *n = queue.take(0);
3639 if (!n->isVisible() && n->distance()<=maxDistance) // not yet processed
3643 // add direct children
3646 QListIterator<DotNode> li(*n->m_children);
3648 for (li.toFirst();(dn=li.current());++li)
3657 void DotCallGraph::determineTruncatedNodes(QList<DotNode> &queue)
3659 while (queue.count()>0)
3661 DotNode *n = queue.take(0);
3662 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
3664 bool truncated = FALSE;
3667 QListIterator<DotNode> li(*n->m_children);
3669 for (li.toFirst();(dn=li.current());++li)
3671 if (!dn->isVisible())
3677 n->markAsTruncated(truncated);
3684 DotCallGraph::DotCallGraph(MemberDef *md,bool inverse)
3686 m_inverse = inverse;
3687 m_diskName = md->getOutputFileBase()+"_"+md->anchor();
3688 m_scope = md->getOuterScope();
3690 uniqueId = md->getReference()+"$"+
3691 md->getOutputFileBase()+"#"+md->anchor();
3693 if (Config_getBool("HIDE_SCOPE_NAMES"))
3699 name = md->qualifiedName();
3701 m_startNode = new DotNode(m_curNodeNumber++,
3702 linkToText(md->getLanguage(),name,FALSE),
3707 m_startNode->setDistance(0);
3708 m_usedNodes = new QDict<DotNode>(1009);
3709 m_usedNodes->insert(uniqueId,m_startNode);
3710 buildGraph(m_startNode,md,1);
3712 static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3713 int maxNodes = nodes;
3714 //int directChildNodes = 1;
3715 //if (m_startNode->m_children!=0)
3716 // directChildNodes+=m_startNode->m_children->count();
3717 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3718 QList<DotNode> openNodeQueue;
3719 openNodeQueue.append(m_startNode);
3720 determineVisibleNodes(openNodeQueue,maxNodes);
3721 openNodeQueue.clear();
3722 openNodeQueue.append(m_startNode);
3723 determineTruncatedNodes(openNodeQueue);
3726 DotCallGraph::~DotCallGraph()
3728 deleteNodes(m_startNode);
3732 QCString DotCallGraph::writeGraph(FTextStream &out, GraphOutputFormat format,
3733 const char *path,const char *fileName,
3734 const char *relPath,bool generateImageMap,int
3738 // store the original directory
3741 err("Output dir %s does not exist!\n",path); exit(1);
3743 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3745 QCString baseName = m_diskName + (m_inverse ? "_icgraph" : "_cgraph");
3746 QCString mapName = baseName;
3748 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3749 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3750 QCString absDotName = absBaseName+".dot";
3751 QCString absMapName = absBaseName+".map";
3752 QCString absPdfName = absBaseName+".pdf";
3753 QCString absEpsName = absBaseName+".eps";
3754 QCString absImgName = absBaseName+"."+imgExt;
3756 bool regenerate = FALSE;
3757 if (updateDotGraph(m_startNode,
3762 FALSE, // renderParents
3763 m_inverse, // backArrows
3764 m_startNode->label()
3766 !checkDeliverables(format==BITMAP ? absImgName :
3767 usePDFLatex ? absPdfName : absEpsName,
3768 format==BITMAP && generateImageMap ? absMapName : QCString())
3774 // run dot to create a bitmap image
3775 QCString dotArgs(maxCmdLine);
3776 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3777 dotRun->addJob(imgExt,absImgName);
3778 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3779 DotManager::instance()->addRun(dotRun);
3782 else if (format==EPS)
3784 // run dot to create a .eps image
3785 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3788 dotRun->addJob("pdf",absPdfName);
3792 dotRun->addJob("ps",absEpsName);
3794 DotManager::instance()->addRun(dotRun);
3798 Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3800 if (format==BITMAP && generateImageMap)
3802 if (imgExt=="svg") // Scalable vector graphics
3804 out << "<div class=\"center\">";
3805 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3809 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3811 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3812 out << "<!-- SVG " << mapId << " -->" << endl;
3814 out << "</div>" << endl;
3816 else // bitmap graphics
3818 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3819 << imgExt << "\" border=\"0\" usemap=\"#"
3820 << mapName << "\" alt=\"";
3822 out << "</div>" << endl;
3824 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3826 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3827 FALSE,QCString(),mapName);
3828 out << "<!-- MAP " << mapId << " -->" << endl;
3832 else if (format==EPS) // encapsulated postscript
3834 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3836 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3837 out << endl << "% FIG " << figId << endl;
3840 if (!regenerate) removeDotGraph(absDotName);
3845 bool DotCallGraph::isTrivial() const
3847 return m_startNode->m_children==0;
3850 bool DotCallGraph::isTooBig() const
3852 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3853 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
3854 return numNodes>=maxNodes;
3857 //-------------------------------------------------------------
3859 DotDirDeps::DotDirDeps(DirDef *dir) : m_dir(dir)
3863 DotDirDeps::~DotDirDeps()
3867 QCString DotDirDeps::writeGraph(FTextStream &out,
3868 GraphOutputFormat format,
3870 const char *fileName,
3871 const char *relPath,
3872 bool generateImageMap,
3876 // store the original directory
3879 err("Output dir %s does not exist!\n",path); exit(1);
3881 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3883 QCString baseName=m_dir->getOutputFileBase()+"_dep";
3884 QCString mapName=escapeCharsInString(baseName,FALSE);
3886 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3887 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3888 QCString absDotName = absBaseName+".dot";
3889 QCString absMapName = absBaseName+".map";
3890 QCString absPdfName = absBaseName+".pdf";
3891 QCString absEpsName = absBaseName+".eps";
3892 QCString absImgName = absBaseName+"."+imgExt;
3894 // compute md5 checksum of the graph were are about to generate
3896 FTextStream md5stream(&theGraph);
3897 m_dir->writeDepGraph(md5stream);
3899 QCString sigStr(33);
3900 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
3901 MD5SigToString(md5_sig,sigStr.data(),33);
3902 bool regenerate=FALSE;
3903 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
3904 !checkDeliverables(format==BITMAP ? absImgName :
3905 usePDFLatex ? absPdfName : absEpsName,
3906 format==BITMAP && generateImageMap ? absMapName : QCString())
3911 QFile f(absDotName);
3912 if (!f.open(IO_WriteOnly))
3914 err("Cannot create file %s.dot for writing!\n",baseName.data());
3917 t << theGraph.data();
3922 // run dot to create a bitmap image
3923 QCString dotArgs(maxCmdLine);
3924 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3925 dotRun->addJob(imgExt,absImgName);
3926 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3927 DotManager::instance()->addRun(dotRun);
3929 else if (format==EPS)
3931 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3934 dotRun->addJob("pdf",absPdfName);
3938 dotRun->addJob("ps",absEpsName);
3940 DotManager::instance()->addRun(dotRun);
3943 Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3945 if (format==BITMAP && generateImageMap)
3947 if (imgExt=="svg") // Scalable vector graphics
3949 out << "<div class=\"center\">";
3950 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3954 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3956 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3957 out << "<!-- SVG " << mapId << " -->" << endl;
3959 out << "</div>" << endl;
3961 else // bitmap graphics
3963 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3964 << imgExt << "\" border=\"0\" usemap=\"#"
3965 << mapName << "\" alt=\"";
3966 out << convertToXML(m_dir->displayName());
3968 out << "</div>" << endl;
3970 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3972 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3973 TRUE,QCString(),mapName);
3974 out << "<!-- MAP " << mapId << " -->" << endl;
3978 else if (format==EPS)
3980 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3982 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3983 out << endl << "% FIG " << figId << endl;
3986 if (!regenerate) removeDotGraph(absDotName);
3991 bool DotDirDeps::isTrivial() const
3993 return m_dir->depGraphIsTrivial();
3996 //-------------------------------------------------------------
3998 void generateGraphLegend(const char *path)
4001 // store the original directory
4004 err("Output dir %s does not exist!\n",path); exit(1);
4008 FTextStream md5stream(&theGraph);
4009 writeGraphHeader(md5stream,theTranslator->trLegendTitle());
4010 md5stream << " Node9 [shape=\"box\",label=\"Inherited\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",fillcolor=\"grey75\",style=\"filled\" fontcolor=\"black\"];\n";
4011 md5stream << " Node10 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4012 md5stream << " Node10 [shape=\"box\",label=\"PublicBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPublicBase" << Doxygen::htmlFileExtension << "\"];\n";
4013 md5stream << " Node11 -> Node10 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4014 md5stream << " Node11 [shape=\"box\",label=\"Truncated\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"red\",URL=\"$classTruncated" << Doxygen::htmlFileExtension << "\"];\n";
4015 md5stream << " Node13 -> Node9 [dir=\"back\",color=\"darkgreen\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4016 md5stream << " Node13 [shape=\"box\",label=\"ProtectedBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classProtectedBase" << Doxygen::htmlFileExtension << "\"];\n";
4017 md5stream << " Node14 -> Node9 [dir=\"back\",color=\"firebrick4\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4018 md5stream << " Node14 [shape=\"box\",label=\"PrivateBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPrivateBase" << Doxygen::htmlFileExtension << "\"];\n";
4019 md5stream << " Node15 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4020 md5stream << " Node15 [shape=\"box\",label=\"Undocumented\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"grey75\"];\n";
4021 md5stream << " Node16 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4022 md5stream << " Node16 [shape=\"box\",label=\"Templ< int >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
4023 md5stream << " Node17 -> Node16 [dir=\"back\",color=\"orange\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"< int >\",fontname=\"" << FONTNAME << "\"];\n";
4024 md5stream << " Node17 [shape=\"box\",label=\"Templ< T >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
4025 md5stream << " Node18 -> Node9 [dir=\"back\",color=\"darkorchid3\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"m_usedClass\",fontname=\"" << FONTNAME << "\"];\n";
4026 md5stream << " Node18 [shape=\"box\",label=\"Used\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classUsed" << Doxygen::htmlFileExtension << "\"];\n";
4027 writeGraphFooter(md5stream);
4029 QCString sigStr(33);
4030 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
4031 MD5SigToString(md5_sig,sigStr.data(),33);
4032 QCString absBaseName = (QCString)path+"/graph_legend";
4033 QCString absDotName = absBaseName+".dot";
4034 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
4035 QCString imgName = "graph_legend."+imgExt;
4036 QCString absImgName = absBaseName+"."+imgExt;
4037 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
4038 !checkDeliverables(absImgName))
4040 QFile dotFile(absDotName);
4041 if (!dotFile.open(IO_WriteOnly))
4043 err("Could not open file %s for writing\n",dotFile.name().data());
4047 FTextStream dotText(&dotFile);
4048 dotText << theGraph;
4051 // run dot to generate the a bitmap image from the graph
4053 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
4054 dotRun->addJob(imgExt,absImgName);
4055 DotManager::instance()->addRun(dotRun);
4059 removeDotGraph(absDotName);
4061 Doxygen::indexList->addImageFile(imgName);
4065 DotManager::instance()->addSVGObject(
4066 absBaseName+Config_getString("HTML_FILE_EXTENSION"),
4068 absImgName,QCString());
4073 void writeDotGraphFromFile(const char *inFile,const char *outDir,
4074 const char *outFile,GraphOutputFormat format)
4079 err("Output dir %s does not exist!\n",outDir); exit(1);
4082 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
4083 QCString imgName = (QCString)outFile+"."+imgExt;
4084 QCString absImgName = d.absPath().utf8()+"/"+imgName;
4085 QCString absOutFile = d.absPath().utf8()+"/"+outFile;
4087 DotRunner dotRun(inFile,d.absPath().data(),FALSE,absImgName);
4089 dotRun.addJob(imgExt,absImgName);
4092 if (Config_getBool("USE_PDFLATEX"))
4094 dotRun.addJob("pdf",absOutFile+".pdf");
4098 dotRun.addJob("ps",absOutFile+".eps");
4102 dotRun.preventCleanUp();
4108 if (format==BITMAP) checkDotResult(absImgName);
4110 Doxygen::indexList->addImageFile(imgName);
4115 /*! Writes user defined image map to the output.
4116 * \param t text stream to write to
4117 * \param inFile just the basename part of the filename
4118 * \param outDir output directory
4119 * \param relPath relative path the to root of the output dir
4120 * \param baseName the base name of the output files
4121 * \param context the scope in which this graph is found (for resolving links)
4122 * \param graphId a unique id for this graph, use for dynamic sections
4124 void writeDotImageMapFromFile(FTextStream &t,
4125 const QCString &inFile, const QCString &outDir,
4126 const QCString &relPath, const QCString &baseName,
4127 const QCString &context,int graphId)
4133 err("Output dir %s does not exist!\n",outDir.data()); exit(1);
4136 QCString mapName = baseName+".map";
4137 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
4138 QCString imgName = baseName+"."+imgExt;
4139 QCString absOutFile = d.absPath().utf8()+"/"+mapName;
4141 DotRunner dotRun(inFile,d.absPath().data(),FALSE);
4142 dotRun.addJob(MAP_CMD,absOutFile);
4143 dotRun.preventCleanUp();
4149 if (imgExt=="svg") // vector graphics
4151 //writeSVGFigureLink(t,relPath,inFile,inFile+".svg");
4152 //DotFilePatcher patcher(inFile+".svg");
4153 QCString svgName=outDir+"/"+baseName+".svg";
4154 writeSVGFigureLink(t,relPath,baseName,svgName);
4155 DotFilePatcher patcher(svgName);
4156 patcher.addSVGConversion(relPath,TRUE,context,TRUE,graphId);
4159 else // bitmap graphics
4161 t << "<img src=\"" << relPath << imgName << "\" alt=\""
4162 << imgName << "\" border=\"0\" usemap=\"#" << mapName << "\"/>" << endl
4163 << "<map name=\"" << mapName << "\" id=\"" << mapName << "\">";
4165 convertMapFile(t, absOutFile, relPath ,TRUE, context);
4167 t << "</map>" << endl;
4169 d.remove(absOutFile);
4172 //-------------------------------------------------------------
4174 DotGroupCollaboration::DotGroupCollaboration(GroupDef* gd)
4177 QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase();
4178 m_usedNodes = new QDict<DotNode>(1009);
4179 m_rootNode = new DotNode(m_curNodeId++, gd->groupTitle(), "", tmp_url, TRUE );
4180 m_rootNode->markAsVisible();
4181 m_usedNodes->insert(gd->name(), m_rootNode );
4182 m_edges.setAutoDelete(TRUE);
4184 m_diskName = gd->getOutputFileBase();
4189 DotGroupCollaboration::~DotGroupCollaboration()
4194 void DotGroupCollaboration::buildGraph(GroupDef* gd)
4197 //===========================
4201 GroupList *groups = gd->partOfGroups();
4204 GroupListIterator gli(*groups);
4206 for (gli.toFirst();(d=gli.current());++gli)
4208 DotNode* nnode = m_usedNodes->find(d->name());
4211 tmp_url = d->getReference()+"$"+d->getOutputFileBase();
4212 QCString tooltip = d->briefDescriptionAsTooltip();
4213 nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_url );
4214 nnode->markAsVisible();
4215 m_usedNodes->insert(d->name(), nnode );
4218 addEdge( nnode, m_rootNode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
4223 if ( gd->getSubGroups() && gd->getSubGroups()->count() )
4225 QListIterator<GroupDef> defli(*gd->getSubGroups());
4227 for (;(def=defli.current());++defli)
4229 DotNode* nnode = m_usedNodes->find(def->name());
4232 tmp_url = def->getReference()+"$"+def->getOutputFileBase();
4233 QCString tooltip = def->briefDescriptionAsTooltip();
4234 nnode = new DotNode(m_curNodeId++, def->groupTitle(), tooltip, tmp_url );
4235 nnode->markAsVisible();
4236 m_usedNodes->insert(def->name(), nnode );
4239 addEdge( m_rootNode, nnode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
4243 //=======================
4244 // Write collaboration
4247 addMemberList( gd->getMemberList(MemberListType_allMembersList) );
4250 if ( gd->getClasses() && gd->getClasses()->count() )
4252 ClassSDict::Iterator defli(*gd->getClasses());
4254 for (;(def=defli.current());++defli)
4256 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4257 if (!def->anchor().isEmpty())
4259 tmp_url+="#"+def->anchor();
4261 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tclass );
4266 if ( gd->getNamespaces() && gd->getNamespaces()->count() )
4268 NamespaceSDict::Iterator defli(*gd->getNamespaces());
4270 for (;(def=defli.current());++defli)
4272 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4273 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tnamespace );
4278 if ( gd->getFiles() && gd->getFiles()->count() )
4280 QListIterator<FileDef> defli(*gd->getFiles());
4282 for (;(def=defli.current());++defli)
4284 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4285 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tfile );
4290 if ( gd->getPages() && gd->getPages()->count() )
4292 PageSDict::Iterator defli(*gd->getPages());
4294 for (;(def=defli.current());++defli)
4296 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4297 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tpages );
4302 if ( gd->getDirs() && gd->getDirs()->count() )
4304 QListIterator<DirDef> defli(*gd->getDirs());
4306 for (;(def=defli.current());++defli)
4308 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4309 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tdir );
4314 void DotGroupCollaboration::addMemberList( MemberList* ml )
4316 if ( !( ml && ml->count()) ) return;
4317 MemberListIterator defli(*ml);
4319 for (;(def=defli.current());++defli)
4321 QCString tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension
4323 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tmember );
4327 DotGroupCollaboration::Edge* DotGroupCollaboration::addEdge(
4328 DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType,
4329 const QCString& _label, const QCString& _url )
4331 // search a existing link.
4332 QListIterator<Edge> lli(m_edges);
4334 for ( lli.toFirst(); (newEdge=lli.current()); ++lli)
4336 if ( newEdge->pNStart==_pNStart &&
4337 newEdge->pNEnd==_pNEnd &&
4338 newEdge->eType==_eType
4340 { // edge already found
4344 if ( newEdge==0 ) // new link
4346 newEdge = new Edge(_pNStart,_pNEnd,_eType);
4347 m_edges.append( newEdge );
4350 if (!_label.isEmpty())
4352 newEdge->links.append(new Link(_label,_url));
4358 void DotGroupCollaboration::addCollaborationMember(
4359 Definition* def, QCString& url, EdgeType eType )
4361 // Create group nodes
4362 if ( !def->partOfGroups() )
4364 GroupListIterator gli(*def->partOfGroups());
4367 for (;(d=gli.current());++gli)
4369 DotNode* nnode = m_usedNodes->find(d->name());
4370 if ( nnode != m_rootNode )
4374 tmp_str = d->getReference()+"$"+d->getOutputFileBase();
4375 QCString tooltip = d->briefDescriptionAsTooltip();
4376 nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_str );
4377 nnode->markAsVisible();
4378 m_usedNodes->insert(d->name(), nnode );
4380 tmp_str = def->qualifiedName();
4381 addEdge( m_rootNode, nnode, eType, tmp_str, url );
4387 QCString DotGroupCollaboration::writeGraph( FTextStream &t, GraphOutputFormat format,
4388 const char *path, const char *fileName, const char *relPath,
4389 bool writeImageMap,int graphId) const
4392 // store the original directory
4395 err("Output dir %s does not exist!\n",path); exit(1);
4397 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
4400 FTextStream md5stream(&theGraph);
4401 writeGraphHeader(md5stream,m_rootNode->label());
4403 // clean write flags
4404 QDictIterator<DotNode> dni(*m_usedNodes);
4406 for (dni.toFirst();(pn=dni.current());++dni)
4408 pn->clearWriteFlag();
4411 // write other nodes.
4412 for (dni.toFirst();(pn=dni.current());++dni)
4414 pn->write(md5stream,DotNode::Inheritance,format,TRUE,FALSE,FALSE,FALSE);
4418 QListIterator<Edge> eli(m_edges);
4420 for (eli.toFirst();(edge=eli.current());++eli)
4422 edge->write( md5stream );
4425 writeGraphFooter(md5stream);
4428 QCString sigStr(33);
4429 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
4430 MD5SigToString(md5_sig,sigStr.data(),33);
4431 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
4432 QCString baseName = m_diskName;
4433 QCString imgName = baseName+"."+imgExt;
4434 QCString mapName = baseName+".map";
4435 QCString absPath = d.absPath().data();
4436 QCString absBaseName = absPath+"/"+baseName;
4437 QCString absDotName = absBaseName+".dot";
4438 QCString absImgName = absBaseName+"."+imgExt;
4439 QCString absMapName = absBaseName+".map";
4440 QCString absPdfName = absBaseName+".pdf";
4441 QCString absEpsName = absBaseName+".eps";
4442 bool regenerate=FALSE;
4443 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
4444 !checkDeliverables(format==BITMAP ? absImgName :
4445 usePDFLatex ? absPdfName : absEpsName,
4446 format==BITMAP /*&& generateImageMap*/ ? absMapName : QCString())
4451 QFile dotfile(absDotName);
4452 if (dotfile.open(IO_WriteOnly))
4454 FTextStream tdot(&dotfile);
4459 if (format==BITMAP) // run dot to create a bitmap image
4461 QCString dotArgs(maxCmdLine);
4463 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
4464 dotRun->addJob(imgExt,absImgName);
4465 if (writeImageMap) dotRun->addJob(MAP_CMD,absMapName);
4466 DotManager::instance()->addRun(dotRun);
4469 else if (format==EPS)
4471 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
4474 dotRun->addJob("pdf",absPdfName);
4478 dotRun->addJob("ps",absEpsName);
4480 DotManager::instance()->addRun(dotRun);
4484 if (format==BITMAP && writeImageMap)
4486 QCString mapLabel = escapeCharsInString(baseName,FALSE);
4487 t << "<center><table><tr><td>";
4491 t << "<div class=\"center\">";
4492 if (regenerate || !writeSVGFigureLink(t,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
4496 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
4498 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
4499 t << "<!-- SVG " << mapId << " -->" << endl;
4501 t << "</div>" << endl;
4505 t << "<img src=\"" << relPath << imgName
4506 << "\" border=\"0\" alt=\"\" usemap=\"#"
4507 << mapLabel << "\"/>" << endl;
4508 if (regenerate || !insertMapFile(t,absMapName,relPath,mapLabel))
4510 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
4511 FALSE,QCString(),mapLabel);
4512 t << "<!-- MAP " << mapId << " -->" << endl;
4516 t << "</td></tr></table></center>" << endl;
4518 else if (format==EPS)
4520 if (regenerate || !writeVecGfxFigure(t,baseName,absBaseName))
4522 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
4523 t << endl << "% FIG " << figId << endl;
4526 if (!regenerate) removeDotGraph(absDotName);
4531 void DotGroupCollaboration::Edge::write( FTextStream &t ) const
4533 const char* linkTypeColor[] = {
4542 QCString arrowStyle = "dir=\"none\", style=\"dashed\"";
4543 t << " Node" << pNStart->number();
4545 t << "Node" << pNEnd->number();
4547 t << " [shape=plaintext";
4548 if (links.count()>0) // there are links
4551 // HTML-like edge labels crash on my Mac with Graphviz 2.0! and
4552 // are not supported by older version of dot.
4554 //t << label=<<TABLE BORDER=\"0\" CELLBORDER=\"0\">";
4555 //QListIterator<Link> lli(links);
4557 //for( lli.toFirst(); (link=lli.current()); ++lli)
4560 // if ( !link->url.isEmpty() )
4561 // t << " HREF=\"" << link->url << "\"";
4562 // t << ">" << link->label << "</TD></TR>";
4567 QListIterator<Link> lli(links);
4571 const int maxLabels = 10;
4572 for( lli.toFirst(); (link=lli.current()) && count<maxLabels; ++lli,++count)
4574 if (first) first=FALSE; else t << "\\n";
4575 t << convertLabel(link->label);
4577 if (count==maxLabels) t << "\\n...";
4584 arrowStyle = "dir=\"back\", style=\"solid\"";
4586 t << ", color=\"" << linkTypeColor[(int)eType] << "\"";
4589 t << ", " << arrowStyle;
4593 bool DotGroupCollaboration::isTrivial() const
4595 return m_usedNodes->count() <= 1;
4598 void DotGroupCollaboration::writeGraphHeader(FTextStream &t,
4599 const QCString &title) const
4602 if (title.isEmpty())
4604 t << "\"Dot Graph\"";
4608 t << "\"" << convertToXML(title) << "\"";
4612 if (Config_getBool("DOT_TRANSPARENT"))
4614 t << " bgcolor=\"transparent\";" << endl;
4616 t << " edge [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\","
4617 "labelfontname=\"" << FONTNAME << "\",labelfontsize=\"" << FONTSIZE << "\"];\n";
4618 t << " node [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\",shape=record];\n";
4619 t << " rankdir=LR;\n";
4622 void writeDotDirDepGraph(FTextStream &t,DirDef *dd)
4624 t << "digraph \"" << dd->displayName() << "\" {\n";
4625 if (Config_getBool("DOT_TRANSPARENT"))
4627 t << " bgcolor=transparent;\n";
4629 t << " compound=true\n";
4630 t << " node [ fontsize=\"" << FONTSIZE << "\", fontname=\"" << FONTNAME << "\"];\n";
4631 t << " edge [ labelfontsize=\"" << FONTSIZE << "\", labelfontname=\"" << FONTNAME << "\"];\n";
4633 QDict<DirDef> dirsInGraph(257);
4635 dirsInGraph.insert(dd->getOutputFileBase(),dd);
4638 t << " subgraph cluster" << dd->parent()->getOutputFileBase() << " {\n";
4639 t << " graph [ bgcolor=\"#ddddee\", pencolor=\"black\", label=\""
4640 << dd->parent()->shortName()
4641 << "\" fontname=\"" << FONTNAME << "\", fontsize=\"" << FONTSIZE << "\", URL=\"";
4642 t << dd->parent()->getOutputFileBase() << Doxygen::htmlFileExtension;
4645 if (dd->isCluster())
4647 t << " subgraph cluster" << dd->getOutputFileBase() << " {\n";
4648 t << " graph [ bgcolor=\"#eeeeff\", pencolor=\"black\", label=\"\""
4649 << " URL=\"" << dd->getOutputFileBase() << Doxygen::htmlFileExtension
4651 t << " " << dd->getOutputFileBase() << " [shape=plaintext label=\""
4652 << dd->shortName() << "\"];\n";
4654 // add nodes for sub directories
4655 QListIterator<DirDef> sdi(dd->subDirs());
4657 for (sdi.toFirst();(sdir=sdi.current());++sdi)
4659 t << " " << sdir->getOutputFileBase() << " [shape=box label=\""
4660 << sdir->shortName() << "\"";
4661 if (sdir->isCluster())
4663 t << " color=\"red\"";
4667 t << " color=\"black\"";
4669 t << " fillcolor=\"white\" style=\"filled\"";
4670 t << " URL=\"" << sdir->getOutputFileBase()
4671 << Doxygen::htmlFileExtension << "\"";
4673 dirsInGraph.insert(sdir->getOutputFileBase(),sdir);
4679 t << " " << dd->getOutputFileBase() << " [shape=box, label=\""
4680 << dd->shortName() << "\", style=\"filled\", fillcolor=\"#eeeeff\","
4681 << " pencolor=\"black\", URL=\"" << dd->getOutputFileBase()
4682 << Doxygen::htmlFileExtension << "\"];\n";
4689 // add nodes for other used directories
4690 QDictIterator<UsedDir> udi(*dd->usedDirs());
4692 //printf("*** For dir %s\n",shortName().data());
4693 for (udi.toFirst();(udir=udi.current());++udi)
4694 // for each used dir (=directly used or a parent of a directly used dir)
4696 const DirDef *usedDir=udir->dir();
4700 //printf("*** check relation %s->%s same_parent=%d !%s->isParentOf(%s)=%d\n",
4701 // dir->shortName().data(),usedDir->shortName().data(),
4702 // dir->parent()==usedDir->parent(),
4703 // usedDir->shortName().data(),
4704 // shortName().data(),
4705 // !usedDir->isParentOf(this)
4707 if (dir!=usedDir && dir->parent()==usedDir->parent() &&
4708 !usedDir->isParentOf(dd))
4709 // include if both have the same parent (or no parent)
4711 t << " " << usedDir->getOutputFileBase() << " [shape=box label=\""
4712 << usedDir->shortName() << "\"";
4713 if (usedDir->isCluster())
4715 if (!Config_getBool("DOT_TRANSPARENT"))
4717 t << " fillcolor=\"white\" style=\"filled\"";
4719 t << " color=\"red\"";
4721 t << " URL=\"" << usedDir->getOutputFileBase()
4722 << Doxygen::htmlFileExtension << "\"];\n";
4723 dirsInGraph.insert(usedDir->getOutputFileBase(),usedDir);
4730 // add relations between all selected directories
4732 QDictIterator<DirDef> di(dirsInGraph);
4733 for (di.toFirst();(dir=di.current());++di) // foreach dir in the graph
4735 QDictIterator<UsedDir> udi(*dir->usedDirs());
4737 for (udi.toFirst();(udir=udi.current());++udi) // foreach used dir
4739 const DirDef *usedDir=udir->dir();
4740 if ((dir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4741 (usedDir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4742 !usedDir->isParentOf(dir) && // don't point to own parent
4743 dirsInGraph.find(usedDir->getOutputFileBase())) // only point to nodes that are in the graph
4745 QCString relationName;
4746 relationName.sprintf("dir_%06d_%06d",dir->dirCount(),usedDir->dirCount());
4747 if (Doxygen::dirRelations.find(relationName)==0)
4750 Doxygen::dirRelations.append(relationName,
4751 new DirRelation(relationName,dir,udir));
4753 int nrefs = udir->filePairs().count();
4754 t << " " << dir->getOutputFileBase() << "->"
4755 << usedDir->getOutputFileBase();
4756 t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5";
4757 t << " headhref=\"" << relationName << Doxygen::htmlFileExtension