Doxygen
Loading...
Searching...
No Matches
dotdirdeps.cpp
Go to the documentation of this file.
1/******************************************************************************
2*
3* Copyright (C) 1997-2019 by Dimitri van Heesch.
4*
5* Permission to use, copy, modify, and distribute this software and its
6* documentation under the terms of the GNU General Public License is hereby
7* granted. No representations are made about the suitability of this software
8* for any purpose. It is provided "as is" without express or implied warranty.
9* See the GNU General Public License for more details.
10*
11* Documents produced by Doxygen are derivative works derived from the
12* input used in their production; they are not affected by this license.
13*
14*/
15
16#include "dotdirdeps.h"
17#include "util.h"
18#include "doxygen.h"
19#include "config.h"
20#include "image.h"
21#include "dotnode.h"
22
23#include <algorithm>
24#include <iterator>
25#include <utility>
26#include <cstdint>
27#include <math.h>
28#include <cassert>
29#include <map>
30#include <memory>
31#include <string>
32#include <vector>
33
34using DirDefMap = std::map<std::string,const DirDef *>;
35
36/** Properties are used to format the directories in the graph distinctively. */
38{
39 bool isIncomplete = false; //!< true if not all successors of a cluster are drawn
40 bool isOrphaned = false; //!< true if parent is not drawn
41 bool isTruncated = false; //!< true has successors, none is drawn
42 bool isOriginal = false; //!< true if is the directory for which the graph is drawn
43 bool isPeripheral = false; //!< true if no successor of parent of original directory
44};
45
46/** Builder helper to create instances of the DotDirProperty struct */
48{
49 public:
50 DotDirPropertyBuilder &makeIncomplete(bool b=true) { m_property.isIncomplete=b; return *this; }
51 DotDirPropertyBuilder &makeOrphaned (bool b=true) { m_property.isOrphaned =b; return *this; }
52 DotDirPropertyBuilder &makeTruncated (bool b=true) { m_property.isTruncated =b; return *this; }
53 DotDirPropertyBuilder &makeOriginal (bool b=true) { m_property.isOriginal =b; return *this; }
54 DotDirPropertyBuilder &makePeripheral(bool b=true) { m_property.isPeripheral=b; return *this; }
55 operator DotDirProperty() { return std::move(m_property); }
56 private:
57 DotDirProperty m_property;
58};
59
60/** Elements consist of (1) directory relation and (2) whether it is pointing only to inherited dependees. */
61typedef std::vector< std::pair< std::unique_ptr<DirRelation>, bool> > DirRelations;
62
63/** Returns a DOT color name according to the directory depth. */
65{
66 int hue = Config_getInt(HTML_COLORSTYLE_HUE);
67 int sat = Config_getInt(HTML_COLORSTYLE_SAT);
68 int gamma = Config_getInt(HTML_COLORSTYLE_GAMMA);
69 assert(depthIndex>=0 && depthIndex<=Config_getInt(DIR_GRAPH_MAX_DEPTH));
70 float fraction = static_cast<float>(depthIndex)/static_cast<float>(Config_getInt(DIR_GRAPH_MAX_DEPTH));
71 const char hex[] = "0123456789abcdef";
72 int range = 0x40; // range from darkest color to lightest color
73 int luma = 0xef-static_cast<int>(fraction*static_cast<float>(range)); // interpolation
74 double r=0, g=0, b=0;
75 ColoredImage::hsl2rgb(hue/360.0,sat/255.0,
76 pow(luma/255.0,gamma/100.0),&r,&g,&b);
77 int red = static_cast<int>(r*255.0);
78 int green = static_cast<int>(g*255.0);
79 int blue = static_cast<int>(b*255.0);
80 assert(red>=0 && red<=255);
81 assert(green>=0 && green<=255);
82 assert(blue>=0 && blue<=255);
83 char colStr[8];
84 colStr[0]='#';
85 colStr[1]=hex[red>>4];
86 colStr[2]=hex[red&0xf];
87 colStr[3]=hex[green>>4];
88 colStr[4]=hex[green&0xf];
89 colStr[5]=hex[blue>>4];
90 colStr[6]=hex[blue&0xf];
91 colStr[7]=0;
92 //printf("i=%d max=%d fraction=%f luma=%d %02x %02x %02x -> color=%s\n",
93 // depthIndex,Config_getInt(DIR_GRAPH_MAX_DEPTH),fraction,luma,red,green,blue,colStr);
94 return colStr;
95}
96
97/** Returns a DOT color name according to the directory properties. */
98static const char* getDirectoryBorderColor(const DotDirProperty &property)
99{
100 if (property.isTruncated && property.isOrphaned)
101 {
102 return "red";
103 }
104 else if (property.isTruncated)
105 {
106 return "red";
107 }
108 else if (property.isOrphaned)
109 {
110 return "grey50";
111 }
112 else
113 {
114 return "grey25";
115 }
116}
117
118/** Returns a DOT node style according to the directory properties. */
119static std::string getDirectoryBorderStyle(const DotDirProperty &property)
120{
121 std::string style = "filled";
122 if (property.isOriginal)
123 {
124 style += ",bold";
125 }
126 if (property.isIncomplete)
127 {
128 style += ",dashed";
129 }
130 else if (property.isTruncated && property.isOrphaned)
131 {
132 style += ",dashed";
133 }
134 return style;
135}
136
137static TextStream &common_attributes(TextStream &t, const DirDef *const dir, const DotDirProperty &prop)
138{
139 QCString url = dir->getOutputFileBase();
141 return t <<
142 "style=\"" << getDirectoryBorderStyle(prop) << "\", "
143 "URL=\"" << url << "\","
144 "tooltip=\"" << escapeTooltip(dir->briefDescriptionAsTooltip()) << "\"";
145}
146
147/**
148 * Puts DOT code for drawing directory to stream and adds it to the list.
149 * @param[in,out] t stream to which the DOT code is written to
150 * @param[in] directory will be mapped to a node in DOT code
151 * @param[in] property are evaluated for formatting
152 * @param[in,out] directoriesInGraph lists the directories which have been written to the output stream
153 * @param[in] startLevel current level to calculate relative distances from to determine the background color
154 */
155static void drawDirectory(TextStream &t, const DirDef *const directory, const DotDirProperty &property,
156 DirDefMap &directoriesInGraph,int startLevel)
157{
158 t << " " << directory->getOutputFileBase() << " ["
159 "label=\"" << DotNode::convertLabel(directory->shortName()) << "\", "
160 "fillcolor=\"" << getDirectoryBackgroundColor(directory->level()-startLevel) << "\", "
161 "color=\"" << getDirectoryBorderColor(property) << "\", ";
162 common_attributes(t, directory, property)
163 << "];\n";
164 directoriesInGraph.emplace(directory->getOutputFileBase().str(), directory);
165}
166
167/** Checks, if the directory is a the maximum drawn directory level. */
168static bool isAtMaxDepth(const DirDef *const directory, const int startLevel)
169{
170 return (directory->level() - startLevel) >= Config_getInt(DIR_GRAPH_MAX_DEPTH);
171}
172
173/**
174 * Writes DOT code for opening a cluster subgraph to stream.
175 *
176 * Ancestor clusters directly get a label. Other clusters get a plain text node with a label instead.
177 * This is because the plain text node can be used to draw dependency relationships.
178 */
179static void drawClusterOpening(TextStream &outputStream, const DirDef *const directory,
180 const DotDirProperty &directoryProperty, DirDefMap &directoriesInGraph, const bool isAncestor,int startLevel)
181{
182 outputStream << " subgraph cluster" << directory->getOutputFileBase() << " {\n"
183 " graph [ "
184 "bgcolor=\"" << getDirectoryBackgroundColor(directory->level()-startLevel) << "\", "
185 "pencolor=\"" << getDirectoryBorderColor(directoryProperty) << "\", "
186 "label=\"";
187 if (isAncestor)
188 {
189 outputStream << DotNode::convertLabel(directory->shortName());
190 }
191 outputStream << "\", "
192 << Config_getString(DOT_COMMON_ATTR) << " ";
193 common_attributes(outputStream, directory, directoryProperty)
194 << "]\n";
195 if (!isAncestor)
196 {
197 outputStream << " " << directory->getOutputFileBase() << " [shape=plaintext, "
198 "label=\"" << DotNode::convertLabel(directory->shortName()) << "\""
199 "];\n";
200 directoriesInGraph.emplace(directory->getOutputFileBase().str(), directory);
201 }
202}
203
205{
206 t << " }\n";
207}
208
209/**
210 * Assembles a list of the directory relations and whether or not they result from "inheritance".
211 * @param dependencies Array to add the dependencies to.
212 * @param srcDir is the source of the dependency.
213 * @param isLeaf true, if no children are drawn for this directory.
214 */
215static void addDependencies(DirRelations &dependencies,const DirDef *const srcDir, bool isLeaf)
216{
217 for (const auto &usedDirectory : srcDir->usedDirs())
218 {
219 const auto dstDir = usedDirectory->dir();
220 if (!dstDir->isParentOf(srcDir) && (isLeaf || usedDirectory->hasDirectSrcDeps()))
221 {
222 QCString relationName;
223 relationName.sprintf("dir_%06d_%06d", srcDir->dirIndex(), dstDir->dirIndex());
224 bool directRelation = isLeaf ? usedDirectory->hasDirectDstDeps() : usedDirectory->hasDirectDeps();
225 dependencies.emplace_back(
226 std::make_unique<DirRelation>(relationName, srcDir, usedDirectory.get()),
227 directRelation);
228 }
229 }
230}
231
232/** Recursively draws directory tree. */
233static void drawTree(DirRelations &dependencies, TextStream &t, const DirDef *const directory,
234 int startLevel, DirDefMap &directoriesInGraph, const bool isTreeRoot)
235{
236 if (!directory->hasSubdirs())
237 {
238 const DotDirProperty directoryProperty = DotDirPropertyBuilder().makeOriginal(isTreeRoot);
239 drawDirectory(t, directory, directoryProperty, directoriesInGraph,startLevel);
240 addDependencies(dependencies, directory, true);
241 }
242 else
243 {
244 if (isAtMaxDepth(directory, startLevel)) // maximum nesting level reached
245 {
246 const DotDirProperty directoryProperty = DotDirPropertyBuilder().makeOriginal(isTreeRoot);
247 drawDirectory(t, directory, directoryProperty, directoriesInGraph,startLevel);
248 addDependencies(dependencies, directory, true);
249 }
250 else // start a new nesting level
251 {
252 // open cluster
253 {
254 const DotDirProperty directoryProperty = DotDirPropertyBuilder().makeOriginal(isTreeRoot);
255 drawClusterOpening(t, directory, directoryProperty, directoriesInGraph, false, startLevel);
256 addDependencies(dependencies, directory, false);
257 }
258
259 // process all sub directories
260 for (const auto subDirectory : directory->subDirs())
261 {
262 drawTree(dependencies, t, subDirectory, startLevel, directoriesInGraph, false);
263 }
264
265 // close cluster
266 {
268 }
269 }
270 }
271}
272
273/**
274 * Write DOT code for directory dependency graph.
275 *
276 * Code is generated for a directory. Successors (sub-directories) of this directory are recursively drawn.
277 * Recursion is limited by `DIR_GRAPH_MAX_DEPTH`. The dependencies of those directories
278 * are drawn.
279 *
280 * If a dependee is not part of directory tree above, then the dependency is drawn to the first parent of the
281 * dependee, whose parent is an ancestor (sub-directory) of the original directory.
282 *
283 * @param t stream where the DOT code is written to
284 * @param dd directory for which the graph is generated for
285 * @param linkRelations if true, hyperlinks to the list of file dependencies are added
286 */
287void writeDotDirDepGraph(TextStream &t,const DirDef *dd,bool linkRelations)
288{
289 DirDefMap dirsInGraph;
290
291 dirsInGraph.emplace(dd->getOutputFileBase().str(),dd);
292
293 std::vector<const DirDef *> usedDirsNotDrawn, usedDirsDrawn;
294 for (const auto& usedDir : dd->usedDirs())
295 {
296 usedDirsNotDrawn.push_back(usedDir->dir());
297 }
298
299 auto moveDrawnDirs = [&usedDirsDrawn,&usedDirsNotDrawn](const std::vector<const DirDef *>::iterator &newEnd)
300 {
301 // usedDirsNotDrawn is split into two segments: [begin()....newEnd-1] and [newEnd....end()]
302 // where the second segment starting at newEnd has been drawn, so append this segment to the usedDirsDrawn list and
303 // remove it from the usedDirsNotDrawn list.
304 std::move(newEnd, std::end(usedDirsNotDrawn), std::back_inserter(usedDirsDrawn));
305 usedDirsNotDrawn.erase(newEnd, usedDirsNotDrawn.end());
306 };
307
308 // if dd has a parent draw it as the outer layer
309 const auto parent = dd->parent();
310 if (parent)
311 {
312 const DotDirProperty parentDirProperty = DotDirPropertyBuilder().
313 makeIncomplete().
314 makeOrphaned(parent->parent()!=nullptr);
315 drawClusterOpening(t, parent, parentDirProperty, dirsInGraph, true, parent->level());
316
317 {
318 // draw all directories which have `dd->parent()` as parent and `dd` as dependent
319 const auto newEnd = std::stable_partition(usedDirsNotDrawn.begin(), usedDirsNotDrawn.end(),
320 [&](const DirDef *const usedDir)
321 {
322 if (dd!=usedDir && dd->parent()==usedDir->parent()) // usedDir and dd share the same parent
323 {
324 const DotDirProperty usedDirProperty = DotDirPropertyBuilder().makeTruncated(usedDir->hasSubdirs());
325 drawDirectory(t, usedDir, usedDirProperty, dirsInGraph, parent->level());
326 return false; // part of the drawn partition
327 }
328 return true; // part of the not-drawn partition
329 });
330 moveDrawnDirs(newEnd);
331 }
332 }
333
334 // draw the directory tree with dd as root
335 DirRelations dependencies;
336 drawTree(dependencies, t, dd, dd->level(), dirsInGraph, true);
337
338 if (parent)
339 {
341 }
342
343 // add nodes for other used directories (i.e. outside of the cluster of directories directly connected to dd)
344 {
345 const auto newEnd = std::stable_partition(usedDirsNotDrawn.begin(), usedDirsNotDrawn.end(),
346 [&](const DirDef *const usedDir) // for each used dir (=directly used or a parent of a directly used dir)
347 {
348 const DirDef *dir=dd;
349 while (dir)
350 {
351 if (dir!=usedDir && dir->parent()==usedDir->parent()) // include if both have the same parent (or no parent)
352 {
353 const DotDirProperty usedDirProperty = DotDirPropertyBuilder().
354 makeOrphaned(usedDir->parent()!=nullptr).
355 makeTruncated(usedDir->hasSubdirs()).
356 makePeripheral();
357 drawDirectory(t, usedDir, usedDirProperty, dirsInGraph, dir->level());
358 return false; // part of the drawn partition
359 }
360 dir=dir->parent();
361 }
362 return true; // part of the not-drawn partition
363 });
364 moveDrawnDirs(newEnd);
365 }
366
367 // add relations between all selected directories
368 {
369 for (const auto &relationPair : dependencies)
370 {
371 const auto &relation = relationPair.first;
372 const bool directRelation = relationPair.second;
373 const auto udir = relation->destination();
374 const auto usedDir = udir->dir();
375 const bool destIsSibling = std::find(std::begin(usedDirsDrawn), std::end(usedDirsDrawn), usedDir) != std::end(usedDirsDrawn);
376 const bool destIsDrawn = dirsInGraph.find(usedDir->getOutputFileBase().str())!=dirsInGraph.end(); // only point to nodes that are in the graph
377 const bool atMaxDepth = isAtMaxDepth(usedDir, dd->level());
378
379 if (destIsSibling || (destIsDrawn && (directRelation || atMaxDepth)))
380 {
381 const auto relationName = relation->getOutputFileBase();
382 const auto dir = relation->source();
383 Doxygen::dirRelations.add(relationName,
384 std::make_unique<DirRelation>(
385 relationName,dir,udir));
386 size_t nrefs = udir->filePairs().size();
387 t << " " << dir->getOutputFileBase() << "->"
388 << usedDir->getOutputFileBase();
389 t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5";
390 if (linkRelations)
391 {
392 QCString fn = relationName;
394 t << " headhref=\"" << fn << "\"";
395 t << " href=\"" << fn << "\"";
396 }
397 t << " color=\"steelblue1\" fontcolor=\"steelblue1\"];\n";
398 }
399 }
400 }
401}
402
404{
405}
406
410
412{
413 return m_dir->getOutputFileBase()+"_dep";
414
415}
416
418{
419 // compute md5 checksum of the graph were are about to generate
420 TextStream md5stream;
421 writeGraphHeader(md5stream, m_dir->displayName());
422 md5stream << " compound=true\n";
424 writeGraphFooter(md5stream);
425 m_theGraph = md5stream.str();
426}
427
432
434{
435 return convertToXML(m_dir->displayName());
436}
437
439 const QCString &path, const QCString &fileName, const QCString &relPath, bool generateImageMap,
440 int graphId, bool linkRelations)
441{
442 m_linkRelations = linkRelations;
443 m_urlOnly = TRUE;
444 return DotGraph::writeGraph(out, graphFormat, textFormat, path, fileName, relPath, generateImageMap, graphId);
445}
446
448{
449 return m_dir->depGraphIsTrivial();
450}
static void hsl2rgb(double h, double s, double l, double *pRed, double *pGreen, double *pBlue)
Definition image.cpp:368
virtual QCString briefDescriptionAsTooltip() const =0
virtual QCString getOutputFileBase() const =0
A model of a directory symbol.
Definition dirdef.h:110
virtual int level() const =0
virtual int dirIndex() const =0
virtual DirDef * parent() const =0
virtual const QCString shortName() const =0
virtual const DirList & subDirs() const =0
virtual const UsedDirLinkedMap & usedDirs() const =0
virtual bool hasSubdirs() const =0
QCString getImgAltText() const override
QCString getMapLabel() const override
QCString writeGraph(TextStream &out, GraphOutputFormat gf, EmbeddedOutputFormat ef, const QCString &path, const QCString &fileName, const QCString &relPath, bool writeImageMap=TRUE, int graphId=-1, bool linkRelations=TRUE)
~DotDirDeps() override
void computeTheGraph() override
const DirDef * m_dir
Definition dotdirdeps.h:50
bool isTrivial() const
bool m_linkRelations
Definition dotdirdeps.h:52
DotDirDeps(const DirDef *dir)
QCString getBaseName() const override
Builder helper to create instances of the DotDirProperty struct.
DotDirPropertyBuilder & makeOriginal(bool b=true)
DotDirProperty m_property
DotDirPropertyBuilder & makePeripheral(bool b=true)
DotDirPropertyBuilder & makeOrphaned(bool b=true)
DotDirPropertyBuilder & makeIncomplete(bool b=true)
DotDirPropertyBuilder & makeTruncated(bool b=true)
static void writeGraphFooter(TextStream &t)
Definition dotgraph.cpp:301
bool m_urlOnly
Definition dotgraph.h:100
static void writeGraphHeader(TextStream &t, const QCString &title=QCString())
Definition dotgraph.cpp:276
QCString m_theGraph
Definition dotgraph.h:95
QCString m_baseName
Definition dotgraph.h:94
QCString writeGraph(TextStream &t, GraphOutputFormat gf, EmbeddedOutputFormat ef, const QCString &path, const QCString &fileName, const QCString &relPath, bool writeImageMap=TRUE, int graphId=-1)
Definition dotgraph.cpp:115
static QCString convertLabel(const QCString &, bool htmlLike=false)
Definition dotnode.cpp:196
static DirRelationLinkedMap dirRelations
Definition doxygen.h:130
This is an alternative implementation of QCString.
Definition qcstring.h:101
const std::string & str() const
Definition qcstring.h:526
QCString & sprintf(const char *format,...)
Definition qcstring.cpp:29
Text streaming class that buffers data.
Definition textstream.h:36
std::string str() const
Return the contents of the buffer as a std::string object.
Definition textstream.h:229
#define Config_getInt(name)
Definition config.h:34
#define Config_getString(name)
Definition config.h:32
static constexpr auto hex
constexpr DocNodeVariant * parent(DocNodeVariant *n)
returns the parent node of a given node n or nullptr if the node has no parent.
Definition docnode.h:1310
std::vector< std::pair< std::unique_ptr< DirRelation >, bool > > DirRelations
Elements consist of (1) directory relation and (2) whether it is pointing only to inherited dependees...
static TextStream & common_attributes(TextStream &t, const DirDef *const dir, const DotDirProperty &prop)
static bool isAtMaxDepth(const DirDef *const directory, const int startLevel)
Checks, if the directory is a the maximum drawn directory level.
static void drawClusterClosing(TextStream &t)
static void addDependencies(DirRelations &dependencies, const DirDef *const srcDir, bool isLeaf)
Assembles a list of the directory relations and whether or not they result from "inheritance".
static QCString getDirectoryBackgroundColor(int depthIndex)
Returns a DOT color name according to the directory depth.
static std::string getDirectoryBorderStyle(const DotDirProperty &property)
Returns a DOT node style according to the directory properties.
static void drawDirectory(TextStream &t, const DirDef *const directory, const DotDirProperty &property, DirDefMap &directoriesInGraph, int startLevel)
Puts DOT code for drawing directory to stream and adds it to the list.
static void drawClusterOpening(TextStream &outputStream, const DirDef *const directory, const DotDirProperty &directoryProperty, DirDefMap &directoriesInGraph, const bool isAncestor, int startLevel)
Writes DOT code for opening a cluster subgraph to stream.
std::map< std::string, const DirDef * > DirDefMap
static void drawTree(DirRelations &dependencies, TextStream &t, const DirDef *const directory, int startLevel, DirDefMap &directoriesInGraph, const bool isTreeRoot)
Recursively draws directory tree.
void writeDotDirDepGraph(TextStream &t, const DirDef *dd, bool linkRelations)
Write DOT code for directory dependency graph.
static const char * getDirectoryBorderColor(const DotDirProperty &property)
Returns a DOT color name according to the directory properties.
EmbeddedOutputFormat
Definition dotgraph.h:30
GraphOutputFormat
Definition dotgraph.h:29
QCString escapeTooltip(const QCString &tooltip)
Definition dotnode.cpp:99
#define TRUE
Definition qcstring.h:37
#define FALSE
Definition qcstring.h:34
Properties are used to format the directories in the graph distinctively.
bool isTruncated
true has successors, none is drawn
bool isOrphaned
true if parent is not drawn
bool isOriginal
true if is the directory for which the graph is drawn
bool isPeripheral
true if no successor of parent of original directory
bool isIncomplete
true if not all successors of a cluster are drawn
QCString escapeCharsInString(const QCString &name, bool allowDots, bool allowUnderscore)
Definition util.cpp:3684
QCString convertToXML(const QCString &s, bool keepEntities)
Definition util.cpp:4266
void addHtmlExtensionIfMissing(QCString &fName)
Definition util.cpp:5243
A bunch of utility functions.