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