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