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