1 /*****************************************************************************
3 * $Id: dot.cpp,v 1.20 2001/03/19 19:27:40 root Exp $
6 * Copyright (C) 1997-2012 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
33 #include "docparser.h"
38 #include "vhdldocgen.h"
41 #include "ftextstream.h"
47 #include <qwaitcondition.h>
49 #define MAP_CMD "cmapx"
51 //#define FONTNAME "Helvetica"
52 #define FONTNAME getDotFontName()
53 #define FONTSIZE getDotFontSize()
55 //--------------------------------------------------------------------
57 static const char svgZoomHeader[] =
58 "<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"
60 " <circle id=\"rim\" cx=\"0\" cy=\"0\" r=\"7\"/>\n"
61 " <circle id=\"rim2\" cx=\"0\" cy=\"0\" r=\"3.5\"/>\n"
62 " <g id=\"zoomPlus\">\n"
63 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
64 " <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomplus.mouseover\" end=\"zoomplus.mouseout\"/>\n"
66 " <path d=\"M-4,0h8M0,-4v8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
68 " <g id=\"zoomMin\">\n"
69 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
70 " <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomminus.mouseover\" end=\"zoomminus.mouseout\"/>\n"
72 " <path d=\"M-4,0h8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
74 " <g id=\"dirArrow\">\n"
75 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
77 " <g id=\"resetDef\">\n"
78 " <use xlink:href=\"#rim2\" fill=\"#404040\">\n"
79 " <set attributeName=\"fill\" to=\"#808080\" begin=\"reset.mouseover\" end=\"reset.mouseout\"/>\n"
84 "<script type=\"text/javascript\">\n"
87 static const char svgZoomFooter[] =
88 " <g id=\"navigator\" transform=\"translate(0 0)\" fill=\"#404254\">\n"
89 " <rect fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\".5\" x=\"0\" y=\"0\" width=\"60\" height=\"60\"/>\n"
90 " <use id=\"zoomplus\" xlink:href=\"#zoomPlus\" x=\"17\" y=\"9\" onmousedown=\"handleZoom(evt,'in')\"/>\n"
91 " <use id=\"zoomminus\" xlink:href=\"#zoomMin\" x=\"42\" y=\"9\" onmousedown=\"handleZoom(evt,'out')\"/>\n"
92 " <use id=\"reset\" xlink:href=\"#resetDef\" x=\"30\" y=\"36\" onmousedown=\"handleReset()\"/>\n"
93 " <g id=\"arrowUp\" xlink:href=\"#dirArrow\" transform=\"translate(30 24)\" onmousedown=\"handlePan(0,-1)\">\n"
94 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
95 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowUp.mouseover\" end=\"arrowUp.mouseout\"/>\n"
97 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
99 " <g id=\"arrowRight\" xlink:href=\"#dirArrow\" transform=\"rotate(90) translate(36 -43)\" onmousedown=\"handlePan(1,0)\">\n"
100 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
101 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowRight.mouseover\" end=\"arrowRight.mouseout\"/>\n"
103 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
105 " <g id=\"arrowDown\" xlink:href=\"#dirArrow\" transform=\"rotate(180) translate(-30 -48)\" onmousedown=\"handlePan(0,1)\">\n"
106 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
107 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowDown.mouseover\" end=\"arrowDown.mouseout\"/>\n"
109 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
111 " <g id=\"arrowLeft\" xlink:href=\"#dirArrow\" transform=\"rotate(270) translate(-36 17)\" onmousedown=\"handlePan(-1,0)\">\n"
112 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
113 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowLeft.mouseover\" end=\"arrowLeft.mouseout\"/>\n"
115 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
119 " <svg viewBox=\"0 0 25 25\" width=\"100%\" height=\"30px\" preserveAspectRatio=\"xMaxYMin meet\"> \n"
120 " <g id=\"printButton\" transform=\"scale(0.4 0.4)\" onmousedown=\"handlePrint(evt)\">\n"
121 " <rect height=\"23.33753581\" id=\"paper\" rx=\"2\" style=\"fill:#f2f5e9;fill-rule:evenodd;stroke:#111111;stroke-width:3.224;stroke-linejoin:round;\" transform=\"matrix(1.000000,0.000000,-0.339266,0.940691,0.000000,0.000000)\" width=\"25.55231285\" x=\"26.69387353\" y=\"7.36162977\"/>\n"
122 " <rect height=\"26.272097\" id=\"body\" rx=\"2\" style=\"fill:#404040;fill-rule:evenodd;stroke:#111111;stroke-width:3.125;stroke-linejoin:round;\" width=\"50\" x=\"4.5295201\" y=\"27.078951\"/>\n"
123 " <rect height=\"8.27750969\" id=\"tray\" style=\"fill:#d2d5c9;fill-rule:evenodd;stroke:#111111;stroke-width:3.125;stroke-linecap:round;stroke-linejoin:round;\" width=\"40\" x=\"10.28778839\" y=\"44.96812282\"/>\n"
127 " <svg viewBox=\"0 0 15 15\" width=\"100%\" height=\"30px\" preserveAspectRatio=\"xMaxYMin meet\">\n"
128 " <g id=\"arrow_out\" transform=\"scale(0.3 0.3)\">\n"
129 " <a xlink:href=\"$orgname\" target=\"_base\">\n"
130 " <rect id=\"button\" ry=\"5\" rx=\"5\" y=\"6\" x=\"6\" height=\"38\" width=\"38\"\n"
131 " fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\"1.0\"/>\n"
132 " <path id=\"arrow\"\n"
133 " 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"
134 " style=\"fill:#404040;\"/>\n"
141 //--------------------------------------------------------------------
143 static const int maxCmdLine = 40960;
145 /*! mapping from protection levels to color names */
146 static const char *normalEdgeColorMap[] =
148 "midnightblue", // Public
149 "darkgreen", // Protected
150 "firebrick4", // Private
151 "darkorchid3", // "use" relation
152 "grey75", // Undocumented
153 "orange" // template relation
156 static const char *normalArrowStyleMap[] =
159 "empty", // Protected
161 "open", // "use" relation
163 0 // template relation
166 static const char *normalEdgeStyleMap[] =
168 "solid", // inheritance
172 static const char *umlEdgeColorMap[] =
174 "midnightblue", // Public
175 "darkgreen", // Protected
176 "firebrick4", // Private
177 "grey25", // "use" relation
178 "grey75", // Undocumented
179 "orange" // template relation
182 static const char *umlArrowStyleMap[] =
185 "onormal", // Protected
186 "onormal", // Private
187 "odiamond", // "use" relation
189 0 // template relation
192 static const char *umlEdgeStyleMap[] =
194 "solid", // inheritance
198 /** Helper struct holding the properties of a edge in a dot graph. */
199 struct EdgeProperties
201 const char * const *edgeColorMap;
202 const char * const *arrowStyleMap;
203 const char * const *edgeStyleMap;
206 static EdgeProperties normalEdgeProps =
208 normalEdgeColorMap, normalArrowStyleMap, normalEdgeStyleMap
211 static EdgeProperties umlEdgeProps =
213 umlEdgeColorMap, umlArrowStyleMap, umlEdgeStyleMap
217 static QCString getDotFontName()
219 static QCString dotFontName = Config_getString("DOT_FONTNAME");
220 if (dotFontName.isEmpty())
222 //dotFontName="FreeSans.ttf";
223 dotFontName="Helvetica";
228 static int getDotFontSize()
230 static int dotFontSize = Config_getInt("DOT_FONTSIZE");
231 if (dotFontSize<4) dotFontSize=4;
235 static void writeGraphHeader(FTextStream &t,const QCString &title=QCString())
237 static bool interactiveSVG = Config_getBool("INTERACTIVE_SVG");
241 t << "\"Dot Graph\"";
245 t << "\"" << convertToXML(title) << "\"";
247 t << endl << "{" << endl;
248 if (interactiveSVG) // insert a comment to force regeneration when this
251 t << " // INTERACTIVE_SVG=YES\n";
253 if (Config_getBool("DOT_TRANSPARENT"))
255 t << " bgcolor=\"transparent\";" << endl;
257 t << " edge [fontname=\"" << FONTNAME << "\","
258 "fontsize=\"" << FONTSIZE << "\","
259 "labelfontname=\"" << FONTNAME << "\","
260 "labelfontsize=\"" << FONTSIZE << "\"];\n";
261 t << " node [fontname=\"" << FONTNAME << "\","
262 "fontsize=\"" << FONTSIZE << "\",shape=record];\n";
265 static void writeGraphFooter(FTextStream &t)
270 static QCString replaceRef(const QCString &buf,const QCString relPath,
271 bool urlOnly,const QCString &context,const QCString &target=QCString())
273 // search for href="...", store ... part in link
274 QCString href = "href";
275 //bool isXLink=FALSE;
277 int indexS = buf.find("href=\""), indexE;
278 if (indexS>5 && buf.find("xlink:href=\"")!=-1) // XLink href (for SVG)
282 href.prepend("xlink:");
285 if (indexS>=0 && (indexE=buf.find('"',indexS+len))!=-1)
287 QCString link = buf.mid(indexS+len,indexE-indexS-len);
289 if (urlOnly) // for user defined dot graphs
291 if (link.left(5)=="\\ref " || link.left(5)=="@ref ") // \ref url
294 // fake ref node to resolve the url
295 DocRef *df = new DocRef( (DocNode*) 0, link.mid(5), context );
296 result+=externalRef(relPath,df->ref(),TRUE);
297 if (!df->file().isEmpty())
298 result += df->file().data() + Doxygen::htmlFileExtension;
299 if (!df->anchor().isEmpty())
300 result += "#" + df->anchor();
306 result = href+"=\"" + link + "\"";
309 else // ref$url (external ref via tag file), or $url (local ref)
311 int marker = link.find('$');
314 QCString ref = link.left(marker);
315 QCString url = link.mid(marker+1);
318 result = externalLinkTarget() + externalRef(relPath,ref,FALSE);
321 result+=externalRef(relPath,ref,TRUE);
324 else // should not happen, but handle properly anyway
326 result = href+"=\"" + link + "\"";
329 if (!target.isEmpty())
331 result+=" target=\""+target+"\"";
333 QCString leftPart = buf.left(indexS);
334 QCString rightPart = buf.mid(indexE+1);
335 return leftPart + result + rightPart;
343 /*! converts the rectangles in a client site image map into a stream
344 * \param t the stream to which the result is written.
345 * \param mapName the name of the map file.
346 * \param relPath the relative path to the root of the output directory
347 * (used in case CREATE_SUBDIRS is enabled).
348 * \param urlOnly if FALSE the url field in the map contains an external
349 * references followed by a $ and then the URL.
350 * \param context the context (file, class, or namespace) in which the
352 * \returns TRUE if successful.
354 static bool convertMapFile(FTextStream &t,const char *mapName,
355 const QCString relPath, bool urlOnly=FALSE,
356 const QCString &context=QCString())
359 if (!f.open(IO_ReadOnly))
361 err("error: problems opening map file %s for inclusion in the docs!\n"
362 "If you installed Graphviz/dot after a previous failing run, \n"
363 "try deleting the output directory and rerun doxygen.\n",mapName);
366 const int maxLineLen=10240;
367 while (!f.atEnd()) // foreach line
369 QCString buf(maxLineLen);
370 int numBytes = f.readLine(buf.data(),maxLineLen);
371 buf[numBytes-1]='\0';
373 if (buf.left(5)=="<area")
375 t << replaceRef(buf,relPath,urlOnly,context);
381 static QArray<int> s_newNumber;
382 static int s_max_newNumber=0;
384 inline int reNumberNode(int number, bool doReNumbering)
392 int s = s_newNumber.size();
396 ns = s * 3 / 2 + 5; // new size
397 if (number>=ns) // number still doesn't fit
399 ns = number * 3 / 2 + 5;
401 s_newNumber.resize(ns);
402 for (int i=s;i<ns;i++) // clear new part of the array
407 int i = s_newNumber.at(number);
408 if (i == 0) // not yet mapped
410 i = ++s_max_newNumber; // start from 1
411 s_newNumber.at(number) = i;
417 static void resetReNumbering()
420 s_newNumber.resize(s_max_newNumber);
423 static QCString g_dotFontPath;
425 static void setDotFontPath(const char *path)
427 ASSERT(g_dotFontPath.isEmpty());
428 g_dotFontPath = portable_getenv("DOTFONTPATH");
429 QCString newFontPath = Config_getString("DOT_FONTPATH");
430 QCString spath = path;
431 if (!newFontPath.isEmpty() && !spath.isEmpty())
433 newFontPath.prepend(spath+portable_pathListSeparator());
435 else if (newFontPath.isEmpty() && !spath.isEmpty())
441 portable_unsetenv("DOTFONTPATH");
444 portable_setenv("DOTFONTPATH",newFontPath);
447 static void unsetDotFontPath()
449 if (g_dotFontPath.isEmpty())
451 portable_unsetenv("DOTFONTPATH");
455 portable_setenv("DOTFONTPATH",g_dotFontPath);
460 static bool readBoundingBox(const char *fileName,int *width,int *height,bool isEps)
462 QCString bb = isEps ? QCString("%%PageBoundingBox:") : QCString("/MediaBox [");
464 if (!f.open(IO_ReadOnly|IO_Raw))
466 //printf("readBoundingBox: could not open %s\n",fileName);
469 const int maxLineLen=1024;
470 char buf[maxLineLen];
473 int numBytes = f.readLine(buf,maxLineLen-1); // read line
477 const char *p = strstr(buf,bb);
478 if (p) // found PageBoundingBox or /MediaBox string
481 if (sscanf(p+bb.length(),"%d %d %d %d",&x,&y,width,height)!=4)
483 //printf("readBoundingBox sscanf fail\n");
491 //printf("Read error %d!\n",numBytes);
495 err("Failed to extract bounding box from generated diagram file %s\n",fileName);
499 static bool writeVecGfxFigure(FTextStream &out,const QCString &baseName,
500 const QCString &figureName)
502 int width=400,height=550;
503 static bool usePdfLatex = Config_getBool("USE_PDFLATEX");
506 if (!readBoundingBox(figureName+".pdf",&width,&height,FALSE))
508 //printf("writeVecGfxFigure()=0\n");
514 if (!readBoundingBox(figureName+".eps",&width,&height,TRUE))
516 //printf("writeVecGfxFigure()=0\n");
520 //printf("Got PDF/EPS size %d,%d\n",width,height);
521 int maxWidth = 350; /* approx. page width in points, excl. margins */
522 int maxHeight = 550; /* approx. page height in points, excl. margins */
523 out << "\\nopagebreak\n"
524 "\\begin{figure}[H]\n"
527 if (width>maxWidth || height>maxHeight) // figure too big for page
529 // c*width/maxWidth > c*height/maxHeight, where c=maxWidth*maxHeight>0
530 if (width*maxHeight>height*maxWidth)
532 out << "\\includegraphics[width=" << maxWidth << "pt]";
536 out << "\\includegraphics[height=" << maxHeight << "pt]";
541 out << "\\includegraphics[width=" << width << "pt]";
544 out << "{" << baseName << "}\n"
548 //printf("writeVecGfxFigure()=1\n");
552 // extract size from a dot generated SVG file
553 static bool readSVGSize(const QCString &fileName,int *width,int *height)
557 if (!f.open(IO_ReadOnly))
561 const int maxLineLen=4096;
562 char buf[maxLineLen];
563 while (!f.atEnd() && !found)
565 int numBytes = f.readLine(buf,maxLineLen-1); // read line
569 if (strncmp(buf,"<!--zoomable ",13)==0)
573 sscanf(buf,"<!--zoomable %d",height);
574 //printf("Found zoomable for %s!\n",fileName.data());
577 else if (sscanf(buf,"<svg width=\"%dpt\" height=\"%dpt\"",width,height)==2)
579 //printf("Found fixed size %dx%d for %s!\n",*width,*height,fileName.data());
585 //printf("Read error %d!\n",numBytes);
592 static void writeSVGNotSupported(FTextStream &out)
594 out << "<p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p>";
597 // check if a reference to a SVG figure can be written and does so if possible.
598 // return FALSE if not possible (for instance because the SVG file is not yet generated).
599 static bool writeSVGFigureLink(FTextStream &out,const QCString &relPath,
600 const QCString &baseName,const QCString &absImgName)
602 int width=600,height=600;
603 if (!readSVGSize(absImgName,&width,&height))
612 height+=300; // add some extra space for zooming
613 if (height>600) height=600; // clip to maximum height of 600 pixels
614 out << "<div class=\"zoom\">";
615 //out << "<object type=\"image/svg+xml\" data=\""
616 //out << "<embed type=\"image/svg+xml\" src=\""
617 out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
618 << relPath << baseName << ".svg\" width=\"100%\" height=\"" << height << "\">";
622 //out << "<object type=\"image/svg+xml\" data=\""
623 //out << "<embed type=\"image/svg+xml\" src=\""
624 out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
625 << relPath << baseName << ".svg\" width=\""
626 << ((width*96+48)/72) << "\" height=\""
627 << ((height*96+48)/72) << "\">";
629 writeSVGNotSupported(out);
630 //out << "</object>";
641 // since dot silently reproduces the input file when it does not
642 // support the PNG format, we need to check the result.
643 static void checkDotResult(const QCString &imgName)
645 if (Config_getEnum("DOT_IMAGE_FORMAT")=="png")
647 FILE *f = fopen(imgName,"rb");
651 if (fread(data,1,4,f)==4)
653 if (!(data[1]=='P' && data[2]=='N' && data[3]=='G'))
655 err("error: Image `%s' produced by dot is not a valid PNG!\n"
656 "You should either select a different format "
657 "(DOT_IMAGE_FORMAT in the config file) or install a more "
658 "recent version of graphviz (1.7+)\n",imgName.data()
664 err("error: Could not read image `%s' generated by dot!\n",imgName.data());
670 err("error: Could not open image `%s' generated by dot!\n",imgName.data());
675 static bool insertMapFile(FTextStream &out,const QCString &mapFile,
676 const QCString &relPath,const QCString &mapLabel)
678 QFileInfo fi(mapFile);
679 if (fi.exists() && fi.size()>0) // reuse existing map file
682 FTextStream tmpout(&tmpstr);
683 convertMapFile(tmpout,mapFile,relPath);
684 if (!tmpstr.isEmpty())
686 out << "<map name=\"" << mapLabel << "\" id=\"" << mapLabel << "\">" << endl;
688 out << "</map>" << endl;
692 return FALSE; // no map file yet, need to generate it
695 static void removeDotGraph(const QCString &dotName)
697 static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
707 /*! Checks if a file "baseName".md5 exists. If so the contents
708 * are compared with \a md5. If equal FALSE is returned. If the .md5
709 * file does not exist or its contents are not equal to \a md5,
710 * a new .md5 is generated with the \a md5 string as contents.
712 static bool checkAndUpdateMd5Signature(const QCString &baseName,
715 QFile f(baseName+".md5");
716 if (f.open(IO_ReadOnly))
719 QCString md5stored(33);
720 int bytesRead=f.readBlock(md5stored.data(),32);
723 if (bytesRead==32 && md5==md5stored)
730 // create checksum file
731 if (f.open(IO_WriteOnly))
733 f.writeBlock(md5.data(),32);
739 static bool checkDeliverables(const QCString &file1,
740 const QCString &file2=QCString())
744 if (!file1.isEmpty())
747 file1Ok = (fi.exists() && fi.size()>0);
749 if (!file2.isEmpty())
752 file2Ok = (fi.exists() && fi.size()>0);
754 return file1Ok && file2Ok;
757 //--------------------------------------------------------------------
759 /** Class representing a list of DotNode objects. */
760 class DotNodeList : public QList<DotNode>
763 DotNodeList() : QList<DotNode>() {}
765 int compareItems(GCI item1,GCI item2)
767 return stricmp(((DotNode *)item1)->m_label,((DotNode *)item2)->m_label);
771 //--------------------------------------------------------------------
773 DotRunner::DotRunner(const QCString &file,const QCString &path,
774 bool checkResult,const QCString &imageName)
775 : m_file(file), m_path(path),
776 m_checkResult(checkResult), m_imageName(imageName)
778 static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
779 m_cleanUp = dotCleanUp;
780 m_jobs.setAutoDelete(TRUE);
783 void DotRunner::addJob(const char *format,const char *output)
785 QCString args = QCString("-T")+format+" -o \""+output+"\"";
786 m_jobs.append(new QCString(args));
789 void DotRunner::addPostProcessing(const char *cmd,const char *args)
795 bool DotRunner::run()
798 QCString dotExe = Config_getString("DOT_PATH")+"dot";
799 bool multiTargets = Config_getBool("DOT_MULTI_TARGETS");
801 QListIterator<QCString> li(m_jobs);
803 QCString file = m_file;
804 QCString path = m_path;
805 QCString imageName = m_imageName;
806 QCString postCmd = m_postCmd;
807 QCString postArgs = m_postArgs;
808 bool checkResult = m_checkResult;
809 bool cleanUp = m_cleanUp;
812 dotArgs="\""+file+"\"";
813 for (li.toFirst();(s=li.current());++li)
818 if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
825 for (li.toFirst();(s=li.current());++li)
827 dotArgs="\""+file+"\" "+*s;
828 if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
834 if (!postCmd.isEmpty() && portable_system(postCmd,postArgs)!=0)
836 err("error: Problems running '%s' as a post-processing step for dot output\n",m_postCmd.data());
839 if (checkResult) checkDotResult(imageName);
842 //printf("removing dot file %s\n",m_file.data());
843 //QDir(path).remove(file);
844 m_cleanupItem.file = file;
845 m_cleanupItem.path = path;
849 err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n",
850 exitCode,dotExe.data(),dotArgs.data());
854 //--------------------------------------------------------------------
856 DotFilePatcher::DotFilePatcher(const char *patchFile)
857 : m_patchFile(patchFile)
859 m_maps.setAutoDelete(TRUE);
862 QCString DotFilePatcher::file() const
867 int DotFilePatcher::addMap(const QCString &mapFile,const QCString &relPath,
868 bool urlOnly,const QCString &context,const QCString &label)
870 int id = m_maps.count();
872 map->mapFile = mapFile;
873 map->relPath = relPath;
874 map->urlOnly = urlOnly;
875 map->context = context;
877 map->zoomable = FALSE;
883 int DotFilePatcher::addFigure(const QCString &baseName,
884 const QCString &figureName,bool heightCheck)
886 int id = m_maps.count();
888 map->mapFile = figureName;
889 map->urlOnly = heightCheck;
890 map->label = baseName;
891 map->zoomable = FALSE;
897 int DotFilePatcher::addSVGConversion(const QCString &relPath,bool urlOnly,
898 const QCString &context,bool zoomable,
901 int id = m_maps.count();
903 map->relPath = relPath;
904 map->urlOnly = urlOnly;
905 map->context = context;
906 map->zoomable = zoomable;
907 map->graphId = graphId;
912 int DotFilePatcher::addSVGObject(const QCString &baseName,
913 const QCString &absImgName,
914 const QCString &relPath)
916 int id = m_maps.count();
918 map->mapFile = absImgName;
919 map->relPath = relPath;
920 map->label = baseName;
921 map->zoomable = FALSE;
927 bool DotFilePatcher::run()
929 //printf("DotFilePatcher::run(): %s\n",m_patchFile.data());
930 static bool interactiveSVG = Config_getBool("INTERACTIVE_SVG");
931 bool isSVGFile = m_patchFile.right(4)==".svg";
936 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
937 interactiveSVG = interactiveSVG && map->zoomable;
938 graphId = map->graphId;
939 relPath = map->relPath;
940 //printf("DotFilePatcher::addSVGConversion: file=%s zoomable=%d\n",
941 // m_patchFile.data(),map->zoomable);
943 QCString tmpName = m_patchFile+".tmp";
944 if (!QDir::current().rename(m_patchFile,tmpName))
946 err("Failed to rename file %s to %s!\n",m_patchFile.data(),tmpName.data());
950 QFile fo(m_patchFile);
951 if (!fi.open(IO_ReadOnly))
953 err("error: problem opening file %s for patching!\n",tmpName.data());
954 QDir::current().rename(tmpName,m_patchFile);
957 if (!fo.open(IO_WriteOnly))
959 err("error: problem opening file %s for patching!\n",m_patchFile.data());
960 QDir::current().rename(tmpName,m_patchFile);
964 const int maxLineLen=100*1024;
967 bool insideHeader=FALSE;
968 bool replacedHeader=FALSE;
969 bool foundSize=FALSE;
970 while (!fi.atEnd()) // foreach line
972 QCString line(maxLineLen);
973 int numBytes = fi.readLine(line.data(),maxLineLen);
979 //printf("line=[%s]\n",line.stripWhiteSpace().data());
981 ASSERT(numBytes<maxLineLen);
986 if (line.find("<svg")!=-1 && !replacedHeader)
989 count = sscanf(line.data(),"<svg width=\"%dpt\" height=\"%dpt\"",&width,&height);
990 //printf("width=%d height=%d\n",width,height);
991 foundSize = count==2 && (width>500 || height>450);
992 if (foundSize) insideHeader=TRUE;
994 else if (insideHeader && !replacedHeader && line.find("<title>")!=-1)
998 // insert special replacement header for interactive SVGs
999 t << "<!--zoomable " << height << " -->\n";
1001 t << "var viewWidth = " << width << ";\n";
1002 t << "var viewHeight = " << height << ";\n";
1005 t << "var sectionId = 'dynsection-" << graphId << "';\n";
1008 t << "<script xlink:href=\"" << relPath << "svgpan.js\"/>\n";
1009 t << "<svg id=\"graph\" class=\"graph\">\n";
1010 t << "<g id=\"viewport\">\n";
1013 replacedHeader=TRUE;
1016 if (!insideHeader || !foundSize) // copy SVG and replace refs,
1017 // unless we are inside the header of the SVG.
1018 // Then we replace it with another header.
1020 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
1021 t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
1024 else if ((i=line.find("<!-- SVG"))!=-1 || (i=line.find("[!-- SVG"))!=-1)
1026 //printf("Found marker at %d\n",i);
1029 int n = sscanf(line.data()+i+1,"!-- SVG %d",&mapId);
1030 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1032 int e = QMAX(line.find("--]"),line.find("-->"));
1033 Map *map = m_maps.at(mapId);
1034 //printf("DotFilePatcher::writeSVGFigure: file=%s zoomable=%d\n",
1035 // m_patchFile.data(),map->zoomable);
1036 if (!writeSVGFigureLink(t,map->relPath,map->label,map->mapFile))
1038 err("Problem extracting size from SVG file %s\n",map->mapFile.data());
1040 if (e!=-1) t << line.mid(e+3);
1042 else // error invalid map id!
1044 err("Found invalid SVG id in file %s!\n",m_patchFile.data());
1048 else if ((i=line.find("<!-- MAP"))!=-1)
1052 int n = sscanf(line.data()+i,"<!-- MAP %d",&mapId);
1053 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1055 Map *map = m_maps.at(mapId);
1056 //printf("patching MAP %d in file %s with contents of %s\n",
1057 // mapId,m_patchFile.data(),map->mapFile.data());
1058 t << "<map name=\"" << map->label << "\" id=\"" << map->label << "\">" << endl;
1059 convertMapFile(t,map->mapFile,map->relPath,map->urlOnly,map->context);
1060 t << "</map>" << endl;
1062 else // error invalid map id!
1064 err("Found invalid MAP id in file %s!\n",m_patchFile.data());
1068 else if ((i=line.find("% FIG"))!=-1)
1071 int n = sscanf(line.data()+i+2,"FIG %d",&mapId);
1072 //printf("line='%s' n=%d\n",line.data()+i,n);
1073 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1075 Map *map = m_maps.at(mapId);
1076 //printf("patching FIG %d in file %s with contents of %s\n",
1077 // mapId,m_patchFile.data(),map->mapFile.data());
1078 writeVecGfxFigure(t,map->label,map->mapFile);
1080 else // error invalid map id!
1082 err("Found invalid bounding FIG id in file %s!\n",mapId,m_patchFile.data());
1093 if (isSVGFile && interactiveSVG && replacedHeader)
1095 QCString orgName=m_patchFile.left(m_patchFile.length()-4)+"_org.svg";
1096 t << substitute(svgZoomFooter,"$orgname",stripPath(orgName));
1098 // keep original SVG file so we can refer to it, we do need to replace
1099 // dummy link by real ones
1102 if (!fi.open(IO_ReadOnly))
1104 err("error: problem opening file %s for reading!\n",tmpName.data());
1107 if (!fo.open(IO_WriteOnly))
1109 err("error: problem opening file %s for writing!\n",orgName.data());
1113 while (!fi.atEnd()) // foreach line
1115 QCString line(maxLineLen);
1116 int numBytes = fi.readLine(line.data(),maxLineLen);
1121 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
1122 t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
1127 // remove temporary file
1128 QDir::current().remove(tmpName);
1132 //--------------------------------------------------------------------
1134 void DotRunnerQueue::enqueue(DotRunner *runner)
1136 QMutexLocker locker(&m_mutex);
1137 m_queue.enqueue(runner);
1138 m_bufferNotEmpty.wakeAll();
1141 DotRunner *DotRunnerQueue::dequeue()
1143 QMutexLocker locker(&m_mutex);
1144 while (m_queue.isEmpty())
1146 // wait until something is added to the queue
1147 m_bufferNotEmpty.wait(&m_mutex);
1149 DotRunner *result = m_queue.dequeue();
1153 uint DotRunnerQueue::count() const
1155 QMutexLocker locker(&m_mutex);
1156 return m_queue.count();
1159 //--------------------------------------------------------------------
1161 DotWorkerThread::DotWorkerThread(int id,DotRunnerQueue *queue)
1162 : m_id(id), m_queue(queue)
1164 m_cleanupItems.setAutoDelete(TRUE);
1167 void DotWorkerThread::run()
1170 while ((runner=m_queue->dequeue()))
1173 DotRunner::CleanupItem cleanup = runner->cleanup();
1174 if (!cleanup.file.isEmpty())
1176 m_cleanupItems.append(new DotRunner::CleanupItem(cleanup));
1181 void DotWorkerThread::cleanup()
1183 QListIterator<DotRunner::CleanupItem> it(m_cleanupItems);
1184 DotRunner::CleanupItem *ci;
1185 for (;(ci=it.current());++it)
1187 QDir(ci->path).remove(ci->file);
1191 //--------------------------------------------------------------------
1193 DotManager *DotManager::m_theInstance = 0;
1195 DotManager *DotManager::instance()
1199 m_theInstance = new DotManager;
1201 return m_theInstance;
1204 DotManager::DotManager() : m_dotMaps(1007)
1206 m_dotRuns.setAutoDelete(TRUE);
1207 m_dotMaps.setAutoDelete(TRUE);
1208 m_queue = new DotRunnerQueue;
1210 int numThreads = QMIN(32,Config_getInt("DOT_NUM_THREADS"));
1213 if (numThreads==0) numThreads = QMAX(2,QThread::idealThreadCount()+1);
1214 for (i=0;i<numThreads;i++)
1216 DotWorkerThread *thread = new DotWorkerThread(i,m_queue);
1218 if (thread->isRunning())
1220 m_workers.append(thread);
1222 else // no more threads available!
1227 ASSERT(m_workers.count()>0);
1231 DotManager::~DotManager()
1236 void DotManager::addRun(DotRunner *run)
1238 m_dotRuns.append(run);
1241 int DotManager::addMap(const QCString &file,const QCString &mapFile,
1242 const QCString &relPath,bool urlOnly,const QCString &context,
1243 const QCString &label)
1245 DotFilePatcher *map = m_dotMaps.find(file);
1248 map = new DotFilePatcher(file);
1249 m_dotMaps.append(file,map);
1251 return map->addMap(mapFile,relPath,urlOnly,context,label);
1254 int DotManager::addFigure(const QCString &file,const QCString &baseName,
1255 const QCString &figureName,bool heightCheck)
1257 DotFilePatcher *map = m_dotMaps.find(file);
1260 map = new DotFilePatcher(file);
1261 m_dotMaps.append(file,map);
1263 return map->addFigure(baseName,figureName,heightCheck);
1266 int DotManager::addSVGConversion(const QCString &file,const QCString &relPath,
1267 bool urlOnly,const QCString &context,bool zoomable,
1270 DotFilePatcher *map = m_dotMaps.find(file);
1273 map = new DotFilePatcher(file);
1274 m_dotMaps.append(file,map);
1276 return map->addSVGConversion(relPath,urlOnly,context,zoomable,graphId);
1279 int DotManager::addSVGObject(const QCString &file,const QCString &baseName,
1280 const QCString &absImgName,const QCString &relPath)
1282 DotFilePatcher *map = m_dotMaps.find(file);
1285 map = new DotFilePatcher(file);
1286 m_dotMaps.append(file,map);
1288 return map->addSVGObject(baseName,absImgName,relPath);
1291 bool DotManager::run()
1293 uint numDotRuns = m_dotRuns.count();
1294 uint numDotMaps = m_dotMaps.count();
1295 if (numDotRuns+numDotMaps>1)
1297 if (m_workers.count()==0)
1299 msg("Generating dot graphs in single threaded mode...\n");
1303 msg("Generating dot graphs using %d parallel threads...\n",QMIN(numDotRuns+numDotMaps,m_workers.count()));
1307 QListIterator<DotRunner> li(m_dotRuns);
1310 if (Config_getBool("GENERATE_HTML"))
1312 setDotFontPath(Config_getString("HTML_OUTPUT"));
1315 else if (Config_getBool("GENERATE_LATEX"))
1317 setDotFontPath(Config_getString("LATEX_OUTPUT"));
1320 else if (Config_getBool("GENERATE_RTF"))
1322 setDotFontPath(Config_getString("RTF_OUTPUT"));
1325 portable_sysTimerStart();
1326 // fill work queue with dot operations
1329 if (m_workers.count()==0) // no threads to work with
1331 for (li.toFirst();(dr=li.current());++li)
1333 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1338 else // use multiple threads to run instances of dot in parallel
1340 for (li.toFirst();(dr=li.current());++li)
1342 m_queue->enqueue(dr);
1344 // wait for the queue to become empty
1345 while ((i=m_queue->count())>0)
1350 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1353 portable_sleep(100);
1355 while ((int)numDotRuns>=prev)
1357 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1360 // signal the workers we are done
1361 for (i=0;i<(int)m_workers.count();i++)
1363 m_queue->enqueue(0); // add terminator for each worker
1365 // wait for the workers to finish
1366 for (i=0;i<(int)m_workers.count();i++)
1368 m_workers.at(i)->wait();
1370 // clean up dot files from main thread
1371 for (i=0;i<(int)m_workers.count();i++)
1373 m_workers.at(i)->cleanup();
1376 portable_sysTimerStop();
1382 // patch the output file and insert the maps and figures
1384 SDict<DotFilePatcher>::Iterator di(m_dotMaps);
1385 DotFilePatcher *map;
1386 // since patching the svg files may involve patching the header of the SVG
1387 // (for zoomable SVGs), and patching the .html files requires reading that
1388 // header after the SVG is patched, we first process the .svg files and
1389 // then the other files.
1390 for (di.toFirst();(map=di.current());++di)
1392 if (map->file().right(4)==".svg")
1394 msg("Patching output file %d/%d\n",i,numDotMaps);
1395 if (!map->run()) return FALSE;
1399 for (di.toFirst();(map=di.current());++di)
1401 if (map->file().right(4)!=".svg")
1403 msg("Patching output file %d/%d\n",i,numDotMaps);
1404 if (!map->run()) return FALSE;
1412 //--------------------------------------------------------------------
1415 /*! helper function that deletes all nodes in a connected graph, given
1416 * one of the graph's nodes
1418 static void deleteNodes(DotNode *node,SDict<DotNode> *skipNodes=0)
1420 //printf("deleteNodes skipNodes=%p\n",skipNodes);
1421 static DotNodeList deletedNodes;
1422 deletedNodes.setAutoDelete(TRUE);
1423 node->deleteNode(deletedNodes,skipNodes); // collect nodes to be deleted.
1424 deletedNodes.clear(); // actually remove the nodes.
1427 DotNode::DotNode(int n,const char *lab,const char *tip, const char *url,
1428 bool isRoot,ClassDef *cd)
1443 , m_truncated(Unknown)
1455 void DotNode::addChild(DotNode *n,
1458 const char *edgeLab,
1459 const char *edgeURL,
1465 m_children = new QList<DotNode>;
1466 m_edgeInfo = new QList<EdgeInfo>;
1467 m_edgeInfo->setAutoDelete(TRUE);
1469 m_children->append(n);
1470 EdgeInfo *ei = new EdgeInfo;
1471 ei->m_color = edgeColor;
1472 ei->m_style = edgeStyle;
1473 ei->m_label = edgeLab;
1474 ei->m_url = edgeURL;
1476 ei->m_labColor=edgeColor;
1478 ei->m_labColor=edgeLabCol;
1479 m_edgeInfo->append(ei);
1482 void DotNode::addParent(DotNode *n)
1486 m_parents = new QList<DotNode>;
1488 m_parents->append(n);
1491 void DotNode::removeChild(DotNode *n)
1493 if (m_children) m_children->remove(n);
1496 void DotNode::removeParent(DotNode *n)
1498 if (m_parents) m_parents->remove(n);
1501 void DotNode::deleteNode(DotNodeList &deletedList,SDict<DotNode> *skipNodes)
1503 if (m_deleted) return; // avoid recursive loops in case the graph has cycles
1505 if (m_parents!=0) // delete all parent nodes of this node
1507 QListIterator<DotNode> dnlip(*m_parents);
1509 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
1511 //pn->removeChild(this);
1512 pn->deleteNode(deletedList,skipNodes);
1515 if (m_children!=0) // delete all child nodes of this node
1517 QListIterator<DotNode> dnlic(*m_children);
1519 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
1521 //cn->removeParent(this);
1522 cn->deleteNode(deletedList,skipNodes);
1525 // add this node to the list of deleted nodes.
1526 //printf("skipNodes=%p find(%p)=%p\n",skipNodes,this,skipNodes ? skipNodes->find((int)this) : 0);
1527 if (skipNodes==0 || skipNodes->find((char*)this)==0)
1529 //printf("deleting\n");
1530 deletedList.append(this);
1534 void DotNode::setDistance(int distance)
1536 if (distance<m_distance) m_distance = distance;
1539 static QCString convertLabel(const QCString &l)
1542 QCString bBefore("\\_/<({[: =-+@%#~?$"); // break before character set
1543 QCString bAfter(">]),:;|"); // break after character set
1544 const char *p=l.data();
1545 if (p==0) return result;
1552 int foldLen=17; // ideal text length
1555 QCString replacement;
1558 case '\\': replacement="\\\\"; break;
1559 case '\n': replacement="\\n"; break;
1560 case '<': replacement="\\<"; break;
1561 case '>': replacement="\\>"; break;
1562 case '|': replacement="\\|"; break;
1563 case '{': replacement="\\{"; break;
1564 case '}': replacement="\\}"; break;
1565 case '"': replacement="\\\""; break;
1566 default: cs[0]=c; replacement=cs; break;
1568 // Some heuristics to insert newlines to prevent too long
1569 // boxes and at the same time prevent ugly breaks
1572 result+=replacement;
1573 foldLen = (3*foldLen+sinceLast+2)/4;
1576 else if ((pc!=':' || c!=':') && charsLeft>foldLen/3 && sinceLast>foldLen && bBefore.contains(c))
1579 result+=replacement;
1580 foldLen = (foldLen+sinceLast+1)/2;
1583 else if (charsLeft>1+foldLen/4 && sinceLast>foldLen+foldLen/3 &&
1584 !isupper(c) && isupper(*p))
1586 result+=replacement;
1588 foldLen = (foldLen+sinceLast+1)/2;
1591 else if (charsLeft>foldLen/3 && sinceLast>foldLen && bAfter.contains(c) && (c!=':' || *p!=':'))
1593 result+=replacement;
1595 foldLen = (foldLen+sinceLast+1)/2;
1600 result+=replacement;
1609 static QCString escapeTooltip(const QCString &tooltip)
1612 const char *p=tooltip.data();
1613 if (p==0) return result;
1619 case '"': result+="\\\""; break;
1620 default: result+=c; break;
1626 static void writeBoxMemberList(FTextStream &t,
1627 char prot,MemberList *ml,ClassDef *scope,
1628 bool isStatic=FALSE,const QDict<void> *skipNames=0)
1633 MemberListIterator mlia(*ml);
1636 for (mlia.toFirst();(mma = mlia.current());++mlia)
1638 if (mma->getClassDef()==scope &&
1639 (skipNames==0 || skipNames->find(mma->name())==0))
1646 for (mlia.toFirst();(mma = mlia.current());++mlia)
1648 if (mma->getClassDef() == scope &&
1649 (skipNames==0 || skipNames->find(mma->name())==0))
1651 static int limit = Config_getInt("UML_LIMIT_NUM_FIELDS");
1652 if (limit>0 && (totalCount>limit*3/2 && count>=limit))
1654 t << theTranslator->trAndMore(QCString().sprintf("%d",totalCount-count)) << "\\l";
1660 t << convertLabel(mma->name());
1661 if (!mma->isObjCMethod() &&
1662 (mma->isFunction() || mma->isSlot() || mma->isSignal())) t << "()";
1668 // write member groups within the memberlist
1669 MemberGroupList *mgl = ml->getMemberGroupList();
1672 MemberGroupListIterator mgli(*mgl);
1674 for (mgli.toFirst();(mg=mgli.current());++mgli)
1678 writeBoxMemberList(t,prot,mg->members(),scope,isStatic,skipNames);
1685 void DotNode::writeBox(FTextStream &t,
1687 GraphOutputFormat /*format*/,
1688 bool hasNonReachableChildren,
1691 const char *labCol =
1692 m_url.isEmpty() ? "grey75" : // non link
1694 (hasNonReachableChildren) ? "red" : "black"
1696 t << " Node" << reNumberNode(m_number,reNumber) << " [label=\"";
1697 static bool umlLook = Config_getBool("UML_LOOK");
1699 if (m_classDef && umlLook && (gt==Inheritance || gt==Collaboration))
1701 // add names shown as relation to a dictionary, so we don't show
1702 // them as attributes as well
1703 QDict<void> arrowNames(17);
1706 QListIterator<EdgeInfo> li(*m_edgeInfo);
1708 for (li.toFirst();(ei=li.current());++li)
1710 if (!ei->m_label.isEmpty())
1712 arrowNames.insert(ei->m_label,(void*)0x8);
1717 //printf("DotNode::writeBox for %s\n",m_classDef->name().data());
1718 static bool extractPrivate = Config_getBool("EXTRACT_PRIVATE");
1719 t << "{" << convertLabel(m_label);
1721 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubAttribs),m_classDef,FALSE,&arrowNames);
1722 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticAttribs),m_classDef,TRUE,&arrowNames);
1723 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::properties),m_classDef,FALSE,&arrowNames);
1724 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacAttribs),m_classDef,FALSE,&arrowNames);
1725 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticAttribs),m_classDef,TRUE,&arrowNames);
1726 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proAttribs),m_classDef,FALSE,&arrowNames);
1727 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticAttribs),m_classDef,TRUE,&arrowNames);
1730 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priAttribs),m_classDef,FALSE,&arrowNames);
1731 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticAttribs),m_classDef,TRUE,&arrowNames);
1734 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubMethods),m_classDef);
1735 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticMethods),m_classDef,TRUE);
1736 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubSlots),m_classDef);
1737 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacMethods),m_classDef);
1738 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticMethods),m_classDef,TRUE);
1739 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proMethods),m_classDef);
1740 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticMethods),m_classDef,TRUE);
1741 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proSlots),m_classDef);
1744 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priMethods),m_classDef);
1745 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticMethods),m_classDef,TRUE);
1746 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priSlots),m_classDef);
1748 if (m_classDef->getLanguage()!=SrcLangExt_Fortran &&
1749 m_classDef->getMemberGroupSDict())
1751 MemberGroupSDict::Iterator mgdi(*m_classDef->getMemberGroupSDict());
1753 for (mgdi.toFirst();(mg=mgdi.current());++mgdi)
1757 writeBoxMemberList(t,'*',mg->members(),m_classDef,FALSE,&arrowNames);
1763 else // standard look
1765 t << convertLabel(m_label);
1767 t << "\",height=0.2,width=0.4";
1770 t << ",color=\"black\", fillcolor=\"grey75\", style=\"filled\" fontcolor=\"black\"";
1774 static bool dotTransparent = Config_getBool("DOT_TRANSPARENT");
1775 static bool vhdlOpt = Config_getBool("OPTIMIZE_OUTPUT_VHDL");
1776 if (!dotTransparent)
1778 ClassDef* ccd=m_classDef;
1780 t << ",color=\"" << labCol << "\", fillcolor=\"";
1781 if (ccd && vhdlOpt && (VhdlDocGen::VhdlClasses)ccd->protection()==VhdlDocGen::ARCHITECTURECLASS)
1785 t << "\", style=\"filled\"";
1789 t << ",color=\"" << labCol << "\"";
1791 if (!m_url.isEmpty())
1793 int anchorPos = m_url.findRev('#');
1796 t << ",URL=\"" << m_url << Doxygen::htmlFileExtension << "\"";
1800 t << ",URL=\"" << m_url.left(anchorPos) << Doxygen::htmlFileExtension
1801 << m_url.right(m_url.length()-anchorPos) << "\"";
1804 if (!m_tooltip.isEmpty())
1806 t << ",tooltip=\"" << escapeTooltip(m_tooltip) << "\"";
1812 void DotNode::writeArrow(FTextStream &t,
1814 GraphOutputFormat format,
1824 t << reNumberNode(cn->number(),reNumber);
1826 t << reNumberNode(m_number,reNumber);
1829 t << reNumberNode(m_number,reNumber);
1831 t << reNumberNode(cn->number(),reNumber);
1834 static bool umlLook = Config_getBool("UML_LOOK");
1835 const EdgeProperties *eProps = umlLook ? ¨EdgeProps : &normalEdgeProps;
1836 QCString aStyle = eProps->arrowStyleMap[ei->m_color];
1837 bool umlUseArrow = aStyle=="odiamond";
1839 if (pointBack && !umlUseArrow) t << "dir=\"back\",";
1840 t << "color=\"" << eProps->edgeColorMap[ei->m_color]
1841 << "\",fontsize=\"" << FONTSIZE << "\",";
1842 t << "style=\"" << eProps->edgeStyleMap[ei->m_style] << "\"";
1843 if (!ei->m_label.isEmpty())
1845 t << ",label=\" " << convertLabel(ei->m_label) << "\" ";
1848 eProps->arrowStyleMap[ei->m_color] &&
1849 (gt==Inheritance || gt==Collaboration)
1852 bool rev = pointBack;
1853 if (umlUseArrow) rev=!rev; // UML use relates has arrow on the start side
1855 t << ",arrowtail=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
1857 t << ",arrowhead=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
1860 if (format==BITMAP) t << ",fontname=\"" << FONTNAME << "\"";
1864 void DotNode::write(FTextStream &t,
1866 GraphOutputFormat format,
1873 //printf("DotNode::write(%d) name=%s this=%p written=%d\n",distance,m_label.data(),this,m_written);
1874 if (m_written) return; // node already written to the output
1875 if (!m_visible) return; // node is not visible
1876 writeBox(t,gt,format,m_truncated==Truncated,reNumber);
1878 QList<DotNode> *nl = toChildren ? m_children : m_parents;
1883 QListIterator<DotNode> dnli1(*nl);
1884 QListIterator<EdgeInfo> dnli2(*m_edgeInfo);
1886 for (dnli1.toFirst();(cn=dnli1.current());++dnli1,++dnli2)
1888 if (cn->isVisible())
1890 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",cn->label().data());
1891 writeArrow(t,gt,format,cn,dnli2.current(),topDown,backArrows,reNumber);
1893 cn->write(t,gt,format,topDown,toChildren,backArrows,reNumber);
1896 else // render parents
1898 QListIterator<DotNode> dnli(*nl);
1900 for (dnli.toFirst();(pn=dnli.current());++dnli)
1902 if (pn->isVisible())
1904 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",pn->label().data());
1909 pn->m_edgeInfo->at(pn->m_children->findRef(this)),
1915 pn->write(t,gt,format,TRUE,FALSE,backArrows,reNumber);
1919 //printf("end DotNode::write(%d) name=%s\n",distance,m_label.data());
1922 void DotNode::writeXML(FTextStream &t,bool isClassGraph)
1924 t << " <node id=\"" << m_number << "\">" << endl;
1925 t << " <label>" << convertToXML(m_label) << "</label>" << endl;
1926 if (!m_url.isEmpty())
1928 QCString url(m_url);
1929 char *refPtr = url.data();
1930 char *urlPtr = strchr(url.data(),'$');
1934 t << " <link refid=\"" << convertToXML(urlPtr) << "\"";
1937 t << " external=\"" << convertToXML(refPtr) << "\"";
1944 QListIterator<DotNode> nli(*m_children);
1945 QListIterator<EdgeInfo> eli(*m_edgeInfo);
1948 for (;(childNode=nli.current());++nli,++eli)
1950 edgeInfo=eli.current();
1951 t << " <childnode refid=\"" << childNode->m_number << "\" relation=\"";
1954 switch(edgeInfo->m_color)
1956 case EdgeInfo::Blue: t << "public-inheritance"; break;
1957 case EdgeInfo::Green: t << "protected-inheritance"; break;
1958 case EdgeInfo::Red: t << "private-inheritance"; break;
1959 case EdgeInfo::Purple: t << "usage"; break;
1960 case EdgeInfo::Orange: t << "template-instance"; break;
1961 case EdgeInfo::Grey: ASSERT(0); break;
1964 else // include graph
1969 if (!edgeInfo->m_label.isEmpty())
1973 while ((ni=edgeInfo->m_label.find('\n',p))!=-1)
1976 << convertToXML(edgeInfo->m_label.mid(p,ni-p))
1977 << "</edgelabel>" << endl;
1981 << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p))
1982 << "</edgelabel>" << endl;
1984 t << " </childnode>" << endl;
1987 t << " </node>" << endl;
1991 void DotNode::writeDEF(FTextStream &t)
1993 const char* nodePrefix = " node-";
1995 t << " node = {" << endl;
1996 t << nodePrefix << "id = " << m_number << ';' << endl;
1997 t << nodePrefix << "label = '" << m_label << "';" << endl;
1999 if (!m_url.isEmpty())
2001 QCString url(m_url);
2002 char *refPtr = url.data();
2003 char *urlPtr = strchr(url.data(),'$');
2007 t << nodePrefix << "link = {" << endl << " "
2008 << nodePrefix << "link-id = '" << urlPtr << "';" << endl;
2012 t << " " << nodePrefix << "link-external = '"
2013 << refPtr << "';" << endl;
2020 QListIterator<DotNode> nli(*m_children);
2021 QListIterator<EdgeInfo> eli(*m_edgeInfo);
2024 for (;(childNode=nli.current());++nli,++eli)
2026 edgeInfo=eli.current();
2027 t << " node-child = {" << endl;
2028 t << " child-id = '" << childNode->m_number << "';" << endl;
2029 t << " relation = ";
2031 switch(edgeInfo->m_color)
2033 case EdgeInfo::Blue: t << "public-inheritance"; break;
2034 case EdgeInfo::Green: t << "protected-inheritance"; break;
2035 case EdgeInfo::Red: t << "private-inheritance"; break;
2036 case EdgeInfo::Purple: t << "usage"; break;
2037 case EdgeInfo::Orange: t << "template-instance"; break;
2038 case EdgeInfo::Grey: ASSERT(0); break;
2042 if (!edgeInfo->m_label.isEmpty())
2044 t << " edgelabel = <<_EnD_oF_dEf_TeXt_" << endl
2045 << edgeInfo->m_label << endl
2046 << "_EnD_oF_dEf_TeXt_;" << endl;
2048 t << " }; /* node-child */" << endl;
2049 } /* for (;childNode...) */
2051 t << " }; /* node */" << endl;
2055 void DotNode::clearWriteFlag()
2060 QListIterator<DotNode> dnlip(*m_parents);
2062 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
2066 pn->clearWriteFlag();
2072 QListIterator<DotNode> dnlic(*m_children);
2074 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2078 cn->clearWriteFlag();
2084 void DotNode::colorConnectedNodes(int curColor)
2088 QListIterator<DotNode> dnlic(*m_children);
2090 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2092 if (cn->m_subgraphId==-1) // uncolored child node
2094 cn->m_subgraphId=curColor;
2095 cn->markAsVisible();
2096 cn->colorConnectedNodes(curColor);
2097 //printf("coloring node %s (%p): %d\n",cn->m_label.data(),cn,cn->m_subgraphId);
2104 QListIterator<DotNode> dnlip(*m_parents);
2106 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
2108 if (pn->m_subgraphId==-1) // uncolored parent node
2110 pn->m_subgraphId=curColor;
2111 pn->markAsVisible();
2112 pn->colorConnectedNodes(curColor);
2113 //printf("coloring node %s (%p): %d\n",pn->m_label.data(),pn,pn->m_subgraphId);
2119 const DotNode *DotNode::findDocNode() const
2121 if (!m_url.isEmpty()) return this;
2122 //printf("findDocNode(): `%s'\n",m_label.data());
2125 QListIterator<DotNode> dnli(*m_parents);
2127 for (dnli.toFirst();(pn=dnli.current());++dnli)
2132 const DotNode *dn = pn->findDocNode();
2139 QListIterator<DotNode> dnli(*m_children);
2141 for (dnli.toFirst();(cn=dnli.current());++dnli)
2146 const DotNode *dn = cn->findDocNode();
2154 //--------------------------------------------------------------------
2156 int DotGfxHierarchyTable::m_curNodeNumber;
2158 void DotGfxHierarchyTable::writeGraph(FTextStream &out,
2159 const char *path,const char *fileName) const
2161 //printf("DotGfxHierarchyTable::writeGraph(%s)\n",name);
2162 //printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count());
2164 static bool vhdl = Config_getBool("OPTIMIZE_OUTPUT_VHDL");
2166 if (m_rootSubgraphs->count()==0) return;
2169 // store the original directory
2172 err("error: Output dir %s does not exist!\n",path); exit(1);
2175 // put each connected subgraph of the hierarchy in a row of the HTML output
2176 out << "<table border=\"0\" cellspacing=\"10\" cellpadding=\"0\">" << endl;
2178 QListIterator<DotNode> dnli(*m_rootSubgraphs);
2181 for (dnli.toFirst();(n=dnli.current());++dnli)
2187 QCString l=n->m_url;
2188 l=VhdlDocGen::convertFileNameToClassName(l);
2189 ClassDef *cd=Doxygen::classSDict->find(l);
2190 if (cd==0) continue;
2191 // only entities are shown
2192 if ((VhdlDocGen::VhdlClasses)cd->protection()!=VhdlDocGen::ENTITYCLASS)
2196 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
2197 baseName.sprintf("inherit_graph_%d",count++);
2198 //baseName = convertNameToFile(baseName);
2199 QCString imgName = baseName+"."+ imgExt;
2200 QCString mapName = baseName+".map";
2201 QCString absImgName = QCString(d.absPath().data())+"/"+imgName;
2202 QCString absMapName = QCString(d.absPath().data())+"/"+mapName;
2203 QCString absBaseName = QCString(d.absPath().data())+"/"+baseName;
2204 QListIterator<DotNode> dnli2(*m_rootNodes);
2207 // compute md5 checksum of the graph were are about to generate
2209 FTextStream md5stream(&theGraph);
2210 writeGraphHeader(md5stream,theTranslator->trGraphicalHierarchy());
2211 md5stream << " rankdir=\"LR\";" << endl;
2212 for (dnli2.toFirst();(node=dnli2.current());++dnli2)
2214 if (node->m_subgraphId==n->m_subgraphId)
2216 node->clearWriteFlag();
2219 for (dnli2.toFirst();(node=dnli2.current());++dnli2)
2221 if (node->m_subgraphId==n->m_subgraphId)
2223 node->write(md5stream,DotNode::Hierarchy,BITMAP,FALSE,TRUE,TRUE,TRUE);
2226 writeGraphFooter(md5stream);
2229 QCString sigStr(33);
2230 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
2231 MD5SigToString(md5_sig,sigStr.data(),33);
2232 bool regenerate=FALSE;
2233 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
2234 !checkDeliverables(absImgName,absMapName))
2237 // image was new or has changed
2238 QCString dotName=absBaseName+".dot";
2240 if (!f.open(IO_WriteOnly)) return;
2246 DotRunner *dotRun = new DotRunner(dotName,d.absPath().data(),TRUE,absImgName);
2247 dotRun->addJob(imgExt,absImgName);
2248 dotRun->addJob(MAP_CMD,absMapName);
2249 DotManager::instance()->addRun(dotRun);
2253 removeDotGraph(absBaseName+".dot");
2255 Doxygen::indexList.addImageFile(imgName);
2256 // write image and map in a table row
2257 QCString mapLabel = escapeCharsInString(n->m_label,FALSE);
2259 if (imgExt=="svg") // vector graphics
2261 if (regenerate || !writeSVGFigureLink(out,QCString(),baseName,absImgName))
2265 DotManager::instance()->addSVGConversion(absImgName,QCString(),
2266 FALSE,QCString(),FALSE,0);
2268 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,
2269 absImgName,QCString());
2270 out << "<!-- SVG " << mapId << " -->" << endl;
2273 else // normal bitmap
2275 out << "<img src=\"" << imgName << "\" border=\"0\" alt=\"\" usemap=\"#"
2276 << mapLabel << "\"/>" << endl;
2278 if (regenerate || !insertMapFile(out,absMapName,QCString(),mapLabel))
2280 int mapId = DotManager::instance()->addMap(fileName,absMapName,QCString(),
2281 FALSE,QCString(),mapLabel);
2282 out << "<!-- MAP " << mapId << " -->" << endl;
2286 out << "</td></tr>" << endl;
2288 out << "</table>" << endl;
2291 void DotGfxHierarchyTable::addHierarchy(DotNode *n,ClassDef *cd,bool hideSuper)
2293 //printf("addHierarchy `%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count());
2294 if (cd->subClasses())
2296 BaseClassListIterator bcli(*cd->subClasses());
2298 for ( ; (bcd=bcli.current()) ; ++bcli )
2300 ClassDef *bClass=bcd->classDef;
2301 //printf(" Trying sub class=`%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count());
2302 if (bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses()))
2305 //printf(" Node `%s' Found visible class=`%s'\n",n->m_label.data(),
2306 // bClass->name().data());
2307 if ((bn=m_usedNodes->find(bClass->name()))) // node already present
2309 if (n->m_children==0 || n->m_children->findRef(bn)==-1) // no arrow yet
2311 n->addChild(bn,bcd->prot);
2313 //printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n",
2314 // n->m_label.data(),
2315 // bn->m_label.data(),
2316 // bn->m_children ? bn->m_children->count() : 0,
2317 // bn->m_parents ? bn->m_parents->count() : 0
2322 // printf(" Class already has an arrow!\n");
2327 QCString tmp_url="";
2328 if (bClass->isLinkable() && !bClass->isHidden())
2330 tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase();
2331 if (!bClass->anchor().isEmpty())
2333 tmp_url+="#"+bClass->anchor();
2336 QCString tooltip = bClass->briefDescriptionAsTooltip();
2337 bn = new DotNode(m_curNodeNumber++,
2338 bClass->displayName(),
2342 n->addChild(bn,bcd->prot);
2344 //printf(" Adding node %s to new base node %s (c=%d,p=%d)\n",
2345 // n->m_label.data(),
2346 // bn->m_label.data(),
2347 // bn->m_children ? bn->m_children->count() : 0,
2348 // bn->m_parents ? bn->m_parents->count() : 0
2350 //printf(" inserting %s (%p)\n",bClass->name().data(),bn);
2351 m_usedNodes->insert(bClass->name(),bn); // add node to the used list
2353 if (!bClass->visited && !hideSuper && bClass->subClasses())
2355 bool wasVisited=bClass->visited;
2356 bClass->visited=TRUE;
2357 addHierarchy(bn,bClass,wasVisited);
2362 //printf("end addHierarchy\n");
2365 void DotGfxHierarchyTable::addClassList(ClassSDict *cl)
2367 ClassSDict::Iterator cli(*cl);
2369 for (cli.toLast();(cd=cli.current());--cli)
2371 //printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count());
2372 if (!hasVisibleRoot(cd->baseClasses()) &&
2373 cd->isVisibleInHierarchy()
2374 ) // root node in the forest
2376 QCString tmp_url="";
2377 if (cd->isLinkable() && !cd->isHidden())
2379 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2380 if (!cd->anchor().isEmpty())
2382 tmp_url+="#"+cd->anchor();
2385 //printf("Inserting root class %s\n",cd->name().data());
2386 QCString tooltip = cd->briefDescriptionAsTooltip();
2387 DotNode *n = new DotNode(m_curNodeNumber++,
2392 //m_usedNodes->clear();
2393 m_usedNodes->insert(cd->name(),n);
2394 m_rootNodes->insert(0,n);
2395 if (!cd->visited && cd->subClasses())
2397 addHierarchy(n,cd,cd->visited);
2404 DotGfxHierarchyTable::DotGfxHierarchyTable()
2407 m_rootNodes = new QList<DotNode>;
2408 m_usedNodes = new QDict<DotNode>(1009);
2409 m_usedNodes->setAutoDelete(TRUE);
2410 m_rootSubgraphs = new DotNodeList;
2412 // build a graph with each class as a node and the inheritance relations
2414 initClassHierarchy(Doxygen::classSDict);
2415 initClassHierarchy(Doxygen::hiddenClasses);
2416 addClassList(Doxygen::classSDict);
2417 addClassList(Doxygen::hiddenClasses);
2418 // m_usedNodes now contains all nodes in the graph
2420 // color the graph into a set of independent subgraphs
2423 QListIterator<DotNode> dnli(*m_rootNodes);
2424 while (!done) // there are still nodes to color
2427 done=TRUE; // we are done unless there are still uncolored nodes
2428 for (dnli.toLast();(n=dnli.current());--dnli)
2430 if (n->m_subgraphId==-1) // not yet colored
2432 //printf("Starting at node %s (%p): %d\n",n->m_label.data(),n,curColor);
2433 done=FALSE; // still uncolored nodes
2434 n->m_subgraphId=curColor;
2436 n->colorConnectedNodes(curColor);
2438 const DotNode *dn=n->findDocNode();
2440 m_rootSubgraphs->inSort(dn);
2442 m_rootSubgraphs->inSort(n);
2447 //printf("Number of independent subgraphs: %d\n",curColor);
2448 //QListIterator<DotNode> dnli2(*m_rootSubgraphs);
2450 //for (dnli2.toFirst();(n=dnli2.current());++dnli2)
2452 // printf("Node %s color=%d (c=%d,p=%d)\n",
2453 // n->m_label.data(),n->m_subgraphId,
2454 // n->m_children?n->m_children->count():0,
2455 // n->m_parents?n->m_parents->count():0);
2459 DotGfxHierarchyTable::~DotGfxHierarchyTable()
2461 //printf("DotGfxHierarchyTable::~DotGfxHierarchyTable\n");
2463 //QDictIterator<DotNode> di(*m_usedNodes);
2465 //for (;(n=di.current());++di)
2467 // printf("Node %p: %s\n",n,n->label().data());
2472 delete m_rootSubgraphs;
2475 //--------------------------------------------------------------------
2477 int DotClassGraph::m_curNodeNumber = 0;
2479 void DotClassGraph::addClass(ClassDef *cd,DotNode *n,int prot,
2480 const char *label,const char *usedName,const char *templSpec,bool base,int distance)
2482 if (Config_getBool("HIDE_UNDOC_CLASSES") && !cd->isLinkable()) return;
2484 int edgeStyle = (label || prot==EdgeInfo::Orange) ? EdgeInfo::Dashed : EdgeInfo::Solid;
2486 if (usedName) // name is a typedef
2490 else if (templSpec) // name has a template part
2492 className=insertTemplateSpecifierInScope(cd->name(),templSpec);
2494 else // just a normal name
2496 className=cd->displayName();
2498 //printf("DotClassGraph::addClass(class=`%s',parent=%s,prot=%d,label=%s,dist=%d,usedName=%s,templSpec=%s,base=%d)\n",
2499 // className.data(),n->m_label.data(),prot,label,distance,usedName,templSpec,base);
2500 DotNode *bn = m_usedNodes->find(className);
2501 if (bn) // class already inserted
2505 n->addChild(bn,prot,edgeStyle,label);
2510 bn->addChild(n,prot,edgeStyle,label);
2513 bn->setDistance(distance);
2514 //printf(" add exiting node %s of %s\n",bn->m_label.data(),n->m_label.data());
2518 QCString displayName=className;
2519 if (Config_getBool("HIDE_SCOPE_NAMES")) displayName=stripScope(displayName);
2521 if (cd->isLinkable() && !cd->isHidden())
2523 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2524 if (!cd->anchor().isEmpty())
2526 tmp_url+="#"+cd->anchor();
2529 QCString tooltip = cd->briefDescriptionAsTooltip();
2530 bn = new DotNode(m_curNodeNumber++,
2539 n->addChild(bn,prot,edgeStyle,label);
2544 bn->addChild(n,prot,edgeStyle,label);
2547 bn->setDistance(distance);
2548 m_usedNodes->insert(className,bn);
2549 //printf(" add new child node `%s' to %s hidden=%d url=%s\n",
2550 // className.data(),n->m_label.data(),cd->isHidden(),tmp_url.data());
2552 buildGraph(cd,bn,base,distance+1);
2556 void DotClassGraph::determineTruncatedNodes(QList<DotNode> &queue,bool includeParents)
2558 while (queue.count()>0)
2560 DotNode *n = queue.take(0);
2561 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
2563 bool truncated = FALSE;
2566 QListIterator<DotNode> li(*n->m_children);
2568 for (li.toFirst();(dn=li.current());++li)
2570 if (!dn->isVisible())
2576 if (n->m_parents && includeParents)
2578 QListIterator<DotNode> li(*n->m_parents);
2580 for (li.toFirst();(dn=li.current());++li)
2582 if (!dn->isVisible())
2588 n->markAsTruncated(truncated);
2593 bool DotClassGraph::determineVisibleNodes(DotNode *rootNode,
2594 int maxNodes,bool includeParents)
2596 QList<DotNode> childQueue;
2597 QList<DotNode> parentQueue;
2598 QArray<int> childTreeWidth;
2599 QArray<int> parentTreeWidth;
2600 childQueue.append(rootNode);
2601 if (includeParents) parentQueue.append(rootNode);
2602 bool firstNode=TRUE; // flag to force reprocessing rootNode in the parent loop
2603 // despite being marked visible in the child loop
2604 while ((childQueue.count()>0 || parentQueue.count()>0) && maxNodes>0)
2606 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
2607 if (childQueue.count()>0)
2609 DotNode *n = childQueue.take(0);
2610 int distance = n->distance();
2611 if (!n->isVisible() && distance<maxDistance) // not yet processed
2615 int oldSize=(int)childTreeWidth.size();
2616 if (distance>oldSize)
2618 childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance));
2619 int i; for (i=oldSize;i<distance;i++) childTreeWidth[i]=0;
2621 childTreeWidth[distance-1]+=n->label().length();
2625 // add direct children
2628 QListIterator<DotNode> li(*n->m_children);
2630 for (li.toFirst();(dn=li.current());++li)
2632 childQueue.append(dn);
2637 if (includeParents && parentQueue.count()>0)
2639 DotNode *n = parentQueue.take(0);
2640 if ((!n->isVisible() || firstNode) && n->distance()<maxDistance) // not yet processed
2643 int distance = n->distance();
2646 int oldSize = (int)parentTreeWidth.size();
2647 if (distance>oldSize)
2649 parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance));
2650 int i; for (i=oldSize;i<distance;i++) parentTreeWidth[i]=0;
2652 parentTreeWidth[distance-1]+=n->label().length();
2656 // add direct parents
2659 QListIterator<DotNode> li(*n->m_parents);
2661 for (li.toFirst();(dn=li.current());++li)
2663 parentQueue.append(dn);
2669 if (Config_getBool("UML_LOOK")) return FALSE; // UML graph are always top to bottom
2671 int maxHeight=(int)QMAX(childTreeWidth.size(),parentTreeWidth.size());
2673 for (i=0;i<childTreeWidth.size();i++)
2675 if (childTreeWidth.at(i)>maxWidth) maxWidth=childTreeWidth.at(i);
2677 for (i=0;i<parentTreeWidth.size();i++)
2679 if (parentTreeWidth.at(i)>maxWidth) maxWidth=parentTreeWidth.at(i);
2681 //printf("max tree width=%d, max tree height=%d\n",maxWidth,maxHeight);
2682 return maxWidth>80 && maxHeight<12; // used metric to decide to render the tree
2683 // from left to right instead of top to bottom,
2684 // with the idea to render very wide trees in
2685 // left to right order.
2688 void DotClassGraph::buildGraph(ClassDef *cd,DotNode *n,bool base,int distance)
2690 //printf("DocClassGraph::buildGraph(%s,distance=%d,base=%d)\n",
2691 // cd->name().data(),distance,base);
2692 // ---- Add inheritance relations
2694 if (m_graphType == DotNode::Inheritance || m_graphType==DotNode::Collaboration)
2696 BaseClassList *bcl = base ? cd->baseClasses() : cd->subClasses();
2699 BaseClassListIterator bcli(*bcl);
2701 for ( ; (bcd=bcli.current()) ; ++bcli )
2703 //printf("-------- inheritance relation %s->%s templ=`%s'\n",
2704 // cd->name().data(),bcd->classDef->name().data(),bcd->templSpecifiers.data());
2705 addClass(bcd->classDef,n,bcd->prot,0,bcd->usedName,
2706 bcd->templSpecifiers,base,distance);
2710 if (m_graphType == DotNode::Collaboration)
2712 // ---- Add usage relations
2714 UsesClassDict *dict =
2715 base ? cd->usedImplementationClasses() :
2716 cd->usedByImplementationClasses()
2720 UsesClassDictIterator ucdi(*dict);
2722 for (;(ucd=ucdi.current());++ucdi)
2725 QDictIterator<void> dvi(*ucd->accessors);
2730 for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count)
2739 label+=QCString("\n")+s;
2742 if (count==maxLabels) label+="\n...";
2743 //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data());
2744 addClass(ucd->classDef,n,EdgeInfo::Purple,label,0,
2745 ucd->templSpecifiers,base,distance);
2750 // ---- Add template instantiation relations
2752 static bool templateRelations = Config_getBool("TEMPLATE_RELATIONS");
2753 if (templateRelations)
2755 if (base) // template relations for base classes
2757 ClassDef *templMaster=cd->templateMaster();
2760 QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances());
2761 ClassDef *templInstance;
2762 for (;(templInstance=cli.current());++cli)
2764 if (templInstance==cd)
2766 addClass(templMaster,n,EdgeInfo::Orange,cli.currentKey(),0,
2772 else // template relations for super classes
2774 QDict<ClassDef> *templInstances = cd->getTemplateInstances();
2777 QDictIterator<ClassDef> cli(*templInstances);
2778 ClassDef *templInstance;
2779 for (;(templInstance=cli.current());++cli)
2781 addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0,
2789 DotClassGraph::DotClassGraph(ClassDef *cd,DotNode::GraphType t)
2791 //printf("--------------- DotClassGraph::DotClassGraph `%s'\n",cd->displayName().data());
2793 QCString tmp_url="";
2794 if (cd->isLinkable() && !cd->isHidden())
2796 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2797 if (!cd->anchor().isEmpty())
2799 tmp_url+="#"+cd->anchor();
2802 QCString className = cd->displayName();
2803 QCString tooltip = cd->briefDescriptionAsTooltip();
2804 m_startNode = new DotNode(m_curNodeNumber++,
2808 TRUE, // is a root node
2811 m_startNode->setDistance(0);
2812 m_usedNodes = new QDict<DotNode>(1009);
2813 m_usedNodes->insert(className,m_startNode);
2815 //printf("Root node %s\n",cd->name().data());
2818 buildGraph(cd,m_startNode,TRUE,1);
2819 if (t==DotNode::Inheritance) buildGraph(cd,m_startNode,FALSE,1);
2822 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
2823 //int directChildNodes = 1;
2824 //if (m_startNode->m_children!=0)
2825 // directChildNodes+=m_startNode->m_children->count();
2826 //if (t==DotNode::Inheritance && m_startNode->m_parents!=0)
2827 // directChildNodes+=m_startNode->m_parents->count();
2828 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
2829 //openNodeQueue.append(m_startNode);
2830 m_lrRank = determineVisibleNodes(m_startNode,maxNodes,t==DotNode::Inheritance);
2831 QList<DotNode> openNodeQueue;
2832 openNodeQueue.append(m_startNode);
2833 determineTruncatedNodes(openNodeQueue,t==DotNode::Inheritance);
2835 m_diskName = cd->getFileBase().copy();
2838 bool DotClassGraph::isTrivial() const
2840 static bool umlLook = Config_getBool("UML_LOOK");
2841 if (m_graphType==DotNode::Inheritance)
2842 return m_startNode->m_children==0 && m_startNode->m_parents==0;
2844 return !umlLook && m_startNode->m_children==0;
2847 bool DotClassGraph::isTooBig() const
2849 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
2851 numNodes+= m_startNode->m_children ? m_startNode->m_children->count() : 0;
2852 if (m_graphType==DotNode::Inheritance)
2854 numNodes+= m_startNode->m_parents ? m_startNode->m_parents->count() : 0;
2856 return numNodes>=maxNodes;
2859 DotClassGraph::~DotClassGraph()
2861 deleteNodes(m_startNode);
2865 /*! Computes a 16 byte md5 checksum for a given dot graph.
2866 * The md5 checksum is returned as a 32 character ASCII string.
2868 QCString computeMd5Signature(DotNode *root,
2869 DotNode::GraphType gt,
2870 GraphOutputFormat format,
2874 const QCString &title,
2880 //printf("computeMd5Signature\n");
2882 FTextStream md5stream(&buf);
2883 writeGraphHeader(md5stream,title);
2886 md5stream << " rankdir=\"LR\";" << endl;
2888 root->clearWriteFlag();
2889 root->write(md5stream,
2892 gt!=DotNode::CallGraph && gt!=DotNode::Dependency,
2896 if (renderParents && root->m_parents)
2898 QListIterator<DotNode> dnli(*root->m_parents);
2900 for (dnli.toFirst();(pn=dnli.current());++dnli)
2902 if (pn->isVisible())
2904 root->writeArrow(md5stream, // stream
2906 format, // output format
2908 pn->m_edgeInfo->at(pn->m_children->findRef(root)), // edge info
2910 backArrows, // point back?
2911 reNumber // renumber nodes
2914 pn->write(md5stream, // stream
2916 format, // output format
2918 FALSE, // toChildren?
2919 backArrows, // backward pointing arrows?
2920 reNumber // renumber nodes?
2924 writeGraphFooter(md5stream);
2926 QCString sigStr(33);
2927 MD5Buffer((const unsigned char *)buf.data(),buf.length(),md5_sig);
2928 MD5SigToString(md5_sig,sigStr.data(),33);
2933 graphStr=buf.data();
2934 //printf("md5: %s | file: %s\n",sigStr,baseName.data());
2938 static bool updateDotGraph(DotNode *root,
2939 DotNode::GraphType gt,
2940 const QCString &baseName,
2941 GraphOutputFormat format,
2945 const QCString &title=QCString()
2949 // TODO: write graph to theGraph, then compute md5 checksum
2950 QCString md5 = computeMd5Signature(
2951 root,gt,format,lrRank,renderParents,
2952 backArrows,title,theGraph);
2953 QFile f(baseName+".dot");
2954 if (f.open(IO_WriteOnly))
2959 return checkAndUpdateMd5Signature(baseName,md5); // graph needs to be regenerated
2962 QCString DotClassGraph::diskName() const
2964 QCString result=m_diskName.copy();
2965 switch (m_graphType)
2967 case DotNode::Collaboration:
2968 result+="_coll_graph";
2971 // result+="_intf_graph";
2973 case DotNode::Inheritance:
2974 result+="_inherit_graph";
2983 QCString DotClassGraph::writeGraph(FTextStream &out,
2984 GraphOutputFormat format,
2986 const char *fileName,
2987 const char *relPath,
2989 bool generateImageMap,
2993 // store the original directory
2996 err("error: Output dir %s does not exist!\n",path); exit(1);
2998 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3002 switch (m_graphType)
3004 case DotNode::Collaboration:
3008 // mapName="intf_map";
3010 case DotNode::Inheritance:
3011 mapName="inherit_map";
3017 baseName = convertNameToFile(diskName());
3019 // derive target file names from baseName
3020 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3021 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3022 QCString absDotName = absBaseName+".dot";
3023 QCString absMapName = absBaseName+".map";
3024 QCString absPdfName = absBaseName+".pdf";
3025 QCString absEpsName = absBaseName+".eps";
3026 QCString absImgName = absBaseName+"."+imgExt;
3028 bool regenerate = FALSE;
3029 if (updateDotGraph(m_startNode,
3034 m_graphType==DotNode::Inheritance,
3036 m_startNode->label()
3038 !checkDeliverables(format==BITMAP ? absImgName :
3039 usePDFLatex ? absPdfName : absEpsName,
3040 format==BITMAP && generateImageMap ? absMapName : QCString())
3044 if (format==BITMAP) // run dot to create a bitmap image
3046 QCString dotArgs(maxCmdLine);
3048 DotRunner *dotRun = new DotRunner(absDotName,
3049 d.absPath().data(),TRUE,absImgName);
3050 dotRun->addJob(imgExt,absImgName);
3051 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3052 DotManager::instance()->addRun(dotRun);
3055 else if (format==EPS) // run dot to create a .eps image
3057 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3060 dotRun->addJob("pdf",absPdfName);
3064 dotRun->addJob("ps",absEpsName);
3066 DotManager::instance()->addRun(dotRun);
3069 Doxygen::indexList.addImageFile(baseName+"."+imgExt);
3071 if (format==BITMAP && generateImageMap) // produce HTML to include the image
3073 QCString mapLabel = escapeCharsInString(m_startNode->m_label,FALSE)+"_"+
3074 escapeCharsInString(mapName,FALSE);
3075 if (imgExt=="svg") // add link to SVG file without map file
3077 out << "<div class=\"center\">";
3078 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3082 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3084 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3085 out << "<!-- SVG " << mapId << " -->" << endl;
3087 out << "</div>" << endl;
3089 else // add link to bitmap file with image map
3091 out << "<div class=\"center\">";
3092 out << "<img src=\"" << relPath << baseName << "."
3093 << imgExt << "\" border=\"0\" usemap=\"#"
3094 << mapLabel << "\" alt=\"";
3095 switch (m_graphType)
3097 case DotNode::Collaboration:
3098 out << "Collaboration graph";
3100 case DotNode::Inheritance:
3101 out << "Inheritance graph";
3108 out << "</div>" << endl;
3110 if (regenerate || !insertMapFile(out,absMapName,relPath,mapLabel))
3112 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3113 FALSE,QCString(),mapLabel);
3114 out << "<!-- MAP " << mapId << " -->" << endl;
3118 else if (format==EPS) // produce tex to include the .eps image
3120 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3122 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE /*TRUE*/);
3123 out << endl << "% FIG " << figId << endl;
3126 if (!regenerate) removeDotGraph(absDotName);
3131 //--------------------------------------------------------------------
3133 void DotClassGraph::writeXML(FTextStream &t)
3135 QDictIterator<DotNode> dni(*m_usedNodes);
3137 for (;(node=dni.current());++dni)
3139 node->writeXML(t,TRUE);
3143 void DotClassGraph::writeDEF(FTextStream &t)
3145 QDictIterator<DotNode> dni(*m_usedNodes);
3147 for (;(node=dni.current());++dni)
3153 //--------------------------------------------------------------------
3155 int DotInclDepGraph::m_curNodeNumber = 0;
3157 void DotInclDepGraph::buildGraph(DotNode *n,FileDef *fd,int distance)
3159 QList<IncludeInfo> *includeFiles =
3160 m_inverse ? fd->includedByFileList() : fd->includeFileList();
3163 QListIterator<IncludeInfo> ili(*includeFiles);
3165 for (;(ii=ili.current());++ili)
3167 FileDef *bfd = ii->fileDef;
3168 QCString in = ii->includeName;
3169 //printf(">>>> in=`%s' bfd=%p\n",ii->includeName.data(),bfd);
3170 bool doc=TRUE,src=FALSE;
3173 in = bfd->absFilePath();
3174 doc = bfd->isLinkable() && !bfd->isHidden();
3175 src = bfd->generateSourceFile();
3177 if (doc || src || !Config_getBool("HIDE_UNDOC_RELATIONS"))
3180 if (bfd) url=bfd->getOutputFileBase().copy();
3183 url=bfd->getSourceFileBase();
3185 DotNode *bn = m_usedNodes->find(in);
3186 if (bn) // file is already a node in the graph
3188 n->addChild(bn,0,0,0);
3190 bn->setDistance(distance);
3198 tmp_url=doc || src ? bfd->getReference()+"$"+url : QCString();
3199 tooltip = bfd->briefDescriptionAsTooltip();
3202 m_curNodeNumber++, // n
3203 ii->includeName, // label
3209 n->addChild(bn,0,0,0);
3211 m_usedNodes->insert(in,bn);
3212 bn->setDistance(distance);
3214 if (bfd) buildGraph(bn,bfd,distance+1);
3221 void DotInclDepGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
3223 while (queue.count()>0 && maxNodes>0)
3225 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
3226 DotNode *n = queue.take(0);
3227 if (!n->isVisible() && n->distance()<maxDistance) // not yet processed
3231 // add direct children
3234 QListIterator<DotNode> li(*n->m_children);
3236 for (li.toFirst();(dn=li.current());++li)
3245 void DotInclDepGraph::determineTruncatedNodes(QList<DotNode> &queue)
3247 while (queue.count()>0)
3249 DotNode *n = queue.take(0);
3250 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
3252 bool truncated = FALSE;
3255 QListIterator<DotNode> li(*n->m_children);
3257 for (li.toFirst();(dn=li.current());++li)
3259 if (!dn->isVisible())
3265 n->markAsTruncated(truncated);
3271 DotInclDepGraph::DotInclDepGraph(FileDef *fd,bool inverse)
3274 m_inverse = inverse;
3276 m_diskName = fd->getFileBase().copy();
3277 QCString tmp_url=fd->getReference()+"$"+fd->getFileBase();
3278 m_startNode = new DotNode(m_curNodeNumber++,
3284 m_startNode->setDistance(0);
3285 m_usedNodes = new QDict<DotNode>(1009);
3286 m_usedNodes->insert(fd->absFilePath(),m_startNode);
3287 buildGraph(m_startNode,fd,1);
3289 static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3290 int maxNodes = nodes;
3291 //int directChildNodes = 1;
3292 //if (m_startNode->m_children!=0)
3293 // directChildNodes+=m_startNode->m_children->count();
3294 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3295 QList<DotNode> openNodeQueue;
3296 openNodeQueue.append(m_startNode);
3297 determineVisibleNodes(openNodeQueue,maxNodes);
3298 openNodeQueue.clear();
3299 openNodeQueue.append(m_startNode);
3300 determineTruncatedNodes(openNodeQueue);
3303 DotInclDepGraph::~DotInclDepGraph()
3305 deleteNodes(m_startNode);
3309 QCString DotInclDepGraph::diskName() const
3311 QCString result=m_diskName.copy();
3312 if (m_inverse) result+="_dep";
3314 return convertNameToFile(result);
3317 QCString DotInclDepGraph::writeGraph(FTextStream &out,
3318 GraphOutputFormat format,
3320 const char *fileName,
3321 const char *relPath,
3322 bool generateImageMap,
3327 // store the original directory
3330 err("error: Output dir %s does not exist!\n",path); exit(1);
3332 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3334 QCString baseName=m_diskName;
3335 if (m_inverse) baseName+="_dep";
3337 baseName=convertNameToFile(baseName);
3338 QCString mapName=escapeCharsInString(m_startNode->m_label,FALSE);
3339 if (m_inverse) mapName+="dep";
3341 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3342 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3343 QCString absDotName = absBaseName+".dot";
3344 QCString absMapName = absBaseName+".map";
3345 QCString absPdfName = absBaseName+".pdf";
3346 QCString absEpsName = absBaseName+".eps";
3347 QCString absImgName = absBaseName+"."+imgExt;
3349 bool regenerate = FALSE;
3350 if (updateDotGraph(m_startNode,
3351 DotNode::Dependency,
3355 FALSE, // renderParents
3356 m_inverse, // backArrows
3357 m_startNode->label()
3359 !checkDeliverables(format==BITMAP ? absImgName :
3360 usePDFLatex ? absPdfName : absEpsName,
3361 format==BITMAP && generateImageMap ? absMapName : QCString())
3367 // run dot to create a bitmap image
3368 QCString dotArgs(maxCmdLine);
3369 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3370 dotRun->addJob(imgExt,absImgName);
3371 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3372 DotManager::instance()->addRun(dotRun);
3374 else if (format==EPS)
3376 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3379 dotRun->addJob("pdf",absPdfName);
3383 dotRun->addJob("ps",absEpsName);
3385 DotManager::instance()->addRun(dotRun);
3389 Doxygen::indexList.addImageFile(baseName+"."+imgExt);
3391 if (format==BITMAP && generateImageMap)
3393 if (imgExt=="svg") // Scalable vector graphics
3395 out << "<div class=\"center\">";
3396 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3400 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3402 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3403 out << "<!-- SVG " << mapId << " -->" << endl;
3405 out << "</div>" << endl;
3407 else // bitmap graphics
3409 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3410 << imgExt << "\" border=\"0\" usemap=\"#"
3411 << mapName << "\" alt=\"\"/>";
3412 out << "</div>" << endl;
3414 QCString absMapName = absBaseName+".map";
3415 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3417 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3418 FALSE,QCString(),mapName);
3419 out << "<!-- MAP " << mapId << " -->" << endl;
3423 else if (format==EPS) // encapsulated postscript
3425 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3427 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3428 out << endl << "% FIG " << figId << endl;
3431 if (!regenerate) removeDotGraph(absDotName);
3436 bool DotInclDepGraph::isTrivial() const
3438 return m_startNode->m_children==0;
3441 bool DotInclDepGraph::isTooBig() const
3443 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3444 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
3445 return numNodes>=maxNodes;
3448 void DotInclDepGraph::writeXML(FTextStream &t)
3450 QDictIterator<DotNode> dni(*m_usedNodes);
3452 for (;(node=dni.current());++dni)
3454 node->writeXML(t,FALSE);
3458 //-------------------------------------------------------------
3460 int DotCallGraph::m_curNodeNumber = 0;
3462 void DotCallGraph::buildGraph(DotNode *n,MemberDef *md,int distance)
3464 LockingPtr<MemberSDict> refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers();
3467 MemberSDict::Iterator mri(*refs);
3469 for (;(rmd=mri.current());++mri)
3471 if (rmd->isFunction() || rmd->isSlot() || rmd->isSignal())
3474 uniqueId=rmd->getReference()+"$"+
3475 rmd->getOutputFileBase()+"#"+rmd->anchor();
3476 DotNode *bn = m_usedNodes->find(uniqueId);
3477 if (bn) // file is already a node in the graph
3479 n->addChild(bn,0,0,0);
3481 bn->setDistance(distance);
3486 if (Config_getBool("HIDE_SCOPE_NAMES"))
3488 name = rmd->getOuterScope()==m_scope ?
3489 rmd->name() : rmd->qualifiedName();
3493 name = rmd->qualifiedName();
3495 QCString tooltip = rmd->briefDescriptionAsTooltip();
3498 linkToText(rmd->getLanguage(),name,FALSE),
3503 n->addChild(bn,0,0,0);
3505 bn->setDistance(distance);
3506 m_usedNodes->insert(uniqueId,bn);
3508 buildGraph(bn,rmd,distance+1);
3515 void DotCallGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
3517 while (queue.count()>0 && maxNodes>0)
3519 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
3520 DotNode *n = queue.take(0);
3521 if (!n->isVisible() && n->distance()<maxDistance) // not yet processed
3525 // add direct children
3528 QListIterator<DotNode> li(*n->m_children);
3530 for (li.toFirst();(dn=li.current());++li)
3539 void DotCallGraph::determineTruncatedNodes(QList<DotNode> &queue)
3541 while (queue.count()>0)
3543 DotNode *n = queue.take(0);
3544 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
3546 bool truncated = FALSE;
3549 QListIterator<DotNode> li(*n->m_children);
3551 for (li.toFirst();(dn=li.current());++li)
3553 if (!dn->isVisible())
3559 n->markAsTruncated(truncated);
3566 DotCallGraph::DotCallGraph(MemberDef *md,bool inverse)
3569 m_inverse = inverse;
3570 m_diskName = md->getOutputFileBase()+"_"+md->anchor();
3571 m_scope = md->getOuterScope();
3573 uniqueId = md->getReference()+"$"+
3574 md->getOutputFileBase()+"#"+md->anchor();
3576 if (Config_getBool("HIDE_SCOPE_NAMES"))
3582 name = md->qualifiedName();
3584 m_startNode = new DotNode(m_curNodeNumber++,
3585 linkToText(md->getLanguage(),name,FALSE),
3590 m_startNode->setDistance(0);
3591 m_usedNodes = new QDict<DotNode>(1009);
3592 m_usedNodes->insert(uniqueId,m_startNode);
3593 buildGraph(m_startNode,md,1);
3595 static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3596 int maxNodes = nodes;
3597 //int directChildNodes = 1;
3598 //if (m_startNode->m_children!=0)
3599 // directChildNodes+=m_startNode->m_children->count();
3600 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3601 QList<DotNode> openNodeQueue;
3602 openNodeQueue.append(m_startNode);
3603 determineVisibleNodes(openNodeQueue,maxNodes);
3604 openNodeQueue.clear();
3605 openNodeQueue.append(m_startNode);
3606 determineTruncatedNodes(openNodeQueue);
3609 DotCallGraph::~DotCallGraph()
3611 deleteNodes(m_startNode);
3615 QCString DotCallGraph::writeGraph(FTextStream &out, GraphOutputFormat format,
3616 const char *path,const char *fileName,
3617 const char *relPath,bool generateImageMap,int
3621 // store the original directory
3624 err("error: Output dir %s does not exist!\n",path); exit(1);
3626 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3628 QCString baseName = m_diskName + (m_inverse ? "_icgraph" : "_cgraph");
3629 QCString mapName = baseName;
3631 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3632 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3633 QCString absDotName = absBaseName+".dot";
3634 QCString absMapName = absBaseName+".map";
3635 QCString absPdfName = absBaseName+".pdf";
3636 QCString absEpsName = absBaseName+".eps";
3637 QCString absImgName = absBaseName+"."+imgExt;
3639 bool regenerate = FALSE;
3640 if (updateDotGraph(m_startNode,
3645 FALSE, // renderParents
3646 m_inverse, // backArrows
3647 m_startNode->label()
3649 !checkDeliverables(format==BITMAP ? absImgName :
3650 usePDFLatex ? absPdfName : absEpsName,
3651 format==BITMAP && generateImageMap ? absMapName : QCString())
3657 // run dot to create a bitmap image
3658 QCString dotArgs(maxCmdLine);
3659 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3660 dotRun->addJob(imgExt,absImgName);
3661 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3662 DotManager::instance()->addRun(dotRun);
3665 else if (format==EPS)
3667 // run dot to create a .eps image
3668 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3671 dotRun->addJob("pdf",absPdfName);
3675 dotRun->addJob("ps",absEpsName);
3677 DotManager::instance()->addRun(dotRun);
3681 Doxygen::indexList.addImageFile(baseName+"."+imgExt);
3683 if (format==BITMAP && generateImageMap)
3685 if (imgExt=="svg") // Scalable vector graphics
3687 out << "<div class=\"center\">";
3688 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3692 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3694 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3695 out << "<!-- SVG " << mapId << " -->" << endl;
3697 out << "</div>" << endl;
3699 else // bitmap graphics
3701 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3702 << imgExt << "\" border=\"0\" usemap=\"#"
3703 << mapName << "\" alt=\"";
3705 out << "</div>" << endl;
3707 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3709 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3710 FALSE,QCString(),mapName);
3711 out << "<!-- MAP " << mapId << " -->" << endl;
3715 else if (format==EPS) // encapsulated postscript
3717 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3719 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3720 out << endl << "% FIG " << figId << endl;
3723 if (!regenerate) removeDotGraph(absDotName);
3728 bool DotCallGraph::isTrivial() const
3730 return m_startNode->m_children==0;
3733 bool DotCallGraph::isTooBig() const
3735 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3736 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
3737 return numNodes>=maxNodes;
3740 //-------------------------------------------------------------
3742 DotDirDeps::DotDirDeps(DirDef *dir) : m_dir(dir)
3746 DotDirDeps::~DotDirDeps()
3750 QCString DotDirDeps::writeGraph(FTextStream &out,
3751 GraphOutputFormat format,
3753 const char *fileName,
3754 const char *relPath,
3755 bool generateImageMap,
3759 // store the original directory
3762 err("error: Output dir %s does not exist!\n",path); exit(1);
3764 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3766 QCString baseName=m_dir->getOutputFileBase()+"_dep";
3767 QCString mapName=escapeCharsInString(baseName,FALSE);
3769 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3770 QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3771 QCString absDotName = absBaseName+".dot";
3772 QCString absMapName = absBaseName+".map";
3773 QCString absPdfName = absBaseName+".pdf";
3774 QCString absEpsName = absBaseName+".eps";
3775 QCString absImgName = absBaseName+"."+imgExt;
3777 // compute md5 checksum of the graph were are about to generate
3779 FTextStream md5stream(&theGraph);
3780 m_dir->writeDepGraph(md5stream);
3782 QCString sigStr(33);
3783 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
3784 MD5SigToString(md5_sig,sigStr.data(),33);
3785 bool regenerate=FALSE;
3786 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
3787 !checkDeliverables(format==BITMAP ? absImgName :
3788 usePDFLatex ? absPdfName : absEpsName,
3789 format==BITMAP && generateImageMap ? absMapName : QCString())
3794 QFile f(absDotName);
3795 if (!f.open(IO_WriteOnly))
3797 err("Cannot create file %s.dot for writing!\n",baseName.data());
3800 t << theGraph.data();
3805 // run dot to create a bitmap image
3806 QCString dotArgs(maxCmdLine);
3807 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3808 dotRun->addJob(imgExt,absImgName);
3809 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3810 DotManager::instance()->addRun(dotRun);
3812 else if (format==EPS)
3814 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3817 dotRun->addJob("pdf",absPdfName);
3821 dotRun->addJob("ps",absEpsName);
3823 DotManager::instance()->addRun(dotRun);
3826 Doxygen::indexList.addImageFile(baseName+"."+imgExt);
3828 if (format==BITMAP && generateImageMap)
3830 if (imgExt=="svg") // Scalable vector graphics
3832 out << "<div class=\"center\">";
3833 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3837 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3839 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3840 out << "<!-- SVG " << mapId << " -->" << endl;
3842 out << "</div>" << endl;
3844 else // bitmap graphics
3846 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3847 << imgExt << "\" border=\"0\" usemap=\"#"
3848 << mapName << "\" alt=\"";
3849 out << convertToXML(m_dir->displayName());
3851 out << "</div>" << endl;
3853 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3855 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3856 TRUE,QCString(),mapName);
3857 out << "<!-- MAP " << mapId << " -->" << endl;
3861 else if (format==EPS)
3863 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3865 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3866 out << endl << "% FIG " << figId << endl;
3869 if (!regenerate) removeDotGraph(absDotName);
3874 bool DotDirDeps::isTrivial() const
3876 return m_dir->depGraphIsTrivial();
3879 //-------------------------------------------------------------
3881 void generateGraphLegend(const char *path)
3884 // store the original directory
3887 err("error: Output dir %s does not exist!\n",path); exit(1);
3891 FTextStream md5stream(&theGraph);
3892 writeGraphHeader(md5stream,theTranslator->trLegendTitle());
3893 md5stream << " Node9 [shape=\"box\",label=\"Inherited\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",fillcolor=\"grey75\",style=\"filled\" fontcolor=\"black\"];\n";
3894 md5stream << " Node10 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3895 md5stream << " Node10 [shape=\"box\",label=\"PublicBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPublicBase" << Doxygen::htmlFileExtension << "\"];\n";
3896 md5stream << " Node11 -> Node10 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3897 md5stream << " Node11 [shape=\"box\",label=\"Truncated\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"red\",URL=\"$classTruncated" << Doxygen::htmlFileExtension << "\"];\n";
3898 md5stream << " Node13 -> Node9 [dir=\"back\",color=\"darkgreen\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3899 md5stream << " Node13 [shape=\"box\",label=\"ProtectedBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classProtectedBase" << Doxygen::htmlFileExtension << "\"];\n";
3900 md5stream << " Node14 -> Node9 [dir=\"back\",color=\"firebrick4\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3901 md5stream << " Node14 [shape=\"box\",label=\"PrivateBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPrivateBase" << Doxygen::htmlFileExtension << "\"];\n";
3902 md5stream << " Node15 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3903 md5stream << " Node15 [shape=\"box\",label=\"Undocumented\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"grey75\"];\n";
3904 md5stream << " Node16 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3905 md5stream << " Node16 [shape=\"box\",label=\"Templ< int >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
3906 md5stream << " Node17 -> Node16 [dir=\"back\",color=\"orange\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"< int >\",fontname=\"" << FONTNAME << "\"];\n";
3907 md5stream << " Node17 [shape=\"box\",label=\"Templ< T >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
3908 md5stream << " Node18 -> Node9 [dir=\"back\",color=\"darkorchid3\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"m_usedClass\",fontname=\"" << FONTNAME << "\"];\n";
3909 md5stream << " Node18 [shape=\"box\",label=\"Used\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classUsed" << Doxygen::htmlFileExtension << "\"];\n";
3910 writeGraphFooter(md5stream);
3912 QCString sigStr(33);
3913 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
3914 MD5SigToString(md5_sig,sigStr.data(),33);
3915 QCString absBaseName = (QCString)path+"/graph_legend";
3916 QCString absDotName = absBaseName+".dot";
3917 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3918 QCString imgName = "graph_legend."+imgExt;
3919 QCString absImgName = absBaseName+"."+imgExt;
3920 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
3921 !checkDeliverables(absImgName))
3923 QFile dotFile(absDotName);
3924 if (!dotFile.open(IO_WriteOnly))
3926 err("Could not open file %s for writing\n",
3927 convertToQCString(dotFile.name()).data());
3931 FTextStream dotText(&dotFile);
3932 dotText << theGraph;
3935 // run dot to generate the a bitmap image from the graph
3937 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3938 dotRun->addJob(imgExt,absImgName);
3939 DotManager::instance()->addRun(dotRun);
3943 removeDotGraph(absDotName);
3945 Doxygen::indexList.addImageFile(imgName);
3949 DotManager::instance()->addSVGObject(
3950 absBaseName+Config_getString("HTML_FILE_EXTENSION"),
3952 absImgName,QCString());
3957 void writeDotGraphFromFile(const char *inFile,const char *outDir,
3958 const char *outFile,GraphOutputFormat format)
3963 err("error: Output dir %s does not exist!\n",outDir); exit(1);
3966 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3967 QCString imgName = (QCString)outFile+"."+imgExt;
3968 QCString absImgName = d.absPath().utf8()+"/"+imgName;
3969 QCString absOutFile = d.absPath().utf8()+"/"+outFile;
3971 DotRunner dotRun(inFile,d.absPath().data(),FALSE,absImgName);
3973 dotRun.addJob(imgExt,absImgName);
3976 if (Config_getBool("USE_PDFLATEX"))
3978 dotRun.addJob("pdf",absOutFile+".pdf");
3982 dotRun.addJob("ps",absOutFile+".eps");
3986 dotRun.preventCleanUp();
3992 if (format==BITMAP) checkDotResult(absImgName);
3994 Doxygen::indexList.addImageFile(imgName);
3999 /*! Writes user defined image map to the output.
4000 * \param t text stream to write to
4001 * \param inFile just the basename part of the filename
4002 * \param outDir output directory
4003 * \param relPath relative path the to root of the output dir
4004 * \param baseName the base name of the output files
4005 * \param context the scope in which this graph is found (for resolving links)
4006 * \param graphId a unique id for this graph, use for dynamic sections
4008 void writeDotImageMapFromFile(FTextStream &t,
4009 const QCString &inFile, const QCString &outDir,
4010 const QCString &relPath, const QCString &baseName,
4011 const QCString &context,int graphId)
4017 err("error: Output dir %s does not exist!\n",outDir.data()); exit(1);
4020 QCString mapName = baseName+".map";
4021 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
4022 QCString imgName = baseName+"."+imgExt;
4023 QCString absOutFile = d.absPath().utf8()+"/"+mapName;
4025 DotRunner dotRun(inFile,d.absPath().data(),FALSE);
4026 dotRun.addJob(MAP_CMD,absOutFile);
4027 dotRun.preventCleanUp();
4033 if (imgExt=="svg") // vector graphics
4035 //writeSVGFigureLink(t,relPath,inFile,inFile+".svg");
4036 //DotFilePatcher patcher(inFile+".svg");
4037 QCString svgName=outDir+"/"+baseName+".svg";
4038 writeSVGFigureLink(t,relPath,baseName,svgName);
4039 DotFilePatcher patcher(svgName);
4040 patcher.addSVGConversion(relPath,TRUE,context,TRUE,graphId);
4043 else // bitmap graphics
4045 t << "<img src=\"" << relPath << imgName << "\" alt=\""
4046 << imgName << "\" border=\"0\" usemap=\"#" << mapName << "\"/>" << endl
4047 << "<map name=\"" << mapName << "\" id=\"" << mapName << "\">";
4049 convertMapFile(t, absOutFile, relPath ,TRUE, context);
4051 t << "</map>" << endl;
4053 d.remove(absOutFile);
4056 //-------------------------------------------------------------
4058 DotGroupCollaboration::DotGroupCollaboration(GroupDef* gd)
4061 QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase();
4062 m_usedNodes = new QDict<DotNode>(1009);
4063 m_rootNode = new DotNode(m_curNodeId++, gd->groupTitle(), "", tmp_url, TRUE );
4064 m_rootNode->markAsVisible();
4065 m_usedNodes->insert(gd->name(), m_rootNode );
4066 m_edges.setAutoDelete(TRUE);
4068 m_diskName = gd->getOutputFileBase();
4073 DotGroupCollaboration::~DotGroupCollaboration()
4078 void DotGroupCollaboration::buildGraph(GroupDef* gd)
4081 //===========================
4085 LockingPtr<GroupList> groups = gd->partOfGroups();
4088 GroupListIterator gli(*groups);
4090 for (gli.toFirst();(d=gli.current());++gli)
4092 DotNode* nnode = m_usedNodes->find(d->name());
4095 tmp_url = d->getReference()+"$"+d->getOutputFileBase();
4096 QCString tooltip = d->briefDescriptionAsTooltip();
4097 nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_url );
4098 nnode->markAsVisible();
4099 m_usedNodes->insert(d->name(), nnode );
4102 addEdge( nnode, m_rootNode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
4107 if ( gd->getSubGroups() && gd->getSubGroups()->count() )
4109 QListIterator<GroupDef> defli(*gd->getSubGroups());
4111 for (;(def=defli.current());++defli)
4113 DotNode* nnode = m_usedNodes->find(def->name());
4116 tmp_url = def->getReference()+"$"+def->getOutputFileBase();
4117 QCString tooltip = def->briefDescriptionAsTooltip();
4118 nnode = new DotNode(m_curNodeId++, def->groupTitle(), tooltip, tmp_url );
4119 nnode->markAsVisible();
4120 m_usedNodes->insert(def->name(), nnode );
4123 addEdge( m_rootNode, nnode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
4127 //=======================
4128 // Write collaboration
4131 addMemberList( gd->getMemberList(MemberList::allMembersList) );
4134 if ( gd->getClasses() && gd->getClasses()->count() )
4136 ClassSDict::Iterator defli(*gd->getClasses());
4138 for (;(def=defli.current());++defli)
4140 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4141 if (!def->anchor().isEmpty())
4143 tmp_url+="#"+def->anchor();
4145 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tclass );
4150 if ( gd->getNamespaces() && gd->getNamespaces()->count() )
4152 NamespaceSDict::Iterator defli(*gd->getNamespaces());
4154 for (;(def=defli.current());++defli)
4156 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4157 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tnamespace );
4162 if ( gd->getFiles() && gd->getFiles()->count() )
4164 QListIterator<FileDef> defli(*gd->getFiles());
4166 for (;(def=defli.current());++defli)
4168 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4169 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tfile );
4174 if ( gd->getPages() && gd->getPages()->count() )
4176 PageSDict::Iterator defli(*gd->getPages());
4178 for (;(def=defli.current());++defli)
4180 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4181 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tpages );
4186 if ( gd->getDirs() && gd->getDirs()->count() )
4188 QListIterator<DirDef> defli(*gd->getDirs());
4190 for (;(def=defli.current());++defli)
4192 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4193 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tdir );
4198 void DotGroupCollaboration::addMemberList( MemberList* ml )
4200 if ( !( ml && ml->count()) ) return;
4201 MemberListIterator defli(*ml);
4203 for (;(def=defli.current());++defli)
4205 QCString tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension
4207 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tmember );
4211 DotGroupCollaboration::Edge* DotGroupCollaboration::addEdge(
4212 DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType,
4213 const QCString& _label, const QCString& _url )
4215 // search a existing link.
4216 QListIterator<Edge> lli(m_edges);
4218 for ( lli.toFirst(); (newEdge=lli.current()); ++lli)
4220 if ( newEdge->pNStart==_pNStart &&
4221 newEdge->pNEnd==_pNEnd &&
4222 newEdge->eType==_eType
4224 { // edge already found
4228 if ( newEdge==0 ) // new link
4230 newEdge = new Edge(_pNStart,_pNEnd,_eType);
4231 m_edges.append( newEdge );
4234 if (!_label.isEmpty())
4236 newEdge->links.append(new Link(_label,_url));
4242 void DotGroupCollaboration::addCollaborationMember(
4243 Definition* def, QCString& url, EdgeType eType )
4245 // Create group nodes
4246 if ( !def->partOfGroups() )
4248 GroupListIterator gli(*def->partOfGroups());
4251 for (;(d=gli.current());++gli)
4253 DotNode* nnode = m_usedNodes->find(d->name());
4254 if ( nnode != m_rootNode )
4258 tmp_str = d->getReference()+"$"+d->getOutputFileBase();
4259 QCString tooltip = d->briefDescriptionAsTooltip();
4260 nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_str );
4261 nnode->markAsVisible();
4262 m_usedNodes->insert(d->name(), nnode );
4264 tmp_str = def->qualifiedName();
4265 addEdge( m_rootNode, nnode, eType, tmp_str, url );
4271 QCString DotGroupCollaboration::writeGraph( FTextStream &t, GraphOutputFormat format,
4272 const char *path, const char *fileName, const char *relPath,
4273 bool writeImageMap,int graphId) const
4276 // store the original directory
4279 err("error: Output dir %s does not exist!\n",path); exit(1);
4281 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
4284 FTextStream md5stream(&theGraph);
4285 writeGraphHeader(md5stream,m_rootNode->label());
4287 // clean write flags
4288 QDictIterator<DotNode> dni(*m_usedNodes);
4290 for (dni.toFirst();(pn=dni.current());++dni)
4292 pn->clearWriteFlag();
4295 // write other nodes.
4296 for (dni.toFirst();(pn=dni.current());++dni)
4298 pn->write(md5stream,DotNode::Inheritance,format,TRUE,FALSE,FALSE,FALSE);
4302 QListIterator<Edge> eli(m_edges);
4304 for (eli.toFirst();(edge=eli.current());++eli)
4306 edge->write( md5stream );
4309 writeGraphFooter(md5stream);
4312 QCString sigStr(33);
4313 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
4314 MD5SigToString(md5_sig,sigStr.data(),33);
4315 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
4316 QCString baseName = m_diskName;
4317 QCString imgName = baseName+"."+imgExt;
4318 QCString mapName = baseName+".map";
4319 QCString absPath = d.absPath().data();
4320 QCString absBaseName = absPath+"/"+baseName;
4321 QCString absDotName = absBaseName+".dot";
4322 QCString absImgName = absBaseName+"."+imgExt;
4323 QCString absMapName = absBaseName+".map";
4324 QCString absPdfName = absBaseName+".pdf";
4325 QCString absEpsName = absBaseName+".eps";
4326 bool regenerate=FALSE;
4327 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
4328 !checkDeliverables(format==BITMAP ? absImgName :
4329 usePDFLatex ? absPdfName : absEpsName,
4330 format==BITMAP /*&& generateImageMap*/ ? absMapName : QCString())
4335 QFile dotfile(absDotName);
4336 if (dotfile.open(IO_WriteOnly))
4338 FTextStream tdot(&dotfile);
4343 if (format==BITMAP) // run dot to create a bitmap image
4345 QCString dotArgs(maxCmdLine);
4347 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
4348 dotRun->addJob(imgExt,absImgName);
4349 if (writeImageMap) dotRun->addJob(MAP_CMD,absMapName);
4350 DotManager::instance()->addRun(dotRun);
4353 else if (format==EPS)
4355 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
4358 dotRun->addJob("pdf",absPdfName);
4362 dotRun->addJob("ps",absEpsName);
4364 DotManager::instance()->addRun(dotRun);
4368 if (format==BITMAP && writeImageMap)
4370 QCString mapLabel = escapeCharsInString(baseName,FALSE);
4371 t << "<center><table><tr><td>";
4375 t << "<div class=\"center\">";
4376 if (regenerate || !writeSVGFigureLink(t,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
4380 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
4382 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
4383 t << "<!-- SVG " << mapId << " -->" << endl;
4385 t << "</div>" << endl;
4389 t << "<img src=\"" << relPath << imgName
4390 << "\" border=\"0\" alt=\"\" usemap=\"#"
4391 << mapLabel << "\"/>" << endl;
4392 if (regenerate || !insertMapFile(t,absMapName,relPath,mapLabel))
4394 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
4395 FALSE,QCString(),mapLabel);
4396 t << "<!-- MAP " << mapId << " -->" << endl;
4400 t << "</td></tr></table></center>" << endl;
4402 else if (format==EPS)
4404 if (regenerate || !writeVecGfxFigure(t,baseName,absBaseName))
4406 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
4407 t << endl << "% FIG " << figId << endl;
4410 if (!regenerate) removeDotGraph(absDotName);
4415 void DotGroupCollaboration::Edge::write( FTextStream &t ) const
4417 const char* linkTypeColor[] = {
4426 QCString arrowStyle = "dir=\"none\", style=\"dashed\"";
4427 t << " Node" << pNStart->number();
4429 t << "Node" << pNEnd->number();
4431 t << " [shape=plaintext";
4432 if (links.count()>0) // there are links
4435 // HTML-like edge labels crash on my Mac with Graphviz 2.0! and
4436 // are not supported by older version of dot.
4438 //t << label=<<TABLE BORDER=\"0\" CELLBORDER=\"0\">";
4439 //QListIterator<Link> lli(links);
4441 //for( lli.toFirst(); (link=lli.current()); ++lli)
4444 // if ( !link->url.isEmpty() )
4445 // t << " HREF=\"" << link->url << "\"";
4446 // t << ">" << link->label << "</TD></TR>";
4451 QListIterator<Link> lli(links);
4455 const int maxLabels = 10;
4456 for( lli.toFirst(); (link=lli.current()) && count<maxLabels; ++lli,++count)
4458 if (first) first=FALSE; else t << "\\n";
4459 t << convertLabel(link->label);
4461 if (count==maxLabels) t << "\\n...";
4468 arrowStyle = "dir=\"back\", style=\"solid\"";
4470 t << ", color=\"" << linkTypeColor[(int)eType] << "\"";
4473 t << ", " << arrowStyle;
4477 bool DotGroupCollaboration::isTrivial() const
4479 return m_usedNodes->count() <= 1;
4482 void DotGroupCollaboration::writeGraphHeader(FTextStream &t,
4483 const QCString &title) const
4486 if (title.isEmpty())
4488 t << "\"Dot Graph\"";
4492 t << "\"" << convertToXML(title) << "\"";
4496 if (Config_getBool("DOT_TRANSPARENT"))
4498 t << " bgcolor=\"transparent\";" << endl;
4500 t << " edge [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\","
4501 "labelfontname=\"" << FONTNAME << "\",labelfontsize=\"" << FONTSIZE << "\"];\n";
4502 t << " node [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\",shape=record];\n";
4503 t << " rankdir=LR;\n";
4506 void writeDotDirDepGraph(FTextStream &t,DirDef *dd)
4508 t << "digraph \"" << dd->displayName() << "\" {\n";
4509 if (Config_getBool("DOT_TRANSPARENT"))
4511 t << " bgcolor=transparent;\n";
4513 t << " compound=true\n";
4514 t << " node [ fontsize=\"" << FONTSIZE << "\", fontname=\"" << FONTNAME << "\"];\n";
4515 t << " edge [ labelfontsize=\"" << FONTSIZE << "\", labelfontname=\"" << FONTNAME << "\"];\n";
4517 QDict<DirDef> dirsInGraph(257);
4519 dirsInGraph.insert(dd->getOutputFileBase(),dd);
4522 t << " subgraph cluster" << dd->parent()->getOutputFileBase() << " {\n";
4523 t << " graph [ bgcolor=\"#ddddee\", pencolor=\"black\", label=\""
4524 << dd->parent()->shortName()
4525 << "\" fontname=\"" << FONTNAME << "\", fontsize=\"" << FONTSIZE << "\", URL=\"";
4526 t << dd->parent()->getOutputFileBase() << Doxygen::htmlFileExtension;
4529 if (dd->isCluster())
4531 t << " subgraph cluster" << dd->getOutputFileBase() << " {\n";
4532 t << " graph [ bgcolor=\"#eeeeff\", pencolor=\"black\", label=\"\""
4533 << " URL=\"" << dd->getOutputFileBase() << Doxygen::htmlFileExtension
4535 t << " " << dd->getOutputFileBase() << " [shape=plaintext label=\""
4536 << dd->shortName() << "\"];\n";
4538 // add nodes for sub directories
4539 QListIterator<DirDef> sdi(dd->subDirs());
4541 for (sdi.toFirst();(sdir=sdi.current());++sdi)
4543 t << " " << sdir->getOutputFileBase() << " [shape=box label=\""
4544 << sdir->shortName() << "\"";
4545 if (sdir->isCluster())
4547 t << " color=\"red\"";
4551 t << " color=\"black\"";
4553 t << " fillcolor=\"white\" style=\"filled\"";
4554 t << " URL=\"" << sdir->getOutputFileBase()
4555 << Doxygen::htmlFileExtension << "\"";
4557 dirsInGraph.insert(sdir->getOutputFileBase(),sdir);
4563 t << " " << dd->getOutputFileBase() << " [shape=box, label=\""
4564 << dd->shortName() << "\", style=\"filled\", fillcolor=\"#eeeeff\","
4565 << " pencolor=\"black\", URL=\"" << dd->getOutputFileBase()
4566 << Doxygen::htmlFileExtension << "\"];\n";
4573 // add nodes for other used directories
4574 QDictIterator<UsedDir> udi(*dd->usedDirs());
4576 //printf("*** For dir %s\n",shortName().data());
4577 for (udi.toFirst();(udir=udi.current());++udi)
4578 // for each used dir (=directly used or a parent of a directly used dir)
4580 const DirDef *usedDir=udir->dir();
4584 //printf("*** check relation %s->%s same_parent=%d !%s->isParentOf(%s)=%d\n",
4585 // dir->shortName().data(),usedDir->shortName().data(),
4586 // dir->parent()==usedDir->parent(),
4587 // usedDir->shortName().data(),
4588 // shortName().data(),
4589 // !usedDir->isParentOf(this)
4591 if (dir!=usedDir && dir->parent()==usedDir->parent() &&
4592 !usedDir->isParentOf(dd))
4593 // include if both have the same parent (or no parent)
4595 t << " " << usedDir->getOutputFileBase() << " [shape=box label=\""
4596 << usedDir->shortName() << "\"";
4597 if (usedDir->isCluster())
4599 if (!Config_getBool("DOT_TRANSPARENT"))
4601 t << " fillcolor=\"white\" style=\"filled\"";
4603 t << " color=\"red\"";
4605 t << " URL=\"" << usedDir->getOutputFileBase()
4606 << Doxygen::htmlFileExtension << "\"];\n";
4607 dirsInGraph.insert(usedDir->getOutputFileBase(),usedDir);
4614 // add relations between all selected directories
4616 QDictIterator<DirDef> di(dirsInGraph);
4617 for (di.toFirst();(dir=di.current());++di) // foreach dir in the graph
4619 QDictIterator<UsedDir> udi(*dir->usedDirs());
4621 for (udi.toFirst();(udir=udi.current());++udi) // foreach used dir
4623 const DirDef *usedDir=udir->dir();
4624 if ((dir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4625 (usedDir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4626 !usedDir->isParentOf(dir) && // don't point to own parent
4627 dirsInGraph.find(usedDir->getOutputFileBase())) // only point to nodes that are in the graph
4629 QCString relationName;
4630 relationName.sprintf("dir_%06d_%06d",dir->dirCount(),usedDir->dirCount());
4631 if (Doxygen::dirRelations.find(relationName)==0)
4634 Doxygen::dirRelations.append(relationName,
4635 new DirRelation(relationName,dir,udir));
4637 int nrefs = udir->filePairs().count();
4638 t << " " << dir->getOutputFileBase() << "->"
4639 << usedDir->getOutputFileBase();
4640 t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5";
4641 t << " headhref=\"" << relationName << Doxygen::htmlFileExtension