Chameleon

Chameleon Svn Source Tree

Root/branches/xZenu/src/util/doxygen/src/dot.cpp

Source at commit 1322 created 9 years 5 months ago.
By meklort, Add doxygen to utils folder
1/*****************************************************************************
2 *
3 * $Id: dot.cpp,v 1.20 2001/03/19 19:27:40 root Exp $
4 *
5 *
6 * Copyright (C) 1997-2011 by Dimitri van Heesch.
7 *
8 * Permission to use, copy, modify, and distribute this software and its
9 * documentation under the terms of the GNU General Public License is hereby
10 * granted. No representations are made about the suitability of this software
11 * for any purpose. It is provided "as is" without express or implied warranty.
12 * See the GNU General Public License for more details.
13 *
14 * Documents produced by Doxygen are derivative works derived from the
15 * input used in their production; they are not affected by this license.
16 *
17 */
18
19#ifdef _WIN32
20#include <windows.h>
21#define BITMAP W_BITMAP
22#endif
23
24#include <stdlib.h>
25
26#include "dot.h"
27#include "doxygen.h"
28#include "message.h"
29#include "util.h"
30#include "config.h"
31#include "language.h"
32#include "defargs.h"
33#include "docparser.h"
34#include "debug.h"
35#include "pagedef.h"
36#include "portable.h"
37#include "dirdef.h"
38#include "vhdldocgen.h"
39#include <qdir.h>
40#include <qfile.h>
41#include "ftextstream.h"
42#include "md5.h"
43#include <qqueue.h>
44
45#include <qthread.h>
46#include <qmutex.h>
47#include <qwaitcondition.h>
48
49#define MAP_CMD "cmapx"
50
51//#define FONTNAME "Helvetica"
52#define FONTNAME getDotFontName()
53#define FONTSIZE getDotFontSize()
54
55//--------------------------------------------------------------------
56
57static const int maxCmdLine = 40960;
58
59/*! mapping from protection levels to color names */
60static const char *edgeColorMap[] =
61{
62 "midnightblue", // Public
63 "darkgreen", // Protected
64 "firebrick4", // Private
65 "darkorchid3", // "use" relation
66 "grey75", // Undocumented
67 "orange" // template relation
68};
69
70static const char *arrowStyle[] =
71{
72 "empty", // Public
73 "empty", // Protected
74 "empty", // Private
75 "open", // "use" relation
76 0, // Undocumented
77 0 // template relation
78};
79
80static const char *edgeStyleMap[] =
81{
82 "solid", // inheritance
83 "dashed" // usage
84};
85
86static QCString getDotFontName()
87{
88 static QCString dotFontName = Config_getString("DOT_FONTNAME");
89 if (dotFontName.isEmpty())
90 {
91 //dotFontName="FreeSans.ttf";
92 dotFontName="Helvetica";
93 }
94 return dotFontName;
95}
96
97static int getDotFontSize()
98{
99 static int dotFontSize = Config_getInt("DOT_FONTSIZE");
100 if (dotFontSize<4) dotFontSize=4;
101 return dotFontSize;
102}
103
104static void writeGraphHeader(FTextStream &t)
105{
106 t << "digraph G" << endl;
107 t << "{" << endl;
108 if (Config_getBool("DOT_TRANSPARENT"))
109 {
110 t << " bgcolor=\"transparent\";" << endl;
111 }
112 t << " edge [fontname=\"" << FONTNAME << "\","
113 "fontsize=\"" << FONTSIZE << "\","
114 "labelfontname=\"" << FONTNAME << "\","
115 "labelfontsize=\"" << FONTSIZE << "\"];\n";
116 t << " node [fontname=\"" << FONTNAME << "\","
117 "fontsize=\"" << FONTSIZE << "\",shape=record];\n";
118}
119
120static void writeGraphFooter(FTextStream &t)
121{
122 t << "}" << endl;
123}
124
125static QCString replaceRef(const QCString &buf,const QCString relPath,
126 bool urlOnly,const QCString &context,const QCString &target=QCString())
127{
128 // search for href="...", store ... part in link
129 QCString href = "href";
130 bool isXLink=FALSE;
131 int len = 6;
132 int indexS = buf.find("href=\""), indexE;
133 if (indexS>5 && buf.find("xlink:href=\"")!=-1) // XLink href (for SVG)
134 {
135 indexS-=6;
136 len+=6;
137 href.prepend("xlink:");
138 isXLink=TRUE;
139 }
140 if (indexS>=0 && (indexE=buf.find('"',indexS+len))!=-1)
141 {
142 QCString link = buf.mid(indexS+len,indexE-indexS-len);
143 QCString result;
144 if (urlOnly) // for user defined dot graphs
145 {
146 if (link.left(5)=="\\ref " || link.left(5)=="@ref ") // \ref url
147 {
148 result=href+"=\"";
149 // fake ref node to resolve the url
150 DocRef *df = new DocRef( (DocNode*) 0, link.mid(5), context );
151 result+=externalRef(relPath,df->ref(),TRUE);
152 if (!df->file().isEmpty())
153 result += df->file().data() + Doxygen::htmlFileExtension;
154 if (!df->anchor().isEmpty())
155 result += "#" + df->anchor();
156 delete df;
157 result += "\"";
158 }
159 else
160 {
161 result = href+"=\"" + link + "\"";
162 }
163 }
164 else // ref$url (external ref via tag file), or $url (local ref)
165 {
166 int marker = link.find('$');
167 if (marker!=-1)
168 {
169 QCString ref = link.left(marker);
170 QCString url = link.mid(marker+1);
171 if (!ref.isEmpty())
172 {
173 result = externalLinkTarget() + externalRef(relPath,ref,FALSE);
174 }
175 result+= href+"=\"";
176 result+=externalRef(relPath,ref,TRUE);
177 result+= url + "\"";
178 }
179 else // should not happen, but handle properly anyway
180 {
181 result = href+"=\"" + link + "\"";
182 }
183 }
184 if (!target.isEmpty())
185 {
186 result+=" target=\""+target+"\"";
187 }
188 QCString leftPart = buf.left(indexS);
189 QCString rightPart = buf.mid(indexE+1);
190 return leftPart + result + rightPart;
191 }
192 else
193 {
194 return buf;
195 }
196}
197
198/*! converts the rectangles in a client site image map into a stream
199 * \param t the stream to which the result is written.
200 * \param mapName the name of the map file.
201 * \param relPath the relative path to the root of the output directory
202 * (used in case CREATE_SUBDIRS is enabled).
203 * \param urlOnly if FALSE the url field in the map contains an external
204 * references followed by a $ and then the URL.
205 * \param context the context (file, class, or namespace) in which the
206 * map file was found
207 * \returns TRUE if succesful.
208 */
209static bool convertMapFile(FTextStream &t,const char *mapName,
210 const QCString relPath, bool urlOnly=FALSE,
211 const QCString &context=QCString())
212{
213 QFile f(mapName);
214 if (!f.open(IO_ReadOnly))
215 {
216 err("error: problems opening map file %s for inclusion in the docs!\n"
217 "If you installed Graphviz/dot after a previous failing run, \n"
218 "try deleting the output directory and rerun doxygen.\n",mapName);
219 return FALSE;
220 }
221 const int maxLineLen=10240;
222 while (!f.atEnd()) // foreach line
223 {
224 QCString buf(maxLineLen);
225 int numBytes = f.readLine(buf.data(),maxLineLen);
226 buf[numBytes-1]='\0';
227
228 if (buf.left(5)=="<area")
229 {
230 t << replaceRef(buf,relPath,urlOnly,context);
231 }
232 }
233 return TRUE;
234}
235
236static QArray<int> s_newNumber;
237static int s_max_newNumber=0;
238
239inline int reNumberNode(int number, bool doReNumbering)
240{
241 if (!doReNumbering)
242 {
243 return number;
244 }
245 else
246 {
247 int s = s_newNumber.size();
248 if (number>=s)
249 {
250 int ns=0;
251 ns = s * 3 / 2 + 5; // new size
252 if (number>=ns) // number still doesn't fit
253 {
254 ns = number * 3 / 2 + 5;
255 }
256 s_newNumber.resize(ns);
257 for (int i=s;i<ns;i++) // clear new part of the array
258 {
259 s_newNumber.at(i)=0;
260 }
261 }
262 int i = s_newNumber.at(number);
263 if (i == 0) // not yet mapped
264 {
265 i = ++s_max_newNumber; // start from 1
266 s_newNumber.at(number) = i;
267 }
268 return i;
269 }
270}
271
272static void resetReNumbering()
273{
274 s_max_newNumber=0;
275 s_newNumber.resize(s_max_newNumber);
276}
277
278static QCString g_dotFontPath;
279
280static void setDotFontPath(const char *path)
281{
282 ASSERT(g_dotFontPath.isEmpty());
283 g_dotFontPath = portable_getenv("DOTFONTPATH");
284 QCString newFontPath = Config_getString("DOT_FONTPATH");
285 QCString spath = path;
286 if (!newFontPath.isEmpty() && !spath.isEmpty())
287 {
288 newFontPath.prepend(spath+portable_pathListSeparator());
289 }
290 else if (newFontPath.isEmpty() && !spath.isEmpty())
291 {
292 newFontPath=path;
293 }
294 else
295 {
296 portable_unsetenv("DOTFONTPATH");
297 return;
298 }
299 portable_setenv("DOTFONTPATH",newFontPath);
300}
301
302static void unsetDotFontPath()
303{
304 if (g_dotFontPath.isEmpty())
305 {
306 portable_unsetenv("DOTFONTPATH");
307 }
308 else
309 {
310 portable_setenv("DOTFONTPATH",g_dotFontPath);
311 }
312 g_dotFontPath="";
313}
314
315static bool readBoundingBox(const char *fileName,int *width,int *height,bool isEps)
316{
317 QCString bb = isEps ? QCString("%%PageBoundingBox:") : QCString(" /MediaBox [ ");
318 QFile f(fileName);
319 if (!f.open(IO_ReadOnly|IO_Raw))
320 {
321 //printf("readBoundingBox: could not open %s\n",fileName);
322 return FALSE;
323 }
324 const int maxLineLen=1024;
325 char buf[maxLineLen];
326 while (!f.atEnd())
327 {
328 int numBytes = f.readLine(buf,maxLineLen-1); // read line
329 if (numBytes>0)
330 {
331 buf[numBytes]='\0';
332 if (strncmp(buf,bb.data(),bb.length()-1)==0) // found PageBoundingBox string
333 {
334 int x,y;
335 if (sscanf(buf+bb.length(),"%d %d %d %d",&x,&y,width,height)!=4)
336 {
337 //printf("readBoundingBox sscanf fail\n");
338 return FALSE;
339 }
340 return TRUE;
341 }
342 }
343 else // read error!
344 {
345 //printf("Read error %d!\n",numBytes);
346 return FALSE;
347 }
348 }
349 //printf("readBoundingBox: bounding box not found\n");
350 return FALSE;
351}
352
353static bool writeVecGfxFigure(FTextStream &out,const QCString &baseName,
354 const QCString &figureName)
355{
356 int width=420,height=600;
357 static bool usePdfLatex = Config_getBool("USE_PDFLATEX");
358 if (usePdfLatex)
359 {
360 if (!readBoundingBox(figureName+".pdf",&width,&height,FALSE))
361 {
362 //printf("writeVecGfxFigure()=0\n");
363 return FALSE;
364 }
365 }
366 else
367 {
368 if (!readBoundingBox(figureName+".eps",&width,&height,TRUE))
369 {
370 //printf("writeVecGfxFigure()=0\n");
371 return FALSE;
372 }
373 }
374 //printf("Got PDF/EPS size %d,%d\n",width,height);
375 int maxWidth = 400; /* approx. page width in points, excl. margins */
376 int maxHeight = 600; /* approx. page height in points, excl. margins */
377 out << "\\nopagebreak\n"
378 "\\begin{figure}[H]\n"
379 "\\begin{center}\n"
380 "\\leavevmode\n";
381 if (width>maxWidth || height>maxHeight) // figure too big for page
382 {
383 // c*width/maxWidth > c*height/maxHeight, where c=maxWidth*maxHeight>0
384 if (width*maxHeight>height*maxWidth)
385 {
386 out << "\\includegraphics[width=" << maxWidth << "pt]";
387 }
388 else
389 {
390 out << "\\includegraphics[height=" << maxHeight << "pt]";
391 }
392 }
393 else
394 {
395 out << "\\includegraphics[width=" << width << "pt]";
396 }
397
398 out << "{" << baseName << "}\n"
399 "\\end{center}\n"
400 "\\end{figure}\n";
401
402 //printf("writeVecGfxFigure()=1\n");
403 return TRUE;
404}
405
406// extract size from a dot generated SVG file
407static bool readSVGSize(const QCString &fileName,int *width,int *height)
408{
409 bool found=FALSE;
410 QFile f(fileName);
411 if (!f.open(IO_ReadOnly))
412 {
413 return FALSE;
414 }
415 const int maxLineLen=4096;
416 char buf[maxLineLen];
417 while (!f.atEnd() && !found)
418 {
419 int numBytes = f.readLine(buf,maxLineLen-1); // read line
420 if (numBytes>0)
421 {
422 buf[numBytes]='\0';
423 if (sscanf(buf,"<svg width=\"%dpt\" height=\"%dpt\"",width,height)==2)
424 {
425 found=TRUE;
426 }
427 }
428 else // read error!
429 {
430 //printf("Read error %d!\n",numBytes);
431 return FALSE;
432 }
433 }
434 return TRUE;
435}
436
437static void writeSVGNotSupported(FTextStream &out)
438{
439 out << "<p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p>";
440}
441
442// check if a reference to a SVG figure can be written and does so if possible.
443// return FALSE if not possible (for instance because the SVG file is not yet generated).
444static bool writeSVGFigureLink(FTextStream &out,const QCString &relPath,
445 const QCString &baseName,const QCString &absImgName)
446{
447 int width=600,height=450;
448 if (!readSVGSize(absImgName,&width,&height))
449 {
450 return FALSE;
451 }
452// out << "<object type=\"image/svg+xml\" data=\""
453 out << "<iframe src=\""
454 << relPath << baseName << ".svg\" width=\""
455 << ((width*96+48)/72) << "\" height=\""
456 << ((height*96+48)/72) << "\" frameborder=\"0\" scrolling=\"no\">";
457 writeSVGNotSupported(out);
458// out << "</object>";
459 out << "</iframe>";
460
461 return TRUE;
462}
463
464// since dot silently reproduces the input file when it does not
465// support the PNG format, we need to check the result.
466static void checkDotResult(const QCString &imgName)
467{
468 if (Config_getEnum("DOT_IMAGE_FORMAT")=="png")
469 {
470 QFile f(imgName);
471 if (f.open(IO_ReadOnly))
472 {
473 char data[4];
474 if (f.readBlock(data,4)==4)
475 {
476 if (!(data[1]=='P' && data[2]=='N' && data[3]=='G'))
477 {
478 err("error: Image `%s' produced by dot is not a valid PNG!\n"
479 "You should either select a different format "
480 "(DOT_IMAGE_FORMAT in the config file) or install a more "
481 "recent version of graphviz (1.7+)\n",imgName.data()
482 );
483 }
484 }
485 else
486 {
487 err("error: Could not read image `%s' generated by dot!\n",imgName.data());
488 }
489 }
490 else
491 {
492 err("error: Could not open image `%s' generated by dot!\n",imgName.data());
493 }
494 }
495}
496
497static bool insertMapFile(FTextStream &out,const QCString &mapFile,
498 const QCString &relPath,const QCString &mapLabel)
499{
500 QFileInfo fi(mapFile);
501 if (fi.exists() && fi.size()>0) // reuse existing map file
502 {
503 QGString tmpstr;
504 FTextStream tmpout(&tmpstr);
505 convertMapFile(tmpout,mapFile,relPath);
506 if (!tmpstr.isEmpty())
507 {
508 out << "<map name=\"" << mapLabel << "\" id=\"" << mapLabel << "\">" << endl;
509 out << tmpstr;
510 out << "</map>" << endl;
511 }
512 return TRUE;
513 }
514 return FALSE; // no map file yet, need to generate it
515}
516
517static void removeDotGraph(const QCString &dotName)
518{
519 static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
520 if (dotCleanUp)
521 {
522 QDir d;
523 d.remove(dotName);
524 }
525}
526
527
528
529/*! Checks if a file "baseName".md5 exists. If so the contents
530 * are compared with \a md5. If equal FALSE is returned. If the .md5
531 * file does not exist or its contents are not equal to \a md5,
532 * a new .md5 is generated with the \a md5 string as contents.
533 */
534static bool checkAndUpdateMd5Signature(const QCString &baseName,const QCString &md5)
535{
536 QFile f(baseName+".md5");
537 if (f.open(IO_ReadOnly))
538 {
539 // read checksum
540 QCString md5stored(33);
541 int bytesRead=f.readBlock(md5stored.data(),32);
542 md5stored[32]='\0';
543 // compare checksum
544 if (bytesRead==32 && md5==md5stored)
545 {
546 // bail out if equal
547 return FALSE;
548 }
549 }
550 f.close();
551 // create checksum file
552 if (f.open(IO_WriteOnly))
553 {
554 f.writeBlock(md5.data(),32);
555 f.close();
556 }
557 return TRUE;
558}
559
560static bool checkDeliverables(const QCString &file1,
561 const QCString &file2=QCString())
562{
563 bool file1Ok = TRUE;
564 bool file2Ok = TRUE;
565 if (!file1.isEmpty())
566 {
567 QFileInfo fi(file1);
568 file1Ok = (fi.exists() && fi.size()>0);
569 }
570 if (!file2.isEmpty())
571 {
572 QFileInfo fi(file2);
573 file2Ok = (fi.exists() && fi.size()>0);
574 }
575 return file1Ok && file2Ok;
576}
577
578//--------------------------------------------------------------------
579
580class DotNodeList : public QList<DotNode>
581{
582 public:
583 DotNodeList() : QList<DotNode>() {}
584 ~DotNodeList() {}
585 int compareItems(GCI item1,GCI item2)
586 {
587 return stricmp(((DotNode *)item1)->m_label,((DotNode *)item2)->m_label);
588 }
589};
590
591//--------------------------------------------------------------------
592
593DotRunner::DotRunner(const QCString &file,const QCString &path,
594 bool checkResult,const QCString &imageName)
595 : m_file(file), m_path(path),
596 m_checkResult(checkResult), m_imageName(imageName)
597{
598 static bool dotCleanUp = Config_getBool("DOT_CLEANUP");
599 m_cleanUp = dotCleanUp;
600 m_jobs.setAutoDelete(TRUE);
601}
602
603void DotRunner::addJob(const char *format,const char *output)
604{
605 QCString args = QCString("-T")+format+" -o \""+output+"\"";
606 m_jobs.append(new QCString(args));
607}
608
609void DotRunner::addPostProcessing(const char *cmd,const char *args)
610{
611 m_postCmd = cmd;
612 m_postArgs = args;
613}
614
615bool DotRunner::run()
616{
617 int exitCode=0;
618 static QCString dotExe = Config_getString("DOT_PATH")+"dot";
619 static bool multiTargets = Config_getBool("DOT_MULTI_TARGETS");
620 QCString dotArgs;
621 QListIterator<QCString> li(m_jobs);
622 QCString *s;
623 QCString file = m_file;
624 QCString path = m_path;
625 QCString imageName = m_imageName;
626 QCString postCmd = m_postCmd;
627 QCString postArgs = m_postArgs;
628 bool checkResult = m_checkResult;
629 bool cleanUp = m_cleanUp;
630 if (multiTargets)
631 {
632 dotArgs="\""+file+"\"";
633 for (li.toFirst();(s=li.current());++li)
634 {
635 dotArgs+=' ';
636 dotArgs+=*s;
637 }
638 if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
639 {
640 goto error;
641 }
642 }
643 else
644 {
645 for (li.toFirst();(s=li.current());++li)
646 {
647 dotArgs="\""+file+"\" "+*s;
648 if ((exitCode=portable_system(dotExe,dotArgs,FALSE))!=0)
649 {
650 goto error;
651 }
652 }
653 }
654 if (!postCmd.isEmpty() && portable_system(postCmd,postArgs)!=0)
655 {
656 err("error: Problems running '%s' as a post-processing step for dot output\n",m_postCmd.data());
657 return FALSE;
658 }
659 if (checkResult) checkDotResult(imageName);
660 if (cleanUp)
661 {
662 //printf("removing dot file %s\n",m_file.data());
663 //QDir(path).remove(file);
664 m_cleanupItem.file = file;
665 m_cleanupItem.path = path;
666 }
667 return TRUE;
668error:
669 err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n",
670 exitCode,dotExe.data(),dotArgs.data());
671 return FALSE;
672}
673
674//--------------------------------------------------------------------
675
676DotFilePatcher::DotFilePatcher(const char *patchFile)
677 : m_patchFile(patchFile)
678{
679 m_maps.setAutoDelete(TRUE);
680}
681
682int DotFilePatcher::addMap(const QCString &mapFile,const QCString &relPath,
683 bool urlOnly,const QCString &context,const QCString &label)
684{
685 int id = m_maps.count();
686 Map *map = new Map;
687 map->mapFile = mapFile;
688 map->relPath = relPath;
689 map->urlOnly = urlOnly;
690 map->context = context;
691 map->label = label;
692 m_maps.append(map);
693 return id;
694}
695
696int DotFilePatcher::addFigure(const QCString &baseName,
697 const QCString &figureName,bool heightCheck)
698{
699 int id = m_maps.count();
700 Map *map = new Map;
701 map->mapFile = figureName;
702 map->urlOnly = heightCheck;
703 map->label = baseName;
704 m_maps.append(map);
705 return id;
706}
707
708int DotFilePatcher::addSVGConversion(const QCString &relPath,bool urlOnly,
709 const QCString &context)
710{
711 int id = m_maps.count();
712 Map *map = new Map;
713 map->relPath = relPath;
714 map->urlOnly = urlOnly;
715 map->context = context;
716 m_maps.append(map);
717 return id;
718}
719
720int DotFilePatcher::addSVGObject(const QCString &baseName,
721 const QCString &absImgName,
722 const QCString &relPath)
723{
724 int id = m_maps.count();
725 Map *map = new Map;
726 map->mapFile = absImgName;
727 map->relPath = relPath;
728 map->label = baseName;
729 m_maps.append(map);
730 return id;
731}
732
733bool DotFilePatcher::run()
734{
735 //printf("DotFilePatcher::run(): %s\n",m_patchFile.data());
736 bool isSVGFile = m_patchFile.right(4)==".svg";
737 QCString tmpName = m_patchFile+".tmp";
738 if (!QDir::current().rename(m_patchFile,tmpName))
739 {
740 err("Failed to rename file %s to %s!\n",m_patchFile.data(),tmpName.data());
741 return FALSE;
742 }
743 QFile fi(tmpName);
744 QFile fo(m_patchFile);
745 if (!fi.open(IO_ReadOnly))
746 {
747 err("error: problem opening file %s for patching!\n",tmpName.data());
748 QDir::current().rename(tmpName,m_patchFile);
749 return FALSE;
750 }
751 if (!fo.open(IO_WriteOnly))
752 {
753 err("error: problem opening file %s for patching!\n",m_patchFile.data());
754 QDir::current().rename(tmpName,m_patchFile);
755 return FALSE;
756 }
757 FTextStream t(&fo);
758 const int maxLineLen=100*1024;
759 while (!fi.atEnd()) // foreach line
760 {
761 QCString line(maxLineLen);
762 int numBytes = fi.readLine(line.data(),maxLineLen);
763 //printf("line=[%s]\n",line.stripWhiteSpace().data());
764 int i;
765 ASSERT(numBytes<maxLineLen);
766 if (isSVGFile)
767 {
768 Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
769 t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
770 }
771 else if ((i=line.find("<!-- SVG"))!=-1 || (i=line.find("[!-- SVG"))!=-1)
772 {
773 //printf("Found marker at %d\n",i);
774 int mapId=-1;
775 t << line.left(i);
776 int n = sscanf(line.data()+i+1,"!-- SVG %d",&mapId);
777 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
778 {
779 int e = QMAX(line.find("--]"),line.find("-->"));
780 Map *map = m_maps.at(mapId);
781 if (!writeSVGFigureLink(t,map->relPath,map->label,map->mapFile))
782 {
783 err("Problem extracting size from SVG file %s\n",map->mapFile.data());
784 }
785 if (e!=-1) t << line.mid(e+3);
786 }
787 else // error invalid map id!
788 {
789 err("Found invalid SVG id in file %s!\n",m_patchFile.data());
790 t << line.mid(i);
791 }
792 }
793 else if ((i=line.find("<!-- MAP"))!=-1)
794 {
795 int mapId=-1;
796 t << line.left(i);
797 int n = sscanf(line.data()+i,"<!-- MAP %d",&mapId);
798 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
799 {
800 Map *map = m_maps.at(mapId);
801 //printf("patching MAP %d in file %s with contents of %s\n",
802 // mapId,m_patchFile.data(),map->mapFile.data());
803 t << "<map name=\"" << map->label << "\" id=\"" << map->label << "\">" << endl;
804 convertMapFile(t,map->mapFile,map->relPath,map->urlOnly,map->context);
805 t << "</map>" << endl;
806 }
807 else // error invalid map id!
808 {
809 err("Found invalid MAP id in file %s!\n",m_patchFile.data());
810 t << line.mid(i);
811 }
812 }
813 else if ((i=line.find("% FIG"))!=-1)
814 {
815 int mapId=-1;
816 int n = sscanf(line.data()+i+2,"FIG %d",&mapId);
817 //printf("line='%s' n=%d\n",line.data()+i,n);
818 if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
819 {
820 Map *map = m_maps.at(mapId);
821 //printf("patching FIG %d in file %s with contents of %s\n",
822 // mapId,m_patchFile.data(),map->mapFile.data());
823 writeVecGfxFigure(t,map->label,map->mapFile);
824 }
825 else // error invalid map id!
826 {
827 err("Found invalid bounding FIG id in file %s!\n",mapId,m_patchFile.data());
828 t << line;
829 }
830 }
831 else
832 {
833 t << line;
834 }
835 }
836 fi.close();
837 QDir::current().remove(tmpName);
838 return TRUE;
839}
840
841//--------------------------------------------------------------------
842
843void DotRunnerQueue::enqueue(DotRunner *runner)
844{
845 QMutexLocker locker(&m_mutex);
846 m_queue.enqueue(runner);
847 m_bufferNotEmpty.wakeAll();
848}
849
850DotRunner *DotRunnerQueue::dequeue()
851{
852 QMutexLocker locker(&m_mutex);
853 while (m_queue.isEmpty())
854 {
855 // wait until something is added to the queue
856 m_bufferNotEmpty.wait(&m_mutex);
857 }
858 DotRunner *result = m_queue.dequeue();
859 return result;
860}
861
862uint DotRunnerQueue::count() const
863{
864 QMutexLocker locker(&m_mutex);
865 return m_queue.count();
866}
867
868//--------------------------------------------------------------------
869
870DotWorkerThread::DotWorkerThread(int id,DotRunnerQueue *queue)
871 : m_id(id), m_queue(queue)
872{
873 m_cleanupItems.setAutoDelete(TRUE);
874}
875
876void DotWorkerThread::run()
877{
878 DotRunner *runner;
879 while ((runner=m_queue->dequeue()))
880 {
881 runner->run();
882 DotRunner::CleanupItem cleanup = runner->cleanup();
883 if (!cleanup.file.isEmpty())
884 {
885 m_cleanupItems.append(new DotRunner::CleanupItem(cleanup));
886 }
887 }
888}
889
890void DotWorkerThread::cleanup()
891{
892 QListIterator<DotRunner::CleanupItem> it(m_cleanupItems);
893 DotRunner::CleanupItem *ci;
894 for (;(ci=it.current());++it)
895 {
896 QDir(ci->path).remove(ci->file);
897 }
898}
899
900//--------------------------------------------------------------------
901
902DotManager *DotManager::m_theInstance = 0;
903
904DotManager *DotManager::instance()
905{
906 if (!m_theInstance)
907 {
908 m_theInstance = new DotManager;
909 }
910 return m_theInstance;
911}
912
913DotManager::DotManager() : m_dotMaps(1007)
914{
915 m_dotRuns.setAutoDelete(TRUE);
916 m_dotMaps.setAutoDelete(TRUE);
917 m_queue = new DotRunnerQueue;
918 int i;
919 int numThreads = QMIN(32,Config_getInt("DOT_NUM_THREADS"));
920 if (numThreads==0) numThreads = QMAX(1,QThread::idealThreadCount()+1);
921 for (i=0;i<numThreads;i++)
922 {
923 DotWorkerThread *thread = new DotWorkerThread(i,m_queue);
924 thread->start();
925 if (thread->isRunning())
926 {
927 m_workers.append(thread);
928 }
929 else // no more threads available!
930 {
931 delete thread;
932 }
933 }
934 ASSERT(m_workers.count()>0);
935}
936
937DotManager::~DotManager()
938{
939 delete m_queue;
940}
941
942void DotManager::addRun(DotRunner *run)
943{
944 m_dotRuns.append(run);
945}
946
947int DotManager::addMap(const QCString &file,const QCString &mapFile,
948 const QCString &relPath,bool urlOnly,const QCString &context,
949 const QCString &label)
950{
951 DotFilePatcher *map = m_dotMaps.find(file);
952 if (map==0)
953 {
954 map = new DotFilePatcher(file);
955 m_dotMaps.append(file,map);
956 }
957 return map->addMap(mapFile,relPath,urlOnly,context,label);
958}
959
960int DotManager::addFigure(const QCString &file,const QCString &baseName,
961 const QCString &figureName,bool heightCheck)
962{
963 DotFilePatcher *map = m_dotMaps.find(file);
964 if (map==0)
965 {
966 map = new DotFilePatcher(file);
967 m_dotMaps.append(file,map);
968 }
969 return map->addFigure(baseName,figureName,heightCheck);
970}
971
972int DotManager::addSVGConversion(const QCString &file,const QCString &relPath,
973 bool urlOnly,const QCString &context)
974{
975 DotFilePatcher *map = m_dotMaps.find(file);
976 if (map==0)
977 {
978 map = new DotFilePatcher(file);
979 m_dotMaps.append(file,map);
980 }
981 return map->addSVGConversion(relPath,urlOnly,context);
982}
983
984int DotManager::addSVGObject(const QCString &file,const QCString &baseName,
985 const QCString &absImgName,const QCString &relPath)
986{
987 DotFilePatcher *map = m_dotMaps.find(file);
988 if (map==0)
989 {
990 map = new DotFilePatcher(file);
991 m_dotMaps.append(file,map);
992 }
993 return map->addSVGObject(baseName,absImgName,relPath);
994}
995
996bool DotManager::run()
997{
998 uint numDotRuns = m_dotRuns.count();
999 uint numDotMaps = m_dotMaps.count();
1000 if (numDotRuns+numDotMaps>1)
1001 {
1002 msg("Generating dot graphs using %d parallel threads...\n",QMIN(numDotRuns+numDotMaps,m_workers.count()));
1003 }
1004 int i=1;
1005 QListIterator<DotRunner> li(m_dotRuns);
1006
1007 bool setPath=FALSE;
1008 if (Config_getBool("GENERATE_HTML"))
1009 {
1010 setDotFontPath(Config_getString("HTML_OUTPUT"));
1011 setPath=TRUE;
1012 }
1013 else if (Config_getBool("GENERATE_LATEX"))
1014 {
1015 setDotFontPath(Config_getString("LATEX_OUTPUT"));
1016 setPath=TRUE;
1017 }
1018 else if (Config_getBool("GENERATE_RTF"))
1019 {
1020 setDotFontPath(Config_getString("RTF_OUTPUT"));
1021 setPath=TRUE;
1022 }
1023 portable_sysTimerStart();
1024 // fill work queue with dot operations
1025 DotRunner *dr;
1026 for (li.toFirst();(dr=li.current());++li)
1027 {
1028 m_queue->enqueue(dr);
1029 }
1030 int prev=1;
1031 // wait for the queue to become empty
1032 while ((i=m_queue->count())>0)
1033 {
1034 i = numDotRuns - i;
1035 while (i>=prev)
1036 {
1037 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1038 prev++;
1039 }
1040 portable_sleep(100);
1041 }
1042 while ((int)numDotRuns>=prev)
1043 {
1044 msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1045 prev++;
1046 }
1047 // signal the workers we are done
1048 for (i=0;i<(int)m_workers.count();i++)
1049 {
1050 m_queue->enqueue(0); // add terminator for each worker
1051 }
1052 // wait for the workers to finish
1053 for (i=0;i<(int)m_workers.count();i++)
1054 {
1055 m_workers.at(i)->wait();
1056 }
1057 // clean up dot files from main thread
1058 for (i=0;i<(int)m_workers.count();i++)
1059 {
1060 m_workers.at(i)->cleanup();
1061 }
1062 portable_sysTimerStop();
1063 if (setPath)
1064 {
1065 unsetDotFontPath();
1066 }
1067
1068 // patch the output file and insert the maps and figures
1069 i=1;
1070 SDict<DotFilePatcher>::Iterator di(m_dotMaps);
1071 DotFilePatcher *map;
1072 for (di.toFirst();(map=di.current());++di)
1073 {
1074 msg("Patching output file %d/%d\n",i,numDotMaps);
1075 if (!map->run()) return FALSE;
1076 i++;
1077 }
1078 return TRUE;
1079}
1080
1081
1082//--------------------------------------------------------------------
1083
1084
1085/*! helper function that deletes all nodes in a connected graph, given
1086 * one of the graph's nodes
1087 */
1088static void deleteNodes(DotNode *node,SDict<DotNode> *skipNodes=0)
1089{
1090 //printf("deleteNodes skipNodes=%p\n",skipNodes);
1091 static DotNodeList deletedNodes;
1092 deletedNodes.setAutoDelete(TRUE);
1093 node->deleteNode(deletedNodes,skipNodes); // collect nodes to be deleted.
1094 deletedNodes.clear(); // actually remove the nodes.
1095}
1096
1097DotNode::DotNode(int n,const char *lab,const char *tip, const char *url,
1098 bool isRoot,ClassDef *cd)
1099 : m_subgraphId(-1)
1100 , m_number(n)
1101 , m_label(lab)
1102 , m_tooltip(tip)
1103 , m_url(url)
1104 , m_parents(0)
1105 , m_children(0)
1106 , m_edgeInfo(0)
1107 , m_deleted(FALSE)
1108 , m_written(FALSE)
1109 , m_hasDoc(FALSE)
1110 , m_isRoot(isRoot)
1111 , m_classDef(cd)
1112 , m_visible(FALSE)
1113 , m_truncated(Unknown)
1114 , m_distance(1000)
1115{
1116}
1117
1118DotNode::~DotNode()
1119{
1120 delete m_children;
1121 delete m_parents;
1122 delete m_edgeInfo;
1123}
1124
1125void DotNode::addChild(DotNode *n,
1126 int edgeColor,
1127 int edgeStyle,
1128 const char *edgeLab,
1129 const char *edgeURL,
1130 int edgeLabCol
1131 )
1132{
1133 if (m_children==0)
1134 {
1135 m_children = new QList<DotNode>;
1136 m_edgeInfo = new QList<EdgeInfo>;
1137 m_edgeInfo->setAutoDelete(TRUE);
1138 }
1139 m_children->append(n);
1140 EdgeInfo *ei = new EdgeInfo;
1141 ei->m_color = edgeColor;
1142 ei->m_style = edgeStyle;
1143 ei->m_label = edgeLab;
1144 ei->m_url = edgeURL;
1145 if (edgeLabCol==-1)
1146 ei->m_labColor=edgeColor;
1147 else
1148 ei->m_labColor=edgeLabCol;
1149 m_edgeInfo->append(ei);
1150}
1151
1152void DotNode::addParent(DotNode *n)
1153{
1154 if (m_parents==0)
1155 {
1156 m_parents = new QList<DotNode>;
1157 }
1158 m_parents->append(n);
1159}
1160
1161void DotNode::removeChild(DotNode *n)
1162{
1163 if (m_children) m_children->remove(n);
1164}
1165
1166void DotNode::removeParent(DotNode *n)
1167{
1168 if (m_parents) m_parents->remove(n);
1169}
1170
1171void DotNode::deleteNode(DotNodeList &deletedList,SDict<DotNode> *skipNodes)
1172{
1173 if (m_deleted) return; // avoid recursive loops in case the graph has cycles
1174 m_deleted=TRUE;
1175 if (m_parents!=0) // delete all parent nodes of this node
1176 {
1177 QListIterator<DotNode> dnlip(*m_parents);
1178 DotNode *pn;
1179 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
1180 {
1181 //pn->removeChild(this);
1182 pn->deleteNode(deletedList,skipNodes);
1183 }
1184 }
1185 if (m_children!=0) // delete all child nodes of this node
1186 {
1187 QListIterator<DotNode> dnlic(*m_children);
1188 DotNode *cn;
1189 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
1190 {
1191 //cn->removeParent(this);
1192 cn->deleteNode(deletedList,skipNodes);
1193 }
1194 }
1195 // add this node to the list of deleted nodes.
1196 //printf("skipNodes=%p find(%p)=%p\n",skipNodes,this,skipNodes ? skipNodes->find((int)this) : 0);
1197 if (skipNodes==0 || skipNodes->find((char*)this)==0)
1198 {
1199 //printf("deleting\n");
1200 deletedList.append(this);
1201 }
1202}
1203
1204void DotNode::setDistance(int distance)
1205{
1206 if (distance<m_distance) m_distance = distance;
1207}
1208
1209static QCString convertLabel(const QCString &l)
1210{
1211 QCString result;
1212 const char *p=l.data();
1213 if (p==0) return result;
1214 char c;
1215 while ((c=*p++))
1216 {
1217 switch(c)
1218 {
1219 case '\\': result+="\\\\"; break;
1220 case '\n': result+="\\n"; break;
1221 case '<': result+="\\<"; break;
1222 case '>': result+="\\>"; break;
1223 case '|': result+="\\|"; break;
1224 case '{': result+="\\{"; break;
1225 case '}': result+="\\}"; break;
1226 case '"': result+="\\\""; break;
1227 default: result+=c; break;
1228 }
1229 }
1230 return result;
1231}
1232
1233#if 0
1234static QCString escapeTooltip(const QCString &tooltip)
1235{
1236 QCString result;
1237 const char *p=tooltip.data();
1238 if (p==0) return result;
1239 char c;
1240 while ((c=*p++))
1241 {
1242 switch(c)
1243 {
1244 case '\\': result+="\\\\"; break;
1245 default: result+=c; break;
1246 }
1247 }
1248 return result;
1249}
1250#endif
1251
1252static void writeBoxMemberList(FTextStream &t,char prot,MemberList *ml,ClassDef *scope)
1253{
1254 if (ml)
1255 {
1256 MemberListIterator mlia(*ml);
1257 MemberDef *mma;
1258 for (mlia.toFirst();(mma = mlia.current());++mlia)
1259 {
1260 if (mma->getClassDef() == scope)
1261 {
1262 t << prot << " " << convertLabel(mma->name());
1263 if (!mma->isObjCMethod() &&
1264 (mma->isFunction() || mma->isSlot() || mma->isSignal())) t << "()";
1265 t << "\\l";
1266 }
1267 }
1268 // write member groups within the memberlist
1269 MemberGroupList *mgl = ml->getMemberGroupList();
1270 if (mgl)
1271 {
1272 MemberGroupListIterator mgli(*mgl);
1273 MemberGroup *mg;
1274 for (mgli.toFirst();(mg=mgli.current());++mgli)
1275 {
1276 if (mg->members())
1277 {
1278 writeBoxMemberList(t,prot,mg->members(),scope);
1279 }
1280 }
1281 }
1282 }
1283}
1284
1285void DotNode::writeBox(FTextStream &t,
1286 GraphType gt,
1287 GraphOutputFormat /*format*/,
1288 bool hasNonReachableChildren,
1289 bool reNumber)
1290{
1291 const char *labCol =
1292 m_url.isEmpty() ? "grey75" : // non link
1293 (
1294 (hasNonReachableChildren) ? "red" : "black"
1295 );
1296 t << " Node" << reNumberNode(m_number,reNumber) << " [label=\"";
1297
1298 if (m_classDef && Config_getBool("UML_LOOK") &&
1299 (gt==Inheritance || gt==Collaboration))
1300 {
1301 t << "{" << convertLabel(m_label);
1302 t << "\\n|";
1303 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubAttribs),m_classDef);
1304 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticAttribs),m_classDef);
1305 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::properties),m_classDef);
1306 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacAttribs),m_classDef);
1307 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticAttribs),m_classDef);
1308 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proAttribs),m_classDef);
1309 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticAttribs),m_classDef);
1310 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priAttribs),m_classDef);
1311 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticAttribs),m_classDef);
1312 t << "|";
1313 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubMethods),m_classDef);
1314 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubStaticMethods),m_classDef);
1315 writeBoxMemberList(t,'+',m_classDef->getMemberList(MemberList::pubSlots),m_classDef);
1316 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacMethods),m_classDef);
1317 writeBoxMemberList(t,'~',m_classDef->getMemberList(MemberList::pacStaticMethods),m_classDef);
1318 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proMethods),m_classDef);
1319 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proStaticMethods),m_classDef);
1320 writeBoxMemberList(t,'#',m_classDef->getMemberList(MemberList::proSlots),m_classDef);
1321 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priMethods),m_classDef);
1322 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priStaticMethods),m_classDef);
1323 writeBoxMemberList(t,'-',m_classDef->getMemberList(MemberList::priSlots),m_classDef);
1324 if (m_classDef->getMemberGroupSDict())
1325 {
1326 MemberGroupSDict::Iterator mgdi(*m_classDef->getMemberGroupSDict());
1327 MemberGroup *mg;
1328 for (mgdi.toFirst();(mg=mgdi.current());++mgdi)
1329 {
1330 if (mg->members())
1331 {
1332 writeBoxMemberList(t,'*',mg->members(),m_classDef);
1333 }
1334 }
1335 }
1336 t << "}";
1337 }
1338 else // standard look
1339 {
1340 t << convertLabel(m_label);
1341 }
1342 t << "\",height=0.2,width=0.4";
1343 if (m_isRoot)
1344 {
1345 t << ",color=\"black\", fillcolor=\"grey75\", style=\"filled\" fontcolor=\"black\"";
1346 }
1347 else
1348 {
1349 static bool dotTransparent = Config_getBool("DOT_TRANSPARENT");
1350 static bool vhdlOpt = Config_getBool("OPTIMIZE_OUTPUT_VHDL");
1351 if (!dotTransparent)
1352 {
1353 ClassDef* ccd=this->m_classDef;
1354
1355 t << ",color=\"" << labCol << "\", fillcolor=\"";
1356 if (ccd && vhdlOpt && (VhdlDocGen::VhdlClasses)ccd->protection()==VhdlDocGen::ARCHITECTURECLASS)
1357 t << "khaki";
1358 else
1359 t << "white";
1360 t << "\", style=\"filled\"";
1361 }
1362 else
1363 {
1364 t << ",color=\"" << labCol << "\"";
1365 }
1366 if (!m_url.isEmpty())
1367 {
1368 int anchorPos = m_url.findRev('#');
1369 if (anchorPos==-1)
1370 {
1371 t << ",URL=\"" << m_url << Doxygen::htmlFileExtension << "\"";
1372 }
1373 else
1374 {
1375 t << ",URL=\"" << m_url.left(anchorPos) << Doxygen::htmlFileExtension
1376 << m_url.right(m_url.length()-anchorPos) << "\"";
1377 }
1378 }
1379 if (!m_tooltip.isEmpty())
1380 {
1381 t << ",tooltip=\"" << /*escapeTooltip(m_tooltip)*/ m_tooltip << "\"";
1382 }
1383 }
1384 t << "];" << endl;
1385}
1386
1387void DotNode::writeArrow(FTextStream &t,
1388 GraphType gt,
1389 GraphOutputFormat format,
1390 DotNode *cn,
1391 EdgeInfo *ei,
1392 bool topDown,
1393 bool pointBack,
1394 bool reNumber
1395 )
1396{
1397 t << " Node";
1398 if (topDown)
1399 t << reNumberNode(cn->number(),reNumber);
1400 else
1401 t << reNumberNode(m_number,reNumber);
1402 t << " -> Node";
1403 if (topDown)
1404 t << reNumberNode(m_number,reNumber);
1405 else
1406 t << reNumberNode(cn->number(),reNumber);
1407 t << " [";
1408 if (pointBack) t << "dir=back,";
1409 t << "color=\"" << edgeColorMap[ei->m_color]
1410 << "\",fontsize=\"" << FONTSIZE << "\",style=\"" << edgeStyleMap[ei->m_style] << "\"";
1411 if (!ei->m_label.isEmpty())
1412 {
1413 t << ",label=\"" << convertLabel(ei->m_label) << "\"";
1414 }
1415 if (Config_getBool("UML_LOOK") &&
1416 arrowStyle[ei->m_color] &&
1417 (gt==Inheritance || gt==Collaboration)
1418 )
1419 {
1420 if (pointBack)
1421 t << ",arrowtail=\"" << arrowStyle[ei->m_color] << "\"";
1422 else
1423 t << ",arrowhead=\"" << arrowStyle[ei->m_color] << "\"";
1424 }
1425
1426 if (format==BITMAP) t << ",fontname=\"" << FONTNAME << "\"";
1427 t << "];" << endl;
1428}
1429
1430void DotNode::write(FTextStream &t,
1431 GraphType gt,
1432 GraphOutputFormat format,
1433 bool topDown,
1434 bool toChildren,
1435 bool backArrows,
1436 bool reNumber
1437 )
1438{
1439 //printf("DotNode::write(%d) name=%s this=%p written=%d\n",distance,m_label.data(),this,m_written);
1440 if (m_written) return; // node already written to the output
1441 if (!m_visible) return; // node is not visible
1442 writeBox(t,gt,format,m_truncated==Truncated,reNumber);
1443 m_written=TRUE;
1444 QList<DotNode> *nl = toChildren ? m_children : m_parents;
1445 if (nl)
1446 {
1447 if (toChildren)
1448 {
1449 QListIterator<DotNode> dnli1(*nl);
1450 QListIterator<EdgeInfo> dnli2(*m_edgeInfo);
1451 DotNode *cn;
1452 for (dnli1.toFirst();(cn=dnli1.current());++dnli1,++dnli2)
1453 {
1454 if (cn->isVisible())
1455 {
1456 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",cn->label().data());
1457 writeArrow(t,gt,format,cn,dnli2.current(),topDown,backArrows,reNumber);
1458 }
1459 cn->write(t,gt,format,topDown,toChildren,backArrows,reNumber);
1460 }
1461 }
1462 else // render parents
1463 {
1464 QListIterator<DotNode> dnli(*nl);
1465 DotNode *pn;
1466 for (dnli.toFirst();(pn=dnli.current());++dnli)
1467 {
1468 if (pn->isVisible())
1469 {
1470 //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",pn->label().data());
1471 writeArrow(t,
1472 gt,
1473 format,
1474 pn,
1475 pn->m_edgeInfo->at(pn->m_children->findRef(this)),
1476 FALSE,
1477 backArrows,
1478 reNumber
1479 );
1480 }
1481 pn->write(t,gt,format,TRUE,FALSE,backArrows,reNumber);
1482 }
1483 }
1484 }
1485 //printf("end DotNode::write(%d) name=%s\n",distance,m_label.data());
1486}
1487
1488void DotNode::writeXML(FTextStream &t,bool isClassGraph)
1489{
1490 t << " <node id=\"" << m_number << "\">" << endl;
1491 t << " <label>" << convertToXML(m_label) << "</label>" << endl;
1492 if (!m_url.isEmpty())
1493 {
1494 QCString url(m_url);
1495 char *refPtr = url.data();
1496 char *urlPtr = strchr(url.data(),'$');
1497 if (urlPtr)
1498 {
1499 *urlPtr++='\0';
1500 t << " <link refid=\"" << convertToXML(urlPtr) << "\"";
1501 if (*refPtr!='\0')
1502 {
1503 t << " external=\"" << convertToXML(refPtr) << "\"";
1504 }
1505 t << "/>" << endl;
1506 }
1507 }
1508 if (m_children)
1509 {
1510 QListIterator<DotNode> nli(*m_children);
1511 QListIterator<EdgeInfo> eli(*m_edgeInfo);
1512 DotNode *childNode;
1513 EdgeInfo *edgeInfo;
1514 for (;(childNode=nli.current());++nli,++eli)
1515 {
1516 edgeInfo=eli.current();
1517 t << " <childnode refid=\"" << childNode->m_number << "\" relation=\"";
1518 if (isClassGraph)
1519 {
1520 switch(edgeInfo->m_color)
1521 {
1522 case EdgeInfo::Blue: t << "public-inheritance"; break;
1523 case EdgeInfo::Green: t << "protected-inheritance"; break;
1524 case EdgeInfo::Red: t << "private-inheritance"; break;
1525 case EdgeInfo::Purple: t << "usage"; break;
1526 case EdgeInfo::Orange: t << "template-instance"; break;
1527 case EdgeInfo::Grey: ASSERT(0); break;
1528 }
1529 }
1530 else // include graph
1531 {
1532 t << "include";
1533 }
1534 t << "\">" << endl;
1535 if (!edgeInfo->m_label.isEmpty())
1536 {
1537 int p=0;
1538 int ni;
1539 while ((ni=edgeInfo->m_label.find('\n',p))!=-1)
1540 {
1541 t << " <edgelabel>"
1542 << convertToXML(edgeInfo->m_label.mid(p,ni-p))
1543 << "</edgelabel>" << endl;
1544 p=ni+1;
1545 }
1546 t << " <edgelabel>"
1547 << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p))
1548 << "</edgelabel>" << endl;
1549 }
1550 t << " </childnode>" << endl;
1551 }
1552 }
1553 t << " </node>" << endl;
1554}
1555
1556
1557void DotNode::writeDEF(FTextStream &t)
1558{
1559 const char* nodePrefix = " node-";
1560
1561 t << " node = {" << endl;
1562 t << nodePrefix << "id = " << m_number << ';' << endl;
1563 t << nodePrefix << "label = '" << m_label << "';" << endl;
1564
1565 if (!m_url.isEmpty())
1566 {
1567 QCString url(m_url);
1568 char *refPtr = url.data();
1569 char *urlPtr = strchr(url.data(),'$');
1570 if (urlPtr)
1571 {
1572 *urlPtr++='\0';
1573 t << nodePrefix << "link = {" << endl << " "
1574 << nodePrefix << "link-id = '" << urlPtr << "';" << endl;
1575
1576 if (*refPtr!='\0')
1577 {
1578 t << " " << nodePrefix << "link-external = '"
1579 << refPtr << "';" << endl;
1580 }
1581 t << " };" << endl;
1582 }
1583 }
1584 if (m_children)
1585 {
1586 QListIterator<DotNode> nli(*m_children);
1587 QListIterator<EdgeInfo> eli(*m_edgeInfo);
1588 DotNode *childNode;
1589 EdgeInfo *edgeInfo;
1590 for (;(childNode=nli.current());++nli,++eli)
1591 {
1592 edgeInfo=eli.current();
1593 t << " node-child = {" << endl;
1594 t << " child-id = '" << childNode->m_number << "';" << endl;
1595 t << " relation = ";
1596
1597 switch(edgeInfo->m_color)
1598 {
1599 case EdgeInfo::Blue: t << "public-inheritance"; break;
1600 case EdgeInfo::Green: t << "protected-inheritance"; break;
1601 case EdgeInfo::Red: t << "private-inheritance"; break;
1602 case EdgeInfo::Purple: t << "usage"; break;
1603 case EdgeInfo::Orange: t << "template-instance"; break;
1604 case EdgeInfo::Grey: ASSERT(0); break;
1605 }
1606 t << ';' << endl;
1607
1608 if (!edgeInfo->m_label.isEmpty())
1609 {
1610 t << " edgelabel = <<_EnD_oF_dEf_TeXt_" << endl
1611 << edgeInfo->m_label << endl
1612 << "_EnD_oF_dEf_TeXt_;" << endl;
1613 }
1614 t << " }; /* node-child */" << endl;
1615 } /* for (;childNode...) */
1616 }
1617 t << " }; /* node */" << endl;
1618}
1619
1620
1621void DotNode::clearWriteFlag()
1622{
1623 m_written=FALSE;
1624 if (m_parents!=0)
1625 {
1626 QListIterator<DotNode> dnlip(*m_parents);
1627 DotNode *pn;
1628 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
1629 {
1630 if (pn->m_written)
1631 {
1632 pn->clearWriteFlag();
1633 }
1634 }
1635 }
1636 if (m_children!=0)
1637 {
1638 QListIterator<DotNode> dnlic(*m_children);
1639 DotNode *cn;
1640 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
1641 {
1642 if (cn->m_written)
1643 {
1644 cn->clearWriteFlag();
1645 }
1646 }
1647 }
1648}
1649
1650void DotNode::colorConnectedNodes(int curColor)
1651{
1652 if (m_children)
1653 {
1654 QListIterator<DotNode> dnlic(*m_children);
1655 DotNode *cn;
1656 for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
1657 {
1658 if (cn->m_subgraphId==-1) // uncolored child node
1659 {
1660 cn->m_subgraphId=curColor;
1661 cn->markAsVisible();
1662 cn->colorConnectedNodes(curColor);
1663 //printf("coloring node %s (%p): %d\n",cn->m_label.data(),cn,cn->m_subgraphId);
1664 }
1665 }
1666 }
1667
1668 if (m_parents)
1669 {
1670 QListIterator<DotNode> dnlip(*m_parents);
1671 DotNode *pn;
1672 for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
1673 {
1674 if (pn->m_subgraphId==-1) // uncolored parent node
1675 {
1676 pn->m_subgraphId=curColor;
1677 pn->markAsVisible();
1678 pn->colorConnectedNodes(curColor);
1679 //printf("coloring node %s (%p): %d\n",pn->m_label.data(),pn,pn->m_subgraphId);
1680 }
1681 }
1682 }
1683}
1684
1685const DotNode *DotNode::findDocNode() const
1686{
1687 if (!m_url.isEmpty()) return this;
1688 //printf("findDocNode(): `%s'\n",m_label.data());
1689 if (m_parents)
1690 {
1691 QListIterator<DotNode> dnli(*m_parents);
1692 DotNode *pn;
1693 for (dnli.toFirst();(pn=dnli.current());++dnli)
1694 {
1695 if (!pn->m_hasDoc)
1696 {
1697 pn->m_hasDoc=TRUE;
1698 const DotNode *dn = pn->findDocNode();
1699 if (dn) return dn;
1700 }
1701 }
1702 }
1703 if (m_children)
1704 {
1705 QListIterator<DotNode> dnli(*m_children);
1706 DotNode *cn;
1707 for (dnli.toFirst();(cn=dnli.current());++dnli)
1708 {
1709 if (!cn->m_hasDoc)
1710 {
1711 cn->m_hasDoc=TRUE;
1712 const DotNode *dn = cn->findDocNode();
1713 if (dn) return dn;
1714 }
1715 }
1716 }
1717 return 0;
1718}
1719
1720//--------------------------------------------------------------------
1721
1722int DotGfxHierarchyTable::m_curNodeNumber;
1723
1724void DotGfxHierarchyTable::writeGraph(FTextStream &out,
1725 const char *path,const char *fileName) const
1726{
1727 //printf("DotGfxHierarchyTable::writeGraph(%s)\n",name);
1728 //printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count());
1729 if (m_rootSubgraphs->count()==0) return;
1730
1731 QDir d(path);
1732 // store the original directory
1733 if (!d.exists())
1734 {
1735 err("error: Output dir %s does not exist!\n",path); exit(1);
1736 }
1737
1738 // put each connected subgraph of the hierarchy in a row of the HTML output
1739 out << "<table border=\"0\" cellspacing=\"10\" cellpadding=\"0\">" << endl;
1740
1741 QListIterator<DotNode> dnli(*m_rootSubgraphs);
1742 DotNode *n;
1743 int count=0;
1744 for (dnli.toFirst();(n=dnli.current());++dnli)
1745 {
1746 QCString baseName;
1747 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
1748 baseName.sprintf("inherit_graph_%d",count++);
1749 //baseName = convertNameToFile(baseName);
1750 QCString imgName = baseName+"."+ imgExt;
1751 QCString mapName = baseName+".map";
1752 QCString absImgName = QCString(d.absPath().data())+"/"+imgName;
1753 QCString absMapName = QCString(d.absPath().data())+"/"+mapName;
1754 QCString absBaseName = QCString(d.absPath().data())+"/"+baseName;
1755 QListIterator<DotNode> dnli2(*m_rootNodes);
1756 DotNode *node;
1757
1758 // compute md5 checksum of the graph were are about to generate
1759 QGString theGraph;
1760 FTextStream md5stream(&theGraph);
1761 writeGraphHeader(md5stream);
1762 md5stream << " rankdir=LR;" << endl;
1763 for (dnli2.toFirst();(node=dnli2.current());++dnli2)
1764 {
1765 if (node->m_subgraphId==n->m_subgraphId)
1766 {
1767 node->clearWriteFlag();
1768 }
1769 }
1770 for (dnli2.toFirst();(node=dnli2.current());++dnli2)
1771 {
1772 if (node->m_subgraphId==n->m_subgraphId)
1773 {
1774 node->write(md5stream,DotNode::Hierarchy,BITMAP,FALSE,TRUE,TRUE,TRUE);
1775 }
1776 }
1777 writeGraphFooter(md5stream);
1778 resetReNumbering();
1779 uchar md5_sig[16];
1780 QCString sigStr(33);
1781 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
1782 MD5SigToString(md5_sig,sigStr.data(),33);
1783 bool regenerate=FALSE;
1784 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
1785 !checkDeliverables(absImgName,absMapName))
1786 {
1787 regenerate=TRUE;
1788 // image was new or has changed
1789 QCString dotName=absBaseName+".dot";
1790 QFile f(dotName);
1791 if (!f.open(IO_WriteOnly)) return;
1792 FTextStream t(&f);
1793 t << theGraph;
1794 f.close();
1795 resetReNumbering();
1796
1797 DotRunner *dotRun = new DotRunner(dotName,d.absPath().data(),TRUE,absImgName);
1798 dotRun->addJob(imgExt,absImgName);
1799 dotRun->addJob(MAP_CMD,absMapName);
1800 DotManager::instance()->addRun(dotRun);
1801 }
1802 else
1803 {
1804 removeDotGraph(absBaseName+".dot");
1805 }
1806 Doxygen::indexList.addImageFile(imgName);
1807 // write image and map in a table row
1808 QCString mapLabel = escapeCharsInString(n->m_label,FALSE);
1809 out << "<tr><td>";
1810 if (imgExt=="svg") // vector graphics
1811 {
1812 if (regenerate || !writeSVGFigureLink(out,QCString(),baseName,absImgName))
1813 {
1814 if (regenerate)
1815 {
1816 DotManager::instance()->addSVGConversion(absImgName,QCString(),
1817 FALSE,QCString());
1818 }
1819 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,
1820 absImgName,QCString());
1821 out << "<!-- SVG " << mapId << " -->" << endl;
1822 }
1823 }
1824 else // normal bitmap
1825 {
1826 out << "<img src=\"" << imgName << "\" border=\"0\" alt=\"\" usemap=\"#"
1827 << mapLabel << "\"/>" << endl;
1828
1829 if (regenerate || !insertMapFile(out,absMapName,QCString(),mapLabel))
1830 {
1831 int mapId = DotManager::instance()->addMap(fileName,absMapName,QCString(),
1832 FALSE,QCString(),mapLabel);
1833 out << "<!-- MAP " << mapId << " -->" << endl;
1834 }
1835 }
1836
1837 out << "</td></tr>" << endl;
1838 }
1839 out << "</table>" << endl;
1840}
1841
1842void DotGfxHierarchyTable::addHierarchy(DotNode *n,ClassDef *cd,bool hideSuper)
1843{
1844 //printf("addHierarchy `%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count());
1845 if (cd->subClasses())
1846 {
1847 BaseClassListIterator bcli(*cd->subClasses());
1848 BaseClassDef *bcd;
1849 for ( ; (bcd=bcli.current()) ; ++bcli )
1850 {
1851 ClassDef *bClass=bcd->classDef;
1852 //printf(" Trying sub class=`%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count());
1853 if (bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses()))
1854 {
1855 DotNode *bn;
1856 //printf(" Node `%s' Found visible class=`%s'\n",n->m_label.data(),
1857 // bClass->name().data());
1858 if ((bn=m_usedNodes->find(bClass->name()))) // node already present
1859 {
1860 if (n->m_children==0 || n->m_children->findRef(bn)==-1) // no arrow yet
1861 {
1862 n->addChild(bn,bcd->prot);
1863 bn->addParent(n);
1864 //printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n",
1865 // n->m_label.data(),
1866 // bn->m_label.data(),
1867 // bn->m_children ? bn->m_children->count() : 0,
1868 // bn->m_parents ? bn->m_parents->count() : 0
1869 // );
1870 }
1871 //else
1872 //{
1873 // printf(" Class already has an arrow!\n");
1874 //}
1875 }
1876 else
1877 {
1878 QCString tmp_url="";
1879 if (bClass->isLinkable() && !bClass->isHidden())
1880 {
1881 tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase();
1882 if (!bClass->anchor().isEmpty())
1883 {
1884 tmp_url+="#"+bClass->anchor();
1885 }
1886 }
1887 QCString tooltip = bClass->briefDescriptionAsTooltip();
1888 bn = new DotNode(m_curNodeNumber++,
1889 bClass->displayName(),
1890 tooltip,
1891 tmp_url.data()
1892 );
1893 n->addChild(bn,bcd->prot);
1894 bn->addParent(n);
1895 //printf(" Adding node %s to new base node %s (c=%d,p=%d)\n",
1896 // n->m_label.data(),
1897 // bn->m_label.data(),
1898 // bn->m_children ? bn->m_children->count() : 0,
1899 // bn->m_parents ? bn->m_parents->count() : 0
1900 // );
1901 //printf(" inserting %s (%p)\n",bClass->name().data(),bn);
1902 m_usedNodes->insert(bClass->name(),bn); // add node to the used list
1903 }
1904 if (!bClass->visited && !hideSuper && bClass->subClasses())
1905 {
1906 bool wasVisited=bClass->visited;
1907 bClass->visited=TRUE;
1908 addHierarchy(bn,bClass,wasVisited);
1909 }
1910 }
1911 }
1912 }
1913 //printf("end addHierarchy\n");
1914}
1915
1916void DotGfxHierarchyTable::addClassList(ClassSDict *cl)
1917{
1918 ClassSDict::Iterator cli(*cl);
1919 ClassDef *cd;
1920 for (cli.toLast();(cd=cli.current());--cli)
1921 {
1922 //printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count());
1923 if (!hasVisibleRoot(cd->baseClasses()) &&
1924 cd->isVisibleInHierarchy()
1925 ) // root node in the forest
1926 {
1927 QCString tmp_url="";
1928 if (cd->isLinkable() && !cd->isHidden())
1929 {
1930 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
1931 if (!cd->anchor().isEmpty())
1932 {
1933 tmp_url+="#"+cd->anchor();
1934 }
1935 }
1936 //printf("Inserting root class %s\n",cd->name().data());
1937 QCString tooltip = cd->briefDescriptionAsTooltip();
1938 DotNode *n = new DotNode(m_curNodeNumber++,
1939 cd->displayName(),
1940 tooltip,
1941 tmp_url.data());
1942
1943 //m_usedNodes->clear();
1944 m_usedNodes->insert(cd->name(),n);
1945 m_rootNodes->insert(0,n);
1946 if (!cd->visited && cd->subClasses())
1947 {
1948 addHierarchy(n,cd,cd->visited);
1949 cd->visited=TRUE;
1950 }
1951 }
1952 }
1953}
1954
1955DotGfxHierarchyTable::DotGfxHierarchyTable()
1956{
1957 m_curNodeNumber=0;
1958 m_rootNodes = new QList<DotNode>;
1959 m_usedNodes = new QDict<DotNode>(1009);
1960 m_usedNodes->setAutoDelete(TRUE);
1961 m_rootSubgraphs = new DotNodeList;
1962
1963 // build a graph with each class as a node and the inheritance relations
1964 // as edges
1965 initClassHierarchy(Doxygen::classSDict);
1966 initClassHierarchy(Doxygen::hiddenClasses);
1967 addClassList(Doxygen::classSDict);
1968 addClassList(Doxygen::hiddenClasses);
1969 // m_usedNodes now contains all nodes in the graph
1970
1971 // color the graph into a set of independent subgraphs
1972 bool done=FALSE;
1973 int curColor=0;
1974 QListIterator<DotNode> dnli(*m_rootNodes);
1975 while (!done) // there are still nodes to color
1976 {
1977 DotNode *n;
1978 done=TRUE; // we are done unless there are still uncolored nodes
1979 for (dnli.toLast();(n=dnli.current());--dnli)
1980 {
1981 if (n->m_subgraphId==-1) // not yet colored
1982 {
1983 //printf("Starting at node %s (%p): %d\n",n->m_label.data(),n,curColor);
1984 done=FALSE; // still uncolored nodes
1985 n->m_subgraphId=curColor;
1986 n->markAsVisible();
1987 n->colorConnectedNodes(curColor);
1988 curColor++;
1989 const DotNode *dn=n->findDocNode();
1990 if (dn!=0)
1991 m_rootSubgraphs->inSort(dn);
1992 else
1993 m_rootSubgraphs->inSort(n);
1994 }
1995 }
1996 }
1997
1998 //printf("Number of independent subgraphs: %d\n",curColor);
1999 //QListIterator<DotNode> dnli2(*m_rootSubgraphs);
2000 //DotNode *n;
2001 //for (dnli2.toFirst();(n=dnli2.current());++dnli2)
2002 //{
2003 // printf("Node %s color=%d (c=%d,p=%d)\n",
2004 // n->m_label.data(),n->m_subgraphId,
2005 // n->m_children?n->m_children->count():0,
2006 // n->m_parents?n->m_parents->count():0);
2007 //}
2008}
2009
2010DotGfxHierarchyTable::~DotGfxHierarchyTable()
2011{
2012 //printf("DotGfxHierarchyTable::~DotGfxHierarchyTable\n");
2013
2014 //QDictIterator<DotNode> di(*m_usedNodes);
2015 //DotNode *n;
2016 //for (;(n=di.current());++di)
2017 //{
2018 // printf("Node %p: %s\n",n,n->label().data());
2019 //}
2020
2021 delete m_rootNodes;
2022 delete m_usedNodes;
2023 delete m_rootSubgraphs;
2024}
2025
2026//--------------------------------------------------------------------
2027
2028int DotClassGraph::m_curNodeNumber = 0;
2029
2030void DotClassGraph::addClass(ClassDef *cd,DotNode *n,int prot,
2031 const char *label,const char *usedName,const char *templSpec,bool base,int distance)
2032{
2033 if (Config_getBool("HIDE_UNDOC_CLASSES") && !cd->isLinkable()) return;
2034
2035 int edgeStyle = (label || prot==EdgeInfo::Orange) ? EdgeInfo::Dashed : EdgeInfo::Solid;
2036 QCString className;
2037 if (usedName) // name is a typedef
2038 {
2039 className=usedName;
2040 }
2041 else if (templSpec) // name has a template part
2042 {
2043 className=insertTemplateSpecifierInScope(cd->name(),templSpec);
2044 }
2045 else // just a normal name
2046 {
2047 className=cd->displayName();
2048 }
2049 //printf("DotClassGraph::addClass(class=`%s',parent=%s,prot=%d,label=%s,dist=%d,usedName=%s,templSpec=%s,base=%d)\n",
2050 // className.data(),n->m_label.data(),prot,label,distance,usedName,templSpec,base);
2051 DotNode *bn = m_usedNodes->find(className);
2052 if (bn) // class already inserted
2053 {
2054 if (base)
2055 {
2056 n->addChild(bn,prot,edgeStyle,label);
2057 bn->addParent(n);
2058 }
2059 else
2060 {
2061 bn->addChild(n,prot,edgeStyle,label);
2062 n->addParent(bn);
2063 }
2064 bn->setDistance(distance);
2065 //printf(" add exiting node %s of %s\n",bn->m_label.data(),n->m_label.data());
2066 }
2067 else // new class
2068 {
2069 QCString displayName=className;
2070 if (Config_getBool("HIDE_SCOPE_NAMES")) displayName=stripScope(displayName);
2071 QCString tmp_url;
2072 if (cd->isLinkable() && !cd->isHidden())
2073 {
2074 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2075 if (!cd->anchor().isEmpty())
2076 {
2077 tmp_url+="#"+cd->anchor();
2078 }
2079 }
2080 QCString tooltip = cd->briefDescriptionAsTooltip();
2081 bn = new DotNode(m_curNodeNumber++,
2082 displayName,
2083 tooltip,
2084 tmp_url.data(),
2085 FALSE, // rootNode
2086 cd
2087 );
2088 if (base)
2089 {
2090 n->addChild(bn,prot,edgeStyle,label);
2091 bn->addParent(n);
2092 }
2093 else
2094 {
2095 bn->addChild(n,prot,edgeStyle,label);
2096 n->addParent(bn);
2097 }
2098 bn->setDistance(distance);
2099 m_usedNodes->insert(className,bn);
2100 //printf(" add new child node `%s' to %s hidden=%d url=%s\n",
2101 // className.data(),n->m_label.data(),cd->isHidden(),tmp_url.data());
2102
2103 buildGraph(cd,bn,base,distance+1);
2104 }
2105}
2106
2107void DotClassGraph::determineTruncatedNodes(QList<DotNode> &queue,bool includeParents)
2108{
2109 while (queue.count()>0)
2110 {
2111 DotNode *n = queue.take(0);
2112 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
2113 {
2114 bool truncated = FALSE;
2115 if (n->m_children)
2116 {
2117 QListIterator<DotNode> li(*n->m_children);
2118 DotNode *dn;
2119 for (li.toFirst();(dn=li.current());++li)
2120 {
2121 if (!dn->isVisible())
2122 truncated = TRUE;
2123 else
2124 queue.append(dn);
2125 }
2126 }
2127 if (n->m_parents && includeParents)
2128 {
2129 QListIterator<DotNode> li(*n->m_parents);
2130 DotNode *dn;
2131 for (li.toFirst();(dn=li.current());++li)
2132 {
2133 if (!dn->isVisible())
2134 truncated = TRUE;
2135 else
2136 queue.append(dn);
2137 }
2138 }
2139 n->markAsTruncated(truncated);
2140 }
2141 }
2142}
2143
2144bool DotClassGraph::determineVisibleNodes(DotNode *rootNode,
2145 int maxNodes,bool includeParents)
2146{
2147 QList<DotNode> childQueue;
2148 QList<DotNode> parentQueue;
2149 QArray<int> childTreeWidth;
2150 QArray<int> parentTreeWidth;
2151 childQueue.append(rootNode);
2152 if (includeParents) parentQueue.append(rootNode);
2153 bool firstNode=TRUE; // flag to force reprocessing rootNode in the parent loop
2154 // despite being marked visible in the child loop
2155 while ((childQueue.count()>0 || parentQueue.count()>0) && maxNodes>0)
2156 {
2157 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
2158 if (childQueue.count()>0)
2159 {
2160 DotNode *n = childQueue.take(0);
2161 int distance = n->distance();
2162 if (!n->isVisible() && distance<maxDistance) // not yet processed
2163 {
2164 if (distance>0)
2165 {
2166 int oldSize=(int)childTreeWidth.size();
2167 if (distance>oldSize)
2168 {
2169 childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance));
2170 int i; for (i=oldSize;i<distance;i++) childTreeWidth[i]=0;
2171 }
2172 childTreeWidth[distance-1]+=n->label().length();
2173 }
2174 n->markAsVisible();
2175 maxNodes--;
2176 // add direct children
2177 if (n->m_children)
2178 {
2179 QListIterator<DotNode> li(*n->m_children);
2180 DotNode *dn;
2181 for (li.toFirst();(dn=li.current());++li)
2182 {
2183 childQueue.append(dn);
2184 }
2185 }
2186 }
2187 }
2188 if (includeParents && parentQueue.count()>0)
2189 {
2190 DotNode *n = parentQueue.take(0);
2191 if ((!n->isVisible() || firstNode) && n->distance()<maxDistance) // not yet processed
2192 {
2193 firstNode=FALSE;
2194 int distance = n->distance();
2195 if (distance>0)
2196 {
2197 int oldSize = (int)parentTreeWidth.size();
2198 if (distance>oldSize)
2199 {
2200 parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance));
2201 int i; for (i=oldSize;i<distance;i++) parentTreeWidth[i]=0;
2202 }
2203 parentTreeWidth[distance-1]+=n->label().length();
2204 }
2205 n->markAsVisible();
2206 maxNodes--;
2207 // add direct parents
2208 if (n->m_parents)
2209 {
2210 QListIterator<DotNode> li(*n->m_parents);
2211 DotNode *dn;
2212 for (li.toFirst();(dn=li.current());++li)
2213 {
2214 parentQueue.append(dn);
2215 }
2216 }
2217 }
2218 }
2219 }
2220 if (Config_getBool("UML_LOOK")) return FALSE; // UML graph are always top to bottom
2221 int maxWidth=0;
2222 int maxHeight=(int)QMAX(childTreeWidth.size(),parentTreeWidth.size());
2223 uint i;
2224 for (i=0;i<childTreeWidth.size();i++)
2225 {
2226 if (childTreeWidth.at(i)>maxWidth) maxWidth=childTreeWidth.at(i);
2227 }
2228 for (i=0;i<parentTreeWidth.size();i++)
2229 {
2230 if (parentTreeWidth.at(i)>maxWidth) maxWidth=parentTreeWidth.at(i);
2231 }
2232 //printf("max tree width=%d, max tree height=%d\n",maxWidth,maxHeight);
2233 return maxWidth>80 && maxHeight<12; // used metric to decide to render the tree
2234 // from left to right instead of top to bottom,
2235 // with the idea to render very wide trees in
2236 // left to right order.
2237}
2238
2239void DotClassGraph::buildGraph(ClassDef *cd,DotNode *n,bool base,int distance)
2240{
2241 //printf("DocClassGraph::buildGraph(%s,distance=%d,base=%d)\n",
2242 // cd->name().data(),distance,base);
2243 // ---- Add inheritance relations
2244
2245 if (m_graphType == DotNode::Inheritance || m_graphType==DotNode::Collaboration)
2246 {
2247 BaseClassList *bcl = base ? cd->baseClasses() : cd->subClasses();
2248 if (bcl)
2249 {
2250 BaseClassListIterator bcli(*bcl);
2251 BaseClassDef *bcd;
2252 for ( ; (bcd=bcli.current()) ; ++bcli )
2253 {
2254 //printf("-------- inheritance relation %s->%s templ=`%s'\n",
2255 // cd->name().data(),bcd->classDef->name().data(),bcd->templSpecifiers.data());
2256 addClass(bcd->classDef,n,bcd->prot,0,bcd->usedName,
2257 bcd->templSpecifiers,base,distance);
2258 }
2259 }
2260 }
2261 if (m_graphType == DotNode::Collaboration)
2262 {
2263 // ---- Add usage relations
2264
2265 UsesClassDict *dict =
2266 base ? cd->usedImplementationClasses() :
2267 cd->usedByImplementationClasses()
2268 ;
2269 if (dict)
2270 {
2271 UsesClassDictIterator ucdi(*dict);
2272 UsesClassDef *ucd;
2273 for (;(ucd=ucdi.current());++ucdi)
2274 {
2275 QCString label;
2276 QDictIterator<void> dvi(*ucd->accessors);
2277 const char *s;
2278 bool first=TRUE;
2279 int count=0;
2280 int maxLabels=10;
2281 for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count)
2282 {
2283 if (first)
2284 {
2285 label=s;
2286 first=FALSE;
2287 }
2288 else
2289 {
2290 label+=QCString("\n")+s;
2291 }
2292 }
2293 if (count==maxLabels) label+="\n...";
2294 //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data());
2295 addClass(ucd->classDef,n,EdgeInfo::Purple,label,0,
2296 ucd->templSpecifiers,base,distance);
2297 }
2298 }
2299 }
2300
2301 // ---- Add template instantiation relations
2302
2303 static bool templateRelations = Config_getBool("TEMPLATE_RELATIONS");
2304 if (templateRelations)
2305 {
2306 if (base) // template relations for base classes
2307 {
2308 ClassDef *templMaster=cd->templateMaster();
2309 if (templMaster)
2310 {
2311 QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances());
2312 ClassDef *templInstance;
2313 for (;(templInstance=cli.current());++cli)
2314 {
2315 if (templInstance==cd)
2316 {
2317 addClass(templMaster,n,EdgeInfo::Orange,cli.currentKey(),0,
2318 0,TRUE,distance);
2319 }
2320 }
2321 }
2322 }
2323 else // template relations for super classes
2324 {
2325 QDict<ClassDef> *templInstances = cd->getTemplateInstances();
2326 if (templInstances)
2327 {
2328 QDictIterator<ClassDef> cli(*templInstances);
2329 ClassDef *templInstance;
2330 for (;(templInstance=cli.current());++cli)
2331 {
2332 addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0,
2333 0,FALSE,distance);
2334 }
2335 }
2336 }
2337 }
2338}
2339
2340DotClassGraph::DotClassGraph(ClassDef *cd,DotNode::GraphType t)
2341{
2342 //printf("--------------- DotClassGraph::DotClassGraph `%s'\n",cd->displayName().data());
2343 m_graphType = t;
2344 QCString tmp_url="";
2345 if (cd->isLinkable() && !cd->isHidden())
2346 {
2347 tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2348 if (!cd->anchor().isEmpty())
2349 {
2350 tmp_url+="#"+cd->anchor();
2351 }
2352 }
2353 QCString className = cd->displayName();
2354 QCString tooltip = cd->briefDescriptionAsTooltip();
2355 m_startNode = new DotNode(m_curNodeNumber++,
2356 className,
2357 tooltip,
2358 tmp_url.data(),
2359 TRUE, // is a root node
2360 cd
2361 );
2362 m_startNode->setDistance(0);
2363 m_usedNodes = new QDict<DotNode>(1009);
2364 m_usedNodes->insert(className,m_startNode);
2365
2366 //printf("Root node %s\n",cd->name().data());
2367 //if (m_recDepth>0)
2368 //{
2369 buildGraph(cd,m_startNode,TRUE,1);
2370 if (t==DotNode::Inheritance) buildGraph(cd,m_startNode,FALSE,1);
2371 //}
2372
2373 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
2374 //int directChildNodes = 1;
2375 //if (m_startNode->m_children!=0)
2376 // directChildNodes+=m_startNode->m_children->count();
2377 //if (t==DotNode::Inheritance && m_startNode->m_parents!=0)
2378 // directChildNodes+=m_startNode->m_parents->count();
2379 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
2380 //openNodeQueue.append(m_startNode);
2381 m_lrRank = determineVisibleNodes(m_startNode,maxNodes,t==DotNode::Inheritance);
2382 QList<DotNode> openNodeQueue;
2383 openNodeQueue.append(m_startNode);
2384 determineTruncatedNodes(openNodeQueue,t==DotNode::Inheritance);
2385
2386 m_diskName = cd->getFileBase().copy();
2387}
2388
2389bool DotClassGraph::isTrivial() const
2390{
2391 if (m_graphType==DotNode::Inheritance)
2392 return m_startNode->m_children==0 && m_startNode->m_parents==0;
2393 else
2394 return m_startNode->m_children==0;
2395}
2396
2397bool DotClassGraph::isTooBig() const
2398{
2399 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
2400 int numNodes = 0;
2401 numNodes+= m_startNode->m_children ? m_startNode->m_children->count() : 0;
2402 if (m_graphType==DotNode::Inheritance)
2403 {
2404 numNodes+= m_startNode->m_parents ? m_startNode->m_parents->count() : 0;
2405 }
2406 return numNodes>=maxNodes;
2407}
2408
2409DotClassGraph::~DotClassGraph()
2410{
2411 deleteNodes(m_startNode);
2412 delete m_usedNodes;
2413}
2414
2415/*! Computes a 16 byte md5 checksum for a given dot graph.
2416 * The md5 checksum is returned as a 32 character ASCII string.
2417 */
2418QCString computeMd5Signature(DotNode *root,
2419 DotNode::GraphType gt,
2420 GraphOutputFormat format,
2421 bool lrRank,
2422 bool renderParents,
2423 bool backArrows,
2424 QCString &graphStr
2425 )
2426{
2427 bool reNumber=TRUE;
2428
2429 //printf("computeMd5Signature\n");
2430 QGString buf;
2431 FTextStream md5stream(&buf);
2432 writeGraphHeader(md5stream);
2433 if (lrRank)
2434 {
2435 md5stream << " rankdir=LR;" << endl;
2436 }
2437 root->clearWriteFlag();
2438 root->write(md5stream,
2439 gt,
2440 format,
2441 gt!=DotNode::CallGraph && gt!=DotNode::Dependency,
2442 TRUE,
2443 backArrows,
2444 reNumber);
2445 if (renderParents && root->m_parents)
2446 {
2447 QListIterator<DotNode> dnli(*root->m_parents);
2448 DotNode *pn;
2449 for (dnli.toFirst();(pn=dnli.current());++dnli)
2450 {
2451 if (pn->isVisible())
2452 {
2453 root->writeArrow(md5stream, // stream
2454 gt, // graph type
2455 format, // output format
2456 pn, // child node
2457 pn->m_edgeInfo->at(pn->m_children->findRef(root)), // edge info
2458 FALSE, // topDown?
2459 backArrows, // point back?
2460 reNumber // renumber nodes
2461 );
2462 }
2463 pn->write(md5stream, // stream
2464 gt, // graph type
2465 format, // output format
2466 TRUE, // topDown?
2467 FALSE, // toChildren?
2468 backArrows, // backward pointing arrows?
2469 reNumber // renumber nodes?
2470 );
2471 }
2472 }
2473 writeGraphFooter(md5stream);
2474 uchar md5_sig[16];
2475 QCString sigStr(33);
2476 MD5Buffer((const unsigned char *)buf.data(),buf.length(),md5_sig);
2477 MD5SigToString(md5_sig,sigStr.data(),33);
2478 if (reNumber)
2479 {
2480 resetReNumbering();
2481 }
2482 graphStr=buf.data();
2483 //printf("md5: %s | file: %s\n",sigStr,baseName.data());
2484 return sigStr;
2485}
2486
2487static bool updateDotGraph(DotNode *root,
2488 DotNode::GraphType gt,
2489 const QCString &baseName,
2490 GraphOutputFormat format,
2491 bool lrRank,
2492 bool renderParents,
2493 bool backArrows
2494 )
2495{
2496 QCString theGraph;
2497 // TODO: write graph to theGraph, then compute md5 checksum
2498 QCString md5 = computeMd5Signature(
2499 root,gt,format,lrRank,renderParents,backArrows,theGraph);
2500 QFile f(baseName+".dot");
2501 if (f.open(IO_WriteOnly))
2502 {
2503 FTextStream t(&f);
2504 t << theGraph;
2505 }
2506 return checkAndUpdateMd5Signature(baseName,md5); // graph needs to be regenerated
2507}
2508
2509QCString DotClassGraph::diskName() const
2510{
2511 QCString result=m_diskName.copy();
2512 switch (m_graphType)
2513 {
2514 case DotNode::Collaboration:
2515 result+="_coll_graph";
2516 break;
2517 //case Interface:
2518 // result+="_intf_graph";
2519 // break;
2520 case DotNode::Inheritance:
2521 result+="_inherit_graph";
2522 break;
2523 default:
2524 ASSERT(0);
2525 break;
2526 }
2527 return result;
2528}
2529
2530QCString DotClassGraph::writeGraph(FTextStream &out,
2531 GraphOutputFormat format,
2532 const char *path,
2533 const char *fileName,
2534 const char *relPath,
2535 bool /*isTBRank*/,
2536 bool generateImageMap) const
2537{
2538 QDir d(path);
2539 // store the original directory
2540 if (!d.exists())
2541 {
2542 err("error: Output dir %s does not exist!\n",path); exit(1);
2543 }
2544 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
2545
2546 QCString baseName;
2547 QCString mapName;
2548 switch (m_graphType)
2549 {
2550 case DotNode::Collaboration:
2551 mapName="coll_map";
2552 break;
2553 //case Interface:
2554 // mapName="intf_map";
2555 // break;
2556 case DotNode::Inheritance:
2557 mapName="inherit_map";
2558 break;
2559 default:
2560 ASSERT(0);
2561 break;
2562 }
2563 baseName = convertNameToFile(diskName());
2564
2565 // derive target file names from baseName
2566 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
2567 QCString absBaseName = QCString(d.absPath())+"/"+baseName;
2568 QCString absDotName = absBaseName+".dot";
2569 QCString absMapName = absBaseName+".map";
2570 QCString absPdfName = absBaseName+".pdf";
2571 QCString absEpsName = absBaseName+".eps";
2572 QCString absImgName = absBaseName+"."+imgExt;
2573
2574 bool regenerate = FALSE;
2575 if (updateDotGraph(m_startNode,
2576 m_graphType,
2577 absBaseName,
2578 format,
2579 m_lrRank,
2580 m_graphType==DotNode::Inheritance,
2581 TRUE
2582 ) ||
2583 !checkDeliverables(format==BITMAP ? absImgName :
2584 usePDFLatex ? absPdfName : absEpsName,
2585 format==BITMAP && generateImageMap ? absMapName : QCString())
2586 )
2587 {
2588 regenerate=TRUE;
2589 if (format==BITMAP) // run dot to create a bitmap image
2590 {
2591 QCString dotArgs(maxCmdLine);
2592
2593 DotRunner *dotRun = new DotRunner(absDotName,
2594 d.absPath().data(),TRUE,absImgName);
2595 dotRun->addJob(imgExt,absImgName);
2596 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
2597 DotManager::instance()->addRun(dotRun);
2598
2599 }
2600 else if (format==EPS) // run dot to create a .eps image
2601 {
2602 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
2603 if (usePDFLatex)
2604 {
2605 dotRun->addJob("pdf",absPdfName);
2606 }
2607 else
2608 {
2609 dotRun->addJob("ps",absEpsName);
2610 }
2611 DotManager::instance()->addRun(dotRun);
2612 }
2613 }
2614 Doxygen::indexList.addImageFile(baseName+"."+imgExt);
2615
2616 if (format==BITMAP && generateImageMap) // produce HTML to include the image
2617 {
2618 QCString mapLabel = escapeCharsInString(m_startNode->m_label,FALSE)+"_"+
2619 escapeCharsInString(mapName,FALSE);
2620 if (imgExt=="svg") // add link to SVG file without map file
2621 {
2622 out << "<div class=\"center\">";
2623 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
2624 {
2625 if (regenerate)
2626 {
2627 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString());
2628 }
2629 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
2630 out << "<!-- SVG " << mapId << " -->" << endl;
2631 }
2632 out << "</div>" << endl;
2633 }
2634 else // add link to bitmap file with image map
2635 {
2636 out << "<div class=\"center\">";
2637 out << "<img src=\"" << relPath << baseName << "."
2638 << imgExt << "\" border=\"0\" usemap=\"#"
2639 << mapLabel << "\" alt=\"";
2640 switch (m_graphType)
2641 {
2642 case DotNode::Collaboration:
2643 out << "Collaboration graph";
2644 break;
2645 case DotNode::Inheritance:
2646 out << "Inheritance graph";
2647 break;
2648 default:
2649 ASSERT(0);
2650 break;
2651 }
2652 out << "\"/>";
2653 out << "</div>" << endl;
2654
2655 if (regenerate || !insertMapFile(out,absMapName,relPath,mapLabel))
2656 {
2657 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
2658 FALSE,QCString(),mapLabel);
2659 out << "<!-- MAP " << mapId << " -->" << endl;
2660 }
2661 }
2662 }
2663 else if (format==EPS) // produce tex to include the .eps image
2664 {
2665 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
2666 {
2667 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE /*TRUE*/);
2668 out << endl << "% FIG " << figId << endl;
2669 }
2670 }
2671 if (!regenerate) removeDotGraph(absDotName);
2672
2673 return baseName;
2674}
2675
2676//--------------------------------------------------------------------
2677
2678void DotClassGraph::writeXML(FTextStream &t)
2679{
2680 QDictIterator<DotNode> dni(*m_usedNodes);
2681 DotNode *node;
2682 for (;(node=dni.current());++dni)
2683 {
2684 node->writeXML(t,TRUE);
2685 }
2686}
2687
2688void DotClassGraph::writeDEF(FTextStream &t)
2689{
2690 QDictIterator<DotNode> dni(*m_usedNodes);
2691 DotNode *node;
2692 for (;(node=dni.current());++dni)
2693 {
2694 node->writeDEF(t);
2695 }
2696}
2697
2698//--------------------------------------------------------------------
2699
2700int DotInclDepGraph::m_curNodeNumber = 0;
2701
2702void DotInclDepGraph::buildGraph(DotNode *n,FileDef *fd,int distance)
2703{
2704 QList<IncludeInfo> *includeFiles =
2705 m_inverse ? fd->includedByFileList() : fd->includeFileList();
2706 if (includeFiles)
2707 {
2708 QListIterator<IncludeInfo> ili(*includeFiles);
2709 IncludeInfo *ii;
2710 for (;(ii=ili.current());++ili)
2711 {
2712 FileDef *bfd = ii->fileDef;
2713 QCString in = ii->includeName;
2714 //printf(">>>> in=`%s' bfd=%p\n",ii->includeName.data(),bfd);
2715 bool doc=TRUE,src=FALSE;
2716 if (bfd)
2717 {
2718 in = bfd->absFilePath();
2719 doc = bfd->isLinkable() && !bfd->isHidden();
2720 src = bfd->generateSourceFile();
2721 }
2722 if (doc || src || !Config_getBool("HIDE_UNDOC_RELATIONS"))
2723 {
2724 QCString url="";
2725 if (bfd) url=bfd->getOutputFileBase().copy();
2726 if (!doc && src)
2727 {
2728 url=bfd->getSourceFileBase();
2729 }
2730 DotNode *bn = m_usedNodes->find(in);
2731 if (bn) // file is already a node in the graph
2732 {
2733 n->addChild(bn,0,0,0);
2734 bn->addParent(n);
2735 bn->setDistance(distance);
2736 }
2737 else
2738 {
2739 QCString tmp_url;
2740 QCString tooltip;
2741 if (bfd)
2742 {
2743 tmp_url=doc || src ? bfd->getReference()+"$"+url : QCString();
2744 tooltip = bfd->briefDescriptionAsTooltip();
2745 }
2746 bn = new DotNode(
2747 m_curNodeNumber++, // n
2748 ii->includeName, // label
2749 tooltip, // tip
2750 tmp_url, // url
2751 FALSE, // rootNode
2752 0 // cd
2753 );
2754 n->addChild(bn,0,0,0);
2755 bn->addParent(n);
2756 m_usedNodes->insert(in,bn);
2757 bn->setDistance(distance);
2758
2759 if (bfd) buildGraph(bn,bfd,distance+1);
2760 }
2761 }
2762 }
2763 }
2764}
2765
2766void DotInclDepGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
2767{
2768 while (queue.count()>0 && maxNodes>0)
2769 {
2770 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
2771 DotNode *n = queue.take(0);
2772 if (!n->isVisible() && n->distance()<maxDistance) // not yet processed
2773 {
2774 n->markAsVisible();
2775 maxNodes--;
2776 // add direct children
2777 if (n->m_children)
2778 {
2779 QListIterator<DotNode> li(*n->m_children);
2780 DotNode *dn;
2781 for (li.toFirst();(dn=li.current());++li)
2782 {
2783 queue.append(dn);
2784 }
2785 }
2786 }
2787 }
2788}
2789
2790void DotInclDepGraph::determineTruncatedNodes(QList<DotNode> &queue)
2791{
2792 while (queue.count()>0)
2793 {
2794 DotNode *n = queue.take(0);
2795 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
2796 {
2797 bool truncated = FALSE;
2798 if (n->m_children)
2799 {
2800 QListIterator<DotNode> li(*n->m_children);
2801 DotNode *dn;
2802 for (li.toFirst();(dn=li.current());++li)
2803 {
2804 if (!dn->isVisible())
2805 truncated = TRUE;
2806 else
2807 queue.append(dn);
2808 }
2809 }
2810 n->markAsTruncated(truncated);
2811 }
2812 }
2813}
2814
2815
2816DotInclDepGraph::DotInclDepGraph(FileDef *fd,bool inverse)
2817{
2818 m_maxDistance = 0;
2819 m_inverse = inverse;
2820 ASSERT(fd!=0);
2821 m_diskName = fd->getFileBase().copy();
2822 QCString tmp_url=fd->getReference()+"$"+fd->getFileBase();
2823 m_startNode = new DotNode(m_curNodeNumber++,
2824 fd->docName(),
2825 "",
2826 tmp_url.data(),
2827 TRUE // root node
2828 );
2829 m_startNode->setDistance(0);
2830 m_usedNodes = new QDict<DotNode>(1009);
2831 m_usedNodes->insert(fd->absFilePath(),m_startNode);
2832 buildGraph(m_startNode,fd,1);
2833
2834 static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES");
2835 int maxNodes = nodes;
2836 //int directChildNodes = 1;
2837 //if (m_startNode->m_children!=0)
2838 // directChildNodes+=m_startNode->m_children->count();
2839 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
2840 QList<DotNode> openNodeQueue;
2841 openNodeQueue.append(m_startNode);
2842 determineVisibleNodes(openNodeQueue,maxNodes);
2843 openNodeQueue.clear();
2844 openNodeQueue.append(m_startNode);
2845 determineTruncatedNodes(openNodeQueue);
2846}
2847
2848DotInclDepGraph::~DotInclDepGraph()
2849{
2850 deleteNodes(m_startNode);
2851 delete m_usedNodes;
2852}
2853
2854QCString DotInclDepGraph::diskName() const
2855{
2856 QCString result=m_diskName.copy();
2857 if (m_inverse) result+="_dep";
2858 result+="_incl";
2859 return convertNameToFile(result);
2860}
2861
2862QCString DotInclDepGraph::writeGraph(FTextStream &out,
2863 GraphOutputFormat format,
2864 const char *path,
2865 const char *fileName,
2866 const char *relPath,
2867 bool generateImageMap
2868 ) const
2869{
2870 QDir d(path);
2871 // store the original directory
2872 if (!d.exists())
2873 {
2874 err("error: Output dir %s does not exist!\n",path); exit(1);
2875 }
2876 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
2877
2878 QCString baseName=m_diskName;
2879 if (m_inverse) baseName+="_dep";
2880 baseName+="_incl";
2881 baseName=convertNameToFile(baseName);
2882 QCString mapName=escapeCharsInString(m_startNode->m_label,FALSE);
2883 if (m_inverse) mapName+="dep";
2884
2885 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
2886 QCString absBaseName = QCString(d.absPath())+"/"+baseName;
2887 QCString absDotName = absBaseName+".dot";
2888 QCString absMapName = absBaseName+".map";
2889 QCString absPdfName = absBaseName+".pdf";
2890 QCString absEpsName = absBaseName+".eps";
2891 QCString absImgName = absBaseName+"."+imgExt;
2892
2893 bool regenerate = FALSE;
2894 if (updateDotGraph(m_startNode,
2895 DotNode::Dependency,
2896 absBaseName,
2897 format,
2898 FALSE, // lrRank
2899 FALSE, // renderParents
2900 m_inverse // backArrows
2901 ) ||
2902 !checkDeliverables(format==BITMAP ? absImgName :
2903 usePDFLatex ? absPdfName : absEpsName,
2904 format==BITMAP && generateImageMap ? absMapName : QCString())
2905 )
2906 {
2907 regenerate=TRUE;
2908 if (format==BITMAP)
2909 {
2910 // run dot to create a bitmap image
2911 QCString dotArgs(maxCmdLine);
2912 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
2913 dotRun->addJob(imgExt,absImgName);
2914 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
2915 DotManager::instance()->addRun(dotRun);
2916 }
2917 else if (format==EPS)
2918 {
2919 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
2920 if (usePDFLatex)
2921 {
2922 dotRun->addJob("pdf",absPdfName);
2923 }
2924 else
2925 {
2926 dotRun->addJob("ps",absEpsName);
2927 }
2928 DotManager::instance()->addRun(dotRun);
2929
2930 }
2931 }
2932 Doxygen::indexList.addImageFile(baseName+"."+imgExt);
2933
2934 if (format==BITMAP && generateImageMap)
2935 {
2936 if (imgExt=="svg") // Scalable vector graphics
2937 {
2938 out << "<div class=\"center\">";
2939 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
2940 {
2941 if (regenerate)
2942 {
2943 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString());
2944 }
2945 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
2946 out << "<!-- SVG " << mapId << " -->" << endl;
2947 }
2948 out << "</div>" << endl;
2949 }
2950 else // bitmap graphics
2951 {
2952 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
2953 << imgExt << "\" border=\"0\" usemap=\"#"
2954 << mapName << "\" alt=\"\"/>";
2955 out << "</div>" << endl;
2956
2957 QCString absMapName = absBaseName+".map";
2958 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
2959 {
2960 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
2961 FALSE,QCString(),mapName);
2962 out << "<!-- MAP " << mapId << " -->" << endl;
2963 }
2964 }
2965 }
2966 else if (format==EPS) // encapsulated postscript
2967 {
2968 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
2969 {
2970 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
2971 out << endl << "% FIG " << figId << endl;
2972 }
2973 }
2974 if (!regenerate) removeDotGraph(absDotName);
2975
2976 return baseName;
2977}
2978
2979bool DotInclDepGraph::isTrivial() const
2980{
2981 return m_startNode->m_children==0;
2982}
2983
2984bool DotInclDepGraph::isTooBig() const
2985{
2986 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
2987 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
2988 return numNodes>=maxNodes;
2989}
2990
2991void DotInclDepGraph::writeXML(FTextStream &t)
2992{
2993 QDictIterator<DotNode> dni(*m_usedNodes);
2994 DotNode *node;
2995 for (;(node=dni.current());++dni)
2996 {
2997 node->writeXML(t,FALSE);
2998 }
2999}
3000
3001//-------------------------------------------------------------
3002
3003int DotCallGraph::m_curNodeNumber = 0;
3004
3005void DotCallGraph::buildGraph(DotNode *n,MemberDef *md,int distance)
3006{
3007 LockingPtr<MemberSDict> refs = m_inverse ? md->getReferencedByMembers() : md->getReferencesMembers();
3008 if (!refs.isNull())
3009 {
3010 MemberSDict::Iterator mri(*refs);
3011 MemberDef *rmd;
3012 for (;(rmd=mri.current());++mri)
3013 {
3014 if (rmd->isFunction() || rmd->isSlot() || rmd->isSignal())
3015 {
3016 QCString uniqueId;
3017 uniqueId=rmd->getReference()+"$"+
3018 rmd->getOutputFileBase()+"#"+rmd->anchor();
3019 DotNode *bn = m_usedNodes->find(uniqueId);
3020 if (bn) // file is already a node in the graph
3021 {
3022 n->addChild(bn,0,0,0);
3023 bn->addParent(n);
3024 bn->setDistance(distance);
3025 }
3026 else
3027 {
3028 QCString name;
3029 if (Config_getBool("HIDE_SCOPE_NAMES"))
3030 {
3031 name = rmd->getOuterScope()==m_scope ?
3032 rmd->name() : rmd->qualifiedName();
3033 }
3034 else
3035 {
3036 name = rmd->qualifiedName();
3037 }
3038 QCString tooltip = rmd->briefDescriptionAsTooltip();
3039 bn = new DotNode(
3040 m_curNodeNumber++,
3041 linkToText(name,FALSE),
3042 tooltip,
3043 uniqueId,
3044 0 //distance
3045 );
3046 n->addChild(bn,0,0,0);
3047 bn->addParent(n);
3048 bn->setDistance(distance);
3049 m_usedNodes->insert(uniqueId,bn);
3050
3051 buildGraph(bn,rmd,distance+1);
3052 }
3053 }
3054 }
3055 }
3056}
3057
3058void DotCallGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
3059{
3060 while (queue.count()>0 && maxNodes>0)
3061 {
3062 static int maxDistance = Config_getInt("MAX_DOT_GRAPH_DEPTH");
3063 DotNode *n = queue.take(0);
3064 if (!n->isVisible() && n->distance()<maxDistance) // not yet processed
3065 {
3066 n->markAsVisible();
3067 maxNodes--;
3068 // add direct children
3069 if (n->m_children)
3070 {
3071 QListIterator<DotNode> li(*n->m_children);
3072 DotNode *dn;
3073 for (li.toFirst();(dn=li.current());++li)
3074 {
3075 queue.append(dn);
3076 }
3077 }
3078 }
3079 }
3080}
3081
3082void DotCallGraph::determineTruncatedNodes(QList<DotNode> &queue)
3083{
3084 while (queue.count()>0)
3085 {
3086 DotNode *n = queue.take(0);
3087 if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
3088 {
3089 bool truncated = FALSE;
3090 if (n->m_children)
3091 {
3092 QListIterator<DotNode> li(*n->m_children);
3093 DotNode *dn;
3094 for (li.toFirst();(dn=li.current());++li)
3095 {
3096 if (!dn->isVisible())
3097 truncated = TRUE;
3098 else
3099 queue.append(dn);
3100 }
3101 }
3102 n->markAsTruncated(truncated);
3103 }
3104 }
3105}
3106
3107
3108
3109DotCallGraph::DotCallGraph(MemberDef *md,bool inverse)
3110{
3111 m_maxDistance = 0;
3112 m_inverse = inverse;
3113 m_diskName = md->getOutputFileBase()+"_"+md->anchor();
3114 m_scope = md->getOuterScope();
3115 QCString uniqueId;
3116 uniqueId = md->getReference()+"$"+
3117 md->getOutputFileBase()+"#"+md->anchor();
3118 QCString name;
3119 if (Config_getBool("HIDE_SCOPE_NAMES"))
3120 {
3121 name = md->name();
3122 }
3123 else
3124 {
3125 name = md->qualifiedName();
3126 }
3127 m_startNode = new DotNode(m_curNodeNumber++,
3128 linkToText(name,FALSE),
3129 "",
3130 uniqueId.data(),
3131 TRUE // root node
3132 );
3133 m_startNode->setDistance(0);
3134 m_usedNodes = new QDict<DotNode>(1009);
3135 m_usedNodes->insert(uniqueId,m_startNode);
3136 buildGraph(m_startNode,md,1);
3137
3138 static int nodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3139 int maxNodes = nodes;
3140 //int directChildNodes = 1;
3141 //if (m_startNode->m_children!=0)
3142 // directChildNodes+=m_startNode->m_children->count();
3143 //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3144 QList<DotNode> openNodeQueue;
3145 openNodeQueue.append(m_startNode);
3146 determineVisibleNodes(openNodeQueue,maxNodes);
3147 openNodeQueue.clear();
3148 openNodeQueue.append(m_startNode);
3149 determineTruncatedNodes(openNodeQueue);
3150}
3151
3152DotCallGraph::~DotCallGraph()
3153{
3154 deleteNodes(m_startNode);
3155 delete m_usedNodes;
3156}
3157
3158QCString DotCallGraph::writeGraph(FTextStream &out, GraphOutputFormat format,
3159 const char *path,const char *fileName,
3160 const char *relPath,bool generateImageMap) const
3161{
3162 QDir d(path);
3163 // store the original directory
3164 if (!d.exists())
3165 {
3166 err("error: Output dir %s does not exist!\n",path); exit(1);
3167 }
3168 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3169
3170 QCString baseName = m_diskName + (m_inverse ? "_icgraph" : "_cgraph");
3171 QCString mapName = baseName;
3172
3173 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3174 QCString absBaseName = QCString(d.absPath())+"/"+baseName;
3175 QCString absDotName = absBaseName+".dot";
3176 QCString absMapName = absBaseName+".map";
3177 QCString absPdfName = absBaseName+".pdf";
3178 QCString absEpsName = absBaseName+".eps";
3179 QCString absImgName = absBaseName+"."+imgExt;
3180
3181 bool regenerate=FALSE;
3182 if (updateDotGraph(m_startNode,
3183 DotNode::CallGraph,
3184 absBaseName,
3185 format,
3186 TRUE, // lrRank
3187 FALSE, // renderParents
3188 m_inverse // backArrows
3189 ) ||
3190 !checkDeliverables(format==BITMAP ? absImgName :
3191 usePDFLatex ? absPdfName : absEpsName,
3192 format==BITMAP && generateImageMap ? absMapName : QCString())
3193 )
3194 {
3195 regenerate=TRUE;
3196 if (format==BITMAP)
3197 {
3198 // run dot to create a bitmap image
3199 QCString dotArgs(maxCmdLine);
3200 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3201 dotRun->addJob(imgExt,absImgName);
3202 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3203 DotManager::instance()->addRun(dotRun);
3204
3205 }
3206 else if (format==EPS)
3207 {
3208 // run dot to create a .eps image
3209 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3210 if (usePDFLatex)
3211 {
3212 dotRun->addJob("pdf",absPdfName);
3213 }
3214 else
3215 {
3216 dotRun->addJob("ps",absEpsName);
3217 }
3218 DotManager::instance()->addRun(dotRun);
3219
3220 }
3221 }
3222 Doxygen::indexList.addImageFile(baseName+"."+imgExt);
3223
3224 if (format==BITMAP && generateImageMap)
3225 {
3226 if (imgExt=="svg") // Scalable vector graphics
3227 {
3228 out << "<div class=\"center\">";
3229 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3230 {
3231 if (regenerate)
3232 {
3233 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString());
3234 }
3235 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3236 out << "<!-- SVG " << mapId << " -->" << endl;
3237 }
3238 out << "</div>" << endl;
3239 }
3240 else // bitmap graphics
3241 {
3242 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3243 << imgExt << "\" border=\"0\" usemap=\"#"
3244 << mapName << "\" alt=\"";
3245 out << "\"/>";
3246 out << "</div>" << endl;
3247
3248 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3249 {
3250 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3251 FALSE,QCString(),mapName);
3252 out << "<!-- MAP " << mapId << " -->" << endl;
3253 }
3254 }
3255 }
3256 else if (format==EPS) // encapsulated postscript
3257 {
3258 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3259 {
3260 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3261 out << endl << "% FIG " << figId << endl;
3262 }
3263 }
3264 if (!regenerate) removeDotGraph(absDotName);
3265
3266 return baseName;
3267}
3268
3269bool DotCallGraph::isTrivial() const
3270{
3271 return m_startNode->m_children==0;
3272}
3273
3274bool DotCallGraph::isTooBig() const
3275{
3276 static int maxNodes = Config_getInt("DOT_GRAPH_MAX_NODES");
3277 int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
3278 return numNodes>=maxNodes;
3279}
3280
3281//-------------------------------------------------------------
3282
3283DotDirDeps::DotDirDeps(DirDef *dir) : m_dir(dir)
3284{
3285}
3286
3287DotDirDeps::~DotDirDeps()
3288{
3289}
3290
3291QCString DotDirDeps::writeGraph(FTextStream &out,
3292 GraphOutputFormat format,
3293 const char *path,
3294 const char *fileName,
3295 const char *relPath,
3296 bool generateImageMap) const
3297{
3298 QDir d(path);
3299 // store the original directory
3300 if (!d.exists())
3301 {
3302 err("error: Output dir %s does not exist!\n",path); exit(1);
3303 }
3304 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3305
3306 QCString baseName=m_dir->getOutputFileBase()+"_dep";
3307 QCString mapName=escapeCharsInString(baseName,FALSE);
3308
3309 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3310 QCString absBaseName = QCString(d.absPath())+"/"+baseName;
3311 QCString absDotName = absBaseName+".dot";
3312 QCString absMapName = absBaseName+".map";
3313 QCString absPdfName = absBaseName+".pdf";
3314 QCString absEpsName = absBaseName+".eps";
3315 QCString absImgName = absBaseName+"."+imgExt;
3316
3317 // compute md5 checksum of the graph were are about to generate
3318 QGString theGraph;
3319 FTextStream md5stream(&theGraph);
3320 m_dir->writeDepGraph(md5stream);
3321 uchar md5_sig[16];
3322 QCString sigStr(33);
3323 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
3324 MD5SigToString(md5_sig,sigStr.data(),33);
3325 bool regenerate=FALSE;
3326 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
3327 !checkDeliverables(format==BITMAP ? absImgName :
3328 usePDFLatex ? absPdfName : absEpsName,
3329 format==BITMAP && generateImageMap ? absMapName : QCString())
3330 )
3331 {
3332 regenerate=TRUE;
3333
3334 QFile f(absDotName);
3335 if (!f.open(IO_WriteOnly))
3336 {
3337 err("Cannot create file %s.dot for writing!\n",baseName.data());
3338 }
3339 FTextStream t(&f);
3340 t << theGraph.data();
3341 f.close();
3342
3343 if (format==BITMAP)
3344 {
3345 // run dot to create a bitmap image
3346 QCString dotArgs(maxCmdLine);
3347 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3348 dotRun->addJob(imgExt,absImgName);
3349 if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3350 DotManager::instance()->addRun(dotRun);
3351 }
3352 else if (format==EPS)
3353 {
3354 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3355 if (usePDFLatex)
3356 {
3357 dotRun->addJob("pdf",absPdfName);
3358 }
3359 else
3360 {
3361 dotRun->addJob("ps",absEpsName);
3362 }
3363 DotManager::instance()->addRun(dotRun);
3364 }
3365 }
3366 Doxygen::indexList.addImageFile(baseName+"."+imgExt);
3367
3368 if (format==BITMAP && generateImageMap)
3369 {
3370 if (imgExt=="svg") // Scalable vector graphics
3371 {
3372 out << "<div class=\"center\">";
3373 if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3374 {
3375 if (regenerate)
3376 {
3377 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString());
3378 }
3379 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3380 out << "<!-- SVG " << mapId << " -->" << endl;
3381 }
3382 out << "</div>" << endl;
3383 }
3384 else // bitmap graphics
3385 {
3386 out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3387 << imgExt << "\" border=\"0\" usemap=\"#"
3388 << mapName << "\" alt=\"";
3389 out << convertToXML(m_dir->displayName());
3390 out << "\"/>";
3391 out << "</div>" << endl;
3392
3393 if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3394 {
3395 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3396 TRUE,QCString(),mapName);
3397 out << "<!-- MAP " << mapId << " -->" << endl;
3398 }
3399 }
3400 }
3401 else if (format==EPS)
3402 {
3403 if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3404 {
3405 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3406 out << endl << "% FIG " << figId << endl;
3407 }
3408 }
3409 if (!regenerate) removeDotGraph(absDotName);
3410
3411 return baseName;
3412}
3413
3414bool DotDirDeps::isTrivial() const
3415{
3416 return m_dir->depGraphIsTrivial();
3417}
3418
3419//-------------------------------------------------------------
3420
3421void generateGraphLegend(const char *path)
3422{
3423 QDir d(path);
3424 // store the original directory
3425 if (!d.exists())
3426 {
3427 err("error: Output dir %s does not exist!\n",path); exit(1);
3428 }
3429
3430 QGString theGraph;
3431 FTextStream md5stream(&theGraph);
3432 writeGraphHeader(md5stream);
3433 md5stream << " Node9 [shape=\"box\",label=\"Inherited\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",fillcolor=\"grey75\",style=\"filled\" fontcolor=\"black\"];\n";
3434 md5stream << " Node10 -> Node9 [dir=back,color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3435 md5stream << " Node10 [shape=\"box\",label=\"PublicBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPublicBase" << Doxygen::htmlFileExtension << "\"];\n";
3436 md5stream << " Node11 -> Node10 [dir=back,color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3437 md5stream << " Node11 [shape=\"box\",label=\"Truncated\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"red\",URL=\"$classTruncated" << Doxygen::htmlFileExtension << "\"];\n";
3438 md5stream << " Node13 -> Node9 [dir=back,color=\"darkgreen\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3439 md5stream << " Node13 [shape=\"box\",label=\"ProtectedBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classProtectedBase" << Doxygen::htmlFileExtension << "\"];\n";
3440 md5stream << " Node14 -> Node9 [dir=back,color=\"firebrick4\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3441 md5stream << " Node14 [shape=\"box\",label=\"PrivateBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPrivateBase" << Doxygen::htmlFileExtension << "\"];\n";
3442 md5stream << " Node15 -> Node9 [dir=back,color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3443 md5stream << " Node15 [shape=\"box\",label=\"Undocumented\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"grey75\"];\n";
3444 md5stream << " Node16 -> Node9 [dir=back,color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
3445 md5stream << " Node16 [shape=\"box\",label=\"Templ< int >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
3446 md5stream << " Node17 -> Node16 [dir=back,color=\"orange\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"< int >\",fontname=\"" << FONTNAME << "\"];\n";
3447 md5stream << " Node17 [shape=\"box\",label=\"Templ< T >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
3448 md5stream << " Node18 -> Node9 [dir=back,color=\"darkorchid3\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"m_usedClass\",fontname=\"" << FONTNAME << "\"];\n";
3449 md5stream << " Node18 [shape=\"box\",label=\"Used\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classUsed" << Doxygen::htmlFileExtension << "\"];\n";
3450 writeGraphFooter(md5stream);
3451 uchar md5_sig[16];
3452 QCString sigStr(33);
3453 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
3454 MD5SigToString(md5_sig,sigStr.data(),33);
3455 QCString absBaseName = (QCString)path+"/graph_legend";
3456 QCString absDotName = absBaseName+".dot";
3457 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3458 QCString imgName = "graph_legend."+imgExt;
3459 QCString absImgName = absBaseName+"."+imgExt;
3460 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
3461 !checkDeliverables(absImgName))
3462 {
3463 QFile dotFile(absDotName);
3464 if (!dotFile.open(IO_WriteOnly))
3465 {
3466 err("Could not open file %s for writing\n",
3467 convertToQCString(dotFile.name()).data());
3468 return;
3469 }
3470
3471 FTextStream dotText(&dotFile);
3472 dotText << theGraph;
3473 dotFile.close();
3474
3475 // run dot to generate the a bitmap image from the graph
3476
3477 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3478 dotRun->addJob(imgExt,absImgName);
3479 DotManager::instance()->addRun(dotRun);
3480 }
3481 else
3482 {
3483 removeDotGraph(absDotName);
3484 }
3485 Doxygen::indexList.addImageFile(imgName);
3486
3487 if (imgExt=="svg")
3488 {
3489 DotManager::instance()->addSVGObject(
3490 absBaseName+Config_getString("HTML_FILE_EXTENSION"),
3491 "graph_legend",
3492 absImgName,QCString());
3493 }
3494
3495}
3496
3497void writeDotGraphFromFile(const char *inFile,const char *outDir,
3498 const char *outFile,GraphOutputFormat format)
3499{
3500 QDir d(outDir);
3501 if (!d.exists())
3502 {
3503 err("error: Output dir %s does not exist!\n",outDir); exit(1);
3504 }
3505
3506 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3507 QCString imgName = (QCString)outFile+"."+imgExt;
3508 QCString absImgName = QCString(d.absPath())+"/"+imgName;
3509 QCString absOutFile = QCString(d.absPath())+"/"+outFile;
3510
3511 DotRunner dotRun(inFile,d.absPath().data(),FALSE,absImgName);
3512 if (format==BITMAP)
3513 dotRun.addJob(imgExt,absImgName);
3514 else // format==EPS
3515 {
3516 if (Config_getBool("USE_PDFLATEX"))
3517 {
3518 dotRun.addJob("pdf",absOutFile+".pdf");
3519 }
3520 else
3521 {
3522 dotRun.addJob("ps",absOutFile+".eps");
3523 }
3524 }
3525
3526 dotRun.preventCleanUp();
3527 if (!dotRun.run())
3528 {
3529 return;
3530 }
3531
3532 if (format==BITMAP) checkDotResult(absImgName);
3533
3534 Doxygen::indexList.addImageFile(imgName);
3535
3536}
3537
3538
3539/*! Writes user defined image map to the output.
3540 * \param t text stream to write to
3541 * \param inFile just the basename part of the filename
3542 * \param outDir output directory
3543 * \param relPath relative path the to root of the output dir
3544 * \param baseName the base name of the output files
3545 * \param context the scope in which this graph is found (for resolving links)
3546 */
3547void writeDotImageMapFromFile(FTextStream &t,
3548 const QCString &inFile, const QCString &outDir,
3549 const QCString &relPath, const QCString &baseName,
3550 const QCString &context)
3551{
3552
3553 QDir d(outDir);
3554 if (!d.exists())
3555 {
3556 err("error: Output dir %s does not exist!\n",outDir.data()); exit(1);
3557 }
3558
3559 QCString mapName = baseName+".map";
3560 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3561 QCString imgName = baseName+"."+imgExt;
3562 QCString absOutFile = QCString(d.absPath())+"/"+mapName;
3563
3564 DotRunner dotRun(inFile,d.absPath().data(),FALSE);
3565 dotRun.addJob(MAP_CMD,absOutFile);
3566 dotRun.preventCleanUp();
3567 if (!dotRun.run())
3568 {
3569 return;
3570 }
3571
3572 if (imgExt=="svg") // vector graphics
3573 {
3574 writeSVGFigureLink(t,relPath,inFile,inFile+".svg");
3575 DotFilePatcher patcher(inFile+".svg");
3576 patcher.addSVGConversion(relPath,TRUE,context);
3577 patcher.run();
3578 }
3579 else // bitmap graphics
3580 {
3581 t << "<img src=\"" << relPath << imgName << "\" alt=\""
3582 << imgName << "\" border=\"0\" usemap=\"#" << mapName << "\">" << endl
3583 << "<map name=\"" << mapName << "\" id=\"" << mapName << "\">";
3584
3585 convertMapFile(t, absOutFile, relPath ,TRUE, context);
3586
3587 t << "</map>" << endl;
3588 }
3589 d.remove(absOutFile);
3590}
3591
3592//-------------------------------------------------------------
3593
3594DotGroupCollaboration::DotGroupCollaboration(GroupDef* gd)
3595{
3596 m_curNodeId = 0;
3597 QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase();
3598 m_usedNodes = new QDict<DotNode>(1009);
3599 m_rootNode = new DotNode(m_curNodeId++, gd->groupTitle(), "", tmp_url, TRUE );
3600 m_rootNode->markAsVisible();
3601 m_usedNodes->insert(gd->name(), m_rootNode );
3602 m_edges.setAutoDelete(TRUE);
3603
3604 m_diskName = gd->getOutputFileBase();
3605
3606 buildGraph( gd );
3607}
3608
3609DotGroupCollaboration::~DotGroupCollaboration()
3610{
3611 delete m_usedNodes;
3612}
3613
3614void DotGroupCollaboration::buildGraph(GroupDef* gd)
3615{
3616 QCString tmp_url;
3617 //===========================
3618 // hierarchy.
3619
3620 // Write parents
3621 LockingPtr<GroupList> groups = gd->partOfGroups();
3622 if ( groups!=0 )
3623 {
3624 GroupListIterator gli(*groups);
3625 GroupDef *d;
3626 for (gli.toFirst();(d=gli.current());++gli)
3627 {
3628 DotNode* nnode = m_usedNodes->find(d->name());
3629 if ( !nnode )
3630 { // add node
3631 tmp_url = d->getReference()+"$"+d->getOutputFileBase();
3632 QCString tooltip = d->briefDescriptionAsTooltip();
3633 nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_url );
3634 nnode->markAsVisible();
3635 m_usedNodes->insert(d->name(), nnode );
3636 }
3637 tmp_url = "";
3638 addEdge( nnode, m_rootNode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
3639 }
3640 }
3641
3642 // Add subgroups
3643 if ( gd->getSubGroups() && gd->getSubGroups()->count() )
3644 {
3645 QListIterator<GroupDef> defli(*gd->getSubGroups());
3646 GroupDef *def;
3647 for (;(def=defli.current());++defli)
3648 {
3649 DotNode* nnode = m_usedNodes->find(def->name());
3650 if ( !nnode )
3651 { // add node
3652 tmp_url = def->getReference()+"$"+def->getOutputFileBase();
3653 QCString tooltip = def->briefDescriptionAsTooltip();
3654 nnode = new DotNode(m_curNodeId++, def->groupTitle(), tooltip, tmp_url );
3655 nnode->markAsVisible();
3656 m_usedNodes->insert(def->name(), nnode );
3657 }
3658 tmp_url = "";
3659 addEdge( m_rootNode, nnode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
3660 }
3661 }
3662
3663 //=======================
3664 // Write collaboration
3665
3666 // Add members
3667 addMemberList( gd->getMemberList(MemberList::allMembersList) );
3668
3669 // Add classes
3670 if ( gd->getClasses() && gd->getClasses()->count() )
3671 {
3672 ClassSDict::Iterator defli(*gd->getClasses());
3673 ClassDef *def;
3674 for (;(def=defli.current());++defli)
3675 {
3676 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
3677 if (!def->anchor().isEmpty())
3678 {
3679 tmp_url+="#"+def->anchor();
3680 }
3681 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tclass );
3682 }
3683 }
3684
3685 // Add namespaces
3686 if ( gd->getNamespaces() && gd->getNamespaces()->count() )
3687 {
3688 NamespaceSDict::Iterator defli(*gd->getNamespaces());
3689 NamespaceDef *def;
3690 for (;(def=defli.current());++defli)
3691 {
3692 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
3693 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tnamespace );
3694 }
3695 }
3696
3697 // Add files
3698 if ( gd->getFiles() && gd->getFiles()->count() )
3699 {
3700 QListIterator<FileDef> defli(*gd->getFiles());
3701 FileDef *def;
3702 for (;(def=defli.current());++defli)
3703 {
3704 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
3705 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tfile );
3706 }
3707 }
3708
3709 // Add pages
3710 if ( gd->getPages() && gd->getPages()->count() )
3711 {
3712 PageSDict::Iterator defli(*gd->getPages());
3713 PageDef *def;
3714 for (;(def=defli.current());++defli)
3715 {
3716 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
3717 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tpages );
3718 }
3719 }
3720
3721 // Add directories
3722 if ( gd->getDirs() && gd->getDirs()->count() )
3723 {
3724 QListIterator<DirDef> defli(*gd->getDirs());
3725 DirDef *def;
3726 for (;(def=defli.current());++defli)
3727 {
3728 tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
3729 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tdir );
3730 }
3731 }
3732}
3733
3734void DotGroupCollaboration::addMemberList( MemberList* ml )
3735{
3736 if ( !( ml && ml->count()) ) return;
3737 MemberListIterator defli(*ml);
3738 MemberDef *def;
3739 for (;(def=defli.current());++defli)
3740 {
3741 QCString tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension
3742 +"#"+def->anchor();
3743 addCollaborationMember( def, tmp_url, DotGroupCollaboration::tmember );
3744 }
3745}
3746
3747DotGroupCollaboration::Edge* DotGroupCollaboration::addEdge(
3748 DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType,
3749 const QCString& _label, const QCString& _url )
3750{
3751 // search a existing link.
3752 QListIterator<Edge> lli(m_edges);
3753 Edge* newEdge = 0;
3754 for ( lli.toFirst(); (newEdge=lli.current()); ++lli)
3755 {
3756 if ( newEdge->pNStart==_pNStart &&
3757 newEdge->pNEnd==_pNEnd &&
3758 newEdge->eType==_eType
3759 )
3760 { // edge already found
3761 break;
3762 }
3763 }
3764 if ( newEdge==0 ) // new link
3765 {
3766 newEdge = new Edge(_pNStart,_pNEnd,_eType);
3767 m_edges.append( newEdge );
3768 }
3769
3770 if (!_label.isEmpty())
3771 {
3772 newEdge->links.append(new Link(_label,_url));
3773 }
3774
3775 return newEdge;
3776}
3777
3778void DotGroupCollaboration::addCollaborationMember(
3779 Definition* def, QCString& url, EdgeType eType )
3780{
3781 // Create group nodes
3782 if ( !def->partOfGroups() )
3783 return;
3784 GroupListIterator gli(*def->partOfGroups());
3785 GroupDef *d;
3786 QCString tmp_str;
3787 for (;(d=gli.current());++gli)
3788 {
3789 DotNode* nnode = m_usedNodes->find(d->name());
3790 if ( nnode != m_rootNode )
3791 {
3792 if ( nnode==0 )
3793 { // add node
3794 tmp_str = d->getReference()+"$"+d->getOutputFileBase();
3795 QCString tooltip = d->briefDescriptionAsTooltip();
3796 nnode = new DotNode(m_curNodeId++, d->groupTitle(), tooltip, tmp_str );
3797 nnode->markAsVisible();
3798 m_usedNodes->insert(d->name(), nnode );
3799 }
3800 tmp_str = def->qualifiedName();
3801 addEdge( m_rootNode, nnode, eType, tmp_str, url );
3802 }
3803 }
3804}
3805
3806
3807QCString DotGroupCollaboration::writeGraph( FTextStream &t, GraphOutputFormat format,
3808 const char *path, const char *fileName, const char *relPath,
3809 bool writeImageMap) const
3810{
3811 QDir d(path);
3812 // store the original directory
3813 if (!d.exists())
3814 {
3815 err("error: Output dir %s does not exist!\n",path); exit(1);
3816 }
3817 static bool usePDFLatex = Config_getBool("USE_PDFLATEX");
3818
3819 QGString theGraph;
3820 FTextStream md5stream(&theGraph);
3821 writeGraphHeader(md5stream);
3822
3823 // clean write flags
3824 QDictIterator<DotNode> dni(*m_usedNodes);
3825 DotNode *pn;
3826 for (dni.toFirst();(pn=dni.current());++dni)
3827 {
3828 pn->clearWriteFlag();
3829 }
3830
3831 // write other nodes.
3832 for (dni.toFirst();(pn=dni.current());++dni)
3833 {
3834 pn->write(md5stream,DotNode::Inheritance,format,TRUE,FALSE,FALSE,FALSE);
3835 }
3836
3837 // write edges
3838 QListIterator<Edge> eli(m_edges);
3839 Edge* edge;
3840 for (eli.toFirst();(edge=eli.current());++eli)
3841 {
3842 edge->write( md5stream );
3843 }
3844
3845 writeGraphFooter(md5stream);
3846 resetReNumbering();
3847 uchar md5_sig[16];
3848 QCString sigStr(33);
3849 MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
3850 MD5SigToString(md5_sig,sigStr.data(),33);
3851 QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT");
3852 QCString baseName = m_diskName;
3853 QCString imgName = baseName+"."+imgExt;
3854 QCString mapName = baseName+".map";
3855 QCString absPath = d.absPath().data();
3856 QCString absBaseName = absPath+"/"+baseName;
3857 QCString absDotName = absBaseName+".dot";
3858 QCString absImgName = absBaseName+"."+imgExt;
3859 QCString absMapName = absBaseName+".map";
3860 QCString absPdfName = absBaseName+".pdf";
3861 QCString absEpsName = absBaseName+".eps";
3862 bool regenerate=FALSE;
3863 if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
3864 !checkDeliverables(format==BITMAP ? absImgName :
3865 usePDFLatex ? absPdfName : absEpsName,
3866 format==BITMAP /*&& generateImageMap*/ ? absMapName : QCString())
3867 )
3868 {
3869 regenerate=TRUE;
3870
3871 QFile dotfile(absDotName);
3872 if (dotfile.open(IO_WriteOnly))
3873 {
3874 FTextStream tdot(&dotfile);
3875 tdot << theGraph;
3876 dotfile.close();
3877 }
3878
3879 if (format==BITMAP) // run dot to create a bitmap image
3880 {
3881 QCString dotArgs(maxCmdLine);
3882
3883 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3884 dotRun->addJob(imgExt,absImgName);
3885 if (writeImageMap) dotRun->addJob(MAP_CMD,absMapName);
3886 DotManager::instance()->addRun(dotRun);
3887
3888 }
3889 else if (format==EPS)
3890 {
3891 DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3892 if (usePDFLatex)
3893 {
3894 dotRun->addJob("pdf",absPdfName);
3895 }
3896 else
3897 {
3898 dotRun->addJob("ps",absEpsName);
3899 }
3900 DotManager::instance()->addRun(dotRun);
3901 }
3902
3903 }
3904 if (format==BITMAP && writeImageMap)
3905 {
3906 QCString mapLabel = escapeCharsInString(baseName,FALSE);
3907 t << "<center><table><tr><td>";
3908
3909 if (imgExt=="svg")
3910 {
3911 t << "<div class=\"center\">";
3912 if (regenerate || !writeSVGFigureLink(t,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3913 {
3914 if (regenerate)
3915 {
3916 DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString());
3917 }
3918 int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3919 t << "<!-- SVG " << mapId << " -->" << endl;
3920 }
3921 t << "</div>" << endl;
3922 }
3923 else
3924 {
3925 t << "<img src=\"" << relPath << imgName
3926 << "\" border=\"0\" alt=\"\" usemap=\"#"
3927 << mapLabel << "\"/>" << endl;
3928 if (regenerate || !insertMapFile(t,absMapName,relPath,mapLabel))
3929 {
3930 int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3931 FALSE,QCString(),mapLabel);
3932 t << "<!-- MAP " << mapId << " -->" << endl;
3933 }
3934 }
3935
3936 t << "</td></tr></table></center>" << endl;
3937 }
3938 else if (format==EPS)
3939 {
3940 if (regenerate || !writeVecGfxFigure(t,baseName,absBaseName))
3941 {
3942 int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3943 t << endl << "% FIG " << figId << endl;
3944 }
3945 }
3946 if (!regenerate) removeDotGraph(absDotName);
3947
3948 return baseName;
3949}
3950
3951void DotGroupCollaboration::Edge::write( FTextStream &t ) const
3952{
3953 const char* linkTypeColor[] = {
3954 "darkorchid3"
3955 ,"orange"
3956 ,"blueviolet"
3957 ,"darkgreen"
3958 ,"firebrick4"
3959 ,"grey75"
3960 ,"midnightblue"
3961 };
3962 QCString arrowStyle = "dir=\"none\", style=\"dashed\"";
3963 t << " Node" << pNStart->number();
3964 t << "->";
3965 t << "Node" << pNEnd->number();
3966
3967 t << " [shape=plaintext";
3968 if (links.count()>0) // there are links
3969 {
3970 t << ", ";
3971 // HTML-like edge labels crash on my Mac with Graphviz 2.0! and
3972 // are not supported by older version of dot.
3973 //
3974 //t << label=<<TABLE BORDER=\"0\" CELLBORDER=\"0\">";
3975 //QListIterator<Link> lli(links);
3976 //Link *link;
3977 //for( lli.toFirst(); (link=lli.current()); ++lli)
3978 //{
3979 // t << "<TR><TD";
3980 // if ( !link->url.isEmpty() )
3981 // t << " HREF=\"" << link->url << "\"";
3982 // t << ">" << link->label << "</TD></TR>";
3983 //}
3984 //t << "</TABLE>>";
3985
3986 t << "label=\"";
3987 QListIterator<Link> lli(links);
3988 Link *link;
3989 bool first=TRUE;
3990 int count=0;
3991 const int maxLabels = 10;
3992 for( lli.toFirst(); (link=lli.current()) && count<maxLabels; ++lli,++count)
3993 {
3994 if (first) first=FALSE; else t << "\\n";
3995 t << convertLabel(link->label);
3996 }
3997 if (count==maxLabels) t << "\\n...";
3998 t << "\"";
3999
4000 }
4001 switch( eType )
4002 {
4003 case thierarchy :
4004 arrowStyle = "dir=\"back\", style=\"solid\"";
4005 default :
4006 t << ", color=\"" << linkTypeColor[(int)eType] << "\"";
4007 break;
4008 }
4009 t << ", " << arrowStyle;
4010 t << "];" << endl;
4011}
4012
4013bool DotGroupCollaboration::isTrivial() const
4014{
4015 return m_usedNodes->count() <= 1;
4016}
4017
4018void DotGroupCollaboration::writeGraphHeader(FTextStream &t) const
4019{
4020 t << "digraph structs" << endl;
4021 t << "{" << endl;
4022 if (Config_getBool("DOT_TRANSPARENT"))
4023 {
4024 t << " bgcolor=\"transparent\";" << endl;
4025 }
4026 t << " edge [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\","
4027 "labelfontname=\"" << FONTNAME << "\",labelfontsize=\"" << FONTSIZE << "\"];\n";
4028 t << " node [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\",shape=record];\n";
4029 t << " rankdir=LR;\n";
4030}
4031
4032void writeDotDirDepGraph(FTextStream &t,DirDef *dd)
4033{
4034 t << "digraph G {\n";
4035 if (Config_getBool("DOT_TRANSPARENT"))
4036 {
4037 t << " bgcolor=transparent;\n";
4038 }
4039 t << " compound=true\n";
4040 t << " node [ fontsize=\"" << FONTSIZE << "\", fontname=\"" << FONTNAME << "\"];\n";
4041 t << " edge [ labelfontsize=\"" << FONTSIZE << "\", labelfontname=\"" << FONTNAME << "\"];\n";
4042
4043 QDict<DirDef> dirsInGraph(257);
4044
4045 dirsInGraph.insert(dd->getOutputFileBase(),dd);
4046 if (dd->parent())
4047 {
4048 t << " subgraph cluster" << dd->parent()->getOutputFileBase() << " {\n";
4049 t << " graph [ bgcolor=\"#ddddee\", pencolor=\"black\", label=\""
4050 << dd->parent()->shortName()
4051 << "\" fontname=\"" << FONTNAME << "\", fontsize=\"" << FONTSIZE << "\", URL=\"";
4052 t << dd->parent()->getOutputFileBase() << Doxygen::htmlFileExtension;
4053 t << "\"]\n";
4054 }
4055 if (dd->isCluster())
4056 {
4057 t << " subgraph cluster" << dd->getOutputFileBase() << " {\n";
4058 t << " graph [ bgcolor=\"#eeeeff\", pencolor=\"black\", label=\"\""
4059 << " URL=\"" << dd->getOutputFileBase() << Doxygen::htmlFileExtension
4060 << "\"];\n";
4061 t << " " << dd->getOutputFileBase() << " [shape=plaintext label=\""
4062 << dd->shortName() << "\"];\n";
4063
4064 // add nodes for sub directories
4065 QListIterator<DirDef> sdi(dd->subDirs());
4066 DirDef *sdir;
4067 for (sdi.toFirst();(sdir=sdi.current());++sdi)
4068 {
4069 t << " " << sdir->getOutputFileBase() << " [shape=box label=\""
4070 << sdir->shortName() << "\"";
4071 if (sdir->isCluster())
4072 {
4073 t << " color=\"red\"";
4074 }
4075 else
4076 {
4077 t << " color=\"black\"";
4078 }
4079 t << " fillcolor=\"white\" style=\"filled\"";
4080 t << " URL=\"" << sdir->getOutputFileBase()
4081 << Doxygen::htmlFileExtension << "\"";
4082 t << "];\n";
4083 dirsInGraph.insert(sdir->getOutputFileBase(),sdir);
4084 }
4085 t << " }\n";
4086 }
4087 else
4088 {
4089 t << " " << dd->getOutputFileBase() << " [shape=box, label=\""
4090 << dd->shortName() << "\", style=\"filled\", fillcolor=\"#eeeeff\","
4091 << " pencolor=\"black\", URL=\"" << dd->getOutputFileBase()
4092 << Doxygen::htmlFileExtension << "\"];\n";
4093 }
4094 if (dd->parent())
4095 {
4096 t << " }\n";
4097 }
4098
4099 // add nodes for other used directories
4100 QDictIterator<UsedDir> udi(*dd->usedDirs());
4101 UsedDir *udir;
4102 //printf("*** For dir %s\n",shortName().data());
4103 for (udi.toFirst();(udir=udi.current());++udi)
4104 // for each used dir (=directly used or a parent of a directly used dir)
4105 {
4106 const DirDef *usedDir=udir->dir();
4107 DirDef *dir=dd;
4108 while (dir)
4109 {
4110 //printf("*** check relation %s->%s same_parent=%d !%s->isParentOf(%s)=%d\n",
4111 // dir->shortName().data(),usedDir->shortName().data(),
4112 // dir->parent()==usedDir->parent(),
4113 // usedDir->shortName().data(),
4114 // shortName().data(),
4115 // !usedDir->isParentOf(this)
4116 // );
4117 if (dir!=usedDir && dir->parent()==usedDir->parent() &&
4118 !usedDir->isParentOf(dd))
4119 // include if both have the same parent (or no parent)
4120 {
4121 t << " " << usedDir->getOutputFileBase() << " [shape=box label=\""
4122 << usedDir->shortName() << "\"";
4123 if (usedDir->isCluster())
4124 {
4125 if (!Config_getBool("DOT_TRANSPARENT"))
4126 {
4127 t << " fillcolor=\"white\" style=\"filled\"";
4128 }
4129 t << " color=\"red\"";
4130 }
4131 t << " URL=\"" << usedDir->getOutputFileBase()
4132 << Doxygen::htmlFileExtension << "\"];\n";
4133 dirsInGraph.insert(usedDir->getOutputFileBase(),usedDir);
4134 break;
4135 }
4136 dir=dir->parent();
4137 }
4138 }
4139
4140 // add relations between all selected directories
4141 DirDef *dir;
4142 QDictIterator<DirDef> di(dirsInGraph);
4143 for (di.toFirst();(dir=di.current());++di) // foreach dir in the graph
4144 {
4145 QDictIterator<UsedDir> udi(*dir->usedDirs());
4146 UsedDir *udir;
4147 for (udi.toFirst();(udir=udi.current());++udi) // foreach used dir
4148 {
4149 const DirDef *usedDir=udir->dir();
4150 if ((dir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4151 (usedDir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4152 !usedDir->isParentOf(dir) && // don't point to own parent
4153 dirsInGraph.find(usedDir->getOutputFileBase())) // only point to nodes that are in the graph
4154 {
4155 QCString relationName;
4156 relationName.sprintf("dir_%06d_%06d",dir->dirCount(),usedDir->dirCount());
4157 if (Doxygen::dirRelations.find(relationName)==0)
4158 {
4159 // new relation
4160 Doxygen::dirRelations.append(relationName,
4161 new DirRelation(relationName,dir,udir));
4162 }
4163 int nrefs = udir->filePairs().count();
4164 t << " " << dir->getOutputFileBase() << "->"
4165 << usedDir->getOutputFileBase();
4166 t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5";
4167 t << " headhref=\"" << relationName << Doxygen::htmlFileExtension
4168 << "\"];\n";
4169 }
4170 }
4171 }
4172
4173 t << "}\n";
4174}
4175

Archive Download this file

Revision: 1322