summaryrefslogtreecommitdiff
path: root/src/dot.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dot.cpp')
-rw-r--r--src/dot.cpp4648
1 files changed, 4648 insertions, 0 deletions
diff --git a/src/dot.cpp b/src/dot.cpp
new file mode 100644
index 0000000..71a96a1
--- /dev/null
+++ b/src/dot.cpp
@@ -0,0 +1,4648 @@
+/*****************************************************************************
+ *
+ * $Id: dot.cpp,v 1.20 2001/03/19 19:27:40 root Exp $
+ *
+ *
+ * Copyright (C) 1997-2012 by Dimitri van Heesch.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation under the terms of the GNU General Public License is hereby
+ * granted. No representations are made about the suitability of this software
+ * for any purpose. It is provided "as is" without express or implied warranty.
+ * See the GNU General Public License for more details.
+ *
+ * Documents produced by Doxygen are derivative works derived from the
+ * input used in their production; they are not affected by this license.
+ *
+ */
+
+#ifdef _WIN32
+#include <windows.h>
+#define BITMAP W_BITMAP
+#endif
+
+#include <stdlib.h>
+
+#include "dot.h"
+#include "doxygen.h"
+#include "message.h"
+#include "util.h"
+#include "config.h"
+#include "language.h"
+#include "defargs.h"
+#include "docparser.h"
+#include "debug.h"
+#include "pagedef.h"
+#include "portable.h"
+#include "dirdef.h"
+#include "vhdldocgen.h"
+#include <qdir.h>
+#include <qfile.h>
+#include "ftextstream.h"
+#include "md5.h"
+#include <qqueue.h>
+
+#include <qthread.h>
+#include <qmutex.h>
+#include <qwaitcondition.h>
+
+#define MAP_CMD "cmapx"
+
+//#define FONTNAME "Helvetica"
+#define FONTNAME getDotFontName()
+#define FONTSIZE getDotFontSize()
+
+//--------------------------------------------------------------------
+
+static const char svgZoomHeader[] =
+"<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"
+" <defs>\n"
+" <circle id=\"rim\" cx=\"0\" cy=\"0\" r=\"7\"/>\n"
+" <circle id=\"rim2\" cx=\"0\" cy=\"0\" r=\"3.5\"/>\n"
+" <g id=\"zoomPlus\">\n"
+" <use xlink:href=\"#rim\" fill=\"#404040\">\n"
+" <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomplus.mouseover\" end=\"zoomplus.mouseout\"/>\n"
+" </use>\n"
+" <path d=\"M-4,0h8M0,-4v8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
+" </g>\n"
+" <g id=\"zoomMin\">\n"
+" <use xlink:href=\"#rim\" fill=\"#404040\">\n"
+" <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomminus.mouseover\" end=\"zoomminus.mouseout\"/>\n"
+" </use>\n"
+" <path d=\"M-4,0h8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
+" </g>\n"
+" <g id=\"dirArrow\">\n"
+" <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
+" </g>\n"
+" <g id=\"resetDef\">\n"
+" <use xlink:href=\"#rim2\" fill=\"#404040\">\n"
+" <set attributeName=\"fill\" to=\"#808080\" begin=\"reset.mouseover\" end=\"reset.mouseout\"/>\n"
+" </use>\n"
+" </g>\n"
+" </defs>\n"
+"\n"
+"<script type=\"text/javascript\">\n"
+;
+
+static const char svgZoomFooter[] =
+" <g id=\"navigator\" transform=\"translate(0 0)\" fill=\"#404254\">\n"
+" <rect fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\".5\" x=\"0\" y=\"0\" width=\"60\" height=\"60\"/>\n"
+" <use id=\"zoomplus\" xlink:href=\"#zoomPlus\" x=\"17\" y=\"9\" onmousedown=\"handleZoom(evt,'in')\"/>\n"
+" <use id=\"zoomminus\" xlink:href=\"#zoomMin\" x=\"42\" y=\"9\" onmousedown=\"handleZoom(evt,'out')\"/>\n"
+" <use id=\"reset\" xlink:href=\"#resetDef\" x=\"30\" y=\"36\" onmousedown=\"handleReset()\"/>\n"
+" <g id=\"arrowUp\" xlink:href=\"#dirArrow\" transform=\"translate(30 24)\" onmousedown=\"handlePan(0,-1)\">\n"
+" <use xlink:href=\"#rim\" fill=\"#404040\">\n"
+" <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowUp.mouseover\" end=\"arrowUp.mouseout\"/>\n"
+" </use>\n"
+" <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
+" </g>\n"
+" <g id=\"arrowRight\" xlink:href=\"#dirArrow\" transform=\"rotate(90) translate(36 -43)\" onmousedown=\"handlePan(1,0)\">\n"
+" <use xlink:href=\"#rim\" fill=\"#404040\">\n"
+" <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowRight.mouseover\" end=\"arrowRight.mouseout\"/>\n"
+" </use>\n"
+" <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
+" </g>\n"
+" <g id=\"arrowDown\" xlink:href=\"#dirArrow\" transform=\"rotate(180) translate(-30 -48)\" onmousedown=\"handlePan(0,1)\">\n"
+" <use xlink:href=\"#rim\" fill=\"#404040\">\n"
+" <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowDown.mouseover\" end=\"arrowDown.mouseout\"/>\n"
+" </use>\n"
+" <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
+" </g>\n"
+" <g id=\"arrowLeft\" xlink:href=\"#dirArrow\" transform=\"rotate(270) translate(-36 17)\" onmousedown=\"handlePan(-1,0)\">\n"
+" <use xlink:href=\"#rim\" fill=\"#404040\">\n"
+" <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowLeft.mouseover\" end=\"arrowLeft.mouseout\"/>\n"
+" </use>\n"
+" <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
+" </g>\n"
+" </g>\n"
+/*
+" <svg viewBox=\"0 0 25 25\" width=\"100%\" height=\"30px\" preserveAspectRatio=\"xMaxYMin meet\"> \n"
+" <g id=\"printButton\" transform=\"scale(0.4 0.4)\" onmousedown=\"handlePrint(evt)\">\n"
+" <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"
+" <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"
+" <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"
+" </g>\n"
+" </svg>\n"
+*/
+" <svg viewBox=\"0 0 15 15\" width=\"100%\" height=\"30px\" preserveAspectRatio=\"xMaxYMin meet\">\n"
+" <g id=\"arrow_out\" transform=\"scale(0.3 0.3)\">\n"
+" <a xlink:href=\"$orgname\" target=\"_base\">\n"
+" <rect id=\"button\" ry=\"5\" rx=\"5\" y=\"6\" x=\"6\" height=\"38\" width=\"38\"\n"
+" fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\"1.0\"/>\n"
+" <path id=\"arrow\"\n"
+" 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"
+" style=\"fill:#404040;\"/>\n"
+" </a>\n"
+" </g>\n"
+" </svg>\n"
+"</svg>\n"
+;
+
+//--------------------------------------------------------------------
+
+static const int maxCmdLine = 40960;
+
+/*! mapping from protection levels to color names */
+static const char *normalEdgeColorMap[] =
+{
+ "midnightblue", // Public
+ "darkgreen", // Protected
+ "firebrick4", // Private
+ "darkorchid3", // "use" relation
+ "grey75", // Undocumented
+ "orange" // template relation
+};
+
+static const char *normalArrowStyleMap[] =
+{
+ "empty", // Public
+ "empty", // Protected
+ "empty", // Private
+ "open", // "use" relation
+ 0, // Undocumented
+ 0 // template relation
+};
+
+static const char *normalEdgeStyleMap[] =
+{
+ "solid", // inheritance
+ "dashed" // usage
+};
+
+static const char *umlEdgeColorMap[] =
+{
+ "midnightblue", // Public
+ "darkgreen", // Protected
+ "firebrick4", // Private
+ "grey25", // "use" relation
+ "grey75", // Undocumented
+ "orange" // template relation
+};
+
+static const char *umlArrowStyleMap[] =
+{
+ "onormal", // Public
+ "onormal", // Protected
+ "onormal", // Private
+ "odiamond", // "use" relation
+ 0, // Undocumented
+ 0 // template relation
+};
+
+static const char *umlEdgeStyleMap[] =
+{
+ "solid", // inheritance
+ "solid" // usage
+};
+
+/** Helper struct holding the properties of a edge in a dot graph. */
+struct EdgeProperties
+{
+ const char * const *edgeColorMap;
+ const char * const *arrowStyleMap;
+ const char * const *edgeStyleMap;
+};
+
+static EdgeProperties normalEdgeProps =
+{
+ normalEdgeColorMap, normalArrowStyleMap, normalEdgeStyleMap
+};
+
+static EdgeProperties umlEdgeProps =
+{
+ umlEdgeColorMap, umlArrowStyleMap, umlEdgeStyleMap
+};
+
+
+static QCString getDotFontName()
+{
+ static QCString dotFontName = Config_getString("DOT_FONTNAME");
+ if (dotFontName.isEmpty())
+ {
+ //dotFontName="FreeSans.ttf";
+ dotFontName="Helvetica";
+ }
+ return dotFontName;
+}
+
+static int getDotFontSize()
+{
+ static int dotFontSize = Config_getInt("DOT_FONTSIZE");
+ if (dotFontSize<4) dotFontSize=4;
+ return dotFontSize;
+}
+
+static void writeGraphHeader(FTextStream &t,const QCString &title=QCString())
+{
+ static bool interactiveSVG = Config_getBool("INTERACTIVE_SVG");
+ t << "digraph ";
+ if (title.isEmpty())
+ {
+ t << "\"Dot Graph\"";
+ }
+ else
+ {
+ t << "\"" << convertToXML(title) << "\"";
+ }
+ t << endl << "{" << endl;
+ if (interactiveSVG) // insert a comment to force regeneration when this
+ // option is toggled
+ {
+ t << " // INTERACTIVE_SVG=YES\n";
+ }
+ if (Config_getBool("DOT_TRANSPARENT"))
+ {
+ t << " bgcolor=\"transparent\";" << endl;
+ }
+ t << " edge [fontname=\"" << FONTNAME << "\","
+ "fontsize=\"" << FONTSIZE << "\","
+ "labelfontname=\"" << FONTNAME << "\","
+ "labelfontsize=\"" << FONTSIZE << "\"];\n";
+ t << " node [fontname=\"" << FONTNAME << "\","
+ "fontsize=\"" << FONTSIZE << "\",shape=record];\n";
+}
+
+static void writeGraphFooter(FTextStream &t)
+{
+ t << "}" << endl;
+}
+
+static QCString replaceRef(const QCString &buf,const QCString relPath,
+ bool urlOnly,const QCString &context,const QCString &target=QCString())
+{
+ // search for href="...", store ... part in link
+ QCString href = "href";
+ //bool isXLink=FALSE;
+ int len = 6;
+ int indexS = buf.find("href=\""), indexE;
+ if (indexS>5 && buf.find("xlink:href=\"")!=-1) // XLink href (for SVG)
+ {
+ indexS-=6;
+ len+=6;
+ href.prepend("xlink:");
+ //isXLink=TRUE;
+ }
+ if (indexS>=0 && (indexE=buf.find('"',indexS+len))!=-1)
+ {
+ QCString link = buf.mid(indexS+len,indexE-indexS-len);
+ QCString result;
+ if (urlOnly) // for user defined dot graphs
+ {
+ if (link.left(5)=="\\ref " || link.left(5)=="@ref ") // \ref url
+ {
+ result=href+"=\"";
+ // fake ref node to resolve the url
+ DocRef *df = new DocRef( (DocNode*) 0, link.mid(5), context );
+ result+=externalRef(relPath,df->ref(),TRUE);
+ if (!df->file().isEmpty())
+ result += df->file().data() + Doxygen::htmlFileExtension;
+ if (!df->anchor().isEmpty())
+ result += "#" + df->anchor();
+ delete df;
+ result += "\"";
+ }
+ else
+ {
+ result = href+"=\"" + link + "\"";
+ }
+ }
+ else // ref$url (external ref via tag file), or $url (local ref)
+ {
+ int marker = link.find('$');
+ if (marker!=-1)
+ {
+ QCString ref = link.left(marker);
+ QCString url = link.mid(marker+1);
+ if (!ref.isEmpty())
+ {
+ result = externalLinkTarget() + externalRef(relPath,ref,FALSE);
+ }
+ result+= href+"=\"";
+ result+=externalRef(relPath,ref,TRUE);
+ result+= url + "\"";
+ }
+ else // should not happen, but handle properly anyway
+ {
+ result = href+"=\"" + link + "\"";
+ }
+ }
+ if (!target.isEmpty())
+ {
+ result+=" target=\""+target+"\"";
+ }
+ QCString leftPart = buf.left(indexS);
+ QCString rightPart = buf.mid(indexE+1);
+ return leftPart + result + rightPart;
+ }
+ else
+ {
+ return buf;
+ }
+}
+
+/*! converts the rectangles in a client site image map into a stream
+ * \param t the stream to which the result is written.
+ * \param mapName the name of the map file.
+ * \param relPath the relative path to the root of the output directory
+ * (used in case CREATE_SUBDIRS is enabled).
+ * \param urlOnly if FALSE the url field in the map contains an external
+ * references followed by a $ and then the URL.
+ * \param context the context (file, class, or namespace) in which the
+ * map file was found
+ * \returns TRUE if successful.
+ */
+static bool convertMapFile(FTextStream &t,const char *mapName,
+ const QCString relPath, bool urlOnly=FALSE,
+ const QCString &context=QCString())
+{
+ QFile f(mapName);
+ if (!f.open(IO_ReadOnly))
+ {
+ err("error: problems opening map file %s for inclusion in the docs!\n"
+ "If you installed Graphviz/dot after a previous failing run, \n"
+ "try deleting the output directory and rerun doxygen.\n",mapName);
+ return FALSE;
+ }
+ const int maxLineLen=10240;
+ while (!f.atEnd()) // foreach line
+ {
+ QCString buf(maxLineLen);
+ int numBytes = f.readLine(buf.data(),maxLineLen);
+ buf[numBytes-1]='\0';
+
+ if (buf.left(5)=="<area")
+ {
+ t << replaceRef(buf,relPath,urlOnly,context);
+ }
+ }
+ return TRUE;
+}
+
+static QArray<int> s_newNumber;
+static int s_max_newNumber=0;
+
+inline int reNumberNode(int number, bool doReNumbering)
+{
+ if (!doReNumbering)
+ {
+ return number;
+ }
+ else
+ {
+ int s = s_newNumber.size();
+ if (number>=s)
+ {
+ int ns=0;
+ ns = s * 3 / 2 + 5; // new size
+ if (number>=ns) // number still doesn't fit
+ {
+ ns = number * 3 / 2 + 5;
+ }
+ s_newNumber.resize(ns);
+ for (int i=s;i<ns;i++) // clear new part of the array
+ {
+ s_newNumber.at(i)=0;
+ }
+ }
+ int i = s_newNumber.at(number);
+ if (i == 0) // not yet mapped
+ {
+ i = ++s_max_newNumber; // start from 1
+ s_newNumber.at(number) = i;
+ }
+ return i;
+ }
+}
+
+static void resetReNumbering()
+{
+ s_max_newNumber=0;
+ s_newNumber.resize(s_max_newNumber);
+}
+
+static QCString g_dotFontPath;
+
+static void setDotFontPath(const char *path)
+{
+ ASSERT(g_dotFontPath.isEmpty());
+ g_dotFontPath = portable_getenv("DOTFONTPATH");
+ QCString newFontPath = Config_getString("DOT_FONTPATH");
+ QCString spath = path;
+ if (!newFontPath.isEmpty() && !spath.isEmpty())
+ {
+ newFontPath.prepend(spath+portable_pathListSeparator());
+ }
+ else if (newFontPath.isEmpty() && !spath.isEmpty())
+ {
+ newFontPath=path;
+ }
+ else
+ {
+ portable_unsetenv("DOTFONTPATH");
+ return;
+ }
+ portable_setenv("DOTFONTPATH",newFontPath);
+}
+
+static void unsetDotFontPath()
+{
+ if (g_dotFontPath.isEmpty())
+ {
+ portable_unsetenv("DOTFONTPATH");
+ }
+ else
+ {
+ portable_setenv("DOTFONTPATH",g_dotFontPath);
+ }
+ g_dotFontPath="";
+}
+
+static bool readBoundingBox(const char *fileName,int *width,int *height,bool isEps)
+{
+ QCString bb = isEps ? QCString("%%PageBoundingBox:") : QCString("/MediaBox [");
+ QFile f(fileName);
+ if (!f.open(IO_ReadOnly|IO_Raw))
+ {
+ //printf("readBoundingBox: could not open %s\n",fileName);
+ return FALSE;
+ }
+ const int maxLineLen=1024;
+ char buf[maxLineLen];
+ while (!f.atEnd())
+ {
+ int numBytes = f.readLine(buf,maxLineLen-1); // read line
+ if (numBytes>0)
+ {
+ buf[numBytes]='\0';
+ const char *p = strstr(buf,bb);
+ if (p) // found PageBoundingBox or /MediaBox string
+ {
+ int x,y;
+ if (sscanf(p+bb.length(),"%d %d %d %d",&x,&y,width,height)!=4)
+ {
+ //printf("readBoundingBox sscanf fail\n");
+ return FALSE;
+ }
+ return TRUE;
+ }
+ }
+ else // read error!
+ {
+ //printf("Read error %d!\n",numBytes);
+ return FALSE;
+ }
+ }
+ err("Failed to extract bounding box from generated diagram file %s\n",fileName);
+ return FALSE;
+}
+
+static bool writeVecGfxFigure(FTextStream &out,const QCString &baseName,
+ const QCString &figureName)
+{
+ int width=400,height=550;
+ static bool usePdfLatex = Config_getBool("USE_PDFLATEX");
+ if (usePdfLatex)
+ {
+ if (!readBoundingBox(figureName+".pdf",&width,&height,FALSE))
+ {
+ //printf("writeVecGfxFigure()=0\n");
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!readBoundingBox(figureName+".eps",&width,&height,TRUE))
+ {
+ //printf("writeVecGfxFigure()=0\n");
+ return FALSE;
+ }
+ }
+ //printf("Got PDF/EPS size %d,%d\n",width,height);
+ int maxWidth = 350; /* approx. page width in points, excl. margins */
+ int maxHeight = 550; /* approx. page height in points, excl. margins */
+ out << "\\nopagebreak\n"
+ "\\begin{figure}[H]\n"
+ "\\begin{center}\n"
+ "\\leavevmode\n";
+ if (width>maxWidth || height>maxHeight) // figure too big for page
+ {
+ // c*width/maxWidth > c*height/maxHeight, where c=maxWidth*maxHeight>0
+ if (width*maxHeight>height*maxWidth)
+ {
+ out << "\\includegraphics[width=" << maxWidth << "pt]";
+ }
+ else
+ {
+ out << "\\includegraphics[height=" << maxHeight << "pt]";
+ }
+ }
+ else
+ {
+ out << "\\includegraphics[width=" << width << "pt]";
+ }
+
+ out << "{" << baseName << "}\n"
+ "\\end{center}\n"
+ "\\end{figure}\n";
+
+ //printf("writeVecGfxFigure()=1\n");
+ return TRUE;
+}
+
+// extract size from a dot generated SVG file
+static bool readSVGSize(const QCString &fileName,int *width,int *height)
+{
+ bool found=FALSE;
+ QFile f(fileName);
+ if (!f.open(IO_ReadOnly))
+ {
+ return FALSE;
+ }
+ const int maxLineLen=4096;
+ char buf[maxLineLen];
+ while (!f.atEnd() && !found)
+ {
+ int numBytes = f.readLine(buf,maxLineLen-1); // read line
+ if (numBytes>0)
+ {
+ buf[numBytes]='\0';
+ if (strncmp(buf,"<!--zoomable ",13)==0)
+ {
+ *width=-1;
+ *height=-1;
+ sscanf(buf,"<!--zoomable %d",height);
+ //printf("Found zoomable for %s!\n",fileName.data());
+ found=TRUE;
+ }
+ else if (sscanf(buf,"<svg width=\"%dpt\" height=\"%dpt\"",width,height)==2)
+ {
+ //printf("Found fixed size %dx%d for %s!\n",*width,*height,fileName.data());
+ found=TRUE;
+ }
+ }
+ else // read error!
+ {
+ //printf("Read error %d!\n",numBytes);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void writeSVGNotSupported(FTextStream &out)
+{
+ out << "<p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p>";
+}
+
+// check if a reference to a SVG figure can be written and does so if possible.
+// return FALSE if not possible (for instance because the SVG file is not yet generated).
+static bool writeSVGFigureLink(FTextStream &out,const QCString &relPath,
+ const QCString &baseName,const QCString &absImgName)
+{
+ int width=600,height=600;
+ if (!readSVGSize(absImgName,&width,&height))
+ {
+ return FALSE;
+ }
+ if (width==-1)
+ {
+ if (height<=60)
+ height=300;
+ else
+ height+=300; // add some extra space for zooming
+ if (height>600) height=600; // clip to maximum height of 600 pixels
+ out << "<div class=\"zoom\">";
+ //out << "<object type=\"image/svg+xml\" data=\""
+ //out << "<embed type=\"image/svg+xml\" src=\""
+ out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
+ << relPath << baseName << ".svg\" width=\"100%\" height=\"" << height << "\">";
+ }
+ else
+ {
+ //out << "<object type=\"image/svg+xml\" data=\""
+ //out << "<embed type=\"image/svg+xml\" src=\""
+ out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
+ << relPath << baseName << ".svg\" width=\""
+ << ((width*96+48)/72) << "\" height=\""
+ << ((height*96+48)/72) << "\">";
+ }
+ writeSVGNotSupported(out);
+ //out << "</object>";
+ //out << "</embed>";
+ out << "</iframe>";
+ if (width==-1)
+ {
+ out << "</div>";
+ }
+
+ return TRUE;
+}
+
+// since dot silently reproduces the input file when it does not
+// support the PNG format, we need to check the result.
+static void checkDotResult(const QCString &imgName)
+{
+ if (Config_getEnum("DOT_IMAGE_FORMAT")=="png")
+ {
+ FILE *f = fopen(imgName,"rb");
+ if (f)
+ {
+ char data[4];
+ if (fread(data,1,4,f)==4)
+ {
+ if (!(data[1]=='P' && data[2]=='N' && data[3]=='G'))
+ {
+ err("error: Image `%s' produced by dot is not a valid PNG!\n"
+ "You should either select a different format "
+ "(DOT_IMAGE_FORMAT in the config file) or install a more "
+ "recent version of graphviz (1.7+)\n",imgName.data()
+ );
+ }
+ }
+ else
+ {
+ err("error: Could not read image `%s' generated by dot!\n",imgName.data());
+ }
+ fclose(f);
+ }
+ else
+ {
+ err("error: Could not open image `%s' generated by dot!\n",imgName.data());
+ }
+ }
+}
+
+static bool insertMapFile(FTextStream &out,const QCString &mapFile,
+ const QCString &relPath,const QCString &mapLabel)
+{
+ QFileInfo fi(mapFile);
+ if (fi.exists() && fi.size()>0) // reuse existing map file
+ {
+ QGString tmpstr;
+ FTextStream tmpout(&tmpstr);
+ convertMapFile(tmpout,mapFile,relPath);
+ if (!tmpstr.isEmpty())
+ {
+ out << "<map name=\"" << mapLabel << "\" id=\"" << mapLabel << "\">" << endl;
+ out << tmpstr;
+ out << "</map>" << endl;
+ }
+ return TRUE;
+ }
+ return FALSE; // no map file yet, need to generate it
+}
+
+static void removeDotGraph(const QCString &dotName)
+{
+ static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
+ if (dotCleanUp)
+ {
+ QDir d;
+ d.remove(dotName);
+ }
+}
+
+
+
+/*! Checks if a file "baseName".md5 exists. If so the contents
+ * are compared with \a md5. If equal FALSE is returned. If the .md5
+ * file does not exist or its contents are not equal to \a md5,
+ * a new .md5 is generated with the \a md5 string as contents.
+ */
+static bool checkAndUpdateMd5Signature(const QCString &baseName,
+ const QCString &md5)
+{
+ QFile f(baseName+".md5");
+ if (f.open(IO_ReadOnly))
+ {
+ // read checksum
+ QCString md5stored(33);
+ int bytesRead=f.readBlock(md5stored.data(),32);
+ md5stored[32]='\0';
+ // compare checksum
+ if (bytesRead==32 && md5==md5stored)
+ {
+ // bail out if equal
+ return FALSE;
+ }
+ }
+ f.close();
+ // create checksum file
+ if (f.open(IO_WriteOnly))
+ {
+ f.writeBlock(md5.data(),32);
+ f.close();
+ }
+ return TRUE;
+}
+
+static bool checkDeliverables(const QCString &file1,
+ const QCString &file2=QCString())
+{
+ bool file1Ok = TRUE;
+ bool file2Ok = TRUE;
+ if (!file1.isEmpty())
+ {
+ QFileInfo fi(file1);
+ file1Ok = (fi.exists() && fi.size()>0);
+ }
+ if (!file2.isEmpty())
+ {
+ QFileInfo fi(file2);
+ file2Ok = (fi.exists() && fi.size()>0);
+ }
+ return file1Ok && file2Ok;
+}
+
+//--------------------------------------------------------------------
+
+/** Class representing a list of DotNode objects. */
+class DotNodeList : public QList<DotNode>
+{
+ public:
+ DotNodeList() : QList<DotNode>() {}
+ ~DotNodeList() {}
+ int compareItems(GCI item1,GCI item2)
+ {
+ return stricmp(((DotNode *)item1)->m_label,((DotNode *)item2)->m_label);
+ }
+};
+
+//--------------------------------------------------------------------
+
+DotRunner::DotRunner(const QCString &file,const QCString &path,
+ bool checkResult,const QCString &imageName)
+ : m_file(file), m_path(path),
+ m_checkResult(checkResult), m_imageName(imageName)
+{
+ static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
+ m_cleanUp = dotCleanUp;
+ m_jobs.setAutoDelete(TRUE);
+}
+
+void DotRunner::addJob(const char *format,const char *output)
+{
+ QCString args = QCString("-T")+format+" -o \""+output+"\"";
+ m_jobs.append(new QCString(args));
+}
+
+void DotRunner::addPostProcessing(const char *cmd,const char *args)
+{
+ m_postCmd = cmd;
+ m_postArgs = args;
+}
+
+bool DotRunner::run()
+{
+ int exitCode=0;
+ QCString dotExe = Config_getString("DOT_PATH")+"dot";
+ bool multiTargets = Config_getBool("DOT_MULTI_TARGETS");
+ QCString dotArgs;
+ QListIterator<QCString> li(m_jobs);
+ QCString *s;
+ QCString file = m_file;
+ QCString path = m_path;
+ QCString imageName = m_imageName;
+ QCString postCmd = m_postCmd;
+ QCString postArgs = m_postArgs;
+ bool checkResult = m_checkResult;
+ bool cleanUp = m_cleanUp;
+ if (multiTargets)
+ {
+ dotArgs="\""+file+"\"";
+ for (li.toFirst();(s=li.current());++li)
+ {
+ dotArgs+=' ';
+ dotArgs+=*s;
+ }
+ if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
+ {
+ goto error;
+ }
+ }
+ else
+ {
+ for (li.toFirst();(s=li.current());++li)
+ {
+ dotArgs="\""+file+"\" "+*s;
+ if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
+ {
+ goto error;
+ }
+ }
+ }
+ if (!postCmd.isEmpty() && portable_system(postCmd,postArgs)!=0)
+ {
+ err("error: Problems running '%s' as a post-processing step for dot output\n",m_postCmd.data());
+ return FALSE;
+ }
+ if (checkResult) checkDotResult(imageName);
+ if (cleanUp)
+ {
+ //printf("removing dot file %s\n",m_file.data());
+ //QDir(path).remove(file);
+ m_cleanupItem.file = file;
+ m_cleanupItem.path = path;
+ }
+ return TRUE;
+error:
+ err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n",
+ exitCode,dotExe.data(),dotArgs.data());
+ return FALSE;
+}
+
+//--------------------------------------------------------------------
+
+DotFilePatcher::DotFilePatcher(const char *patchFile)
+ : m_patchFile(patchFile)
+{
+ m_maps.setAutoDelete(TRUE);
+}
+
+QCString DotFilePatcher::file() const
+{
+ return m_patchFile;
+}
+
+int DotFilePatcher::addMap(const QCString &mapFile,const QCString &relPath,
+ bool urlOnly,const QCString &context,const QCString &label)
+{
+ int id = m_maps.count();
+ Map *map = new Map;
+ map->mapFile = mapFile;
+ map->relPath = relPath;
+ map->urlOnly = urlOnly;
+ map->context = context;
+ map->label = label;
+ map->zoomable = FALSE;
+ map->graphId = -1;
+ m_maps.append(map);
+ return id;
+}
+
+int DotFilePatcher::addFigure(const QCString &baseName,
+ const QCString &figureName,bool heightCheck)
+{
+ int id = m_maps.count();
+ Map *map = new Map;
+ map->mapFile = figureName;
+ map->urlOnly = heightCheck;
+ map->label = baseName;
+ map->zoomable = FALSE;
+ map->graphId = -1;
+ m_maps.append(map);
+ return id;
+}
+
+int DotFilePatcher::addSVGConversion(const QCString &relPath,bool urlOnly,
+ const QCString &context,bool zoomable,
+ int graphId)
+{
+ int id = m_maps.count();
+ Map *map = new Map;
+ map->relPath = relPath;
+ map->urlOnly = urlOnly;
+ map->context = context;
+ map->zoomable = zoomable;
+ map->graphId = graphId;
+ m_maps.append(map);
+ return id;
+}
+
+int DotFilePatcher::addSVGObject(const QCString &baseName,
+ const QCString &absImgName,
+ const QCString &relPath)
+{
+ int id = m_maps.count();
+ Map *map = new Map;
+ map->mapFile = absImgName;
+ map->relPath = relPath;
+ map->label = baseName;
+ map->zoomable = FALSE;
+ map->graphId = -1;
+ m_maps.append(map);
+ return id;
+}
+
+bool DotFilePatcher::run()
+{
+ //printf("DotFilePatcher::run(): %s\n",m_patchFile.data());
+ static bool interactiveSVG = Config_getBool("INTERACTIVE_SVG");
+ bool isSVGFile = m_patchFile.right(4)==".svg";
+ int graphId = -1;
+ QCString relPath;
+ if (isSVGFile)
+ {
+ Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
+ interactiveSVG = interactiveSVG && map->zoomable;
+ graphId = map->graphId;
+ relPath = map->relPath;
+ //printf("DotFilePatcher::addSVGConversion: file=%s zoomable=%d\n",
+ // m_patchFile.data(),map->zoomable);
+ }
+ QCString tmpName = m_patchFile+".tmp";
+ if (!QDir::current().rename(m_patchFile,tmpName))
+ {
+ err("Failed to rename file %s to %s!\n",m_patchFile.data(),tmpName.data());
+ return FALSE;
+ }
+ QFile fi(tmpName);
+ QFile fo(m_patchFile);
+ if (!fi.open(IO_ReadOnly))
+ {
+ err("error: problem opening file %s for patching!\n",tmpName.data());
+ QDir::current().rename(tmpName,m_patchFile);
+ return FALSE;
+ }
+ if (!fo.open(IO_WriteOnly))
+ {
+ err("error: problem opening file %s for patching!\n",m_patchFile.data());
+ QDir::current().rename(tmpName,m_patchFile);
+ return FALSE;
+ }
+ FTextStream t(&fo);
+ const int maxLineLen=100*1024;
+ int lineNr=1;
+ int width,height;
+ bool insideHeader=FALSE;
+ bool replacedHeader=FALSE;
+ bool foundSize=FALSE;
+ while (!fi.atEnd()) // foreach line
+ {
+ QCString line(maxLineLen);
+ int numBytes = fi.readLine(line.data(),maxLineLen);
+ if (numBytes<=0)
+ {
+ break;
+ }
+
+ //printf("line=[%s]\n",line.stripWhiteSpace().data());
+ int i;
+ ASSERT(numBytes<maxLineLen);
+ if (isSVGFile)
+ {
+ if (interactiveSVG)
+ {
+ if (line.find("<svg")!=-1 && !replacedHeader)
+ {
+ int count;
+ count = sscanf(line.data(),"<svg width=\"%dpt\" height=\"%dpt\"",&width,&height);
+ //printf("width=%d height=%d\n",width,height);
+ foundSize = count==2 && (width>500 || height>450);
+ if (foundSize) insideHeader=TRUE;
+ }
+ else if (insideHeader && !replacedHeader && line.find("<title>")!=-1)
+ {
+ if (foundSize)
+ {
+ // insert special replacement header for interactive SVGs
+ t << "<!--zoomable " << height << " -->\n";
+ t << svgZoomHeader;
+ t << "var viewWidth = " << width << ";\n";
+ t << "var viewHeight = " << height << ";\n";
+ if (graphId>=0)
+ {
+ t << "var sectionId = 'dynsection-" << graphId << "';\n";
+ }
+ t << "</script>\n";
+ t << "<script xlink:href=\"" << relPath << "svgpan.js\"/>\n";
+ t << "<svg id=\"graph\" class=\"graph\">\n";
+ t << "<g id=\"viewport\">\n";
+ }
+ insideHeader=FALSE;
+ replacedHeader=TRUE;
+ }
+ }
+ if (!insideHeader || !foundSize) // copy SVG and replace refs,
+ // unless we are inside the header of the SVG.
+ // Then we replace it with another header.
+ {
+ Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
+ t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
+ }
+ }
+ else if ((i=line.find("<!-- SVG"))!=-1 || (i=line.find("[!-- SVG"))!=-1)
+ {
+ //printf("Found marker at %d\n",i);
+ int mapId=-1;
+ t << line.left(i);
+ int n = sscanf(line.data()+i+1,"!-- SVG %d",&mapId);
+ if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
+ {
+ int e = QMAX(line.find("--]"),line.find("-->"));
+ Map *map = m_maps.at(mapId);
+ //printf("DotFilePatcher::writeSVGFigure: file=%s zoomable=%d\n",
+ // m_patchFile.data(),map->zoomable);
+ if (!writeSVGFigureLink(t,map->relPath,map->label,map->mapFile))
+ {
+ err("Problem extracting size from SVG file %s\n",map->mapFile.data());
+ }
+ if (e!=-1) t << line.mid(e+3);
+ }
+ else // error invalid map id!
+ {
+ err("Found invalid SVG id in file %s!\n",m_patchFile.data());
+ t << line.mid(i);
+ }
+ }
+ else if ((i=line.find("<!-- MAP"))!=-1)
+ {
+ int mapId=-1;
+ t << line.left(i);
+ int n = sscanf(line.data()+i,"<!-- MAP %d",&mapId);
+ if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
+ {
+ Map *map = m_maps.at(mapId);
+ //printf("patching MAP %d in file %s with contents of %s\n",
+ // mapId,m_patchFile.data(),map->mapFile.data());
+ t << "<map name=\"" << map->label << "\" id=\"" << map->label << "\">" << endl;
+ convertMapFile(t,map->mapFile,map->relPath,map->urlOnly,map->context);
+ t << "</map>" << endl;
+ }
+ else // error invalid map id!
+ {
+ err("Found invalid MAP id in file %s!\n",m_patchFile.data());
+ t << line.mid(i);
+ }
+ }
+ else if ((i=line.find("% FIG"))!=-1)
+ {
+ int mapId=-1;
+ int n = sscanf(line.data()+i+2,"FIG %d",&mapId);
+ //printf("line='%s' n=%d\n",line.data()+i,n);
+ if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
+ {
+ Map *map = m_maps.at(mapId);
+ //printf("patching FIG %d in file %s with contents of %s\n",
+ // mapId,m_patchFile.data(),map->mapFile.data());
+ writeVecGfxFigure(t,map->label,map->mapFile);
+ }
+ else // error invalid map id!
+ {
+ err("Found invalid bounding FIG id in file %s!\n",mapId,m_patchFile.data());
+ t << line;
+ }
+ }
+ else
+ {
+ t << line;
+ }
+ lineNr++;
+ }
+ fi.close();
+ if (isSVGFile && interactiveSVG && replacedHeader)
+ {
+ QCString orgName=m_patchFile.left(m_patchFile.length()-4)+"_org.svg";
+ t << substitute(svgZoomFooter,"$orgname",stripPath(orgName));
+ fo.close();
+ // keep original SVG file so we can refer to it, we do need to replace
+ // dummy link by real ones
+ QFile fi(tmpName);
+ QFile fo(orgName);
+ if (!fi.open(IO_ReadOnly))
+ {
+ err("error: problem opening file %s for reading!\n",tmpName.data());
+ return FALSE;
+ }
+ if (!fo.open(IO_WriteOnly))
+ {
+ err("error: problem opening file %s for writing!\n",orgName.data());
+ return FALSE;
+ }
+ FTextStream t(&fo);
+ while (!fi.atEnd()) // foreach line
+ {
+ QCString line(maxLineLen);
+ int numBytes = fi.readLine(line.data(),maxLineLen);
+ if (numBytes<=0)
+ {
+ break;
+ }
+ Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
+ t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
+ }
+ fi.close();
+ fo.close();
+ }
+ // remove temporary file
+ QDir::current().remove(tmpName);
+ return TRUE;
+}
+
+//--------------------------------------------------------------------
+
+void DotRunnerQueue::enqueue(DotRunner *runner)
+{
+ QMutexLocker locker(&m_mutex);
+ m_queue.enqueue(runner);
+ m_bufferNotEmpty.wakeAll();
+}
+
+DotRunner *DotRunnerQueue::dequeue()
+{
+ QMutexLocker locker(&m_mutex);
+ while (m_queue.isEmpty())
+ {
+ // wait until something is added to the queue
+ m_bufferNotEmpty.wait(&m_mutex);
+ }
+ DotRunner *result = m_queue.dequeue();
+ return result;
+}
+
+uint DotRunnerQueue::count() const
+{
+ QMutexLocker locker(&m_mutex);
+ return m_queue.count();
+}
+
+//--------------------------------------------------------------------
+
+DotWorkerThread::DotWorkerThread(int id,DotRunnerQueue *queue)
+ : m_id(id), m_queue(queue)
+{
+ m_cleanupItems.setAutoDelete(TRUE);
+}
+
+void DotWorkerThread::run()
+{
+ DotRunner *runner;
+ while ((runner=m_queue->dequeue()))
+ {
+ runner->run();
+ DotRunner::CleanupItem cleanup = runner->cleanup();
+ if (!cleanup.file.isEmpty())
+ {
+ m_cleanupItems.append(new DotRunner::CleanupItem(cleanup));
+ }
+ }
+}
+
+void DotWorkerThread::cleanup()
+{
+ QListIterator<DotRunner::CleanupItem> it(m_cleanupItems);
+ DotRunner::CleanupItem *ci;
+ for (;(ci=it.current());++it)
+ {
+ QDir(ci->path).remove(ci->file);
+ }
+}
+
+//--------------------------------------------------------------------
+
+DotManager *DotManager::m_theInstance = 0;
+
+DotManager *DotManager::instance()
+{
+ if (!m_theInstance)
+ {
+ m_theInstance = new DotManager;
+ }
+ return m_theInstance;
+}
+
+DotManager::DotManager() : m_dotMaps(1007)
+{
+ m_dotRuns.setAutoDelete(TRUE);
+ m_dotMaps.setAutoDelete(TRUE);
+ m_queue = new DotRunnerQueue;
+ int i;
+ int numThreads = QMIN(32,Config_getInt("DOT_NUM_THREADS"));
+ if (numThreads!=1)
+ {
+ if (numThreads==0) numThreads = QMAX(2,QThread::idealThreadCount()+1);
+ for (i=0;i<numThreads;i++)
+ {
+ DotWorkerThread *thread = new DotWorkerThread(i,m_queue);
+ thread->start();
+ if (thread->isRunning())
+ {
+ m_workers.append(thread);
+ }
+ else // no more threads available!
+ {
+ delete thread;
+ }
+ }
+ ASSERT(m_workers.count()>0);
+ }
+}
+
+DotManager::~DotManager()
+{
+ delete m_queue;
+}
+
+void DotManager::addRun(DotRunner *run)
+{
+ m_dotRuns.append(run);
+}
+
+int DotManager::addMap(const QCString &file,const QCString &mapFile,
+ const QCString &relPath,bool urlOnly,const QCString &context,
+ const QCString &label)
+{
+ DotFilePatcher *map = m_dotMaps.find(file);
+ if (map==0)
+ {
+ map = new DotFilePatcher(file);
+ m_dotMaps.append(file,map);
+ }
+ return map->addMap(mapFile,relPath,urlOnly,context,label);
+}
+
+int DotManager::addFigure(const QCString &file,const QCString &baseName,
+ const QCString &figureName,bool heightCheck)
+{
+ DotFilePatcher *map = m_dotMaps.find(file);
+ if (map==0)
+ {
+ map = new DotFilePatcher(file);
+ m_dotMaps.append(file,map);
+ }
+ return map->addFigure(baseName,figureName,heightCheck);
+}
+
+int DotManager::addSVGConversion(const QCString &file,const QCString &relPath,
+ bool urlOnly,const QCString &context,bool zoomable,
+ int graphId)
+{
+ DotFilePatcher *map = m_dotMaps.find(file);
+ if (map==0)
+ {
+ map = new DotFilePatcher(file);
+ m_dotMaps.append(file,map);
+ }
+ return map->addSVGConversion(relPath,urlOnly,context,zoomable,graphId);
+}
+
+int DotManager::addSVGObject(const QCString &file,const QCString &baseName,
+ const QCString &absImgName,const QCString &relPath)
+{
+ DotFilePatcher *map = m_dotMaps.find(file);
+ if (map==0)
+ {
+ map = new DotFilePatcher(file);
+ m_dotMaps.append(file,map);
+ }
+ return map->addSVGObject(baseName,absImgName,relPath);
+}
+
+bool DotManager::run()
+{
+ uint numDotRuns = m_dotRuns.count();
+ uint numDotMaps = m_dotMaps.count();
+ if (numDotRuns+numDotMaps>1)
+ {
+ if (m_workers.count()==0)
+ {
+ msg("Generating dot graphs in single threaded mode...\n");
+ }
+ else
+ {
+ msg("Generating dot graphs using %d parallel threads...\n",QMIN(numDotRuns+numDotMaps,m_workers.count()));
+ }
+ }
+ int i=1;
+ QListIterator<DotRunner> li(m_dotRuns);
+
+ bool setPath=FALSE;
+ if (Config_getBool("GENERATE_HTML"))
+ {
+ setDotFontPath(Config_getString("HTML_OUTPUT"));
+ setPath=TRUE;
+ }
+ else if (Config_getBool("GENERATE_LATEX"))
+ {
+ setDotFontPath(Config_getString("LATEX_OUTPUT"));
+ setPath=TRUE;
+ }
+ else if (Config_getBool("GENERATE_RTF"))
+ {
+ setDotFontPath(Config_getString("RTF_OUTPUT"));
+ setPath=TRUE;
+ }
+ portable_sysTimerStart();
+ // fill work queue with dot operations
+ DotRunner *dr;
+ int prev=1;
+ if (m_workers.count()==0) // no threads to work with
+ {
+ for (li.toFirst();(dr=li.current());++li)
+ {
+ msg("Running dot for graph %d/%d\n",prev,numDotRuns);
+ dr->run();
+ prev++;
+ }
+ }
+ else // use multiple threads to run instances of dot in parallel
+ {
+ for (li.toFirst();(dr=li.current());++li)
+ {
+ m_queue->enqueue(dr);
+ }
+ // wait for the queue to become empty
+ while ((i=m_queue->count())>0)
+ {
+ i = numDotRuns - i;
+ while (i>=prev)
+ {
+ msg("Running dot for graph %d/%d\n",prev,numDotRuns);
+ prev++;
+ }
+ portable_sleep(100);
+ }
+ while ((int)numDotRuns>=prev)
+ {
+ msg("Running dot for graph %d/%d\n",prev,numDotRuns);
+ prev++;
+ }
+ // signal the workers we are done
+ for (i=0;i<(int)m_workers.count();i++)
+ {
+ m_queue->enqueue(0); // add terminator for each worker
+ }
+ // wait for the workers to finish
+ for (i=0;i<(int)m_workers.count();i++)
+ {
+ m_workers.at(i)->wait();
+ }
+ // clean up dot files from main thread
+ for (i=0;i<(int)m_workers.count();i++)
+ {
+ m_workers.at(i)->cleanup();
+ }
+ }
+ portable_sysTimerStop();
+ if (setPath)
+ {
+ unsetDotFontPath();
+ }
+
+ // patch the output file and insert the maps and figures
+ i=1;
+ SDict<DotFilePatcher>::Iterator di(m_dotMaps);
+ DotFilePatcher *map;
+ // since patching the svg files may involve patching the header of the SVG
+ // (for zoomable SVGs), and patching the .html files requires reading that
+ // header after the SVG is patched, we first process the .svg files and
+ // then the other files.
+ for (di.toFirst();(map=di.current());++di)
+ {
+ if (map->file().right(4)==".svg")
+ {
+ msg("Patching output file %d/%d\n",i,numDotMaps);
+ if (!map->run()) return FALSE;
+ i++;
+ }
+ }
+ for (di.toFirst();(map=di.current());++di)
+ {
+ if (map->file().right(4)!=".svg")
+ {
+ msg("Patching output file %d/%d\n",i,numDotMaps);
+ if (!map->run()) return FALSE;
+ i++;
+ }
+ }
+ return TRUE;
+}
+
+
+//--------------------------------------------------------------------
+
+
+/*! helper function that deletes all nodes in a connected graph, given
+ * one of the graph's nodes
+ */
+static void deleteNodes(DotNode *node,SDict<DotNode> *skipNodes=0)
+{
+ //printf("deleteNodes skipNodes=%p\n",skipNodes);
+ static DotNodeList deletedNodes;
+ deletedNodes.setAutoDelete(TRUE);
+ node->deleteNode(deletedNodes,skipNodes); // collect nodes to be deleted.
+ deletedNodes.clear(); // actually remove the nodes.
+}
+
+DotNode::DotNode(int n,const char *lab,const char *tip, const char *url,
+ bool isRoot,ClassDef *cd)
+ : m_subgraphId(-1)
+ , m_number(n)
+ , m_label(lab)
+ , m_tooltip(tip)
+ , m_url(url)
+ , m_parents(0)
+ , m_children(0)
+ , m_edgeInfo(0)
+ , m_deleted(FALSE)
+ , m_written(FALSE)
+ , m_hasDoc(FALSE)
+ , m_isRoot(isRoot)
+ , m_classDef(cd)
+ , m_visible(FALSE)
+ , m_truncated(Unknown)
+ , m_distance(1000)
+{
+}
+
+DotNode::~DotNode()
+{
+ delete m_children;
+ delete m_parents;
+ delete m_edgeInfo;
+}
+
+void DotNode::addChild(DotNode *n,
+ int edgeColor,
+ int edgeStyle,
+ const char *edgeLab,
+ const char *edgeURL,
+ int edgeLabCol
+ )
+{
+ if (m_children==0)
+ {
+ m_children = new QList<DotNode>;
+ m_edgeInfo = new QList<EdgeInfo>;
+ m_edgeInfo->setAutoDelete(TRUE);
+ }
+ m_children->append(n);
+ EdgeInfo *ei = new EdgeInfo;
+ ei->m_color = edgeColor;
+ ei->m_style = edgeStyle;
+ ei->m_label = edgeLab;
+ ei->m_url = edgeURL;
+ if (edgeLabCol==-1)
+ ei->m_labColor=edgeColor;
+ else
+ ei->m_labColor=edgeLabCol;
+ m_edgeInfo->append(ei);
+}
+
+void DotNode::addParent(DotNode *n)
+{
+ if (m_parents==0)
+ {
+ m_parents = new QList<DotNode>;
+ }
+ m_parents->append(n);
+}
+
+void DotNode::removeChild(DotNode *n)
+{
+ if (m_children) m_children->remove(n);
+}
+
+void DotNode::removeParent(DotNode *n)
+{
+ if (m_parents) m_parents->remove(n);
+}
+
+void DotNode::deleteNode(DotNodeList &deletedList,SDict<DotNode> *skipNodes)
+{
+ if (m_deleted) return; // avoid recursive loops in case the graph has cycles
+ m_deleted=TRUE;
+ if (m_parents!=0) // delete all parent nodes of this node
+ {
+ QListIterator<DotNode> dnlip(*m_parents);
+ DotNode *pn;
+ for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
+ {
+ //pn->removeChild(this);
+ pn->deleteNode(deletedList,skipNodes);
+ }
+ }
+ if (m_children!=0) // delete all child nodes of this node
+ {
+ QListIterator<DotNode> dnlic(*m_children);
+ DotNode *cn;
+ for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
+ {
+ //cn->removeParent(this);
+ cn->deleteNode(deletedList,skipNodes);
+ }
+ }
+ // add this node to the list of deleted nodes.
+ //printf("skipNodes=%p find(%p)=%p\n",skipNodes,this,skipNodes ? skipNodes->find((int)this) : 0);
+ if (skipNodes==0 || skipNodes->find((char*)this)==0)
+ {
+ //printf("deleting\n");
+ deletedList.append(this);
+ }
+}
+
+void DotNode::setDistance(int distance)
+{
+ if (distance<m_distance) m_distance = distance;
+}
+
+static QCString convertLabel(const QCString &l)
+{
+ QCString result;
+ QCString bBefore("\\_/<({[: =-+@%#~?$"); // break before character set
+ QCString bAfter(">]),:;|"); // break after character set
+ const char *p=l.data();
+ if (p==0) return result;
+ char c,pc=0;
+ char cs[2];
+ cs[1]=0;
+ int len=l.length();
+ int charsLeft=len;
+ int sinceLast=0;
+ int foldLen=17; // ideal text length
+ while ((c=*p++))
+ {
+ QCString replacement;
+ switch(c)
+ {
+ case '\\': replacement="\\\\"; break;
+ case '\n': replacement="\\n"; break;
+ case '<': replacement="\\<"; break;
+ case '>': replacement="\\>"; break;
+ case '|': replacement="\\|"; break;
+ case '{': replacement="\\{"; break;
+ case '}': replacement="\\}"; break;
+ case '"': replacement="\\\""; break;
+ default: cs[0]=c; replacement=cs; break;
+ }
+ // Some heuristics to insert newlines to prevent too long
+ // boxes and at the same time prevent ugly breaks
+ if (c=='\n')
+ {
+ result+=replacement;
+ foldLen = (3*foldLen+sinceLast+2)/4;
+ sinceLast=1;
+ }
+ else if ((pc!=':' || c!=':') && charsLeft>foldLen/3 && sinceLast>foldLen && bBefore.contains(c))
+ {
+ result+="\\l";
+ result+=replacement;
+ foldLen = (foldLen+sinceLast+1)/2;
+ sinceLast=1;
+ }
+ else if (charsLeft>1+foldLen/4 && sinceLast>foldLen+foldLen/3 &&
+ !isupper(c) && isupper(*p))
+ {
+ result+=replacement;
+ result+="\\l";
+ foldLen = (foldLen+sinceLast+1)/2;
+ sinceLast=0;
+ }
+ else if (charsLeft>foldLen/3 && sinceLast>foldLen && bAfter.contains(c) && (c!=':' || *p!=':'))
+ {
+ result+=replacement;
+ result+="\\l";
+ foldLen = (foldLen+sinceLast+1)/2;
+ sinceLast=0;
+ }
+ else
+ {
+ result+=replacement;
+ sinceLast++;
+ }
+ charsLeft--;
+ pc=c;
+ }
+ return result;
+}
+
+static QCString escapeTooltip(const QCString &tooltip)
+{
+ QCString result;
+ const char *p=tooltip.data();
+ if (p==0) return result;
+ char c;
+ while ((c=*p++))
+ {
+ switch(c)
+ {
+ case '"': result+="\\\""; break;
+ default: result+=c; break;
+ }
+ }
+ return result;
+}
+
+static void writeBoxMemberList(FTextStream &t,
+ char prot,MemberList *ml,ClassDef *scope,
+ bool isStatic=FALSE,const QDict<void> *skipNames=0)
+{
+ (void)isStatic;
+ if (ml)
+ {
+ MemberListIterator mlia(*ml);
+ MemberDef *mma;
+ int totalCount=0;
+ for (mlia.toFirst();(mma = mlia.current());++mlia)
+ {
+ if (mma->getClassDef()==scope &&
+ (skipNames==0 || skipNames->find(mma->name())==0))
+ {
+ totalCount++;
+ }
+ }
+
+ int count=0;
+ for (mlia.toFirst();(mma = mlia.current());++mlia)
+ {
+ if (mma->getClassDef() == scope &&
+ (skipNames==0 || skipNames->find(mma->name())==0))
+ {
+ static int limit = Config_getInt("UML_LIMIT_NUM_FIELDS");
+ if (limit>0 && (totalCount>limit*3/2 && count>=limit))
+ {
+ t << theTranslator->trAndMore(QCString().sprintf("%d",totalCount-count)) << "\\l";
+ break;
+ }
+ else
+ {
+ t << prot << " ";
+ t << convertLabel(mma->name());
+ if (!mma->isObjCMethod() &&
+ (mma->isFunction() || mma->isSlot() || mma->isSignal())) t << "()";
+ t << "\\l";
+ count++;
+ }
+ }
+ }
+ // write member groups within the memberlist
+ MemberGroupList *mgl = ml->getMemberGroupList();
+ if (mgl)
+ {
+ MemberGroupListIterator mgli(*mgl);
+ MemberGroup *mg;
+ for (mgli.toFirst();(mg=mgli.current());++mgli)
+ {
+ if (mg->members())
+ {
+ writeBoxMemberList(t,prot,mg->members(),scope,isStatic,skipNames);
+ }
+ }
+ }
+ }
+}
+
+void DotNode::writeBox(FTextStream &t,
+ GraphType gt,
+ GraphOutputFormat /*format*/,
+ bool hasNonReachableChildren,
+ bool reNumber)
+{
+ const char *labCol =
+ m_url.isEmpty() ? "grey75" : // non link
+ (
+ (hasNonReachableChildren) ? "red" : "black"
+ );
+ t << " Node" << reNumberNode(m_number,reNumber) << " [label=\"";
+ static bool umlLook = Config_getBool("UML_LOOK");
+
+ if (m_classDef && umlLook && (gt==Inheritance || gt==Collaboration))
+ {
+ // add names shown as relation to a dictionary, so we don't show
+ // them as attributes as well
+ QDict<void> arrowNames(17);
+ if (m_edgeInfo)
+ {
+ QListIterator<EdgeInfo> li(*m_edgeInfo);
+ EdgeInfo *ei;
+ for (li.toFirst();(ei=li.current());++li)
+ {
+ if (!ei->m_label.isEmpty())
+ {
+ arrowNames.insert(ei->m_label,(void*)0x8);
+ }
+ }
+ }
+
+ //printf("DotNode::writeBox for %s\n",m_classDef->name().data());
+ static bool extractPrivate = Config_getBool("EXTRACT_PRIVATE");
+ t << "{" << convertLabel(m_label);
+ t << "\\n|";
+ writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubAttribs),m_classDef,FALSE,&arrowNames);
+ writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticAttribs),m_classDef,TRUE,&arrowNames);
+ writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::properties),m_classDef,FALSE,&arrowNames);
+ writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacAttribs),m_classDef,FALSE,&arrowNames);
+ writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticAttribs),m_classDef,TRUE,&arrowNames);
+ writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proAttribs),m_classDef,FALSE,&arrowNames);
+ writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticAttribs),m_classDef,TRUE,&arrowNames);
+ if (extractPrivate)
+ {
+ writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priAttribs),m_classDef,FALSE,&arrowNames);
+ writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticAttribs),m_classDef,TRUE,&arrowNames);
+ }
+ t << "|";
+ writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubMethods),m_classDef);
+ writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticMethods),m_classDef,TRUE);
+ writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubSlots),m_classDef);
+ writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacMethods),m_classDef);
+ writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticMethods),m_classDef,TRUE);
+ writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proMethods),m_classDef);
+ writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticMethods),m_classDef,TRUE);
+ writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proSlots),m_classDef);
+ if (extractPrivate)
+ {
+ writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priMethods),m_classDef);
+ writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticMethods),m_classDef,TRUE);
+ writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priSlots),m_classDef);
+ }
+ if (m_classDef->getLanguage()!=SrcLangExt_Fortran &&
+ m_classDef->getMemberGroupSDict())
+ {
+ MemberGroupSDict::Iterator mgdi(*m_classDef->getMemberGroupSDict());
+ MemberGroup *mg;
+ for (mgdi.toFirst();(mg=mgdi.current());++mgdi)
+ {
+ if (mg->members())
+ {
+ writeBoxMemberList(t,'*',mg->members(),m_classDef,FALSE,&arrowNames);
+ }
+ }
+ }
+ t << "}";
+ }
+ else // standard look
+ {
+ t << convertLabel(m_label);
+ }
+ t << "\",height=0.2,width=0.4";
+ if (m_isRoot)
+ {
+ t << ",color=\"black\", fillcolor=\"grey75\", style=\"filled\" fontcolor=\"black\"";
+ }
+ else
+ {
+ static bool dotTransparent = Config_getBool("DOT_TRANSPARENT");
+ static bool vhdlOpt = Config_getBool("OPTIMIZE_OUTPUT_VHDL");
+ if (!dotTransparent)
+ {
+ ClassDef* ccd=m_classDef;
+
+ t << ",color=\"" << labCol << "\", fillcolor=\"";
+ if (ccd && vhdlOpt && (VhdlDocGen::VhdlClasses)ccd->protection()==VhdlDocGen::ARCHITECTURECLASS)
+ t << "khaki";
+ else
+ t << "white";
+ t << "\", style=\"filled\"";
+ }
+ else
+ {
+ t << ",color=\"" << labCol << "\"";
+ }
+ if (!m_url.isEmpty())
+ {
+ int anchorPos = m_url.findRev('#');
+ if (anchorPos==-1)
+ {
+ t << ",URL=\"" << m_url << Doxygen::htmlFileExtension << "\"";
+ }
+ else
+ {
+ t << ",URL=\"" << m_url.left(anchorPos) << Doxygen::htmlFileExtension
+ << m_url.right(m_url.length()-anchorPos) << "\"";
+ }
+ }
+ if (!m_tooltip.isEmpty())
+ {
+ t << ",tooltip=\"" << escapeTooltip(m_tooltip) << "\"";
+ }
+ }
+ t << "];" << endl;
+}
+
+void DotNode::writeArrow(FTextStream &t,
+ GraphType gt,
+ GraphOutputFormat format,
+ DotNode *cn,
+ EdgeInfo *ei,
+ bool topDown,
+ bool pointBack,
+ bool reNumber
+ )
+{
+ t << " Node";
+ if (topDown)
+ t << reNumberNode(cn->number(),reNumber);
+ else
+ t << reNumberNode(m_number,reNumber);
+ t << " -> Node";
+ if (topDown)
+ t << reNumberNode(m_number,reNumber);
+ else
+ t << reNumberNode(cn->number(),reNumber);
+ t << " [";
+
+ static bool umlLook = Config_getBool("UML_LOOK");
+ const EdgeProperties *eProps = umlLook ? &umlEdgeProps : &normalEdgeProps;
+ QCString aStyle = eProps->arrowStyleMap[ei->m_color];
+ bool umlUseArrow = aStyle=="odiamond";
+
+ if (pointBack && !umlUseArrow) t << "dir=\"back\",";
+ t << "color=\"" << eProps->edgeColorMap[ei->m_color]
+ << "\",fontsize=\"" << FONTSIZE << "\",";
+ t << "style=\"" << eProps->edgeStyleMap[ei->m_style] << "\"";
+ if (!ei->m_label.isEmpty())
+ {
+ t << ",label=\" " << convertLabel(ei->m_label) << "\" ";
+ }
+ if (umlLook &&
+ eProps->arrowStyleMap[ei->m_color] &&
+ (gt==Inheritance || gt==Collaboration)
+ )
+ {
+ bool rev = pointBack;
+ if (umlUseArrow) rev=!rev; // UML use relates has arrow on the start side
+ if (rev)
+ t << ",arrowtail=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
+ else
+ t << ",arrowhead=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
+ }
+
+ if (format==BITMAP) t << ",fontname=\"" << FONTNAME << "\"";
+ t << "];" << endl;
+}
+
+void DotNode::write(FTextStream &t,
+ GraphType gt,
+ GraphOutputFormat format,
+ bool topDown,
+ bool toChildren,
+ bool backArrows,
+ bool reNumber
+ )
+{
+ //printf("DotNode::write(%d) name=%s this=%p written=%d\n",distance,m_label.data(),this,m_written);
+ if (m_written) return; // node already written to the output
+ if (!m_visible) return; // node is not visible
+ writeBox(t,gt,format,m_truncated==Truncated,reNumber);
+ m_written=TRUE;
+ QList<DotNode> *nl = toChildren ? m_children : m_parents;
+ if (nl)
+ {
+ if (toChildren)
+ {
+ QListIterator<DotNode> dnli1(*nl);
+ QListIterator<EdgeInfo> dnli2(*m_edgeInfo);
+ DotNode *cn;
+ for (dnli1.toFirst();(cn=dnli1.current());++dnli1,++dnli2)
+ {
+ if (cn->isVisible())
+ {
+ //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",cn->label().data());
+ writeArrow(t,gt,format,cn,dnli2.current(),topDown,backArrows,reNumber);
+ }
+ cn->write(t,gt,format,topDown,toChildren,backArrows,reNumber);
+ }
+ }
+ else // render parents
+ {
+ QListIterator<DotNode> dnli(*nl);
+ DotNode *pn;
+ for (dnli.toFirst();(pn=dnli.current());++dnli)
+ {
+ if (pn->isVisible())
+ {
+ //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",pn->label().data());
+ writeArrow(t,
+ gt,
+ format,
+ pn,
+ pn->m_edgeInfo->at(pn->m_children->findRef(this)),
+ FALSE,
+ backArrows,
+ reNumber
+ );
+ }
+ pn->write(t,gt,format,TRUE,FALSE,backArrows,reNumber);
+ }
+ }
+ }
+ //printf("end DotNode::write(%d) name=%s\n",distance,m_label.data());
+}
+
+void DotNode::writeXML(FTextStream &t,bool isClassGraph)
+{
+ t << " <node id=\"" << m_number << "\">" << endl;
+ t << " <label>" << convertToXML(m_label) << "</label>" << endl;
+ if (!m_url.isEmpty())
+ {
+ QCString url(m_url);
+ char *refPtr = url.data();
+ char *urlPtr = strchr(url.data(),'$');
+ if (urlPtr)
+ {
+ *urlPtr++='\0';
+ t << " <link refid=\"" << convertToXML(urlPtr) << "\"";
+ if (*refPtr!='\0')
+ {
+ t << " external=\"" << convertToXML(refPtr) << "\"";
+ }
+ t << "/>" << endl;
+ }
+ }
+ if (m_children)
+ {
+ QListIterator<DotNode> nli(*m_children);
+ QListIterator<EdgeInfo> eli(*m_edgeInfo);
+ DotNode *childNode;
+ EdgeInfo *edgeInfo;
+ for (;(childNode=nli.current());++nli,++eli)
+ {
+ edgeInfo=eli.current();
+ t << " <childnode refid=\"" << childNode->m_number << "\" relation=\"";
+ if (isClassGraph)
+ {
+ switch(edgeInfo->m_color)
+ {
+ case EdgeInfo::Blue: t << "public-inheritance"; break;
+ case EdgeInfo::Green: t << "protected-inheritance"; break;
+ case EdgeInfo::Red: t << "private-inheritance"; break;
+ case EdgeInfo::Purple: t << "usage"; break;
+ case EdgeInfo::Orange: t << "template-instance"; break;
+ case EdgeInfo::Grey: ASSERT(0); break;
+ }
+ }
+ else // include graph
+ {
+ t << "include";
+ }
+ t << "\">" << endl;
+ if (!edgeInfo->m_label.isEmpty())
+ {
+ int p=0;
+ int ni;
+ while ((ni=edgeInfo->m_label.find('\n',p))!=-1)
+ {
+ t << " <edgelabel>"
+ << convertToXML(edgeInfo->m_label.mid(p,ni-p))
+ << "</edgelabel>" << endl;
+ p=ni+1;
+ }
+ t << " <edgelabel>"
+ << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p))
+ << "</edgelabel>" << endl;
+ }
+ t << " </childnode>" << endl;
+ }
+ }
+ t << " </node>" << endl;
+}
+
+
+void DotNode::writeDEF(FTextStream &t)
+{
+ const char* nodePrefix = " node-";
+
+ t << " node = {" << endl;
+ t << nodePrefix << "id = " << m_number << ';' << endl;
+ t << nodePrefix << "label = '" << m_label << "';" << endl;
+
+ if (!m_url.isEmpty())
+ {
+ QCString url(m_url);
+ char *refPtr = url.data();
+ char *urlPtr = strchr(url.data(),'$');
+ if (urlPtr)
+ {
+ *urlPtr++='\0';
+ t << nodePrefix << "link = {" << endl << " "
+ << nodePrefix << "link-id = '" << urlPtr << "';" << endl;
+
+ if (*refPtr!='\0')
+ {
+ t << " " << nodePrefix << "link-external = '"
+ << refPtr << "';" << endl;
+ }
+ t << " };" << endl;
+ }
+ }
+ if (m_children)
+ {
+ QListIterator<DotNode> nli(*m_children);
+ QListIterator<EdgeInfo> eli(*m_edgeInfo);
+ DotNode *childNode;
+ EdgeInfo *edgeInfo;
+ for (;(childNode=nli.current());++nli,++eli)
+ {
+ edgeInfo=eli.current();
+ t << " node-child = {" << endl;
+ t << " child-id = '" << childNode->m_number << "';" << endl;
+ t << " relation = ";
+
+ switch(edgeInfo->m_color)
+ {
+ case EdgeInfo::Blue: t << "public-inheritance"; break;
+ case EdgeInfo::Green: t << "protected-inheritance"; break;
+ case EdgeInfo::Red: t << "private-inheritance"; break;
+ case EdgeInfo::Purple: t << "usage"; break;
+ case EdgeInfo::Orange: t << "template-instance"; break;
+ case EdgeInfo::Grey: ASSERT(0); break;
+ }
+ t << ';' << endl;
+
+ if (!edgeInfo->m_label.isEmpty())
+ {
+ t << " edgelabel = <<_EnD_oF_dEf_TeXt_" << endl
+ << edgeInfo->m_label << endl
+ << "_EnD_oF_dEf_TeXt_;" << endl;
+ }
+ t << " }; /* node-child */" << endl;
+ } /* for (;childNode...) */
+ }
+ t << " }; /* node */" << endl;
+}
+
+
+void DotNode::clearWriteFlag()
+{
+ m_written=FALSE;
+ if (m_parents!=0)
+ {
+ QListIterator<DotNode> dnlip(*m_parents);
+ DotNode *pn;
+ for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
+ {
+ if (pn->m_written)
+ {
+ pn->clearWriteFlag();
+ }
+ }
+ }
+ if (m_children!=0)
+ {
+ QListIterator<DotNode> dnlic(*m_children);
+ DotNode *cn;
+ for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
+ {
+ if (cn->m_written)
+ {
+ cn->clearWriteFlag();
+ }
+ }
+ }
+}
+
+void DotNode::colorConnectedNodes(int curColor)
+{
+ if (m_children)
+ {
+ QListIterator<DotNode> dnlic(*m_children);
+ DotNode *cn;
+ for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
+ {
+ if (cn->m_subgraphId==-1) // uncolored child node
+ {
+ cn->m_subgraphId=curColor;
+ cn->markAsVisible();
+ cn->colorConnectedNodes(curColor);
+ //printf("coloring node %s (%p): %d\n",cn->m_label.data(),cn,cn->m_subgraphId);
+ }
+ }
+ }
+
+ if (m_parents)
+ {
+ QListIterator<DotNode> dnlip(*m_parents);
+ DotNode *pn;
+ for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
+ {
+ if (pn->m_subgraphId==-1) // uncolored parent node
+ {
+ pn->m_subgraphId=curColor;
+ pn->markAsVisible();
+ pn->colorConnectedNodes(curColor);
+ //printf("coloring node %s (%p): %d\n",pn->m_label.data(),pn,pn->m_subgraphId);
+ }
+ }
+ }
+}
+
+const DotNode *DotNode::findDocNode() const
+{
+ if (!m_url.isEmpty()) return this;
+ //printf("findDocNode(): `%s'\n",m_label.data());
+ if (m_parents)
+ {
+ QListIterator<DotNode> dnli(*m_parents);
+ DotNode *pn;
+ for (dnli.toFirst();(pn=dnli.current());++dnli)
+ {
+ if (!pn->m_hasDoc)
+ {
+ pn->m_hasDoc=TRUE;
+ const DotNode *dn = pn->findDocNode();
+ if (dn) return dn;
+ }
+ }
+ }
+ if (m_children)
+ {
+ QListIterator<DotNode> dnli(*m_children);
+ DotNode *cn;
+ for (dnli.toFirst();(cn=dnli.current());++dnli)
+ {
+ if (!cn->m_hasDoc)
+ {
+ cn->m_hasDoc=TRUE;
+ const DotNode *dn = cn->findDocNode();
+ if (dn) return dn;
+ }
+ }
+ }
+ return 0;
+}
+
+//--------------------------------------------------------------------
+
+int DotGfxHierarchyTable::m_curNodeNumber;
+
+void DotGfxHierarchyTable::writeGraph(FTextStream &out,
+ const char *path,const char *fileName) const
+{
+ //printf("DotGfxHierarchyTable::writeGraph(%s)\n",name);
+ //printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count());
+
+ static bool vhdl = Config_getBool("OPTIMIZE_OUTPUT_VHDL");
+
+ if (m_rootSubgraphs->count()==0) return;
+
+ QDir d(path);
+ // store the original directory
+ if (!d.exists())
+ {
+ err("error: Output dir %s does not exist!\n",path); exit(1);
+ }
+
+ // put each connected subgraph of the hierarchy in a row of the HTML output
+ out << "<table border=\"0\" cellspacing=\"10\" cellpadding=\"0\">" << endl;
+
+ QListIterator<DotNode> dnli(*m_rootSubgraphs);
+ DotNode *n;
+ int count=0;
+ for (dnli.toFirst();(n=dnli.current());++dnli)
+ {
+ QCString baseName;
+
+ if (vhdl)
+ {
+ QCString l=n->m_url;
+ l=VhdlDocGen::convertFileNameToClassName(l);
+ ClassDef *cd=Doxygen::classSDict->find(l);
+ if (cd==0) continue;
+ // only entities are shown
+ if ((VhdlDocGen::VhdlClasses)cd->protection()!=VhdlDocGen::ENTITYCLASS)
+ continue;
+ }
+
+ QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
+ baseName.sprintf("inherit_graph_%d",count++);
+ //baseName = convertNameToFile(baseName);
+ QCString imgName = baseName+"."+ imgExt;
+ QCString mapName = baseName+".map";
+ QCString absImgName = QCString(d.absPath().data())+"/"+imgName;
+ QCString absMapName = QCString(d.absPath().data())+"/"+mapName;
+ QCString absBaseName = QCString(d.absPath().data())+"/"+baseName;
+ QListIterator<DotNode> dnli2(*m_rootNodes);
+ DotNode *node;
+
+ // compute md5 checksum of the graph were are about to generate
+ QGString theGraph;
+ FTextStream md5stream(&theGraph);
+ writeGraphHeader(md5stream,theTranslator->trGraphicalHierarchy());
+ md5stream << " rankdir=\"LR\";" << endl;
+ for (dnli2.toFirst();(node=dnli2.current());++dnli2)
+ {
+ if (node->m_subgraphId==n->m_subgraphId)
+ {
+ node->clearWriteFlag();
+ }
+ }
+ for (dnli2.toFirst();(node=dnli2.current());++dnli2)
+ {
+ if (node->m_subgraphId==n->m_subgraphId)
+ {
+ node->write(md5stream,DotNode::Hierarchy,BITMAP,FALSE,TRUE,TRUE,TRUE);
+ }
+ }
+ writeGraphFooter(md5stream);
+ resetReNumbering();
+ uchar md5_sig[16];
+ QCString sigStr(33);
+ MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
+ MD5SigToString(md5_sig,sigStr.data(),33);
+ bool regenerate=FALSE;
+ if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
+ !checkDeliverables(absImgName,absMapName))
+ {
+ regenerate=TRUE;
+ // image was new or has changed
+ QCString dotName=absBaseName+".dot";
+ QFile f(dotName);
+ if (!f.open(IO_WriteOnly)) return;
+ FTextStream t(&f);
+ t << theGraph;
+ f.close();
+ resetReNumbering();
+
+ DotRunner *dotRun = new DotRunner(dotName,d.absPath().data(),TRUE,absImgName);
+ dotRun->addJob(imgExt,absImgName);
+ dotRun->addJob(MAP_CMD,absMapName);
+ DotManager::instance()->addRun(dotRun);
+ }
+ else
+ {
+ removeDotGraph(absBaseName+".dot");
+ }
+ Doxygen::indexList.addImageFile(imgName);
+ // write image and map in a table row
+ QCString mapLabel = escapeCharsInString(n->m_label,FALSE);
+ out << "<tr><td>";
+ if (imgExt=="svg") // vector graphics
+ {
+ if (regenerate || !writeSVGFigureLink(out,QCString(),baseName,absImgName))
+ {
+ if (regenerate)
+ {
+ DotManager::instance()->addSVGConversion(absImgName,QCString(),
+ FALSE,QCString(),FALSE,0);
+ }
+ int mapId = DotManager::instance()->addSVGObject(fileName,baseName,
+ absImgName,QCString());
+ out << "<!-- SVG " << mapId << " -->" << endl;
+ }
+ }
+ else // normal bitmap
+ {
+ out << "<img src=\"" << imgName << "\" border=\"0\" alt=\"\" usemap=\"#"
+ << mapLabel << "\"/>" << endl;
+
+ if (regenerate || !insertMapFile(out,absMapName,QCString(),mapLabel))
+ {
+ int mapId = DotManager::instance()->addMap(fileName,absMapName,QCString(),
+ FALSE,QCString(),mapLabel);
+ out << "<!-- MAP " << mapId << " -->" << endl;
+ }
+ }
+
+ out << "</td></tr>" << endl;
+ }
+ out << "</table>" << endl;
+}
+
+void DotGfxHierarchyTable::addHierarchy(DotNode *n,ClassDef *cd,bool hideSuper)
+{
+ //printf("addHierarchy `%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count());
+ if (cd->subClasses())
+ {
+ BaseClassListIterator bcli(*cd->subClasses());
+ BaseClassDef *bcd;
+ for ( ; (bcd=bcli.current()) ; ++bcli )
+ {
+ ClassDef *bClass=bcd->classDef;
+ //printf(" Trying sub class=`%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count());
+ if (bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses()))
+ {
+ DotNode *bn;
+ //printf(" Node `%s' Found visible class=`%s'\n",n->m_label.data(),
+ // bClass->name().data());
+ if ((bn=m_usedNodes->find(bClass->name()))) // node already present
+ {
+ if (n->m_children==0 || n->m_children->findRef(bn)==-1) // no arrow yet
+ {
+ n->addChild(bn,bcd->prot);
+ bn->addParent(n);
+ //printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n",
+ // n->m_label.data(),
+ // bn->m_label.data(),
+ // bn->m_children ? bn->m_children->count() : 0,
+ // bn->m_parents ? bn->m_parents->count() : 0
+ // );
+ }
+ //else
+ //{
+ // printf(" Class already has an arrow!\n");
+ //}
+ }
+ else
+ {
+ QCString tmp_url="";
+ if (bClass->isLinkable() && !bClass->isHidden())
+ {
+ tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase();
+ if (!bClass->anchor().isEmpty())
+ {
+ tmp_url+="#"+bClass->anchor();
+ }
+ }
+ QCString tooltip = bClass->briefDescriptionAsTooltip();
+ bn = new DotNode(m_curNodeNumber++,
+ bClass->displayName(),
+ tooltip,
+ tmp_url.data()
+ );
+ n->addChild(bn,bcd->prot);
+ bn->addParent(n);
+ //printf(" Adding node %s to new base node %s (c=%d,p=%d)\n",
+ // n->m_label.data(),
+ // bn->m_label.data(),
+ // bn->m_children ? bn->m_children->count() : 0,
+ // bn->m_parents ? bn->m_parents->count() : 0
+ // );
+ //printf(" inserting %s (%p)\n",bClass->name().data(),bn);
+ m_usedNodes->insert(bClass->name(),bn); // add node to the used list
+ }
+ if (!bClass->visited && !hideSuper && bClass->subClasses())
+ {
+ bool wasVisited=bClass->visited;
+ bClass->visited=TRUE;
+ addHierarchy(bn,bClass,wasVisited);
+ }
+ }
+ }
+ }
+ //printf("end addHierarchy\n");
+}
+
+void DotGfxHierarchyTable::addClassList(ClassSDict *cl)
+{
+ ClassSDict::Iterator cli(*cl);
+ ClassDef *cd;
+ for (cli.toLast();(cd=cli.current());--cli)
+ {
+ //printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count());
+ if (!hasVisibleRoot(cd->baseClasses()) &&
+ cd->isVisibleInHierarchy()
+ ) // root node in the forest
+ {
+ QCString tmp_url="";
+ if (cd->isLinkable() && !cd->isHidden())
+ {
+ tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
+ if (!cd->anchor().isEmpty())
+ {
+ tmp_url+="#"+cd->anchor();
+ }
+ }
+ //printf("Inserting root class %s\n",cd->name().data());
+ QCString tooltip = cd->briefDescriptionAsTooltip();
+ DotNode *n = new DotNode(m_curNodeNumber++,
+ cd->displayName(),
+ tooltip,
+ tmp_url.data());
+
+ //m_usedNodes->clear();
+ m_usedNodes->insert(cd->name(),n);
+ m_rootNodes->insert(0,n);
+ if (!cd->visited && cd->subClasses())
+ {
+ addHierarchy(n,cd,cd->visited);
+ cd->visited=TRUE;
+ }
+ }
+ }
+}
+
+DotGfxHierarchyTable::DotGfxHierarchyTable()
+{
+ m_curNodeNumber=0;
+ m_rootNodes = new QList<DotNode>;
+ m_usedNodes = new QDict<DotNode>(1009);
+ m_usedNodes->setAutoDelete(TRUE);
+ m_rootSubgraphs = new DotNodeList;
+
+ // build a graph with each class as a node and the inheritance relations
+ // as edges
+ initClassHierarchy(Doxygen::classSDict);
+ initClassHierarchy(Doxygen::hiddenClasses);
+ addClassList(Doxygen::classSDict);
+ addClassList(Doxygen::hiddenClasses);
+ // m_usedNodes now contains all nodes in the graph
+
+ // color the graph into a set of independent subgraphs
+ bool done=FALSE;
+ int curColor=0;
+ QListIterator<DotNode> dnli(*m_rootNodes);
+ while (!done) // there are still nodes to color
+ {
+ DotNode *n;
+ done=TRUE; // we are done unless there are still uncolored nodes
+ for (dnli.toLast();(n=dnli.current());--dnli)
+ {
+ if (n->m_subgraphId==-1) // not yet colored
+ {
+ //printf("Starting at node %s (%p): %d\n",n->m_label.data(),n,curColor);
+ done=FALSE; // still uncolored nodes
+ n->m_subgraphId=curColor;
+ n->markAsVisible();
+ n->colorConnectedNodes(curColor);
+ curColor++;
+ const DotNode *dn=n->findDocNode();
+ if (dn!=0)
+ m_rootSubgraphs->inSort(dn);
+ else
+ m_rootSubgraphs->inSort(n);
+ }
+ }
+ }
+
+ //printf("Number of independent subgraphs: %d\n",curColor);
+ //QListIterator<DotNode> dnli2(*m_rootSubgraphs);
+ //DotNode *n;
+ //for (dnli2.toFirst();(n=dnli2.current());++dnli2)
+ //{
+ // printf("Node %s color=%d (c=%d,p=%d)\n",
+ // n->m_label.data(),n->m_subgraphId,
+ // n->m_children?n->m_children->count():0,
+ // n->m_parents?n->m_parents->count():0);
+ //}
+}
+
+DotGfxHierarchyTable::~DotGfxHierarchyTable()
+{
+ //printf("DotGfxHierarchyTable::~DotGfxHierarchyTable\n");
+
+ //QDictIterator<DotNode> di(*m_usedNodes);
+ //DotNode *n;
+ //for (;(n=di.current());++di)
+ //{
+ // printf("Node %p: %s\n",n,n->label().data());
+ //}
+
+ delete m_rootNodes;
+ delete m_usedNodes;
+ delete m_rootSubgraphs;
+}
+
+//--------------------------------------------------------------------
+
+int DotClassGraph::m_curNodeNumber = 0;
+
+void DotClassGraph::addClass(ClassDef *cd,DotNode *n,int prot,
+ const char *label,const char *usedName,const char *templSpec,bool base,int distance)
+{
+ if (Config_getBool("HIDE_UNDOC_CLASSES") && !cd->isLinkable()) return;
+
+ int edgeStyle = (label || prot==EdgeInfo::Orange) ? EdgeInfo::Dashed : EdgeInfo::Solid;
+ QCString className;
+ if (usedName) // name is a typedef
+ {
+ className=usedName;
+ }
+ else if (templSpec) // name has a template part
+ {
+ className=insertTemplateSpecifierInScope(cd->name(),templSpec);
+ }
+ else // just a normal name
+ {
+ className=cd->displayName();
+ }
+ //printf("DotClassGraph::addClass(class=`%s',parent=%s,prot=%d,label=%s,dist=%d,usedName=%s,templSpec=%s,base=%d)\n",
+ // className.data(),n->m_label.data(),prot,label,distance,usedName,templSpec,base);
+ DotNode *bn = m_usedNodes->find(className);
+ if (bn) // class already inserted
+ {
+ if (base)
+ {
+ n->addChild(bn,prot,edgeStyle,label);
+ bn->addParent(n);
+ }
+ else
+ {
+ bn->addChild(n,prot,edgeStyle,label);
+ n->addParent(bn);
+ }
+ bn->setDistance(distance);
+ //printf(" add exiting node %s of %s\n",bn->m_label.data(),n->m_label.data());
+ }
+ else // new class
+ {
+ QCString displayName=className;
+ if (Config_getBool("HIDE_SCOPE_NAMES")) displayName=stripScope(displayName);
+ QCString tmp_url;
+ if (cd->isLinkable() && !cd->isHidden())
+ {
+ tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
+ if (!cd->anchor().isEmpty())
+ {
+ tmp_url+="#"+cd->anchor();
+ }
+ }
+ QCString tooltip = cd->briefDescriptionAsTooltip();
+ bn = new DotNode(m_curNodeNumber++,
+ displayName,
+ tooltip,
+ tmp_url.data(),
+ FALSE, // rootNode
+ cd
+ );
+ if (base)
+ {
+ n->addChild(bn,prot,edgeStyle,label);
+ bn->addParent(n);
+ }
+ else
+ {
+ bn->addChild(n,prot,edgeStyle,label);
+ n->addParent(bn);
+ }
+ bn->setDistance(distance);
+ m_usedNodes->insert(className,bn);
+ //printf(" add new child node `%s' to %s hidden=%d url=%s\n",
+ // className.data(),n->m_label.data(),cd->isHidden(),tmp_url.data());
+
+ buildGraph(cd,bn,base,distance+1);
+ }
+}
+
+void DotClassGraph::determineTruncatedNodes(QList<DotNode> &queue,bool includeParents)
+{
+ while (queue.count()>0)
+ {
+ DotNode *n = queue.take(0);
+ if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
+ {
+ bool truncated = FALSE;
+ if (n->m_children)
+ {
+ QListIterator<DotNode> li(*n->m_children);
+ DotNode *dn;
+ for (li.toFirst();(dn=li.current());++li)
+ {
+ if (!dn->isVisible())
+ truncated = TRUE;
+ else
+ queue.append(dn);
+ }
+ }
+ if (n->m_parents && includeParents)
+ {
+ QListIterator<DotNode> li(*n->m_parents);
+ DotNode *dn;
+ for (li.toFirst();(dn=li.current());++li)
+ {
+ if (!dn->isVisible())
+ truncated = TRUE;
+ else
+ queue.append(dn);
+ }
+ }
+ n->markAsTruncated(truncated);
+ }
+ }
+}
+
+bool DotClassGraph::determineVisibleNodes(DotNode *rootNode,
+ int maxNodes,bool includeParents)
+{
+ QList<DotNode> childQueue;
+ QList<DotNode> parentQueue;
+ QArray<int> childTreeWidth;
+ QArray<int> parentTreeWidth;
+ childQueue.append(rootNode);
+ if (includeParents) parentQueue.append(rootNode);
+ bool firstNode=TRUE; // flag to force reprocessing rootNode in the parent loop
+ // despite being marked visible in the child loop
+ while ((childQueue.count()>0 || parentQueue.count()>0) && maxNodes>0)
+ {
+ static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
+ if (childQueue.count()>0)
+ {
+ DotNode *n = childQueue.take(0);
+ int distance = n->distance();
+ if (!n->isVisible() && distance<maxDistance) // not yet processed
+ {
+ if (distance>0)
+ {
+ int oldSize=(int)childTreeWidth.size();
+ if (distance>oldSize)
+ {
+ childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance));
+ int i; for (i=oldSize;i<distance;i++) childTreeWidth[i]=0;
+ }
+ childTreeWidth[distance-1]+=n->label().length();
+ }
+ n->markAsVisible();
+ maxNodes--;
+ // add direct children
+ if (n->m_children)
+ {
+ QListIterator<DotNode> li(*n->m_children);
+ DotNode *dn;
+ for (li.toFirst();(dn=li.current());++li)
+ {
+ childQueue.append(dn);
+ }
+ }
+ }
+ }
+ if (includeParents && parentQueue.count()>0)
+ {
+ DotNode *n = parentQueue.take(0);
+ if ((!n->isVisible() || firstNode) && n->distance()<maxDistance) // not yet processed
+ {
+ firstNode=FALSE;
+ int distance = n->distance();
+ if (distance>0)
+ {
+ int oldSize = (int)parentTreeWidth.size();
+ if (distance>oldSize)
+ {
+ parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance));
+ int i; for (i=oldSize;i<distance;i++) parentTreeWidth[i]=0;
+ }
+ parentTreeWidth[distance-1]+=n->label().length();
+ }
+ n->markAsVisible();
+ maxNodes--;
+ // add direct parents
+ if (n->m_parents)
+ {
+ QListIterator<DotNode> li(*n->m_parents);
+ DotNode *dn;
+ for (li.toFirst();(dn=li.current());++li)
+ {
+ parentQueue.append(dn);
+ }
+ }
+ }
+ }
+ }
+ if (Config_getBool("UML_LOOK")) return FALSE; // UML graph are always top to bottom
+ int maxWidth=0;
+ int maxHeight=(int)QMAX(childTreeWidth.size(),parentTreeWidth.size());
+ uint i;
+ for (i=0;i<childTreeWidth.size();i++)
+ {
+ if (childTreeWidth.at(i)>maxWidth) maxWidth=childTreeWidth.at(i);
+ }
+ for (i=0;i<parentTreeWidth.size();i++)
+ {
+ if (parentTreeWidth.at(i)>maxWidth) maxWidth=parentTreeWidth.at(i);
+ }
+ //printf("max tree width=%d, max tree height=%d\n",maxWidth,maxHeight);
+ return maxWidth>80 && maxHeight<12; // used metric to decide to render the tree
+ // from left to right instead of top to bottom,
+ // with the idea to render very wide trees in
+ // left to right order.
+}
+
+void DotClassGraph::buildGraph(ClassDef *cd,DotNode *n,bool base,int distance)
+{
+ //printf("DocClassGraph::buildGraph(%s,distance=%d,base=%d)\n",
+ // cd->name().data(),distance,base);
+ // ---- Add inheritance relations
+
+ if (m_graphType == DotNode::Inheritance || m_graphType==DotNode::Collaboration)
+ {
+ BaseClassList *bcl = base ? cd->baseClasses() : cd->subClasses();
+ if (bcl)
+ {
+ BaseClassListIterator bcli(*bcl);
+ BaseClassDef *bcd;
+ for ( ; (bcd=bcli.current()) ; ++bcli )
+ {
+ //printf("-------- inheritance relation %s->%s templ=`%s'\n",
+ // cd->name().data(),bcd->classDef->name().data(),bcd->templSpecifiers.data());
+ addClass(bcd->classDef,n,bcd->prot,0,bcd->usedName,
+ bcd->templSpecifiers,base,distance);
+ }
+ }
+ }
+ if (m_graphType == DotNode::Collaboration)
+ {
+ // ---- Add usage relations
+
+ UsesClassDict *dict =
+ base ? cd->usedImplementationClasses() :
+ cd->usedByImplementationClasses()
+ ;
+ if (dict)
+ {
+ UsesClassDictIterator ucdi(*dict);
+ UsesClassDef *ucd;
+ for (;(ucd=ucdi.current());++ucdi)
+ {
+ QCString label;
+ QDictIterator<void> dvi(*ucd->accessors);
+ const char *s;
+ bool first=TRUE;
+ int count=0;
+ int maxLabels=10;
+ for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count)
+ {
+ if (first)
+ {
+ label=s;
+ first=FALSE;
+ }
+ else
+ {
+ label+=QCString("\n")+s;
+ }
+ }
+ if (count==maxLabels) label+="\n...";
+ //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data());
+ addClass(ucd->classDef,n,EdgeInfo::Purple,label,0,
+ ucd->templSpecifiers,base,distance);
+ }
+ }
+ }
+
+ // ---- Add template instantiation relations
+
+ static bool templateRelations = Config_getBool("TEMPLATE_RELATIONS");
+ if (templateRelations)
+ {
+ if (base) // template relations for base classes
+ {
+ ClassDef *templMaster=cd->templateMaster();
+ if (templMaster)
+ {
+ QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances());
+ ClassDef *templInstance;
+ for (;(templInstance=cli.current());++cli)
+ {
+ if (templInstance==cd)
+ {
+ addClass(templMaster,n,EdgeInfo::Orange,cli.currentKey(),0,
+ 0,TRUE,distance);
+ }
+ }
+ }
+ }
+ else // template relations for super classes
+ {
+ QDict<ClassDef> *templInstances = cd->getTemplateInstances();
+ if (templInstances)
+ {
+ QDictIterator<ClassDef> cli(*templInstances);
+ ClassDef *templInstance;
+ for (;(templInstance=cli.current());++cli)
+ {
+ addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0,
+ 0,FALSE,distance);
+ }
+ }
+ }
+ }
+}
+
+DotClassGraph::DotClassGraph(ClassDef *cd,DotNode::GraphType t)
+{
+ //printf("--------------- DotClassGraph::DotClassGraph `%s'\n",cd->displayName().data());
+ m_graphType = t;
+ QCString tmp_url="";
+ if (cd->isLinkable() && !cd->isHidden())
+ {
+ tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
+ if (!cd->anchor().isEmpty())
+ {
+ tmp_url+="#"+cd->anchor();
+ }
+ }
+ QCString className = cd->displayName();
+ QCString tooltip = cd->briefDescriptionAsTooltip();
+ m_startNode = new DotNode(m_curNodeNumber++,
+ className,
+ tooltip,
+ tmp_url.data(),
+ TRUE, // is a root node
+ cd
+ );
+ m_startNode->setDistance(0);
+ m_usedNodes = new QDict<DotNode>(1009);
+ m_usedNodes->insert(className,m_startNode);
+
+ //printf("Root node %s\n",cd->name().data());
+ //if (m_recDepth>0)
+ //{
+ buildGraph(cd,m_startNode,TRUE,1);
+ if (t==DotNode::Inheritance) buildGraph(cd,m_startNode,FALSE,1);
+ //}
+
+ static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
+ //int directChildNodes = 1;
+ //if (m_startNode->m_children!=0)
+ // directChildNodes+=m_startNode->m_children->count();
+ //if (t==DotNode::Inheritance && m_startNode->m_parents!=0)
+ // directChildNodes+=m_startNode->m_parents->count();
+ //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
+ //openNodeQueue.append(m_startNode);
+ m_lrRank = determineVisibleNodes(m_startNode,maxNodes,t==DotNode::Inheritance);
+ QList<DotNode> openNodeQueue;
+ openNodeQueue.append(m_startNode);
+ determineTruncatedNodes(openNodeQueue,t==DotNode::Inheritance);
+
+ m_diskName = cd->getFileBase().copy();
+}
+
+bool DotClassGraph::isTrivial() const
+{
+ static bool umlLook = Config_getBool("UML_LOOK");
+ if (m_graphType==DotNode::Inheritance)
+ return m_startNode->m_children==0 && m_startNode->m_parents==0;
+ else
+ return !umlLook && m_startNode->m_children==0;
+}
+
+bool DotClassGraph::isTooBig() const
+{
+ static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
+ int numNodes = 0;
+ numNodes+= m_startNode->m_children ? m_startNode->m_children->count() : 0;
+ if (m_graphType==DotNode::Inheritance)
+ {
+ numNodes+= m_startNode->m_parents ? m_startNode->m_parents->count() : 0;
+ }
+ return numNodes>=maxNodes;
+}
+
+DotClassGraph::~DotClassGraph()
+{
+ deleteNodes(m_startNode);
+ delete m_usedNodes;
+}
+
+/*! Computes a 16 byte md5 checksum for a given dot graph.
+ * The md5 checksum is returned as a 32 character ASCII string.
+ */
+QCString computeMd5Signature(DotNode *root,
+ DotNode::GraphType gt,
+ GraphOutputFormat format,
+ bool lrRank,
+ bool renderParents,
+ bool backArrows,
+ const QCString &title,
+ QCString &graphStr
+ )
+{
+ bool reNumber=TRUE;
+
+ //printf("computeMd5Signature\n");
+ QGString buf;
+ FTextStream md5stream(&buf);
+ writeGraphHeader(md5stream,title);
+ if (lrRank)
+ {
+ md5stream << " rankdir=\"LR\";" << endl;
+ }
+ root->clearWriteFlag();
+ root->write(md5stream,
+ gt,
+ format,
+ gt!=DotNode::CallGraph && gt!=DotNode::Dependency,
+ TRUE,
+ backArrows,
+ reNumber);
+ if (renderParents && root->m_parents)
+ {
+ QListIterator<DotNode> dnli(*root->m_parents);
+ DotNode *pn;
+ for (dnli.toFirst();(pn=dnli.current());++dnli)
+ {
+ if (pn->isVisible())
+ {
+ root->writeArrow(md5stream, // stream
+ gt, // graph type
+ format, // output format
+ pn, // child node
+ pn->m_edgeInfo->at(pn->m_children->findRef(root)), // edge info
+ FALSE, // topDown?
+ backArrows, // point back?
+ reNumber // renumber nodes
+ );
+ }
+ pn->write(md5stream, // stream
+ gt, // graph type
+ format, // output format
+ TRUE, // topDown?
+ FALSE, // toChildren?
+ backArrows, // backward pointing arrows?
+ reNumber // renumber nodes?
+ );
+ }
+ }
+ writeGraphFooter(md5stream);
+ uchar md5_sig[16];
+ QCString sigStr(33);
+ MD5Buffer((const unsigned char *)buf.data(),buf.length(),md5_sig);
+ MD5SigToString(md5_sig,sigStr.data(),33);
+ if (reNumber)
+ {
+ resetReNumbering();
+ }
+ graphStr=buf.data();
+ //printf("md5: %s | file: %s\n",sigStr,baseName.data());
+ return sigStr;
+}
+
+static bool updateDotGraph(DotNode *root,
+ DotNode::GraphType gt,
+ const QCString &baseName,
+ GraphOutputFormat format,
+ bool lrRank,
+ bool renderParents,
+ bool backArrows,
+ const QCString &title=QCString()
+ )
+{
+ QCString theGraph;
+ // TODO: write graph to theGraph, then compute md5 checksum
+ QCString md5 = computeMd5Signature(
+ root,gt,format,lrRank,renderParents,
+ backArrows,title,theGraph);
+ QFile f(baseName+".dot");
+ if (f.open(IO_WriteOnly))
+ {
+ FTextStream t(&f);
+ t << theGraph;
+ }
+ return checkAndUpdateMd5Signature(baseName,md5); // graph needs to be regenerated
+}
+
+QCString DotClassGraph::diskName() const
+{
+ QCString result=m_diskName.copy();
+ switch (m_graphType)
+ {
+ case DotNode::Collaboration:
+ result+="_coll_graph";
+ break;
+ //case Interface:
+ // result+="_intf_graph";
+ // break;
+ case DotNode::Inheritance:
+ result+="_inherit_graph";
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ return result;
+}
+
+QCString DotClassGraph::writeGraph(FTextStream &out,
+ GraphOutputFormat format,
+ const char *path,
+ const char *fileName,
+ const char *relPath,
+ bool /*isTBRank*/,
+ bool generateImageMap,
+ int graphId) const
+{
+ QDir d(path);
+ // store the original directory
+ if (!d.exists())
+ {
+ err("error: Output dir %s does not exist!\n",path); exit(1);
+ }
+ static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
+
+ QCString baseName;
+ QCString mapName;
+ switch (m_graphType)
+ {
+ case DotNode::Collaboration:
+ mapName="coll_map";
+ break;
+ //case Interface:
+ // mapName="intf_map";
+ // break;
+ case DotNode::Inheritance:
+ mapName="inherit_map";
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ baseName = convertNameToFile(diskName());
+
+ // derive target file names from baseName
+ QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
+ QCString absBaseName = d.absPath().utf8()+"/"+baseName;
+ QCString absDotName = absBaseName+".dot";
+ QCString absMapName = absBaseName+".map";
+ QCString absPdfName = absBaseName+".pdf";
+ QCString absEpsName = absBaseName+".eps";
+ QCString absImgName = absBaseName+"."+imgExt;
+
+ bool regenerate = FALSE;
+ if (updateDotGraph(m_startNode,
+ m_graphType,
+ absBaseName,
+ format,
+ m_lrRank,
+ m_graphType==DotNode::Inheritance,
+ TRUE,
+ m_startNode->label()
+ ) ||
+ !checkDeliverables(format==BITMAP ? absImgName :
+ usePDFLatex ? absPdfName : absEpsName,
+ format==BITMAP && generateImageMap ? absMapName : QCString())
+ )
+ {
+ regenerate=TRUE;
+ if (format==BITMAP) // run dot to create a bitmap image
+ {
+ QCString dotArgs(maxCmdLine);
+
+ DotRunner *dotRun = new DotRunner(absDotName,
+ d.absPath().data(),TRUE,absImgName);
+ dotRun->addJob(imgExt,absImgName);
+ if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
+ DotManager::instance()->addRun(dotRun);
+
+ }
+ else if (format==EPS) // run dot to create a .eps image
+ {
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
+ if (usePDFLatex)
+ {
+ dotRun->addJob("pdf",absPdfName);
+ }
+ else
+ {
+ dotRun->addJob("ps",absEpsName);
+ }
+ DotManager::instance()->addRun(dotRun);
+ }
+ }
+ Doxygen::indexList.addImageFile(baseName+"."+imgExt);
+
+ if (format==BITMAP && generateImageMap) // produce HTML to include the image
+ {
+ QCString mapLabel = escapeCharsInString(m_startNode->m_label,FALSE)+"_"+
+ escapeCharsInString(mapName,FALSE);
+ if (imgExt=="svg") // add link to SVG file without map file
+ {
+ out << "<div class=\"center\">";
+ if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
+ {
+ if (regenerate)
+ {
+ DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
+ }
+ int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
+ out << "<!-- SVG " << mapId << " -->" << endl;
+ }
+ out << "</div>" << endl;
+ }
+ else // add link to bitmap file with image map
+ {
+ out << "<div class=\"center\">";
+ out << "<img src=\"" << relPath << baseName << "."
+ << imgExt << "\" border=\"0\" usemap=\"#"
+ << mapLabel << "\" alt=\"";
+ switch (m_graphType)
+ {
+ case DotNode::Collaboration:
+ out << "Collaboration graph";
+ break;
+ case DotNode::Inheritance:
+ out << "Inheritance graph";
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ out << "\"/>";
+ out << "</div>" << endl;
+
+ if (regenerate || !insertMapFile(out,absMapName,relPath,mapLabel))
+ {
+ int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
+ FALSE,QCString(),mapLabel);
+ out << "<!-- MAP " << mapId << " -->" << endl;
+ }
+ }
+ }
+ else if (format==EPS) // produce tex to include the .eps image
+ {
+ if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
+ {
+ int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE /*TRUE*/);
+ out << endl << "% FIG " << figId << endl;
+ }
+ }
+ if (!regenerate) removeDotGraph(absDotName);
+
+ return baseName;
+}
+
+//--------------------------------------------------------------------
+
+void DotClassGraph::writeXML(FTextStream &t)
+{
+ QDictIterator<DotNode> dni(*m_usedNodes);
+ DotNode *node;
+ for (;(node=dni.current());++dni)
+ {
+ node->writeXML(t,TRUE);
+ }
+}
+
+void DotClassGraph::writeDEF(FTextStream &t)
+{
+ QDictIterator<DotNode> dni(*m_usedNodes);
+ DotNode *node;
+ for (;(node=dni.current());++dni)
+ {
+ node->writeDEF(t);
+ }
+}
+
+//--------------------------------------------------------------------
+
+int DotInclDepGraph::m_curNodeNumber = 0;
+
+void DotInclDepGraph::buildGraph(DotNode *n,FileDef *fd,int distance)
+{
+ QList<IncludeInfo> *includeFiles =
+ m_inverse ? fd->includedByFileList() : fd->includeFileList();
+ if (includeFiles)
+ {
+ QListIterator<IncludeInfo> ili(*includeFiles);
+ IncludeInfo *ii;
+ for (;(ii=ili.current());++ili)
+ {
+ FileDef *bfd = ii->fileDef;
+ QCString in = ii->includeName;
+ //printf(">>>> in=`%s' bfd=%p\n",ii->includeName.data(),bfd);
+ bool doc=TRUE,src=FALSE;
+ if (bfd)
+ {
+ in = bfd->absFilePath();
+ doc = bfd->isLinkable() && !bfd->isHidden();
+ src = bfd->generateSourceFile();
+ }
+ if (doc || src || !Config_getBool("HIDE_UNDOC_RELATIONS"))
+ {
+ QCString url="";
+ if (bfd) url=bfd->getOutputFileBase().copy();
+ if (!doc && src)
+ {
+ url=bfd->getSourceFileBase();
+ }
+ DotNode *bn = m_usedNodes->find(in);
+ if (bn) // file is already a node in the graph
+ {
+ n->addChild(bn,0,0,0);
+ bn->addParent(n);
+ bn->setDistance(distance);
+ }
+ else
+ {
+ QCString tmp_url;
+ QCString tooltip;
+ if (bfd)
+ {
+ tmp_url=doc || src ? bfd->getReference()+"$"+url : QCString();
+ tooltip = bfd->briefDescriptionAsTooltip();
+ }
+ bn = new DotNode(
+ m_curNodeNumber++, // n
+ ii->includeName, // label
+ tooltip, // tip
+ tmp_url, // url
+ FALSE, // rootNode
+ 0 // cd
+ );
+ n->addChild(bn,0,0,0);
+ bn->addParent(n);
+ m_usedNodes->insert(in,bn);
+ bn->setDistance(distance);
+
+ if (bfd) buildGraph(bn,bfd,distance+1);
+ }
+ }
+ }
+ }
+}
+
+void DotInclDepGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
+{
+ while (queue.count()>0 && maxNodes>0)
+ {
+ static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
+ DotNode *n = queue.take(0);
+ if (!n->isVisible() && n->distance()<maxDistance) // not yet processed
+ {
+ n->markAsVisible();
+ maxNodes--;
+ // add direct children
+ if (n->m_children)
+ {
+ QListIterator<DotNode> li(*n->m_children);
+ DotNode *dn;
+ for (li.toFirst();(dn=li.current());++li)
+ {
+ queue.append(dn);
+ }
+ }
+ }
+ }
+}
+
+void DotInclDepGraph::determineTruncatedNodes(QList<DotNode> &queue)
+{
+ while (queue.count()>0)
+ {
+ DotNode *n = queue.take(0);
+ if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
+ {
+ bool truncated = FALSE;
+ if (n->m_children)
+ {
+ QListIterator<DotNode> li(*n->m_children);
+ DotNode *dn;
+ for (li.toFirst();(dn=li.current());++li)
+ {
+ if (!dn->isVisible())
+ truncated = TRUE;
+ else
+ queue.append(dn);
+ }
+ }
+ n->markAsTruncated(truncated);
+ }
+ }
+}
+
+
+DotInclDepGraph::DotInclDepGraph(FileDef *fd,bool inverse)
+{
+ m_maxDistance = 0;
+ m_inverse = inverse;
+ ASSERT(fd!=0);
+ m_diskName = fd->getFileBase().copy();
+ QCString tmp_url=fd->getReference()+"$"+fd->getFileBase();
+ m_startNode = new DotNode(m_curNodeNumber++,
+ fd->docName(),
+ "",
+ tmp_url.data(),
+ TRUE // root node
+ );
+ m_startNode->setDistance(0);
+ m_usedNodes = new QDict<DotNode>(1009);
+ m_usedNodes->insert(fd->absFilePath(),m_startNode);
+ buildGraph(m_startNode,fd,1);
+
+ static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES");
+ int maxNodes = nodes;
+ //int directChildNodes = 1;
+ //if (m_startNode->m_children!=0)
+ // directChildNodes+=m_startNode->m_children->count();
+ //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
+ QList<DotNode> openNodeQueue;
+ openNodeQueue.append(m_startNode);
+ determineVisibleNodes(openNodeQueue,maxNodes);
+ openNodeQueue.clear();
+ openNodeQueue.append(m_startNode);
+ determineTruncatedNodes(openNodeQueue);
+}
+
+DotInclDepGraph::~DotInclDepGraph()
+{
+ deleteNodes(m_startNode);
+ delete m_usedNodes;
+}
+
+QCString DotInclDepGraph::diskName() const
+{
+ QCString result=m_diskName.copy();
+ if (m_inverse) result+="_dep";
+ result+="_incl";
+ return convertNameToFile(result);
+}
+
+QCString DotInclDepGraph::writeGraph(FTextStream &out,
+ GraphOutputFormat format,
+ const char *path,
+ const char *fileName,
+ const char *relPath,
+ bool generateImageMap,
+ int graphId
+ ) const
+{
+ QDir d(path);
+ // store the original directory
+ if (!d.exists())
+ {
+ err("error: Output dir %s does not exist!\n",path); exit(1);
+ }
+ static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
+
+ QCString baseName=m_diskName;
+ if (m_inverse) baseName+="_dep";
+ baseName+="_incl";
+ baseName=convertNameToFile(baseName);
+ QCString mapName=escapeCharsInString(m_startNode->m_label,FALSE);
+ if (m_inverse) mapName+="dep";
+
+ QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
+ QCString absBaseName = d.absPath().utf8()+"/"+baseName;
+ QCString absDotName = absBaseName+".dot";
+ QCString absMapName = absBaseName+".map";
+ QCString absPdfName = absBaseName+".pdf";
+ QCString absEpsName = absBaseName+".eps";
+ QCString absImgName = absBaseName+"."+imgExt;
+
+ bool regenerate = FALSE;
+ if (updateDotGraph(m_startNode,
+ DotNode::Dependency,
+ absBaseName,
+ format,
+ FALSE, // lrRank
+ FALSE, // renderParents
+ m_inverse, // backArrows
+ m_startNode->label()
+ ) ||
+ !checkDeliverables(format==BITMAP ? absImgName :
+ usePDFLatex ? absPdfName : absEpsName,
+ format==BITMAP && generateImageMap ? absMapName : QCString())
+ )
+ {
+ regenerate=TRUE;
+ if (format==BITMAP)
+ {
+ // run dot to create a bitmap image
+ QCString dotArgs(maxCmdLine);
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
+ dotRun->addJob(imgExt,absImgName);
+ if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
+ DotManager::instance()->addRun(dotRun);
+ }
+ else if (format==EPS)
+ {
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
+ if (usePDFLatex)
+ {
+ dotRun->addJob("pdf",absPdfName);
+ }
+ else
+ {
+ dotRun->addJob("ps",absEpsName);
+ }
+ DotManager::instance()->addRun(dotRun);
+
+ }
+ }
+ Doxygen::indexList.addImageFile(baseName+"."+imgExt);
+
+ if (format==BITMAP && generateImageMap)
+ {
+ if (imgExt=="svg") // Scalable vector graphics
+ {
+ out << "<div class=\"center\">";
+ if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
+ {
+ if (regenerate)
+ {
+ DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
+ }
+ int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
+ out << "<!-- SVG " << mapId << " -->" << endl;
+ }
+ out << "</div>" << endl;
+ }
+ else // bitmap graphics
+ {
+ out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
+ << imgExt << "\" border=\"0\" usemap=\"#"
+ << mapName << "\" alt=\"\"/>";
+ out << "</div>" << endl;
+
+ QCString absMapName = absBaseName+".map";
+ if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
+ {
+ int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
+ FALSE,QCString(),mapName);
+ out << "<!-- MAP " << mapId << " -->" << endl;
+ }
+ }
+ }
+ else if (format==EPS) // encapsulated postscript
+ {
+ if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
+ {
+ int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
+ out << endl << "% FIG " << figId << endl;
+ }
+ }
+ if (!regenerate) removeDotGraph(absDotName);
+
+ return baseName;
+}
+
+bool DotInclDepGraph::isTrivial() const
+{
+ return m_startNode->m_children==0;
+}
+
+bool DotInclDepGraph::isTooBig() const
+{
+ static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
+ int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
+ return numNodes>=maxNodes;
+}
+
+void DotInclDepGraph::writeXML(FTextStream &t)
+{
+ QDictIterator<DotNode> dni(*m_usedNodes);
+ DotNode *node;
+ for (;(node=dni.current());++dni)
+ {
+ node->writeXML(t,FALSE);
+ }
+}
+
+//-------------------------------------------------------------
+
+int DotCallGraph::m_curNodeNumber = 0;
+
+void DotCallGraph::buildGraph(DotNode *n,MemberDef *md,int distance)
+{
+ LockingPtr<MemberSDict> refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers();
+ if (!refs.isNull())
+ {
+ MemberSDict::Iterator mri(*refs);
+ MemberDef *rmd;
+ for (;(rmd=mri.current());++mri)
+ {
+ if (rmd->isFunction() || rmd->isSlot() || rmd->isSignal())
+ {
+ QCString uniqueId;
+ uniqueId=rmd->getReference()+"$"+
+ rmd->getOutputFileBase()+"#"+rmd->anchor();
+ DotNode *bn = m_usedNodes->find(uniqueId);
+ if (bn) // file is already a node in the graph
+ {
+ n->addChild(bn,0,0,0);
+ bn->addParent(n);
+ bn->setDistance(distance);
+ }
+ else
+ {
+ QCString name;
+ if (Config_getBool("HIDE_SCOPE_NAMES"))
+ {
+ name = rmd->getOuterScope()==m_scope ?
+ rmd->name() : rmd->qualifiedName();
+ }
+ else
+ {
+ name = rmd->qualifiedName();
+ }
+ QCString tooltip = rmd->briefDescriptionAsTooltip();
+ bn = new DotNode(
+ m_curNodeNumber++,
+ linkToText(rmd->getLanguage(),name,FALSE),
+ tooltip,
+ uniqueId,
+ 0 //distance
+ );
+ n->addChild(bn,0,0,0);
+ bn->addParent(n);
+ bn->setDistance(distance);
+ m_usedNodes->insert(uniqueId,bn);
+
+ buildGraph(bn,rmd,distance+1);
+ }
+ }
+ }
+ }
+}
+
+void DotCallGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
+{
+ while (queue.count()>0 && maxNodes>0)
+ {
+ static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
+ DotNode *n = queue.take(0);
+ if (!n->isVisible() && n->distance()<maxDistance) // not yet processed
+ {
+ n->markAsVisible();
+ maxNodes--;
+ // add direct children
+ if (n->m_children)
+ {
+ QListIterator<DotNode> li(*n->m_children);
+ DotNode *dn;
+ for (li.toFirst();(dn=li.current());++li)
+ {
+ queue.append(dn);
+ }
+ }
+ }
+ }
+}
+
+void DotCallGraph::determineTruncatedNodes(QList<DotNode> &queue)
+{
+ while (queue.count()>0)
+ {
+ DotNode *n = queue.take(0);
+ if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
+ {
+ bool truncated = FALSE;
+ if (n->m_children)
+ {
+ QListIterator<DotNode> li(*n->m_children);
+ DotNode *dn;
+ for (li.toFirst();(dn=li.current());++li)
+ {
+ if (!dn->isVisible())
+ truncated = TRUE;
+ else
+ queue.append(dn);
+ }
+ }
+ n->markAsTruncated(truncated);
+ }
+ }
+}
+
+
+
+DotCallGraph::DotCallGraph(MemberDef *md,bool inverse)
+{
+ m_maxDistance = 0;
+ m_inverse = inverse;
+ m_diskName = md->getOutputFileBase()+"_"+md->anchor();
+ m_scope = md->getOuterScope();
+ QCString uniqueId;
+ uniqueId = md->getReference()+"$"+
+ md->getOutputFileBase()+"#"+md->anchor();
+ QCString name;
+ if (Config_getBool("HIDE_SCOPE_NAMES"))
+ {
+ name = md->name();
+ }
+ else
+ {
+ name = md->qualifiedName();
+ }
+ m_startNode = new DotNode(m_curNodeNumber++,
+ linkToText(md->getLanguage(),name,FALSE),
+ "",
+ uniqueId.data(),
+ TRUE // root node
+ );
+ m_startNode->setDistance(0);
+ m_usedNodes = new QDict<DotNode>(1009);
+ m_usedNodes->insert(uniqueId,m_startNode);
+ buildGraph(m_startNode,md,1);
+
+ static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES");
+ int maxNodes = nodes;
+ //int directChildNodes = 1;
+ //if (m_startNode->m_children!=0)
+ // directChildNodes+=m_startNode->m_children->count();
+ //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
+ QList<DotNode> openNodeQueue;
+ openNodeQueue.append(m_startNode);
+ determineVisibleNodes(openNodeQueue,maxNodes);
+ openNodeQueue.clear();
+ openNodeQueue.append(m_startNode);
+ determineTruncatedNodes(openNodeQueue);
+}
+
+DotCallGraph::~DotCallGraph()
+{
+ deleteNodes(m_startNode);
+ delete m_usedNodes;
+}
+
+QCString DotCallGraph::writeGraph(FTextStream &out, GraphOutputFormat format,
+ const char *path,const char *fileName,
+ const char *relPath,bool generateImageMap,int
+ graphId) const
+{
+ QDir d(path);
+ // store the original directory
+ if (!d.exists())
+ {
+ err("error: Output dir %s does not exist!\n",path); exit(1);
+ }
+ static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
+
+ QCString baseName = m_diskName + (m_inverse ? "_icgraph" : "_cgraph");
+ QCString mapName = baseName;
+
+ QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
+ QCString absBaseName = d.absPath().utf8()+"/"+baseName;
+ QCString absDotName = absBaseName+".dot";
+ QCString absMapName = absBaseName+".map";
+ QCString absPdfName = absBaseName+".pdf";
+ QCString absEpsName = absBaseName+".eps";
+ QCString absImgName = absBaseName+"."+imgExt;
+
+ bool regenerate = FALSE;
+ if (updateDotGraph(m_startNode,
+ DotNode::CallGraph,
+ absBaseName,
+ format,
+ TRUE, // lrRank
+ FALSE, // renderParents
+ m_inverse, // backArrows
+ m_startNode->label()
+ ) ||
+ !checkDeliverables(format==BITMAP ? absImgName :
+ usePDFLatex ? absPdfName : absEpsName,
+ format==BITMAP && generateImageMap ? absMapName : QCString())
+ )
+ {
+ regenerate=TRUE;
+ if (format==BITMAP)
+ {
+ // run dot to create a bitmap image
+ QCString dotArgs(maxCmdLine);
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
+ dotRun->addJob(imgExt,absImgName);
+ if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
+ DotManager::instance()->addRun(dotRun);
+
+ }
+ else if (format==EPS)
+ {
+ // run dot to create a .eps image
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
+ if (usePDFLatex)
+ {
+ dotRun->addJob("pdf",absPdfName);
+ }
+ else
+ {
+ dotRun->addJob("ps",absEpsName);
+ }
+ DotManager::instance()->addRun(dotRun);
+
+ }
+ }
+ Doxygen::indexList.addImageFile(baseName+"."+imgExt);
+
+ if (format==BITMAP && generateImageMap)
+ {
+ if (imgExt=="svg") // Scalable vector graphics
+ {
+ out << "<div class=\"center\">";
+ if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
+ {
+ if (regenerate)
+ {
+ DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
+ }
+ int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
+ out << "<!-- SVG " << mapId << " -->" << endl;
+ }
+ out << "</div>" << endl;
+ }
+ else // bitmap graphics
+ {
+ out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
+ << imgExt << "\" border=\"0\" usemap=\"#"
+ << mapName << "\" alt=\"";
+ out << "\"/>";
+ out << "</div>" << endl;
+
+ if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
+ {
+ int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
+ FALSE,QCString(),mapName);
+ out << "<!-- MAP " << mapId << " -->" << endl;
+ }
+ }
+ }
+ else if (format==EPS) // encapsulated postscript
+ {
+ if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
+ {
+ int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
+ out << endl << "% FIG " << figId << endl;
+ }
+ }
+ if (!regenerate) removeDotGraph(absDotName);
+
+ return baseName;
+}
+
+bool DotCallGraph::isTrivial() const
+{
+ return m_startNode->m_children==0;
+}
+
+bool DotCallGraph::isTooBig() const
+{
+ static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
+ int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
+ return numNodes>=maxNodes;
+}
+
+//-------------------------------------------------------------
+
+DotDirDeps::DotDirDeps(DirDef *dir) : m_dir(dir)
+{
+}
+
+DotDirDeps::~DotDirDeps()
+{
+}
+
+QCString DotDirDeps::writeGraph(FTextStream &out,
+ GraphOutputFormat format,
+ const char *path,
+ const char *fileName,
+ const char *relPath,
+ bool generateImageMap,
+ int graphId) const
+{
+ QDir d(path);
+ // store the original directory
+ if (!d.exists())
+ {
+ err("error: Output dir %s does not exist!\n",path); exit(1);
+ }
+ static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
+
+ QCString baseName=m_dir->getOutputFileBase()+"_dep";
+ QCString mapName=escapeCharsInString(baseName,FALSE);
+
+ QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
+ QCString absBaseName = d.absPath().utf8()+"/"+baseName;
+ QCString absDotName = absBaseName+".dot";
+ QCString absMapName = absBaseName+".map";
+ QCString absPdfName = absBaseName+".pdf";
+ QCString absEpsName = absBaseName+".eps";
+ QCString absImgName = absBaseName+"."+imgExt;
+
+ // compute md5 checksum of the graph were are about to generate
+ QGString theGraph;
+ FTextStream md5stream(&theGraph);
+ m_dir->writeDepGraph(md5stream);
+ uchar md5_sig[16];
+ QCString sigStr(33);
+ MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
+ MD5SigToString(md5_sig,sigStr.data(),33);
+ bool regenerate=FALSE;
+ if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
+ !checkDeliverables(format==BITMAP ? absImgName :
+ usePDFLatex ? absPdfName : absEpsName,
+ format==BITMAP && generateImageMap ? absMapName : QCString())
+ )
+ {
+ regenerate=TRUE;
+
+ QFile f(absDotName);
+ if (!f.open(IO_WriteOnly))
+ {
+ err("Cannot create file %s.dot for writing!\n",baseName.data());
+ }
+ FTextStream t(&f);
+ t << theGraph.data();
+ f.close();
+
+ if (format==BITMAP)
+ {
+ // run dot to create a bitmap image
+ QCString dotArgs(maxCmdLine);
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
+ dotRun->addJob(imgExt,absImgName);
+ if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
+ DotManager::instance()->addRun(dotRun);
+ }
+ else if (format==EPS)
+ {
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
+ if (usePDFLatex)
+ {
+ dotRun->addJob("pdf",absPdfName);
+ }
+ else
+ {
+ dotRun->addJob("ps",absEpsName);
+ }
+ DotManager::instance()->addRun(dotRun);
+ }
+ }
+ Doxygen::indexList.addImageFile(baseName+"."+imgExt);
+
+ if (format==BITMAP && generateImageMap)
+ {
+ if (imgExt=="svg") // Scalable vector graphics
+ {
+ out << "<div class=\"center\">";
+ if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
+ {
+ if (regenerate)
+ {
+ DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
+ }
+ int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
+ out << "<!-- SVG " << mapId << " -->" << endl;
+ }
+ out << "</div>" << endl;
+ }
+ else // bitmap graphics
+ {
+ out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
+ << imgExt << "\" border=\"0\" usemap=\"#"
+ << mapName << "\" alt=\"";
+ out << convertToXML(m_dir->displayName());
+ out << "\"/>";
+ out << "</div>" << endl;
+
+ if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
+ {
+ int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
+ TRUE,QCString(),mapName);
+ out << "<!-- MAP " << mapId << " -->" << endl;
+ }
+ }
+ }
+ else if (format==EPS)
+ {
+ if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
+ {
+ int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
+ out << endl << "% FIG " << figId << endl;
+ }
+ }
+ if (!regenerate) removeDotGraph(absDotName);
+
+ return baseName;
+}
+
+bool DotDirDeps::isTrivial() const
+{
+ return m_dir->depGraphIsTrivial();
+}
+
+//-------------------------------------------------------------
+
+void generateGraphLegend(const char *path)
+{
+ QDir d(path);
+ // store the original directory
+ if (!d.exists())
+ {
+ err("error: Output dir %s does not exist!\n",path); exit(1);
+ }
+
+ QGString theGraph;
+ FTextStream md5stream(&theGraph);
+ writeGraphHeader(md5stream,theTranslator->trLegendTitle());
+ md5stream << " Node9 [shape=\"box\",label=\"Inherited\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",fillcolor=\"grey75\",style=\"filled\" fontcolor=\"black\"];\n";
+ md5stream << " Node10 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
+ md5stream << " Node10 [shape=\"box\",label=\"PublicBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPublicBase" << Doxygen::htmlFileExtension << "\"];\n";
+ md5stream << " Node11 -> Node10 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
+ md5stream << " Node11 [shape=\"box\",label=\"Truncated\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"red\",URL=\"$classTruncated" << Doxygen::htmlFileExtension << "\"];\n";
+ md5stream << " Node13 -> Node9 [dir=\"back\",color=\"darkgreen\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
+ md5stream << " Node13 [shape=\"box\",label=\"ProtectedBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classProtectedBase" << Doxygen::htmlFileExtension << "\"];\n";
+ md5stream << " Node14 -> Node9 [dir=\"back\",color=\"firebrick4\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
+ md5stream << " Node14 [shape=\"box\",label=\"PrivateBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPrivateBase" << Doxygen::htmlFileExtension << "\"];\n";
+ md5stream << " Node15 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
+ md5stream << " Node15 [shape=\"box\",label=\"Undocumented\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"grey75\"];\n";
+ md5stream << " Node16 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
+ md5stream << " Node16 [shape=\"box\",label=\"Templ< int >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
+ md5stream << " Node17 -> Node16 [dir=\"back\",color=\"orange\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"< int >\",fontname=\"" << FONTNAME << "\"];\n";
+ md5stream << " Node17 [shape=\"box\",label=\"Templ< T >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
+ md5stream << " Node18 -> Node9 [dir=\"back\",color=\"darkorchid3\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"m_usedClass\",fontname=\"" << FONTNAME << "\"];\n";
+ md5stream << " Node18 [shape=\"box\",label=\"Used\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classUsed" << Doxygen::htmlFileExtension << "\"];\n";
+ writeGraphFooter(md5stream);
+ uchar md5_sig[16];
+ QCString sigStr(33);
+ MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
+ MD5SigToString(md5_sig,sigStr.data(),33);
+ QCString absBaseName = (QCString)path+"/graph_legend";
+ QCString absDotName = absBaseName+".dot";
+ QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
+ QCString imgName = "graph_legend."+imgExt;
+ QCString absImgName = absBaseName+"."+imgExt;
+ if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
+ !checkDeliverables(absImgName))
+ {
+ QFile dotFile(absDotName);
+ if (!dotFile.open(IO_WriteOnly))
+ {
+ err("Could not open file %s for writing\n",
+ convertToQCString(dotFile.name()).data());
+ return;
+ }
+
+ FTextStream dotText(&dotFile);
+ dotText << theGraph;
+ dotFile.close();
+
+ // run dot to generate the a bitmap image from the graph
+
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
+ dotRun->addJob(imgExt,absImgName);
+ DotManager::instance()->addRun(dotRun);
+ }
+ else
+ {
+ removeDotGraph(absDotName);
+ }
+ Doxygen::indexList.addImageFile(imgName);
+
+ if (imgExt=="svg")
+ {
+ DotManager::instance()->addSVGObject(
+ absBaseName+Config_getString("HTML_FILE_EXTENSION"),
+ "graph_legend",
+ absImgName,QCString());
+ }
+
+}
+
+void writeDotGraphFromFile(const char *inFile,const char *outDir,
+ const char *outFile,GraphOutputFormat format)
+{
+ QDir d(outDir);
+ if (!d.exists())
+ {
+ err("error: Output dir %s does not exist!\n",outDir); exit(1);
+ }
+
+ QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
+ QCString imgName = (QCString)outFile+"."+imgExt;
+ QCString absImgName = d.absPath().utf8()+"/"+imgName;
+ QCString absOutFile = d.absPath().utf8()+"/"+outFile;
+
+ DotRunner dotRun(inFile,d.absPath().data(),FALSE,absImgName);
+ if (format==BITMAP)
+ dotRun.addJob(imgExt,absImgName);
+ else // format==EPS
+ {
+ if (Config_getBool("USE_PDFLATEX"))
+ {
+ dotRun.addJob("pdf",absOutFile+".pdf");
+ }
+ else
+ {
+ dotRun.addJob("ps",absOutFile+".eps");
+ }
+ }
+
+ dotRun.preventCleanUp();
+ if (!dotRun.run())
+ {
+ return;
+ }
+
+ if (format==BITMAP) checkDotResult(absImgName);
+
+ Doxygen::indexList.addImageFile(imgName);
+
+}
+
+
+/*! Writes user defined image map to the output.
+ * \param t text stream to write to
+ * \param inFile just the basename part of the filename
+ * \param outDir output directory
+ * \param relPath relative path the to root of the output dir
+ * \param baseName the base name of the output files
+ * \param context the scope in which this graph is found (for resolving links)
+ * \param graphId a unique id for this graph, use for dynamic sections
+ */
+void writeDotImageMapFromFile(FTextStream &t,
+ const QCString &inFile, const QCString &outDir,
+ const QCString &relPath, const QCString &baseName,
+ const QCString &context,int graphId)
+{
+
+ QDir d(outDir);
+ if (!d.exists())
+ {
+ err("error: Output dir %s does not exist!\n",outDir.data()); exit(1);
+ }
+
+ QCString mapName = baseName+".map";
+ QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
+ QCString imgName = baseName+"."+imgExt;
+ QCString absOutFile = d.absPath().utf8()+"/"+mapName;
+
+ DotRunner dotRun(inFile,d.absPath().data(),FALSE);
+ dotRun.addJob(MAP_CMD,absOutFile);
+ dotRun.preventCleanUp();
+ if (!dotRun.run())
+ {
+ return;
+ }
+
+ if (imgExt=="svg") // vector graphics
+ {
+ //writeSVGFigureLink(t,relPath,inFile,inFile+".svg");
+ //DotFilePatcher patcher(inFile+".svg");
+ QCString svgName=outDir+"/"+baseName+".svg";
+ writeSVGFigureLink(t,relPath,baseName,svgName);
+ DotFilePatcher patcher(svgName);
+ patcher.addSVGConversion(relPath,TRUE,context,TRUE,graphId);
+ patcher.run();
+ }
+ else // bitmap graphics
+ {
+ t << "<img src=\"" << relPath << imgName << "\" alt=\""
+ << imgName << "\" border=\"0\" usemap=\"#" << mapName << "\"/>" << endl
+ << "<map name=\"" << mapName << "\" id=\"" << mapName << "\">";
+
+ convertMapFile(t, absOutFile, relPath ,TRUE, context);
+
+ t << "</map>" << endl;
+ }
+ d.remove(absOutFile);
+}
+
+//-------------------------------------------------------------
+
+DotGroupCollaboration::DotGroupCollaboration(GroupDef* gd)
+{
+ m_curNodeId = 0;
+ QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase();
+ m_usedNodes = new QDict<DotNode>(1009);
+ m_rootNode = new DotNode(m_curNodeId++, gd->groupTitle(), "", tmp_url, TRUE );
+ m_rootNode->markAsVisible();
+ m_usedNodes->insert(gd->name(), m_rootNode );
+ m_edges.setAutoDelete(TRUE);
+
+ m_diskName = gd->getOutputFileBase();
+
+ buildGraph( gd );
+}
+
+DotGroupCollaboration::~DotGroupCollaboration()
+{
+ delete m_usedNodes;
+}
+
+void DotGroupCollaboration::buildGraph(GroupDef* gd)
+{
+ QCString tmp_url;
+ //===========================
+ // hierarchy.
+
+ // Write parents
+ LockingPtr<GroupList> groups = gd->partOfGroups();
+ if ( groups!=0 )
+ {
+ GroupListIterator gli(*groups);
+ GroupDef *d;
+ for (gli.toFirst();(d=gli.current());++gli)
+ {
+ DotNode* nnode = m_usedNodes->find(d->name());
+ if ( !nnode )
+ { // add node
+ tmp_url = d->getReference()+"$"+d->getOutputFileBase();
+ QCString tooltip = d->briefDescriptionAsTooltip();
+ nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_url );
+ nnode->markAsVisible();
+ m_usedNodes->insert(d->name(), nnode );
+ }
+ tmp_url = "";
+ addEdge( nnode, m_rootNode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
+ }
+ }
+
+ // Add subgroups
+ if ( gd->getSubGroups() && gd->getSubGroups()->count() )
+ {
+ QListIterator<GroupDef> defli(*gd->getSubGroups());
+ GroupDef *def;
+ for (;(def=defli.current());++defli)
+ {
+ DotNode* nnode = m_usedNodes->find(def->name());
+ if ( !nnode )
+ { // add node
+ tmp_url = def->getReference()+"$"+def->getOutputFileBase();
+ QCString tooltip = def->briefDescriptionAsTooltip();
+ nnode = new DotNode(m_curNodeId++, def->groupTitle(), tooltip, tmp_url );
+ nnode->markAsVisible();
+ m_usedNodes->insert(def->name(), nnode );
+ }
+ tmp_url = "";
+ addEdge( m_rootNode, nnode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
+ }
+ }
+
+ //=======================
+ // Write collaboration
+
+ // Add members
+ addMemberList( gd->getMemberList(MemberList::allMembersList) );
+
+ // Add classes
+ if ( gd->getClasses() && gd->getClasses()->count() )
+ {
+ ClassSDict::Iterator defli(*gd->getClasses());
+ ClassDef *def;
+ for (;(def=defli.current());++defli)
+ {
+ tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
+ if (!def->anchor().isEmpty())
+ {
+ tmp_url+="#"+def->anchor();
+ }
+ addCollaborationMember( def, tmp_url, DotGroupCollaboration::tclass );
+ }
+ }
+
+ // Add namespaces
+ if ( gd->getNamespaces() && gd->getNamespaces()->count() )
+ {
+ NamespaceSDict::Iterator defli(*gd->getNamespaces());
+ NamespaceDef *def;
+ for (;(def=defli.current());++defli)
+ {
+ tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
+ addCollaborationMember( def, tmp_url, DotGroupCollaboration::tnamespace );
+ }
+ }
+
+ // Add files
+ if ( gd->getFiles() && gd->getFiles()->count() )
+ {
+ QListIterator<FileDef> defli(*gd->getFiles());
+ FileDef *def;
+ for (;(def=defli.current());++defli)
+ {
+ tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
+ addCollaborationMember( def, tmp_url, DotGroupCollaboration::tfile );
+ }
+ }
+
+ // Add pages
+ if ( gd->getPages() && gd->getPages()->count() )
+ {
+ PageSDict::Iterator defli(*gd->getPages());
+ PageDef *def;
+ for (;(def=defli.current());++defli)
+ {
+ tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
+ addCollaborationMember( def, tmp_url, DotGroupCollaboration::tpages );
+ }
+ }
+
+ // Add directories
+ if ( gd->getDirs() && gd->getDirs()->count() )
+ {
+ QListIterator<DirDef> defli(*gd->getDirs());
+ DirDef *def;
+ for (;(def=defli.current());++defli)
+ {
+ tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
+ addCollaborationMember( def, tmp_url, DotGroupCollaboration::tdir );
+ }
+ }
+}
+
+void DotGroupCollaboration::addMemberList( MemberList* ml )
+{
+ if ( !( ml && ml->count()) ) return;
+ MemberListIterator defli(*ml);
+ MemberDef *def;
+ for (;(def=defli.current());++defli)
+ {
+ QCString tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension
+ +"#"+def->anchor();
+ addCollaborationMember( def, tmp_url, DotGroupCollaboration::tmember );
+ }
+}
+
+DotGroupCollaboration::Edge* DotGroupCollaboration::addEdge(
+ DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType,
+ const QCString& _label, const QCString& _url )
+{
+ // search a existing link.
+ QListIterator<Edge> lli(m_edges);
+ Edge* newEdge = 0;
+ for ( lli.toFirst(); (newEdge=lli.current()); ++lli)
+ {
+ if ( newEdge->pNStart==_pNStart &&
+ newEdge->pNEnd==_pNEnd &&
+ newEdge->eType==_eType
+ )
+ { // edge already found
+ break;
+ }
+ }
+ if ( newEdge==0 ) // new link
+ {
+ newEdge = new Edge(_pNStart,_pNEnd,_eType);
+ m_edges.append( newEdge );
+ }
+
+ if (!_label.isEmpty())
+ {
+ newEdge->links.append(new Link(_label,_url));
+ }
+
+ return newEdge;
+}
+
+void DotGroupCollaboration::addCollaborationMember(
+ Definition* def, QCString& url, EdgeType eType )
+{
+ // Create group nodes
+ if ( !def->partOfGroups() )
+ return;
+ GroupListIterator gli(*def->partOfGroups());
+ GroupDef *d;
+ QCString tmp_str;
+ for (;(d=gli.current());++gli)
+ {
+ DotNode* nnode = m_usedNodes->find(d->name());
+ if ( nnode != m_rootNode )
+ {
+ if ( nnode==0 )
+ { // add node
+ tmp_str = d->getReference()+"$"+d->getOutputFileBase();
+ QCString tooltip = d->briefDescriptionAsTooltip();
+ nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_str );
+ nnode->markAsVisible();
+ m_usedNodes->insert(d->name(), nnode );
+ }
+ tmp_str = def->qualifiedName();
+ addEdge( m_rootNode, nnode, eType, tmp_str, url );
+ }
+ }
+}
+
+
+QCString DotGroupCollaboration::writeGraph( FTextStream &t, GraphOutputFormat format,
+ const char *path, const char *fileName, const char *relPath,
+ bool writeImageMap,int graphId) const
+{
+ QDir d(path);
+ // store the original directory
+ if (!d.exists())
+ {
+ err("error: Output dir %s does not exist!\n",path); exit(1);
+ }
+ static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
+
+ QGString theGraph;
+ FTextStream md5stream(&theGraph);
+ writeGraphHeader(md5stream,m_rootNode->label());
+
+ // clean write flags
+ QDictIterator<DotNode> dni(*m_usedNodes);
+ DotNode *pn;
+ for (dni.toFirst();(pn=dni.current());++dni)
+ {
+ pn->clearWriteFlag();
+ }
+
+ // write other nodes.
+ for (dni.toFirst();(pn=dni.current());++dni)
+ {
+ pn->write(md5stream,DotNode::Inheritance,format,TRUE,FALSE,FALSE,FALSE);
+ }
+
+ // write edges
+ QListIterator<Edge> eli(m_edges);
+ Edge* edge;
+ for (eli.toFirst();(edge=eli.current());++eli)
+ {
+ edge->write( md5stream );
+ }
+
+ writeGraphFooter(md5stream);
+ resetReNumbering();
+ uchar md5_sig[16];
+ QCString sigStr(33);
+ MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
+ MD5SigToString(md5_sig,sigStr.data(),33);
+ QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
+ QCString baseName = m_diskName;
+ QCString imgName = baseName+"."+imgExt;
+ QCString mapName = baseName+".map";
+ QCString absPath = d.absPath().data();
+ QCString absBaseName = absPath+"/"+baseName;
+ QCString absDotName = absBaseName+".dot";
+ QCString absImgName = absBaseName+"."+imgExt;
+ QCString absMapName = absBaseName+".map";
+ QCString absPdfName = absBaseName+".pdf";
+ QCString absEpsName = absBaseName+".eps";
+ bool regenerate=FALSE;
+ if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
+ !checkDeliverables(format==BITMAP ? absImgName :
+ usePDFLatex ? absPdfName : absEpsName,
+ format==BITMAP /*&& generateImageMap*/ ? absMapName : QCString())
+ )
+ {
+ regenerate=TRUE;
+
+ QFile dotfile(absDotName);
+ if (dotfile.open(IO_WriteOnly))
+ {
+ FTextStream tdot(&dotfile);
+ tdot << theGraph;
+ dotfile.close();
+ }
+
+ if (format==BITMAP) // run dot to create a bitmap image
+ {
+ QCString dotArgs(maxCmdLine);
+
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
+ dotRun->addJob(imgExt,absImgName);
+ if (writeImageMap) dotRun->addJob(MAP_CMD,absMapName);
+ DotManager::instance()->addRun(dotRun);
+
+ }
+ else if (format==EPS)
+ {
+ DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
+ if (usePDFLatex)
+ {
+ dotRun->addJob("pdf",absPdfName);
+ }
+ else
+ {
+ dotRun->addJob("ps",absEpsName);
+ }
+ DotManager::instance()->addRun(dotRun);
+ }
+
+ }
+ if (format==BITMAP && writeImageMap)
+ {
+ QCString mapLabel = escapeCharsInString(baseName,FALSE);
+ t << "<center><table><tr><td>";
+
+ if (imgExt=="svg")
+ {
+ t << "<div class=\"center\">";
+ if (regenerate || !writeSVGFigureLink(t,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
+ {
+ if (regenerate)
+ {
+ DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
+ }
+ int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
+ t << "<!-- SVG " << mapId << " -->" << endl;
+ }
+ t << "</div>" << endl;
+ }
+ else
+ {
+ t << "<img src=\"" << relPath << imgName
+ << "\" border=\"0\" alt=\"\" usemap=\"#"
+ << mapLabel << "\"/>" << endl;
+ if (regenerate || !insertMapFile(t,absMapName,relPath,mapLabel))
+ {
+ int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
+ FALSE,QCString(),mapLabel);
+ t << "<!-- MAP " << mapId << " -->" << endl;
+ }
+ }
+
+ t << "</td></tr></table></center>" << endl;
+ }
+ else if (format==EPS)
+ {
+ if (regenerate || !writeVecGfxFigure(t,baseName,absBaseName))
+ {
+ int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
+ t << endl << "% FIG " << figId << endl;
+ }
+ }
+ if (!regenerate) removeDotGraph(absDotName);
+
+ return baseName;
+}
+
+void DotGroupCollaboration::Edge::write( FTextStream &t ) const
+{
+ const char* linkTypeColor[] = {
+ "darkorchid3"
+ ,"orange"
+ ,"blueviolet"
+ ,"darkgreen"
+ ,"firebrick4"
+ ,"grey75"
+ ,"midnightblue"
+ };
+ QCString arrowStyle = "dir=\"none\", style=\"dashed\"";
+ t << " Node" << pNStart->number();
+ t << "->";
+ t << "Node" << pNEnd->number();
+
+ t << " [shape=plaintext";
+ if (links.count()>0) // there are links
+ {
+ t << ", ";
+ // HTML-like edge labels crash on my Mac with Graphviz 2.0! and
+ // are not supported by older version of dot.
+ //
+ //t << label=<<TABLE BORDER=\"0\" CELLBORDER=\"0\">";
+ //QListIterator<Link> lli(links);
+ //Link *link;
+ //for( lli.toFirst(); (link=lli.current()); ++lli)
+ //{
+ // t << "<TR><TD";
+ // if ( !link->url.isEmpty() )
+ // t << " HREF=\"" << link->url << "\"";
+ // t << ">" << link->label << "</TD></TR>";
+ //}
+ //t << "</TABLE>>";
+
+ t << "label=\"";
+ QListIterator<Link> lli(links);
+ Link *link;
+ bool first=TRUE;
+ int count=0;
+ const int maxLabels = 10;
+ for( lli.toFirst(); (link=lli.current()) && count<maxLabels; ++lli,++count)
+ {
+ if (first) first=FALSE; else t << "\\n";
+ t << convertLabel(link->label);
+ }
+ if (count==maxLabels) t << "\\n...";
+ t << "\"";
+
+ }
+ switch( eType )
+ {
+ case thierarchy :
+ arrowStyle = "dir=\"back\", style=\"solid\"";
+ default :
+ t << ", color=\"" << linkTypeColor[(int)eType] << "\"";
+ break;
+ }
+ t << ", " << arrowStyle;
+ t << "];" << endl;
+}
+
+bool DotGroupCollaboration::isTrivial() const
+{
+ return m_usedNodes->count() <= 1;
+}
+
+void DotGroupCollaboration::writeGraphHeader(FTextStream &t,
+ const QCString &title) const
+{
+ t << "digraph ";
+ if (title.isEmpty())
+ {
+ t << "\"Dot Graph\"";
+ }
+ else
+ {
+ t << "\"" << convertToXML(title) << "\"";
+ }
+ t << endl;
+ t << "{" << endl;
+ if (Config_getBool("DOT_TRANSPARENT"))
+ {
+ t << " bgcolor=\"transparent\";" << endl;
+ }
+ t << " edge [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\","
+ "labelfontname=\"" << FONTNAME << "\",labelfontsize=\"" << FONTSIZE << "\"];\n";
+ t << " node [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\",shape=record];\n";
+ t << " rankdir=LR;\n";
+}
+
+void writeDotDirDepGraph(FTextStream &t,DirDef *dd)
+{
+ t << "digraph \"" << dd->displayName() << "\" {\n";
+ if (Config_getBool("DOT_TRANSPARENT"))
+ {
+ t << " bgcolor=transparent;\n";
+ }
+ t << " compound=true\n";
+ t << " node [ fontsize=\"" << FONTSIZE << "\", fontname=\"" << FONTNAME << "\"];\n";
+ t << " edge [ labelfontsize=\"" << FONTSIZE << "\", labelfontname=\"" << FONTNAME << "\"];\n";
+
+ QDict<DirDef> dirsInGraph(257);
+
+ dirsInGraph.insert(dd->getOutputFileBase(),dd);
+ if (dd->parent())
+ {
+ t << " subgraph cluster" << dd->parent()->getOutputFileBase() << " {\n";
+ t << " graph [ bgcolor=\"#ddddee\", pencolor=\"black\", label=\""
+ << dd->parent()->shortName()
+ << "\" fontname=\"" << FONTNAME << "\", fontsize=\"" << FONTSIZE << "\", URL=\"";
+ t << dd->parent()->getOutputFileBase() << Doxygen::htmlFileExtension;
+ t << "\"]\n";
+ }
+ if (dd->isCluster())
+ {
+ t << " subgraph cluster" << dd->getOutputFileBase() << " {\n";
+ t << " graph [ bgcolor=\"#eeeeff\", pencolor=\"black\", label=\"\""
+ << " URL=\"" << dd->getOutputFileBase() << Doxygen::htmlFileExtension
+ << "\"];\n";
+ t << " " << dd->getOutputFileBase() << " [shape=plaintext label=\""
+ << dd->shortName() << "\"];\n";
+
+ // add nodes for sub directories
+ QListIterator<DirDef> sdi(dd->subDirs());
+ DirDef *sdir;
+ for (sdi.toFirst();(sdir=sdi.current());++sdi)
+ {
+ t << " " << sdir->getOutputFileBase() << " [shape=box label=\""
+ << sdir->shortName() << "\"";
+ if (sdir->isCluster())
+ {
+ t << " color=\"red\"";
+ }
+ else
+ {
+ t << " color=\"black\"";
+ }
+ t << " fillcolor=\"white\" style=\"filled\"";
+ t << " URL=\"" << sdir->getOutputFileBase()
+ << Doxygen::htmlFileExtension << "\"";
+ t << "];\n";
+ dirsInGraph.insert(sdir->getOutputFileBase(),sdir);
+ }
+ t << " }\n";
+ }
+ else
+ {
+ t << " " << dd->getOutputFileBase() << " [shape=box, label=\""
+ << dd->shortName() << "\", style=\"filled\", fillcolor=\"#eeeeff\","
+ << " pencolor=\"black\", URL=\"" << dd->getOutputFileBase()
+ << Doxygen::htmlFileExtension << "\"];\n";
+ }
+ if (dd->parent())
+ {
+ t << " }\n";
+ }
+
+ // add nodes for other used directories
+ QDictIterator<UsedDir> udi(*dd->usedDirs());
+ UsedDir *udir;
+ //printf("*** For dir %s\n",shortName().data());
+ for (udi.toFirst();(udir=udi.current());++udi)
+ // for each used dir (=directly used or a parent of a directly used dir)
+ {
+ const DirDef *usedDir=udir->dir();
+ DirDef *dir=dd;
+ while (dir)
+ {
+ //printf("*** check relation %s->%s same_parent=%d !%s->isParentOf(%s)=%d\n",
+ // dir->shortName().data(),usedDir->shortName().data(),
+ // dir->parent()==usedDir->parent(),
+ // usedDir->shortName().data(),
+ // shortName().data(),
+ // !usedDir->isParentOf(this)
+ // );
+ if (dir!=usedDir && dir->parent()==usedDir->parent() &&
+ !usedDir->isParentOf(dd))
+ // include if both have the same parent (or no parent)
+ {
+ t << " " << usedDir->getOutputFileBase() << " [shape=box label=\""
+ << usedDir->shortName() << "\"";
+ if (usedDir->isCluster())
+ {
+ if (!Config_getBool("DOT_TRANSPARENT"))
+ {
+ t << " fillcolor=\"white\" style=\"filled\"";
+ }
+ t << " color=\"red\"";
+ }
+ t << " URL=\"" << usedDir->getOutputFileBase()
+ << Doxygen::htmlFileExtension << "\"];\n";
+ dirsInGraph.insert(usedDir->getOutputFileBase(),usedDir);
+ break;
+ }
+ dir=dir->parent();
+ }
+ }
+
+ // add relations between all selected directories
+ DirDef *dir;
+ QDictIterator<DirDef> di(dirsInGraph);
+ for (di.toFirst();(dir=di.current());++di) // foreach dir in the graph
+ {
+ QDictIterator<UsedDir> udi(*dir->usedDirs());
+ UsedDir *udir;
+ for (udi.toFirst();(udir=udi.current());++udi) // foreach used dir
+ {
+ const DirDef *usedDir=udir->dir();
+ if ((dir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
+ (usedDir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
+ !usedDir->isParentOf(dir) && // don't point to own parent
+ dirsInGraph.find(usedDir->getOutputFileBase())) // only point to nodes that are in the graph
+ {
+ QCString relationName;
+ relationName.sprintf("dir_%06d_%06d",dir->dirCount(),usedDir->dirCount());
+ if (Doxygen::dirRelations.find(relationName)==0)
+ {
+ // new relation
+ Doxygen::dirRelations.append(relationName,
+ new DirRelation(relationName,dir,udir));
+ }
+ int nrefs = udir->filePairs().count();
+ t << " " << dir->getOutputFileBase() << "->"
+ << usedDir->getOutputFileBase();
+ t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5";
+ t << " headhref=\"" << relationName << Doxygen::htmlFileExtension
+ << "\"];\n";
+ }
+ }
+ }
+
+ t << "}\n";
+}