My Project
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
dot.cpp
Go to the documentation of this file.
1 /*****************************************************************************
2  *
3  *
4  *
5  *
6  * Copyright (C) 1997-2015 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 #include <stdlib.h>
20 
21 #include <qdir.h>
22 #include <qfile.h>
23 #include <qqueue.h>
24 #include <qthread.h>
25 #include <qmutex.h>
26 #include <qwaitcondition.h>
27 
28 #include "dot.h"
29 #include "doxygen.h"
30 #include "message.h"
31 #include "util.h"
32 #include "config.h"
33 #include "language.h"
34 #include "defargs.h"
35 #include "docparser.h"
36 #include "debug.h"
37 #include "pagedef.h"
38 #include "portable.h"
39 #include "dirdef.h"
40 #include "vhdldocgen.h"
41 #include "ftextstream.h"
42 #include "md5.h"
43 #include "memberlist.h"
44 #include "groupdef.h"
45 #include "classlist.h"
46 #include "filename.h"
47 #include "namespacedef.h"
48 #include "memberdef.h"
49 #include "membergroup.h"
50 
51 #define MAP_CMD "cmapx"
52 
53 //#define FONTNAME "Helvetica"
54 #define FONTNAME getDotFontName()
55 #define FONTSIZE getDotFontSize()
56 
57 //--------------------------------------------------------------------
58 
59 static const char svgZoomHeader[] =
60 "<svg id=\"main\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:space=\"preserve\" onload=\"init(evt)\">\n"
61 "<style type=\"text/css\"><![CDATA[\n"
62 ".edge:hover path { stroke: red; }\n"
63 ".edge:hover polygon { stroke: red; fill: red; }\n"
64 "]]></style>\n"
65 "<script type=\"text/javascript\"><![CDATA[\n"
66 "var edges = document.getElementsByTagName('g');\n"
67 "if (edges && edges.length) {\n"
68 " for (var i=0;i<edges.length;i++) {\n"
69 " if (edges[i].id.substr(0,4)=='edge') {\n"
70 " edges[i].setAttribute('class','edge');\n"
71 " }\n"
72 " }\n"
73 "}\n"
74 "]]></script>\n"
75 " <defs>\n"
76 " <circle id=\"rim\" cx=\"0\" cy=\"0\" r=\"7\"/>\n"
77 " <circle id=\"rim2\" cx=\"0\" cy=\"0\" r=\"3.5\"/>\n"
78 " <g id=\"zoomPlus\">\n"
79 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
80 " <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomplus.mouseover\" end=\"zoomplus.mouseout\"/>\n"
81 " </use>\n"
82 " <path d=\"M-4,0h8M0,-4v8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
83 " </g>\n"
84 " <g id=\"zoomMin\">\n"
85 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
86 " <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomminus.mouseover\" end=\"zoomminus.mouseout\"/>\n"
87 " </use>\n"
88 " <path d=\"M-4,0h8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
89 " </g>\n"
90 " <g id=\"dirArrow\">\n"
91 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
92 " </g>\n"
93 " <g id=\"resetDef\">\n"
94 " <use xlink:href=\"#rim2\" fill=\"#404040\">\n"
95 " <set attributeName=\"fill\" to=\"#808080\" begin=\"reset.mouseover\" end=\"reset.mouseout\"/>\n"
96 " </use>\n"
97 " </g>\n"
98 " </defs>\n"
99 "\n"
100 "<script type=\"text/javascript\">\n"
101 ;
102 
103 static const char svgZoomFooter[] =
104 // navigation panel
105 " <g id=\"navigator\" transform=\"translate(0 0)\" fill=\"#404254\">\n"
106 " <rect fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\".5\" x=\"0\" y=\"0\" width=\"60\" height=\"60\"/>\n"
107 // zoom in
108 " <use id=\"zoomplus\" xlink:href=\"#zoomPlus\" x=\"17\" y=\"9\" onmousedown=\"handleZoom(evt,'in')\"/>\n"
109 // zoom out
110 " <use id=\"zoomminus\" xlink:href=\"#zoomMin\" x=\"42\" y=\"9\" onmousedown=\"handleZoom(evt,'out')\"/>\n"
111 // reset zoom
112 " <use id=\"reset\" xlink:href=\"#resetDef\" x=\"30\" y=\"36\" onmousedown=\"handleReset()\"/>\n"
113 // arrow up
114 " <g id=\"arrowUp\" xlink:href=\"#dirArrow\" transform=\"translate(30 24)\" onmousedown=\"handlePan(0,-1)\">\n"
115 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
116 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowUp.mouseover\" end=\"arrowUp.mouseout\"/>\n"
117 " </use>\n"
118 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
119 " </g>\n"
120 // arrow right
121 " <g id=\"arrowRight\" xlink:href=\"#dirArrow\" transform=\"rotate(90) translate(36 -43)\" onmousedown=\"handlePan(1,0)\">\n"
122 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
123 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowRight.mouseover\" end=\"arrowRight.mouseout\"/>\n"
124 " </use>\n"
125 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
126 " </g>\n"
127 // arrow down
128 " <g id=\"arrowDown\" xlink:href=\"#dirArrow\" transform=\"rotate(180) translate(-30 -48)\" onmousedown=\"handlePan(0,1)\">\n"
129 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
130 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowDown.mouseover\" end=\"arrowDown.mouseout\"/>\n"
131 " </use>\n"
132 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
133 " </g>\n"
134 // arrow left
135 " <g id=\"arrowLeft\" xlink:href=\"#dirArrow\" transform=\"rotate(270) translate(-36 17)\" onmousedown=\"handlePan(-1,0)\">\n"
136 " <use xlink:href=\"#rim\" fill=\"#404040\">\n"
137 " <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowLeft.mouseover\" end=\"arrowLeft.mouseout\"/>\n"
138 " </use>\n"
139 " <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
140 " </g>\n"
141 " </g>\n"
142 // link to orginial SVG
143 " <svg viewBox=\"0 0 15 15\" width=\"100%\" height=\"30px\" preserveAspectRatio=\"xMaxYMin meet\">\n"
144 " <g id=\"arrow_out\" transform=\"scale(0.3 0.3)\">\n"
145 " <a xlink:href=\"$orgname\" target=\"_base\">\n"
146 " <rect id=\"button\" ry=\"5\" rx=\"5\" y=\"6\" x=\"6\" height=\"38\" width=\"38\"\n"
147 " fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\"1.0\"/>\n"
148 " <path id=\"arrow\"\n"
149 " d=\"M 11.500037,31.436501 C 11.940474,20.09759 22.043105,11.32322 32.158766,21.979434 L 37.068811,17.246167 C 37.068811,17.246167 37.088388,32 37.088388,32 L 22.160133,31.978069 C 22.160133,31.978069 26.997745,27.140456 26.997745,27.140456 C 18.528582,18.264221 13.291696,25.230495 11.500037,31.436501 z\"\n"
150 " style=\"fill:#404040;\"/>\n"
151 " </a>\n"
152 " </g>\n"
153 " </svg>\n"
154 "</svg>\n"
155 ;
156 
157 //--------------------------------------------------------------------
158 
160 static const char *normalEdgeColorMap[] =
161 {
162  "midnightblue", // Public
163  "darkgreen", // Protected
164  "firebrick4", // Private
165  "darkorchid3", // "use" relation
166  "grey75", // Undocumented
167  "orange", // template relation
168  "orange" // type constraint
169 };
170 
171 static const char *normalArrowStyleMap[] =
172 {
173  "empty", // Public
174  "empty", // Protected
175  "empty", // Private
176  "open", // "use" relation
177  0, // Undocumented
178  0 // template relation
179 };
180 
181 static const char *normalEdgeStyleMap[] =
182 {
183  "solid", // inheritance
184  "dashed" // usage
185 };
186 
187 static const char *umlEdgeColorMap[] =
188 {
189  "midnightblue", // Public
190  "darkgreen", // Protected
191  "firebrick4", // Private
192  "grey25", // "use" relation
193  "grey75", // Undocumented
194  "orange", // template relation
195  "orange" // type constraint
196 };
197 
198 static const char *umlArrowStyleMap[] =
199 {
200  "onormal", // Public
201  "onormal", // Protected
202  "onormal", // Private
203  "odiamond", // "use" relation
204  0, // Undocumented
205  0 // template relation
206 };
207 
208 static const char *umlEdgeStyleMap[] =
209 {
210  "solid", // inheritance
211  "solid" // usage
212 };
213 
216 {
217  const char * const *edgeColorMap;
218  const char * const *arrowStyleMap;
219  const char * const *edgeStyleMap;
220 };
221 
223 {
225 };
226 
228 {
230 };
231 
232 
233 static QCString getDotFontName()
234 {
235  static QCString dotFontName = Config_getString(DOT_FONTNAME);
236  if (dotFontName.isEmpty())
237  {
238  //dotFontName="FreeSans.ttf";
239  dotFontName="Helvetica";
240  }
241  return dotFontName;
242 }
243 
244 static int getDotFontSize()
245 {
246  static int dotFontSize = Config_getInt(DOT_FONTSIZE);
247  if (dotFontSize<4) dotFontSize=4;
248  return dotFontSize;
249 }
250 
251 static void writeGraphHeader(FTextStream &t,const QCString &title=QCString())
252 {
253  static bool interactiveSVG = Config_getBool(INTERACTIVE_SVG);
254  t << "digraph ";
255  if (title.isEmpty())
256  {
257  t << "\"Dot Graph\"";
258  }
259  else
260  {
261  t << "\"" << convertToXML(title) << "\"";
262  }
263  t << endl << "{" << endl;
264  if (interactiveSVG) // insert a comment to force regeneration when this
265  // option is toggled
266  {
267  t << " // INTERACTIVE_SVG=YES\n";
268  }
269  if (Config_getBool(DOT_TRANSPARENT))
270  {
271  t << " bgcolor=\"transparent\";" << endl;
272  }
273  t << " edge [fontname=\"" << FONTNAME << "\","
274  "fontsize=\"" << FONTSIZE << "\","
275  "labelfontname=\"" << FONTNAME << "\","
276  "labelfontsize=\"" << FONTSIZE << "\"];\n";
277  t << " node [fontname=\"" << FONTNAME << "\","
278  "fontsize=\"" << FONTSIZE << "\",shape=record];\n";
279 }
280 
282 {
283  t << "}" << endl;
284 }
285 
286 static QCString replaceRef(const QCString &buf,const QCString relPath,
287  bool urlOnly,const QCString &context,const QCString &target=QCString())
288 {
289  // search for href="...", store ... part in link
290  QCString href = "href";
291  //bool isXLink=FALSE;
292  int len = 6;
293  int indexS = buf.find("href=\""), indexE;
294  if (indexS>5 && buf.find("xlink:href=\"")!=-1) // XLink href (for SVG)
295  {
296  indexS-=6;
297  len+=6;
298  href.prepend("xlink:");
299  //isXLink=TRUE;
300  }
301  if (indexS>=0 && (indexE=buf.find('"',indexS+len))!=-1)
302  {
303  QCString link = buf.mid(indexS+len,indexE-indexS-len);
304  QCString result;
305  if (urlOnly) // for user defined dot graphs
306  {
307  if (link.left(5)=="\\ref " || link.left(5)=="@ref ") // \ref url
308  {
309  result=href+"=\"";
310  // fake ref node to resolve the url
311  DocRef *df = new DocRef( (DocNode*) 0, link.mid(5), context );
312  result+=externalRef(relPath,df->ref(),TRUE);
313  if (!df->file().isEmpty())
314  result += df->file().data() + Doxygen::htmlFileExtension;
315  if (!df->anchor().isEmpty())
316  result += "#" + df->anchor();
317  delete df;
318  result += "\"";
319  }
320  else
321  {
322  result = href+"=\"" + link + "\"";
323  }
324  }
325  else // ref$url (external ref via tag file), or $url (local ref)
326  {
327  int marker = link.find('$');
328  if (marker!=-1)
329  {
330  QCString ref = link.left(marker);
331  QCString url = link.mid(marker+1);
332  if (!ref.isEmpty())
333  {
334  result = externalLinkTarget() + externalRef(relPath,ref,FALSE);
335  }
336  result+= href+"=\"";
337  result+=externalRef(relPath,ref,TRUE);
338  result+= url + "\"";
339  }
340  else // should not happen, but handle properly anyway
341  {
342  result = href+"=\"" + link + "\"";
343  }
344  }
345  if (!target.isEmpty())
346  {
347  result+=" target=\""+target+"\"";
348  }
349  QCString leftPart = buf.left(indexS);
350  QCString rightPart = buf.mid(indexE+1);
351  return leftPart + result + rightPart;
352  }
353  else
354  {
355  return buf;
356  }
357 }
358 
370 static bool convertMapFile(FTextStream &t,const char *mapName,
371  const QCString relPath, bool urlOnly=FALSE,
372  const QCString &context=QCString())
373 {
374  QFile f(mapName);
375  if (!f.open(IO_ReadOnly))
376  {
377  err("problems opening map file %s for inclusion in the docs!\n"
378  "If you installed Graphviz/dot after a previous failing run, \n"
379  "try deleting the output directory and rerun doxygen.\n",mapName);
380  return FALSE;
381  }
382  const int maxLineLen=10240;
383  while (!f.atEnd()) // foreach line
384  {
385  QCString buf(maxLineLen);
386  int numBytes = f.readLine(buf.rawData(),maxLineLen);
387  if (numBytes>0)
388  {
389  buf.resize(numBytes+1);
390 
391  if (buf.left(5)=="<area")
392  {
393  t << replaceRef(buf,relPath,urlOnly,context);
394  }
395  }
396  }
397  return TRUE;
398 }
399 
400 static QCString g_dotFontPath;
401 
402 static void setDotFontPath(const char *path)
403 {
404  ASSERT(g_dotFontPath.isEmpty());
405  g_dotFontPath = portable_getenv("DOTFONTPATH");
406  QCString newFontPath = Config_getString(DOT_FONTPATH);
407  QCString spath = path;
408  if (!newFontPath.isEmpty() && !spath.isEmpty())
409  {
410  newFontPath.prepend(spath+portable_pathListSeparator());
411  }
412  else if (newFontPath.isEmpty() && !spath.isEmpty())
413  {
414  newFontPath=path;
415  }
416  else
417  {
418  portable_unsetenv("DOTFONTPATH");
419  return;
420  }
421  portable_setenv("DOTFONTPATH",newFontPath);
422 }
423 
424 static void unsetDotFontPath()
425 {
426  if (g_dotFontPath.isEmpty())
427  {
428  portable_unsetenv("DOTFONTPATH");
429  }
430  else
431  {
432  portable_setenv("DOTFONTPATH",g_dotFontPath);
433  }
434  g_dotFontPath="";
435 }
436 
437 static bool readBoundingBox(const char *fileName,int *width,int *height,bool isEps)
438 {
439  QCString bb = isEps ? QCString("%%PageBoundingBox:") : QCString("/MediaBox [");
440  QFile f(fileName);
441  if (!f.open(IO_ReadOnly|IO_Raw))
442  {
443  //printf("readBoundingBox: could not open %s\n",fileName);
444  return FALSE;
445  }
446  const int maxLineLen=1024;
447  char buf[maxLineLen];
448  while (!f.atEnd())
449  {
450  int numBytes = f.readLine(buf,maxLineLen-1); // read line
451  if (numBytes>0)
452  {
453  buf[numBytes]='\0';
454  const char *p = strstr(buf,bb);
455  if (p) // found PageBoundingBox or /MediaBox string
456  {
457  int x,y;
458  if (sscanf(p+bb.length(),"%d %d %d %d",&x,&y,width,height)!=4)
459  {
460  //printf("readBoundingBox sscanf fail\n");
461  return FALSE;
462  }
463  return TRUE;
464  }
465  }
466  else // read error!
467  {
468  //printf("Read error %d!\n",numBytes);
469  return FALSE;
470  }
471  }
472  err("Failed to extract bounding box from generated diagram file %s\n",fileName);
473  return FALSE;
474 }
475 
476 static bool writeVecGfxFigure(FTextStream &out,const QCString &baseName,
477  const QCString &figureName)
478 {
479  int width=400,height=550;
480  static bool usePdfLatex = Config_getBool(USE_PDFLATEX);
481  if (usePdfLatex)
482  {
483  if (!readBoundingBox(figureName+".pdf",&width,&height,FALSE))
484  {
485  //printf("writeVecGfxFigure()=0\n");
486  return FALSE;
487  }
488  }
489  else
490  {
491  if (!readBoundingBox(figureName+".eps",&width,&height,TRUE))
492  {
493  //printf("writeVecGfxFigure()=0\n");
494  return FALSE;
495  }
496  }
497  //printf("Got PDF/EPS size %d,%d\n",width,height);
498  int maxWidth = 350; /* approx. page width in points, excl. margins */
499  int maxHeight = 550; /* approx. page height in points, excl. margins */
500  out << "\\nopagebreak\n"
501  "\\begin{figure}[H]\n"
502  "\\begin{center}\n"
503  "\\leavevmode\n";
504  if (width>maxWidth || height>maxHeight) // figure too big for page
505  {
506  // c*width/maxWidth > c*height/maxHeight, where c=maxWidth*maxHeight>0
507  if (width*maxHeight>height*maxWidth)
508  {
509  out << "\\includegraphics[width=" << maxWidth << "pt]";
510  }
511  else
512  {
513  out << "\\includegraphics[height=" << maxHeight << "pt]";
514  }
515  }
516  else
517  {
518  out << "\\includegraphics[width=" << width << "pt]";
519  }
520 
521  out << "{" << baseName << "}\n"
522  "\\end{center}\n"
523  "\\end{figure}\n";
524 
525  //printf("writeVecGfxFigure()=1\n");
526  return TRUE;
527 }
528 
529 // extract size from a dot generated SVG file
530 static bool readSVGSize(const QCString &fileName,int *width,int *height)
531 {
532  bool found=FALSE;
533  QFile f(fileName);
534  if (!f.open(IO_ReadOnly))
535  {
536  return FALSE;
537  }
538  const int maxLineLen=4096;
539  char buf[maxLineLen];
540  while (!f.atEnd() && !found)
541  {
542  int numBytes = f.readLine(buf,maxLineLen-1); // read line
543  if (numBytes>0)
544  {
545  buf[numBytes]='\0';
546  if (qstrncmp(buf,"<!--zoomable ",13)==0)
547  {
548  *width=-1;
549  *height=-1;
550  sscanf(buf,"<!--zoomable %d",height);
551  //printf("Found zoomable for %s!\n",fileName.data());
552  found=TRUE;
553  }
554  else if (sscanf(buf,"<svg width=\"%dpt\" height=\"%dpt\"",width,height)==2)
555  {
556  //printf("Found fixed size %dx%d for %s!\n",*width,*height,fileName.data());
557  found=TRUE;
558  }
559  }
560  else // read error!
561  {
562  //printf("Read error %d!\n",numBytes);
563  return FALSE;
564  }
565  }
566  return TRUE;
567 }
568 
570 {
571  out << "<p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p>";
572 }
573 
574 // check if a reference to a SVG figure can be written and does so if possible.
575 // return FALSE if not possible (for instance because the SVG file is not yet generated).
576 static bool writeSVGFigureLink(FTextStream &out,const QCString &relPath,
577  const QCString &baseName,const QCString &absImgName)
578 {
579  int width=600,height=600;
580  if (!readSVGSize(absImgName,&width,&height))
581  {
582  return FALSE;
583  }
584  if (width==-1)
585  {
586  if (height<=60)
587  height=300;
588  else
589  height+=300; // add some extra space for zooming
590  if (height>600) height=600; // clip to maximum height of 600 pixels
591  out << "<div class=\"zoom\">";
592  //out << "<object type=\"image/svg+xml\" data=\""
593  //out << "<embed type=\"image/svg+xml\" src=\""
594  out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
595  << relPath << baseName << ".svg\" width=\"100%\" height=\"" << height << "\">";
596  }
597  else
598  {
599  //out << "<object type=\"image/svg+xml\" data=\""
600  //out << "<embed type=\"image/svg+xml\" src=\""
601  out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\""
602  << relPath << baseName << ".svg\" width=\""
603  << ((width*96+48)/72) << "\" height=\""
604  << ((height*96+48)/72) << "\">";
605  }
607  //out << "</object>";
608  //out << "</embed>";
609  out << "</iframe>";
610  if (width==-1)
611  {
612  out << "</div>";
613  }
614 
615  return TRUE;
616 }
617 
618 // since dot silently reproduces the input file when it does not
619 // support the PNG format, we need to check the result.
620 static void checkDotResult(const char *imgExt, const char *imgName)
621 {
622  if (qstrcmp(imgExt,"png")==0)
623  {
624  FILE *f = portable_fopen(imgName,"rb");
625  if (f)
626  {
627  char data[4];
628  if (fread(data,1,4,f)==4)
629  {
630  if (!(data[1]=='P' && data[2]=='N' && data[3]=='G'))
631  {
632  err("Image `%s' produced by dot is not a valid PNG!\n"
633  "You should either select a different format "
634  "(DOT_IMAGE_FORMAT in the config file) or install a more "
635  "recent version of graphviz (1.7+)\n",imgName
636  );
637  }
638  }
639  else
640  {
641  err("Could not read image `%s' generated by dot!\n",imgName);
642  }
643  fclose(f);
644  }
645  else
646  {
647  err("Could not open image `%s' generated by dot!\n",imgName);
648  }
649  }
650 }
651 
652 static bool insertMapFile(FTextStream &out,const QCString &mapFile,
653  const QCString &relPath,const QCString &mapLabel)
654 {
655  QFileInfo fi(mapFile);
656  if (fi.exists() && fi.size()>0) // reuse existing map file
657  {
658  QGString tmpstr;
659  FTextStream tmpout(&tmpstr);
660  convertMapFile(tmpout,mapFile,relPath);
661  if (!tmpstr.isEmpty())
662  {
663  out << "<map name=\"" << mapLabel << "\" id=\"" << mapLabel << "\">" << endl;
664  out << tmpstr;
665  out << "</map>" << endl;
666  }
667  return TRUE;
668  }
669  return FALSE; // no map file yet, need to generate it
670 }
671 
672 static void removeDotGraph(const QCString &dotName)
673 {
674  static bool dotCleanUp = Config_getBool(DOT_CLEANUP);
675  if (dotCleanUp)
676  {
677  QDir d;
678  d.remove(dotName);
679  }
680 }
681 
682 
683 
689 static bool checkAndUpdateMd5Signature(const QCString &baseName,
690  const QCString &md5)
691 {
692  QFile f(baseName+".md5");
693  if (f.open(IO_ReadOnly))
694  {
695  // read checksum
696  QCString md5stored(33);
697  int bytesRead=f.readBlock(md5stored.rawData(),32);
698  md5stored[32]='\0';
699  // compare checksum
700  if (bytesRead==32 && md5==md5stored)
701  {
702  // bail out if equal
703  return FALSE;
704  }
705  }
706  f.close();
707  // create checksum file
708  if (f.open(IO_WriteOnly))
709  {
710  f.writeBlock(md5.data(),32);
711  f.close();
712  }
713  return TRUE;
714 }
715 
716 static bool checkDeliverables(const QCString &file1,
717  const QCString &file2=QCString())
718 {
719  bool file1Ok = TRUE;
720  bool file2Ok = TRUE;
721  if (!file1.isEmpty())
722  {
723  QFileInfo fi(file1);
724  file1Ok = (fi.exists() && fi.size()>0);
725  }
726  if (!file2.isEmpty())
727  {
728  QFileInfo fi(file2);
729  file2Ok = (fi.exists() && fi.size()>0);
730  }
731  return file1Ok && file2Ok;
732 }
733 
734 //--------------------------------------------------------------------
735 
736 inline int DotNode::findParent( DotNode *n )
737 {
738  if ( !m_parents ) return -1;
739  return m_parents->find(n);
740 }
741 
742 //--------------------------------------------------------------------
743 
744 int DotNodeList::compareValues(const DotNode *n1,const DotNode *n2) const
745 {
746  return qstricmp(n1->m_label,n2->m_label);
747 }
748 
749 //--------------------------------------------------------------------
750 
751 DotRunner::DotRunner(const QCString &file,const QCString &path,
752  bool checkResult,const QCString &imageName)
753  : m_dotExe(Config_getString(DOT_PATH)+"dot"),
754  m_file(file), m_path(path),
755  m_checkResult(checkResult), m_imageName(imageName),
756  m_imgExt(getDotImageExtension())
757 {
758  static bool dotCleanUp = Config_getBool(DOT_CLEANUP);
759  static bool dotMultiTargets = Config_getBool(DOT_MULTI_TARGETS);
760  m_cleanUp = dotCleanUp;
761  m_multiTargets = dotMultiTargets;
762  m_jobs.setAutoDelete(TRUE);
763 }
764 
765 void DotRunner::addJob(const char *format,const char *output)
766 {
767  QCString args = QCString("-T")+format+" -o \""+output+"\"";
768  m_jobs.append(new DotConstString(args));
769 }
770 
771 void DotRunner::addPostProcessing(const char *cmd,const char *args)
772 {
773  m_postCmd.set(cmd);
774  m_postArgs.set(args);
775 }
776 
778 {
779  int exitCode=0;
780 
781  QCString dotArgs;
782  QListIterator<DotConstString> li(m_jobs);
783  DotConstString *s;
784  if (m_multiTargets)
785  {
786  dotArgs=QCString("\"")+m_file.data()+"\"";
787  for (li.toFirst();(s=li.current());++li)
788  {
789  dotArgs+=' ';
790  dotArgs+=s->data();
791  }
792  if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0)
793  {
794  goto error;
795  }
796  }
797  else
798  {
799  for (li.toFirst();(s=li.current());++li)
800  {
801  dotArgs=QCString("\"")+m_file.data()+"\" "+s->data();
802  if ((exitCode=portable_system(m_dotExe.data(),dotArgs,FALSE))!=0)
803  {
804  goto error;
805  }
806  }
807  }
809  {
810  err("Problems running '%s' as a post-processing step for dot output\n",m_postCmd.data());
811  return FALSE;
812  }
813  if (m_checkResult)
814  {
816  }
817  if (m_cleanUp)
818  {
819  //printf("removing dot file %s\n",m_file.data());
820  //QDir(path).remove(file);
823  }
824  return TRUE;
825 error:
826  err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n",
827  exitCode,m_dotExe.data(),dotArgs.data());
828  return FALSE;
829 }
830 
831 //--------------------------------------------------------------------
832 
833 DotFilePatcher::DotFilePatcher(const char *patchFile)
834  : m_patchFile(patchFile)
835 {
836  m_maps.setAutoDelete(TRUE);
837 }
838 
839 QCString DotFilePatcher::file() const
840 {
841  return m_patchFile;
842 }
843 
844 int DotFilePatcher::addMap(const QCString &mapFile,const QCString &relPath,
845  bool urlOnly,const QCString &context,const QCString &label)
846 {
847  int id = m_maps.count();
848  Map *map = new Map;
849  map->mapFile = mapFile;
850  map->relPath = relPath;
851  map->urlOnly = urlOnly;
852  map->context = context;
853  map->label = label;
854  map->zoomable = FALSE;
855  map->graphId = -1;
856  m_maps.append(map);
857  return id;
858 }
859 
860 int DotFilePatcher::addFigure(const QCString &baseName,
861  const QCString &figureName,bool heightCheck)
862 {
863  int id = m_maps.count();
864  Map *map = new Map;
865  map->mapFile = figureName;
866  map->urlOnly = heightCheck;
867  map->label = baseName;
868  map->zoomable = FALSE;
869  map->graphId = -1;
870  m_maps.append(map);
871  return id;
872 }
873 
874 int DotFilePatcher::addSVGConversion(const QCString &relPath,bool urlOnly,
875  const QCString &context,bool zoomable,
876  int graphId)
877 {
878  int id = m_maps.count();
879  Map *map = new Map;
880  map->relPath = relPath;
881  map->urlOnly = urlOnly;
882  map->context = context;
883  map->zoomable = zoomable;
884  map->graphId = graphId;
885  m_maps.append(map);
886  return id;
887 }
888 
889 int DotFilePatcher::addSVGObject(const QCString &baseName,
890  const QCString &absImgName,
891  const QCString &relPath)
892 {
893  int id = m_maps.count();
894  Map *map = new Map;
895  map->mapFile = absImgName;
896  map->relPath = relPath;
897  map->label = baseName;
898  map->zoomable = FALSE;
899  map->graphId = -1;
900  m_maps.append(map);
901  return id;
902 }
903 
905 {
906  //printf("DotFilePatcher::run(): %s\n",m_patchFile.data());
907  static bool interactiveSVG = Config_getBool(INTERACTIVE_SVG);
908  bool isSVGFile = m_patchFile.right(4)==".svg";
909  int graphId = -1;
910  QCString relPath;
911  if (isSVGFile)
912  {
913  Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
914  interactiveSVG = interactiveSVG && map->zoomable;
915  graphId = map->graphId;
916  relPath = map->relPath;
917  //printf("DotFilePatcher::addSVGConversion: file=%s zoomable=%d\n",
918  // m_patchFile.data(),map->zoomable);
919  }
920  QString tmpName = QString::fromUtf8(m_patchFile+".tmp");
921  QString patchFile = QString::fromUtf8(m_patchFile);
922  if (!QDir::current().rename(patchFile,tmpName))
923  {
924  err("Failed to rename file %s to %s!\n",m_patchFile.data(),tmpName.data());
925  return FALSE;
926  }
927  QFile fi(tmpName);
928  QFile fo(patchFile);
929  if (!fi.open(IO_ReadOnly))
930  {
931  err("problem opening file %s for patching!\n",tmpName.data());
932  QDir::current().rename(tmpName,patchFile);
933  return FALSE;
934  }
935  if (!fo.open(IO_WriteOnly))
936  {
937  err("problem opening file %s for patching!\n",m_patchFile.data());
938  QDir::current().rename(tmpName,patchFile);
939  return FALSE;
940  }
941  FTextStream t(&fo);
942  const int maxLineLen=100*1024;
943  int lineNr=1;
944  int width,height;
945  bool insideHeader=FALSE;
946  bool replacedHeader=FALSE;
947  bool foundSize=FALSE;
948  while (!fi.atEnd()) // foreach line
949  {
950  QCString line(maxLineLen);
951  int numBytes = fi.readLine(line.rawData(),maxLineLen);
952  if (numBytes<=0)
953  {
954  break;
955  }
956  line.resize(numBytes+1);
957 
958  //printf("line=[%s]\n",line.stripWhiteSpace().data());
959  int i;
960  ASSERT(numBytes<maxLineLen);
961  if (isSVGFile)
962  {
963  if (interactiveSVG)
964  {
965  if (line.find("<svg")!=-1 && !replacedHeader)
966  {
967  int count;
968  count = sscanf(line.data(),"<svg width=\"%dpt\" height=\"%dpt\"",&width,&height);
969  //printf("width=%d height=%d\n",width,height);
970  foundSize = count==2 && (width>500 || height>450);
971  if (foundSize) insideHeader=TRUE;
972  }
973  else if (insideHeader && !replacedHeader && line.find("<title>")!=-1)
974  {
975  if (foundSize)
976  {
977  // insert special replacement header for interactive SVGs
978  t << "<!--zoomable " << height << " -->\n";
979  t << svgZoomHeader;
980  t << "var viewWidth = " << width << ";\n";
981  t << "var viewHeight = " << height << ";\n";
982  if (graphId>=0)
983  {
984  t << "var sectionId = 'dynsection-" << graphId << "';\n";
985  }
986  t << "</script>\n";
987  t << "<script xlink:href=\"" << relPath << "svgpan.js\"/>\n";
988  t << "<svg id=\"graph\" class=\"graph\">\n";
989  t << "<g id=\"viewport\">\n";
990  }
991  insideHeader=FALSE;
992  replacedHeader=TRUE;
993  }
994  }
995  if (!insideHeader || !foundSize) // copy SVG and replace refs,
996  // unless we are inside the header of the SVG.
997  // Then we replace it with another header.
998  {
999  Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
1000  t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
1001  }
1002  }
1003  else if ((i=line.find("<!-- SVG"))!=-1 || (i=line.find("[!-- SVG"))!=-1)
1004  {
1005  //printf("Found marker at %d\n",i);
1006  int mapId=-1;
1007  t << line.left(i);
1008  int n = sscanf(line.data()+i+1,"!-- SVG %d",&mapId);
1009  if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1010  {
1011  int e = QMAX(line.find("--]"),line.find("-->"));
1012  Map *map = m_maps.at(mapId);
1013  //printf("DotFilePatcher::writeSVGFigure: file=%s zoomable=%d\n",
1014  // m_patchFile.data(),map->zoomable);
1015  if (!writeSVGFigureLink(t,map->relPath,map->label,map->mapFile))
1016  {
1017  err("Problem extracting size from SVG file %s\n",map->mapFile.data());
1018  }
1019  if (e!=-1) t << line.mid(e+3);
1020  }
1021  else // error invalid map id!
1022  {
1023  err("Found invalid SVG id in file %s!\n",m_patchFile.data());
1024  t << line.mid(i);
1025  }
1026  }
1027  else if ((i=line.find("<!-- MAP"))!=-1)
1028  {
1029  int mapId=-1;
1030  t << line.left(i);
1031  int n = sscanf(line.data()+i,"<!-- MAP %d",&mapId);
1032  if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1033  {
1034  Map *map = m_maps.at(mapId);
1035  //printf("patching MAP %d in file %s with contents of %s\n",
1036  // mapId,m_patchFile.data(),map->mapFile.data());
1037  t << "<map name=\"" << map->label << "\" id=\"" << map->label << "\">" << endl;
1038  convertMapFile(t,map->mapFile,map->relPath,map->urlOnly,map->context);
1039  t << "</map>" << endl;
1040  }
1041  else // error invalid map id!
1042  {
1043  err("Found invalid MAP id in file %s!\n",m_patchFile.data());
1044  t << line.mid(i);
1045  }
1046  }
1047  else if ((i=line.find("% FIG"))!=-1)
1048  {
1049  int mapId=-1;
1050  int n = sscanf(line.data()+i+2,"FIG %d",&mapId);
1051  //printf("line='%s' n=%d\n",line.data()+i,n);
1052  if (n==1 && mapId>=0 && mapId<(int)m_maps.count())
1053  {
1054  Map *map = m_maps.at(mapId);
1055  //printf("patching FIG %d in file %s with contents of %s\n",
1056  // mapId,m_patchFile.data(),map->mapFile.data());
1057  if (!writeVecGfxFigure(t,map->label,map->mapFile))
1058  {
1059  err("problem writing FIG %d figure!\n",mapId);
1060  return FALSE;
1061  }
1062  }
1063  else // error invalid map id!
1064  {
1065  err("Found invalid bounding FIG %d in file %s!\n",mapId,m_patchFile.data());
1066  t << line;
1067  }
1068  }
1069  else
1070  {
1071  t << line;
1072  }
1073  lineNr++;
1074  }
1075  fi.close();
1076  if (isSVGFile && interactiveSVG && replacedHeader)
1077  {
1078  QCString orgName=m_patchFile.left(m_patchFile.length()-4)+"_org.svg";
1079  t << substitute(svgZoomFooter,"$orgname",stripPath(orgName));
1080  fo.close();
1081  // keep original SVG file so we can refer to it, we do need to replace
1082  // dummy link by real ones
1083  QFile fi(tmpName);
1084  QFile fo(orgName);
1085  if (!fi.open(IO_ReadOnly))
1086  {
1087  err("problem opening file %s for reading!\n",tmpName.data());
1088  return FALSE;
1089  }
1090  if (!fo.open(IO_WriteOnly))
1091  {
1092  err("problem opening file %s for writing!\n",orgName.data());
1093  return FALSE;
1094  }
1095  FTextStream t(&fo);
1096  while (!fi.atEnd()) // foreach line
1097  {
1098  QCString line(maxLineLen);
1099  int numBytes = fi.readLine(line.rawData(),maxLineLen);
1100  if (numBytes<=0)
1101  {
1102  break;
1103  }
1104  line.resize(numBytes+1);
1105  Map *map = m_maps.at(0); // there is only one 'map' for a SVG file
1106  t << replaceRef(line,map->relPath,map->urlOnly,map->context,"_top");
1107  }
1108  fi.close();
1109  fo.close();
1110  }
1111  // remove temporary file
1112  QDir::current().remove(tmpName);
1113  return TRUE;
1114 }
1115 
1116 //--------------------------------------------------------------------
1117 
1119 {
1120  QMutexLocker locker(&m_mutex);
1121  m_queue.enqueue(runner);
1122  m_bufferNotEmpty.wakeAll();
1123 }
1124 
1126 {
1127  QMutexLocker locker(&m_mutex);
1128  while (m_queue.isEmpty())
1129  {
1130  // wait until something is added to the queue
1131  m_bufferNotEmpty.wait(&m_mutex);
1132  }
1133  DotRunner *result = m_queue.dequeue();
1134  return result;
1135 }
1136 
1138 {
1139  QMutexLocker locker(&m_mutex);
1140  return m_queue.count();
1141 }
1142 
1143 //--------------------------------------------------------------------
1144 
1146  : m_queue(queue)
1147 {
1148  m_cleanupItems.setAutoDelete(TRUE);
1149 }
1150 
1152 {
1153  DotRunner *runner;
1154  while ((runner=m_queue->dequeue()))
1155  {
1156  runner->run();
1157  const DotRunner::CleanupItem &cleanup = runner->cleanup();
1158  if (!cleanup.file.isEmpty())
1159  {
1160  m_cleanupItems.append(new DotRunner::CleanupItem(cleanup));
1161  }
1162  }
1163 }
1164 
1166 {
1167  QListIterator<DotRunner::CleanupItem> it(m_cleanupItems);
1169  for (;(ci=it.current());++it)
1170  {
1171  QDir(ci->path.data()).remove(ci->file.data());
1172  }
1173 }
1174 
1175 //--------------------------------------------------------------------
1176 
1178 
1180 {
1181  if (!m_theInstance)
1182  {
1183  m_theInstance = new DotManager;
1184  }
1185  return m_theInstance;
1186 }
1187 
1188 DotManager::DotManager() : m_dotMaps(1009)
1189 {
1190  m_dotRuns.setAutoDelete(TRUE);
1191  m_dotMaps.setAutoDelete(TRUE);
1192  m_queue = new DotRunnerQueue;
1193  int i;
1194  int numThreads = QMIN(32,Config_getInt(DOT_NUM_THREADS));
1195  if (numThreads!=1)
1196  {
1197  if (numThreads==0) numThreads = QMAX(2,QThread::idealThreadCount()+1);
1198  for (i=0;i<numThreads;i++)
1199  {
1200  DotWorkerThread *thread = new DotWorkerThread(m_queue);
1201  thread->start();
1202  if (thread->isRunning())
1203  {
1204  m_workers.append(thread);
1205  }
1206  else // no more threads available!
1207  {
1208  delete thread;
1209  }
1210  }
1211  ASSERT(m_workers.count()>0);
1212  }
1213 }
1214 
1216 {
1217  delete m_queue;
1218 }
1219 
1221 {
1222  m_dotRuns.append(run);
1223 }
1224 
1225 int DotManager::addMap(const QCString &file,const QCString &mapFile,
1226  const QCString &relPath,bool urlOnly,const QCString &context,
1227  const QCString &label)
1228 {
1229  DotFilePatcher *map = m_dotMaps.find(file);
1230  if (map==0)
1231  {
1232  map = new DotFilePatcher(file);
1233  m_dotMaps.append(file,map);
1234  }
1235  return map->addMap(mapFile,relPath,urlOnly,context,label);
1236 }
1237 
1238 int DotManager::addFigure(const QCString &file,const QCString &baseName,
1239  const QCString &figureName,bool heightCheck)
1240 {
1241  DotFilePatcher *map = m_dotMaps.find(file);
1242  if (map==0)
1243  {
1244  map = new DotFilePatcher(file);
1245  m_dotMaps.append(file,map);
1246  }
1247  return map->addFigure(baseName,figureName,heightCheck);
1248 }
1249 
1250 int DotManager::addSVGConversion(const QCString &file,const QCString &relPath,
1251  bool urlOnly,const QCString &context,bool zoomable,
1252  int graphId)
1253 {
1254  DotFilePatcher *map = m_dotMaps.find(file);
1255  if (map==0)
1256  {
1257  map = new DotFilePatcher(file);
1258  m_dotMaps.append(file,map);
1259  }
1260  return map->addSVGConversion(relPath,urlOnly,context,zoomable,graphId);
1261 }
1262 
1263 int DotManager::addSVGObject(const QCString &file,const QCString &baseName,
1264  const QCString &absImgName,const QCString &relPath)
1265 {
1266  DotFilePatcher *map = m_dotMaps.find(file);
1267  if (map==0)
1268  {
1269  map = new DotFilePatcher(file);
1270  m_dotMaps.append(file,map);
1271  }
1272  return map->addSVGObject(baseName,absImgName,relPath);
1273 }
1274 
1276 {
1277  uint numDotRuns = m_dotRuns.count();
1278  uint numDotMaps = m_dotMaps.count();
1279  if (numDotRuns+numDotMaps>1)
1280  {
1281  if (m_workers.count()==0)
1282  {
1283  msg("Generating dot graphs in single threaded mode...\n");
1284  }
1285  else
1286  {
1287  msg("Generating dot graphs using %d parallel threads...\n",QMIN(numDotRuns+numDotMaps,m_workers.count()));
1288  }
1289  }
1290  int i=1;
1291  QListIterator<DotRunner> li(m_dotRuns);
1292 
1293  bool setPath=FALSE;
1294  if (Config_getBool(GENERATE_HTML))
1295  {
1296  setDotFontPath(Config_getString(HTML_OUTPUT));
1297  setPath=TRUE;
1298  }
1299  else if (Config_getBool(GENERATE_LATEX))
1300  {
1301  setDotFontPath(Config_getString(LATEX_OUTPUT));
1302  setPath=TRUE;
1303  }
1304  else if (Config_getBool(GENERATE_RTF))
1305  {
1306  setDotFontPath(Config_getString(RTF_OUTPUT));
1307  setPath=TRUE;
1308  }
1310  // fill work queue with dot operations
1311  DotRunner *dr;
1312  int prev=1;
1313  if (m_workers.count()==0) // no threads to work with
1314  {
1315  for (li.toFirst();(dr=li.current());++li)
1316  {
1317  msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1318  dr->run();
1319  prev++;
1320  }
1321  }
1322  else // use multiple threads to run instances of dot in parallel
1323  {
1324  for (li.toFirst();(dr=li.current());++li)
1325  {
1326  m_queue->enqueue(dr);
1327  }
1328  // wait for the queue to become empty
1329  while ((i=m_queue->count())>0)
1330  {
1331  i = numDotRuns - i;
1332  while (i>=prev)
1333  {
1334  msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1335  prev++;
1336  }
1337  portable_sleep(100);
1338  }
1339  while ((int)numDotRuns>=prev)
1340  {
1341  msg("Running dot for graph %d/%d\n",prev,numDotRuns);
1342  prev++;
1343  }
1344  // signal the workers we are done
1345  for (i=0;i<(int)m_workers.count();i++)
1346  {
1347  m_queue->enqueue(0); // add terminator for each worker
1348  }
1349  // wait for the workers to finish
1350  for (i=0;i<(int)m_workers.count();i++)
1351  {
1352  m_workers.at(i)->wait();
1353  }
1354  // clean up dot files from main thread
1355  for (i=0;i<(int)m_workers.count();i++)
1356  {
1357  m_workers.at(i)->cleanup();
1358  }
1359  }
1361  if (setPath)
1362  {
1363  unsetDotFontPath();
1364  }
1365 
1366  // patch the output file and insert the maps and figures
1367  i=1;
1369  DotFilePatcher *map;
1370  // since patching the svg files may involve patching the header of the SVG
1371  // (for zoomable SVGs), and patching the .html files requires reading that
1372  // header after the SVG is patched, we first process the .svg files and
1373  // then the other files.
1374  for (di.toFirst();(map=di.current());++di)
1375  {
1376  if (map->file().right(4)==".svg")
1377  {
1378  msg("Patching output file %d/%d\n",i,numDotMaps);
1379  if (!map->run()) return FALSE;
1380  i++;
1381  }
1382  }
1383  for (di.toFirst();(map=di.current());++di)
1384  {
1385  if (map->file().right(4)!=".svg")
1386  {
1387  msg("Patching output file %d/%d\n",i,numDotMaps);
1388  if (!map->run()) return FALSE;
1389  i++;
1390  }
1391  }
1392  return TRUE;
1393 }
1394 
1395 //--------------------------------------------------------------------
1396 
1397 
1401 static void deleteNodes(DotNode *node,SDict<DotNode> *skipNodes=0)
1402 {
1403  //printf("deleteNodes skipNodes=%p\n",skipNodes);
1404  static DotNodeList deletedNodes;
1405  deletedNodes.setAutoDelete(TRUE);
1406  node->deleteNode(deletedNodes,skipNodes); // collect nodes to be deleted.
1407  deletedNodes.clear(); // actually remove the nodes.
1408 }
1409 
1410 DotNode::DotNode(int n,const char *lab,const char *tip, const char *url,
1411  bool isRoot,ClassDef *cd)
1412  : m_subgraphId(-1)
1413  , m_number(n)
1414  , m_label(lab)
1415  , m_tooltip(tip)
1416  , m_url(url)
1417  , m_parents(0)
1418  , m_children(0)
1419  , m_edgeInfo(0)
1420  , m_deleted(FALSE)
1421  , m_written(FALSE)
1422  , m_hasDoc(FALSE)
1423  , m_isRoot(isRoot)
1424  , m_classDef(cd)
1425  , m_visible(FALSE)
1426  , m_truncated(Unknown)
1427  , m_distance(1000)
1428 {
1429 }
1430 
1432 {
1433  delete m_children;
1434  delete m_parents;
1435  delete m_edgeInfo;
1436 }
1437 
1439  int edgeColor,
1440  int edgeStyle,
1441  const char *edgeLab,
1442  const char *edgeURL,
1443  int edgeLabCol
1444  )
1445 {
1446  if (m_children==0)
1447  {
1448  m_children = new QList<DotNode>;
1449  m_edgeInfo = new QList<EdgeInfo>;
1450  m_edgeInfo->setAutoDelete(TRUE);
1451  }
1452  m_children->append(n);
1453  EdgeInfo *ei = new EdgeInfo;
1454  ei->m_color = edgeColor;
1455  ei->m_style = edgeStyle;
1456  ei->m_label = edgeLab;
1457  ei->m_url = edgeURL;
1458  if (edgeLabCol==-1)
1459  ei->m_labColor=edgeColor;
1460  else
1461  ei->m_labColor=edgeLabCol;
1462  m_edgeInfo->append(ei);
1463 }
1464 
1466 {
1467  if (m_parents==0)
1468  {
1469  m_parents = new QList<DotNode>;
1470  }
1471  m_parents->append(n);
1472 }
1473 
1475 {
1476  if (m_children) m_children->remove(n);
1477 }
1478 
1480 {
1481  if (m_parents) m_parents->remove(n);
1482 }
1483 
1484 void DotNode::deleteNode(DotNodeList &deletedList,SDict<DotNode> *skipNodes)
1485 {
1486  if (m_deleted) return; // avoid recursive loops in case the graph has cycles
1487  m_deleted=TRUE;
1488  if (m_parents!=0) // delete all parent nodes of this node
1489  {
1490  QListIterator<DotNode> dnlip(*m_parents);
1491  DotNode *pn;
1492  for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
1493  {
1494  //pn->removeChild(this);
1495  pn->deleteNode(deletedList,skipNodes);
1496  }
1497  }
1498  if (m_children!=0) // delete all child nodes of this node
1499  {
1500  QListIterator<DotNode> dnlic(*m_children);
1501  DotNode *cn;
1502  for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
1503  {
1504  //cn->removeParent(this);
1505  cn->deleteNode(deletedList,skipNodes);
1506  }
1507  }
1508  // add this node to the list of deleted nodes.
1509  //printf("skipNodes=%p find(%p)=%p\n",skipNodes,this,skipNodes ? skipNodes->find((int)this) : 0);
1510  if (skipNodes==0 || skipNodes->find((char*)this)==0)
1511  {
1512  //printf("deleting\n");
1513  deletedList.append(this);
1514  }
1515 }
1516 
1517 void DotNode::setDistance(int distance)
1518 {
1519  if (distance<m_distance) m_distance = distance;
1520 }
1521 
1522 static QCString convertLabel(const QCString &l)
1523 {
1524  QCString result;
1525  QCString bBefore("\\_/<({[: =-+@%#~?$"); // break before character set
1526  QCString bAfter(">]),:;|"); // break after character set
1527  const char *p=l.data();
1528  if (p==0) return result;
1529  char c,pc=0;
1530  char cs[2];
1531  cs[1]=0;
1532  int len=l.length();
1533  int charsLeft=len;
1534  int sinceLast=0;
1535  int foldLen=17; // ideal text length
1536  while ((c=*p++))
1537  {
1538  QCString replacement;
1539  switch(c)
1540  {
1541  case '\\': replacement="\\\\"; break;
1542  case '\n': replacement="\\n"; break;
1543  case '<': replacement="\\<"; break;
1544  case '>': replacement="\\>"; break;
1545  case '|': replacement="\\|"; break;
1546  case '{': replacement="\\{"; break;
1547  case '}': replacement="\\}"; break;
1548  case '"': replacement="\\\""; break;
1549  default: cs[0]=c; replacement=cs; break;
1550  }
1551  // Some heuristics to insert newlines to prevent too long
1552  // boxes and at the same time prevent ugly breaks
1553  if (c=='\n')
1554  {
1555  result+=replacement;
1556  foldLen = (3*foldLen+sinceLast+2)/4;
1557  sinceLast=1;
1558  }
1559  else if ((pc!=':' || c!=':') && charsLeft>foldLen/3 && sinceLast>foldLen && bBefore.contains(c))
1560  {
1561  result+="\\l";
1562  result+=replacement;
1563  foldLen = (foldLen+sinceLast+1)/2;
1564  sinceLast=1;
1565  }
1566  else if (charsLeft>1+foldLen/4 && sinceLast>foldLen+foldLen/3 &&
1567  !isupper(c) && isupper(*p))
1568  {
1569  result+=replacement;
1570  result+="\\l";
1571  foldLen = (foldLen+sinceLast+1)/2;
1572  sinceLast=0;
1573  }
1574  else if (charsLeft>foldLen/3 && sinceLast>foldLen && bAfter.contains(c) && (c!=':' || *p!=':'))
1575  {
1576  result+=replacement;
1577  result+="\\l";
1578  foldLen = (foldLen+sinceLast+1)/2;
1579  sinceLast=0;
1580  }
1581  else
1582  {
1583  result+=replacement;
1584  sinceLast++;
1585  }
1586  charsLeft--;
1587  pc=c;
1588  }
1589  return result;
1590 }
1591 
1592 static QCString escapeTooltip(const QCString &tooltip)
1593 {
1594  QCString result;
1595  const char *p=tooltip.data();
1596  if (p==0) return result;
1597  char c;
1598  while ((c=*p++))
1599  {
1600  switch(c)
1601  {
1602  case '"': result+="\\\""; break;
1603  default: result+=c; break;
1604  }
1605  }
1606  return result;
1607 }
1608 
1610  char prot,MemberList *ml,ClassDef *scope,
1611  bool isStatic=FALSE,const QDict<void> *skipNames=0)
1612 {
1613  (void)isStatic;
1614  if (ml)
1615  {
1616  MemberListIterator mlia(*ml);
1617  MemberDef *mma;
1618  int totalCount=0;
1619  for (mlia.toFirst();(mma = mlia.current());++mlia)
1620  {
1621  if (mma->getClassDef()==scope &&
1622  (skipNames==0 || skipNames->find(mma->name())==0))
1623  {
1624  totalCount++;
1625  }
1626  }
1627 
1628  int count=0;
1629  for (mlia.toFirst();(mma = mlia.current());++mlia)
1630  {
1631  if (mma->getClassDef() == scope &&
1632  (skipNames==0 || skipNames->find(mma->name())==0))
1633  {
1634  static int limit = Config_getInt(UML_LIMIT_NUM_FIELDS);
1635  if (limit>0 && (totalCount>limit*3/2 && count>=limit))
1636  {
1637  t << theTranslator->trAndMore(QCString().sprintf("%d",totalCount-count)) << "\\l";
1638  break;
1639  }
1640  else
1641  {
1642  t << prot << " ";
1643  t << convertLabel(mma->name());
1644  if (!mma->isObjCMethod() &&
1645  (mma->isFunction() || mma->isSlot() || mma->isSignal())) t << "()";
1646  t << "\\l";
1647  count++;
1648  }
1649  }
1650  }
1651  // write member groups within the memberlist
1652  MemberGroupList *mgl = ml->getMemberGroupList();
1653  if (mgl)
1654  {
1655  MemberGroupListIterator mgli(*mgl);
1656  MemberGroup *mg;
1657  for (mgli.toFirst();(mg=mgli.current());++mgli)
1658  {
1659  if (mg->members())
1660  {
1661  writeBoxMemberList(t,prot,mg->members(),scope,isStatic,skipNames);
1662  }
1663  }
1664  }
1665  }
1666 }
1667 
1668 static QCString stripProtectionPrefix(const QCString &s)
1669 {
1670  if (!s.isEmpty() && (s[0]=='-' || s[0]=='+' || s[0]=='~' || s[0]=='#'))
1671  {
1672  return s.mid(1);
1673  }
1674  else
1675  {
1676  return s;
1677  }
1678 }
1679 
1681  GraphType gt,
1682  GraphOutputFormat /*format*/,
1683  bool hasNonReachableChildren
1684  )
1685 {
1686  const char *labCol =
1687  m_url.isEmpty() ? "grey75" : // non link
1688  (
1689  (hasNonReachableChildren) ? "red" : "black"
1690  );
1691  t << " Node" << m_number << " [label=\"";
1692  static bool umlLook = Config_getBool(UML_LOOK);
1693 
1694  if (m_classDef && umlLook && (gt==Inheritance || gt==Collaboration))
1695  {
1696  // add names shown as relations to a dictionary, so we don't show
1697  // them as attributes as well
1698  QDict<void> arrowNames(17);
1699  if (m_edgeInfo)
1700  {
1701  // for each edge
1702  QListIterator<EdgeInfo> li(*m_edgeInfo);
1703  EdgeInfo *ei;
1704  for (li.toFirst();(ei=li.current());++li)
1705  {
1706  if (!ei->m_label.isEmpty()) // labels joined by \n
1707  {
1708  int li=ei->m_label.find('\n');
1709  int p=0;
1710  QCString lab;
1711  while ((li=ei->m_label.find('\n',p))!=-1)
1712  {
1713  lab = stripProtectionPrefix(ei->m_label.mid(p,li-p));
1714  arrowNames.insert(lab,(void*)0x8);
1715  p=li+1;
1716  }
1717  lab = stripProtectionPrefix(ei->m_label.right(ei->m_label.length()-p));
1718  arrowNames.insert(lab,(void*)0x8);
1719  }
1720  }
1721  }
1722 
1723  //printf("DotNode::writeBox for %s\n",m_classDef->name().data());
1724  static bool extractPrivate = Config_getBool(EXTRACT_PRIVATE);
1725  t << "{" << convertLabel(m_label);
1726  t << "\\n|";
1734  if (extractPrivate)
1735  {
1738  }
1739  t << "|";
1748  if (extractPrivate)
1749  {
1753  }
1756  {
1758  MemberGroup *mg;
1759  for (mgdi.toFirst();(mg=mgdi.current());++mgdi)
1760  {
1761  if (mg->members())
1762  {
1763  writeBoxMemberList(t,'*',mg->members(),m_classDef,FALSE,&arrowNames);
1764  }
1765  }
1766  }
1767  t << "}";
1768  }
1769  else // standard look
1770  {
1771  t << convertLabel(m_label);
1772  }
1773  t << "\",height=0.2,width=0.4";
1774  if (m_isRoot)
1775  {
1776  t << ",color=\"black\", fillcolor=\"grey75\", style=\"filled\", fontcolor=\"black\"";
1777  }
1778  else
1779  {
1780  static bool dotTransparent = Config_getBool(DOT_TRANSPARENT);
1781  if (!dotTransparent)
1782  {
1783  t << ",color=\"" << labCol << "\", fillcolor=\"";
1784  t << "white";
1785  t << "\", style=\"filled\"";
1786  }
1787  else
1788  {
1789  t << ",color=\"" << labCol << "\"";
1790  }
1791  if (!m_url.isEmpty())
1792  {
1793  int anchorPos = m_url.findRev('#');
1794  if (anchorPos==-1)
1795  {
1796  t << ",URL=\"" << m_url << Doxygen::htmlFileExtension << "\"";
1797  }
1798  else
1799  {
1800  t << ",URL=\"" << m_url.left(anchorPos) << Doxygen::htmlFileExtension
1801  << m_url.right(m_url.length()-anchorPos) << "\"";
1802  }
1803  }
1804  if (!m_tooltip.isEmpty())
1805  {
1806  t << ",tooltip=\"" << escapeTooltip(m_tooltip) << "\"";
1807  }
1808  }
1809  t << "];" << endl;
1810 }
1811 
1813  GraphType gt,
1814  GraphOutputFormat format,
1815  DotNode *cn,
1816  EdgeInfo *ei,
1817  bool topDown,
1818  bool pointBack
1819  )
1820 {
1821  t << " Node";
1822  if (topDown)
1823  t << cn->number();
1824  else
1825  t << m_number;
1826  t << " -> Node";
1827  if (topDown)
1828  t << m_number;
1829  else
1830  t << cn->number();
1831  t << " [";
1832 
1833  static bool umlLook = Config_getBool(UML_LOOK);
1834  const EdgeProperties *eProps = umlLook ? &umlEdgeProps : &normalEdgeProps;
1835  QCString aStyle = eProps->arrowStyleMap[ei->m_color];
1836  bool umlUseArrow = aStyle=="odiamond";
1837 
1838  if (pointBack && !umlUseArrow) t << "dir=\"back\",";
1839  t << "color=\"" << eProps->edgeColorMap[ei->m_color]
1840  << "\",fontsize=\"" << FONTSIZE << "\",";
1841  t << "style=\"" << eProps->edgeStyleMap[ei->m_style] << "\"";
1842  if (!ei->m_label.isEmpty())
1843  {
1844  t << ",label=\" " << convertLabel(ei->m_label) << "\" ";
1845  }
1846  if (umlLook &&
1847  eProps->arrowStyleMap[ei->m_color] &&
1848  (gt==Inheritance || gt==Collaboration)
1849  )
1850  {
1851  bool rev = pointBack;
1852  if (umlUseArrow) rev=!rev; // UML use relates has arrow on the start side
1853  if (rev)
1854  t << ",arrowtail=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
1855  else
1856  t << ",arrowhead=\"" << eProps->arrowStyleMap[ei->m_color] << "\"";
1857  }
1858 
1859  if (format==GOF_BITMAP) t << ",fontname=\"" << FONTNAME << "\"";
1860  t << "];" << endl;
1861 }
1862 
1864  GraphType gt,
1865  GraphOutputFormat format,
1866  bool topDown,
1867  bool toChildren,
1868  bool backArrows
1869  )
1870 {
1871  //printf("DotNode::write(%d) name=%s this=%p written=%d visible=%d\n",m_distance,m_label.data(),this,m_written,m_visible);
1872  if (m_written) return; // node already written to the output
1873  if (!m_visible) return; // node is not visible
1874  writeBox(t,gt,format,m_truncated==Truncated);
1875  m_written=TRUE;
1876  QList<DotNode> *nl = toChildren ? m_children : m_parents;
1877  if (nl)
1878  {
1879  if (toChildren)
1880  {
1881  QListIterator<DotNode> dnli1(*nl);
1882  QListIterator<EdgeInfo> dnli2(*m_edgeInfo);
1883  DotNode *cn;
1884  for (dnli1.toFirst();(cn=dnli1.current());++dnli1,++dnli2)
1885  {
1886  if (cn->isVisible())
1887  {
1888  //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",cn->label().data());
1889  writeArrow(t,gt,format,cn,dnli2.current(),topDown,backArrows);
1890  }
1891  cn->write(t,gt,format,topDown,toChildren,backArrows);
1892  }
1893  }
1894  else // render parents
1895  {
1896  QListIterator<DotNode> dnli(*nl);
1897  DotNode *pn;
1898  for (dnli.toFirst();(pn=dnli.current());++dnli)
1899  {
1900  if (pn->isVisible())
1901  {
1902  //printf("write arrow %s%s%s\n",label().data(),backArrows?"<-":"->",pn->label().data());
1903  writeArrow(t,
1904  gt,
1905  format,
1906  pn,
1907  pn->m_edgeInfo->at(pn->m_children->findRef(this)),
1908  FALSE,
1909  backArrows
1910  );
1911  }
1912  pn->write(t,gt,format,TRUE,FALSE,backArrows);
1913  }
1914  }
1915  }
1916  //printf("end DotNode::write(%d) name=%s\n",distance,m_label.data());
1917 }
1918 
1919 void DotNode::writeXML(FTextStream &t,bool isClassGraph)
1920 {
1921  t << " <node id=\"" << m_number << "\">" << endl;
1922  t << " <label>" << convertToXML(m_label) << "</label>" << endl;
1923  if (!m_url.isEmpty())
1924  {
1925  QCString url(m_url);
1926  const char *refPtr = url.data();
1927  char *urlPtr = strchr(url.rawData(),'$');
1928  if (urlPtr)
1929  {
1930  *urlPtr++='\0';
1931  t << " <link refid=\"" << convertToXML(urlPtr) << "\"";
1932  if (*refPtr!='\0')
1933  {
1934  t << " external=\"" << convertToXML(refPtr) << "\"";
1935  }
1936  t << "/>" << endl;
1937  }
1938  }
1939  if (m_children)
1940  {
1941  QListIterator<DotNode> nli(*m_children);
1942  QListIterator<EdgeInfo> eli(*m_edgeInfo);
1943  DotNode *childNode;
1944  EdgeInfo *edgeInfo;
1945  for (;(childNode=nli.current());++nli,++eli)
1946  {
1947  edgeInfo=eli.current();
1948  t << " <childnode refid=\"" << childNode->m_number << "\" relation=\"";
1949  if (isClassGraph)
1950  {
1951  switch(edgeInfo->m_color)
1952  {
1953  case EdgeInfo::Blue: t << "public-inheritance"; break;
1954  case EdgeInfo::Green: t << "protected-inheritance"; break;
1955  case EdgeInfo::Red: t << "private-inheritance"; break;
1956  case EdgeInfo::Purple: t << "usage"; break;
1957  case EdgeInfo::Orange: t << "template-instance"; break;
1958  case EdgeInfo::Orange2: t << "type-constraint"; break;
1959  case EdgeInfo::Grey: ASSERT(0); break;
1960  }
1961  }
1962  else // include graph
1963  {
1964  t << "include";
1965  }
1966  t << "\">" << endl;
1967  if (!edgeInfo->m_label.isEmpty())
1968  {
1969  int p=0;
1970  int ni;
1971  while ((ni=edgeInfo->m_label.find('\n',p))!=-1)
1972  {
1973  t << " <edgelabel>"
1974  << convertToXML(edgeInfo->m_label.mid(p,ni-p))
1975  << "</edgelabel>" << endl;
1976  p=ni+1;
1977  }
1978  t << " <edgelabel>"
1979  << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p))
1980  << "</edgelabel>" << endl;
1981  }
1982  t << " </childnode>" << endl;
1983  }
1984  }
1985  t << " </node>" << endl;
1986 }
1987 
1988 void DotNode::writeDocbook(FTextStream &t,bool isClassGraph)
1989 {
1990  t << " <node id=\"" << m_number << "\">" << endl;
1991  t << " <label>" << convertToXML(m_label) << "</label>" << endl;
1992  if (!m_url.isEmpty())
1993  {
1994  QCString url(m_url);
1995  const char *refPtr = url.data();
1996  char *urlPtr = strchr(url.rawData(),'$');
1997  if (urlPtr)
1998  {
1999  *urlPtr++='\0';
2000  t << " <link refid=\"" << convertToXML(urlPtr) << "\"";
2001  if (*refPtr!='\0')
2002  {
2003  t << " external=\"" << convertToXML(refPtr) << "\"";
2004  }
2005  t << "/>" << endl;
2006  }
2007  }
2008  if (m_children)
2009  {
2010  QListIterator<DotNode> nli(*m_children);
2011  QListIterator<EdgeInfo> eli(*m_edgeInfo);
2012  DotNode *childNode;
2013  EdgeInfo *edgeInfo;
2014  for (;(childNode=nli.current());++nli,++eli)
2015  {
2016  edgeInfo=eli.current();
2017  t << " <childnode refid=\"" << childNode->m_number << "\" relation=\"";
2018  if (isClassGraph)
2019  {
2020  switch(edgeInfo->m_color)
2021  {
2022  case EdgeInfo::Blue: t << "public-inheritance"; break;
2023  case EdgeInfo::Green: t << "protected-inheritance"; break;
2024  case EdgeInfo::Red: t << "private-inheritance"; break;
2025  case EdgeInfo::Purple: t << "usage"; break;
2026  case EdgeInfo::Orange: t << "template-instance"; break;
2027  case EdgeInfo::Orange2: t << "type-constraint"; break;
2028  case EdgeInfo::Grey: ASSERT(0); break;
2029  }
2030  }
2031  else // include graph
2032  {
2033  t << "include";
2034  }
2035  t << "\">" << endl;
2036  if (!edgeInfo->m_label.isEmpty())
2037  {
2038  int p=0;
2039  int ni;
2040  while ((ni=edgeInfo->m_label.find('\n',p))!=-1)
2041  {
2042  t << " <edgelabel>"
2043  << convertToXML(edgeInfo->m_label.mid(p,ni-p))
2044  << "</edgelabel>" << endl;
2045  p=ni+1;
2046  }
2047  t << " <edgelabel>"
2048  << convertToXML(edgeInfo->m_label.right(edgeInfo->m_label.length()-p))
2049  << "</edgelabel>" << endl;
2050  }
2051  t << " </childnode>" << endl;
2052  }
2053  }
2054  t << " </node>" << endl;
2055 }
2056 
2057 
2059 {
2060  const char* nodePrefix = " node-";
2061 
2062  t << " node = {" << endl;
2063  t << nodePrefix << "id = " << m_number << ';' << endl;
2064  t << nodePrefix << "label = '" << m_label << "';" << endl;
2065 
2066  if (!m_url.isEmpty())
2067  {
2068  QCString url(m_url);
2069  const char *refPtr = url.data();
2070  char *urlPtr = strchr(url.rawData(),'$');
2071  if (urlPtr)
2072  {
2073  *urlPtr++='\0';
2074  t << nodePrefix << "link = {" << endl << " "
2075  << nodePrefix << "link-id = '" << urlPtr << "';" << endl;
2076 
2077  if (*refPtr!='\0')
2078  {
2079  t << " " << nodePrefix << "link-external = '"
2080  << refPtr << "';" << endl;
2081  }
2082  t << " };" << endl;
2083  }
2084  }
2085  if (m_children)
2086  {
2087  QListIterator<DotNode> nli(*m_children);
2088  QListIterator<EdgeInfo> eli(*m_edgeInfo);
2089  DotNode *childNode;
2090  EdgeInfo *edgeInfo;
2091  for (;(childNode=nli.current());++nli,++eli)
2092  {
2093  edgeInfo=eli.current();
2094  t << " node-child = {" << endl;
2095  t << " child-id = '" << childNode->m_number << "';" << endl;
2096  t << " relation = ";
2097 
2098  switch(edgeInfo->m_color)
2099  {
2100  case EdgeInfo::Blue: t << "public-inheritance"; break;
2101  case EdgeInfo::Green: t << "protected-inheritance"; break;
2102  case EdgeInfo::Red: t << "private-inheritance"; break;
2103  case EdgeInfo::Purple: t << "usage"; break;
2104  case EdgeInfo::Orange: t << "template-instance"; break;
2105  case EdgeInfo::Orange2: t << "type-constraint"; break;
2106  case EdgeInfo::Grey: ASSERT(0); break;
2107  }
2108  t << ';' << endl;
2109 
2110  if (!edgeInfo->m_label.isEmpty())
2111  {
2112  t << " edgelabel = <<_EnD_oF_dEf_TeXt_" << endl
2113  << edgeInfo->m_label << endl
2114  << "_EnD_oF_dEf_TeXt_;" << endl;
2115  }
2116  t << " }; /* node-child */" << endl;
2117  } /* for (;childNode...) */
2118  }
2119  t << " }; /* node */" << endl;
2120 }
2121 
2122 
2124 {
2125  m_written=FALSE;
2126  if (m_parents!=0)
2127  {
2128  QListIterator<DotNode> dnlip(*m_parents);
2129  DotNode *pn;
2130  for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
2131  {
2132  if (pn->m_written)
2133  {
2134  pn->clearWriteFlag();
2135  }
2136  }
2137  }
2138  if (m_children!=0)
2139  {
2140  QListIterator<DotNode> dnlic(*m_children);
2141  DotNode *cn;
2142  for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2143  {
2144  if (cn->m_written)
2145  {
2146  cn->clearWriteFlag();
2147  }
2148  }
2149  }
2150 }
2151 
2153 {
2154  if (m_children)
2155  {
2156  QListIterator<DotNode> dnlic(*m_children);
2157  DotNode *cn;
2158  for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2159  {
2160  if (cn->m_subgraphId==-1) // uncolored child node
2161  {
2162  cn->m_subgraphId=curColor;
2163  cn->markAsVisible();
2164  cn->colorConnectedNodes(curColor);
2165  //printf("coloring node %s (%p): %d\n",cn->m_label.data(),cn,cn->m_subgraphId);
2166  }
2167  }
2168  }
2169 
2170  if (m_parents)
2171  {
2172  QListIterator<DotNode> dnlip(*m_parents);
2173  DotNode *pn;
2174  for (dnlip.toFirst();(pn=dnlip.current());++dnlip)
2175  {
2176  if (pn->m_subgraphId==-1) // uncolored parent node
2177  {
2178  pn->m_subgraphId=curColor;
2179  pn->markAsVisible();
2180  pn->colorConnectedNodes(curColor);
2181  //printf("coloring node %s (%p): %d\n",pn->m_label.data(),pn,pn->m_subgraphId);
2182  }
2183  }
2184  }
2185 }
2186 
2187 void DotNode::renumberNodes(int &number)
2188 {
2189  m_number = number++;
2190  if (m_children)
2191  {
2192  QListIterator<DotNode> dnlic(*m_children);
2193  DotNode *cn;
2194  for (dnlic.toFirst();(cn=dnlic.current());++dnlic)
2195  {
2196  cn->renumberNodes(number);
2197  }
2198  }
2199 }
2200 
2202 {
2203  if (!m_url.isEmpty()) return this;
2204  //printf("findDocNode(): `%s'\n",m_label.data());
2205  if (m_parents)
2206  {
2207  QListIterator<DotNode> dnli(*m_parents);
2208  DotNode *pn;
2209  for (dnli.toFirst();(pn=dnli.current());++dnli)
2210  {
2211  if (!pn->m_hasDoc)
2212  {
2213  pn->m_hasDoc=TRUE;
2214  const DotNode *dn = pn->findDocNode();
2215  if (dn) return dn;
2216  }
2217  }
2218  }
2219  if (m_children)
2220  {
2221  QListIterator<DotNode> dnli(*m_children);
2222  DotNode *cn;
2223  for (dnli.toFirst();(cn=dnli.current());++dnli)
2224  {
2225  if (!cn->m_hasDoc)
2226  {
2227  cn->m_hasDoc=TRUE;
2228  const DotNode *dn = cn->findDocNode();
2229  if (dn) return dn;
2230  }
2231  }
2232  }
2233  return 0;
2234 }
2235 
2236 //--------------------------------------------------------------------
2237 
2239  const char *path,const char *fileName,int id) const
2240 {
2241  QDir d(path);
2242  QCString baseName;
2243  QCString imgExt = getDotImageExtension();
2244  QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
2245  baseName.sprintf("inherit_graph_%d",id);
2246  QCString imgName = baseName+"."+ imgExt;
2247  QCString mapName = baseName+".map";
2248  QCString absImgName = QCString(d.absPath().data())+"/"+imgName;
2249  QCString absMapName = QCString(d.absPath().data())+"/"+mapName;
2250  QCString absBaseName = QCString(d.absPath().data())+"/"+baseName;
2251  QListIterator<DotNode> dnli2(*m_rootNodes);
2252  DotNode *node;
2253 
2254  // compute md5 checksum of the graph were are about to generate
2255  QGString theGraph;
2256  FTextStream md5stream(&theGraph);
2258  md5stream << " rankdir=\"LR\";" << endl;
2259  for (dnli2.toFirst();(node=dnli2.current());++dnli2)
2260  {
2261  if (node->m_subgraphId==n->m_subgraphId)
2262  {
2263  node->clearWriteFlag();
2264  }
2265  }
2266  for (dnli2.toFirst();(node=dnli2.current());++dnli2)
2267  {
2268  if (node->m_subgraphId==n->m_subgraphId)
2269  {
2270  node->write(md5stream,DotNode::Hierarchy,GOF_BITMAP,FALSE,TRUE,TRUE);
2271  }
2272  }
2273  writeGraphFooter(md5stream);
2274  uchar md5_sig[16];
2275  QCString sigStr(33);
2276  MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
2277  MD5SigToString(md5_sig,sigStr.rawData(),33);
2278  bool regenerate=FALSE;
2279  if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
2280  !checkDeliverables(absImgName,absMapName))
2281  {
2282  regenerate=TRUE;
2283  // image was new or has changed
2284  QCString dotName=absBaseName+".dot";
2285  QFile f(dotName);
2286  if (!f.open(IO_WriteOnly)) return;
2287  FTextStream t(&f);
2288  t << theGraph;
2289  f.close();
2290 
2291  DotRunner *dotRun = new DotRunner(dotName,d.absPath().data(),TRUE,absImgName);
2292  dotRun->addJob(imgFmt,absImgName);
2293  dotRun->addJob(MAP_CMD,absMapName);
2294  DotManager::instance()->addRun(dotRun);
2295  }
2296  else
2297  {
2298  removeDotGraph(absBaseName+".dot");
2299  }
2300  Doxygen::indexList->addImageFile(imgName);
2301  // write image and map in a table row
2302  QCString mapLabel = escapeCharsInString(n->m_label,FALSE);
2303  if (imgExt=="svg") // vector graphics
2304  {
2305  if (regenerate || !writeSVGFigureLink(out,QCString(),baseName,absImgName))
2306  {
2307  if (regenerate)
2308  {
2309  DotManager::instance()->addSVGConversion(absImgName,QCString(),
2310  FALSE,QCString(),FALSE,0);
2311  }
2312  int mapId = DotManager::instance()->addSVGObject(fileName,baseName,
2313  absImgName,QCString());
2314  out << "<!-- SVG " << mapId << " -->" << endl;
2315  }
2316  }
2317  else // normal bitmap
2318  {
2319  out << "<img src=\"" << imgName << "\" border=\"0\" alt=\"\" usemap=\"#"
2320  << mapLabel << "\"/>" << endl;
2321 
2322  if (regenerate || !insertMapFile(out,absMapName,QCString(),mapLabel))
2323  {
2324  int mapId = DotManager::instance()->addMap(fileName,absMapName,QCString(),
2325  FALSE,QCString(),mapLabel);
2326  out << "<!-- MAP " << mapId << " -->" << endl;
2327  }
2328  }
2329 }
2330 
2332  const char *path,const char *fileName) const
2333 {
2334  //printf("DotGfxHierarchyTable::writeGraph(%s)\n",name);
2335  //printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count());
2336 
2337  if (m_rootSubgraphs->count()==0) return;
2338 
2339  QDir d(path);
2340  // store the original directory
2341  if (!d.exists())
2342  {
2343  err("Output dir %s does not exist!\n",path); exit(1);
2344  }
2345 
2346  // put each connected subgraph of the hierarchy in a row of the HTML output
2347  out << "<table border=\"0\" cellspacing=\"10\" cellpadding=\"0\">" << endl;
2348 
2349  QListIterator<DotNode> dnli(*m_rootSubgraphs);
2350  DotNode *n;
2351  int count=0;
2352  for (dnli.toFirst();(n=dnli.current());++dnli)
2353  {
2354  out << "<tr><td>";
2355  createGraph(n,out,path,fileName,count++);
2356  out << "</td></tr>" << endl;
2357  }
2358  out << "</table>" << endl;
2359 }
2360 
2362 {
2363  //printf("addHierarchy `%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count());
2364  if (cd->subClasses())
2365  {
2366  BaseClassListIterator bcli(*cd->subClasses());
2367  BaseClassDef *bcd;
2368  for ( ; (bcd=bcli.current()) ; ++bcli )
2369  {
2370  ClassDef *bClass=bcd->classDef;
2371  //printf(" Trying sub class=`%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count());
2372  if (bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses()))
2373  {
2374  DotNode *bn;
2375  //printf(" Node `%s' Found visible class=`%s'\n",n->m_label.data(),
2376  // bClass->name().data());
2377  if ((bn=m_usedNodes->find(bClass->name()))) // node already present
2378  {
2379  if (n->m_children==0 || n->m_children->findRef(bn)==-1) // no arrow yet
2380  {
2381  n->addChild(bn,bcd->prot);
2382  bn->addParent(n);
2383  //printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n",
2384  // n->m_label.data(),
2385  // bn->m_label.data(),
2386  // bn->m_children ? bn->m_children->count() : 0,
2387  // bn->m_parents ? bn->m_parents->count() : 0
2388  // );
2389  }
2390  //else
2391  //{
2392  // printf(" Class already has an arrow!\n");
2393  //}
2394  }
2395  else
2396  {
2397  QCString tmp_url="";
2398  if (bClass->isLinkable() && !bClass->isHidden())
2399  {
2400  tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase();
2401  if (!bClass->anchor().isEmpty())
2402  {
2403  tmp_url+="#"+bClass->anchor();
2404  }
2405  }
2406  QCString tooltip = bClass->briefDescriptionAsTooltip();
2407  bn = new DotNode(m_curNodeNumber++,
2408  bClass->displayName(),
2409  tooltip,
2410  tmp_url.data()
2411  );
2412  n->addChild(bn,bcd->prot);
2413  bn->addParent(n);
2414  //printf(" Adding node %s to new base node %s (c=%d,p=%d)\n",
2415  // n->m_label.data(),
2416  // bn->m_label.data(),
2417  // bn->m_children ? bn->m_children->count() : 0,
2418  // bn->m_parents ? bn->m_parents->count() : 0
2419  // );
2420  //printf(" inserting %s (%p)\n",bClass->name().data(),bn);
2421  m_usedNodes->insert(bClass->name(),bn); // add node to the used list
2422  }
2423  if (!bClass->visited && !hideSuper && bClass->subClasses())
2424  {
2425  bool wasVisited=bClass->visited;
2426  bClass->visited=TRUE;
2427  addHierarchy(bn,bClass,wasVisited);
2428  }
2429  }
2430  }
2431  }
2432  //printf("end addHierarchy\n");
2433 }
2434 
2436 {
2437  ClassSDict::Iterator cli(*cl);
2438  ClassDef *cd;
2439  for (cli.toLast();(cd=cli.current());--cli)
2440  {
2441  //printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count());
2442  if (cd->getLanguage()==SrcLangExt_VHDL &&
2444  )
2445  {
2446  continue;
2447  }
2448  if (!hasVisibleRoot(cd->baseClasses()) &&
2449  cd->isVisibleInHierarchy()
2450  ) // root node in the forest
2451  {
2452  QCString tmp_url="";
2453  if (cd->isLinkable() && !cd->isHidden())
2454  {
2455  tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2456  if (!cd->anchor().isEmpty())
2457  {
2458  tmp_url+="#"+cd->anchor();
2459  }
2460  }
2461  //printf("Inserting root class %s\n",cd->name().data());
2462  QCString tooltip = cd->briefDescriptionAsTooltip();
2463  DotNode *n = new DotNode(m_curNodeNumber++,
2464  cd->displayName(),
2465  tooltip,
2466  tmp_url.data());
2467 
2468  //m_usedNodes->clear();
2469  m_usedNodes->insert(cd->name(),n);
2470  m_rootNodes->insert(0,n);
2471  if (!cd->visited && cd->subClasses())
2472  {
2473  addHierarchy(n,cd,cd->visited);
2474  cd->visited=TRUE;
2475  }
2476  }
2477  }
2478 }
2479 
2481 {
2482  m_rootNodes = new QList<DotNode>;
2483  m_usedNodes = new QDict<DotNode>(1009);
2484  m_usedNodes->setAutoDelete(TRUE);
2486 
2487  // build a graph with each class as a node and the inheritance relations
2488  // as edges
2493  // m_usedNodes now contains all nodes in the graph
2494 
2495  // color the graph into a set of independent subgraphs
2496  bool done=FALSE;
2497  int curColor=0;
2498  QListIterator<DotNode> dnli(*m_rootNodes);
2499  while (!done) // there are still nodes to color
2500  {
2501  DotNode *n;
2502  done=TRUE; // we are done unless there are still uncolored nodes
2503  for (dnli.toLast();(n=dnli.current());--dnli)
2504  {
2505  if (n->m_subgraphId==-1) // not yet colored
2506  {
2507  //printf("Starting at node %s (%p): %d\n",n->m_label.data(),n,curColor);
2508  done=FALSE; // still uncolored nodes
2509  n->m_subgraphId=curColor;
2510  n->markAsVisible();
2511  n->colorConnectedNodes(curColor);
2512  curColor++;
2513  const DotNode *dn=n->findDocNode();
2514  if (dn!=0)
2515  m_rootSubgraphs->inSort(dn);
2516  else
2517  m_rootSubgraphs->inSort(n);
2518  }
2519  }
2520  }
2521 
2522  //printf("Number of independent subgraphs: %d\n",curColor);
2523  QListIterator<DotNode> dnli2(*m_rootSubgraphs);
2524  DotNode *n;
2525  for (dnli2.toFirst();(n=dnli2.current());++dnli2)
2526  {
2527  //printf("Node %s color=%d (c=%d,p=%d)\n",
2528  // n->m_label.data(),n->m_subgraphId,
2529  // n->m_children?n->m_children->count():0,
2530  // n->m_parents?n->m_parents->count():0);
2531  int number=0;
2532  n->renumberNodes(number);
2533  }
2534 }
2535 
2537 {
2538  //printf("DotGfxHierarchyTable::~DotGfxHierarchyTable\n");
2539 
2540  //QDictIterator<DotNode> di(*m_usedNodes);
2541  //DotNode *n;
2542  //for (;(n=di.current());++di)
2543  //{
2544  // printf("Node %p: %s\n",n,n->label().data());
2545  //}
2546 
2547  delete m_rootNodes;
2548  delete m_usedNodes;
2549  delete m_rootSubgraphs;
2550 }
2551 
2552 //--------------------------------------------------------------------
2553 
2555  const char *label,const char *usedName,const char *templSpec,bool base,int distance)
2556 {
2557  if (Config_getBool(HIDE_UNDOC_CLASSES) && !cd->isLinkable()) return;
2558 
2559  int edgeStyle = (label || prot==EdgeInfo::Orange || prot==EdgeInfo::Orange2) ? EdgeInfo::Dashed : EdgeInfo::Solid;
2560  QCString className;
2561  if (cd->isAnonymous())
2562  {
2563  className="anonymous:";
2564  className+=label;
2565  }
2566  else if (usedName) // name is a typedef
2567  {
2568  className=usedName;
2569  }
2570  else if (templSpec) // name has a template part
2571  {
2572  className=insertTemplateSpecifierInScope(cd->name(),templSpec);
2573  }
2574  else // just a normal name
2575  {
2576  className=cd->displayName();
2577  }
2578  //printf("DotClassGraph::addClass(class=`%s',parent=%s,prot=%d,label=%s,dist=%d,usedName=%s,templSpec=%s,base=%d)\n",
2579  // className.data(),n->m_label.data(),prot,label,distance,usedName,templSpec,base);
2580  DotNode *bn = m_usedNodes->find(className);
2581  if (bn) // class already inserted
2582  {
2583  if (base)
2584  {
2585  n->addChild(bn,prot,edgeStyle,label);
2586  bn->addParent(n);
2587  }
2588  else
2589  {
2590  bn->addChild(n,prot,edgeStyle,label);
2591  n->addParent(bn);
2592  }
2593  bn->setDistance(distance);
2594  //printf(" add exiting node %s of %s\n",bn->m_label.data(),n->m_label.data());
2595  }
2596  else // new class
2597  {
2598  QCString displayName=className;
2599  if (Config_getBool(HIDE_SCOPE_NAMES)) displayName=stripScope(displayName);
2600  QCString tmp_url;
2601  if (cd->isLinkable() && !cd->isHidden())
2602  {
2603  tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2604  if (!cd->anchor().isEmpty())
2605  {
2606  tmp_url+="#"+cd->anchor();
2607  }
2608  }
2609  QCString tooltip = cd->briefDescriptionAsTooltip();
2610  bn = new DotNode(m_curNodeNumber++,
2611  displayName,
2612  tooltip,
2613  tmp_url.data(),
2614  FALSE, // rootNode
2615  cd
2616  );
2617  if (base)
2618  {
2619  n->addChild(bn,prot,edgeStyle,label);
2620  bn->addParent(n);
2621  }
2622  else
2623  {
2624  bn->addChild(n,prot,edgeStyle,label);
2625  n->addParent(bn);
2626  }
2627  bn->setDistance(distance);
2628  m_usedNodes->insert(className,bn);
2629  //printf(" add new child node `%s' to %s hidden=%d url=%s\n",
2630  // className.data(),n->m_label.data(),cd->isHidden(),tmp_url.data());
2631 
2632  buildGraph(cd,bn,base,distance+1);
2633  }
2634 }
2635 
2636 void DotClassGraph::determineTruncatedNodes(QList<DotNode> &queue,bool includeParents)
2637 {
2638  while (queue.count()>0)
2639  {
2640  DotNode *n = queue.take(0);
2641  if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
2642  {
2643  bool truncated = FALSE;
2644  if (n->m_children)
2645  {
2646  QListIterator<DotNode> li(*n->m_children);
2647  DotNode *dn;
2648  for (li.toFirst();(dn=li.current());++li)
2649  {
2650  if (!dn->isVisible())
2651  truncated = TRUE;
2652  else
2653  queue.append(dn);
2654  }
2655  }
2656  if (n->m_parents && includeParents)
2657  {
2658  QListIterator<DotNode> li(*n->m_parents);
2659  DotNode *dn;
2660  for (li.toFirst();(dn=li.current());++li)
2661  {
2662  if (!dn->isVisible())
2663  truncated = TRUE;
2664  else
2665  queue.append(dn);
2666  }
2667  }
2668  n->markAsTruncated(truncated);
2669  }
2670  }
2671 }
2672 
2674  int maxNodes,bool includeParents)
2675 {
2676  QList<DotNode> childQueue;
2677  QList<DotNode> parentQueue;
2678  QArray<int> childTreeWidth;
2679  QArray<int> parentTreeWidth;
2680  childQueue.append(rootNode);
2681  if (includeParents) parentQueue.append(rootNode);
2682  bool firstNode=TRUE; // flag to force reprocessing rootNode in the parent loop
2683  // despite being marked visible in the child loop
2684  while ((childQueue.count()>0 || parentQueue.count()>0) && maxNodes>0)
2685  {
2686  static int maxDistance = Config_getInt(MAX_DOT_GRAPH_DEPTH);
2687  if (childQueue.count()>0)
2688  {
2689  DotNode *n = childQueue.take(0);
2690  int distance = n->distance();
2691  if (!n->isVisible() && distance<=maxDistance) // not yet processed
2692  {
2693  if (distance>0)
2694  {
2695  int oldSize=(int)childTreeWidth.size();
2696  if (distance>oldSize)
2697  {
2698  childTreeWidth.resize(QMAX(childTreeWidth.size(),(uint)distance));
2699  int i; for (i=oldSize;i<distance;i++) childTreeWidth[i]=0;
2700  }
2701  childTreeWidth[distance-1]+=n->label().length();
2702  }
2703  n->markAsVisible();
2704  maxNodes--;
2705  // add direct children
2706  if (n->m_children)
2707  {
2708  QListIterator<DotNode> li(*n->m_children);
2709  DotNode *dn;
2710  for (li.toFirst();(dn=li.current());++li)
2711  {
2712  childQueue.append(dn);
2713  }
2714  }
2715  }
2716  }
2717  if (includeParents && parentQueue.count()>0)
2718  {
2719  DotNode *n = parentQueue.take(0);
2720  if ((!n->isVisible() || firstNode) && n->distance()<=maxDistance) // not yet processed
2721  {
2722  firstNode=FALSE;
2723  int distance = n->distance();
2724  if (distance>0)
2725  {
2726  int oldSize = (int)parentTreeWidth.size();
2727  if (distance>oldSize)
2728  {
2729  parentTreeWidth.resize(QMAX(parentTreeWidth.size(),(uint)distance));
2730  int i; for (i=oldSize;i<distance;i++) parentTreeWidth[i]=0;
2731  }
2732  parentTreeWidth[distance-1]+=n->label().length();
2733  }
2734  n->markAsVisible();
2735  maxNodes--;
2736  // add direct parents
2737  if (n->m_parents)
2738  {
2739  QListIterator<DotNode> li(*n->m_parents);
2740  DotNode *dn;
2741  for (li.toFirst();(dn=li.current());++li)
2742  {
2743  parentQueue.append(dn);
2744  }
2745  }
2746  }
2747  }
2748  }
2749  if (Config_getBool(UML_LOOK)) return FALSE; // UML graph are always top to bottom
2750  int maxWidth=0;
2751  int maxHeight=(int)QMAX(childTreeWidth.size(),parentTreeWidth.size());
2752  uint i;
2753  for (i=0;i<childTreeWidth.size();i++)
2754  {
2755  if (childTreeWidth.at(i)>maxWidth) maxWidth=childTreeWidth.at(i);
2756  }
2757  for (i=0;i<parentTreeWidth.size();i++)
2758  {
2759  if (parentTreeWidth.at(i)>maxWidth) maxWidth=parentTreeWidth.at(i);
2760  }
2761  //printf("max tree width=%d, max tree height=%d\n",maxWidth,maxHeight);
2762  return maxWidth>80 && maxHeight<12; // used metric to decide to render the tree
2763  // from left to right instead of top to bottom,
2764  // with the idea to render very wide trees in
2765  // left to right order.
2766 }
2767 
2768 void DotClassGraph::buildGraph(ClassDef *cd,DotNode *n,bool base,int distance)
2769 {
2770  static bool templateRelations = Config_getBool(TEMPLATE_RELATIONS);
2771  //printf("DocClassGraph::buildGraph(%s,distance=%d,base=%d)\n",
2772  // cd->name().data(),distance,base);
2773  // ---- Add inheritance relations
2774 
2776  {
2777  BaseClassList *bcl = base ? cd->baseClasses() : cd->subClasses();
2778  if (bcl)
2779  {
2780  BaseClassListIterator bcli(*bcl);
2781  BaseClassDef *bcd;
2782  for ( ; (bcd=bcli.current()) ; ++bcli )
2783  {
2784  //printf("-------- inheritance relation %s->%s templ=`%s'\n",
2785  // cd->name().data(),bcd->classDef->name().data(),bcd->templSpecifiers.data());
2786  addClass(bcd->classDef,n,bcd->prot,0,bcd->usedName,
2787  bcd->templSpecifiers,base,distance);
2788  }
2789  }
2790  }
2792  {
2793  // ---- Add usage relations
2794 
2795  UsesClassDict *dict =
2796  base ? cd->usedImplementationClasses() :
2798  ;
2799  if (dict)
2800  {
2801  UsesClassDictIterator ucdi(*dict);
2802  UsesClassDef *ucd;
2803  for (;(ucd=ucdi.current());++ucdi)
2804  {
2805  QCString label;
2806  QDictIterator<void> dvi(*ucd->accessors);
2807  const char *s;
2808  bool first=TRUE;
2809  int count=0;
2810  int maxLabels=10;
2811  for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count)
2812  {
2813  if (first)
2814  {
2815  label=s;
2816  first=FALSE;
2817  }
2818  else
2819  {
2820  label+=QCString("\n")+s;
2821  }
2822  }
2823  if (count==maxLabels) label+="\n...";
2824  //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data());
2825  addClass(ucd->classDef,n,EdgeInfo::Purple,label,0,
2826  ucd->templSpecifiers,base,distance);
2827  }
2828  }
2829  }
2830  if (templateRelations && base)
2831  {
2833  if (dict)
2834  {
2835  ConstraintClassDictIterator ccdi(*dict);
2836  ConstraintClassDef *ccd;
2837  for (;(ccd=ccdi.current());++ccdi)
2838  {
2839  QCString label;
2840  QDictIterator<void> dvi(*ccd->accessors);
2841  const char *s;
2842  bool first=TRUE;
2843  int count=0;
2844  int maxLabels=10;
2845  for (;(s=dvi.currentKey()) && count<maxLabels;++dvi,++count)
2846  {
2847  if (first)
2848  {
2849  label=s;
2850  first=FALSE;
2851  }
2852  else
2853  {
2854  label+=QCString("\n")+s;
2855  }
2856  }
2857  if (count==maxLabels) label+="\n...";
2858  //printf("addClass: %s templSpec=%s\n",ucd->classDef->name().data(),ucd->templSpecifiers.data());
2859  addClass(ccd->classDef,n,EdgeInfo::Orange2,label,0,
2860  0,TRUE,distance);
2861  }
2862  }
2863  }
2864 
2865  // ---- Add template instantiation relations
2866 
2867  if (templateRelations)
2868  {
2869  if (base) // template relations for base classes
2870  {
2871  ClassDef *templMaster=cd->templateMaster();
2872  if (templMaster)
2873  {
2874  QDictIterator<ClassDef> cli(*templMaster->getTemplateInstances());
2875  ClassDef *templInstance;
2876  for (;(templInstance=cli.current());++cli)
2877  {
2878  if (templInstance==cd)
2879  {
2880  addClass(templMaster,n,EdgeInfo::Orange,cli.currentKey(),0,
2881  0,TRUE,distance);
2882  }
2883  }
2884  }
2885  }
2886  else // template relations for super classes
2887  {
2888  QDict<ClassDef> *templInstances = cd->getTemplateInstances();
2889  if (templInstances)
2890  {
2891  QDictIterator<ClassDef> cli(*templInstances);
2892  ClassDef *templInstance;
2893  for (;(templInstance=cli.current());++cli)
2894  {
2895  addClass(templInstance,n,EdgeInfo::Orange,cli.currentKey(),0,
2896  0,FALSE,distance);
2897  }
2898  }
2899  }
2900  }
2901 }
2902 
2904 
2906 {
2907  m_curNodeNumber = 0;
2908 }
2909 
2911 {
2912  //printf("--------------- DotClassGraph::DotClassGraph `%s'\n",cd->displayName().data());
2913  m_graphType = t;
2914  QCString tmp_url="";
2915  if (cd->isLinkable() && !cd->isHidden())
2916  {
2917  tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
2918  if (!cd->anchor().isEmpty())
2919  {
2920  tmp_url+="#"+cd->anchor();
2921  }
2922  }
2923  QCString className = cd->displayName();
2924  QCString tooltip = cd->briefDescriptionAsTooltip();
2926  className,
2927  tooltip,
2928  tmp_url.data(),
2929  TRUE, // is a root node
2930  cd
2931  );
2933  m_usedNodes = new QDict<DotNode>(1009);
2934  m_usedNodes->insert(className,m_startNode);
2935 
2936  //printf("Root node %s\n",cd->name().data());
2937  //if (m_recDepth>0)
2938  //{
2939  buildGraph(cd,m_startNode,TRUE,1);
2940  if (t==DotNode::Inheritance) buildGraph(cd,m_startNode,FALSE,1);
2941  //}
2942 
2943  static int maxNodes = Config_getInt(DOT_GRAPH_MAX_NODES);
2944  //int directChildNodes = 1;
2945  //if (m_startNode->m_children!=0)
2946  // directChildNodes+=m_startNode->m_children->count();
2947  //if (t==DotNode::Inheritance && m_startNode->m_parents!=0)
2948  // directChildNodes+=m_startNode->m_parents->count();
2949  //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
2950  //openNodeQueue.append(m_startNode);
2952  QList<DotNode> openNodeQueue;
2953  openNodeQueue.append(m_startNode);
2955 
2958 }
2959 
2961 {
2962  static bool umlLook = Config_getBool(UML_LOOK);
2964  return m_startNode->m_children==0 && m_startNode->m_parents==0;
2965  else
2966  return !umlLook && m_startNode->m_children==0;
2967 }
2968 
2970 {
2971  static int maxNodes = Config_getInt(DOT_GRAPH_MAX_NODES);
2972  int numNodes = 0;
2973  numNodes+= m_startNode->m_children ? m_startNode->m_children->count() : 0;
2975  {
2976  numNodes+= m_startNode->m_parents ? m_startNode->m_parents->count() : 0;
2977  }
2978  return numNodes>=maxNodes;
2979 }
2980 
2982 {
2984  delete m_usedNodes;
2985 }
2986 
2991  DotNode::GraphType gt,
2992  GraphOutputFormat format,
2993  bool lrRank,
2994  bool renderParents,
2995  bool backArrows,
2996  const QCString &title,
2997  QCString &graphStr
2998  )
2999 {
3000  //printf("computeMd5Signature\n");
3001  QGString buf;
3002  FTextStream md5stream(&buf);
3003  writeGraphHeader(md5stream,title);
3004  if (lrRank)
3005  {
3006  md5stream << " rankdir=\"LR\";" << endl;
3007  }
3008  root->clearWriteFlag();
3009  root->write(md5stream,
3010  gt,
3011  format,
3013  TRUE,
3014  backArrows);
3015  if (renderParents && root->m_parents)
3016  {
3017  QListIterator<DotNode> dnli(*root->m_parents);
3018  DotNode *pn;
3019  for (dnli.toFirst();(pn=dnli.current());++dnli)
3020  {
3021  if (pn->isVisible())
3022  {
3023  root->writeArrow(md5stream, // stream
3024  gt, // graph type
3025  format, // output format
3026  pn, // child node
3027  pn->m_edgeInfo->at(pn->m_children->findRef(root)), // edge info
3028  FALSE, // topDown?
3029  backArrows // point back?
3030  );
3031  }
3032  pn->write(md5stream, // stream
3033  gt, // graph type
3034  format, // output format
3035  TRUE, // topDown?
3036  FALSE, // toChildren?
3037  backArrows // backward pointing arrows?
3038  );
3039  }
3040  }
3041  writeGraphFooter(md5stream);
3042  uchar md5_sig[16];
3043  QCString sigStr(33);
3044  MD5Buffer((const unsigned char *)buf.data(),buf.length(),md5_sig);
3045  MD5SigToString(md5_sig,sigStr.rawData(),33);
3046  graphStr=buf.data();
3047  //printf("md5: %s | file: %s\n",sigStr,baseName.data());
3048  return sigStr;
3049 }
3050 
3051 static bool updateDotGraph(DotNode *root,
3052  DotNode::GraphType gt,
3053  const QCString &baseName,
3054  GraphOutputFormat format,
3055  bool lrRank,
3056  bool renderParents,
3057  bool backArrows,
3058  const QCString &title=QCString()
3059  )
3060 {
3061  QCString theGraph;
3062  // TODO: write graph to theGraph, then compute md5 checksum
3063  QCString md5 = computeMd5Signature(
3064  root,gt,format,lrRank,renderParents,
3065  backArrows,title,theGraph);
3066  QFile f(baseName+".dot");
3067  if (f.open(IO_WriteOnly))
3068  {
3069  FTextStream t(&f);
3070  t << theGraph;
3071  }
3072  return checkAndUpdateMd5Signature(baseName,md5); // graph needs to be regenerated
3073 }
3074 
3076  GraphOutputFormat graphFormat,
3077  EmbeddedOutputFormat textFormat,
3078  const char *path,
3079  const char *fileName,
3080  const char *relPath,
3081  bool /*isTBRank*/,
3082  bool generateImageMap,
3083  int graphId) const
3084 {
3085  QDir d(path);
3086  // store the original directory
3087  if (!d.exists())
3088  {
3089  err("Output dir %s does not exist!\n",path); exit(1);
3090  }
3091  static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
3092 
3093  QCString baseName;
3094  QCString mapName;
3095  switch (m_graphType)
3096  {
3098  mapName="coll_map";
3099  baseName=m_collabFileName;
3100  break;
3101  case DotNode::Inheritance:
3102  mapName="inherit_map";
3103  baseName=m_inheritFileName;
3104  break;
3105  default:
3106  ASSERT(0);
3107  break;
3108  }
3109 
3110  // derive target file names from baseName
3111  QCString imgExt = getDotImageExtension();
3112  QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
3113  QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3114  QCString absDotName = absBaseName+".dot";
3115  QCString absMapName = absBaseName+".map";
3116  QCString absPdfName = absBaseName+".pdf";
3117  QCString absEpsName = absBaseName+".eps";
3118  QCString absImgName = absBaseName+"."+imgExt;
3119 
3120  bool regenerate = FALSE;
3122  m_graphType,
3123  absBaseName,
3124  graphFormat,
3125  m_lrRank,
3127  TRUE,
3128  m_startNode->label()
3129  ) ||
3130  !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
3131  usePDFLatex ? absPdfName : absEpsName,
3132  graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString())
3133  )
3134  {
3135  regenerate=TRUE;
3136  if (graphFormat==GOF_BITMAP) // run dot to create a bitmap image
3137  {
3138  DotRunner *dotRun = new DotRunner(absDotName,
3139  d.absPath().data(),TRUE,absImgName);
3140  dotRun->addJob(imgFmt,absImgName);
3141  if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3142  DotManager::instance()->addRun(dotRun);
3143 
3144  }
3145  else if (graphFormat==GOF_EPS) // run dot to create a .eps image
3146  {
3147  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3148  if (usePDFLatex)
3149  {
3150  dotRun->addJob("pdf",absPdfName);
3151  }
3152  else
3153  {
3154  dotRun->addJob("ps",absEpsName);
3155  }
3156  DotManager::instance()->addRun(dotRun);
3157  }
3158  }
3159  Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3160 
3161  if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
3162  {
3163  out << "<para>" << endl;
3164  out << " <figure>" << endl;
3165  out << " <title>";
3166  switch (m_graphType)
3167  {
3169  out << "Collaboration graph";
3170  break;
3171  case DotNode::Inheritance:
3172  out << "Inheritance graph";
3173  break;
3174  default:
3175  ASSERT(0);
3176  break;
3177  }
3178  out << "</title>" << endl;
3179  out << " <mediaobject>" << endl;
3180  out << " <imageobject>" << endl;
3181  out << " <imagedata";
3182  out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"1\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
3183  out << "</imagedata>" << endl;
3184  out << " </imageobject>" << endl;
3185  out << " </mediaobject>" << endl;
3186  out << " </figure>" << endl;
3187  out << "</para>" << endl;
3188  }
3189  else if (graphFormat==GOF_BITMAP && generateImageMap) // produce HTML to include the image
3190  {
3191  QCString mapLabel = escapeCharsInString(m_startNode->m_label,FALSE)+"_"+
3192  escapeCharsInString(mapName,FALSE);
3193  if (imgExt=="svg") // add link to SVG file without map file
3194  {
3195  out << "<div class=\"center\">";
3196  if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3197  {
3198  if (regenerate)
3199  {
3200  DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3201  }
3202  int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3203  out << "<!-- SVG " << mapId << " -->" << endl;
3204  }
3205  out << "</div>" << endl;
3206  }
3207  else // add link to bitmap file with image map
3208  {
3209  out << "<div class=\"center\">";
3210  out << "<img src=\"" << relPath << baseName << "."
3211  << imgExt << "\" border=\"0\" usemap=\"#"
3212  << mapLabel << "\" alt=\"";
3213  switch (m_graphType)
3214  {
3216  out << "Collaboration graph";
3217  break;
3218  case DotNode::Inheritance:
3219  out << "Inheritance graph";
3220  break;
3221  default:
3222  ASSERT(0);
3223  break;
3224  }
3225  out << "\"/>";
3226  out << "</div>" << endl;
3227  if (regenerate || !insertMapFile(out,absMapName,relPath,mapLabel))
3228  {
3229  int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3230  FALSE,QCString(),mapLabel);
3231  out << "<!-- MAP " << mapId << " -->" << endl;
3232  }
3233  }
3234  }
3235  else if (graphFormat==GOF_EPS) // produce tex to include the .eps image
3236  {
3237  if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3238  {
3239  int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE /*TRUE*/);
3240  out << endl << "% FIG " << figId << endl;
3241  }
3242  }
3243  if (!regenerate) removeDotGraph(absDotName);
3244 
3245  return baseName;
3246 }
3247 
3248 //--------------------------------------------------------------------
3249 
3251 {
3252  QDictIterator<DotNode> dni(*m_usedNodes);
3253  DotNode *node;
3254  for (;(node=dni.current());++dni)
3255  {
3256  node->writeXML(t,TRUE);
3257  }
3258 }
3259 
3261 {
3262  QDictIterator<DotNode> dni(*m_usedNodes);
3263  DotNode *node;
3264  for (;(node=dni.current());++dni)
3265  {
3266  node->writeDocbook(t,TRUE);
3267  }
3268 }
3269 
3271 {
3272  QDictIterator<DotNode> dni(*m_usedNodes);
3273  DotNode *node;
3274  for (;(node=dni.current());++dni)
3275  {
3276  node->writeDEF(t);
3277  }
3278 }
3279 
3280 //--------------------------------------------------------------------
3281 
3283 {
3284  QList<IncludeInfo> *includeFiles =
3286  if (includeFiles)
3287  {
3288  QListIterator<IncludeInfo> ili(*includeFiles);
3289  IncludeInfo *ii;
3290  for (;(ii=ili.current());++ili)
3291  {
3292  FileDef *bfd = ii->fileDef;
3293  QCString in = ii->includeName;
3294  //printf(">>>> in=`%s' bfd=%p\n",ii->includeName.data(),bfd);
3295  bool doc=TRUE,src=FALSE;
3296  if (bfd)
3297  {
3298  in = bfd->absFilePath();
3299  doc = bfd->isLinkable() && !bfd->isHidden();
3300  src = bfd->generateSourceFile();
3301  }
3302  if (doc || src || !Config_getBool(HIDE_UNDOC_RELATIONS))
3303  {
3304  QCString url="";
3305  if (bfd) url=bfd->getOutputFileBase().copy();
3306  if (!doc && src)
3307  {
3308  url=bfd->getSourceFileBase();
3309  }
3310  DotNode *bn = m_usedNodes->find(in);
3311  if (bn) // file is already a node in the graph
3312  {
3313  n->addChild(bn,0,0,0);
3314  bn->addParent(n);
3315  bn->setDistance(distance);
3316  }
3317  else
3318  {
3319  QCString tmp_url;
3320  QCString tooltip;
3321  if (bfd)
3322  {
3323  tmp_url=doc || src ? bfd->getReference()+"$"+url : QCString();
3324  tooltip = bfd->briefDescriptionAsTooltip();
3325  }
3326  bn = new DotNode(
3327  m_curNodeNumber++, // n
3328  ii->includeName, // label
3329  tooltip, // tip
3330  tmp_url, // url
3331  FALSE, // rootNode
3332  0 // cd
3333  );
3334  n->addChild(bn,0,0,0);
3335  bn->addParent(n);
3336  m_usedNodes->insert(in,bn);
3337  bn->setDistance(distance);
3338 
3339  if (bfd) buildGraph(bn,bfd,distance+1);
3340  }
3341  }
3342  }
3343  }
3344 }
3345 
3346 void DotInclDepGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
3347 {
3348  while (queue.count()>0 && maxNodes>0)
3349  {
3350  static int maxDistance = Config_getInt(MAX_DOT_GRAPH_DEPTH);
3351  DotNode *n = queue.take(0);
3352  if (!n->isVisible() && n->distance()<=maxDistance) // not yet processed
3353  {
3354  n->markAsVisible();
3355  maxNodes--;
3356  // add direct children
3357  if (n->m_children)
3358  {
3359  QListIterator<DotNode> li(*n->m_children);
3360  DotNode *dn;
3361  for (li.toFirst();(dn=li.current());++li)
3362  {
3363  queue.append(dn);
3364  }
3365  }
3366  }
3367  }
3368 }
3369 
3370 void DotInclDepGraph::determineTruncatedNodes(QList<DotNode> &queue)
3371 {
3372  while (queue.count()>0)
3373  {
3374  DotNode *n = queue.take(0);
3375  if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
3376  {
3377  bool truncated = FALSE;
3378  if (n->m_children)
3379  {
3380  QListIterator<DotNode> li(*n->m_children);
3381  DotNode *dn;
3382  for (li.toFirst();(dn=li.current());++li)
3383  {
3384  if (!dn->isVisible())
3385  truncated = TRUE;
3386  else
3387  queue.append(dn);
3388  }
3389  }
3390  n->markAsTruncated(truncated);
3391  }
3392  }
3393 }
3394 
3396 
3398 {
3399  m_curNodeNumber = 0;
3400 }
3401 
3403 {
3404  m_inverse = inverse;
3405  ASSERT(fd!=0);
3408  QCString tmp_url=fd->getReference()+"$"+fd->getOutputFileBase();
3410  fd->docName(),
3411  "",
3412  tmp_url.data(),
3413  TRUE // root node
3414  );
3416  m_usedNodes = new QDict<DotNode>(1009);
3417  m_usedNodes->insert(fd->absFilePath(),m_startNode);
3418  buildGraph(m_startNode,fd,1);
3419 
3420  static int nodes = Config_getInt(DOT_GRAPH_MAX_NODES);
3421  int maxNodes = nodes;
3422  //int directChildNodes = 1;
3423  //if (m_startNode->m_children!=0)
3424  // directChildNodes+=m_startNode->m_children->count();
3425  //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3426  QList<DotNode> openNodeQueue;
3427  openNodeQueue.append(m_startNode);
3428  determineVisibleNodes(openNodeQueue,maxNodes);
3429  openNodeQueue.clear();
3430  openNodeQueue.append(m_startNode);
3431  determineTruncatedNodes(openNodeQueue);
3432 }
3433 
3435 {
3437  delete m_usedNodes;
3438 }
3439 
3441  GraphOutputFormat graphFormat,
3442  EmbeddedOutputFormat textFormat,
3443  const char *path,
3444  const char *fileName,
3445  const char *relPath,
3446  bool generateImageMap,
3447  int graphId
3448  ) const
3449 {
3450  QDir d(path);
3451  // store the original directory
3452  if (!d.exists())
3453  {
3454  err("Output dir %s does not exist!\n",path); exit(1);
3455  }
3456  static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
3457 
3458  QCString baseName;
3459  if (m_inverse)
3460  {
3461  baseName=m_inclByDepFileName;
3462  }
3463  else
3464  {
3465  baseName=m_inclDepFileName;
3466  }
3467  QCString mapName=escapeCharsInString(m_startNode->m_label,FALSE);
3468  if (m_inverse) mapName+="dep";
3469 
3470  QCString imgExt = getDotImageExtension();
3471  QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
3472  QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3473  QCString absDotName = absBaseName+".dot";
3474  QCString absMapName = absBaseName+".map";
3475  QCString absPdfName = absBaseName+".pdf";
3476  QCString absEpsName = absBaseName+".eps";
3477  QCString absImgName = absBaseName+"."+imgExt;
3478 
3479  bool regenerate = FALSE;
3482  absBaseName,
3483  graphFormat,
3484  FALSE, // lrRank
3485  FALSE, // renderParents
3486  m_inverse, // backArrows
3487  m_startNode->label()
3488  ) ||
3489  !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
3490  usePDFLatex ? absPdfName : absEpsName,
3491  graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString())
3492  )
3493  {
3494  regenerate=TRUE;
3495  if (graphFormat==GOF_BITMAP)
3496  {
3497  // run dot to create a bitmap image
3498  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3499  dotRun->addJob(imgFmt,absImgName);
3500  if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3501  DotManager::instance()->addRun(dotRun);
3502  }
3503  else if (graphFormat==GOF_EPS)
3504  {
3505  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3506  if (usePDFLatex)
3507  {
3508  dotRun->addJob("pdf",absPdfName);
3509  }
3510  else
3511  {
3512  dotRun->addJob("ps",absEpsName);
3513  }
3514  DotManager::instance()->addRun(dotRun);
3515  }
3516  }
3517  Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3518 
3519  if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
3520  {
3521  out << "<para>" << endl;
3522  out << " <figure>" << endl;
3523  out << " <title>Dependency diagram";
3524  out << "</title>" << endl;
3525  out << " <mediaobject>" << endl;
3526  out << " <imageobject>" << endl;
3527  out << " <imagedata";
3528  out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"1\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
3529  out << "</imagedata>" << endl;
3530  out << " </imageobject>" << endl;
3531  out << " </mediaobject>" << endl;
3532  out << " </figure>" << endl;
3533  out << "</para>" << endl;
3534  }
3535  else if (graphFormat==GOF_BITMAP && generateImageMap)
3536  {
3537  if (imgExt=="svg") // Scalable vector graphics
3538  {
3539  out << "<div class=\"center\">";
3540  if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3541  {
3542  if (regenerate)
3543  {
3544  DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3545  }
3546  int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3547  out << "<!-- SVG " << mapId << " -->" << endl;
3548  }
3549  out << "</div>" << endl;
3550  }
3551  else // bitmap graphics
3552  {
3553  out << "<div class=\"center\"><img src=\"" << relPath << baseName << "." << imgExt << "\" border=\"0\" usemap=\"#" << mapName << "\" alt=\"\"/>";
3554  out << "</div>" << endl;
3555 
3556  QCString absMapName = absBaseName+".map";
3557  if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3558  {
3559  int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3560  FALSE,QCString(),mapName);
3561  out << "<!-- MAP " << mapId << " -->" << endl;
3562  }
3563  }
3564  }
3565  else if (graphFormat==GOF_EPS) // encapsulated postscript
3566  {
3567  if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3568  {
3569  int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3570  out << endl << "% FIG " << figId << endl;
3571  }
3572  }
3573  if (!regenerate) removeDotGraph(absDotName);
3574 
3575  return baseName;
3576 }
3577 
3579 {
3580  return m_startNode->m_children==0;
3581 }
3582 
3584 {
3585  static int maxNodes = Config_getInt(DOT_GRAPH_MAX_NODES);
3586  int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
3587  return numNodes>=maxNodes;
3588 }
3589 
3591 {
3592  QDictIterator<DotNode> dni(*m_usedNodes);
3593  DotNode *node;
3594  for (;(node=dni.current());++dni)
3595  {
3596  node->writeXML(t,FALSE);
3597  }
3598 }
3599 
3601 {
3602  QDictIterator<DotNode> dni(*m_usedNodes);
3603  DotNode *node;
3604  for (;(node=dni.current());++dni)
3605  {
3606  node->writeDocbook(t,FALSE);
3607  }
3608 }
3609 
3610 //-------------------------------------------------------------
3611 
3612 void DotCallGraph::buildGraph(DotNode *n,MemberDef *md,int distance)
3613 {
3615  if (refs)
3616  {
3617  MemberSDict::Iterator mri(*refs);
3618  MemberDef *rmd;
3619  for (;(rmd=mri.current());++mri)
3620  {
3621  if (rmd->showInCallGraph())
3622  {
3623  QCString uniqueId;
3624  uniqueId=rmd->getReference()+"$"+
3625  rmd->getOutputFileBase()+"#"+rmd->anchor();
3626  DotNode *bn = m_usedNodes->find(uniqueId);
3627  if (bn) // file is already a node in the graph
3628  {
3629  n->addChild(bn,0,0,0);
3630  bn->addParent(n);
3631  bn->setDistance(distance);
3632  }
3633  else
3634  {
3635  QCString name;
3636  if (Config_getBool(HIDE_SCOPE_NAMES))
3637  {
3638  name = rmd->getOuterScope()==m_scope ?
3639  rmd->name() : rmd->qualifiedName();
3640  }
3641  else
3642  {
3643  name = rmd->qualifiedName();
3644  }
3645  QCString tooltip = rmd->briefDescriptionAsTooltip();
3646  bn = new DotNode(
3647  m_curNodeNumber++,
3648  linkToText(rmd->getLanguage(),name,FALSE),
3649  tooltip,
3650  uniqueId,
3651  0 //distance
3652  );
3653  n->addChild(bn,0,0,0);
3654  bn->addParent(n);
3655  bn->setDistance(distance);
3656  m_usedNodes->insert(uniqueId,bn);
3657 
3658  buildGraph(bn,rmd,distance+1);
3659  }
3660  }
3661  }
3662  }
3663 }
3664 
3665 void DotCallGraph::determineVisibleNodes(QList<DotNode> &queue, int &maxNodes)
3666 {
3667  while (queue.count()>0 && maxNodes>0)
3668  {
3669  static int maxDistance = Config_getInt(MAX_DOT_GRAPH_DEPTH);
3670  DotNode *n = queue.take(0);
3671  if (!n->isVisible() && n->distance()<=maxDistance) // not yet processed
3672  {
3673  n->markAsVisible();
3674  maxNodes--;
3675  // add direct children
3676  if (n->m_children)
3677  {
3678  QListIterator<DotNode> li(*n->m_children);
3679  DotNode *dn;
3680  for (li.toFirst();(dn=li.current());++li)
3681  {
3682  queue.append(dn);
3683  }
3684  }
3685  }
3686  }
3687 }
3688 
3689 void DotCallGraph::determineTruncatedNodes(QList<DotNode> &queue)
3690 {
3691  while (queue.count()>0)
3692  {
3693  DotNode *n = queue.take(0);
3694  if (n->isVisible() && n->isTruncated()==DotNode::Unknown)
3695  {
3696  bool truncated = FALSE;
3697  if (n->m_children)
3698  {
3699  QListIterator<DotNode> li(*n->m_children);
3700  DotNode *dn;
3701  for (li.toFirst();(dn=li.current());++li)
3702  {
3703  if (!dn->isVisible())
3704  truncated = TRUE;
3705  else
3706  queue.append(dn);
3707  }
3708  }
3709  n->markAsTruncated(truncated);
3710  }
3711  }
3712 }
3713 
3715 
3717 {
3718  m_curNodeNumber = 0;
3719 }
3720 
3722 {
3723  m_inverse = inverse;
3724  m_diskName = md->getOutputFileBase()+"_"+md->anchor();
3725  m_scope = md->getOuterScope();
3726  QCString uniqueId;
3727  uniqueId = md->getReference()+"$"+
3728  md->getOutputFileBase()+"#"+md->anchor();
3729  QCString name;
3730  if (Config_getBool(HIDE_SCOPE_NAMES))
3731  {
3732  name = md->name();
3733  }
3734  else
3735  {
3736  name = md->qualifiedName();
3737  }
3739  linkToText(md->getLanguage(),name,FALSE),
3740  "",
3741  uniqueId.data(),
3742  TRUE // root node
3743  );
3745  m_usedNodes = new QDict<DotNode>(1009);
3746  m_usedNodes->insert(uniqueId,m_startNode);
3747  buildGraph(m_startNode,md,1);
3748 
3749  static int nodes = Config_getInt(DOT_GRAPH_MAX_NODES);
3750  int maxNodes = nodes;
3751  //int directChildNodes = 1;
3752  //if (m_startNode->m_children!=0)
3753  // directChildNodes+=m_startNode->m_children->count();
3754  //if (directChildNodes>maxNodes) maxNodes=directChildNodes;
3755  QList<DotNode> openNodeQueue;
3756  openNodeQueue.append(m_startNode);
3757  determineVisibleNodes(openNodeQueue,maxNodes);
3758  openNodeQueue.clear();
3759  openNodeQueue.append(m_startNode);
3760  determineTruncatedNodes(openNodeQueue);
3761 }
3762 
3764 {
3766  delete m_usedNodes;
3767 }
3768 
3770  EmbeddedOutputFormat textFormat,
3771  const char *path,const char *fileName,
3772  const char *relPath,bool generateImageMap,int
3773  graphId) const
3774 {
3775  QDir d(path);
3776  // store the original directory
3777  if (!d.exists())
3778  {
3779  err("Output dir %s does not exist!\n",path); exit(1);
3780  }
3781  static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
3782 
3783  QCString baseName = m_diskName + (m_inverse ? "_icgraph" : "_cgraph");
3784  QCString mapName = baseName;
3785 
3786  QCString imgExt = getDotImageExtension();
3787  QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
3788  QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3789  QCString absDotName = absBaseName+".dot";
3790  QCString absMapName = absBaseName+".map";
3791  QCString absPdfName = absBaseName+".pdf";
3792  QCString absEpsName = absBaseName+".eps";
3793  QCString absImgName = absBaseName+"."+imgExt;
3794 
3795  bool regenerate = FALSE;
3798  absBaseName,
3799  graphFormat,
3800  TRUE, // lrRank
3801  FALSE, // renderParents
3802  m_inverse, // backArrows
3803  m_startNode->label()
3804  ) ||
3805  !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
3806  usePDFLatex ? absPdfName : absEpsName,
3807  graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString())
3808  )
3809  {
3810  regenerate=TRUE;
3811  if (graphFormat==GOF_BITMAP)
3812  {
3813  // run dot to create a bitmap image
3814  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3815  dotRun->addJob(imgFmt,absImgName);
3816  if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3817  DotManager::instance()->addRun(dotRun);
3818 
3819  }
3820  else if (graphFormat==GOF_EPS)
3821  {
3822  // run dot to create a .eps image
3823  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3824  if (usePDFLatex)
3825  {
3826  dotRun->addJob("pdf",absPdfName);
3827  }
3828  else
3829  {
3830  dotRun->addJob("ps",absEpsName);
3831  }
3832  DotManager::instance()->addRun(dotRun);
3833 
3834  }
3835  }
3836  Doxygen::indexList->addImageFile(baseName+"."+imgExt);
3837 
3838  if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
3839  {
3840  out << "<para>" << endl;
3841  out << " <figure>" << endl;
3842  out << " <title>Call diagram";
3843  out << "</title>" << endl;
3844  out << " <mediaobject>" << endl;
3845  out << " <imageobject>" << endl;
3846  out << " <imagedata";
3847  out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"1\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
3848  out << "</imagedata>" << endl;
3849  out << " </imageobject>" << endl;
3850  out << " </mediaobject>" << endl;
3851  out << " </figure>" << endl;
3852  out << "</para>" << endl;
3853  }
3854  else if (graphFormat==GOF_BITMAP && generateImageMap)
3855  {
3856  if (imgExt=="svg") // Scalable vector graphics
3857  {
3858  out << "<div class=\"center\">";
3859  if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
3860  {
3861  if (regenerate)
3862  {
3863  DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
3864  }
3865  int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
3866  out << "<!-- SVG " << mapId << " -->" << endl;
3867  }
3868  out << "</div>" << endl;
3869  }
3870  else // bitmap graphics
3871  {
3872  out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
3873  << imgExt << "\" border=\"0\" usemap=\"#"
3874  << mapName << "\" alt=\"";
3875  out << "\"/>";
3876  out << "</div>" << endl;
3877 
3878  if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
3879  {
3880  int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
3881  FALSE,QCString(),mapName);
3882  out << "<!-- MAP " << mapId << " -->" << endl;
3883  }
3884  }
3885  }
3886  else if (graphFormat==GOF_EPS) // encapsulated postscript
3887  {
3888  if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
3889  {
3890  int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
3891  out << endl << "% FIG " << figId << endl;
3892  }
3893  }
3894  if (!regenerate) removeDotGraph(absDotName);
3895 
3896  return baseName;
3897 }
3898 
3900 {
3901  return m_startNode->m_children==0;
3902 }
3903 
3905 {
3906  static int maxNodes = Config_getInt(DOT_GRAPH_MAX_NODES);
3907  int numNodes = m_startNode->m_children ? m_startNode->m_children->count() : 0;
3908  return numNodes>=maxNodes;
3909 }
3910 
3911 //-------------------------------------------------------------
3912 static void writeDotDirDepGraph(FTextStream &t,DirDef *dd,bool linkRelations);
3913 
3914 DotDirDeps::DotDirDeps(DirDef *dir) : m_dir(dir)
3915 {
3916 }
3917 
3919 {
3920 }
3921 
3923  GraphOutputFormat graphFormat,
3924  EmbeddedOutputFormat textFormat,
3925  const char *path,
3926  const char *fileName,
3927  const char *relPath,
3928  bool generateImageMap,
3929  int graphId,
3930  bool linkRelations) const
3931 {
3932  QDir d(path);
3933  // store the original directory
3934  if (!d.exists())
3935  {
3936  err("Output dir %s does not exist!\n",path); exit(1);
3937  }
3938  static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
3939 
3940  QCString baseName=m_dir->getOutputFileBase()+"_dep";
3941  QCString mapName=escapeCharsInString(baseName,FALSE);
3942 
3943  QCString imgExt = getDotImageExtension();
3944  QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
3945  QCString absBaseName = d.absPath().utf8()+"/"+baseName;
3946  QCString absDotName = absBaseName+".dot";
3947  QCString absMapName = absBaseName+".map";
3948  QCString absPdfName = absBaseName+".pdf";
3949  QCString absEpsName = absBaseName+".eps";
3950  QCString absImgName = absBaseName+"."+imgExt;
3951 
3952  // compute md5 checksum of the graph were are about to generate
3953  QGString theGraph;
3954  FTextStream md5stream(&theGraph);
3955  //m_dir->writeDepGraph(md5stream);
3956  writeDotDirDepGraph(md5stream,m_dir,linkRelations);
3957  uchar md5_sig[16];
3958  QCString sigStr(33);
3959  MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
3960  MD5SigToString(md5_sig,sigStr.rawData(),33);
3961  bool regenerate=FALSE;
3962  if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
3963  !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
3964  usePDFLatex ? absPdfName : absEpsName,
3965  graphFormat==GOF_BITMAP && generateImageMap ? absMapName : QCString())
3966  )
3967  {
3968  regenerate=TRUE;
3969 
3970  QFile f(absDotName);
3971  if (!f.open(IO_WriteOnly))
3972  {
3973  err("Cannot create file %s.dot for writing!\n",baseName.data());
3974  }
3975  FTextStream t(&f);
3976  t << theGraph.data();
3977  f.close();
3978 
3979  if (graphFormat==GOF_BITMAP)
3980  {
3981  // run dot to create a bitmap image
3982  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
3983  dotRun->addJob(imgFmt,absImgName);
3984  if (generateImageMap) dotRun->addJob(MAP_CMD,absMapName);
3985  DotManager::instance()->addRun(dotRun);
3986  }
3987  else if (graphFormat==GOF_EPS)
3988  {
3989  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
3990  if (usePDFLatex)
3991  {
3992  dotRun->addJob("pdf",absPdfName);
3993  }
3994  else
3995  {
3996  dotRun->addJob("ps",absEpsName);
3997  }
3998  DotManager::instance()->addRun(dotRun);
3999  }
4000  }
4001  Doxygen::indexList->addImageFile(baseName+"."+imgExt);
4002 
4003  if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
4004  {
4005  out << "<para>" << endl;
4006  out << " <figure>" << endl;
4007  out << " <title>Directory Dependency diagram";
4008  out << "</title>" << endl;
4009  out << " <mediaobject>" << endl;
4010  out << " <imageobject>" << endl;
4011  out << " <imagedata";
4012  out << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"1\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
4013  out << "</imagedata>" << endl;
4014  out << " </imageobject>" << endl;
4015  out << " </mediaobject>" << endl;
4016  out << " </figure>" << endl;
4017  out << "</para>" << endl;
4018  }
4019  else if (graphFormat==GOF_BITMAP && generateImageMap)
4020  {
4021  if (imgExt=="svg") // Scalable vector graphics
4022  {
4023  out << "<div class=\"center\">";
4024  if (regenerate || !writeSVGFigureLink(out,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
4025  {
4026  if (regenerate)
4027  {
4028  DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
4029  }
4030  int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
4031  out << "<!-- SVG " << mapId << " -->" << endl;
4032  }
4033  out << "</div>" << endl;
4034  }
4035  else // bitmap graphics
4036  {
4037  out << "<div class=\"center\"><img src=\"" << relPath << baseName << "."
4038  << imgExt << "\" border=\"0\" usemap=\"#"
4039  << mapName << "\" alt=\"";
4040  out << convertToXML(m_dir->displayName());
4041  out << "\"/>";
4042  out << "</div>" << endl;
4043 
4044  if (regenerate || !insertMapFile(out,absMapName,relPath,mapName))
4045  {
4046  int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
4047  TRUE,QCString(),mapName);
4048  out << "<!-- MAP " << mapId << " -->" << endl;
4049  }
4050  }
4051  }
4052  else if (graphFormat==GOF_EPS)
4053  {
4054  if (regenerate || !writeVecGfxFigure(out,baseName,absBaseName))
4055  {
4056  int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
4057  out << endl << "% FIG " << figId << endl;
4058  }
4059  }
4060  if (!regenerate) removeDotGraph(absDotName);
4061 
4062  return baseName;
4063 }
4064 
4066 {
4067  return m_dir->depGraphIsTrivial();
4068 }
4069 
4070 //-------------------------------------------------------------
4071 
4072 void generateGraphLegend(const char *path)
4073 {
4074  QDir d(path);
4075  // store the original directory
4076  if (!d.exists())
4077  {
4078  err("Output dir %s does not exist!\n",path); exit(1);
4079  }
4080 
4081  QGString theGraph;
4082  FTextStream md5stream(&theGraph);
4084  md5stream << " Node9 [shape=\"box\",label=\"Inherited\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",fillcolor=\"grey75\",style=\"filled\" fontcolor=\"black\"];\n";
4085  md5stream << " Node10 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4086  md5stream << " Node10 [shape=\"box\",label=\"PublicBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPublicBase" << Doxygen::htmlFileExtension << "\"];\n";
4087  md5stream << " Node11 -> Node10 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4088  md5stream << " Node11 [shape=\"box\",label=\"Truncated\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"red\",URL=\"$classTruncated" << Doxygen::htmlFileExtension << "\"];\n";
4089  md5stream << " Node13 -> Node9 [dir=\"back\",color=\"darkgreen\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4090  md5stream << " Node13 [shape=\"box\",label=\"ProtectedBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classProtectedBase" << Doxygen::htmlFileExtension << "\"];\n";
4091  md5stream << " Node14 -> Node9 [dir=\"back\",color=\"firebrick4\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4092  md5stream << " Node14 [shape=\"box\",label=\"PrivateBase\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classPrivateBase" << Doxygen::htmlFileExtension << "\"];\n";
4093  md5stream << " Node15 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4094  md5stream << " Node15 [shape=\"box\",label=\"Undocumented\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"grey75\"];\n";
4095  md5stream << " Node16 -> Node9 [dir=\"back\",color=\"midnightblue\",fontsize=\"" << FONTSIZE << "\",style=\"solid\",fontname=\"" << FONTNAME << "\"];\n";
4096  md5stream << " Node16 [shape=\"box\",label=\"Templ< int >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
4097  md5stream << " Node17 -> Node16 [dir=\"back\",color=\"orange\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"< int >\",fontname=\"" << FONTNAME << "\"];\n";
4098  md5stream << " Node17 [shape=\"box\",label=\"Templ< T >\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classTempl" << Doxygen::htmlFileExtension << "\"];\n";
4099  md5stream << " Node18 -> Node9 [dir=\"back\",color=\"darkorchid3\",fontsize=\"" << FONTSIZE << "\",style=\"dashed\",label=\"m_usedClass\",fontname=\"" << FONTNAME << "\"];\n";
4100  md5stream << " Node18 [shape=\"box\",label=\"Used\",fontsize=\"" << FONTSIZE << "\",height=0.2,width=0.4,fontname=\"" << FONTNAME << "\",color=\"black\",URL=\"$classUsed" << Doxygen::htmlFileExtension << "\"];\n";
4101  writeGraphFooter(md5stream);
4102  uchar md5_sig[16];
4103  QCString sigStr(33);
4104  MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
4105  MD5SigToString(md5_sig,sigStr.rawData(),33);
4106  QCString absBaseName = (QCString)path+"/graph_legend";
4107  QCString absDotName = absBaseName+".dot";
4108  QCString imgExt = getDotImageExtension();
4109  QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
4110  QCString imgName = "graph_legend."+imgExt;
4111  QCString absImgName = absBaseName+"."+imgExt;
4112  if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
4113  !checkDeliverables(absImgName))
4114  {
4115  QFile dotFile(absDotName);
4116  if (!dotFile.open(IO_WriteOnly))
4117  {
4118  err("Could not open file %s for writing\n",dotFile.name().data());
4119  return;
4120  }
4121 
4122  FTextStream dotText(&dotFile);
4123  dotText << theGraph;
4124  dotFile.close();
4125 
4126  // run dot to generate the a bitmap image from the graph
4127 
4128  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),TRUE,absImgName);
4129  dotRun->addJob(imgFmt,absImgName);
4130  DotManager::instance()->addRun(dotRun);
4131  }
4132  else
4133  {
4134  removeDotGraph(absDotName);
4135  }
4136  Doxygen::indexList->addImageFile(imgName);
4137 
4138  if (imgExt=="svg")
4139  {
4141  absBaseName+Config_getString(HTML_FILE_EXTENSION),
4142  "graph_legend",
4143  absImgName,QCString());
4144  }
4145 
4146 }
4147 
4148 void writeDotGraphFromFile(const char *inFile,const char *outDir,
4149  const char *outFile,GraphOutputFormat format)
4150 {
4151  QDir d(outDir);
4152  if (!d.exists())
4153  {
4154  err("Output dir %s does not exist!\n",outDir); exit(1);
4155  }
4156 
4157  QCString imgExt = getDotImageExtension();
4158  QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
4159  QCString imgName = (QCString)outFile+"."+imgExt;
4160  QCString absImgName = d.absPath().utf8()+"/"+imgName;
4161  QCString absOutFile = d.absPath().utf8()+"/"+outFile;
4162 
4163  DotRunner dotRun(inFile,d.absPath().data(),FALSE,absImgName);
4164  if (format==GOF_BITMAP)
4165  dotRun.addJob(imgFmt,absImgName);
4166  else // format==GOF_EPS
4167  {
4168  if (Config_getBool(USE_PDFLATEX))
4169  {
4170  dotRun.addJob("pdf",absOutFile+".pdf");
4171  }
4172  else
4173  {
4174  dotRun.addJob("ps",absOutFile+".eps");
4175  }
4176  }
4177 
4178  dotRun.preventCleanUp();
4179  if (!dotRun.run())
4180  {
4181  return;
4182  }
4183 
4184  if (format==GOF_BITMAP) checkDotResult(getDotImageExtension(),absImgName);
4185 
4186  Doxygen::indexList->addImageFile(imgName);
4187 
4188 }
4189 
4190 
4201  const QCString &inFile, const QCString &outDir,
4202  const QCString &relPath, const QCString &baseName,
4203  const QCString &context,int graphId)
4204 {
4205 
4206  QDir d(outDir);
4207  if (!d.exists())
4208  {
4209  err("Output dir %s does not exist!\n",outDir.data()); exit(1);
4210  }
4211 
4212  QCString mapName = baseName+".map";
4213  QCString imgExt = getDotImageExtension();
4214  QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
4215  QCString imgName = baseName+"."+imgExt;
4216  QCString absOutFile = d.absPath().utf8()+"/"+mapName;
4217 
4218  DotRunner dotRun(inFile,d.absPath().data(),FALSE);
4219  dotRun.addJob(MAP_CMD,absOutFile);
4220  dotRun.preventCleanUp();
4221  if (!dotRun.run())
4222  {
4223  return;
4224  }
4225 
4226  if (imgExt=="svg") // vector graphics
4227  {
4228  //writeSVGFigureLink(t,relPath,inFile,inFile+".svg");
4229  //DotFilePatcher patcher(inFile+".svg");
4230  QCString svgName=outDir+"/"+baseName+".svg";
4231  writeSVGFigureLink(t,relPath,baseName,svgName);
4232  DotFilePatcher patcher(svgName);
4233  patcher.addSVGConversion(relPath,TRUE,context,TRUE,graphId);
4234  patcher.run();
4235  }
4236  else // bitmap graphics
4237  {
4238  t << "<img src=\"" << relPath << imgName << "\" alt=\""
4239  << imgName << "\" border=\"0\" usemap=\"#" << mapName << "\"/>" << endl
4240  << "<map name=\"" << mapName << "\" id=\"" << mapName << "\">";
4241 
4242  convertMapFile(t, absOutFile, relPath ,TRUE, context);
4243 
4244  t << "</map>" << endl;
4245  }
4246  d.remove(absOutFile);
4247 }
4248 
4249 //-------------------------------------------------------------
4250 
4252 
4254 {
4255  m_curNodeNumber = 0;
4256 }
4257 
4259 {
4260  QCString tmp_url = gd->getReference()+"$"+gd->getOutputFileBase();
4261  m_usedNodes = new QDict<DotNode>(1009);
4262  m_rootNode = new DotNode(m_curNodeNumber++, gd->groupTitle(), "", tmp_url, TRUE );
4263  m_rootNode->markAsVisible();
4264  m_usedNodes->insert(gd->name(), m_rootNode );
4265  m_edges.setAutoDelete(TRUE);
4266 
4267  m_diskName = gd->getOutputFileBase();
4268 
4269  buildGraph( gd );
4270 }
4271 
4273 {
4274  delete m_usedNodes;
4275 }
4276 
4278 {
4279  QCString tmp_url;
4280  //===========================
4281  // hierarchy.
4282 
4283  // Write parents
4284  GroupList *groups = gd->partOfGroups();
4285  if ( groups )
4286  {
4287  GroupListIterator gli(*groups);
4288  GroupDef *d;
4289  for (gli.toFirst();(d=gli.current());++gli)
4290  {
4291  DotNode* nnode = m_usedNodes->find(d->name());
4292  if ( !nnode )
4293  { // add node
4294  tmp_url = d->getReference()+"$"+d->getOutputFileBase();
4295  QCString tooltip = d->briefDescriptionAsTooltip();
4296  nnode = new DotNode(m_curNodeNumber++, d->groupTitle(), tooltip, tmp_url );
4297  nnode->markAsVisible();
4298  m_usedNodes->insert(d->name(), nnode );
4299  }
4300  tmp_url = "";
4301  addEdge( nnode, m_rootNode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
4302  }
4303  }
4304 
4305  // Add subgroups
4306  if ( gd->getSubGroups() && gd->getSubGroups()->count() )
4307  {
4308  QListIterator<GroupDef> defli(*gd->getSubGroups());
4309  GroupDef *def;
4310  for (;(def=defli.current());++defli)
4311  {
4312  DotNode* nnode = m_usedNodes->find(def->name());
4313  if ( !nnode )
4314  { // add node
4315  tmp_url = def->getReference()+"$"+def->getOutputFileBase();
4316  QCString tooltip = def->briefDescriptionAsTooltip();
4317  nnode = new DotNode(m_curNodeNumber++, def->groupTitle(), tooltip, tmp_url );
4318  nnode->markAsVisible();
4319  m_usedNodes->insert(def->name(), nnode );
4320  }
4321  tmp_url = "";
4322  addEdge( m_rootNode, nnode, DotGroupCollaboration::thierarchy, tmp_url, tmp_url );
4323  }
4324  }
4325 
4326  //=======================
4327  // Write collaboration
4328 
4329  // Add members
4331 
4332  // Add classes
4333  if ( gd->getClasses() && gd->getClasses()->count() )
4334  {
4335  ClassSDict::Iterator defli(*gd->getClasses());
4336  ClassDef *def;
4337  for (;(def=defli.current());++defli)
4338  {
4339  tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4340  if (!def->anchor().isEmpty())
4341  {
4342  tmp_url+="#"+def->anchor();
4343  }
4345  }
4346  }
4347 
4348  // Add namespaces
4349  if ( gd->getNamespaces() && gd->getNamespaces()->count() )
4350  {
4352  NamespaceDef *def;
4353  for (;(def=defli.current());++defli)
4354  {
4355  tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4357  }
4358  }
4359 
4360  // Add files
4361  if ( gd->getFiles() && gd->getFiles()->count() )
4362  {
4363  QListIterator<FileDef> defli(*gd->getFiles());
4364  FileDef *def;
4365  for (;(def=defli.current());++defli)
4366  {
4367  tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4369  }
4370  }
4371 
4372  // Add pages
4373  if ( gd->getPages() && gd->getPages()->count() )
4374  {
4375  PageSDict::Iterator defli(*gd->getPages());
4376  PageDef *def;
4377  for (;(def=defli.current());++defli)
4378  {
4379  tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4381  }
4382  }
4383 
4384  // Add directories
4385  if ( gd->getDirs() && gd->getDirs()->count() )
4386  {
4387  QListIterator<DirDef> defli(*gd->getDirs());
4388  DirDef *def;
4389  for (;(def=defli.current());++defli)
4390  {
4391  tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension;
4393  }
4394  }
4395 }
4396 
4398 {
4399  if ( !( ml && ml->count()) ) return;
4400  MemberListIterator defli(*ml);
4401  MemberDef *def;
4402  for (;(def=defli.current());++defli)
4403  {
4404  QCString tmp_url = def->getReference()+"$"+def->getOutputFileBase()+Doxygen::htmlFileExtension
4405  +"#"+def->anchor();
4407  }
4408 }
4409 
4411  DotNode* _pNStart, DotNode* _pNEnd, EdgeType _eType,
4412  const QCString& _label, const QCString& _url )
4413 {
4414  // search a existing link.
4415  QListIterator<Edge> lli(m_edges);
4416  Edge* newEdge = 0;
4417  for ( lli.toFirst(); (newEdge=lli.current()); ++lli)
4418  {
4419  if ( newEdge->pNStart==_pNStart &&
4420  newEdge->pNEnd==_pNEnd &&
4421  newEdge->eType==_eType
4422  )
4423  { // edge already found
4424  break;
4425  }
4426  }
4427  if ( newEdge==0 ) // new link
4428  {
4429  newEdge = new Edge(_pNStart,_pNEnd,_eType);
4430  m_edges.append( newEdge );
4431  }
4432 
4433  if (!_label.isEmpty())
4434  {
4435  newEdge->links.append(new Link(_label,_url));
4436  }
4437 
4438  return newEdge;
4439 }
4440 
4442  Definition* def, QCString& url, EdgeType eType )
4443 {
4444  // Create group nodes
4445  if ( !def->partOfGroups() )
4446  return;
4447  GroupListIterator gli(*def->partOfGroups());
4448  GroupDef *d;
4449  QCString tmp_str;
4450  for (;(d=gli.current());++gli)
4451  {
4452  DotNode* nnode = m_usedNodes->find(d->name());
4453  if ( nnode != m_rootNode )
4454  {
4455  if ( nnode==0 )
4456  { // add node
4457  tmp_str = d->getReference()+"$"+d->getOutputFileBase();
4458  QCString tooltip = d->briefDescriptionAsTooltip();
4459  nnode = new DotNode(m_curNodeNumber++, d->groupTitle(), tooltip, tmp_str );
4460  nnode->markAsVisible();
4461  m_usedNodes->insert(d->name(), nnode );
4462  }
4463  tmp_str = def->qualifiedName();
4464  addEdge( m_rootNode, nnode, eType, tmp_str, url );
4465  }
4466  }
4467 }
4468 
4469 
4471  GraphOutputFormat graphFormat, EmbeddedOutputFormat textFormat,
4472  const char *path, const char *fileName, const char *relPath,
4473  bool writeImageMap,int graphId) const
4474 {
4475  QDir d(path);
4476  // store the original directory
4477  if (!d.exists())
4478  {
4479  err("Output dir %s does not exist!\n",path); exit(1);
4480  }
4481  static bool usePDFLatex = Config_getBool(USE_PDFLATEX);
4482 
4483  QGString theGraph;
4484  FTextStream md5stream(&theGraph);
4485  writeGraphHeader(md5stream,m_rootNode->label());
4486 
4487  // clean write flags
4488  QDictIterator<DotNode> dni(*m_usedNodes);
4489  DotNode *pn;
4490  for (dni.toFirst();(pn=dni.current());++dni)
4491  {
4492  pn->clearWriteFlag();
4493  }
4494 
4495  // write other nodes.
4496  for (dni.toFirst();(pn=dni.current());++dni)
4497  {
4498  pn->write(md5stream,DotNode::Inheritance,graphFormat,TRUE,FALSE,FALSE);
4499  }
4500 
4501  // write edges
4502  QListIterator<Edge> eli(m_edges);
4503  Edge* edge;
4504  for (eli.toFirst();(edge=eli.current());++eli)
4505  {
4506  edge->write( md5stream );
4507  }
4508 
4509  writeGraphFooter(md5stream);
4510  uchar md5_sig[16];
4511  QCString sigStr(33);
4512  MD5Buffer((const unsigned char *)theGraph.data(),theGraph.length(),md5_sig);
4513  MD5SigToString(md5_sig,sigStr.rawData(),33);
4514  QCString imgExt = getDotImageExtension();
4515  QCString imgFmt = Config_getEnum(DOT_IMAGE_FORMAT);
4516  QCString baseName = m_diskName;
4517  QCString imgName = baseName+"."+imgExt;
4518  QCString absPath = d.absPath().data();
4519  QCString absBaseName = absPath+"/"+baseName;
4520  QCString absDotName = absBaseName+".dot";
4521  QCString absImgName = absBaseName+"."+imgExt;
4522  QCString absMapName = absBaseName+".map";
4523  QCString absPdfName = absBaseName+".pdf";
4524  QCString absEpsName = absBaseName+".eps";
4525  bool regenerate=FALSE;
4526  if (checkAndUpdateMd5Signature(absBaseName,sigStr) ||
4527  !checkDeliverables(graphFormat==GOF_BITMAP ? absImgName :
4528  usePDFLatex ? absPdfName : absEpsName,
4529  graphFormat==GOF_BITMAP /*&& generateImageMap*/ ? absMapName : QCString())
4530  )
4531  {
4532  regenerate=TRUE;
4533 
4534  QFile dotfile(absDotName);
4535  if (dotfile.open(IO_WriteOnly))
4536  {
4537  FTextStream tdot(&dotfile);
4538  tdot << theGraph;
4539  dotfile.close();
4540  }
4541 
4542  if (graphFormat==GOF_BITMAP) // run dot to create a bitmap image
4543  {
4544  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
4545  dotRun->addJob(imgFmt,absImgName);
4546  if (writeImageMap) dotRun->addJob(MAP_CMD,absMapName);
4547  DotManager::instance()->addRun(dotRun);
4548 
4549  }
4550  else if (graphFormat==GOF_EPS)
4551  {
4552  DotRunner *dotRun = new DotRunner(absDotName,d.absPath().data(),FALSE);
4553  if (usePDFLatex)
4554  {
4555  dotRun->addJob("pdf",absPdfName);
4556  }
4557  else
4558  {
4559  dotRun->addJob("ps",absEpsName);
4560  }
4561  DotManager::instance()->addRun(dotRun);
4562  }
4563 
4564  }
4565  if (graphFormat==GOF_BITMAP && textFormat==EOF_DocBook)
4566  {
4567  t << "<para>" << endl;
4568  t << " <figure>" << endl;
4569  t << " <title>Group Collaboration diagram";
4570  t << "</title>" << endl;
4571  t << " <mediaobject>" << endl;
4572  t << " <imageobject>" << endl;
4573  t << " <imagedata";
4574  t << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"1\" fileref=\"" << relPath << baseName << "." << imgExt << "\">";
4575  t << "</imagedata>" << endl;
4576  t << " </imageobject>" << endl;
4577  t << " </mediaobject>" << endl;
4578  t << " </figure>" << endl;
4579  t << "</para>" << endl;
4580  }
4581  else if (graphFormat==GOF_BITMAP && writeImageMap)
4582  {
4583  QCString mapLabel = escapeCharsInString(baseName,FALSE);
4584  t << "<center><table><tr><td>";
4585 
4586  if (imgExt=="svg")
4587  {
4588  t << "<div class=\"center\">";
4589  if (regenerate || !writeSVGFigureLink(t,relPath,baseName,absImgName)) // need to patch the links in the generated SVG file
4590  {
4591  if (regenerate)
4592  {
4593  DotManager::instance()->addSVGConversion(absImgName,relPath,FALSE,QCString(),TRUE,graphId);
4594  }
4595  int mapId = DotManager::instance()->addSVGObject(fileName,baseName,absImgName,relPath);
4596  t << "<!-- SVG " << mapId << " -->" << endl;
4597  }
4598  t << "</div>" << endl;
4599  }
4600  else
4601  {
4602  t << "<img src=\"" << relPath << imgName
4603  << "\" border=\"0\" alt=\"\" usemap=\"#"
4604  << mapLabel << "\"/>" << endl;
4605  if (regenerate || !insertMapFile(t,absMapName,relPath,mapLabel))
4606  {
4607  int mapId = DotManager::instance()->addMap(fileName,absMapName,relPath,
4608  FALSE,QCString(),mapLabel);
4609  t << "<!-- MAP " << mapId << " -->" << endl;
4610  }
4611  }
4612  t << "</td></tr></table></center>" << endl;
4613  }
4614  else if (graphFormat==GOF_EPS)
4615  {
4616  if (regenerate || !writeVecGfxFigure(t,baseName,absBaseName))
4617  {
4618  int figId = DotManager::instance()->addFigure(fileName,baseName,absBaseName,FALSE);
4619  t << endl << "% FIG " << figId << endl;
4620  }
4621  }
4622  if (!regenerate) removeDotGraph(absDotName);
4623 
4624  return baseName;
4625 }
4626 
4628 {
4629  const char* linkTypeColor[] = {
4630  "darkorchid3"
4631  ,"orange"
4632  ,"blueviolet"
4633  ,"darkgreen"
4634  ,"firebrick4"
4635  ,"grey75"
4636  ,"midnightblue"
4637  };
4638  QCString arrowStyle = "dir=\"none\", style=\"dashed\"";
4639  t << " Node" << pNStart->number();
4640  t << "->";
4641  t << "Node" << pNEnd->number();
4642 
4643  t << " [shape=plaintext";
4644  if (links.count()>0) // there are links
4645  {
4646  t << ", ";
4647  // HTML-like edge labels crash on my Mac with Graphviz 2.0! and
4648  // are not supported by older version of dot.
4649  //
4650  //t << label=<<TABLE BORDER=\"0\" CELLBORDER=\"0\">";
4651  //QListIterator<Link> lli(links);
4652  //Link *link;
4653  //for( lli.toFirst(); (link=lli.current()); ++lli)
4654  //{
4655  // t << "<TR><TD";
4656  // if ( !link->url.isEmpty() )
4657  // t << " HREF=\"" << link->url << "\"";
4658  // t << ">" << link->label << "</TD></TR>";
4659  //}
4660  //t << "</TABLE>>";
4661 
4662  t << "label=\"";
4663  QListIterator<Link> lli(links);
4664  Link *link;
4665  bool first=TRUE;
4666  int count=0;
4667  const int maxLabels = 10;
4668  for( lli.toFirst(); (link=lli.current()) && count<maxLabels; ++lli,++count)
4669  {
4670  if (first) first=FALSE; else t << "\\n";
4671  t << convertLabel(link->label);
4672  }
4673  if (count==maxLabels) t << "\\n...";
4674  t << "\"";
4675 
4676  }
4677  switch( eType )
4678  {
4679  case thierarchy:
4680  arrowStyle = "dir=\"back\", style=\"solid\"";
4681  break;
4682  default:
4683  t << ", color=\"" << linkTypeColor[(int)eType] << "\"";
4684  break;
4685  }
4686  t << ", " << arrowStyle;
4687  t << "];" << endl;
4688 }
4689 
4691 {
4692  return m_usedNodes->count() <= 1;
4693 }
4694 
4696  const QCString &title) const
4697 {
4698  t << "digraph ";
4699  if (title.isEmpty())
4700  {
4701  t << "\"Dot Graph\"";
4702  }
4703  else
4704  {
4705  t << "\"" << convertToXML(title) << "\"";
4706  }
4707  t << endl;
4708  t << "{" << endl;
4709  if (Config_getBool(DOT_TRANSPARENT))
4710  {
4711  t << " bgcolor=\"transparent\";" << endl;
4712  }
4713  t << " edge [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\","
4714  "labelfontname=\"" << FONTNAME << "\",labelfontsize=\"" << FONTSIZE << "\"];\n";
4715  t << " node [fontname=\"" << FONTNAME << "\",fontsize=\"" << FONTSIZE << "\",shape=record];\n";
4716  t << " rankdir=LR;\n";
4717 }
4718 
4719 void writeDotDirDepGraph(FTextStream &t,DirDef *dd,bool linkRelations)
4720 {
4721  t << "digraph \"" << dd->displayName() << "\" {\n";
4722  if (Config_getBool(DOT_TRANSPARENT))
4723  {
4724  t << " bgcolor=transparent;\n";
4725  }
4726  t << " compound=true\n";
4727  t << " node [ fontsize=\"" << FONTSIZE << "\", fontname=\"" << FONTNAME << "\"];\n";
4728  t << " edge [ labelfontsize=\"" << FONTSIZE << "\", labelfontname=\"" << FONTNAME << "\"];\n";
4729 
4730  QDict<DirDef> dirsInGraph(257);
4731 
4732  dirsInGraph.insert(dd->getOutputFileBase(),dd);
4733  if (dd->parent())
4734  {
4735  t << " subgraph cluster" << dd->parent()->getOutputFileBase() << " {\n";
4736  t << " graph [ bgcolor=\"#ddddee\", pencolor=\"black\", label=\""
4737  << dd->parent()->shortName()
4738  << "\" fontname=\"" << FONTNAME << "\", fontsize=\"" << FONTSIZE << "\", URL=\"";
4740  t << "\"]\n";
4741  }
4742  if (dd->isCluster())
4743  {
4744  t << " subgraph cluster" << dd->getOutputFileBase() << " {\n";
4745  t << " graph [ bgcolor=\"#eeeeff\", pencolor=\"black\", label=\"\""
4746  << " URL=\"" << dd->getOutputFileBase() << Doxygen::htmlFileExtension
4747  << "\"];\n";
4748  t << " " << dd->getOutputFileBase() << " [shape=plaintext label=\""
4749  << dd->shortName() << "\"];\n";
4750 
4751  // add nodes for sub directories
4752  QListIterator<DirDef> sdi(dd->subDirs());
4753  DirDef *sdir;
4754  for (sdi.toFirst();(sdir=sdi.current());++sdi)
4755  {
4756  t << " " << sdir->getOutputFileBase() << " [shape=box label=\""
4757  << sdir->shortName() << "\"";
4758  if (sdir->isCluster())
4759  {
4760  t << " color=\"red\"";
4761  }
4762  else
4763  {
4764  t << " color=\"black\"";
4765  }
4766  t << " fillcolor=\"white\" style=\"filled\"";
4767  t << " URL=\"" << sdir->getOutputFileBase()
4768  << Doxygen::htmlFileExtension << "\"";
4769  t << "];\n";
4770  dirsInGraph.insert(sdir->getOutputFileBase(),sdir);
4771  }
4772  t << " }\n";
4773  }
4774  else
4775  {
4776  t << " " << dd->getOutputFileBase() << " [shape=box, label=\""
4777  << dd->shortName() << "\", style=\"filled\", fillcolor=\"#eeeeff\","
4778  << " pencolor=\"black\", URL=\"" << dd->getOutputFileBase()
4779  << Doxygen::htmlFileExtension << "\"];\n";
4780  }
4781  if (dd->parent())
4782  {
4783  t << " }\n";
4784  }
4785 
4786  // add nodes for other used directories
4787  QDictIterator<UsedDir> udi(*dd->usedDirs());
4788  UsedDir *udir;
4789  //printf("*** For dir %s\n",shortName().data());
4790  for (udi.toFirst();(udir=udi.current());++udi)
4791  // for each used dir (=directly used or a parent of a directly used dir)
4792  {
4793  const DirDef *usedDir=udir->dir();
4794  DirDef *dir=dd;
4795  while (dir)
4796  {
4797  //printf("*** check relation %s->%s same_parent=%d !%s->isParentOf(%s)=%d\n",
4798  // dir->shortName().data(),usedDir->shortName().data(),
4799  // dir->parent()==usedDir->parent(),
4800  // usedDir->shortName().data(),
4801  // shortName().data(),
4802  // !usedDir->isParentOf(this)
4803  // );
4804  if (dir!=usedDir && dir->parent()==usedDir->parent() &&
4805  !usedDir->isParentOf(dd))
4806  // include if both have the same parent (or no parent)
4807  {
4808  t << " " << usedDir->getOutputFileBase() << " [shape=box label=\""
4809  << usedDir->shortName() << "\"";
4810  if (usedDir->isCluster())
4811  {
4812  if (!Config_getBool(DOT_TRANSPARENT))
4813  {
4814  t << " fillcolor=\"white\" style=\"filled\"";
4815  }
4816  t << " color=\"red\"";
4817  }
4818  t << " URL=\"" << usedDir->getOutputFileBase()
4819  << Doxygen::htmlFileExtension << "\"];\n";
4820  dirsInGraph.insert(usedDir->getOutputFileBase(),usedDir);
4821  break;
4822  }
4823  dir=dir->parent();
4824  }
4825  }
4826 
4827  // add relations between all selected directories
4828  DirDef *dir;
4829  QDictIterator<DirDef> di(dirsInGraph);
4830  for (di.toFirst();(dir=di.current());++di) // foreach dir in the graph
4831  {
4832  QDictIterator<UsedDir> udi(*dir->usedDirs());
4833  UsedDir *udir;
4834  for (udi.toFirst();(udir=udi.current());++udi) // foreach used dir
4835  {
4836  const DirDef *usedDir=udir->dir();
4837  if ((dir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4838  (usedDir!=dd || !udir->inherited()) && // only show direct dependendies for this dir
4839  !usedDir->isParentOf(dir) && // don't point to own parent
4840  dirsInGraph.find(usedDir->getOutputFileBase())) // only point to nodes that are in the graph
4841  {
4842  QCString relationName;
4843  relationName.sprintf("dir_%06d_%06d",dir->dirCount(),usedDir->dirCount());
4844  if (Doxygen::dirRelations.find(relationName)==0)
4845  {
4846  // new relation
4847  Doxygen::dirRelations.append(relationName,
4848  new DirRelation(relationName,dir,udir));
4849  }
4850  int nrefs = udir->filePairs().count();
4851  t << " " << dir->getOutputFileBase() << "->"
4852  << usedDir->getOutputFileBase();
4853  t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5";
4854  if (linkRelations)
4855  {
4856  t << " headhref=\"" << relationName << Doxygen::htmlFileExtension << "\"";
4857  }
4858  t << "];\n";
4859  }
4860  }
4861  }
4862 
4863  t << "}\n";
4864 }
4865 
4867 {
4872 }
4873