/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
* Documents produced by Doxygen are derivative works derived from the
* input used in their production; they are not affected by this license.
*
*/
#include "dotgfxhierarchytable.h"
#include "language.h"
#include "util.h"
#include "message.h"
#include "doxygen.h"
#include "classlist.h"
QCString DotGfxHierarchyTable::getBaseName() const
{
QCString baseName;
if (m_prefix.isEmpty())
baseName.sprintf("inherit_graph_%d", m_graphId);
else
baseName.sprintf("%sinherit_graph_%d",m_prefix.data(), m_graphId);
return baseName;
}
void DotGfxHierarchyTable::computeTheGraph()
{
FTextStream md5stream(&m_theGraph);
writeGraphHeader(md5stream,theTranslator->trGraphicalHierarchy());
md5stream << " rankdir=\"LR\";" << endl;
for (auto node : m_rootNodes)
{
if (node->subgraphId()==m_rootSubgraphNode->subgraphId())
{
node->clearWriteFlag();
}
}
for (auto node : m_rootNodes)
{
if (node->subgraphId()==m_rootSubgraphNode->subgraphId())
{
node->write(md5stream,Hierarchy,GOF_BITMAP,FALSE,TRUE,TRUE);
}
}
writeGraphFooter(md5stream);
}
QCString DotGfxHierarchyTable::getMapLabel() const
{
return escapeCharsInString(m_rootSubgraphNode->label(),FALSE);
}
void DotGfxHierarchyTable::createGraph(DotNode *n,FTextStream &out,
const char *path,const char *fileName,int id)
{
m_rootSubgraphNode = n;
m_graphId = id;
m_noDivTag = TRUE;
m_zoomable = FALSE;
DotGraph::writeGraph(out, GOF_BITMAP, EOF_Html, path, fileName, "", TRUE, 0);
}
void DotGfxHierarchyTable::writeGraph(FTextStream &out,
const char *path,const char *fileName)
{
//printf("DotGfxHierarchyTable::writeGraph(%s)\n",name);
//printf("m_rootNodes=%p count=%d\n",m_rootNodes,m_rootNodes->count());
if (m_rootSubgraphs.empty()) return;
QDir d(path);
// store the original directory
if (!d.exists())
{
term("Output dir %s does not exist!\n",path);
}
// put each connected subgraph of the hierarchy in a row of the HTML output
out << "
" << endl;
int count=0;
std::sort(m_rootSubgraphs.begin(),m_rootSubgraphs.end(),
[](auto n1,auto n2) { return qstricmp(n1->label(),n2->label())<0; });
for (auto n : m_rootSubgraphs)
{
out << "";
createGraph(n,out,path,fileName,count++);
out << " |
" << endl;
}
out << "
" << endl;
}
void DotGfxHierarchyTable::addHierarchy(DotNode *n,const ClassDef *cd,ClassDefSet &visitedClasses)
{
//printf("addHierarchy '%s' baseClasses=%d\n",cd->name().data(),cd->baseClasses()->count());
for (const auto &bcd : cd->subClasses())
{
ClassDef *bClass=bcd.classDef;
//printf(" Trying sub class='%s' usedNodes=%d\n",bClass->name().data(),m_usedNodes->count());
if (bClass && bClass->isVisibleInHierarchy() && hasVisibleRoot(bClass->baseClasses()))
{
auto it = m_usedNodes.find(bClass->name().str());
//printf(" Node '%s' Found visible class='%s'\n",n->label().data(),
// bClass->name().data());
DotNode *root = 0;
if (it!=m_usedNodes.end()) // node already present
{
const auto &bn = it->second;
root = bn.get();
const auto &children = n->children();
auto child_it = std::find(children.begin(),children.end(),bn.get());
if (child_it==children.end()) // no arrow yet
{
n->addChild(bn.get(),bcd.prot);
bn->addParent(n);
//printf(" Adding node %s to existing base node %s (c=%d,p=%d)\n",
// n->label().data(),
// bn->label().data(),
// bn->children() ? bn->children()->count() : 0,
// bn->parents() ? bn->parents()->count() : 0
// );
}
//else
//{
// printf(" Class already has an arrow!\n");
//}
}
else
{
QCString tmp_url="";
if (bClass->isLinkable() && !bClass->isHidden())
{
tmp_url=bClass->getReference()+"$"+bClass->getOutputFileBase();
if (!bClass->anchor().isEmpty())
{
tmp_url+="#"+bClass->anchor();
}
}
QCString tooltip = bClass->briefDescriptionAsTooltip();
auto bn = std::make_unique(getNextNodeNumber(),
bClass->displayName(),
tooltip,
tmp_url.data()
);
n->addChild(bn.get(),bcd.prot);
bn->addParent(n);
root = bn.get();
//printf(" Adding node %s to new base node %s (c=%d,p=%d)\n",
// n->label().data(),
// bn->label().data(),
// bn->children() ? bn->children()->count() : 0,
// bn->parents() ? bn->parents()->count() : 0
// );
//printf(" inserting %s (%p)\n",bClass->name().data(),bn);
m_usedNodes.insert(std::make_pair(bClass->name().str(),std::move(bn))); // add node to the used list
}
if (visitedClasses.find(bClass)==visitedClasses.end() && !bClass->subClasses().empty())
{
visitedClasses.insert(bClass);
addHierarchy(root,bClass,visitedClasses);
}
}
}
//printf("end addHierarchy\n");
}
void DotGfxHierarchyTable::addClassList(const ClassLinkedMap &cl,ClassDefSet &visitedClasses)
{
for (const auto &cd : cl)
{
//printf("Trying %s subClasses=%d\n",cd->name().data(),cd->subClasses()->count());
if (cd->getLanguage()==SrcLangExt_VHDL &&
(VhdlDocGen::VhdlClasses)cd->protection()!=VhdlDocGen::ENTITYCLASS
)
{
continue;
}
if (Config_getBool(OPTIMIZE_OUTPUT_SLICE) && cd->compoundType() != m_classType)
{
continue;
}
if (!hasVisibleRoot(cd->baseClasses()) &&
cd->isVisibleInHierarchy()
) // root node in the forest
{
QCString tmp_url="";
if (cd->isLinkable() && !cd->isHidden())
{
tmp_url=cd->getReference()+"$"+cd->getOutputFileBase();
if (!cd->anchor().isEmpty())
{
tmp_url+="#"+cd->anchor();
}
}
//printf("Inserting root class %s\n",cd->name().data());
QCString tooltip = cd->briefDescriptionAsTooltip();
auto n = std::make_unique(getNextNodeNumber(),
cd->displayName(),
tooltip,
tmp_url.data());
DotNode *root = n.get();
m_usedNodes.insert(std::make_pair(cd->name().str(),std::move(n)));
m_rootNodes.push_back(root);
if (visitedClasses.find(cd.get())==visitedClasses.end() && !cd->subClasses().empty())
{
addHierarchy(root,cd.get(),visitedClasses);
visitedClasses.insert(cd.get());
}
}
}
}
DotGfxHierarchyTable::DotGfxHierarchyTable(const char *prefix,ClassDef::CompoundType ct)
: m_prefix(prefix)
, m_classType(ct)
{
// build a graph with each class as a node and the inheritance relations
// as edges
ClassDefSet visitedClasses;
addClassList(*Doxygen::classLinkedMap,visitedClasses);
addClassList(*Doxygen::hiddenClassLinkedMap,visitedClasses);
// m_usedNodes now contains all nodes in the graph
// color the graph into a set of independent subgraphs
bool done=FALSE;
int curColor=0;
while (!done) // there are still nodes to color
{
done=TRUE; // we are done unless there are still uncolored nodes
for (auto n : m_rootNodes)
{
if (n->subgraphId()==-1) // not yet colored
{
//printf("Starting at node %s (%p): %d\n",n->label().data(),n,curColor);
done=FALSE; // still uncolored nodes
n->setSubgraphId(curColor);
n->markAsVisible();
n->colorConnectedNodes(curColor);
curColor++;
const DotNode *dn=n->findDocNode();
if (dn!=0)
m_rootSubgraphs.push_back(const_cast(dn));
else
m_rootSubgraphs.push_back(n);
}
}
}
//printf("Number of independent subgraphs: %d\n",curColor);
for (auto n : m_rootSubgraphs)
{
//printf("Node %s color=%d (c=%d,p=%d)\n",
// n->label().data(),n->m_subgraphId,
// n->children()?n->children()->count():0,
// n->parents()?n->parents()->count():0);
int number=0;
n->renumberNodes(number);
}
}