/*****************************************************************************
*
*
*
*
* Copyright (C) 1997-2011 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
* Documents produced by Doxygen are derivative works derived from the
* input used in their production; they are not affected by this license.
*
*/
#ifdef _WIN32
#include
#define BITMAP W_BITMAP
#endif
#include
#include "dot.h"
#include "doxygen.h"
#include "message.h"
#include "util.h"
#include "config.h"
#include "language.h"
#include "defargs.h"
#include "docparser.h"
#include "debug.h"
#include "pagedef.h"
#include "portable.h"
#include "dirdef.h"
#include "vhdldocgen.h"
#include
#include
#include "ftextstream.h"
#include
#include
#include
#include
#include
#define MAP_CMD "cmapx"
//#define FONTNAME "Helvetica"
#define FONTNAME getDotFontName()
#define FONTSIZE getDotFontSize()
//--------------------------------------------------------------------
static const int maxCmdLine = 40960;
/*! mapping from protection levels to color names */
static const char *edgeColorMap[] =
{
"midnightblue", // Public
"darkgreen", // Protected
"firebrick4", // Private
"darkorchid3", // "use" relation
"grey75", // Undocumented
"orange" // template relation
};
static const char *arrowStyle[] =
{
"empty", // Public
"empty", // Protected
"empty", // Private
"open", // "use" relation
0, // Undocumented
0 // template relation
};
static const char *edgeStyleMap[] =
{
"solid", // inheritance
"dashed" // usage
};
static QCString getDotFontName()
{
static QCString dotFontName = Config_getString("DOT_FONTNAME");
if (dotFontName.isEmpty())
{
//dotFontName="FreeSans.ttf";
dotFontName="Helvetica";
}
return dotFontName;
}
static int getDotFontSize()
{
static int dotFontSize = Config_getInt("DOT_FONTSIZE");
if (dotFontSize<4) dotFontSize=4;
return dotFontSize;
}
static void writeGraphHeader(FTextStream &t)
{
t << "digraph G" << endl;
t << "{" << endl;
if (Config_getBool("DOT_TRANSPARENT"))
{
t << " bgcolor=\"transparent\";" << endl;
}
t << " edge [fontname=\"" << FONTNAME << "\","
"fontsize=\"" << FONTSIZE << "\","
"labelfontname=\"" << FONTNAME << "\","
"labelfontsize=\"" << FONTSIZE << "\"];\n";
t << " node [fontname=\"" << FONTNAME << "\","
"fontsize=\"" << FONTSIZE << "\",shape=record];\n";
}
static void writeGraphFooter(FTextStream &t)
{
t << "}" << endl;
}
static QCString replaceRef(const QCString &buf,const QCString relPath,
bool urlOnly,const QCString &context,const QCString &target=QCString())
{
// search for href="...", store ... part in link
QCString href = "href";
bool isXLink=FALSE;
int len = 6;
int indexS = buf.find("href=\""), indexE;
if (indexS>5 && buf.find("xlink:href=\"")!=-1) // XLink href (for SVG)
{
indexS-=6;
len+=6;
href.prepend("xlink:");
isXLink=TRUE;
}
if (indexS>=0 && (indexE=buf.find('"',indexS+len))!=-1)
{
QCString link = buf.mid(indexS+len,indexE-indexS-len);
QCString result;
if (urlOnly) // for user defined dot graphs
{
if (link.left(5)=="\\ref " || link.left(5)=="@ref ") // \ref url
{
result=href+"=\"";
// fake ref node to resolve the url
DocRef *df = new DocRef( (DocNode*) 0, link.mid(5), context );
result+=externalRef(relPath,df->ref(),TRUE);
if (!df->file().isEmpty())
result += df->file().data() + Doxygen::htmlFileExtension;
if (!df->anchor().isEmpty())
result += "#" + df->anchor();
delete df;
result += "\"";
}
else
{
result = href+"=\"" + link + "\"";
}
}
else // ref$url (external ref via tag file), or $url (local ref)
{
int marker = link.find('$');
if (marker!=-1)
{
QCString ref = link.left(marker);
QCString url = link.mid(marker+1);
if (!ref.isEmpty())
{
result = externalLinkTarget() + externalRef(relPath,ref,FALSE);
}
result+= href+"=\"";
result+=externalRef(relPath,ref,TRUE);
result+= url + "\"";
}
else // should not happen, but handle properly anyway
{
result = href+"=\"" + link + "\"";
}
}
if (!target.isEmpty())
{
result+=" target=\""+target+"\"";
}
QCString leftPart = buf.left(indexS);
QCString rightPart = buf.mid(indexE+1);
return leftPart + result + rightPart;
}
else
{
return buf;
}
}
/*! converts the rectangles in a client site image map into a stream
* \param t the stream to which the result is written.
* \param mapName the name of the map file.
* \param relPath the relative path to the root of the output directory
* (used in case CREATE_SUBDIRS is enabled).
* \param urlOnly if FALSE the url field in the map contains an external
* references followed by a $ and then the URL.
* \param context the context (file, class, or namespace) in which the
* map file was found
* \returns TRUE if succesful.
*/
static bool convertMapFile(FTextStream &t,const char *mapName,
const QCString relPath, bool urlOnly=FALSE,
const QCString &context=QCString())
{
QFile f(mapName);
if (!f.open(IO_ReadOnly))
{
err("error: problems opening map file %s for inclusion in the docs!\n"
"If you installed Graphviz/dot after a previous failing run, \n"
"try deleting the output directory and rerun doxygen.\n",mapName);
return FALSE;
}
const int maxLineLen=10240;
while (!f.atEnd()) // foreach line
{
QCString buf(maxLineLen);
int numBytes = f.readLine(buf.data(),maxLineLen);
buf[numBytes-1]='\0';
if (buf.left(5)==" s_newNumber;
static int s_max_newNumber=0;
inline int reNumberNode(int number, bool doReNumbering)
{
if (!doReNumbering)
{
return number;
}
else
{
int s = s_newNumber.size();
if (number>=s)
{
int ns=0;
ns = s * 3 / 2 + 5; // new size
if (number>=ns) // number still doesn't fit
{
ns = number * 3 / 2 + 5;
}
s_newNumber.resize(ns);
for (int i=s;i0)
{
buf[numBytes]='\0';
if (strncmp(buf,bb.data(),bb.length()-1)==0) // found PageBoundingBox string
{
int x,y;
if (sscanf(buf+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;
}
}
//printf("readBoundingBox: bounding box not found\n");
return FALSE;
}
static bool writeVecGfxFigure(FTextStream &out,const QCString &baseName,
const QCString &figureName)
{
int width=420,height=600;
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 = 400; /* approx. page width in points, excl. margins */
int maxHeight = 600; /* approx. page height in points, excl. margins */
out << "\\nopagebreak\n"
"\\begin{figure}[H]\n"
"\\begin{center}\n"
"\\leavevmode\n";
if (width>maxWidth || height>maxHeight) // figure too big for page
{
// c*width/maxWidth > c*height/maxHeight, where c=maxWidth*maxHeight>0
if (width*maxHeight>height*maxWidth)
{
out << "\\includegraphics[width=" << maxWidth << "pt]";
}
else
{
out << "\\includegraphics[height=" << maxHeight << "pt]";
}
}
else
{
out << "\\includegraphics[width=" << width << "pt]";
}
out << "{" << baseName << "}\n"
"\\end{center}\n"
"\\end{figure}\n";
//printf("writeVecGfxFigure()=1\n");
return TRUE;
}
// extract size from a dot generated SVG file
static bool readSVGSize(const QCString &fileName,int *width,int *height)
{
bool found=FALSE;
QFile f(fileName);
if (!f.open(IO_ReadOnly))
{
return FALSE;
}
const int maxLineLen=4096;
char buf[maxLineLen];
while (!f.atEnd() && !found)
{
int numBytes = f.readLine(buf,maxLineLen-1); // read line
if (numBytes>0)
{
buf[numBytes]='\0';
if (sscanf(buf,"
";
}
// check if a reference to a SVG figure can be written and does so if possible.
// return FALSE if not possible (for instance because the SVG file is not yet generated).
static bool writeSVGFigureLink(FTextStream &out,const QCString &relPath,
const QCString &baseName,const QCString &absImgName)
{
int width=600,height=450;
if (!readSVGSize(absImgName,&width,&height))
{
return FALSE;
}
// out << "";
out << "";
return TRUE;
}
// since dot silently reproduces the input file when it does not
// support the PNG format, we need to check the result.
static void checkDotResult(const QCString &imgName)
{
if (Config_getEnum("DOT_IMAGE_FORMAT")=="png")
{
QFile f(imgName);
if (f.open(IO_ReadOnly))
{
char data[4];
if (f.readBlock(data,4)==4)
{
if (!(data[1]=='P' && data[2]=='N' && data[3]=='G'))
{
err("error: Image `%s' produced by dot is not a valid PNG!\n"
"You should either select a different format "
"(DOT_IMAGE_FORMAT in the config file) or install a more "
"recent version of graphviz (1.7+)\n",imgName.data()
);
}
}
else
{
err("error: Could not read image `%s' generated by dot!\n",imgName.data());
}
}
else
{
err("error: Could not open image `%s' generated by dot!\n",imgName.data());
}
}
}
static bool insertMapFile(FTextStream &out,const QCString &mapFile,
const QCString &relPath,const QCString &mapLabel)
{
QFileInfo fi(mapFile);
if (fi.exists() && fi.size()>0) // reuse existing map file
{
QGString tmpstr;
FTextStream tmpout(&tmpstr);
convertMapFile(tmpout,mapFile,relPath);
if (!tmpstr.isEmpty())
{
out << "" << endl;
}
return TRUE;
}
return FALSE; // no map file yet, need to generate it
}
static void removeDotGraph(const QCString &dotName)
{
static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
if (dotCleanUp)
{
QDir d;
d.remove(dotName);
}
}
/*! Checks if a file "baseName".md5 exists. If so the contents
* are compared with \a md5. If equal FALSE is returned. If the .md5
* file does not exist or its contents are not equal to \a md5,
* a new .md5 is generated with the \a md5 string as contents.
*/
static bool checkAndUpdateMd5Signature(const QCString &baseName,const QCString &md5)
{
QFile f(baseName+".md5");
if (f.open(IO_ReadOnly))
{
// read checksum
QCString md5stored(33);
int bytesRead=f.readBlock(md5stored.data(),32);
md5stored[32]='\0';
// compare checksum
if (bytesRead==32 && md5==md5stored)
{
// bail out if equal
return FALSE;
}
}
f.close();
// create checksum file
if (f.open(IO_WriteOnly))
{
f.writeBlock(md5.data(),32);
f.close();
}
return TRUE;
}
static bool checkDeliverables(const QCString &file1,
const QCString &file2=QCString())
{
bool file1Ok = TRUE;
bool file2Ok = TRUE;
if (!file1.isEmpty())
{
QFileInfo fi(file1);
file1Ok = (fi.exists() && fi.size()>0);
}
if (!file2.isEmpty())
{
QFileInfo fi(file2);
file2Ok = (fi.exists() && fi.size()>0);
}
return file1Ok && file2Ok;
}
//--------------------------------------------------------------------
class DotNodeList : public QList
{
public:
DotNodeList() : QList() {}
~DotNodeList() {}
int compareItems(GCI item1,GCI item2)
{
return stricmp(((DotNode *)item1)->m_label,((DotNode *)item2)->m_label);
}
};
//--------------------------------------------------------------------
DotRunner::DotRunner(const QCString &file,const QCString &path,
bool checkResult,const QCString &imageName)
: m_file(file), m_path(path),
m_checkResult(checkResult), m_imageName(imageName)
{
static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
m_cleanUp = dotCleanUp;
m_jobs.setAutoDelete(TRUE);
}
void DotRunner::addJob(const char *format,const char *output)
{
QCString args = QCString("-T")+format+" -o \""+output+"\"";
m_jobs.append(new QCString(args));
}
void DotRunner::addPostProcessing(const char *cmd,const char *args)
{
m_postCmd = cmd;
m_postArgs = args;
}
bool DotRunner::run()
{
int exitCode=0;
static QCString dotExe = Config_getString("DOT_PATH")+"dot";
static bool multiTargets = Config_getBool("DOT_MULTI_TARGETS");
QCString dotArgs;
QListIterator li(m_jobs);
QCString *s;
QCString file = m_file;
QCString path = m_path;
QCString imageName = m_imageName;
QCString postCmd = m_postCmd;
QCString postArgs = m_postArgs;
bool checkResult = m_checkResult;
bool cleanUp = m_cleanUp;
if (multiTargets)
{
dotArgs="\""+file+"\"";
for (li.toFirst();(s=li.current());++li)
{
dotArgs+=' ';
dotArgs+=*s;
}
if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
{
goto error;
}
}
else
{
for (li.toFirst();(s=li.current());++li)
{
dotArgs="\""+file+"\" "+*s;
if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
{
goto error;
}
}
}
if (!postCmd.isEmpty() && portable_system(postCmd,postArgs)!=0)
{
err("error: Problems running '%s' as a post-processing step for dot output\n",m_postCmd.data());
return FALSE;
}
if (checkResult) checkDotResult(imageName);
if (cleanUp)
{
//printf("removing dot file %s\n",m_file.data());
//QDir(path).remove(file);
m_cleanupItem.file = file;
m_cleanupItem.path = path;
}
return TRUE;
error:
err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n",
exitCode,dotExe.data(),dotArgs.data());
return FALSE;
}
//--------------------------------------------------------------------
DotFilePatcher::DotFilePatcher(const char *patchFile)
: m_patchFile(patchFile)
{
m_maps.setAutoDelete(TRUE);
}
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;
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;
m_maps.append(map);
return id;
}
int DotFilePatcher::addSVGConversion(const QCString &relPath,bool urlOnly,
const QCString &context)
{
int id = m_maps.count();
Map *map = new Map;
map->relPath = relPath;
map->urlOnly = urlOnly;
map->context = context;
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;
m_maps.append(map);
return id;
}
bool DotFilePatcher::run()
{
//printf("DotFilePatcher::run(): %s\n",m_patchFile.data());
bool isSVGFile = m_patchFile.right(4)==".svg";
QCString tmpName = m_patchFile+".tmp";
if (!QDir::current().rename(m_patchFile,tmpName))
{
err("Failed to rename file %s to %s!\n",m_patchFile.data(),tmpName.data());
return FALSE;
}
QFile fi(tmpName);
QFile fo(m_patchFile);
if (!fi.open(IO_ReadOnly))
{
err("error: problem opening file %s for patching!\n",tmpName.data());
QDir::current().rename(tmpName,m_patchFile);
return FALSE;
}
if (!fo.open(IO_WriteOnly))
{
err("error: problem opening file %s for patching!\n",m_patchFile.data());
QDir::current().rename(tmpName,m_patchFile);
return FALSE;
}
FTextStream t(&fo);
const int maxLineLen=100*1024;
while (!fi.atEnd()) // foreach line
{
QCString line(maxLineLen);
int numBytes = fi.readLine(line.data(),maxLineLen);
//printf("line=[%s]\n",line.stripWhiteSpace().data());
int i;
ASSERT(numBytesrelPath,map->urlOnly,map->context,"_top");
}
else if ((i=line.find(""));
Map *map = m_maps.at(mapId);
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("" << endl;
}
}
else // normal bitmap
{
out << "" << endl;
if (regenerate || !insertMapFile(out,absMapName,QCString(),mapLabel))
{
int mapId = DotManager::instance()->addMap(fileName,absMapName,QCString(),
FALSE,QCString(),mapLabel);
out << "" << endl;
}
}
out << "" << endl;
}
out << "" << endl;
}
void DotGfxHierarchyTable::addHierarchy(DotNode *n,ClassDef *cd,bool hideSuper)
{
//printf("addHierarchy `%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count());
if (cd->subClasses())
{
BaseClassListIterator bcli(*cd->subClasses());
BaseClassDef *bcd;
for ( ; (bcd=bcli.current()) ; ++bcli )
{
ClassDef *bClass=bcd->classDef;
//printf(" Trying sub class=`%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count());
if (bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses()))
{
DotNode *bn;
//printf(" Node `%s' Found visible class=`%s'\n",n->m_label.data(),
// bClass->name().data());
if ((bn=m_usedNodes->find(bClass->name()))) // node already present
{
if (n->m_children==0 || n->m_children->findRef(bn)==-1) // no arrow yet
{
n->addChild(bn,bcd->prot);
bn->addParent(n);
//printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n",
// n->m_label.data(),
// bn->m_label.data(),
// bn->m_children ? bn->m_children->count() : 0,
// bn->m_parents ? bn->m_parents->count() : 0
// );
}
//else
//{
// printf(" Class already has an arrow!\n");
//}
}
else
{
QCString tmp_url="";
if (bClass->isLinkable() && !bClass->isHidden())
{
tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase();
}
QCString tooltip = bClass->briefDescriptionAsTooltip();
bn = new DotNode(m_curNodeNumber++,
bClass->displayName(),
tooltip,
tmp_url.data()
);
n->addChild(bn,bcd->prot);
bn->addParent(n);
//printf(" Adding node %s to new base node %s (c=%d,p=%d)\n",
// n->m_label.data(),
// bn->m_label.data(),
// bn->m_children ? bn->m_children->count() : 0,
// bn->m_parents ? bn->m_parents->count() : 0
// );
//printf(" inserting %s (%p)\n",bClass->name().data(),bn);
m_usedNodes->insert(bClass->name(),bn); // add node to the used list
}
if (!bClass->visited && !hideSuper && bClass->subClasses())
{
bool wasVisited=bClass->visited;
bClass->visited=TRUE;
addHierarchy(bn,bClass,wasVisited);
}
}
}
}
//printf("end addHierarchy\n");
}
void DotGfxHierarchyTable::addClassList(ClassSDict *cl)
{
ClassSDict::Iterator cli(*cl);
ClassDef *cd;
for (cli.toLast();(cd=cli.current());--cli)
{
//printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count());
if (!hasVisibleRoot(cd->baseClasses()) &&
cd->isVisibleInHierarchy()
) // root node in the forest
{
QCString tmp_url="";
if (cd->isLinkable() && !cd->isHidden())
{
tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
}
//printf("Inserting root class %s\n",cd->name().data());
QCString tooltip = cd->briefDescriptionAsTooltip();
DotNode *n = new DotNode(m_curNodeNumber++,
cd->displayName(),
tooltip,
tmp_url.data());
//m_usedNodes->clear();
m_usedNodes->insert(cd->name(),n);
m_rootNodes->insert(0,n);
if (!cd->visited && cd->subClasses())
{
addHierarchy(n,cd,cd->visited);
cd->visited=TRUE;
}
}
}
}
DotGfxHierarchyTable::DotGfxHierarchyTable()
{
m_curNodeNumber=0;
m_rootNodes = new QList;
m_usedNodes = new QDict(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 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 dnli2(*m_rootSubgraphs);
//DotNode *n;
//for (dnli2.toFirst();(n=dnli2.current());++dnli2)
//{
// printf("Node %s color=%d (c=%d,p=%d)\n",
// n->m_label.data(),n->m_subgraphId,
// n->m_children?n->m_children->count():0,
// n->m_parents?n->m_parents->count():0);
//}
}
DotGfxHierarchyTable::~DotGfxHierarchyTable()
{
//printf("DotGfxHierarchyTable::~DotGfxHierarchyTable\n");
//QDictIterator di(*m_usedNodes);
//DotNode *n;
//for (;(n=di.current());++di)
//{
// printf("Node %p: %s\n",n,n->label().data());
//}
delete m_rootNodes;
delete m_usedNodes;
delete m_rootSubgraphs;
}
//--------------------------------------------------------------------
int DotClassGraph::m_curNodeNumber = 0;
void DotClassGraph::addClass(ClassDef *cd,DotNode *n,int prot,
const char *label,const char *usedName,const char *templSpec,bool base,int distance)
{
if (Config_getBool("HIDE_UNDOC_CLASSES") && !cd->isLinkable()) return;
int edgeStyle = (label || prot==EdgeInfo::Orange) ? EdgeInfo::Dashed : EdgeInfo::Solid;
QCString className;
if (usedName) // name is a typedef
{
className=usedName;
}
else if (templSpec) // name has a template part
{
className=insertTemplateSpecifierInScope(cd->name(),templSpec);
}
else // just a normal name
{
className=cd->displayName();
}
//printf("DotClassGraph::addClass(class=`%s',parent=%s,prot=%d,label=%s,dist=%d,usedName=%s,templSpec=%s,base=%d)\n",
// className.data(),n->m_label.data(),prot,label,distance,usedName,templSpec,base);
DotNode *bn = m_usedNodes->find(className);
if (bn) // class already inserted
{
if (base)
{
n->addChild(bn,prot,edgeStyle,label);
bn->addParent(n);
}
else
{
bn->addChild(n,prot,edgeStyle,label);
n->addParent(bn);
}
bn->setDistance(distance);
//printf(" add exiting node %s of %s\n",bn->m_label.data(),n->m_label.data());
}
else // new class
{
QCString displayName=className;
if (Config_getBool("HIDE_SCOPE_NAMES")) displayName=stripScope(displayName);
QCString tmp_url;
if (cd->isLinkable() && !cd->isHidden())
{
tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
}
QCString tooltip = cd->briefDescriptionAsTooltip();
bn = new DotNode(m_curNodeNumber++,
displayName,
tooltip,
tmp_url.data(),
FALSE, // rootNode
cd
);
if (base)
{
n->addChild(bn,prot,edgeStyle,label);
bn->addParent(n);
}
else
{
bn->addChild(n,prot,edgeStyle,label);
n->addParent(bn);
}
bn->setDistance(distance);
m_usedNodes->insert(className,bn);
//printf(" add new child node `%s' to %s hidden=%d url=%s\n",
// className.data(),n->m_label.data(),cd->isHidden(),tmp_url.data());
buildGraph(cd,bn,base,distance+1);
}
}
void DotClassGraph::determineTruncatedNodes(QList &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 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 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 childQueue;
QList parentQueue;
QArray childTreeWidth;
QArray 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() && distance0)
{
int oldSize=(int)childTreeWidth.size();
if (distance>oldSize)
{
childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance));
int i; for (i=oldSize;ilabel().length();
}
n->markAsVisible();
maxNodes--;
// add direct children
if (n->m_children)
{
QListIterator 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()distance();
if (distance>0)
{
int oldSize = (int)parentTreeWidth.size();
if (distance>oldSize)
{
parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance));
int i; for (i=oldSize;ilabel().length();
}
n->markAsVisible();
maxNodes--;
// add direct parents
if (n->m_parents)
{
QListIterator 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;imaxWidth) maxWidth=childTreeWidth.at(i);
}
for (i=0;imaxWidth) maxWidth=parentTreeWidth.at(i);
}
//printf("max tree width=%d, max tree height=%d\n",maxWidth,maxHeight);
return maxWidth>80 && maxHeight<12; // used metric to decide to render the tree
// from left to right instead of top to bottom,
// with the idea to render very wide trees in
// left to right order.
}
void DotClassGraph::buildGraph(ClassDef *cd,DotNode *n,bool base,int distance)
{
//printf("DocClassGraph::buildGraph(%s,distance=%d,base=%d)\n",
// cd->name().data(),distance,base);
// ---- Add inheritance relations
if (m_graphType == DotNode::Inheritance || m_graphType==DotNode::Collaboration)
{
BaseClassList *bcl = base ? cd->baseClasses() : cd->subClasses();
if (bcl)
{
BaseClassListIterator bcli(*bcl);
BaseClassDef *bcd;
for ( ; (bcd=bcli.current()) ; ++bcli )
{
//printf("-------- inheritance relation %s->%s templ=`%s'\n",
// cd->name().data(),bcd->classDef->name().data(),bcd->templSpecifiers.data());
addClass(bcd->classDef,n,bcd->prot,0,bcd->usedName,
bcd->templSpecifiers,base,distance);
}
}
}
if (m_graphType == DotNode::Collaboration)
{
// ---- Add usage relations
UsesClassDict *dict =
base ? cd->usedImplementationClasses() :
cd->usedByImplementationClasses()
;
if (dict)
{
UsesClassDictIterator ucdi(*dict);
UsesClassDef *ucd;
for (;(ucd=ucdi.current());++ucdi)
{
QCString label;
QDictIterator dvi(*ucd->accessors);
const char *s;
bool first=TRUE;
int count=0;
int maxLabels=10;
for (;(s=dvi.currentKey()) && countclassDef->name().data(),ucd->templSpecifiers.data());
addClass(ucd->classDef,n,EdgeInfo::Purple,label,0,
ucd->templSpecifiers,base,distance);
}
}
}
// ---- Add template instantiation relations
static bool templateRelations = Config_getBool("TEMPLATE_RELATIONS");
if (templateRelations)
{
if (base) // template relations for base classes
{
ClassDef *templMaster=cd->templateMaster();
if (templMaster)
{
QDictIterator cli(*templMaster->getTemplateInstances());
ClassDef *templInstance;
for (;(templInstance=cli.current());++cli)
{
if (templInstance==cd)
{
addClass(templMaster,n,EdgeInfo::Orange,cli.currentKey(),0,
0,TRUE,distance);
}
}
}
}
else // template relations for super classes
{
QDict *templInstances = cd->getTemplateInstances();
if (templInstances)
{
QDictIterator cli(*templInstances);
ClassDef *templInstance;
for (;(templInstance=cli.current());++cli)
{
addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0,
0,FALSE,distance);
}
}
}
}
}
DotClassGraph::DotClassGraph(ClassDef *cd,DotNode::GraphType t)
{
//printf("--------------- DotClassGraph::DotClassGraph `%s'\n",cd->displayName().data());
m_graphType = t;
QCString tmp_url="";
if (cd->isLinkable() && !cd->isHidden())
{
tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
}
QCString className = cd->displayName();
QCString tooltip = cd->briefDescriptionAsTooltip();
m_startNode = new DotNode(m_curNodeNumber++,
className,
tooltip,
tmp_url.data(),
TRUE, // is a root node
cd
);
m_startNode->setDistance(0);
m_usedNodes = new QDict(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 openNodeQueue;
openNodeQueue.append(m_startNode);
determineTruncatedNodes(openNodeQueue,t==DotNode::Inheritance);
m_diskName = cd->getFileBase().copy();
}
bool DotClassGraph::isTrivial() const
{
if (m_graphType==DotNode::Inheritance)
return m_startNode->m_children==0 && m_startNode->m_parents==0;
else
return m_startNode->m_children==0;
}
bool DotClassGraph::isTooBig() const
{
static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
int numNodes = 0;
numNodes+= m_startNode->m_children ? m_startNode->m_children->count() : 0;
if (m_graphType==DotNode::Inheritance)
{
numNodes+= m_startNode->m_parents ? m_startNode->m_parents->count() : 0;
}
return numNodes>=maxNodes;
}
DotClassGraph::~DotClassGraph()
{
deleteNodes(m_startNode);
delete m_usedNodes;
}
/*! Computes a 16 byte md5 checksum for a given dot graph.
* The md5 checksum is returned as a 32 character ASCII string.
*/
QCString computeMd5Signature(DotNode *root,
DotNode::GraphType gt,
GraphOutputFormat format,
bool lrRank,
bool renderParents,
bool backArrows,
QCString &graphStr
)
{
bool reNumber=TRUE;
//printf("computeMd5Signature\n");
QGString buf;
FTextStream md5stream(&buf);
writeGraphHeader(md5stream);
if (lrRank)
{
md5stream << " rankdir=LR;" << endl;
}
root->clearWriteFlag();
root->write(md5stream,
gt,
format,
gt!=DotNode::CallGraph && gt!=DotNode::Dependency,
TRUE,
backArrows,
reNumber);
if (renderParents && root->m_parents)
{
QListIterator dnli(*root->m_parents);
DotNode *pn;
for (dnli.toFirst();(pn=dnli.current());++dnli)
{
if (pn->isVisible())
{
root->writeArrow(md5stream, // stream
gt, // graph type
format, // output format
pn, // child node
pn->m_edgeInfo->at(pn->m_children->findRef(root)), // edge info
FALSE, // topDown?
backArrows, // point back?
reNumber // renumber nodes
);
}
pn->write(md5stream, // stream
gt, // graph type
format, // output format
TRUE, // topDown?
FALSE, // toChildren?
backArrows, // backward pointing arrows?
reNumber // renumber nodes?
);
}
}
writeGraphFooter(md5stream);
uchar md5_sig[16];
QCString sigStr(33);
MD5Buffer((const unsigned char *)buf.data(),buf.length(),md5_sig);
MD5SigToString(md5_sig,sigStr.data(),33);
if (reNumber)
{
resetReNumbering();
}
graphStr=buf.data();
//printf("md5: %s | file: %s\n",sigStr,baseName.data());
return sigStr;
}
static bool updateDotGraph(DotNode *root,
DotNode::GraphType gt,
const QCString &baseName,
GraphOutputFormat format,
bool lrRank,
bool renderParents,
bool backArrows
)
{
QCString theGraph;
// TODO: write graph to theGraph, then compute md5 checksum
QCString md5 = computeMd5Signature(
root,gt,format,lrRank,renderParents,backArrows,theGraph);
QFile f(baseName+".dot");
if (f.open(IO_WriteOnly))
{
FTextStream t(&f);
t << theGraph;
}
return checkAndUpdateMd5Signature(baseName,md5); // graph needs to be regenerated
}
QCString DotClassGraph::diskName() const
{
QCString result=m_diskName.copy();
switch (m_graphType)
{
case DotNode::Collaboration:
result+="_coll_graph";
break;
//case Interface:
// result+="_intf_graph";
// break;
case DotNode::Inheritance:
result+="_inherit_graph";
break;
default:
ASSERT(0);
break;
}
return result;
}
QCString DotClassGraph::writeGraph(FTextStream &out,
GraphOutputFormat format,
const char *path,
const char *fileName,
const char *relPath,
bool /*isTBRank*/,
bool generateImageMap) const
{
QDir d(path);
// store the original directory
if (!d.exists())
{
err("error: Output dir %s does not exist!\n",path); exit(1);
}
static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
QCString baseName;
QCString mapName;
switch (m_graphType)
{
case DotNode::Collaboration:
mapName="coll_map";
break;
//case Interface:
// mapName="intf_map";
// break;
case DotNode::Inheritance:
mapName="inherit_map";
break;
default:
ASSERT(0);
break;
}
baseName = convertNameToFile(diskName());
// derive target file names from baseName
QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
QCString absBaseName = QCString(d.absPath())+"/"+baseName;
QCString absDotName = absBaseName+".dot";
QCString absMapName = absBaseName+".map";
QCString absPdfName = absBaseName+".pdf";
QCString absEpsName = absBaseName+".eps";
QCString absImgName = absBaseName+"."+imgExt;
bool regenerate = FALSE;
if (updateDotGraph(m_startNode,
m_graphType,
absBaseName,
format,
m_lrRank,
m_graphType==DotNode::Inheritance,
TRUE
) ||
!checkDeliverables(format==BITMAP ? absImgName :
usePDFLatex ? absPdfName : absEpsName,
format==BITMAP && generateImageMap ? absMapName : QCString())
)
{
regenerate=TRUE;
if (format==BITMAP) // run dot to create a bitmap image
{
QCString dotArgs(maxCmdLine);
DotRunner *dotRun = new DotRunner(absDotName,
d.absPath().data(),TRUE,absImgName);
dotRun->addJob(imgExt,absImgName);
if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
DotManager::instance()->addRun(dotRun);
}
else if (format==EPS) // run dot to create a .eps image
{
DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
if (usePDFLatex)
{
dotRun->addJob("pdf",absPdfName);
}
else
{
dotRun->addJob("ps",absEpsName);
}
DotManager::instance()->addRun(dotRun);
}
}
Doxygen::indexList.addImageFile(baseName+"."+imgExt);
if (format==BITMAP && generateImageMap) // produce HTML to include the image
{
QCString mapLabel = escapeCharsInString(m_startNode->m_label,FALSE)+"_"+
escapeCharsInString(mapName,FALSE);
if (imgExt=="svg") // add link to SVG file without map file
{
out << "
";
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());
}
int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
out << "" << endl;
}
out << "
" << endl;
}
else // add link to bitmap file with image map
{
out << "
";
out << "";
out << "
" << endl;
if (regenerate || !insertMapFile(out,absMapName,relPath,mapLabel))
{
int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
FALSE,QCString(),mapLabel);
out << "" << endl;
}
}
}
else if (format==EPS) // produce tex to include the .eps image
{
if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
{
int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE /*TRUE*/);
out << endl << "% FIG " << figId << endl;
}
}
if (!regenerate) removeDotGraph(absDotName);
return baseName;
}
//--------------------------------------------------------------------
void DotClassGraph::writeXML(FTextStream &t)
{
QDictIterator dni(*m_usedNodes);
DotNode *node;
for (;(node=dni.current());++dni)
{
node->writeXML(t,TRUE);
}
}
void DotClassGraph::writeDEF(FTextStream &t)
{
QDictIterator dni(*m_usedNodes);
DotNode *node;
for (;(node=dni.current());++dni)
{
node->writeDEF(t);
}
}
//--------------------------------------------------------------------
int DotInclDepGraph::m_curNodeNumber = 0;
void DotInclDepGraph::buildGraph(DotNode *n,FileDef *fd,int distance)
{
QList *includeFiles =
m_inverse ? fd->includedByFileList() : fd->includeFileList();
if (includeFiles)
{
QListIterator ili(*includeFiles);
IncludeInfo *ii;
for (;(ii=ili.current());++ili)
{
FileDef *bfd = ii->fileDef;
QCString in = ii->includeName;
//printf(">>>> in=`%s' bfd=%p\n",ii->includeName.data(),bfd);
bool doc=TRUE,src=FALSE;
if (bfd)
{
in = bfd->absFilePath();
doc = bfd->isLinkable() && !bfd->isHidden();
src = bfd->generateSourceFile();
}
if (doc || src || !Config_getBool("HIDE_UNDOC_RELATIONS"))
{
QCString url="";
if (bfd) url=bfd->getOutputFileBase().copy();
if (!doc && src)
{
url=bfd->getSourceFileBase();
}
DotNode *bn = m_usedNodes->find(in);
if (bn) // file is already a node in the graph
{
n->addChild(bn,0,0,0);
bn->addParent(n);
bn->setDistance(distance);
}
else
{
QCString tmp_url;
QCString tooltip;
if (bfd)
{
tmp_url=doc || src ? bfd->getReference()+"$"+url : QCString();
tooltip = bfd->briefDescriptionAsTooltip();
}
bn = new DotNode(
m_curNodeNumber++, // n
ii->includeName, // label
tooltip, // tip
tmp_url, // url
FALSE, // rootNode
0 // cd
);
n->addChild(bn,0,0,0);
bn->addParent(n);
m_usedNodes->insert(in,bn);
bn->setDistance(distance);
if (bfd) buildGraph(bn,bfd,distance+1);
}
}
}
}
}
void DotInclDepGraph::determineVisibleNodes(QList &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()markAsVisible();
maxNodes--;
// add direct children
if (n->m_children)
{
QListIterator li(*n->m_children);
DotNode *dn;
for (li.toFirst();(dn=li.current());++li)
{
queue.append(dn);
}
}
}
}
}
void DotInclDepGraph::determineTruncatedNodes(QList &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 li(*n->m_children);
DotNode *dn;
for (li.toFirst();(dn=li.current());++li)
{
if (!dn->isVisible())
truncated = TRUE;
else
queue.append(dn);
}
}
n->markAsTruncated(truncated);
}
}
}
DotInclDepGraph::DotInclDepGraph(FileDef *fd,bool inverse)
{
m_maxDistance = 0;
m_inverse = inverse;
ASSERT(fd!=0);
m_diskName = fd->getFileBase().copy();
QCString tmp_url=fd->getReference()+"$"+fd->getFileBase();
m_startNode = new DotNode(m_curNodeNumber++,
fd->docName(),
"",
tmp_url.data(),
TRUE // root node
);
m_startNode->setDistance(0);
m_usedNodes = new QDict(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 openNodeQueue;
openNodeQueue.append(m_startNode);
determineVisibleNodes(openNodeQueue,maxNodes);
openNodeQueue.clear();
openNodeQueue.append(m_startNode);
determineTruncatedNodes(openNodeQueue);
}
DotInclDepGraph::~DotInclDepGraph()
{
deleteNodes(m_startNode);
delete m_usedNodes;
}
QCString DotInclDepGraph::diskName() const
{
QCString result=m_diskName.copy();
if (m_inverse) result+="_dep";
result+="_incl";
return convertNameToFile(result);
}
QCString DotInclDepGraph::writeGraph(FTextStream &out,
GraphOutputFormat format,
const char *path,
const char *fileName,
const char *relPath,
bool generateImageMap
) const
{
QDir d(path);
// store the original directory
if (!d.exists())
{
err("error: Output dir %s does not exist!\n",path); exit(1);
}
static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
QCString baseName=m_diskName;
if (m_inverse) baseName+="_dep";
baseName+="_incl";
baseName=convertNameToFile(baseName);
QCString mapName=escapeCharsInString(m_startNode->m_label,FALSE);
if (m_inverse) mapName+="dep";
QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
QCString absBaseName = QCString(d.absPath())+"/"+baseName;
QCString absDotName = absBaseName+".dot";
QCString absMapName = absBaseName+".map";
QCString absPdfName = absBaseName+".pdf";
QCString absEpsName = absBaseName+".eps";
QCString absImgName = absBaseName+"."+imgExt;
bool regenerate = FALSE;
if (updateDotGraph(m_startNode,
DotNode::Dependency,
absBaseName,
format,
FALSE, // lrRank
FALSE, // renderParents
m_inverse // backArrows
) ||
!checkDeliverables(format==BITMAP ? absImgName :
usePDFLatex ? absPdfName : absEpsName,
format==BITMAP && generateImageMap ? absMapName : QCString())
)
{
regenerate=TRUE;
if (format==BITMAP)
{
// run dot to create a bitmap image
QCString dotArgs(maxCmdLine);
DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
dotRun->addJob(imgExt,absImgName);
if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
DotManager::instance()->addRun(dotRun);
}
else if (format==EPS)
{
DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
if (usePDFLatex)
{
dotRun->addJob("pdf",absPdfName);
}
else
{
dotRun->addJob("ps",absEpsName);
}
DotManager::instance()->addRun(dotRun);
}
}
Doxygen::indexList.addImageFile(baseName+"."+imgExt);
if (format==BITMAP && generateImageMap)
{
if (imgExt=="svg") // Scalable vector graphics
{
out << "
";
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());
}
int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
out << "" << endl;
}
out << "
" << endl;
}
else // bitmap graphics
{
out << "
";
out << "
" << endl;
QCString absMapName = absBaseName+".map";
if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
{
int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
FALSE,QCString(),mapName);
out << "" << endl;
}
}
}
else if (format==EPS) // encapsulated postscript
{
if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
{
int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
out << endl << "% FIG " << figId << endl;
}
}
if (!regenerate) removeDotGraph(absDotName);
return baseName;
}
bool DotInclDepGraph::isTrivial() const
{
return m_startNode->m_children==0;
}
bool DotInclDepGraph::isTooBig() const
{
static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
return numNodes>=maxNodes;
}
void DotInclDepGraph::writeXML(FTextStream &t)
{
QDictIterator dni(*m_usedNodes);
DotNode *node;
for (;(node=dni.current());++dni)
{
node->writeXML(t,FALSE);
}
}
//-------------------------------------------------------------
int DotCallGraph::m_curNodeNumber = 0;
void DotCallGraph::buildGraph(DotNode *n,MemberDef *md,int distance)
{
LockingPtr refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers();
if (!refs.isNull())
{
MemberSDict::Iterator mri(*refs);
MemberDef *rmd;
for (;(rmd=mri.current());++mri)
{
if (rmd->isFunction() || rmd->isSlot() || rmd->isSignal())
{
QCString uniqueId;
uniqueId=rmd->getReference()+"$"+
rmd->getOutputFileBase()+"#"+rmd->anchor();
DotNode *bn = m_usedNodes->find(uniqueId);
if (bn) // file is already a node in the graph
{
n->addChild(bn,0,0,0);
bn->addParent(n);
bn->setDistance(distance);
}
else
{
QCString name;
if (Config_getBool("HIDE_SCOPE_NAMES"))
{
name = rmd->getOuterScope()==m_scope ?
rmd->name() : rmd->qualifiedName();
}
else
{
name = rmd->qualifiedName();
}
QCString tooltip = rmd->briefDescriptionAsTooltip();
bn = new DotNode(
m_curNodeNumber++,
linkToText(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 &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()markAsVisible();
maxNodes--;
// add direct children
if (n->m_children)
{
QListIterator li(*n->m_children);
DotNode *dn;
for (li.toFirst();(dn=li.current());++li)
{
queue.append(dn);
}
}
}
}
}
void DotCallGraph::determineTruncatedNodes(QList &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 li(*n->m_children);
DotNode *dn;
for (li.toFirst();(dn=li.current());++li)
{
if (!dn->isVisible())
truncated = TRUE;
else
queue.append(dn);
}
}
n->markAsTruncated(truncated);
}
}
}
DotCallGraph::DotCallGraph(MemberDef *md,bool inverse)
{
m_maxDistance = 0;
m_inverse = inverse;
m_diskName = md->getOutputFileBase()+"_"+md->anchor();
m_scope = md->getOuterScope();
QCString uniqueId;
uniqueId = md->getReference()+"$"+
md->getOutputFileBase()+"#"+md->anchor();
QCString name;
if (Config_getBool("HIDE_SCOPE_NAMES"))
{
name = md->name();
}
else
{
name = md->qualifiedName();
}
m_startNode = new DotNode(m_curNodeNumber++,
linkToText(name,FALSE),
"",
uniqueId.data(),
TRUE // root node
);
m_startNode->setDistance(0);
m_usedNodes = new QDict(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 openNodeQueue;
openNodeQueue.append(m_startNode);
determineVisibleNodes(openNodeQueue,maxNodes);
openNodeQueue.clear();
openNodeQueue.append(m_startNode);
determineTruncatedNodes(openNodeQueue);
}
DotCallGraph::~DotCallGraph()
{
deleteNodes(m_startNode);
delete m_usedNodes;
}
QCString DotCallGraph::writeGraph(FTextStream &out, GraphOutputFormat format,
const char *path,const char *fileName,
const char *relPath,bool generateImageMap) const
{
QDir d(path);
// store the original directory
if (!d.exists())
{
err("error: Output dir %s does not exist!\n",path); exit(1);
}
static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
QCString baseName = m_diskName + (m_inverse ? "_icgraph" : "_cgraph");
QCString mapName = baseName;
QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
QCString absBaseName = QCString(d.absPath())+"/"+baseName;
QCString absDotName = absBaseName+".dot";
QCString absMapName = absBaseName+".map";
QCString absPdfName = absBaseName+".pdf";
QCString absEpsName = absBaseName+".eps";
QCString absImgName = absBaseName+"."+imgExt;
bool regenerate=FALSE;
if (updateDotGraph(m_startNode,
DotNode::CallGraph,
absBaseName,
format,
TRUE, // lrRank
FALSE, // renderParents
m_inverse // backArrows
) ||
!checkDeliverables(format==BITMAP ? absImgName :
usePDFLatex ? absPdfName : absEpsName,
format==BITMAP && generateImageMap ? absMapName : QCString())
)
{
regenerate=TRUE;
if (format==BITMAP)
{
// run dot to create a bitmap image
QCString dotArgs(maxCmdLine);
DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
dotRun->addJob(imgExt,absImgName);
if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
DotManager::instance()->addRun(dotRun);
}
else if (format==EPS)
{
// run dot to create a .eps image
DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
if (usePDFLatex)
{
dotRun->addJob("pdf",absPdfName);
}
else
{
dotRun->addJob("ps",absEpsName);
}
DotManager::instance()->addRun(dotRun);
}
}
Doxygen::indexList.addImageFile(baseName+"."+imgExt);
if (format==BITMAP && generateImageMap)
{
if (imgExt=="svg") // Scalable vector graphics
{
out << "
";
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());
}
int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
out << "" << endl;
}
out << "
" << endl;
}
else // bitmap graphics
{
out << "
";
out << "
" << endl;
if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
{
int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
FALSE,QCString(),mapName);
out << "" << endl;
}
}
}
else if (format==EPS) // encapsulated postscript
{
if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
{
int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
out << endl << "% FIG " << figId << endl;
}
}
if (!regenerate) removeDotGraph(absDotName);
return baseName;
}
bool DotCallGraph::isTrivial() const
{
return m_startNode->m_children==0;
}
bool DotCallGraph::isTooBig() const
{
static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
return numNodes>=maxNodes;
}
//-------------------------------------------------------------
DotDirDeps::DotDirDeps(DirDef *dir) : m_dir(dir)
{
}
DotDirDeps::~DotDirDeps()
{
}
QCString DotDirDeps::writeGraph(FTextStream &out,
GraphOutputFormat format,
const char *path,
const char *fileName,
const char *relPath,
bool generateImageMap) const
{
QDir d(path);
// store the original directory
if (!d.exists())
{
err("error: Output dir %s does not exist!\n",path); exit(1);
}
static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
QCString baseName=m_dir->getOutputFileBase()+"_dep";
QCString mapName=escapeCharsInString(baseName,FALSE);
QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
QCString absBaseName = QCString(d.absPath())+"/"+baseName;
QCString absDotName = absBaseName+".dot";
QCString absMapName = absBaseName+".map";
QCString absPdfName = absBaseName+".pdf";
QCString absEpsName = absBaseName+".eps";
QCString absImgName = absBaseName+"."+imgExt;
// compute md5 checksum of the graph were are about to generate
QGString theGraph;
FTextStream md5stream(&theGraph);
m_dir->writeDepGraph(md5stream);
uchar md5_sig[16];
QCString sigStr(33);
MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
MD5SigToString(md5_sig,sigStr.data(),33);
bool regenerate=FALSE;
if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
!checkDeliverables(format==BITMAP ? absImgName :
usePDFLatex ? absPdfName : absEpsName,
format==BITMAP && generateImageMap ? absMapName : QCString())
)
{
regenerate=TRUE;
QFile f(absDotName);
if (!f.open(IO_WriteOnly))
{
err("Cannot create file %s.dot for writing!\n",baseName.data());
}
FTextStream t(&f);
t << theGraph.data();
f.close();
if (format==BITMAP)
{
// run dot to create a bitmap image
QCString dotArgs(maxCmdLine);
DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
dotRun->addJob(imgExt,absImgName);
if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
DotManager::instance()->addRun(dotRun);
}
else if (format==EPS)
{
DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
if (usePDFLatex)
{
dotRun->addJob("pdf",absPdfName);
}
else
{
dotRun->addJob("ps",absEpsName);
}
DotManager::instance()->addRun(dotRun);
}
}
Doxygen::indexList.addImageFile(baseName+"."+imgExt);
if (format==BITMAP && generateImageMap)
{
if (imgExt=="svg") // Scalable vector graphics
{
out << "
";
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());
}
int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
out << "" << endl;
}
out << "
";
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());
}
int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
t << "" << endl;
}
t << "
" << endl;
}
else
{
t << "" << endl;
if (regenerate || !insertMapFile(t,absMapName,relPath,mapLabel))
{
int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
FALSE,QCString(),mapLabel);
t << "" << endl;
}
}
t << "
" << endl;
}
else if (format==EPS)
{
if (regenerate || !writeVecGfxFigure(t,baseName,absBaseName))
{
int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
t << endl << "% FIG " << figId << endl;
}
}
if (!regenerate) removeDotGraph(absDotName);
return baseName;
}
void DotGroupCollaboration::Edge::write( FTextStream &t ) const
{
const char* linkTypeColor[] = {
"darkorchid3"
,"orange"
,"blueviolet"
,"darkgreen"
,"firebrick4"
,"grey75"
,"midnightblue"
};
QCString arrowStyle = "dir=\"none\", style=\"dashed\"";
t << " Node" << pNStart->number();
t << "->";
t << "Node" << pNEnd->number();
t << " [shape=plaintext";
if (links.count()>0) // there are links
{
t << ", ";
// HTML-like edge labels crash on my Mac with Graphviz 2.0! and
// are not supported by older version of dot.
//
//t << label=<