summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt10
-rw-r--r--src/classdef.cpp6
-rw-r--r--src/context.cpp10
-rw-r--r--src/defgen.cpp5
-rw-r--r--src/dirdef.cpp1
-rw-r--r--src/docbookgen.cpp15
-rw-r--r--src/docbookgen.h12
-rw-r--r--src/dot.cpp4537
-rw-r--r--src/dot.h488
-rw-r--r--src/dotcallgraph.cpp217
-rw-r--r--src/dotcallgraph.h52
-rw-r--r--src/dotclassgraph.cpp548
-rw-r--r--src/dotclassgraph.h62
-rw-r--r--src/dotdirdeps.cpp220
-rw-r--r--src/dotdirdeps.h51
-rw-r--r--src/dotfilepatcher.cpp539
-rw-r--r--src/dotfilepatcher.h53
-rw-r--r--src/dotgfxhierarchytable.cpp302
-rw-r--r--src/dotgfxhierarchytable.h55
-rw-r--r--src/dotgraph.cpp400
-rw-r--r--src/dotgraph.h113
-rw-r--r--src/dotgroupcollaboration.cpp381
-rw-r--r--src/dotgroupcollaboration.h85
-rw-r--r--src/dotincldepgraph.cpp238
-rw-r--r--src/dotincldepgraph.h56
-rw-r--r--src/dotnode.cpp950
-rw-r--r--src/dotnode.h138
-rw-r--r--src/dotrunner.cpp300
-rw-r--r--src/dotrunner.h138
-rw-r--r--src/doxygen.cpp1
-rw-r--r--src/filedef.cpp1
-rw-r--r--src/groupdef.cpp1
-rw-r--r--src/htmlgen.cpp18
-rw-r--r--src/htmlgen.h12
-rw-r--r--src/index.cpp1
-rw-r--r--src/latexgen.cpp15
-rw-r--r--src/latexgen.h12
-rw-r--r--src/mangen.h12
-rw-r--r--src/memberdef.cpp1
-rw-r--r--src/outputgen.h12
-rw-r--r--src/outputlist.cpp12
-rw-r--r--src/outputlist.h24
-rw-r--r--src/rtfgen.cpp14
-rw-r--r--src/rtfgen.h12
-rw-r--r--src/xmlgen.cpp6
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 ? &umlEdgeProps : &normalEdgeProps;
- QCString aStyle = eProps->arrowStyleMap[ei->m_color];
- bool umlUseArrow = aStyle=="odiamond";
-
- if (pointBack && !umlUseArrow) t << "dir=\"back\",";
- t << "color=\"" << eProps->edgeColorMap[ei->m_color]
- << "\",fontsize=\"" << FONTSIZE << "\",";
- t << "style=\"" << eProps->edgeStyleMap[ei->m_style] << "\"";
- if (!ei->m_label.isEmpty())
- {
- t << ",label=\" " << convertLabel(ei->m_label) << "\" ";
- }
- if (umlLook &&
- eProps->arrowStyleMap[ei->m_color] &&
- (gt==Inheritance || gt==Collaboration)
- )
- {
- bool rev = pointBack;
- if (umlUseArrow) rev=!rev; // UML use relates has arrow on the start side
- if (rev)
- t << ",arrowtail=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
- else
- t << ",arrowhead=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
- }
-
- if (format==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";
-}
diff --git a/src/dot.h b/src/dot.h
index d63dd05..f52d196 100644
--- a/src/dot.h
+++ b/src/dot.h
@@ -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) ? &umlEdgeProps : &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;