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