diff options
Diffstat (limited to 'src')
45 files changed, 5139 insertions, 4997 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 37a21ff..cb1fa22 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -185,6 +185,16 @@ add_library(_doxygen STATIC docparser.cpp docsets.cpp dot.cpp + dotcallgraph.cpp + dotclassgraph.cpp + dotdirdeps.cpp + dotfilepatcher.cpp + dotgfxhierarchytable.cpp + dotgraph.cpp + dotgroupcollaboration.cpp + dotincldepgraph.cpp + dotnode.cpp + dotrunner.cpp doxygen.cpp eclipsehelp.cpp emoji.cpp diff --git a/src/classdef.cpp b/src/classdef.cpp index f7abbe4..a7f24ed 100644 --- a/src/classdef.cpp +++ b/src/classdef.cpp @@ -33,6 +33,8 @@ #include "example.h" #include "outputlist.h" #include "dot.h" +#include "dotclassgraph.h" +#include "dotrunner.h" #include "defargs.h" #include "debug.h" #include "docparser.h" @@ -1717,7 +1719,7 @@ void ClassDefImpl::writeInheritanceGraph(OutputList &ol) const (Config_getBool(CLASS_DIAGRAMS) || Config_getBool(CLASS_GRAPH))) // write class diagram using dot { - DotClassGraph inheritanceGraph(this,DotNode::Inheritance); + DotClassGraph inheritanceGraph(this,Inheritance); if (inheritanceGraph.isTooBig()) { warn_uncond("Inheritance graph for '%s' not generated, too many nodes. Consider increasing DOT_GRAPH_MAX_NODES.\n",name().data()); @@ -1840,7 +1842,7 @@ void ClassDefImpl::writeCollaborationGraph(OutputList &ol) const { if (Config_getBool(HAVE_DOT) /*&& Config_getBool(COLLABORATION_GRAPH)*/) { - DotClassGraph usageImplGraph(this,DotNode::Collaboration); + DotClassGraph usageImplGraph(this,Collaboration); if (!usageImplGraph.isTrivial()) { ol.pushGeneratorState(); diff --git a/src/context.cpp b/src/context.cpp index 26a5b0e..66206b1 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -39,6 +39,12 @@ #include "latexgen.h" #include "latexdocvisitor.h" #include "dot.h" +#include "dotcallgraph.h" +#include "dotclassgraph.h" +#include "dotdirdeps.h" +#include "dotgfxhierarchytable.h" +#include "dotgroupcollaboration.h" +#include "dotincldepgraph.h" #include "diagram.h" #include "example.h" #include "membername.h" @@ -1926,7 +1932,7 @@ class ClassContext::Private : public DefinitionContext<ClassContext::Private> Cachable &cache = getCache(); if (!cache.classGraph) { - cache.classGraph.reset(new DotClassGraph(m_classDef,DotNode::Inheritance)); + cache.classGraph.reset(new DotClassGraph(m_classDef,Inheritance)); } return cache.classGraph.get(); } @@ -2048,7 +2054,7 @@ class ClassContext::Private : public DefinitionContext<ClassContext::Private> Cachable &cache = getCache(); if (!cache.collaborationGraph) { - cache.collaborationGraph.reset(new DotClassGraph(m_classDef,DotNode::Collaboration)); + cache.collaborationGraph.reset(new DotClassGraph(m_classDef,Collaboration)); } return cache.collaborationGraph.get(); } diff --git a/src/defgen.cpp b/src/defgen.cpp index 6601f6e..ab19c2a 100644 --- a/src/defgen.cpp +++ b/src/defgen.cpp @@ -27,6 +27,7 @@ #include "defargs.h" #include "outputgen.h" #include "dot.h" +#include "dotclassgraph.h" #include "arguments.h" #include "memberlist.h" #include "namespacedef.h" @@ -466,14 +467,14 @@ void generateDEFForClass(ClassDef *cd,FTextStream &t) t << " cp-documentation = <<_EnD_oF_dEf_TeXt_" << endl << cd->documentation() << endl << "_EnD_oF_dEf_TeXt_;" << endl; - DotClassGraph inheritanceGraph(cd,DotNode::Inheritance); + DotClassGraph inheritanceGraph(cd,Inheritance); if (!inheritanceGraph.isTrivial()) { t << " cp-inheritancegraph = <<_EnD_oF_dEf_TeXt_" << endl; inheritanceGraph.writeDEF(t); t << endl << "_EnD_oF_dEf_TeXt_;" << endl; } - DotClassGraph collaborationGraph(cd,DotNode::Collaboration); + DotClassGraph collaborationGraph(cd,Collaboration); if (!collaborationGraph.isTrivial()) { t << " cp-collaborationgraph = <<_EnD_oF_dEf_TeXt_" << endl; diff --git a/src/dirdef.cpp b/src/dirdef.cpp index 3803335..5db8b99 100644 --- a/src/dirdef.cpp +++ b/src/dirdef.cpp @@ -8,6 +8,7 @@ #include "language.h" #include "message.h" #include "dot.h" +#include "dotdirdeps.h" #include "layout.h" #include "ftextstream.h" #include "config.h" diff --git a/src/docbookgen.cpp b/src/docbookgen.cpp index 8a062fc..c6bd1c0 100644 --- a/src/docbookgen.cpp +++ b/src/docbookgen.cpp @@ -33,6 +33,11 @@ #include "defargs.h" #include "outputgen.h" #include "dot.h" +#include "dotcallgraph.h" +#include "dotclassgraph.h" +#include "dotdirdeps.h" +#include "dotgroupcollaboration.h" +#include "dotincldepgraph.h" #include "pagedef.h" #include "filename.h" #include "version.h" @@ -1095,7 +1100,7 @@ void DocbookGenerator::startGroupCollaboration() { DB_GEN_C } -void DocbookGenerator::endGroupCollaboration(const DotGroupCollaboration &g) +void DocbookGenerator::endGroupCollaboration(DotGroupCollaboration &g) { DB_GEN_C g.writeGraph(t,GOF_BITMAP,EOF_DocBook,Config_getString(DOCBOOK_OUTPUT),fileName,relPath,FALSE); @@ -1104,7 +1109,7 @@ void DocbookGenerator::startDotGraph() { DB_GEN_C } -void DocbookGenerator::endDotGraph(const DotClassGraph &g) +void DocbookGenerator::endDotGraph(DotClassGraph &g) { DB_GEN_C g.writeGraph(t,GOF_BITMAP,EOF_DocBook,Config_getString(DOCBOOK_OUTPUT),fileName,relPath,TRUE,FALSE); @@ -1113,7 +1118,7 @@ void DocbookGenerator::startInclDepGraph() { DB_GEN_C } -void DocbookGenerator::endInclDepGraph(const DotInclDepGraph &g) +void DocbookGenerator::endInclDepGraph(DotInclDepGraph &g) { DB_GEN_C QCString fn = g.writeGraph(t,GOF_BITMAP,EOF_DocBook,Config_getString(DOCBOOK_OUTPUT), fileName,relPath,FALSE); @@ -1122,7 +1127,7 @@ void DocbookGenerator::startCallGraph() { DB_GEN_C } -void DocbookGenerator::endCallGraph(const DotCallGraph &g) +void DocbookGenerator::endCallGraph(DotCallGraph &g) { DB_GEN_C QCString fn = g.writeGraph(t,GOF_BITMAP,EOF_DocBook,Config_getString(DOCBOOK_OUTPUT), fileName,relPath,FALSE); @@ -1131,7 +1136,7 @@ void DocbookGenerator::startDirDepGraph() { DB_GEN_C } -void DocbookGenerator::endDirDepGraph(const DotDirDeps &g) +void DocbookGenerator::endDirDepGraph(DotDirDeps &g) { DB_GEN_C QCString fn = g.writeGraph(t,GOF_BITMAP,EOF_DocBook,Config_getString(DOCBOOK_OUTPUT), fileName,relPath,FALSE); diff --git a/src/docbookgen.h b/src/docbookgen.h index 8674150..8f71722 100644 --- a/src/docbookgen.h +++ b/src/docbookgen.h @@ -281,16 +281,16 @@ class DocbookGenerator : public OutputGenerator void startClassDiagram(); void endClassDiagram(const ClassDiagram &,const char *,const char *); void startDotGraph(); - void endDotGraph(const DotClassGraph &g); + void endDotGraph(DotClassGraph &g); void startInclDepGraph(); - void endInclDepGraph(const DotInclDepGraph &g); + void endInclDepGraph(DotInclDepGraph &g); void startGroupCollaboration(); - void endGroupCollaboration(const DotGroupCollaboration &g); + void endGroupCollaboration(DotGroupCollaboration &g); void startCallGraph(); - void endCallGraph(const DotCallGraph &g); + void endCallGraph(DotCallGraph &g); void startDirDepGraph(); - void endDirDepGraph(const DotDirDeps &g); - void writeGraphicalHierarchy(const DotGfxHierarchyTable &g){DB_GEN_NEW}; + void endDirDepGraph(DotDirDeps &g); + void writeGraphicalHierarchy(DotGfxHierarchyTable &g){DB_GEN_NEW}; void startQuickIndices(){DB_GEN_EMPTY}; void endQuickIndices(){DB_GEN_EMPTY}; void writeSplitBar(const char *){DB_GEN_EMPTY}; diff --git a/src/dot.cpp b/src/dot.cpp index 5aca277..5cdf92c 100644 --- a/src/dot.cpp +++ b/src/dot.cpp @@ -1,13 +1,10 @@ /***************************************************************************** * - * - * - * - * Copyright (C) 1997-2015 by Dimitri van Heesch. + * Copyright (C) 1997-2019 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 + * 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. * @@ -26,392 +23,47 @@ #include <qwaitcondition.h> #include <qregexp.h> +#include "config.h" #include "dot.h" -#include "doxygen.h" -#include "message.h" +#include "dotrunner.h" +#include "dotfilepatcher.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 "message.h" #include "ftextstream.h" -#include "md5.h" -#include "memberlist.h" -#include "groupdef.h" -#include "classlist.h" -#include "filename.h" -#include "namespacedef.h" -#include "memberdef.h" -#include "membergroup.h" +#include "doxygen.h" +#include "language.h" +#include "index.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" -"<style type=\"text/css\"><![CDATA[\n" -".edge:hover path { stroke: red; }\n" -".edge:hover polygon { stroke: red; fill: red; }\n" -"]]></style>\n" -"<script type=\"text/javascript\"><![CDATA[\n" -"var edges = document.getElementsByTagName('g');\n" -"if (edges && edges.length) {\n" -" for (var i=0;i<edges.length;i++) {\n" -" if (edges[i].id.substr(0,4)=='edge') {\n" -" edges[i].setAttribute('class','edge');\n" -" }\n" -" }\n" -"}\n" -"]]></script>\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[] = -// navigation panel -" <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" -// zoom in -" <use id=\"zoomplus\" xlink:href=\"#zoomPlus\" x=\"17\" y=\"9\" onmousedown=\"handleZoom(evt,'in')\"/>\n" -// zoom out -" <use id=\"zoomminus\" xlink:href=\"#zoomMin\" x=\"42\" y=\"9\" onmousedown=\"handleZoom(evt,'out')\"/>\n" -// reset zoom -" <use id=\"reset\" xlink:href=\"#resetDef\" x=\"30\" y=\"36\" onmousedown=\"handleReset()\"/>\n" -// arrow up -" <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" -// arrow right -" <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" -// arrow down -" <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" -// arrow left -" <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" -// link to original SVG -" <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 int DOT_NUM_THREADS; // will be initialized in initDot //-------------------------------------------------------------------- -/*! 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 - "orange" // type constraint -}; - -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 - "orange" // type constraint -}; - -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 = +void initDot() { - umlEdgeColorMap, umlArrowStyleMap, umlEdgeStyleMap -}; - - -static QCString convertLabel(const QCString &l); - -static QCString getDotFontName() -{ - static QCString dotFontName = Config_getString(DOT_FONTNAME); - if (dotFontName.isEmpty()) + DotGraph::DOT_FONTNAME = Config_getString(DOT_FONTNAME); + if (DotGraph::DOT_FONTNAME.isEmpty()) { - //dotFontName="FreeSans.ttf"; - dotFontName="Helvetica"; + DotGraph::DOT_FONTNAME="Helvetica"; } - return dotFontName; -} -static int getDotFontSize() -{ - static int dotFontSize = Config_getInt(DOT_FONTSIZE); - if (dotFontSize<4) dotFontSize=4; - return dotFontSize; -} + DotGraph::DOT_FONTSIZE = Config_getInt(DOT_FONTSIZE); + if (DotGraph::DOT_FONTSIZE<4) DotGraph::DOT_FONTSIZE=4; -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 << "\"" << convertLabel(title) << "\""; - } - t << endl << "{" << endl; - if (interactiveSVG) // insert a comment to force regeneration when this - // option is toggled - { - t << " // INTERACTIVE_SVG=YES\n"; - } - t << " // LATEX_PDF_SIZE\n"; // write placeholder for LaTeX PDF bounding box size repacement - 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"; -} + DOT_NUM_THREADS = Config_getInt(DOT_NUM_THREADS); + if (DOT_NUM_THREADS > 32) DOT_NUM_THREADS = 32; + if (DOT_NUM_THREADS <= 0) DOT_NUM_THREADS = QMAX(2,QThread::idealThreadCount()+1); -static void writeGraphFooter(FTextStream &t) -{ - t << "}" << endl; -} + // these are copied to be sure to be thread save + DotRunner::DOT_CLEANUP = Config_getBool(DOT_CLEANUP); + DotRunner::DOT_MULTI_TARGETS = Config_getBool(DOT_MULTI_TARGETS); + DotRunner::DOT_EXE.init(Config_getString(DOT_PATH) + "dot"); -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; - bool setTarget = FALSE; - 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(); - if (result != "") setTarget = TRUE; - } - result+= href+"=\""; - result+=externalRef(relPath,ref,TRUE); - result+= url + "\""; - } - else // should not happen, but handle properly anyway - { - result = href+"=\"" + link + "\""; - } - } - if (!target.isEmpty() && !setTarget) - { - result+=" target=\""+target+"\""; - } - QCString leftPart = buf.left(indexS); - QCString rightPart = buf.mid(indexE+1); - return leftPart + result + rightPart; - } - else - { - return buf; - } + DotGraph::IMG_EXT = getDotImageExtension(); } -/*! 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("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.rawData(),maxLineLen); - if (numBytes>0) - { - buf.resize(numBytes+1); - - if (buf.left(5)=="<area") - { - QCString replBuf = replaceRef(buf,relPath,urlOnly,context); - // strip id="..." from replBuf since the id's are not needed and not unique. - int indexS = replBuf.find("id=\""), indexE; - if (indexS>0 && (indexE=replBuf.find('"',indexS+4))!=-1) - { - t << replBuf.left(indexS-1) << replBuf.right(replBuf.length() - indexE - 1); - } - else - { - t << replBuf; - } - } - } - } - return TRUE; -} static QCString g_dotFontPath; @@ -450,147 +102,6 @@ static void unsetDotFontPath() g_dotFontPath=""; } -static bool resetPDFSize(const int width,const int height, const char *base) -{ - QString tmpName = QString::fromUtf8(QCString(base)+".tmp"); - QString patchFile = QString::fromUtf8(QCString(base)+".dot"); - if (!QDir::current().rename(patchFile,tmpName)) - { - err("Failed to rename file %s to %s!\n",patchFile.data(),tmpName.data()); - return FALSE; - } - QFile fi(tmpName); - QFile fo(patchFile); - if (!fi.open(IO_ReadOnly)) - { - err("problem opening file %s for patching!\n",tmpName.data()); - QDir::current().rename(tmpName,patchFile); - return FALSE; - } - if (!fo.open(IO_WriteOnly)) - { - err("problem opening file %s for patching!\n",patchFile.data()); - QDir::current().rename(tmpName,patchFile); - fi.close(); - return FALSE; - } - FTextStream t(&fo); - const int maxLineLen=100*1024; - while (!fi.atEnd()) // foreach line - { - QCString line(maxLineLen); - int numBytes = fi.readLine(line.rawData(),maxLineLen); - if (numBytes<=0) - { - break; - } - line.resize(numBytes+1); - if (line.find("LATEX_PDF_SIZE") != -1) - { - double scale = (width > height ? width : height)/double(MAX_LATEX_GRAPH_INCH); - t << " size=\""<<width/scale << "," <<height/scale <<"\";\n"; - } - else - t << line; - } - fi.close(); - fo.close(); - // remove temporary file - QDir::current().remove(tmpName); - return TRUE; -} -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) { @@ -638,8 +149,8 @@ static void writeSVGNotSupported(FTextStream &out) // 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) +bool writeSVGFigureLink(FTextStream &out,const QCString &relPath, + const QCString &baseName,const QCString &absImgName) { int width=600,height=600; if (!readSVGSize(absImgName,&width,&height)) @@ -680,593 +191,6 @@ static bool writeSVGFigureLink(FTextStream &out,const QCString &relPath, 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 char *imgExt, const char *imgName) -{ - if (qstrcmp(imgExt,"png")==0) - { - FILE *f = portable_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("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 - ); - } - } - else - { - err("Could not read image `%s' generated by dot!\n",imgName); - } - fclose(f); - } - else - { - err("Could not open image `%s' generated by dot!\n",imgName); - } - } -} - -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,FALSE); - 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.rawData(),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; -} - -//-------------------------------------------------------------------- - -inline int DotNode::findParent( DotNode *n ) -{ - if ( !m_parents ) return -1; - return m_parents->find(n); -} - -//-------------------------------------------------------------------- - -int DotNodeList::compareValues(const DotNode *n1,const DotNode *n2) const -{ - return qstricmp(n1->m_label,n2->m_label); -} - -//-------------------------------------------------------------------- - -DotRunner::DotRunner(const QCString &file,const QCString &path, - bool checkResult,const QCString &imageName) - : m_dotExe(Config_getString(DOT_PATH)+"dot"), - m_file(file), m_path(path), - m_checkResult(checkResult), m_imageName(imageName), - m_imgExt(getDotImageExtension()) -{ - static bool dotCleanUp = Config_getBool(DOT_CLEANUP); - static bool dotMultiTargets = Config_getBool(DOT_MULTI_TARGETS); - m_cleanUp = dotCleanUp; - m_multiTargets = dotMultiTargets; - m_jobs.setAutoDelete(TRUE); -} - -void DotRunner::addJob(const char *format,const char *output, const char *base) -{ - QCString args = QCString("-T")+format+" -o \""+output+"\""; - m_jobs.append(new DotConstString(args, base)); -} - -void DotRunner::addPostProcessing(const char *cmd,const char *args) -{ - m_postCmd.set(cmd); - m_postArgs.set(args); -} - -bool DotRunner::run() -{ - int exitCode=0; - int width=0,height=0; - - QCString dotArgs; - QListIterator<DotConstString> li(m_jobs); - DotConstString *s; - if (m_multiTargets) - { - dotArgs=QCString("\"")+m_file.data()+"\""; - for (li.toFirst();(s=li.current());++li) - { - dotArgs+=' '; - dotArgs+=s->data(); - } - if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0) goto error; - dotArgs=QCString("\"")+m_file.data()+"\""; - bool redo = FALSE; - for (li.toFirst();(s=li.current());++li) - { - if (s->pdfData()) - { - if (!readBoundingBox(QCString(s->pdfData())+".pdf",&width,&height,FALSE)) goto error; - if ((width > MAX_LATEX_GRAPH_SIZE) || (height > MAX_LATEX_GRAPH_SIZE)) - { - if (!resetPDFSize(width,height,s->pdfData())) goto error; - dotArgs+=' '; - dotArgs+=s->data(); - redo = TRUE; - } - } - } - if (redo) - { - if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0) goto error; - } - } - else - { - for (li.toFirst();(s=li.current());++li) - { - dotArgs=QCString("\"")+m_file.data()+"\" "+s->data(); - if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0) goto error; - if (s->pdfData()) - { - if (!readBoundingBox(QCString(s->pdfData())+".pdf",&width,&height,FALSE)) goto error; - if ((width > MAX_LATEX_GRAPH_SIZE) || (height > MAX_LATEX_GRAPH_SIZE)) - { - if (!resetPDFSize(width,height,s->pdfData())) goto error; - if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0) goto error; - } - } - } - } - if (!m_postCmd.isEmpty() && portable_system(m_postCmd.data(),m_postArgs.data())!=0) - { - err("Problems running '%s' as a post-processing step for dot output\n",m_postCmd.data()); - return FALSE; - } - if (m_checkResult) - { - checkDotResult(m_imgExt.data(),m_imageName.data()); - } - if (m_cleanUp) - { - //printf("removing dot file %s\n",m_file.data()); - //QDir(path).remove(file); - m_cleanupItem.file.set(m_file.data()); - m_cleanupItem.path.set(m_path.data()); - } - return TRUE; -error: - err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n", - exitCode,m_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); - } - QString tmpName = QString::fromUtf8(m_patchFile+".tmp"); - QString patchFile = QString::fromUtf8(m_patchFile); - if (!QDir::current().rename(patchFile,tmpName)) - { - err("Failed to rename file %s to %s!\n",m_patchFile.data(),tmpName.data()); - return FALSE; - } - QFile fi(tmpName); - QFile fo(patchFile); - if (!fi.open(IO_ReadOnly)) - { - err("problem opening file %s for patching!\n",tmpName.data()); - QDir::current().rename(tmpName,patchFile); - return FALSE; - } - if (!fo.open(IO_WriteOnly)) - { - err("problem opening file %s for patching!\n",m_patchFile.data()); - QDir::current().rename(tmpName,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.rawData(),maxLineLen); - if (numBytes<=0) - { - break; - } - line.resize(numBytes+1); - - //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()) - { - QGString result; - FTextStream tt(&result); - 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()); - convertMapFile(tt,map->mapFile,map->relPath,map->urlOnly,map->context); - if (!result.isEmpty()) - { - t << "<map name=\"" << map->label << "\" id=\"" << map->label << "\">" << endl; - t << result; - 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()); - if (!writeVecGfxFigure(t,map->label,map->mapFile)) - { - err("problem writing FIG %d figure!\n",mapId); - return FALSE; - } - } - else // error invalid map id! - { - err("Found invalid bounding FIG %d 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("problem opening file %s for reading!\n",tmpName.data()); - return FALSE; - } - if (!fo.open(IO_WriteOnly)) - { - err("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.rawData(),maxLineLen); - if (numBytes<=0) - { - break; - } - line.resize(numBytes+1); - 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(DotRunnerQueue *queue) - : m_queue(queue) -{ - m_cleanupItems.setAutoDelete(TRUE); -} - -void DotWorkerThread::run() -{ - DotRunner *runner; - while ((runner=m_queue->dequeue())) - { - runner->run(); - const 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.data()).remove(ci->file.data()); - } -} - //-------------------------------------------------------------------- DotManager *DotManager::m_theInstance = 0; @@ -1282,15 +206,13 @@ DotManager *DotManager::instance() DotManager::DotManager() : m_dotMaps(1009) { - m_dotRuns.setAutoDelete(TRUE); + m_runners.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 (DOT_NUM_THREADS!=1) { - if (numThreads==0) numThreads = QMAX(2,QThread::idealThreadCount()+1); - for (i=0;i<numThreads;i++) + for (i=0;i<DOT_NUM_THREADS;i++) { DotWorkerThread *thread = new DotWorkerThread(m_queue); thread->start(); @@ -1312,11 +234,26 @@ DotManager::~DotManager() delete m_queue; } -void DotManager::addRun(DotRunner *run) +DotRunner* DotManager::createRunner(const QCString& absDotName, const QCString& md5Hash) { - m_dotRuns.append(run); + DotRunner * run = m_runners.find(absDotName); + if (run == 0) + { + run = new DotRunner(absDotName, md5Hash); + m_runners.insert(absDotName, run); + } + else + { + // we have a match + if (md5Hash != QCString(run->getMd5Hash().data())) + { + err("md5 hash does not match for two different runs of %s !\n", absDotName.data()); + } + } + return run; } + int DotManager::addMap(const QCString &file,const QCString &mapFile, const QCString &relPath,bool urlOnly,const QCString &context, const QCString &label) @@ -1369,7 +306,7 @@ int DotManager::addSVGObject(const QCString &file,const QCString &baseName, bool DotManager::run() { - uint numDotRuns = m_dotRuns.count(); + uint numDotRuns = m_runners.count(); uint numDotMaps = m_dotMaps.count(); if (numDotRuns+numDotMaps>1) { @@ -1383,7 +320,7 @@ bool DotManager::run() } } int i=1; - QListIterator<DotRunner> li(m_dotRuns); + QDictIterator<DotRunner> li(m_runners); bool setPath=FALSE; if (Config_getBool(GENERATE_HTML)) @@ -1451,11 +388,6 @@ bool DotManager::run() { 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) @@ -1494,2736 +426,59 @@ bool DotManager::run() //-------------------------------------------------------------------- - -/*! 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,const 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) - , m_renumbered(false) -{ -} - -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) -{ - QString bBefore("\\_/<({[: =-+@%#~?$"); // break before character set - QString bAfter(">]),:;|"); // break after character set - QString p(l); - if (p.isEmpty()) return QCString(); - QString result; - QChar c,pc=0; - uint idx = 0; - int len=p.length(); - int charsLeft=len; - int sinceLast=0; - int foldLen=17; // ideal text length - while (idx < p.length()) - { - c = p[idx++]; - QString 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: replacement=c; 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) && p[idx].category()==QChar::Letter_Uppercase) - { - result+=replacement; - result+="\\l"; - foldLen = (foldLen+sinceLast+1)/2; - sinceLast=0; - } - else if (charsLeft>foldLen/3 && sinceLast>foldLen && bAfter.contains(c) && (c!=':' || p[idx]!=':')) - { - result+=replacement; - result+="\\l"; - foldLen = (foldLen+sinceLast+1)/2; - sinceLast=0; - } - else - { - result+=replacement; - sinceLast++; - } - charsLeft--; - pc=c; - } - return result.utf8(); -} - -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; - case '\\': result+="\\\\"; break; - default: result+=c; break; - } - } - return result; -} - -static void writeBoxMemberList(FTextStream &t, - char prot,MemberList *ml,const 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); - } - } - } - } -} - -static QCString stripProtectionPrefix(const QCString &s) -{ - if (!s.isEmpty() && (s[0]=='-' || s[0]=='+' || s[0]=='~' || s[0]=='#')) - { - return s.mid(1); - } - else - { - return s; - } -} - -void DotNode::writeBox(FTextStream &t, - GraphType gt, - GraphOutputFormat /*format*/, - bool hasNonReachableChildren - ) -{ - const char *labCol = - m_url.isEmpty() ? "grey75" : // non link - ( - (hasNonReachableChildren) ? "red" : "black" - ); - t << " Node" << m_number << " [label=\""; - static bool umlLook = Config_getBool(UML_LOOK); - - if (m_classDef && umlLook && (gt==Inheritance || gt==Collaboration)) - { - // add names shown as relations to a dictionary, so we don't show - // them as attributes as well - QDict<void> arrowNames(17); - if (m_edgeInfo) - { - // for each edge - QListIterator<EdgeInfo> li(*m_edgeInfo); - EdgeInfo *ei; - for (li.toFirst();(ei=li.current());++li) - { - if (!ei->m_label.isEmpty()) // labels joined by \n - { - int li=ei->m_label.find('\n'); - int p=0; - QCString lab; - while ((li=ei->m_label.find('\n',p))!=-1) - { - lab = stripProtectionPrefix(ei->m_label.mid(p,li-p)); - arrowNames.insert(lab,(void*)0x8); - p=li+1; - } - lab = stripProtectionPrefix(ei->m_label.right(ei->m_label.length()-p)); - arrowNames.insert(lab,(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(MemberListType_pubAttribs),m_classDef,FALSE,&arrowNames); - writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubStaticAttribs),m_classDef,TRUE,&arrowNames); - writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_properties),m_classDef,FALSE,&arrowNames); - writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacAttribs),m_classDef,FALSE,&arrowNames); - writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacStaticAttribs),m_classDef,TRUE,&arrowNames); - writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proAttribs),m_classDef,FALSE,&arrowNames); - writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proStaticAttribs),m_classDef,TRUE,&arrowNames); - if (extractPrivate) - { - writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priAttribs),m_classDef,FALSE,&arrowNames); - writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priStaticAttribs),m_classDef,TRUE,&arrowNames); - } - t << "|"; - writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubMethods),m_classDef); - writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubStaticMethods),m_classDef,TRUE); - writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubSlots),m_classDef); - writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacMethods),m_classDef); - writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacStaticMethods),m_classDef,TRUE); - writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proMethods),m_classDef); - writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proStaticMethods),m_classDef,TRUE); - writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proSlots),m_classDef); - if (extractPrivate) - { - writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priMethods),m_classDef); - writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priStaticMethods),m_classDef,TRUE); - writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_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); - if (!dotTransparent) - { - t << ",color=\"" << labCol << "\", fillcolor=\""; - 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) << "\""; - } - else - { - t << ",tooltip=\" \""; // space in tooltip is required otherwise still something like 'Node0' is used - } - t << "];" << endl; -} - -void DotNode::writeArrow(FTextStream &t, - GraphType gt, - GraphOutputFormat format, - DotNode *cn, - EdgeInfo *ei, - bool topDown, - bool pointBack - ) -{ - t << " Node"; - if (topDown) - t << cn->number(); - else - t << m_number; - t << " -> Node"; - if (topDown) - t << m_number; - else - t << cn->number(); - t << " ["; - - static bool umlLook = Config_getBool(UML_LOOK); - const EdgeProperties *eProps = umlLook ? ¨EdgeProps : &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==GOF_BITMAP) t << ",fontname=\"" << FONTNAME << "\""; - t << "];" << endl; -} - -void DotNode::write(FTextStream &t, - GraphType gt, - GraphOutputFormat format, - bool topDown, - bool toChildren, - bool backArrows - ) -{ - //printf("DotNode::write(%d) name=%s this=%p written=%d visible=%d\n",m_distance,m_label.data(),this,m_written,m_visible); - 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); - 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); - } - cn->write(t,gt,format,topDown,toChildren,backArrows); - } - } - 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 - ); - } - pn->write(t,gt,format,TRUE,FALSE,backArrows); - } - } - } - //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); - const char *refPtr = url.data(); - char *urlPtr = strchr(url.rawData(),'$'); - 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::Orange2: t << "type-constraint"; 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::writeDocbook(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); - const char *refPtr = url.data(); - char *urlPtr = strchr(url.rawData(),'$'); - 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::Orange2: t << "type-constraint"; 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); - const char *refPtr = url.data(); - char *urlPtr = strchr(url.rawData(),'$'); - 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::Orange2: t << "type-constraint"; 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); - } - } - } -} - -void DotNode::renumberNodes(int &number) -{ - m_number = number++; - if (m_children) - { - QListIterator<DotNode> dnlic(*m_children); - DotNode *cn; - for (dnlic.toFirst();(cn=dnlic.current());++dnlic) - { - if (!cn->m_renumbered) - { - cn->m_renumbered = true; - cn->renumberNodes(number); - } - } - } -} - -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; -} - -//-------------------------------------------------------------------- - -void DotGfxHierarchyTable::createGraph(DotNode *n,FTextStream &out, - const char *path,const char *fileName,int id) const +class GraphLegendDotGraph : public DotGraph { - QDir d(path); - QCString baseName; - QCString imgExt = getDotImageExtension(); - QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT); - if (m_prefix.isEmpty()) - baseName.sprintf("inherit_graph_%d",id); - else - baseName.sprintf("%sinherit_graph_%d",m_prefix.data(),id); - 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) + private: + virtual QCString getBaseName() const { - node->clearWriteFlag(); + return "graph_legend"; } - } - for (dnli2.toFirst();(node=dnli2.current());++dnli2) - { - if (node->m_subgraphId==n->m_subgraphId) - { - node->write(md5stream,DotNode::Hierarchy,GOF_BITMAP,FALSE,TRUE,TRUE); - } - } - writeGraphFooter(md5stream); - uchar md5_sig[16]; - QCString sigStr(33); - MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig); - MD5SigToString(md5_sig,sigStr.rawData(),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(); - DotRunner *dotRun = new DotRunner(dotName,d.absPath().data(),TRUE,absImgName); - dotRun->addJob(imgFmt,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); - if (imgExt=="svg") // vector graphics - { - if (regenerate || !writeSVGFigureLink(out,QCString(),baseName,absImgName)) + virtual void computeTheGraph() { - if (regenerate) - { - DotManager::instance()->addSVGConversion(absImgName,QCString(), - FALSE,QCString(),FALSE,0); - } - int mapId = DotManager::instance()->addSVGObject(fileName,baseName, - absImgName,QCString()); - out << "<!-- SVG " << mapId << " -->" << endl; + FTextStream md5stream(&m_theGraph); + writeGraphHeader(md5stream,theTranslator->trLegendTitle()); + md5stream << " Node9 [shape=\"box\",label=\"Inherited\",fontsize=\"" << DOT_FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << DOT_FONTNAME << "\",fillcolor=\"grey75\",style=\"filled\" fontcolor=\"black\"];\n"; + md5stream << " Node10 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << DOT_FONTSIZE << "\",style=\"solid\",fontname=\"" << DOT_FONTNAME << "\"];\n"; + md5stream << " Node10 [shape=\"box\",label=\"PublicBase\",fontsize=\"" << DOT_FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << DOT_FONTNAME << "\",color=\"black\",URL=\"$classPublicBase" << Doxygen::htmlFileExtension << "\"];\n"; + md5stream << " Node11 -> Node10 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << DOT_FONTSIZE << "\",style=\"solid\",fontname=\"" << DOT_FONTNAME << "\"];\n"; + md5stream << " Node11 [shape=\"box\",label=\"Truncated\",fontsize=\"" << DOT_FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << DOT_FONTNAME << "\",color=\"red\",URL=\"$classTruncated" << Doxygen::htmlFileExtension << "\"];\n"; + md5stream << " Node13 -> Node9 [dir=\"back\",color=\"darkgreen\",fontsize=\"" << DOT_FONTSIZE << "\",style=\"solid\",fontname=\"" << DOT_FONTNAME << "\"];\n"; + md5stream << " Node13 [shape=\"box\",label=\"ProtectedBase\",fontsize=\"" << DOT_FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << DOT_FONTNAME << "\",color=\"black\",URL=\"$classProtectedBase" << Doxygen::htmlFileExtension << "\"];\n"; + md5stream << " Node14 -> Node9 [dir=\"back\",color=\"firebrick4\",fontsize=\"" << DOT_FONTSIZE << "\",style=\"solid\",fontname=\"" << DOT_FONTNAME << "\"];\n"; + md5stream << " Node14 [shape=\"box\",label=\"PrivateBase\",fontsize=\"" << DOT_FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << DOT_FONTNAME << "\",color=\"black\",URL=\"$classPrivateBase" << Doxygen::htmlFileExtension << "\"];\n"; + md5stream << " Node15 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << DOT_FONTSIZE << "\",style=\"solid\",fontname=\"" << DOT_FONTNAME << "\"];\n"; + md5stream << " Node15 [shape=\"box\",label=\"Undocumented\",fontsize=\"" << DOT_FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << DOT_FONTNAME << "\",color=\"grey75\"];\n"; + md5stream << " Node16 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << DOT_FONTSIZE << "\",style=\"solid\",fontname=\"" << DOT_FONTNAME << "\"];\n"; + md5stream << " Node16 [shape=\"box\",label=\"Templ< int >\",fontsize=\"" << DOT_FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << DOT_FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n"; + md5stream << " Node17 -> Node16 [dir=\"back\",color=\"orange\",fontsize=\"" << DOT_FONTSIZE << "\",style=\"dashed\",label=\"< int >\",fontname=\"" << DOT_FONTNAME << "\"];\n"; + md5stream << " Node17 [shape=\"box\",label=\"Templ< T >\",fontsize=\"" << DOT_FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << DOT_FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n"; + md5stream << " Node18 -> Node9 [dir=\"back\",color=\"darkorchid3\",fontsize=\"" << DOT_FONTSIZE << "\",style=\"dashed\",label=\"m_usedClass\",fontname=\"" << DOT_FONTNAME << "\"];\n"; + md5stream << " Node18 [shape=\"box\",label=\"Used\",fontsize=\"" << DOT_FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << DOT_FONTNAME << "\",color=\"black\",URL=\"$classUsed" << Doxygen::htmlFileExtension << "\"];\n"; + writeGraphFooter(md5stream); } - } - else // normal bitmap - { - out << "<img src=\"" << imgName << "\" border=\"0\" alt=\"\" usemap=\"#" - << mapLabel << "\"/>" << endl; - if (regenerate || !insertMapFile(out,absMapName,QCString(),mapLabel)) + virtual QCString getMapLabel() const { - int mapId = DotManager::instance()->addMap(fileName,absMapName,QCString(), - FALSE,QCString(),mapLabel); - out << "<!-- MAP " << mapId << " -->" << endl; + return ""; } - } -} - -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()); - - if (m_rootSubgraphs->count()==0) return; - - QDir d(path); - // store the original directory - if (!d.exists()) - { - err("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) - { - out << "<tr><td>"; - createGraph(n,out,path,fileName,count++); - out << "</td></tr>" << endl; - } - out << "</table>" << endl; -} - -void DotGfxHierarchyTable::addHierarchy(DotNode *n,const 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(getNextNodeNumber(), - 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->isVisited() && !hideSuper && bClass->subClasses()) - { - bool wasVisited=bClass->isVisited(); - bClass->setVisited(TRUE); - addHierarchy(bn,bClass,wasVisited); - } - } - } - } - //printf("end addHierarchy\n"); -} - -void DotGfxHierarchyTable::addClassList(const ClassSDict *cl) -{ - static bool sliceOpt = Config_getBool(OPTIMIZE_OUTPUT_SLICE); - 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 (cd->getLanguage()==SrcLangExt_VHDL && - (VhdlDocGen::VhdlClasses)cd->protection()!=VhdlDocGen::ENTITYCLASS - ) - { - continue; - } - if (sliceOpt && cd->compoundType() != m_classType) - { - continue; - } - 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(getNextNodeNumber(), - cd->displayName(), - tooltip, - tmp_url.data()); - - //m_usedNodes->clear(); - m_usedNodes->insert(cd->name(),n); - m_rootNodes->insert(0,n); - if (!cd->isVisited() && cd->subClasses()) - { - addHierarchy(n,cd,cd->isVisited()); - cd->setVisited(TRUE); - } - } - } -} - -DotGfxHierarchyTable::DotGfxHierarchyTable(const char *prefix,ClassDef::CompoundType ct) - : m_prefix(prefix) - , m_classType(ct) -{ - 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); - int number=0; - n->renumberNodes(number); - } -} - -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; -} - -//-------------------------------------------------------------------- - -void DotClassGraph::addClass(const 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 || prot==EdgeInfo::Orange2) ? EdgeInfo::Dashed : EdgeInfo::Solid; - QCString className; - if (cd->isAnonymous()) - { - className="anonymous:"; - className+=label; - } - else 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(getNextNodeNumber(), - 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(const ClassDef *cd,DotNode *n,bool base,int distance) -{ - static bool templateRelations = Config_getBool(TEMPLATE_RELATIONS); - //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); - } - } - } - if (templateRelations && base) - { - ConstraintClassDict *dict = cd->templateTypeConstraints(); - if (dict) - { - ConstraintClassDictIterator ccdi(*dict); - ConstraintClassDef *ccd; - for (;(ccd=ccdi.current());++ccdi) - { - QCString label; - QDictIterator<void> dvi(*ccd->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(ccd->classDef,n,EdgeInfo::Orange2,label,0, - 0,TRUE,distance); - } - } - } - - // ---- Add template instantiation relations - - if (templateRelations) - { - if (base) // template relations for base classes - { - const ClassDef *templMaster=cd->templateMaster(); - if (templMaster) - { - QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances()); - const 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 - { - const QDict<ClassDef> *templInstances = cd->getTemplateInstances(); - if (templInstances) - { - QDictIterator<ClassDef> cli(*templInstances); - const ClassDef *templInstance; - for (;(templInstance=cli.current());++cli) - { - addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0, - 0,FALSE,distance); - } - } - } - } -} - -DotClassGraph::DotClassGraph(const 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(getNextNodeNumber(), - 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_collabFileName = cd->collaborationGraphFileName(); - m_inheritFileName = cd->inheritanceGraphFileName(); -} - -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, - const QCString &rank, // either "LR", "RL", or "" - bool renderParents, - bool backArrows, - const QCString &title, - QCString &graphStr - ) -{ - //printf("computeMd5Signature\n"); - QGString buf; - FTextStream md5stream(&buf); - writeGraphHeader(md5stream,title); - if (!rank.isEmpty()) - { - md5stream << " rankdir=\"" << rank << "\";" << endl; - } - root->clearWriteFlag(); - root->write(md5stream, - gt, - format, - gt!=DotNode::CallGraph && gt!=DotNode::Dependency, - TRUE, - backArrows); - 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? - ); - } - pn->write(md5stream, // stream - gt, // graph type - format, // output format - TRUE, // topDown? - FALSE, // toChildren? - backArrows // backward pointing arrows? - ); - } - } - writeGraphFooter(md5stream); - uchar md5_sig[16]; - QCString sigStr(33); - MD5Buffer((const unsigned char *)buf.data(),buf.length(),md5_sig); - MD5SigToString(md5_sig,sigStr.rawData(),33); - 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, - const QCString &rank, - 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,rank,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::writeGraph(FTextStream &out, - GraphOutputFormat graphFormat, - EmbeddedOutputFormat textFormat, - 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("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"; - baseName=m_collabFileName; - break; - case DotNode::Inheritance: - mapName="inherit_map"; - baseName=m_inheritFileName; - break; - default: - ASSERT(0); - break; - } - - // derive target file names from baseName - QCString imgExt = getDotImageExtension(); - QCString imgFmt = 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, - graphFormat, - m_lrRank ? "LR" : "", - m_graphType==DotNode::Inheritance, - TRUE, - m_startNode->label() - ) || - !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName : - usePDFLatex ? absPdfName : absEpsName, - graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString()) - ) - { - regenerate=TRUE; - if (graphFormat==GOF_BITMAP) // run dot to create a bitmap image - { - DotRunner *dotRun = new DotRunner(absDotName, - d.absPath().data(),TRUE,absImgName); - dotRun->addJob(imgFmt,absImgName); - if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName); - DotManager::instance()->addRun(dotRun); - - } - else if (graphFormat==GOF_EPS) // run dot to create a .eps image - { - DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE); - if (usePDFLatex) - { - dotRun->addJob("pdf",absPdfName,absBaseName); - } - else - { - dotRun->addJob("ps",absEpsName); - } - DotManager::instance()->addRun(dotRun); - } - } - Doxygen::indexList->addImageFile(baseName+"."+imgExt); - - if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook) - { - out << "<para>" << endl; - out << " <informalfigure>" << endl; - out << " <mediaobject>" << endl; - out << " <imageobject>" << endl; - out << " <imagedata"; - out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">"; - out << "</imagedata>" << endl; - out << " </imageobject>" << endl; - out << " </mediaobject>" << endl; - out << " </informalfigure>" << endl; - out << "</para>" << endl; - } - else if (graphFormat==GOF_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 (graphFormat==GOF_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::writeDocbook(FTextStream &t) -{ - QDictIterator<DotNode> dni(*m_usedNodes); - DotNode *node; - for (;(node=dni.current());++dni) - { - node->writeDocbook(t,TRUE); - } -} - -void DotClassGraph::writeDEF(FTextStream &t) -{ - QDictIterator<DotNode> dni(*m_usedNodes); - DotNode *node; - for (;(node=dni.current());++dni) - { - node->writeDEF(t); - } -} - -//-------------------------------------------------------------------- - -void DotInclDepGraph::buildGraph(DotNode *n,const 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) - { - const 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( - getNextNodeNumber(),// 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(const FileDef *fd,bool inverse) -{ - m_inverse = inverse; - ASSERT(fd!=0); - m_inclDepFileName = fd->includeDependencyGraphFileName(); - m_inclByDepFileName = fd->includedByDependencyGraphFileName(); - QCString tmp_url=fd->getReference()+"$"+fd->getOutputFileBase(); - QCString tooltip = fd->briefDescriptionAsTooltip(); - m_startNode = new DotNode(getNextNodeNumber(), - fd->docName(), - tooltip, - 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::writeGraph(FTextStream &out, - GraphOutputFormat graphFormat, - EmbeddedOutputFormat textFormat, - 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("Output dir %s does not exist!\n",path); exit(1); - } - static bool usePDFLatex = Config_getBool(USE_PDFLATEX); - - QCString baseName; - if (m_inverse) - { - baseName=m_inclByDepFileName; - } - else - { - baseName=m_inclDepFileName; - } - QCString mapName=escapeCharsInString(m_startNode->m_label,FALSE); - if (m_inverse) mapName+="dep"; - - QCString imgExt = getDotImageExtension(); - QCString imgFmt = 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, - graphFormat, - "", // lrRank - FALSE, // renderParents - m_inverse, // backArrows - m_startNode->label() - ) || - !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName : - usePDFLatex ? absPdfName : absEpsName, - graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString()) - ) - { - regenerate=TRUE; - if (graphFormat==GOF_BITMAP) - { - // run dot to create a bitmap image - DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName); - dotRun->addJob(imgFmt,absImgName); - if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName); - DotManager::instance()->addRun(dotRun); - } - else if (graphFormat==GOF_EPS) - { - DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE); - if (usePDFLatex) - { - dotRun->addJob("pdf",absPdfName,absBaseName); - } - else - { - dotRun->addJob("ps",absEpsName); - } - DotManager::instance()->addRun(dotRun); - } - } - Doxygen::indexList->addImageFile(baseName+"."+imgExt); - - if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook) - { - out << "<para>" << endl; - out << " <informalfigure>" << endl; - out << " <mediaobject>" << endl; - out << " <imageobject>" << endl; - out << " <imagedata"; - out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">"; - out << "</imagedata>" << endl; - out << " </imageobject>" << endl; - out << " </mediaobject>" << endl; - out << " </informalfigure>" << endl; - out << "</para>" << endl; - } - else if (graphFormat==GOF_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 (graphFormat==GOF_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); - } -} - -void DotInclDepGraph::writeDocbook(FTextStream &t) -{ - QDictIterator<DotNode> dni(*m_usedNodes); - DotNode *node; - for (;(node=dni.current());++dni) - { - node->writeDocbook(t,FALSE); - } -} - -//------------------------------------------------------------- - -void DotCallGraph::buildGraph(DotNode *n,const MemberDef *md,int distance) -{ - MemberSDict *refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers(); - if (refs) - { - refs->sort(); - MemberSDict::Iterator mri(*refs); - MemberDef *rmd; - for (;(rmd=mri.current());++mri) - { - if (rmd->showInCallGraph()) - { - 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( - getNextNodeNumber(), - 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(const MemberDef *md,bool inverse) -{ - 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(); - } - QCString tooltip = md->briefDescriptionAsTooltip(); - m_startNode = new DotNode(getNextNodeNumber(), - linkToText(md->getLanguage(),name,FALSE), - tooltip, - 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 graphFormat, - EmbeddedOutputFormat textFormat, - 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("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 = getDotImageExtension(); - QCString imgFmt = 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, - graphFormat, - m_inverse ? "RL" : "LR", // lrRank - FALSE, // renderParents - m_inverse, // backArrows - m_startNode->label() - ) || - !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName : - usePDFLatex ? absPdfName : absEpsName, - graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString()) - ) - { - regenerate=TRUE; - if (graphFormat==GOF_BITMAP) - { - // run dot to create a bitmap image - DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName); - dotRun->addJob(imgFmt,absImgName); - if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName); - DotManager::instance()->addRun(dotRun); - - } - else if (graphFormat==GOF_EPS) - { - // run dot to create a .eps image - DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE); - if (usePDFLatex) - { - dotRun->addJob("pdf",absPdfName,absBaseName); - } - else - { - dotRun->addJob("ps",absEpsName); - } - DotManager::instance()->addRun(dotRun); - - } - } - Doxygen::indexList->addImageFile(baseName+"."+imgExt); - - if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook) - { - out << "<para>" << endl; - out << " <informalfigure>" << endl; - out << " <mediaobject>" << endl; - out << " <imageobject>" << endl; - out << " <imagedata"; - out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">"; - out << "</imagedata>" << endl; - out << " </imageobject>" << endl; - out << " </mediaobject>" << endl; - out << " </informalfigure>" << endl; - out << "</para>" << endl; - } - else if (graphFormat==GOF_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 (graphFormat==GOF_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; -} - -//------------------------------------------------------------- -static void writeDotDirDepGraph(FTextStream &t,const DirDef *dd,bool linkRelations); - -DotDirDeps::DotDirDeps(const DirDef *dir) : m_dir(dir) -{ -} - -DotDirDeps::~DotDirDeps() -{ -} - -QCString DotDirDeps::writeGraph(FTextStream &out, - GraphOutputFormat graphFormat, - EmbeddedOutputFormat textFormat, - const char *path, - const char *fileName, - const char *relPath, - bool generateImageMap, - int graphId, - bool linkRelations) const -{ - QDir d(path); - // store the original directory - if (!d.exists()) - { - err("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 = getDotImageExtension(); - QCString imgFmt = 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); - writeDotDirDepGraph(md5stream,m_dir,linkRelations); - uchar md5_sig[16]; - QCString sigStr(33); - MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig); - MD5SigToString(md5_sig,sigStr.rawData(),33); - bool regenerate=FALSE; - if (checkAndUpdateMd5Signature(absBaseName,sigStr) || - !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName : - usePDFLatex ? absPdfName : absEpsName, - graphFormat==GOF_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 (graphFormat==GOF_BITMAP) - { - // run dot to create a bitmap image - DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName); - dotRun->addJob(imgFmt,absImgName); - if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName); - DotManager::instance()->addRun(dotRun); - } - else if (graphFormat==GOF_EPS) - { - DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE); - if (usePDFLatex) - { - dotRun->addJob("pdf",absPdfName,absBaseName); - } - else - { - dotRun->addJob("ps",absEpsName); - } - DotManager::instance()->addRun(dotRun); - } - } - Doxygen::indexList->addImageFile(baseName+"."+imgExt); - - if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook) - { - out << "<para>" << endl; - out << " <informalfigure>" << endl; - out << " <mediaobject>" << endl; - out << " <imageobject>" << endl; - out << " <imagedata"; - out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">"; - out << "</imagedata>" << endl; - out << " </imageobject>" << endl; - out << " </mediaobject>" << endl; - out << " </informalfigure>" << endl; - out << "</para>" << endl; - } - else if (graphFormat==GOF_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 (graphFormat==GOF_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(); -} - -//------------------------------------------------------------- + friend void generateGraphLegend(const char* path); +}; void generateGraphLegend(const char *path) { QDir d(path); - // store the original directory - if (!d.exists()) - { - err("Output dir %s does not exist!\n",path); exit(1); - } + GraphLegendDotGraph dg; + FTextStream ts; + dg.writeGraph(ts, GOF_BITMAP, EOF_Html, path, "", "", FALSE, 0); - 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.rawData(),33); - QCString absBaseName = (QCString)path+"/graph_legend"; - QCString absDotName = absBaseName+".dot"; - QCString imgExt = getDotImageExtension(); - QCString imgFmt = 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",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(imgFmt,absImgName); - DotManager::instance()->addRun(dotRun); - } - else - { - removeDotGraph(absDotName); - } - Doxygen::indexList->addImageFile(imgName); - - if (imgExt=="svg") + if (getDotImageExtension()=="svg") { DotManager::instance()->addSVGObject( - absBaseName+Config_getString(HTML_FILE_EXTENSION), + dg.absBaseName()+Config_getString(HTML_FILE_EXTENSION), "graph_legend", - absImgName,QCString()); + dg.absImgName(),QCString()); } } @@ -4238,19 +493,20 @@ void writeDotGraphFromFile(const char *inFile,const char *outDir, } QCString imgExt = getDotImageExtension(); - QCString imgFmt = 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); + DotRunner dotRun(inFile, QCString()); if (format==GOF_BITMAP) - dotRun.addJob(imgFmt,absImgName); + { + dotRun.addJob(Config_getEnum(DOT_IMAGE_FORMAT),absImgName); + } else // format==GOF_EPS { if (Config_getBool(USE_PDFLATEX)) { - dotRun.addJob("pdf",absOutFile+".pdf",absOutFile); + dotRun.addJob("pdf",absOutFile+".pdf"); } else { @@ -4264,13 +520,10 @@ void writeDotGraphFromFile(const char *inFile,const char *outDir, return; } - if (format==GOF_BITMAP) checkDotResult(getDotImageExtension(),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 @@ -4294,11 +547,10 @@ void writeDotImageMapFromFile(FTextStream &t, QCString mapName = baseName+".map"; QCString imgExt = getDotImageExtension(); - QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT); QCString imgName = baseName+"."+imgExt; QCString absOutFile = d.absPath().utf8()+"/"+mapName; - DotRunner dotRun(inFile,d.absPath().data(),FALSE); + DotRunner dotRun(inFile, QCString()); dotRun.addJob(MAP_CMD,absOutFile); dotRun.preventCleanUp(); if (!dotRun.run()) @@ -4333,612 +585,3 @@ void writeDotImageMapFromFile(FTextStream &t, } d.remove(absOutFile); } - -//------------------------------------------------------------- - -DotGroupCollaboration::DotGroupCollaboration(const GroupDef* gd) -{ - QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase(); - m_usedNodes = new QDict<DotNode>(1009); - QCString tooltip = gd->briefDescriptionAsTooltip(); - m_rootNode = new DotNode(getNextNodeNumber(), gd->groupTitle(), tooltip, 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(const GroupDef* gd) -{ - QCString tmp_url; - //=========================== - // hierarchy. - - // Write parents - const GroupList *groups = gd->partOfGroups(); - if ( groups ) - { - GroupListIterator gli(*groups); - const 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(getNextNodeNumber(), 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()); - const 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(getNextNodeNumber(), 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(MemberListType_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()); - const 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()); - const 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( - const 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(getNextNodeNumber(), 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 graphFormat, EmbeddedOutputFormat textFormat, - 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("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,graphFormat,TRUE,FALSE,FALSE); - } - - // write edges - QListIterator<Edge> eli(m_edges); - Edge* edge; - for (eli.toFirst();(edge=eli.current());++eli) - { - edge->write( md5stream ); - } - - writeGraphFooter(md5stream); - uchar md5_sig[16]; - QCString sigStr(33); - MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig); - MD5SigToString(md5_sig,sigStr.rawData(),33); - QCString imgExt = getDotImageExtension(); - QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT); - QCString baseName = m_diskName; - QCString imgName = baseName+"."+imgExt; - 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(graphFormat==GOF_BITMAP ? absImgName : - usePDFLatex ? absPdfName : absEpsName, - graphFormat==GOF_BITMAP /*&& generateImageMap*/ ? absMapName : QCString()) - ) - { - regenerate=TRUE; - - QFile dotfile(absDotName); - if (dotfile.open(IO_WriteOnly)) - { - FTextStream tdot(&dotfile); - tdot << theGraph; - dotfile.close(); - } - - if (graphFormat==GOF_BITMAP) // run dot to create a bitmap image - { - DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE); - dotRun->addJob(imgFmt,absImgName); - if (writeImageMap) dotRun->addJob(MAP_CMD,absMapName); - DotManager::instance()->addRun(dotRun); - - } - else if (graphFormat==GOF_EPS) - { - DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE); - if (usePDFLatex) - { - dotRun->addJob("pdf",absPdfName,absBaseName); - } - else - { - dotRun->addJob("ps",absEpsName); - } - DotManager::instance()->addRun(dotRun); - } - - } - if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook) - { - t << "<para>" << endl; - t << " <informalfigure>" << endl; - t << " <mediaobject>" << endl; - t << " <imageobject>" << endl; - t << " <imagedata"; - t << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << relPath << baseName << "." << imgExt << "\">"; - t << "</imagedata>" << endl; - t << " </imageobject>" << endl; - t << " </mediaobject>" << endl; - t << " </informalfigure>" << endl; - t << "</para>" << endl; - } - else if (graphFormat==GOF_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 (graphFormat==GOF_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\""; - break; - 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 << "\"" << convertLabel(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=box];\n"; - t << " rankdir=LR;\n"; -} - -void writeDotDirDepGraph(FTextStream &t,const DirDef *dd,bool linkRelations) -{ - 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()); - const 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(); - const 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 - const 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"; - if (linkRelations) - { - t << " headhref=\"" << relationName << Doxygen::htmlFileExtension << "\""; - } - t << "];\n"; - } - } - } - - t << "}\n"; -} @@ -1,13 +1,10 @@ /****************************************************************************** * - * - * - * - * Copyright (C) 1997-2015 by Dimitri van Heesch. + * Copyright (C) 1997-2019 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 + * 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. * @@ -16,8 +13,8 @@ * */ -#ifndef _DOT_H -#define _DOT_H +#ifndef DOT_H +#define DOT_H #include <qlist.h> #include <qdict.h> @@ -26,473 +23,23 @@ #include <qqueue.h> #include <qthread.h> #include "sortdict.h" -#include "classdef.h" +#include "qgstring.h" +#include "qdir.h" +#include "qcstring.h" +#include "dotgraph.h" -class FileDef; class FTextStream; -class DotNodeList; -class ClassSDict; -class MemberDef; -class Definition; -class DirDef; -class GroupDef; -class DotGroupCollaboration; +class DotRunner; class DotRunnerQueue; - -enum GraphOutputFormat { GOF_BITMAP, GOF_EPS }; -enum EmbeddedOutputFormat { EOF_Html, EOF_LaTeX, EOF_Rtf, EOF_DocBook }; - -// the graphicx LaTeX has a limitation of maximum size of 16384 -// To be on the save side we take it a little bit smaller i.e. 150 inch * 72 dpi -// It is anyway hard to view these size of images -#define MAX_LATEX_GRAPH_INCH 150 -#define MAX_LATEX_GRAPH_SIZE (MAX_LATEX_GRAPH_INCH * 72) - -/** Attributes of an edge of a dot graph */ -struct EdgeInfo -{ - enum Colors { Blue=0, Green=1, Red=2, Purple=3, Grey=4, Orange=5, Orange2=6 }; - enum Styles { Solid=0, Dashed=1 }; - EdgeInfo() : m_color(0), m_style(0), m_labColor(0) {} - ~EdgeInfo() {} - int m_color; - int m_style; - QCString m_label; - QCString m_url; - int m_labColor; -}; - -/** A node in a dot graph */ -class DotNode -{ - public: - enum GraphType { Dependency, Inheritance, Collaboration, Hierarchy, CallGraph }; - enum TruncState { Unknown, Truncated, Untruncated }; - DotNode(int n,const char *lab,const char *tip,const char *url, - bool rootNode=FALSE,const ClassDef *cd=0); - ~DotNode(); - void addChild(DotNode *n, - int edgeColor=EdgeInfo::Purple, - int edgeStyle=EdgeInfo::Solid, - const char *edgeLab=0, - const char *edgeURL=0, - int edgeLabCol=-1 - ); - void addParent(DotNode *n); - void deleteNode(DotNodeList &deletedList,SDict<DotNode> *skipNodes=0); - void removeChild(DotNode *n); - void removeParent(DotNode *n); - int findParent( DotNode *n ); - void write(FTextStream &t,GraphType gt,GraphOutputFormat f, - bool topDown,bool toChildren,bool backArrows); - int m_subgraphId; - void clearWriteFlag(); - void writeXML(FTextStream &t,bool isClassGraph); - void writeDocbook(FTextStream &t,bool isClassGraph); - void writeDEF(FTextStream &t); - QCString label() const { return m_label; } - int number() const { return m_number; } - bool isVisible() const { return m_visible; } - TruncState isTruncated() const { return m_truncated; } - int distance() const { return m_distance; } - void renumberNodes(int &number); - - private: - void colorConnectedNodes(int curColor); - void writeBox(FTextStream &t,GraphType gt,GraphOutputFormat f, - bool hasNonReachableChildren); - void writeArrow(FTextStream &t,GraphType gt,GraphOutputFormat f,DotNode *cn, - EdgeInfo *ei,bool topDown, bool pointBack=TRUE); - void setDistance(int distance); - const DotNode *findDocNode() const; // only works for acyclic graphs! - void markAsVisible(bool b=TRUE) { m_visible=b; } - void markAsTruncated(bool b=TRUE) { m_truncated=b ? Truncated : Untruncated; } - int m_number; - QCString m_label; //!< label text - QCString m_tooltip; //!< node's tooltip - QCString m_url; //!< url of the node (format: remote$local) - QList<DotNode> *m_parents; //!< list of parent nodes (incoming arrows) - QList<DotNode> *m_children; //!< list of child nodes (outgoing arrows) - QList<EdgeInfo> *m_edgeInfo; //!< edge info for each child - bool m_deleted; //!< used to mark a node as deleted - bool m_written; //!< used to mark a node as written - bool m_hasDoc; //!< used to mark a node as documented - bool m_isRoot; //!< indicates if this is a root node - const ClassDef * m_classDef; //!< class representing this node (can be 0) - bool m_visible; //!< is the node visible in the output - TruncState m_truncated; //!< does the node have non-visible children/parents - int m_distance; //!< shortest path to the root node - bool m_renumbered;//!< indicates if the node has been renumbered (to prevent endless loops) - - friend class DotGfxHierarchyTable; - friend class DotClassGraph; - friend class DotInclDepGraph; - friend class DotNodeList; - friend class DotCallGraph; - friend class DotGroupCollaboration; - friend class DotInheritanceGraph; - - friend QCString computeMd5Signature( - DotNode *root, GraphType gt, - GraphOutputFormat f, - const QCString &rank, - bool renderParents, - bool backArrows, - const QCString &title, - QCString &graphStr - ); -}; - -/** Class representing a list of DotNode objects. */ -class DotNodeList : public QList<DotNode> -{ - public: - DotNodeList() : QList<DotNode>() {} - ~DotNodeList() {} - private: - int compareValues(const DotNode *n1,const DotNode *n2) const; -}; - -/** A dot graph */ -class DotGraph -{ - public: - DotGraph() : m_curNodeNumber(0) {} - virtual ~DotGraph() {} - - protected: - int getNextNodeNumber() { return ++m_curNodeNumber; } - - private: - DotGraph(const DotGraph &); - DotGraph &operator=(const DotGraph &); - int m_curNodeNumber; -}; - -/** Represents a graphical class hierarchy */ -class DotGfxHierarchyTable : public DotGraph -{ - public: - DotGfxHierarchyTable(const char *prefix="",ClassDef::CompoundType ct=ClassDef::Class); - ~DotGfxHierarchyTable(); - void writeGraph(FTextStream &t,const char *path, const char *fileName) const; - void createGraph(DotNode *rootNode,FTextStream &t,const char *path,const char *fileName,int id) const; - const DotNodeList *subGraphs() const { return m_rootSubgraphs; } - - private: - void addHierarchy(DotNode *n,const ClassDef *cd,bool hide); - void addClassList(const ClassSDict *cl); - - QCString m_prefix; - ClassDef::CompoundType m_classType; - QList<DotNode> *m_rootNodes; - QDict<DotNode> *m_usedNodes; - DotNodeList *m_rootSubgraphs; -}; - -/** Representation of a class inheritance or dependency graph */ -class DotClassGraph : public DotGraph -{ - public: - DotClassGraph(const ClassDef *cd,DotNode::GraphType t); - ~DotClassGraph(); - bool isTrivial() const; - bool isTooBig() const; - QCString writeGraph(FTextStream &t,GraphOutputFormat gf,EmbeddedOutputFormat ef, - const char *path, const char *fileName, const char *relPath, - bool TBRank=TRUE,bool imageMap=TRUE,int graphId=-1) const; - - void writeXML(FTextStream &t); - void writeDocbook(FTextStream &t); - void writeDEF(FTextStream &t); - - private: - void buildGraph(const ClassDef *cd,DotNode *n,bool base,int distance); - bool determineVisibleNodes(DotNode *rootNode,int maxNodes,bool includeParents); - void determineTruncatedNodes(QList<DotNode> &queue,bool includeParents); - void addClass(const ClassDef *cd,DotNode *n,int prot,const char *label, - const char *usedName,const char *templSpec, - bool base,int distance); - - DotNode * m_startNode; - QDict<DotNode> * m_usedNodes; - DotNode::GraphType m_graphType; - QCString m_collabFileName; - QCString m_inheritFileName; - bool m_lrRank; -}; - -/** Representation of an include dependency graph */ -class DotInclDepGraph : public DotGraph -{ - public: - DotInclDepGraph(const FileDef *fd,bool inverse); - ~DotInclDepGraph(); - QCString writeGraph(FTextStream &t, GraphOutputFormat gf, EmbeddedOutputFormat ef, - const char *path,const char *fileName,const char *relPath, - bool writeImageMap=TRUE,int graphId=-1) const; - bool isTrivial() const; - bool isTooBig() const; - QCString diskName() const; - void writeXML(FTextStream &t); - void writeDocbook(FTextStream &t); - - private: - void buildGraph(DotNode *n,const FileDef *fd,int distance); - void determineVisibleNodes(QList<DotNode> &queue,int &maxNodes); - void determineTruncatedNodes(QList<DotNode> &queue); - - DotNode *m_startNode; - QDict<DotNode> *m_usedNodes; - QCString m_inclDepFileName; - QCString m_inclByDepFileName; - bool m_inverse; -}; - -/** Representation of an call graph */ -class DotCallGraph : public DotGraph -{ - public: - DotCallGraph(const MemberDef *md,bool inverse); - ~DotCallGraph(); - QCString writeGraph(FTextStream &t, GraphOutputFormat gf, EmbeddedOutputFormat ef, - const char *path,const char *fileName, - const char *relPath,bool writeImageMap=TRUE, - int graphId=-1) const; - void buildGraph(DotNode *n,const MemberDef *md,int distance); - bool isTrivial() const; - bool isTooBig() const; - void determineVisibleNodes(QList<DotNode> &queue, int &maxNodes); - void determineTruncatedNodes(QList<DotNode> &queue); - - private: - DotNode *m_startNode; - QDict<DotNode> *m_usedNodes; - bool m_inverse; - QCString m_diskName; - const Definition * m_scope; -}; - -/** Representation of an directory dependency graph */ -class DotDirDeps : public DotGraph -{ - public: - DotDirDeps(const DirDef *dir); - ~DotDirDeps(); - bool isTrivial() const; - QCString writeGraph(FTextStream &out, - GraphOutputFormat gf, - EmbeddedOutputFormat ef, - const char *path, - const char *fileName, - const char *relPath, - bool writeImageMap=TRUE, - int graphId=-1, - bool linkRelations=TRUE) const; - private: - const DirDef *m_dir; -}; - -/** Representation of a group collaboration graph */ -class DotGroupCollaboration : public DotGraph -{ - public : - enum EdgeType - { tmember = 0, - tclass, - tnamespace, - tfile, - tpages, - tdir, - thierarchy - }; - - class Link - { - public: - Link(const QCString lab,const QCString &u) : label(lab), url(u) {} - QCString label; - QCString url; - }; - - class Edge - { - public : - Edge(DotNode *start,DotNode *end,EdgeType type) - : pNStart(start), pNEnd(end), eType(type) - { links.setAutoDelete(TRUE); } - - DotNode* pNStart; - DotNode* pNEnd; - EdgeType eType; - - QList<Link> links; - void write( FTextStream &t ) const; - }; - - DotGroupCollaboration(const GroupDef* gd); - ~DotGroupCollaboration(); - QCString writeGraph(FTextStream &t, GraphOutputFormat gf,EmbeddedOutputFormat ef, - const char *path,const char *fileName,const char *relPath, - bool writeImageMap=TRUE,int graphId=-1) const; - void buildGraph(const GroupDef* gd); - bool isTrivial() const; - - private : - void addCollaborationMember(const Definition* def, QCString& url, EdgeType eType ); - void addMemberList( class MemberList* ml ); - void writeGraphHeader(FTextStream &t,const QCString &title) const; - Edge* addEdge( DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType, - const QCString& _label, const QCString& _url ); - - DotNode *m_rootNode; - QDict<DotNode> *m_usedNodes; - QCString m_diskName; - QList<Edge> m_edges; -}; - -/** Minimal constant string class that is thread safe, once initialized. */ -class DotConstString -{ - public: - DotConstString() { m_str=0; m_pdfstr=0;} - ~DotConstString() { delete[] m_str; delete[] m_pdfstr;} - DotConstString(const QCString &s, const QCString &p = NULL) : m_str(0), m_pdfstr(0) { set(s); setpdf(p);} - DotConstString(const DotConstString &s) : m_str(0), m_pdfstr(0) { set(s.data()); } - const char *data() const { return m_str; } - const char *pdfData() const { return m_pdfstr; } - bool isEmpty() const { return m_str==0 || m_str[0]=='\0'; } - void set(const QCString &s) - { - delete[] m_str; - m_str=0; - if (!s.isEmpty()) - { - m_str=new char[s.length()+1]; - qstrcpy(m_str,s.data()); - } - } - void setpdf(const QCString &p) - { - delete[] m_pdfstr; - m_pdfstr=0; - if (!p.isEmpty()) - { - m_pdfstr=new char[p.length()+1]; - qstrcpy(m_pdfstr,p.data()); - } - } - private: - DotConstString &operator=(const DotConstString &); - char *m_str; - char *m_pdfstr; -}; - -/** Helper class to run dot from doxygen. - */ -class DotRunner -{ - public: - struct CleanupItem - { - DotConstString path; - DotConstString file; - }; - - /** Creates a runner for a dot \a file. */ - DotRunner(const QCString &file,const QCString &fontPath,bool checkResult, - const QCString &imageName = QCString()); - - /** Adds an additional job to the run. - * Performing multiple jobs one file can be faster. - */ - void addJob(const char *format,const char *output, const char *base = NULL); - - void addPostProcessing(const char *cmd,const char *args); - - void preventCleanUp() { m_cleanUp = FALSE; } - - /** Runs dot for all jobs added. */ - bool run(); - const CleanupItem &cleanup() const { return m_cleanupItem; } - - private: - DotConstString m_dotExe; - bool m_multiTargets; - QList<DotConstString> m_jobs; - DotConstString m_postArgs; - DotConstString m_postCmd; - DotConstString m_file; - DotConstString m_path; - bool m_checkResult; - DotConstString m_imageName; - DotConstString m_imgExt; - bool m_cleanUp; - CleanupItem m_cleanupItem; -}; - -/** Helper class to insert a set of map file into an output file */ -class DotFilePatcher -{ - public: - struct Map - { - QCString mapFile; - QCString relPath; - bool urlOnly; - QCString context; - QCString label; - bool zoomable; - int graphId; - }; - DotFilePatcher(const char *patchFile); - int addMap(const QCString &mapFile,const QCString &relPath, - bool urlOnly,const QCString &context,const QCString &label); - int addFigure(const QCString &baseName, - const QCString &figureName,bool heightCheck); - int addSVGConversion(const QCString &relPath,bool urlOnly, - const QCString &context,bool zoomable,int graphId); - int addSVGObject(const QCString &baseName, const QCString &figureName, - const QCString &relPath); - bool run(); - QCString file() const; - - private: - QList<Map> m_maps; - QCString m_patchFile; -}; - -/** Queue of dot jobs to run. */ -class DotRunnerQueue -{ - public: - void enqueue(DotRunner *runner); - DotRunner *dequeue(); - uint count() const; - private: - QWaitCondition m_bufferNotEmpty; - QQueue<DotRunner> m_queue; - mutable QMutex m_mutex; -}; - -/** Worker thread to execute a dot run */ -class DotWorkerThread : public QThread -{ - public: - DotWorkerThread(DotRunnerQueue *queue); - void run(); - void cleanup(); - private: - DotRunnerQueue *m_queue; - QList<DotRunner::CleanupItem> m_cleanupItems; -}; +class DotWorkerThread; +class DotFilePatcher; /** Singleton that manages dot relation actions */ class DotManager { public: static DotManager *instance(); - void addRun(DotRunner *run); + DotRunner* createRunner(const QCString& absDotName, const QCString& md5Hash); int addMap(const QCString &file,const QCString &mapFile, const QCString &relPath,bool urlOnly, const QCString &context,const QCString &label); @@ -507,13 +54,15 @@ class DotManager private: DotManager(); virtual ~DotManager(); - QList<DotRunner> m_dotRuns; + + QDict<DotRunner> m_runners; SDict<DotFilePatcher> m_dotMaps; static DotManager *m_theInstance; DotRunnerQueue *m_queue; QList<DotWorkerThread> m_workers; }; +void initDot(); /** Generated a graphs legend page */ void generateGraphLegend(const char *path); @@ -524,5 +73,10 @@ void writeDotImageMapFromFile(FTextStream &t, const QCString& inFile, const QCString& outDir, const QCString& relPath,const QCString& baseName, const QCString& context,int graphId=-1); +bool writeSVGFigureLink(FTextStream &out,const QCString &relPath, + const QCString &baseName,const QCString &absImgName); +bool convertMapFile(FTextStream &t,const char *mapName, + const QCString relPath, bool urlOnly=FALSE, + const QCString &context=QCString()); #endif diff --git a/src/dotcallgraph.cpp b/src/dotcallgraph.cpp new file mode 100644 index 0000000..15d408a --- /dev/null +++ b/src/dotcallgraph.cpp @@ -0,0 +1,217 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "dotcallgraph.h" + +#include "dotnode.h" +#include "memberlist.h" +#include "config.h" +#include "util.h" + +#define HIDE_SCOPE_NAMES Config_getBool(HIDE_SCOPE_NAMES) +#define DOT_GRAPH_MAX_NODES Config_getInt(DOT_GRAPH_MAX_NODES) +#define MAX_DOT_GRAPH_DEPTH Config_getInt(MAX_DOT_GRAPH_DEPTH) + +void DotCallGraph::buildGraph(DotNode *n,const MemberDef *md,int distance) +{ + MemberSDict *refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers(); + if (refs) + { + refs->sort(); + MemberSDict::Iterator mri(*refs); + MemberDef *rmd; + for (;(rmd=mri.current());++mri) + { + if (rmd->showInCallGraph()) + { + 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 (HIDE_SCOPE_NAMES) + { + name = rmd->getOuterScope()==m_scope ? + rmd->name() : rmd->qualifiedName(); + } + else + { + name = rmd->qualifiedName(); + } + QCString tooltip = rmd->briefDescriptionAsTooltip(); + bn = new DotNode( + getNextNodeNumber(), + 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) + { + DotNode *n = queue.take(0); + if (!n->isVisible() && n->distance()<=MAX_DOT_GRAPH_DEPTH) // not yet processed + { + n->markAsVisible(); + maxNodes--; + // add direct children + if (n->children()) + { + QListIterator<DotNode> li(*n->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->children()) + { + QListIterator<DotNode> li(*n->children()); + const DotNode *dn; + for (li.toFirst();(dn=li.current());++li) + { + if (!dn->isVisible()) + truncated = TRUE; + else + queue.append(dn); + } + } + n->markAsTruncated(truncated); + } + } +} + +DotCallGraph::DotCallGraph(const MemberDef *md,bool inverse) +{ + 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 (HIDE_SCOPE_NAMES) + { + name = md->name(); + } + else + { + name = md->qualifiedName(); + } + QCString tooltip = md->briefDescriptionAsTooltip(); + m_startNode = new DotNode(getNextNodeNumber(), + linkToText(md->getLanguage(),name,FALSE), + tooltip, + 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); + + int maxNodes = DOT_GRAPH_MAX_NODES; + QList<DotNode> openNodeQueue; + openNodeQueue.append(m_startNode); + determineVisibleNodes(openNodeQueue,maxNodes); + openNodeQueue.clear(); + openNodeQueue.append(m_startNode); + determineTruncatedNodes(openNodeQueue); +} + +DotCallGraph::~DotCallGraph() +{ + DotNode::deleteNodes(m_startNode); + delete m_usedNodes; +} + +QCString DotCallGraph::getBaseName() const +{ + return m_diskName + (m_inverse ? "_icgraph" : "_cgraph"); +} + +void DotCallGraph::computeTheGraph() +{ + computeGraph( + m_startNode, + CallGraph, + m_graphFormat, + m_inverse ? "RL" : "LR", + FALSE, + m_inverse, + m_startNode->label(), + m_theGraph); +} + +QCString DotCallGraph::getMapLabel() const +{ + return m_baseName; +} + +QCString DotCallGraph::writeGraph( + FTextStream &out, + GraphOutputFormat graphFormat, + EmbeddedOutputFormat textFormat, + const char *path, + const char *fileName, + const char *relPath,bool generateImageMap, + int graphId) +{ + return DotGraph::writeGraph(out, graphFormat, textFormat, path, fileName, relPath, generateImageMap, graphId); +} + +bool DotCallGraph::isTrivial() const +{ + return m_startNode->children()==0; +} + +bool DotCallGraph::isTooBig() const +{ + int numNodes = m_startNode->children() ? m_startNode->children()->count() : 0; + return numNodes>=DOT_GRAPH_MAX_NODES; +} diff --git a/src/dotcallgraph.h b/src/dotcallgraph.h new file mode 100644 index 0000000..c96b9cf --- /dev/null +++ b/src/dotcallgraph.h @@ -0,0 +1,52 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#ifndef DOTCALLGRAPH_H +#define DOTCALLGRAPH_H + +#include "dotgraph.h" +#include "ftextstream.h" +#include "memberdef.h" + +/** Representation of an call graph */ +class DotCallGraph : public DotGraph +{ + public: + DotCallGraph(const MemberDef *md,bool inverse); + ~DotCallGraph(); + bool isTrivial() const; + bool isTooBig() const; + QCString writeGraph(FTextStream &t, GraphOutputFormat gf, EmbeddedOutputFormat ef, + const char *path,const char *fileName, + const char *relPath,bool writeImageMap=TRUE, + int graphId=-1); + + protected: + virtual QCString getBaseName() const; + virtual QCString getMapLabel() const; + virtual void computeTheGraph(); + + private: + void buildGraph(DotNode *n,const MemberDef *md,int distance); + void determineVisibleNodes(QList<DotNode> &queue, int &maxNodes); + void determineTruncatedNodes(QList<DotNode> &queue); + DotNode *m_startNode; + QDict<DotNode> *m_usedNodes; + bool m_inverse; + QCString m_diskName; + const Definition * m_scope; +}; + +#endif diff --git a/src/dotclassgraph.cpp b/src/dotclassgraph.cpp new file mode 100644 index 0000000..308be4b --- /dev/null +++ b/src/dotclassgraph.cpp @@ -0,0 +1,548 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "dotclassgraph.h" +#include "dotnode.h" + +#include "config.h" +#include "util.h" + +#define HIDE_SCOPE_NAMES Config_getBool(HIDE_SCOPE_NAMES) +#define MAX_DOT_GRAPH_DEPTH Config_getInt(MAX_DOT_GRAPH_DEPTH) +#define UML_LOOK Config_getBool(UML_LOOK) +#define TEMPLATE_RELATIONS Config_getBool(TEMPLATE_RELATIONS) +#define DOT_GRAPH_MAX_NODES Config_getInt(DOT_GRAPH_MAX_NODES) + +void DotClassGraph::addClass(const 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 || prot==EdgeInfo::Orange2) ? EdgeInfo::Dashed : EdgeInfo::Solid; + QCString className; + if (cd->isAnonymous()) + { + className="anonymous:"; + className+=label; + } + else 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->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->label().data(),n->label().data()); + } + else // new class + { + QCString displayName=className; + if (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(getNextNodeNumber(), + 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->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->children()) + { + QListIterator<DotNode> li(*n->children()); + const DotNode *dn; + for (li.toFirst();(dn=li.current());++li) + { + if (!dn->isVisible()) + truncated = TRUE; + else + queue.append(dn); + } + } + if (n->parents() && includeParents) + { + QListIterator<DotNode> li(*n->parents()); + const 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) + { + if (childQueue.count()>0) + { + DotNode *n = childQueue.take(0); + int distance = n->distance(); + if (!n->isVisible() && distance<=MAX_DOT_GRAPH_DEPTH) // 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->children()) + { + QListIterator<DotNode> li(*n->children()); + const 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()<=MAX_DOT_GRAPH_DEPTH) // 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->parents()) + { + QListIterator<DotNode> li(*n->parents()); + const DotNode *dn; + for (li.toFirst();(dn=li.current());++li) + { + parentQueue.append(dn); + } + } + } + } + } + if (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(const 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 == Inheritance || m_graphType==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 == 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); + } + } + } + if (TEMPLATE_RELATIONS && base) + { + ConstraintClassDict *dict = cd->templateTypeConstraints(); + if (dict) + { + ConstraintClassDictIterator ccdi(*dict); + ConstraintClassDef *ccd; + for (;(ccd=ccdi.current());++ccdi) + { + QCString label; + QDictIterator<void> dvi(*ccd->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(ccd->classDef,n,EdgeInfo::Orange2,label,0, + 0,TRUE,distance); + } + } + } + + // ---- Add template instantiation relations + + if (TEMPLATE_RELATIONS) + { + if (base) // template relations for base classes + { + const ClassDef *templMaster=cd->templateMaster(); + if (templMaster) + { + QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances()); + const 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 + { + const QDict<ClassDef> *templInstances = cd->getTemplateInstances(); + if (templInstances) + { + QDictIterator<ClassDef> cli(*templInstances); + const ClassDef *templInstance; + for (;(templInstance=cli.current());++cli) + { + addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0, + 0,FALSE,distance); + } + } + } + } +} + +DotClassGraph::DotClassGraph(const ClassDef *cd,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(getNextNodeNumber(), + 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); + + buildGraph(cd,m_startNode,TRUE,1); + if (t==Inheritance) buildGraph(cd,m_startNode,FALSE,1); + + m_lrRank = determineVisibleNodes(m_startNode,DOT_GRAPH_MAX_NODES,t==Inheritance); + QList<DotNode> openNodeQueue; + openNodeQueue.append(m_startNode); + determineTruncatedNodes(openNodeQueue,t==Inheritance); + + m_collabFileName = cd->collaborationGraphFileName(); + m_inheritFileName = cd->inheritanceGraphFileName(); +} + +bool DotClassGraph::isTrivial() const +{ + if (m_graphType==Inheritance) + return m_startNode->children()==0 && m_startNode->parents()==0; + else + return !UML_LOOK && m_startNode->children()==0; +} + +bool DotClassGraph::isTooBig() const +{ + int numNodes = 0; + numNodes+= m_startNode->children() ? m_startNode->children()->count() : 0; + if (m_graphType==Inheritance) + { + numNodes+= m_startNode->parents() ? m_startNode->parents()->count() : 0; + } + return numNodes>=DOT_GRAPH_MAX_NODES; +} + +DotClassGraph::~DotClassGraph() +{ + DotNode::deleteNodes(m_startNode); + delete m_usedNodes; +} + +QCString DotClassGraph::getBaseName() const +{ + switch (m_graphType) + { + case Collaboration: + return m_collabFileName; + break; + case Inheritance: + return m_inheritFileName; + break; + default: + ASSERT(0); + break; + } + return ""; +} + +void DotClassGraph::computeTheGraph() +{ + computeGraph( + m_startNode, + m_graphType, + m_graphFormat, + m_lrRank ? "LR" : "", + m_graphType == Inheritance, + TRUE, + m_startNode->label(), + m_theGraph + ); +} + +QCString DotClassGraph::getMapLabel() const +{ + QCString mapName; + switch (m_graphType) + { + case Collaboration: + mapName="coll_map"; + break; + case Inheritance: + mapName="inherit_map"; + break; + default: + ASSERT(0); + break; + } + + return escapeCharsInString(m_startNode->label(),FALSE)+"_"+escapeCharsInString(mapName,FALSE); +} + +QCString DotClassGraph::getImgAltText() const +{ + switch (m_graphType) + { + case Collaboration: + return "Collaboration graph"; + break; + case Inheritance: + return "Inheritance graph"; + break; + default: + ASSERT(0); + break; + } + return ""; +} + +QCString DotClassGraph::writeGraph(FTextStream &out, + GraphOutputFormat graphFormat, + EmbeddedOutputFormat textFormat, + const char *path, + const char *fileName, + const char *relPath, + bool /*isTBRank*/, + bool generateImageMap, + int graphId) +{ + return DotGraph::writeGraph(out, graphFormat, textFormat, path, fileName, relPath, generateImageMap, graphId); +} + +//-------------------------------------------------------------------- + +void DotClassGraph::writeXML(FTextStream &t) +{ + QDictIterator<DotNode> dni(*m_usedNodes); + DotNode *node; + for (;(node=dni.current());++dni) + { + node->writeXML(t,TRUE); + } +} + +void DotClassGraph::writeDocbook(FTextStream &t) +{ + QDictIterator<DotNode> dni(*m_usedNodes); + DotNode *node; + for (;(node=dni.current());++dni) + { + node->writeDocbook(t,TRUE); + } +} + +void DotClassGraph::writeDEF(FTextStream &t) +{ + QDictIterator<DotNode> dni(*m_usedNodes); + DotNode *node; + for (;(node=dni.current());++dni) + { + node->writeDEF(t); + } +} diff --git a/src/dotclassgraph.h b/src/dotclassgraph.h new file mode 100644 index 0000000..b3b9291 --- /dev/null +++ b/src/dotclassgraph.h @@ -0,0 +1,62 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#ifndef DOTCLASSGRAPH_H +#define DOTCLASSGRAPH_H + +#include "classdef.h" + +#include "dotgraph.h" + +/** Representation of a class inheritance or dependency graph */ +class DotClassGraph : public DotGraph +{ +public: + DotClassGraph(const ClassDef *cd,GraphType t); + ~DotClassGraph(); + bool isTrivial() const; + bool isTooBig() const; + QCString writeGraph(FTextStream &t,GraphOutputFormat gf,EmbeddedOutputFormat ef, + const char *path, const char *fileName, const char *relPath, + bool TBRank=TRUE,bool imageMap=TRUE,int graphId=-1); + + void writeXML(FTextStream &t); + void writeDocbook(FTextStream &t); + void writeDEF(FTextStream &t); + +protected: + virtual QCString getBaseName() const; + virtual QCString getMapLabel() const; + virtual void computeTheGraph(); + virtual QCString getImgAltText() const; + +private: + void buildGraph(const ClassDef *cd,DotNode *n,bool base,int distance); + bool determineVisibleNodes(DotNode *rootNode,int maxNodes,bool includeParents); + void determineTruncatedNodes(QList<DotNode> &queue,bool includeParents); + void addClass(const ClassDef *cd,DotNode *n,int prot,const char *label, + const char *usedName,const char *templSpec, + bool base,int distance); + + DotNode * m_startNode; + QDict<DotNode> * m_usedNodes; + GraphType m_graphType; + QCString m_collabFileName; + QCString m_inheritFileName; + bool m_lrRank; +}; + + +#endif diff --git a/src/dotdirdeps.cpp b/src/dotdirdeps.cpp new file mode 100644 index 0000000..85906d1 --- /dev/null +++ b/src/dotdirdeps.cpp @@ -0,0 +1,220 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "dotdirdeps.h" + +#include "ftextstream.h" +#include "util.h" +#include "doxygen.h" +#include "config.h" + +void writeDotDirDepGraph(FTextStream &t,const DirDef *dd,bool linkRelations) +{ + t << "digraph \"" << dd->displayName() << "\" {\n"; + if (Config_getBool(DOT_TRANSPARENT)) + { + t << " bgcolor=transparent;\n"; + } + t << " compound=true\n"; + t << " node [ fontsize=\"" << DotGraph::DOT_FONTSIZE << "\", fontname=\"" << DotGraph::DOT_FONTNAME << "\"];\n"; + t << " edge [ labelfontsize=\"" << DotGraph::DOT_FONTSIZE << "\", labelfontname=\"" << DotGraph::DOT_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=\"" << DotGraph::DOT_FONTNAME << "\", fontsize=\"" << DotGraph::DOT_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()); + const 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(); + const 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 + const 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"; + if (linkRelations) + { + t << " headhref=\"" << relationName << Doxygen::htmlFileExtension << "\""; + } + t << "];\n"; + } + } + } + + t << "}\n"; +} + +DotDirDeps::DotDirDeps(const DirDef *dir) : m_dir(dir) +{ +} + +DotDirDeps::~DotDirDeps() +{ +} + +QCString DotDirDeps::getBaseName() const +{ + return m_dir->getOutputFileBase()+"_dep"; + +} + +void DotDirDeps::computeTheGraph() +{ + // compute md5 checksum of the graph were are about to generate + FTextStream md5stream(&m_theGraph); + //m_dir->writeDepGraph(md5stream); + writeDotDirDepGraph(md5stream,m_dir,m_linkRelations); +} + +QCString DotDirDeps::getMapLabel() const +{ + return escapeCharsInString(m_baseName,FALSE); +} + +QCString DotDirDeps::getImgAltText() const +{ + return convertToXML(m_dir->displayName()); +} + +QCString DotDirDeps::writeGraph(FTextStream &out, + GraphOutputFormat graphFormat, + EmbeddedOutputFormat textFormat, + const char *path, + const char *fileName, + const char *relPath, + bool generateImageMap, + int graphId, + bool linkRelations) +{ + m_linkRelations = linkRelations; + m_urlOnly = TRUE; + return DotGraph::writeGraph(out, graphFormat, textFormat, path, fileName, relPath, generateImageMap, graphId); +} + +bool DotDirDeps::isTrivial() const +{ + return m_dir->depGraphIsTrivial(); +} diff --git a/src/dotdirdeps.h b/src/dotdirdeps.h new file mode 100644 index 0000000..f5eef65 --- /dev/null +++ b/src/dotdirdeps.h @@ -0,0 +1,51 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#ifndef DOTDIRDEPS_H +#define DOTDIRDEPS_H + +#include "dotgraph.h" +#include "dirdef.h" + +/** Representation of an directory dependency graph */ +class DotDirDeps : public DotGraph +{ + public: + DotDirDeps(const DirDef *dir); + ~DotDirDeps(); + bool isTrivial() const; + QCString writeGraph(FTextStream &out, + GraphOutputFormat gf, + EmbeddedOutputFormat ef, + const char *path, + const char *fileName, + const char *relPath, + bool writeImageMap=TRUE, + int graphId=-1, + bool linkRelations=TRUE); + + protected: + virtual QCString getBaseName() const; + virtual QCString getMapLabel() const; + virtual void computeTheGraph(); + virtual QCString getImgAltText() const; + + private: + const DirDef *m_dir; + + bool m_linkRelations; +}; + +#endif diff --git a/src/dotfilepatcher.cpp b/src/dotfilepatcher.cpp new file mode 100644 index 0000000..91b7c78 --- /dev/null +++ b/src/dotfilepatcher.cpp @@ -0,0 +1,539 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "dotfilepatcher.h" + +#include "qstring.h" +#include "config.h" +#include "qdir.h" +#include "message.h" +#include "ftextstream.h" +#include "docparser.h" +#include "doxygen.h" +#include "util.h" +#include "dot.h" + +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" +"<style type=\"text/css\"><![CDATA[\n" +".edge:hover path { stroke: red; }\n" +".edge:hover polygon { stroke: red; fill: red; }\n" +"]]></style>\n" +"<script type=\"text/javascript\"><![CDATA[\n" +"var edges = document.getElementsByTagName('g');\n" +"if (edges && edges.length) {\n" +" for (var i=0;i<edges.length;i++) {\n" +" if (edges[i].id.substr(0,4)=='edge') {\n" +" edges[i].setAttribute('class','edge');\n" +" }\n" +" }\n" +"}\n" +"]]></script>\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[] = +// navigation panel +" <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" +// zoom in +" <use id=\"zoomplus\" xlink:href=\"#zoomPlus\" x=\"17\" y=\"9\" onmousedown=\"handleZoom(evt,'in')\"/>\n" +// zoom out +" <use id=\"zoomminus\" xlink:href=\"#zoomMin\" x=\"42\" y=\"9\" onmousedown=\"handleZoom(evt,'out')\"/>\n" +// reset zoom +" <use id=\"reset\" xlink:href=\"#resetDef\" x=\"30\" y=\"36\" onmousedown=\"handleReset()\"/>\n" +// arrow up +" <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" +// arrow right +" <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" +// arrow down +" <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" +// arrow left +" <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" +// link to original SVG +" <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 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; + bool setTarget = FALSE; + 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(); + if (result != "") setTarget = TRUE; + } + result+= href+"=\""; + result+=externalRef(relPath,ref,TRUE); + result+= url + "\""; + } + else // should not happen, but handle properly anyway + { + result = href+"=\"" + link + "\""; + } + } + if (!target.isEmpty() && !setTarget) + { + 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. +*/ +bool convertMapFile(FTextStream &t,const char *mapName, + const QCString relPath, bool urlOnly, + const QCString &context) +{ + QFile f(mapName); + if (!f.open(IO_ReadOnly)) + { + err("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.rawData(),maxLineLen); + if (numBytes>0) + { + buf.resize(numBytes+1); + + if (buf.left(5)=="<area") + { + QCString replBuf = replaceRef(buf,relPath,urlOnly,context); + // strip id="..." from replBuf since the id's are not needed and not unique. + int indexS = replBuf.find("id=\""), indexE; + if (indexS>0 && (indexE=replBuf.find('"',indexS+4))!=-1) + { + t << replBuf.left(indexS-1) << replBuf.right(replBuf.length() - indexE - 1); + } + else + { + t << replBuf; + } + } + } + } + return TRUE; +} + +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()); + bool interactiveSVG_local = 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_local = interactiveSVG_local && map->zoomable; + graphId = map->graphId; + relPath = map->relPath; + //printf("DotFilePatcher::addSVGConversion: file=%s zoomable=%d\n", + // m_patchFile.data(),map->zoomable); + } + QString tmpName = QString::fromUtf8(m_patchFile+".tmp"); + QString patchFile = QString::fromUtf8(m_patchFile); + if (!QDir::current().rename(patchFile,tmpName)) + { + err("Failed to rename file %s to %s!\n",m_patchFile.data(),tmpName.data()); + return FALSE; + } + QFile fi(tmpName); + QFile fo(patchFile); + if (!fi.open(IO_ReadOnly)) + { + err("problem opening file %s for patching!\n",tmpName.data()); + QDir::current().rename(tmpName,patchFile); + return FALSE; + } + if (!fo.open(IO_WriteOnly)) + { + err("problem opening file %s for patching!\n",m_patchFile.data()); + QDir::current().rename(tmpName,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.rawData(),maxLineLen); + if (numBytes<=0) + { + break; + } + line.resize(numBytes+1); + + //printf("line=[%s]\n",line.stripWhiteSpace().data()); + int i; + ASSERT(numBytes<maxLineLen); + if (isSVGFile) + { + if (interactiveSVG_local) + { + 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()) + { + QGString result; + FTextStream tt(&result); + 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()); + convertMapFile(tt,map->mapFile,map->relPath,map->urlOnly,map->context); + if (!result.isEmpty()) + { + t << "<map name=\"" << map->label << "\" id=\"" << map->label << "\">" << endl; + t << result; + 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()); + if (!DotGraph::writeVecGfxFigure(t,map->label,map->mapFile)) + { + err("problem writing FIG %d figure!\n",mapId); + return FALSE; + } + } + else // error invalid map id! + { + err("Found invalid bounding FIG %d in file %s!\n",mapId,m_patchFile.data()); + t << line; + } + } + else + { + t << line; + } + lineNr++; + } + fi.close(); + if (isSVGFile && interactiveSVG_local && 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("problem opening file %s for reading!\n",tmpName.data()); + return FALSE; + } + if (!fo.open(IO_WriteOnly)) + { + err("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.rawData(),maxLineLen); + if (numBytes<=0) + { + break; + } + line.resize(numBytes+1); + 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; +} diff --git a/src/dotfilepatcher.h b/src/dotfilepatcher.h new file mode 100644 index 0000000..dd5c511 --- /dev/null +++ b/src/dotfilepatcher.h @@ -0,0 +1,53 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#ifndef DOTFILEPATCHER_H +#define DOTFILEPATCHER_H + +#include "qcstring.h" +#include "qlist.h" + +/** Helper class to insert a set of map file into an output file */ +class DotFilePatcher +{ + public: + DotFilePatcher(const char *patchFile); + int addMap(const QCString &mapFile,const QCString &relPath, + bool urlOnly,const QCString &context,const QCString &label); + int addFigure(const QCString &baseName, + const QCString &figureName,bool heightCheck); + int addSVGConversion(const QCString &relPath,bool urlOnly, + const QCString &context,bool zoomable,int graphId); + int addSVGObject(const QCString &baseName, const QCString &figureName, + const QCString &relPath); + bool run(); + QCString file() const; + + private: + struct Map + { + QCString mapFile; + QCString relPath; + bool urlOnly; + QCString context; + QCString label; + bool zoomable; + int graphId; + }; + QList<Map> m_maps; + QCString m_patchFile; +}; + +#endif diff --git a/src/dotgfxhierarchytable.cpp b/src/dotgfxhierarchytable.cpp new file mode 100644 index 0000000..0082b7e --- /dev/null +++ b/src/dotgfxhierarchytable.cpp @@ -0,0 +1,302 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "dotgfxhierarchytable.h" + +#include "language.h" +#include "util.h" +#include "message.h" +#include "doxygen.h" +#include "classlist.h" + +#define OPTIMIZE_OUTPUT_SLICE Config_getBool(OPTIMIZE_OUTPUT_SLICE) + +QCString DotGfxHierarchyTable::getBaseName() const +{ + QCString baseName; + if (m_prefix.isEmpty()) + baseName.sprintf("inherit_graph_%d", m_graphId); + else + baseName.sprintf("%sinherit_graph_%d",m_prefix.data(), m_graphId); + return baseName; +} + +void DotGfxHierarchyTable::computeTheGraph() +{ + QListIterator<DotNode> dnli2(*m_rootNodes); + DotNode *node; + + FTextStream md5stream(&m_theGraph); + writeGraphHeader(md5stream,theTranslator->trGraphicalHierarchy()); + md5stream << " rankdir=\"LR\";" << endl; + for (dnli2.toFirst();(node=dnli2.current());++dnli2) + { + if (node->subgraphId()==m_rootSubgraphNode->subgraphId()) + { + node->clearWriteFlag(); + } + } + for (dnli2.toFirst();(node=dnli2.current());++dnli2) + { + if (node->subgraphId()==m_rootSubgraphNode->subgraphId()) + { + node->write(md5stream,Hierarchy,GOF_BITMAP,FALSE,TRUE,TRUE); + } + } + writeGraphFooter(md5stream); + +} + +QCString DotGfxHierarchyTable::getMapLabel() const +{ + return escapeCharsInString(m_rootSubgraphNode->label(),FALSE); +} + +void DotGfxHierarchyTable::createGraph(DotNode *n,FTextStream &out, + const char *path,const char *fileName,int id) +{ + m_rootSubgraphNode = n; + m_graphId = id; + m_noDivTag = TRUE; + m_zoomable = FALSE; + DotGraph::writeGraph(out, GOF_BITMAP, EOF_Html, path, fileName, "", TRUE, 0); +} + +void DotGfxHierarchyTable::writeGraph(FTextStream &out, + const char *path,const char *fileName) +{ + //printf("DotGfxHierarchyTable::writeGraph(%s)\n",name); + //printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count()); + + if (m_rootSubgraphs->count()==0) return; + + QDir d(path); + // store the original directory + if (!d.exists()) + { + err("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) + { + out << "<tr><td>"; + createGraph(n,out,path,fileName,count++); + out << "</td></tr>" << endl; + } + out << "</table>" << endl; +} + +void DotGfxHierarchyTable::addHierarchy(DotNode *n,const 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->label().data(), + // bClass->name().data()); + if ((bn=m_usedNodes->find(bClass->name()))) // node already present + { + if (n->children()==0 || n->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->label().data(), + // bn->label().data(), + // bn->children() ? bn->children()->count() : 0, + // bn->parents() ? bn->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(getNextNodeNumber(), + 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->label().data(), + // bn->label().data(), + // bn->children() ? bn->children()->count() : 0, + // bn->parents() ? bn->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->isVisited() && !hideSuper && bClass->subClasses()) + { + bool wasVisited=bClass->isVisited(); + bClass->setVisited(TRUE); + addHierarchy(bn,bClass,wasVisited); + } + } + } + } + //printf("end addHierarchy\n"); +} + +void DotGfxHierarchyTable::addClassList(const 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 (cd->getLanguage()==SrcLangExt_VHDL && + (VhdlDocGen::VhdlClasses)cd->protection()!=VhdlDocGen::ENTITYCLASS + ) + { + continue; + } + if (OPTIMIZE_OUTPUT_SLICE && cd->compoundType() != m_classType) + { + continue; + } + 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(getNextNodeNumber(), + cd->displayName(), + tooltip, + tmp_url.data()); + + //m_usedNodes->clear(); + m_usedNodes->insert(cd->name(),n); + m_rootNodes->insert(0,n); + if (!cd->isVisited() && cd->subClasses()) + { + addHierarchy(n,cd,cd->isVisited()); + cd->setVisited(TRUE); + } + } + } +} + +DotGfxHierarchyTable::DotGfxHierarchyTable(const char *prefix,ClassDef::CompoundType ct) + : m_prefix(prefix) + , m_classType(ct) +{ + 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->subgraphId()==-1) // not yet colored + { + //printf("Starting at node %s (%p): %d\n",n->label().data(),n,curColor); + done=FALSE; // still uncolored nodes + n->setSubgraphId(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->label().data(),n->m_subgraphId, + // n->children()?n->children()->count():0, + // n->parents()?n->parents()->count():0); + int number=0; + n->renumberNodes(number); + } +} + +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; +} diff --git a/src/dotgfxhierarchytable.h b/src/dotgfxhierarchytable.h new file mode 100644 index 0000000..5a5bcad --- /dev/null +++ b/src/dotgfxhierarchytable.h @@ -0,0 +1,55 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#ifndef DOTGFXHIERARCHYTABLE_H +#define DOTGFXHIERARCHYTABLE_H + +#include "classdef.h" +#include "ftextstream.h" + +#include "dotgraph.h" +#include "dotnode.h" + +/** Represents a graphical class hierarchy */ +class DotGfxHierarchyTable : public DotGraph +{ + public: + DotGfxHierarchyTable(const char *prefix="",ClassDef::CompoundType ct=ClassDef::Class); + ~DotGfxHierarchyTable(); + void createGraph(DotNode *rootNode,FTextStream &t,const char *path, + const char *fileName,int id); + void writeGraph(FTextStream &t,const char *path, const char *fileName); + const DotNodeList *subGraphs() const { return m_rootSubgraphs; } + + protected: + virtual QCString getBaseName() const; + virtual QCString getMapLabel() const; + virtual void computeTheGraph(); + + private: + void addHierarchy(DotNode *n,const ClassDef *cd,bool hide); + void addClassList(const ClassSDict *cl); + + int m_graphId; + QCString m_prefix; + ClassDef::CompoundType m_classType; + QList<DotNode> *m_rootNodes; + QDict<DotNode> *m_usedNodes; + DotNodeList *m_rootSubgraphs; + DotNode * m_rootSubgraphNode; +}; + + +#endif diff --git a/src/dotgraph.cpp b/src/dotgraph.cpp new file mode 100644 index 0000000..ca6bcca --- /dev/null +++ b/src/dotgraph.cpp @@ -0,0 +1,400 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "config.h" +#include "doxygen.h" +#include "index.h" +#include "md5.h" +#include "message.h" +#include "util.h" + +#include "dot.h" +#include "dotrunner.h" +#include "dotgraph.h" +#include "dotnode.h" + +#define MAP_CMD "cmapx" + +QCString DotGraph::DOT_FONTNAME; // will be initialized in initDot +int DotGraph::DOT_FONTSIZE; // will be initialized in initDot + +/*! Checks if a file "baseName".md5 exists. If so the contents +* are compared with \a md5. If equal FALSE is returned. +* The .md5 is created or updated after successful creation of the output file. +*/ +static bool checkMd5Signature(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.rawData(),32); + md5stored[32]='\0'; + // compare checksum + if (bytesRead==32 && md5==md5stored) + { + // bail out if equal + return FALSE; + } + } + 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; +} + +static void removeDotGraph(const QCString &dotName) +{ + if (Config_getBool(DOT_CLEANUP)) + { + QDir d; + d.remove(dotName); + } +} + +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,FALSE); + 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 +} + +//-------------------------------------------------------------------- + +QCString DotGraph::IMG_EXT; + +QCString DotGraph::imgName() const +{ + return m_baseName + ((m_graphFormat == GOF_BITMAP) ? + ("." + IMG_EXT) : (Config_getBool(USE_PDFLATEX) ? ".pdf" : ".eps")); +} + +QCString DotGraph::writeGraph( + FTextStream& t, // output stream for the code file (html, ...) + GraphOutputFormat gf, // bitmap(png/svg) or ps(eps/pdf) + EmbeddedOutputFormat ef, // html, latex, ... + const char* path, // output folder + const char* fileName, // name of the code file (for code patcher) + const char* relPath, // output folder relativ to code file + bool generateImageMap, // in case of bitmap, shall there be code generated? + int graphId) // number of this graph in the current code, used in svg code +{ + m_graphFormat = gf; + m_textFormat = ef; + m_dir = QDir(path); + m_fileName = fileName; + m_relPath = relPath; + m_generateImageMap = generateImageMap; + m_graphId = graphId; + + m_absPath = QCString(m_dir.absPath().data()) + "/"; + m_baseName = getBaseName(); + + computeTheGraph(); + + m_regenerate = prepareDotFile(); + + if (!m_doNotAddImageToIndex) Doxygen::indexList->addImageFile(imgName()); + + generateCode(t); + + return m_baseName; +} + +bool DotGraph::prepareDotFile() +{ + if (!m_dir.exists()) + { + err("Output dir %s does not exist!\n", m_dir.path().data()); exit(1); + } + + QCString sigStr(33); + uchar md5_sig[16]; + // calculate md5 + MD5Buffer((const unsigned char*)m_theGraph.data(), m_theGraph.length(), md5_sig); + // convert result to a string + MD5SigToString(md5_sig, sigStr.rawData(), 33); + + // already queued files are processed again in case the output format has changed + + if (!checkMd5Signature(absBaseName(), sigStr) && + checkDeliverables(absImgName(), + m_graphFormat == GOF_BITMAP && m_generateImageMap ? absMapName() : QCString() + ) + ) + { + // all needed files are there + removeDotGraph(absDotName()); + return FALSE; + } + + // need to rebuild the image + + // write .dot file because image was new or has changed + QFile f(absDotName()); + if (!f.open(IO_WriteOnly)) + { + err("Could not open file %s for writing\n",f.name().data()); + return TRUE; + } + FTextStream t(&f); + t << m_theGraph; + f.close(); + + if (m_graphFormat == GOF_BITMAP) + { + // run dot to create a bitmap image + DotRunner * dotRun = DotManager::instance()->createRunner(absDotName(), sigStr); + dotRun->addJob(Config_getEnum(DOT_IMAGE_FORMAT), absImgName()); + if (m_generateImageMap) dotRun->addJob(MAP_CMD, absMapName()); + } + else if (m_graphFormat == GOF_EPS) + { + // run dot to create a .eps image + DotRunner *dotRun = DotManager::instance()->createRunner(absDotName(), sigStr); + if (Config_getBool(USE_PDFLATEX)) + { + dotRun->addJob("pdf",absImgName()); + } + else + { + dotRun->addJob("ps",absImgName()); + } + } + return TRUE; +} + +void DotGraph::generateCode(FTextStream &t) +{ + if (m_graphFormat==GOF_BITMAP && m_textFormat==EOF_DocBook) + { + t << "<para>" << endl; + t << " <informalfigure>" << endl; + t << " <mediaobject>" << endl; + t << " <imageobject>" << endl; + t << " <imagedata"; + t << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << m_relPath << m_baseName << "." << IMG_EXT << "\">"; + t << "</imagedata>" << endl; + t << " </imageobject>" << endl; + t << " </mediaobject>" << endl; + t << " </informalfigure>" << endl; + t << "</para>" << endl; + } + else if (m_graphFormat==GOF_BITMAP && m_generateImageMap) // produce HTML to include the image + { + if (IMG_EXT=="svg") // add link to SVG file without map file + { + if (!m_noDivTag) t << "<div class=\"center\">"; + if (m_regenerate || !writeSVGFigureLink(t,m_relPath,m_baseName,absImgName())) // need to patch the links in the generated SVG file + { + if (m_regenerate) + { + DotManager::instance()->addSVGConversion(absImgName(),m_relPath,FALSE,QCString(),m_zoomable,m_graphId); + } + int mapId = DotManager::instance()->addSVGObject(m_fileName,m_baseName,absImgName(),m_relPath); + t << "<!-- SVG " << mapId << " -->" << endl; + } + if (!m_noDivTag) t << "</div>" << endl; + } + else // add link to bitmap file with image map + { + if (!m_noDivTag) t << "<div class=\"center\">"; + t << "<img src=\"" << relImgName() << "\" border=\"0\" usemap=\"#" << getMapLabel() << "\" alt=\"" << getImgAltText() << "\"/>"; + if (!m_noDivTag) t << "</div>"; + t << endl; + if (m_regenerate || !insertMapFile(t, absMapName(), m_relPath, getMapLabel())) + { + int mapId = DotManager::instance()->addMap(m_fileName, absMapName(), m_relPath, m_urlOnly, QCString(), getMapLabel()); + t << "<!-- MAP " << mapId << " -->" << endl; + } + } + } + else if (m_graphFormat==GOF_EPS) // produce tex to include the .eps image + { + if (m_regenerate || !writeVecGfxFigure(t,m_baseName,absBaseName())) + { + int figId = DotManager::instance()->addFigure(m_fileName,m_baseName,absBaseName(),FALSE /*TRUE*/); + t << endl << "% FIG " << figId << endl; + } + } +} + +void DotGraph::writeGraphHeader(FTextStream &t,const QCString &title) +{ + t << "digraph "; + if (title.isEmpty()) + { + t << "\"Dot Graph\""; + } + else + { + t << "\"" << convertToXML(title) << "\""; + } + t << endl << "{" << endl; + if (Config_getBool(INTERACTIVE_SVG)) // insert a comment to force regeneration when this + // option is toggled + { + t << " // INTERACTIVE_SVG=YES\n"; + } + t << " // LATEX_PDF_SIZE\n"; // write placeholder for LaTeX PDF bounding box size repacement + if (Config_getBool(DOT_TRANSPARENT)) + { + t << " bgcolor=\"transparent\";" << endl; + } + t << " edge [fontname=\"" << DOT_FONTNAME << "\"," + "fontsize=\"" << DOT_FONTSIZE << "\"," + "labelfontname=\"" << DOT_FONTNAME << "\"," + "labelfontsize=\"" << DOT_FONTSIZE << "\"];\n"; + t << " node [fontname=\"" << DOT_FONTNAME << "\"," + "fontsize=\"" << DOT_FONTSIZE << "\",shape=record];\n"; +} + +void DotGraph::writeGraphFooter(FTextStream &t) +{ + t << "}" << endl; +} + +void DotGraph::computeGraph(DotNode *root, + GraphType gt, + GraphOutputFormat format, + const QCString &rank, // either "LR", "RL", or "" + bool renderParents, + bool backArrows, + const QCString &title, + QGString &graphStr) +{ + //printf("computeMd5Signature\n"); + QGString buf; + FTextStream md5stream(&buf); + writeGraphHeader(md5stream,title); + if (!rank.isEmpty()) + { + md5stream << " rankdir=\"" << rank << "\";" << endl; + } + root->clearWriteFlag(); + root->write(md5stream, gt, format, gt!=CallGraph && gt!=Dependency, TRUE, backArrows); + if (renderParents && root->parents()) + { + QListIterator<DotNode> dnli(*root->parents()); + const 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->edgeInfo()->at(pn->children()->findRef(root)), // edge info + FALSE, // topDown? + backArrows // point back? + ); + } + pn->write(md5stream, // stream + gt, // graph type + format, // output format + TRUE, // topDown? + FALSE, // toChildren? + backArrows // backward pointing arrows? + ); + } + } + writeGraphFooter(md5stream); + + graphStr=buf.data(); +} + +bool DotGraph::writeVecGfxFigure(FTextStream &out,const QCString &baseName, + const QCString &figureName) +{ + int width=400,height=550; + if (Config_getBool(USE_PDFLATEX)) + { + if (!DotRunner::readBoundingBox(figureName+".pdf",&width,&height,FALSE)) + { + //printf("writeVecGfxFigure()=0\n"); + return FALSE; + } + } + else + { + if (!DotRunner::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; +} diff --git a/src/dotgraph.h b/src/dotgraph.h new file mode 100644 index 0000000..27d6938 --- /dev/null +++ b/src/dotgraph.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * + * Copyright (C) 1997-2019 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. + * + */ + +#ifndef DOTGRAPH_H +#define DOTGRAPH_H + +#include <qcstring.h> +#include <qgstring.h> +#include <qdir.h> + +class FTextStream; +class DotNode; + +enum GraphOutputFormat { GOF_BITMAP, GOF_EPS }; +enum EmbeddedOutputFormat { EOF_Html, EOF_LaTeX, EOF_Rtf, EOF_DocBook }; +enum GraphType { Dependency, Inheritance, Collaboration, Hierarchy, CallGraph }; + +/** A dot graph */ +class DotGraph +{ + public: + DotGraph() : m_curNodeNumber(0), m_doNotAddImageToIndex(FALSE), m_noDivTag(FALSE), m_zoomable(TRUE), m_urlOnly(FALSE) {} + virtual ~DotGraph() {} + + static QCString DOT_FONTNAME; // will be initialized in initDot + static int DOT_FONTSIZE; // will be initialized in initDot + + static bool writeVecGfxFigure(FTextStream& out, const QCString& baseName, const QCString& figureName); + + protected: + /** returns node numbers. The Counter is reset by the constructor */ + int getNextNodeNumber() { return ++m_curNodeNumber; } + + QCString writeGraph(FTextStream &t, + GraphOutputFormat gf, + EmbeddedOutputFormat ef, + const char *path, + const char *fileName, + const char *relPath, + bool writeImageMap=TRUE, + int graphId=-1 + ); + + static void writeGraphHeader(FTextStream& t, const QCString& title = QCString()); + static void writeGraphFooter(FTextStream& t); + static void computeGraph(DotNode* root, + GraphType gt, + GraphOutputFormat format, + const QCString& rank, // either "LR", "RL", or "" + bool renderParents, + bool backArrows, + const QCString& title, + QGString& graphStr + ); + + virtual QCString getBaseName() const = 0; + virtual QCString absMapName() const { return m_absPath + m_baseName + ".map"; } + virtual QCString getMapLabel() const = 0; + virtual QCString getImgAltText() const { return ""; } + + virtual void computeTheGraph() = 0; + + static QCString IMG_EXT; + + friend void initDot(); + + QCString absBaseName() const { return m_absPath + m_baseName; } + QCString absDotName() const { return m_absPath + m_baseName + ".dot"; } + QCString imgName() const; + QCString absImgName() const { return m_absPath + imgName(); } + QCString relImgName() const { return m_relPath + imgName(); } + + // the following variables are used while writing the graph to a .dot file + GraphOutputFormat m_graphFormat; + EmbeddedOutputFormat m_textFormat; + QDir m_dir; + QCString m_fileName; + QCString m_relPath; + bool m_generateImageMap; + int m_graphId; + + QCString m_absPath; + QCString m_baseName; + QGString m_theGraph; + bool m_regenerate; + bool m_doNotAddImageToIndex; + bool m_noDivTag; + bool m_zoomable; + bool m_urlOnly; + + private: + DotGraph(const DotGraph &); + DotGraph &operator=(const DotGraph &); + + bool prepareDotFile(); + void generateCode(FTextStream &t); + + int m_curNodeNumber; +}; + +#endif diff --git a/src/dotgroupcollaboration.cpp b/src/dotgroupcollaboration.cpp new file mode 100644 index 0000000..be55ac0 --- /dev/null +++ b/src/dotgroupcollaboration.cpp @@ -0,0 +1,381 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "dotgroupcollaboration.h" + +#include "dotnode.h" +#include "classlist.h" +#include "doxygen.h" +#include "namespacedef.h" +#include "pagedef.h" +#include "util.h" +#include "config.h" + +#define DOT_TRANSPARENT Config_getBool(DOT_TRANSPARENT) + +DotGroupCollaboration::DotGroupCollaboration(const GroupDef* gd) +{ + QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase(); + m_usedNodes = new QDict<DotNode>(1009); + QCString tooltip = gd->briefDescriptionAsTooltip(); + m_rootNode = new DotNode(getNextNodeNumber(), gd->groupTitle(), tooltip, 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(const GroupDef* gd) +{ + QCString tmp_url; + //=========================== + // hierarchy. + + // Write parents + const GroupList *groups = gd->partOfGroups(); + if ( groups ) + { + GroupListIterator gli(*groups); + const 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(getNextNodeNumber(), 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()); + const 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(getNextNodeNumber(), 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(MemberListType_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()); + const 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()); + const 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( + const 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(getNextNodeNumber(), 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::getBaseName() const +{ + return m_diskName; +} + +void DotGroupCollaboration::computeTheGraph() +{ + FTextStream md5stream(&m_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,Inheritance,m_graphFormat,TRUE,FALSE,FALSE); + } + + // write edges + QListIterator<Edge> eli(m_edges); + Edge* edge; + for (eli.toFirst();(edge=eli.current());++eli) + { + edge->write( md5stream ); + } + + writeGraphFooter(md5stream); + +} + +QCString DotGroupCollaboration::getMapLabel() const +{ + return escapeCharsInString(m_baseName, FALSE); +} + +QCString DotGroupCollaboration::writeGraph( FTextStream &t, + GraphOutputFormat graphFormat, EmbeddedOutputFormat textFormat, + const char *path, const char *fileName, const char *relPath, + bool generateImageMap,int graphId) +{ + m_doNotAddImageToIndex = TRUE; + + return DotGraph::writeGraph(t, graphFormat, textFormat, path, fileName, relPath, generateImageMap, graphId); +} + +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 << DotNode::convertLabel(link->label); + } + if (count==maxLabels) t << "\\n..."; + t << "\""; + + } + switch( eType ) + { + case thierarchy: + arrowStyle = "dir=\"back\", style=\"solid\""; + break; + 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 (DOT_TRANSPARENT) + { + t << " bgcolor=\"transparent\";" << endl; + } + t << " edge [fontname=\"" << DOT_FONTNAME << "\",fontsize=\"" << DOT_FONTSIZE << "\"," + "labelfontname=\"" << DOT_FONTNAME << "\",labelfontsize=\"" << DOT_FONTSIZE << "\"];\n"; + t << " node [fontname=\"" << DOT_FONTNAME << "\",fontsize=\"" << DOT_FONTSIZE << "\",shape=box];\n"; + t << " rankdir=LR;\n"; +} diff --git a/src/dotgroupcollaboration.h b/src/dotgroupcollaboration.h new file mode 100644 index 0000000..539637f --- /dev/null +++ b/src/dotgroupcollaboration.h @@ -0,0 +1,85 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#ifndef DOTGROUPCOLLABORATION_H +#define DOTGROUPCOLLABORATION_H + +#include "dotgraph.h" +#include "qlist.h" +#include "groupdef.h" + +/** Representation of a group collaboration graph */ +class DotGroupCollaboration : public DotGraph +{ + public : + DotGroupCollaboration(const GroupDef* gd); + ~DotGroupCollaboration(); + QCString writeGraph(FTextStream &t, GraphOutputFormat gf,EmbeddedOutputFormat ef, + const char *path,const char *fileName,const char *relPath, + bool writeImageMap=TRUE,int graphId=-1); + bool isTrivial() const; + + protected: + virtual QCString getBaseName() const; + virtual QCString getMapLabel() const; + virtual void computeTheGraph(); + + private : + enum EdgeType + { + tmember = 0, + tclass, + tnamespace, + tfile, + tpages, + tdir, + thierarchy + }; + + struct Link + { + Link(const QCString lab,const QCString &u) : label(lab), url(u) {} + QCString label; + QCString url; + }; + + struct Edge + { + Edge(DotNode *start,DotNode *end,EdgeType type) + : pNStart(start), pNEnd(end), eType(type) + { links.setAutoDelete(TRUE); } + + DotNode* pNStart; + DotNode* pNEnd; + EdgeType eType; + + QList<Link> links; + void write( FTextStream &t ) const; + }; + + void buildGraph(const GroupDef* gd); + void addCollaborationMember(const Definition* def, QCString& url, EdgeType eType ); + void addMemberList( class MemberList* ml ); + void writeGraphHeader(FTextStream &t,const QCString &title) const; + Edge* addEdge( DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType, + const QCString& _label, const QCString& _url ); + + DotNode *m_rootNode; + QDict<DotNode> *m_usedNodes; + QCString m_diskName; + QList<Edge> m_edges; +}; + +#endif diff --git a/src/dotincldepgraph.cpp b/src/dotincldepgraph.cpp new file mode 100644 index 0000000..c968b68 --- /dev/null +++ b/src/dotincldepgraph.cpp @@ -0,0 +1,238 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "dotincldepgraph.h" +#include "dotnode.h" +#include "util.h" +#include "config.h" + +void DotInclDepGraph::buildGraph(DotNode *n,const 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) + { + const 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(getNextNodeNumber(),// 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) + { + DotNode *n = queue.take(0); + if (!n->isVisible() && n->distance()<=Config_getInt(MAX_DOT_GRAPH_DEPTH)) // not yet processed + { + n->markAsVisible(); + maxNodes--; + // add direct children + if (n->children()) + { + QListIterator<DotNode> li(*n->children()); + const 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->children()) + { + QListIterator<DotNode> li(*n->children()); + const DotNode *dn; + for (li.toFirst();(dn=li.current());++li) + { + if (!dn->isVisible()) + { + truncated = TRUE; + } + else + { + queue.append(dn); + } + } + } + n->markAsTruncated(truncated); + } + } +} + +DotInclDepGraph::DotInclDepGraph(const FileDef *fd,bool inverse) +{ + m_inverse = inverse; + ASSERT(fd!=0); + m_inclDepFileName = fd->includeDependencyGraphFileName(); + m_inclByDepFileName = fd->includedByDependencyGraphFileName(); + QCString tmp_url=fd->getReference()+"$"+fd->getOutputFileBase(); + QCString tooltip = fd->briefDescriptionAsTooltip(); + m_startNode = new DotNode(getNextNodeNumber(), + fd->docName(), + tooltip, + 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); + + int maxNodes = Config_getInt(DOT_GRAPH_MAX_NODES); + QList<DotNode> openNodeQueue; + openNodeQueue.append(m_startNode); + determineVisibleNodes(openNodeQueue,maxNodes); + openNodeQueue.clear(); + openNodeQueue.append(m_startNode); + determineTruncatedNodes(openNodeQueue); +} + +DotInclDepGraph::~DotInclDepGraph() +{ + DotNode::deleteNodes(m_startNode); + delete m_usedNodes; +} + +QCString DotInclDepGraph::getBaseName() const +{ + if (m_inverse) + { + return m_inclByDepFileName; + } + else + { + return m_inclDepFileName; + } +} + +void DotInclDepGraph::computeTheGraph() +{ + computeGraph(m_startNode, Dependency, m_graphFormat, "", FALSE, + m_inverse, m_startNode->label(), m_theGraph); +} + +QCString DotInclDepGraph::getMapLabel() const +{ + if (m_inverse) + { + return escapeCharsInString(m_startNode->label(),FALSE) + "dep"; + } + else + { + return escapeCharsInString(m_startNode->label(),FALSE); + } +} + +QCString DotInclDepGraph::writeGraph(FTextStream &out, + GraphOutputFormat graphFormat, + EmbeddedOutputFormat textFormat, + const char *path, + const char *fileName, + const char *relPath, + bool generateImageMap, + int graphId) +{ + return DotGraph::writeGraph(out, graphFormat, textFormat, path, fileName, relPath, generateImageMap, graphId); +} + +bool DotInclDepGraph::isTrivial() const +{ + return m_startNode->children()==0; +} + +bool DotInclDepGraph::isTooBig() const +{ + int numNodes = m_startNode->children() ? m_startNode->children()->count() : 0; + return numNodes>=Config_getInt(DOT_GRAPH_MAX_NODES); +} + +void DotInclDepGraph::writeXML(FTextStream &t) +{ + QDictIterator<DotNode> dni(*m_usedNodes); + DotNode *node; + for (;(node=dni.current());++dni) + { + node->writeXML(t,FALSE); + } +} + +void DotInclDepGraph::writeDocbook(FTextStream &t) +{ + QDictIterator<DotNode> dni(*m_usedNodes); + DotNode *node; + for (;(node=dni.current());++dni) + { + node->writeDocbook(t,FALSE); + } +} diff --git a/src/dotincldepgraph.h b/src/dotincldepgraph.h new file mode 100644 index 0000000..b664ccb --- /dev/null +++ b/src/dotincldepgraph.h @@ -0,0 +1,56 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#ifndef DOTINCLDEPGRAPH_H +#define DOTINCLDEPGRAPH_H + +#include "qcstring.h" +#include "filedef.h" + +#include "dotgraph.h" + +/** Representation of an include dependency graph */ +class DotInclDepGraph : public DotGraph +{ + public: + DotInclDepGraph(const FileDef *fd,bool inverse); + ~DotInclDepGraph(); + QCString writeGraph(FTextStream &t, GraphOutputFormat gf, EmbeddedOutputFormat ef, + const char *path,const char *fileName,const char *relPath, + bool writeImageMap=TRUE,int graphId=-1); + bool isTrivial() const; + bool isTooBig() const; + void writeXML(FTextStream &t); + void writeDocbook(FTextStream &t); + + protected: + virtual QCString getBaseName() const; + virtual QCString getMapLabel() const; + virtual void computeTheGraph(); + + private: + QCString diskName() const; + void buildGraph(DotNode *n,const FileDef *fd,int distance); + void determineVisibleNodes(QList<DotNode> &queue,int &maxNodes); + void determineTruncatedNodes(QList<DotNode> &queue); + + DotNode *m_startNode; + QDict<DotNode> *m_usedNodes; + QCString m_inclDepFileName; + QCString m_inclByDepFileName; + bool m_inverse; +}; + +#endif diff --git a/src/dotnode.cpp b/src/dotnode.cpp new file mode 100644 index 0000000..41d5f06 --- /dev/null +++ b/src/dotnode.cpp @@ -0,0 +1,950 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "dotnode.h" + +#include "ftextstream.h" +#include "classdef.h" +#include "config.h" +#include "memberlist.h" +#include "membergroup.h" +#include "language.h" +#include "doxygen.h" +#include "util.h" + +/** 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; +}; + +/*! 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 + "orange" // type constraint +}; + +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 + "orange" // type constraint +}; + +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 +}; + +static EdgeProperties normalEdgeProps = +{ + normalEdgeColorMap, normalArrowStyleMap, normalEdgeStyleMap +}; + +static EdgeProperties umlEdgeProps = +{ + umlEdgeColorMap, umlArrowStyleMap, umlEdgeStyleMap +}; + +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; + case '\\': result+="\\\\"; break; + default: result+=c; break; + } + } + return result; +} + +static void writeBoxMemberList(FTextStream &t, + char prot,MemberList *ml,const 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)) + { + int numFields = Config_getInt(UML_LIMIT_NUM_FIELDS); + if (numFields>0 && (totalCount>numFields*3/2 && count>=numFields)) + { + t << theTranslator->trAndMore(QCString().sprintf("%d",totalCount-count)) << "\\l"; + break; + } + else + { + t << prot << " "; + t << DotNode::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); + } + } + } + } +} + +QCString DotNode::convertLabel(const QCString &l) +{ + QString bBefore("\\_/<({[: =-+@%#~?$"); // break before character set + QString bAfter(">]),:;|"); // break after character set + QString p(l); + if (p.isEmpty()) return QCString(); + QString result; + QChar c,pc=0; + uint idx = 0; + int len=p.length(); + int charsLeft=len; + int sinceLast=0; + int foldLen=17; // ideal text length + while (idx < p.length()) + { + c = p[idx++]; + QString 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: replacement=c; 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) && p[idx].category()==QChar::Letter_Uppercase) + { + result+=replacement; + result+="\\l"; + foldLen = (foldLen+sinceLast+1)/2; + sinceLast=0; + } + else if (charsLeft>foldLen/3 && sinceLast>foldLen && bAfter.contains(c) && (c!=':' || p[idx]!=':')) + { + result+=replacement; + result+="\\l"; + foldLen = (foldLen+sinceLast+1)/2; + sinceLast=0; + } + else + { + result+=replacement; + sinceLast++; + } + charsLeft--; + pc=c; + } + return result.utf8(); +} + +static QCString stripProtectionPrefix(const QCString &s) +{ + if (!s.isEmpty() && (s[0]=='-' || s[0]=='+' || s[0]=='~' || s[0]=='#')) + { + return s.mid(1); + } + else + { + return s; + } +} + +DotNode::DotNode(int n,const char *lab,const char *tip, const char *url, + bool isRoot,const 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) + , m_renumbered(false) +{ +} + +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( + edgeColor, + edgeStyle, + edgeLab, + edgeURL, + edgeLabCol==-1 ? edgeColor : 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; +} + +inline int DotNode::findParent( DotNode *n ) +{ + if ( !m_parents ) return -1; + return m_parents->find(n); +} + +/*! helper function that deletes all nodes in a connected graph, given +* one of the graph's nodes +*/ +void DotNode::deleteNodes(DotNode *node,SDict<DotNode> *skipNodes) +{ + //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. +} + +void DotNode::writeBox(FTextStream &t, + GraphType gt, + GraphOutputFormat /*format*/, + bool hasNonReachableChildren) const +{ + const char *labCol = + m_url.isEmpty() ? "grey75" : // non link + (hasNonReachableChildren ? "red" : "black"); + t << " Node" << m_number << " [label=\""; + + if (m_classDef && Config_getBool(UML_LOOK) && (gt==Inheritance || gt==Collaboration)) + { + // add names shown as relations to a dictionary, so we don't show + // them as attributes as well + QDict<void> arrowNames(17); + if (m_edgeInfo) + { + // for each edge + QListIterator<EdgeInfo> li(*m_edgeInfo); + EdgeInfo *ei; + for (li.toFirst();(ei=li.current());++li) + { + if (!ei->label().isEmpty()) // labels joined by \n + { + int li=ei->label().find('\n'); + int p=0; + QCString lab; + while ((li=ei->label().find('\n',p))!=-1) + { + lab = stripProtectionPrefix(ei->label().mid(p,li-p)); + arrowNames.insert(lab,(void*)0x8); + p=li+1; + } + lab = stripProtectionPrefix(ei->label().right(ei->label().length()-p)); + arrowNames.insert(lab,(void*)0x8); + } + } + } + + //printf("DotNode::writeBox for %s\n",m_classDef->name().data()); + t << "{" << convertLabel(m_label); + t << "\\n|"; + writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubAttribs),m_classDef,FALSE,&arrowNames); + writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubStaticAttribs),m_classDef,TRUE,&arrowNames); + writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_properties),m_classDef,FALSE,&arrowNames); + writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacAttribs),m_classDef,FALSE,&arrowNames); + writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacStaticAttribs),m_classDef,TRUE,&arrowNames); + writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proAttribs),m_classDef,FALSE,&arrowNames); + writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proStaticAttribs),m_classDef,TRUE,&arrowNames); + if (Config_getBool(EXTRACT_PRIVATE)) + { + writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priAttribs),m_classDef,FALSE,&arrowNames); + writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priStaticAttribs),m_classDef,TRUE,&arrowNames); + } + t << "|"; + writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubMethods),m_classDef); + writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubStaticMethods),m_classDef,TRUE); + writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberListType_pubSlots),m_classDef); + writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacMethods),m_classDef); + writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberListType_pacStaticMethods),m_classDef,TRUE); + writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proMethods),m_classDef); + writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proStaticMethods),m_classDef,TRUE); + writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberListType_proSlots),m_classDef); + if (Config_getBool(EXTRACT_PRIVATE)) + { + writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priMethods),m_classDef); + writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_priStaticMethods),m_classDef,TRUE); + writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberListType_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 + { + if (!Config_getBool(DOT_TRANSPARENT)) + { + t << ",color=\"" << labCol << "\", fillcolor=\""; + 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) << "\""; + } + else + { + t << ",tooltip=\" \""; // space in tooltip is required otherwise still something like 'Node0' is used + } + t << "];" << endl; +} + +void DotNode::writeArrow(FTextStream &t, + GraphType gt, + GraphOutputFormat format, + const DotNode *cn, + const EdgeInfo *ei, + bool topDown, + bool pointBack) const +{ + t << " Node"; + if (topDown) + t << cn->number(); + else + t << m_number; + t << " -> Node"; + if (topDown) + t << m_number; + else + t << cn->number(); + t << " ["; + + const EdgeProperties *eProps = Config_getBool(UML_LOOK) ? ¨EdgeProps : &normalEdgeProps; + QCString aStyle = eProps->arrowStyleMap[ei->color()]; + bool umlUseArrow = aStyle=="odiamond"; + + if (pointBack && !umlUseArrow) t << "dir=\"back\","; + t << "color=\"" << eProps->edgeColorMap[ei->color()] + << "\",fontsize=\"" << DotGraph::DOT_FONTSIZE << "\","; + t << "style=\"" << eProps->edgeStyleMap[ei->style()] << "\""; + if (!ei->label().isEmpty()) + { + t << ",label=\" " << convertLabel(ei->label()) << "\" "; + } + if (Config_getBool(UML_LOOK) && + eProps->arrowStyleMap[ei->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->color()] << "\""; + else + t << ",arrowhead=\"" << eProps->arrowStyleMap[ei->color()] << "\""; + } + + if (format==GOF_BITMAP) t << ",fontname=\"" << DotGraph::DOT_FONTNAME << "\""; + t << "];" << endl; +} + +void DotNode::write(FTextStream &t, + GraphType gt, + GraphOutputFormat format, + bool topDown, + bool toChildren, + bool backArrows) const +{ + //printf("DotNode::write(%d) name=%s this=%p written=%d visible=%d\n",m_distance,m_label.data(),this,m_written,m_visible); + 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); + m_written=TRUE; + QList<DotNode> *nl = toChildren ? m_children : m_parents; + if (nl) + { + if (toChildren) + { + QListIterator<DotNode> dnli1(*nl); + QListIterator<EdgeInfo> dnli2(*m_edgeInfo); + const 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); + } + cn->write(t,gt,format,topDown,toChildren,backArrows); + } + } + 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->edgeInfo()->at(pn->children()->findRef(this)), + FALSE, + backArrows + ); + } + pn->write(t,gt,format,TRUE,FALSE,backArrows); + } + } + } + //printf("end DotNode::write(%d) name=%s\n",distance,m_label.data()); +} + +void DotNode::writeXML(FTextStream &t,bool isClassGraph) const +{ + t << " <node id=\"" << m_number << "\">" << endl; + t << " <label>" << convertToXML(m_label) << "</label>" << endl; + if (!m_url.isEmpty()) + { + QCString url(m_url); + const char *refPtr = url.data(); + char *urlPtr = strchr(url.rawData(),'$'); + 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->number() << "\" relation=\""; + if (isClassGraph) + { + switch(edgeInfo->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::Orange2: t << "type-constraint"; break; + case EdgeInfo::Grey: ASSERT(0); break; + } + } + else // include graph + { + t << "include"; + } + t << "\">" << endl; + if (!edgeInfo->label().isEmpty()) + { + int p=0; + int ni; + while ((ni=edgeInfo->label().find('\n',p))!=-1) + { + t << " <edgelabel>" + << convertToXML(edgeInfo->label().mid(p,ni-p)) + << "</edgelabel>" << endl; + p=ni+1; + } + t << " <edgelabel>" + << convertToXML(edgeInfo->label().right(edgeInfo->label().length()-p)) + << "</edgelabel>" << endl; + } + t << " </childnode>" << endl; + } + } + t << " </node>" << endl; +} + +void DotNode::writeDocbook(FTextStream &t,bool isClassGraph) const +{ + t << " <node id=\"" << m_number << "\">" << endl; + t << " <label>" << convertToXML(m_label) << "</label>" << endl; + if (!m_url.isEmpty()) + { + QCString url(m_url); + const char *refPtr = url.data(); + char *urlPtr = strchr(url.rawData(),'$'); + 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->number() << "\" relation=\""; + if (isClassGraph) + { + switch(edgeInfo->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::Orange2: t << "type-constraint"; break; + case EdgeInfo::Grey: ASSERT(0); break; + } + } + else // include graph + { + t << "include"; + } + t << "\">" << endl; + if (!edgeInfo->label().isEmpty()) + { + int p=0; + int ni; + while ((ni=edgeInfo->label().find('\n',p))!=-1) + { + t << " <edgelabel>" + << convertToXML(edgeInfo->label().mid(p,ni-p)) + << "</edgelabel>" << endl; + p=ni+1; + } + t << " <edgelabel>" + << convertToXML(edgeInfo->label().right(edgeInfo->label().length()-p)) + << "</edgelabel>" << endl; + } + t << " </childnode>" << endl; + } + } + t << " </node>" << endl; +} + + +void DotNode::writeDEF(FTextStream &t) const +{ + 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); + const char *refPtr = url.data(); + char *urlPtr = strchr(url.rawData(),'$'); + 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->number() << "';" << endl; + t << " relation = "; + + switch(edgeInfo->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::Orange2: t << "type-constraint"; break; + case EdgeInfo::Grey: ASSERT(0); break; + } + t << ';' << endl; + + if (!edgeInfo->label().isEmpty()) + { + t << " edgelabel = <<_EnD_oF_dEf_TeXt_" << endl + << edgeInfo->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->isWritten()) + { + pn->clearWriteFlag(); + } + } + } + if (m_children!=0) + { + QListIterator<DotNode> dnlic(*m_children); + DotNode *cn; + for (dnlic.toFirst();(cn=dnlic.current());++dnlic) + { + if (cn->isWritten()) + { + 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->subgraphId()==-1) // uncolored child node + { + cn->setSubgraphId(curColor); + cn->markAsVisible(); + cn->colorConnectedNodes(curColor); + //printf("coloring node %s (%p): %d\n",cn->label().data(),cn,cn->subgraphId()); + } + } + } + + if (m_parents) + { + QListIterator<DotNode> dnlip(*m_parents); + DotNode *pn; + for (dnlip.toFirst();(pn=dnlip.current());++dnlip) + { + if (pn->subgraphId()==-1) // uncolored parent node + { + pn->setSubgraphId(curColor); + pn->markAsVisible(); + pn->colorConnectedNodes(curColor); + //printf("coloring node %s (%p): %d\n",pn->label().data(),pn,pn->subgraphId()); + } + } + } +} + +void DotNode::renumberNodes(int &number) +{ + m_number = number++; + if (m_children) + { + QListIterator<DotNode> dnlic(*m_children); + DotNode *cn; + for (dnlic.toFirst();(cn=dnlic.current());++dnlic) + { + if (!cn->isRenumbered()) + { + cn->markRenumbered(); + cn->renumberNodes(number); + } + } + } +} + +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->hasDocumentation()) + { + pn->markHasDocumentation(); + 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->hasDocumentation()) + { + cn->markHasDocumentation(); + const DotNode *dn = cn->findDocNode(); + if (dn) return dn; + } + } + } + return 0; +} + +//-------------------------------------------------------------- + +int DotNodeList::compareValues(const DotNode *n1,const DotNode *n2) const +{ + return qstricmp(n1->label(),n2->label()); +} + + + diff --git a/src/dotnode.h b/src/dotnode.h new file mode 100644 index 0000000..334fdef --- /dev/null +++ b/src/dotnode.h @@ -0,0 +1,138 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#ifndef DOTNODE_H +#define DOTNODE_H + +#include "sortdict.h" + +#include "dotgraph.h" + +class ClassDef; +class DotNodeList; +class FTextStream; + +/** Attributes of an edge of a dot graph */ +class EdgeInfo +{ + public: + enum Colors { Blue=0, Green=1, Red=2, Purple=3, Grey=4, Orange=5, Orange2=6 }; + enum Styles { Solid=0, Dashed=1 }; + EdgeInfo(int color,int style,const QCString &lab,const QCString &url,int labColor) + : m_color(color), m_style(style), m_label(lab), m_url(url), m_labColor(labColor) {} + ~EdgeInfo() {} + int color() const { return m_color; } + int style() const { return m_style; } + QCString label() const { return m_label; } + QCString url() const { return m_url; } + int labelColor() const { return m_labColor; } + private: + int m_color; + int m_style; + QCString m_label; + QCString m_url; + int m_labColor; +}; + +/** A node in a dot graph */ +class DotNode +{ + public: + static void deleteNodes(DotNode* node, SDict<DotNode>* skipNodes = 0); + static QCString convertLabel(const QCString& l); + DotNode(int n,const char *lab,const char *tip,const char *url, + bool rootNode=FALSE,const ClassDef *cd=0); + ~DotNode(); + + enum TruncState { Unknown, Truncated, Untruncated }; + + void addChild(DotNode *n, + int edgeColor=EdgeInfo::Purple, + int edgeStyle=EdgeInfo::Solid, + const char *edgeLab=0, + const char *edgeURL=0, + int edgeLabCol=-1); + void addParent(DotNode *n); + void deleteNode(DotNodeList &deletedList,SDict<DotNode> *skipNodes=0); + void removeChild(DotNode *n); + void removeParent(DotNode *n); + int findParent( DotNode *n ); + + void write(FTextStream &t,GraphType gt,GraphOutputFormat f, + bool topDown,bool toChildren,bool backArrows) const; + void writeXML(FTextStream &t,bool isClassGraph) const; + void writeDocbook(FTextStream &t,bool isClassGraph) const; + void writeDEF(FTextStream &t) const; + void writeBox(FTextStream &t,GraphType gt,GraphOutputFormat f, + bool hasNonReachableChildren) const; + void writeArrow(FTextStream &t,GraphType gt,GraphOutputFormat f,const DotNode *cn, + const EdgeInfo *ei,bool topDown, bool pointBack=TRUE) const; + + QCString label() const { return m_label; } + int number() const { return m_number; } + bool isVisible() const { return m_visible; } + TruncState isTruncated() const { return m_truncated; } + int distance() const { return m_distance; } + int subgraphId() const { return m_subgraphId; } + bool isRenumbered() const { return m_renumbered; } + bool hasDocumentation() const { return m_hasDoc; } + bool isWritten() const { return m_written; } + + void clearWriteFlag(); + void renumberNodes(int &number); + void markRenumbered() { m_renumbered = true; } + void markHasDocumentation() { m_hasDoc = true; } + void setSubgraphId(int id) { m_subgraphId = id; } + + void colorConnectedNodes(int curColor); + void setDistance(int distance); + const DotNode *findDocNode() const; // only works for acyclic graphs! + void markAsVisible(bool b=TRUE) { m_visible=b; } + void markAsTruncated(bool b=TRUE) { m_truncated=b ? Truncated : Untruncated; } + const QList<DotNode> *children() const { return m_children; } + const QList<DotNode> *parents() const { return m_parents; } + const QList<EdgeInfo> *edgeInfo() const { return m_edgeInfo; } + + private: + int m_number; + QCString m_label; //!< label text + QCString m_tooltip; //!< node's tooltip + QCString m_url; //!< url of the node (format: remote$local) + QList<DotNode> *m_parents; //!< list of parent nodes (incoming arrows) + QList<DotNode> *m_children; //!< list of child nodes (outgoing arrows) + QList<EdgeInfo> *m_edgeInfo; //!< edge info for each child + bool m_deleted; //!< used to mark a node as deleted + mutable bool m_written; //!< used to mark a node as written + bool m_hasDoc; //!< used to mark a node as documented + bool m_isRoot; //!< indicates if this is a root node + const ClassDef * m_classDef; //!< class representing this node (can be 0) + bool m_visible; //!< is the node visible in the output + TruncState m_truncated; //!< does the node have non-visible children/parents + int m_distance; //!< shortest path to the root node + bool m_renumbered;//!< indicates if the node has been renumbered (to prevent endless loops) + int m_subgraphId; +}; + +/** Class representing a list of DotNode objects. */ +class DotNodeList : public QList<DotNode> +{ + public: + DotNodeList() : QList<DotNode>() {} + ~DotNodeList() {} + private: + int compareValues(const DotNode *n1,const DotNode *n2) const; +}; + +#endif diff --git a/src/dotrunner.cpp b/src/dotrunner.cpp new file mode 100644 index 0000000..07736bd --- /dev/null +++ b/src/dotrunner.cpp @@ -0,0 +1,300 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#include "dotrunner.h" + +#include "util.h" +#include "portable.h" +#include "dot.h" +#include "message.h" +#include "ftextstream.h" +#include "config.h" + +// the graphicx LaTeX has a limitation of maximum size of 16384 +// To be on the save side we take it a little bit smaller i.e. 150 inch * 72 dpi +// It is anyway hard to view these size of images +#define MAX_LATEX_GRAPH_INCH 150 +#define MAX_LATEX_GRAPH_SIZE (MAX_LATEX_GRAPH_INCH * 72) + + +// since dot silently reproduces the input file when it does not +// support the PNG format, we need to check the result. +static void checkPngResult(const char *imgName) +{ + FILE *f = portable_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("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 + ); + } + } + else + { + err("Could not read image `%s' generated by dot!\n",imgName); + } + fclose(f); + } + else + { + err("Could not open image `%s' generated by dot!\n",imgName); + } +} + +static bool resetPDFSize(const int width,const int height, const char *base) +{ + QString tmpName = QString::fromUtf8(QCString(base)+".tmp"); + QString patchFile = QString::fromUtf8(QCString(base)+".dot"); + if (!QDir::current().rename(patchFile,tmpName)) + { + err("Failed to rename file %s to %s!\n",patchFile.data(),tmpName.data()); + return FALSE; + } + QFile fi(tmpName); + QFile fo(patchFile); + if (!fi.open(IO_ReadOnly)) + { + err("problem opening file %s for patching!\n",tmpName.data()); + QDir::current().rename(tmpName,patchFile); + return FALSE; + } + if (!fo.open(IO_WriteOnly)) + { + err("problem opening file %s for patching!\n",patchFile.data()); + QDir::current().rename(tmpName,patchFile); + fi.close(); + return FALSE; + } + FTextStream t(&fo); + const int maxLineLen=100*1024; + while (!fi.atEnd()) // foreach line + { + QCString line(maxLineLen); + int numBytes = fi.readLine(line.rawData(),maxLineLen); + if (numBytes<=0) + { + break; + } + line.resize(numBytes+1); + if (line.find("LATEX_PDF_SIZE") != -1) + { + double scale = (width > height ? width : height)/double(MAX_LATEX_GRAPH_INCH); + t << " size=\""<<width/scale << "," <<height/scale <<"\";\n"; + } + else + t << line; + } + fi.close(); + fo.close(); + // remove temporary file + QDir::current().remove(tmpName); + return TRUE; +} + +bool DotRunner::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; +} + +bool DotRunner::DOT_CLEANUP; +bool DotRunner::DOT_MULTI_TARGETS; +DotConstString DotRunner::DOT_EXE; + +DotRunner::DotRunner(const QCString& absDotName, const QCString& md5Hash) + : m_file(absDotName), m_md5Hash(md5Hash), m_cleanUp(DOT_CLEANUP) +{ + m_jobs.setAutoDelete(TRUE); +} + +void DotRunner::addJob(const char *format,const char *output) +{ + QListIterator<DotJob> li(m_jobs); + DotJob *s; + for (li.toFirst(); (s = li.current()); ++li) + { + if (qstrcmp(s->format.data(), format) != 0) continue; + if (qstrcmp(s->output.data(), output) != 0) continue; + // we have this job already + return; + } + QCString args = QCString("-T")+format+" -o \""+output+"\""; + m_jobs.append(new DotJob(format, output, args)); +} + +QCString getBaseNameOfOutput(QCString const& output) +{ + int index = output.findRev('.'); + if (index < 0) return output; + return output.left(index); +} + +bool DotRunner::run() +{ + int exitCode=0; + + QCString dotArgs; + QListIterator<DotJob> li(m_jobs); + DotJob *s; + + // create output + if (DOT_MULTI_TARGETS) + { + dotArgs=QCString("\"")+m_file.data()+"\""; + for (li.toFirst();(s=li.current());++li) + { + dotArgs+=' '; + dotArgs+=s->args.data(); + } + if ((exitCode=portable_system(DOT_EXE.data(),dotArgs,FALSE))!=0) goto error; + } + else + { + for (li.toFirst();(s=li.current());++li) + { + dotArgs=QCString("\"")+m_file.data()+"\" "+s->args.data(); + if ((exitCode=portable_system(DOT_EXE.data(),dotArgs,FALSE))!=0) goto error; + } + } + + // check output + // As there should be only one pdf file be generated, we don't need code for regenerating multiple pdf files in one call + for (li.toFirst();(s=li.current());++li) + { + if (qstrncmp(s->format.data(), "pdf", 3) == 0) + { + int width=0,height=0; + if (!readBoundingBox(s->output.data(),&width,&height,FALSE)) goto error; + if ((width > MAX_LATEX_GRAPH_SIZE) || (height > MAX_LATEX_GRAPH_SIZE)) + { + if (!resetPDFSize(width,height,getBaseNameOfOutput(s->output.data()))) goto error; + dotArgs=QCString("\"")+m_file.data()+"\" "+s->args.data(); + if ((exitCode=portable_system(DOT_EXE.data(),dotArgs,FALSE))!=0) goto error; + } + } + + if (qstrncmp(s->format.data(), "png", 3) == 0) + { + checkPngResult(s->output.data()); + } + } + + // remove .dot files + if (m_cleanUp) + { + //printf("removing dot file %s\n",m_file.data()); + QFile::remove(m_file.data()); + } + + // create checksum file + if (!m_md5Hash.isEmpty()) { + QCString md5Name = getBaseNameOfOutput(m_file.data()) + ".md5"; + QFile f(md5Name); + if (f.open(IO_WriteOnly)) + { + f.writeBlock(m_md5Hash.data(),32); + f.close(); + } + } + return TRUE; +error: + err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n", + exitCode,DOT_EXE.data(),dotArgs.data()); + return FALSE; +} + + +//-------------------------------------------------------------------- + +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(DotRunnerQueue *queue) + : m_queue(queue) +{ +} + +void DotWorkerThread::run() +{ + DotRunner *runner; + while ((runner=m_queue->dequeue())) + { + runner->run(); + } +} diff --git a/src/dotrunner.h b/src/dotrunner.h new file mode 100644 index 0000000..4128fe8 --- /dev/null +++ b/src/dotrunner.h @@ -0,0 +1,138 @@ +/****************************************************************************** +* +* Copyright (C) 1997-2019 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. +* +*/ + +#ifndef DOTRUNNER_H +#define DOTRUNNER_H + +#include "qcstring.h" +#include "qlist.h" +#include "qwaitcondition.h" +#include "qthread.h" +#include "qqueue.h" +#include "qmutex.h" + +/** Minimal constant string class that is thread safe, once initialized. */ +class DotConstString +{ + public: + DotConstString() { m_str=0;} + ~DotConstString() { delete[] m_str;} + DotConstString(char const* s) : m_str(0) { set(s); } + DotConstString(const QCString &s) : m_str(0) { set(s); } + DotConstString(const DotConstString &s) : m_str(0) { set(s.data()); } + const char *data() const { return m_str; } + bool isEmpty() const { return m_str==0 || m_str[0]=='\0'; } + void init(const char *s) { set(s); } + + private: + void set(char const* s) + { + delete[] m_str; + m_str=0; + if (s) + { + m_str=new char[strlen(s) + 1]; + qstrcpy(m_str,s); + } + } + + void set(const QCString &s) + { + delete[] m_str; + m_str=0; + if (!s.isEmpty()) + { + m_str=new char[s.length()+1]; + qstrcpy(m_str,s.data()); + } + } + + DotConstString &operator=(const DotConstString &); + + char *m_str; +}; + +/** Helper class to run dot from doxygen from multiple threads. */ +class DotRunner +{ + public: + struct DotJob + { + DotJob(const DotConstString & format, + const DotConstString & output, + const DotConstString & args) + : format(format), output(output), args(args) {} + DotConstString format; + DotConstString output; + DotConstString args; + }; + + /** Creates a runner for a dot \a file. */ + DotRunner(const QCString& absDotName, const QCString& md5Hash); + + /** Adds an additional job to the run. + * Performing multiple jobs one file can be faster. + */ + void addJob(const char *format,const char *output); + + /** Prevent cleanup of the dot file (for user provided dot files) */ + void preventCleanUp() { m_cleanUp = FALSE; } + + /** Runs dot for all jobs added. */ + bool run(); + + // DotConstString const& getFileName() { return m_file; } + DotConstString const& getMd5Hash() { return m_md5Hash; } + + static bool readBoundingBox(const char* fileName, int* width, int* height, bool isEps); + + private: + DotConstString m_file; + DotConstString m_md5Hash; + bool m_cleanUp; + QList<DotJob> m_jobs; + + static bool DOT_CLEANUP; + static bool DOT_MULTI_TARGETS; + static DotConstString DOT_EXE; + friend void initDot(); + +}; + +/** Queue of dot jobs to run. */ +// all methods are thread save +class DotRunnerQueue +{ + public: + void enqueue(DotRunner *runner); + DotRunner *dequeue(); + uint count() const; + private: + QWaitCondition m_bufferNotEmpty; + QQueue<DotRunner> m_queue; + mutable QMutex m_mutex; +}; + +/** Worker thread to execute a dot run */ +class DotWorkerThread : public QThread +{ + public: + DotWorkerThread(DotRunnerQueue *queue); + void run(); + private: + DotRunnerQueue *m_queue; +}; + +#endif diff --git a/src/doxygen.cpp b/src/doxygen.cpp index 30c2001..84a4900 100644 --- a/src/doxygen.cpp +++ b/src/doxygen.cpp @@ -11652,6 +11652,7 @@ void generateOutput() } initSearchIndexer(); + initDot(); bool generateHtml = Config_getBool(GENERATE_HTML); bool generateLatex = Config_getBool(GENERATE_LATEX); diff --git a/src/filedef.cpp b/src/filedef.cpp index f721c9f..7a3323d 100644 --- a/src/filedef.cpp +++ b/src/filedef.cpp @@ -26,6 +26,7 @@ #include "language.h" #include "outputlist.h" #include "dot.h" +#include "dotincldepgraph.h" #include "message.h" #include "docparser.h" #include "searchindex.h" diff --git a/src/groupdef.cpp b/src/groupdef.cpp index 01c4cc4..9525053 100644 --- a/src/groupdef.cpp +++ b/src/groupdef.cpp @@ -33,6 +33,7 @@ #include "docparser.h" #include "searchindex.h" #include "dot.h" +#include "dotgroupcollaboration.h" #include "vhdldocgen.h" #include "layout.h" #include "arguments.h" diff --git a/src/htmlgen.cpp b/src/htmlgen.cpp index 0067fa1..d25dafc 100644 --- a/src/htmlgen.cpp +++ b/src/htmlgen.cpp @@ -28,6 +28,12 @@ #include "diagram.h" #include "version.h" #include "dot.h" +#include "dotcallgraph.h" +#include "dotclassgraph.h" +#include "dotdirdeps.h" +#include "dotgfxhierarchytable.h" +#include "dotgroupcollaboration.h" +#include "dotincldepgraph.h" #include "language.h" #include "htmlhelp.h" #include "docparser.h" @@ -1775,7 +1781,7 @@ void HtmlGenerator::startDotGraph() startSectionHeader(t,relPath,m_sectionCount); } -void HtmlGenerator::endDotGraph(const DotClassGraph &g) +void HtmlGenerator::endDotGraph(DotClassGraph &g) { bool generateLegend = Config_getBool(GENERATE_LEGEND); bool umlLook = Config_getBool(UML_LOOK); @@ -1803,7 +1809,7 @@ void HtmlGenerator::startInclDepGraph() startSectionHeader(t,relPath,m_sectionCount); } -void HtmlGenerator::endInclDepGraph(const DotInclDepGraph &g) +void HtmlGenerator::endInclDepGraph(DotInclDepGraph &g) { endSectionHeader(t); startSectionSummary(t,m_sectionCount); @@ -1821,7 +1827,7 @@ void HtmlGenerator::startGroupCollaboration() startSectionHeader(t,relPath,m_sectionCount); } -void HtmlGenerator::endGroupCollaboration(const DotGroupCollaboration &g) +void HtmlGenerator::endGroupCollaboration(DotGroupCollaboration &g) { endSectionHeader(t); startSectionSummary(t,m_sectionCount); @@ -1839,7 +1845,7 @@ void HtmlGenerator::startCallGraph() startSectionHeader(t,relPath,m_sectionCount); } -void HtmlGenerator::endCallGraph(const DotCallGraph &g) +void HtmlGenerator::endCallGraph(DotCallGraph &g) { endSectionHeader(t); startSectionSummary(t,m_sectionCount); @@ -1857,7 +1863,7 @@ void HtmlGenerator::startDirDepGraph() startSectionHeader(t,relPath,m_sectionCount); } -void HtmlGenerator::endDirDepGraph(const DotDirDeps &g) +void HtmlGenerator::endDirDepGraph(DotDirDeps &g) { endSectionHeader(t); startSectionSummary(t,m_sectionCount); @@ -1870,7 +1876,7 @@ void HtmlGenerator::endDirDepGraph(const DotDirDeps &g) m_sectionCount++; } -void HtmlGenerator::writeGraphicalHierarchy(const DotGfxHierarchyTable &g) +void HtmlGenerator::writeGraphicalHierarchy(DotGfxHierarchyTable &g) { g.writeGraph(t,dir,fileName); } diff --git a/src/htmlgen.h b/src/htmlgen.h index 4bf975d..ebecc81 100644 --- a/src/htmlgen.h +++ b/src/htmlgen.h @@ -283,16 +283,16 @@ class HtmlGenerator : public OutputGenerator void endDescTableData(); void startDotGraph(); - void endDotGraph(const DotClassGraph &g); + void endDotGraph(DotClassGraph &g); void startInclDepGraph(); - void endInclDepGraph(const DotInclDepGraph &g); + void endInclDepGraph(DotInclDepGraph &g); void startGroupCollaboration(); - void endGroupCollaboration(const DotGroupCollaboration &g); + void endGroupCollaboration(DotGroupCollaboration &g); void startCallGraph(); - void endCallGraph(const DotCallGraph &g); + void endCallGraph(DotCallGraph &g); void startDirDepGraph(); - void endDirDepGraph(const DotDirDeps &g); - void writeGraphicalHierarchy(const DotGfxHierarchyTable &g); + void endDirDepGraph(DotDirDeps &g); + void writeGraphicalHierarchy(DotGfxHierarchyTable &g); void startTextBlock(bool) { t << "<div class=\"textblock\">"; } diff --git a/src/index.cpp b/src/index.cpp index ff22a60..5d6c0a5 100644 --- a/src/index.cpp +++ b/src/index.cpp @@ -40,6 +40,7 @@ #include "htmlhelp.h" #include "ftvhelp.h" #include "dot.h" +#include "dotgfxhierarchytable.h" #include "pagedef.h" #include "dirdef.h" #include "vhdldocgen.h" diff --git a/src/latexgen.cpp b/src/latexgen.cpp index ad983a2..6864e74 100644 --- a/src/latexgen.cpp +++ b/src/latexgen.cpp @@ -27,6 +27,11 @@ #include "language.h" #include "version.h" #include "dot.h" +#include "dotcallgraph.h" +#include "dotclassgraph.h" +#include "dotdirdeps.h" +#include "dotgroupcollaboration.h" +#include "dotincldepgraph.h" #include "pagedef.h" #include "docparser.h" #include "latexdocvisitor.h" @@ -2034,7 +2039,7 @@ void LatexGenerator::startDotGraph() newParagraph(); } -void LatexGenerator::endDotGraph(const DotClassGraph &g) +void LatexGenerator::endDotGraph(DotClassGraph &g) { g.writeGraph(t,GOF_EPS,EOF_LaTeX,Config_getString(LATEX_OUTPUT),fileName,relPath); } @@ -2043,7 +2048,7 @@ void LatexGenerator::startInclDepGraph() { } -void LatexGenerator::endInclDepGraph(const DotInclDepGraph &g) +void LatexGenerator::endInclDepGraph(DotInclDepGraph &g) { g.writeGraph(t,GOF_EPS,EOF_LaTeX,Config_getString(LATEX_OUTPUT),fileName,relPath); } @@ -2052,7 +2057,7 @@ void LatexGenerator::startGroupCollaboration() { } -void LatexGenerator::endGroupCollaboration(const DotGroupCollaboration &g) +void LatexGenerator::endGroupCollaboration(DotGroupCollaboration &g) { g.writeGraph(t,GOF_EPS,EOF_LaTeX,Config_getString(LATEX_OUTPUT),fileName,relPath); } @@ -2061,7 +2066,7 @@ void LatexGenerator::startCallGraph() { } -void LatexGenerator::endCallGraph(const DotCallGraph &g) +void LatexGenerator::endCallGraph(DotCallGraph &g) { g.writeGraph(t,GOF_EPS,EOF_LaTeX,Config_getString(LATEX_OUTPUT),fileName,relPath); } @@ -2070,7 +2075,7 @@ void LatexGenerator::startDirDepGraph() { } -void LatexGenerator::endDirDepGraph(const DotDirDeps &g) +void LatexGenerator::endDirDepGraph(DotDirDeps &g) { g.writeGraph(t,GOF_EPS,EOF_LaTeX,Config_getString(LATEX_OUTPUT),fileName,relPath); } diff --git a/src/latexgen.h b/src/latexgen.h index b06a382..6430dbc 100644 --- a/src/latexgen.h +++ b/src/latexgen.h @@ -273,16 +273,16 @@ class LatexGenerator : public OutputGenerator void lastIndexPage(); void startDotGraph(); - void endDotGraph(const DotClassGraph &); + void endDotGraph(DotClassGraph &); void startInclDepGraph(); - void endInclDepGraph(const DotInclDepGraph &); + void endInclDepGraph(DotInclDepGraph &); void startCallGraph(); void startGroupCollaboration(); - void endGroupCollaboration(const DotGroupCollaboration &g); - void endCallGraph(const DotCallGraph &); + void endGroupCollaboration(DotGroupCollaboration &g); + void endCallGraph(DotCallGraph &); void startDirDepGraph(); - void endDirDepGraph(const DotDirDeps &g); - void writeGraphicalHierarchy(const DotGfxHierarchyTable &) {} + void endDirDepGraph(DotDirDeps &g); + void writeGraphicalHierarchy(DotGfxHierarchyTable &) {} void startTextBlock(bool) {} void endTextBlock(bool) {} diff --git a/src/mangen.h b/src/mangen.h index 959a34c..d912923 100644 --- a/src/mangen.h +++ b/src/mangen.h @@ -207,16 +207,16 @@ class ManGenerator : public OutputGenerator void endDescTableData() {} void startDotGraph() {} - void endDotGraph(const DotClassGraph &) {} + void endDotGraph(DotClassGraph &) {} void startInclDepGraph() {} - void endInclDepGraph(const DotInclDepGraph &) {} + void endInclDepGraph(DotInclDepGraph &) {} void startGroupCollaboration() {} - void endGroupCollaboration(const DotGroupCollaboration &) {} + void endGroupCollaboration(DotGroupCollaboration &) {} void startCallGraph() {} - void endCallGraph(const DotCallGraph &) {} + void endCallGraph(DotCallGraph &) {} void startDirDepGraph() {} - void endDirDepGraph(const DotDirDeps &) {} - void writeGraphicalHierarchy(const DotGfxHierarchyTable &) {} + void endDirDepGraph(DotDirDeps &) {} + void writeGraphicalHierarchy(DotGfxHierarchyTable &) {} void startTextBlock(bool) {} void endTextBlock(bool) {} diff --git a/src/memberdef.cpp b/src/memberdef.cpp index 912d7b2..15da899 100644 --- a/src/memberdef.cpp +++ b/src/memberdef.cpp @@ -33,6 +33,7 @@ #include "defargs.h" #include "docparser.h" #include "dot.h" +#include "dotcallgraph.h" #include "searchindex.h" #include "parserintf.h" #include "objcache.h" diff --git a/src/outputgen.h b/src/outputgen.h index 8d9db65..e302f42 100644 --- a/src/outputgen.h +++ b/src/outputgen.h @@ -428,16 +428,16 @@ class OutputGenerator : public BaseOutputDocInterface virtual void startClassDiagram() = 0; virtual void endClassDiagram(const ClassDiagram &,const char *,const char *) = 0; virtual void startDotGraph() = 0; - virtual void endDotGraph(const DotClassGraph &g) = 0; + virtual void endDotGraph(DotClassGraph &g) = 0; virtual void startInclDepGraph() = 0; - virtual void endInclDepGraph(const DotInclDepGraph &g) = 0; + virtual void endInclDepGraph(DotInclDepGraph &g) = 0; virtual void startGroupCollaboration() = 0; - virtual void endGroupCollaboration(const DotGroupCollaboration &g) = 0; + virtual void endGroupCollaboration(DotGroupCollaboration &g) = 0; virtual void startCallGraph() = 0; - virtual void endCallGraph(const DotCallGraph &g) = 0; + virtual void endCallGraph(DotCallGraph &g) = 0; virtual void startDirDepGraph() = 0; - virtual void endDirDepGraph(const DotDirDeps &g) = 0; - virtual void writeGraphicalHierarchy(const DotGfxHierarchyTable &g) = 0; + virtual void endDirDepGraph(DotDirDeps &g) = 0; + virtual void writeGraphicalHierarchy(DotGfxHierarchyTable &g) = 0; virtual void startQuickIndices() = 0; virtual void endQuickIndices() = 0; virtual void writeSplitBar(const char *) = 0; diff --git a/src/outputlist.cpp b/src/outputlist.cpp index 1d6db55..0306f94 100644 --- a/src/outputlist.cpp +++ b/src/outputlist.cpp @@ -316,12 +316,12 @@ void OutputList::forall(void (OutputGenerator::*func)(a1,a2,a3,a4,a5,a6,a7,a8),a FORALL1(const char *a1,a1) FORALL1(char a1,a1) FORALL1(int a1,a1) -FORALL1(const DotClassGraph &a1,a1) -FORALL1(const DotInclDepGraph &a1,a1) -FORALL1(const DotCallGraph &a1,a1) -FORALL1(const DotDirDeps &a1,a1) -FORALL1(const DotGfxHierarchyTable &a1,a1) -FORALL1(const DotGroupCollaboration &a1,a1) +FORALL1(DotClassGraph &a1,a1) +FORALL1(DotInclDepGraph &a1,a1) +FORALL1(DotCallGraph &a1,a1) +FORALL1(DotDirDeps &a1,a1) +FORALL1(DotGfxHierarchyTable &a1,a1) +FORALL1(DotGroupCollaboration &a1,a1) FORALL1(SectionTypes a1,a1) #if defined(HAS_BOOL_TYPE) || defined(Q_HAS_BOOL_TYPE) FORALL1(bool a1,a1) diff --git a/src/outputlist.h b/src/outputlist.h index 1371b3a..35d68a8 100644 --- a/src/outputlist.h +++ b/src/outputlist.h @@ -391,25 +391,25 @@ class OutputList : public OutputDocInterface { forall(&OutputGenerator::endDescTableData); } void startDotGraph() { forall(&OutputGenerator::startDotGraph); } - void endDotGraph(const DotClassGraph &g) + void endDotGraph(DotClassGraph &g) { forall(&OutputGenerator::endDotGraph,g); } void startInclDepGraph() { forall(&OutputGenerator::startInclDepGraph); } - void endInclDepGraph(const DotInclDepGraph &g) + void endInclDepGraph(DotInclDepGraph &g) { forall(&OutputGenerator::endInclDepGraph,g); } void startCallGraph() { forall(&OutputGenerator::startCallGraph); } - void endCallGraph(const DotCallGraph &g) + void endCallGraph(DotCallGraph &g) { forall(&OutputGenerator::endCallGraph,g); } void startDirDepGraph() { forall(&OutputGenerator::startDirDepGraph); } - void endDirDepGraph(const DotDirDeps &g) + void endDirDepGraph(DotDirDeps &g) { forall(&OutputGenerator::endDirDepGraph,g); } void startGroupCollaboration() { forall(&OutputGenerator::startGroupCollaboration); } - void endGroupCollaboration(const DotGroupCollaboration &g) + void endGroupCollaboration(DotGroupCollaboration &g) { forall(&OutputGenerator::endGroupCollaboration,g); } - void writeGraphicalHierarchy(const DotGfxHierarchyTable &g) + void writeGraphicalHierarchy(DotGfxHierarchyTable &g) { forall(&OutputGenerator::writeGraphicalHierarchy,g); } void startTextBlock(bool dense=FALSE) { forall(&OutputGenerator::startTextBlock,dense); } @@ -520,12 +520,12 @@ class OutputList : public OutputDocInterface FORALLPROTO1(char); FORALLPROTO1(IndexSections); FORALLPROTO1(int); - FORALLPROTO1(const DotClassGraph &); - FORALLPROTO1(const DotInclDepGraph &); - FORALLPROTO1(const DotCallGraph &); - FORALLPROTO1(const DotGroupCollaboration &); - FORALLPROTO1(const DotDirDeps &); - FORALLPROTO1(const DotGfxHierarchyTable &); + FORALLPROTO1(DotClassGraph &); + FORALLPROTO1(DotInclDepGraph &); + FORALLPROTO1(DotCallGraph &); + FORALLPROTO1(DotGroupCollaboration &); + FORALLPROTO1(DotDirDeps &); + FORALLPROTO1(DotGfxHierarchyTable &); FORALLPROTO1(SectionTypes); #if defined(HAS_BOOL_TYPE) || defined(Q_HAS_BOOL_TYPE) FORALLPROTO1(bool); diff --git a/src/rtfgen.cpp b/src/rtfgen.cpp index bb2075b..8139784 100644 --- a/src/rtfgen.cpp +++ b/src/rtfgen.cpp @@ -31,6 +31,10 @@ #include "diagram.h" #include "language.h" #include "dot.h" +#include "dotcallgraph.h" +#include "dotclassgraph.h" +#include "dotdirdeps.h" +#include "dotincldepgraph.h" #include "version.h" #include "pagedef.h" #include "rtfstyle.h" @@ -2497,7 +2501,7 @@ void RTFGenerator::startDotGraph() DBG_RTF(t << "{\\comment (startDotGraph)}" << endl) } -void RTFGenerator::endDotGraph(const DotClassGraph &g) +void RTFGenerator::endDotGraph(DotClassGraph &g) { newParagraph(); @@ -2521,7 +2525,7 @@ void RTFGenerator::startInclDepGraph() DBG_RTF(t << "{\\comment (startInclDepGraph)}" << endl) } -void RTFGenerator::endInclDepGraph(const DotInclDepGraph &g) +void RTFGenerator::endInclDepGraph(DotInclDepGraph &g) { newParagraph(); @@ -2543,7 +2547,7 @@ void RTFGenerator::startGroupCollaboration() { } -void RTFGenerator::endGroupCollaboration(const DotGroupCollaboration &) +void RTFGenerator::endGroupCollaboration(DotGroupCollaboration &) { } @@ -2552,7 +2556,7 @@ void RTFGenerator::startCallGraph() DBG_RTF(t << "{\\comment (startCallGraph)}" << endl) } -void RTFGenerator::endCallGraph(const DotCallGraph &g) +void RTFGenerator::endCallGraph(DotCallGraph &g) { newParagraph(); @@ -2575,7 +2579,7 @@ void RTFGenerator::startDirDepGraph() DBG_RTF(t << "{\\comment (startDirDepGraph)}" << endl) } -void RTFGenerator::endDirDepGraph(const DotDirDeps &g) +void RTFGenerator::endDirDepGraph(DotDirDeps &g) { newParagraph(); diff --git a/src/rtfgen.h b/src/rtfgen.h index 3f05821..fe3e753 100644 --- a/src/rtfgen.h +++ b/src/rtfgen.h @@ -202,16 +202,16 @@ class RTFGenerator : public OutputGenerator void endDescTableData(); void startDotGraph(); - void endDotGraph(const DotClassGraph &); + void endDotGraph(DotClassGraph &); void startInclDepGraph(); - void endInclDepGraph(const DotInclDepGraph &); + void endInclDepGraph(DotInclDepGraph &); void startGroupCollaboration(); - void endGroupCollaboration(const DotGroupCollaboration &g); + void endGroupCollaboration(DotGroupCollaboration &g); void startCallGraph(); - void endCallGraph(const DotCallGraph &); + void endCallGraph(DotCallGraph &); void startDirDepGraph(); - void endDirDepGraph(const DotDirDeps &g); - void writeGraphicalHierarchy(const DotGfxHierarchyTable &) {} + void endDirDepGraph(DotDirDeps &g); + void writeGraphicalHierarchy(DotGfxHierarchyTable &) {} void startMemberGroupHeader(bool); void endMemberGroupHeader(); diff --git a/src/xmlgen.cpp b/src/xmlgen.cpp index fe324be..b992b81 100644 --- a/src/xmlgen.cpp +++ b/src/xmlgen.cpp @@ -29,6 +29,8 @@ #include "defargs.h" #include "outputgen.h" #include "dot.h" +#include "dotclassgraph.h" +#include "dotincldepgraph.h" #include "pagedef.h" #include "filename.h" #include "version.h" @@ -1407,14 +1409,14 @@ static void generateXMLForClass(const ClassDef *cd,FTextStream &ti) t << " <detaileddescription>" << endl; writeXMLDocBlock(t,cd->docFile(),cd->docLine(),cd,0,cd->documentation()); t << " </detaileddescription>" << endl; - DotClassGraph inheritanceGraph(cd,DotNode::Inheritance); + DotClassGraph inheritanceGraph(cd,Inheritance); if (!inheritanceGraph.isTrivial()) { t << " <inheritancegraph>" << endl; inheritanceGraph.writeXML(t); t << " </inheritancegraph>" << endl; } - DotClassGraph collaborationGraph(cd,DotNode::Collaboration); + DotClassGraph collaborationGraph(cd,Collaboration); if (!collaborationGraph.isTrivial()) { t << " <collaborationgraph>" << endl; |