1 /******************************************************************************
2 * ftvhelp.cpp,v 1.0 2000/09/06 16:09:00
4 * Copyright (C) 1997-2015 by Dimitri van Heesch.
6 * Permission to use, copy, modify, and distribute this software and its
7 * documentation under the terms of the GNU General Public License is hereby
8 * granted. No representations are made about the suitability of this software
9 * for any purpose. It is provided "as is" without express or implied warranty.
10 * See the GNU General Public License for more details.
12 * Documents produced by Doxygen are derivative works derived from the
13 * input used in their production; they are not affected by this license.
15 * Original version contributed by Kenney Wong <kwong@ea.com>
16 * Modified by Dimitri van Heesch
18 * Folder Tree View for offline help on browsers that do not support HTML Help.
25 #include <qfileinfo.h>
35 #include "docparser.h"
36 #include "htmldocvisitor.h"
40 #include "resourcemgr.h"
42 #define MAX_INDENT 1024
44 static int folderId=1;
48 FTVNode(bool dir,const char *r,const char *f,const char *a,
49 const char *n,bool sepIndex,bool navIndex,Definition *df)
50 : isLast(TRUE), isDir(dir),ref(r),file(f),anchor(a),name(n), index(0),
51 parent(0), separateIndex(sepIndex), addToNavIndex(navIndex),
52 def(df) { children.setAutoDelete(TRUE); }
53 int computeTreeDepth(int level) const;
54 int numNodesAtLevel(int level,int maxLevel) const;
62 QList<FTVNode> children;
69 int FTVNode::computeTreeDepth(int level) const
72 QListIterator<FTVNode> li(children);
74 for (;(n=li.current());++li)
76 if (n->children.count()>0)
78 int d = n->computeTreeDepth(level+1);
79 if (d>maxDepth) maxDepth=d;
85 int FTVNode::numNodesAtLevel(int level,int maxLevel) const
91 QListIterator<FTVNode> li(children);
93 for (;(n=li.current());++li)
95 num+=n->numNodesAtLevel(level+1,maxLevel);
101 //----------------------------------------------------------------------------
103 /*! Constructs an ftv help object.
104 * The object has to be \link initialize() initialized\endlink before it can
107 FTVHelp::FTVHelp(bool TLI)
110 m_indentNodes = new QList<FTVNode>[MAX_INDENT];
111 m_indentNodes[0].setAutoDelete(TRUE);
113 m_topLevelIndex = TLI;
116 /*! Destroys the ftv help object. */
119 delete[] m_indentNodes;
122 /*! This will create a folder tree view table of contents file (tree.js).
125 void FTVHelp::initialize()
129 /*! Finalizes the FTV help. This will finish and close the
130 * contents file (index.js).
133 void FTVHelp::finalize()
138 /*! Increase the level of the contents hierarchy.
139 * This will start a new sublist in contents file.
140 * \sa decContentsDepth()
142 void FTVHelp::incContentsDepth()
144 //printf("incContentsDepth() indent=%d\n",m_indent);
146 ASSERT(m_indent<MAX_INDENT);
149 /*! Decrease the level of the contents hierarchy.
150 * This will end the current sublist.
151 * \sa incContentsDepth()
153 void FTVHelp::decContentsDepth()
155 //printf("decContentsDepth() indent=%d\n",m_indent);
160 QList<FTVNode> *nl = &m_indentNodes[m_indent];
161 FTVNode *parent = nl->getLast();
164 QList<FTVNode> *children = &m_indentNodes[m_indent+1];
165 while (!children->isEmpty())
167 parent->children.append(children->take(0));
173 /*! Add a list item to the contents file.
174 * \param isDir TRUE if the item is a directory, FALSE if it is a text
175 * \param name the name of the item.
176 * \param ref the URL of to the item.
177 * \param file the file containing the definition of the item
178 * \param anchor the anchor within the file.
179 * \param separateIndex put the entries in a separate index file
180 * \param addToNavIndex add this entry to the quick navigation index
181 * \param def Definition corresponding to this entry
183 void FTVHelp::addContentsItem(bool isDir,
193 //printf("%p: m_indent=%d addContentsItem(%s,%s,%s,%s)\n",this,m_indent,name,ref,file,anchor);
194 QList<FTVNode> *nl = &m_indentNodes[m_indent];
195 FTVNode *newNode = new FTVNode(isDir,ref,file,anchor,name,separateIndex,addToNavIndex,def);
198 nl->getLast()->isLast=FALSE;
201 newNode->index = nl->count()-1;
204 QList<FTVNode> *pnl = &m_indentNodes[m_indent-1];
205 newNode->parent = pnl->getLast();
210 static QCString node2URL(FTVNode *n,bool overruleFile=FALSE,bool srcLink=FALSE)
212 QCString url = n->file;
213 if (!url.isEmpty() && url.at(0)=='!') // relative URL
218 else if (!url.isEmpty() && url.at(0)=='^') // absolute URL
220 // skip, keep ^ in the output
222 else // local file (with optional anchor)
224 if (overruleFile && n->def && n->def->definitionType()==Definition::TypeFile)
226 FileDef *fd = (FileDef*)n->def;
229 url = fd->getSourceFileBase();
233 url = fd->getOutputFileBase();
236 url+=Doxygen::htmlFileExtension;
237 if (!n->anchor.isEmpty()) url+="#"+n->anchor;
242 QCString FTVHelp::generateIndentLabel(FTVNode *n,int level)
247 result=generateIndentLabel(n->parent,level+1);
249 result+=QCString().setNum(n->index)+"_";
253 void FTVHelp::generateIndent(FTextStream &t, FTVNode *n,bool opened)
256 FTVNode *p = n->parent;
257 while (p) { indent++; p=p->parent; }
260 QCString dir = opened ? "▼" : "►";
261 t << "<span style=\"width:" << (indent*16) << "px;display:inline-block;\"> </span>"
262 << "<span id=\"arr_" << generateIndentLabel(n,0) << "\" class=\"arrow\" ";
263 t << "onclick=\"toggleFolder('" << generateIndentLabel(n,0) << "')\"";
269 t << "<span style=\"width:" << ((indent+1)*16) << "px;display:inline-block;\"> </span>";
273 void FTVHelp::generateLink(FTextStream &t,FTVNode *n)
275 //printf("FTVHelp::generateLink(ref=%s,file=%s,anchor=%s\n",
276 // n->ref.data(),n->file.data(),n->anchor.data());
277 bool setTarget = FALSE;
278 if (n->file.isEmpty()) // no link
280 t << "<b>" << convertToHtml(n->name) << "</b>";
282 else // link into other frame
284 if (!n->ref.isEmpty()) // link to entity imported via tag file
286 t << "<a class=\"elRef\" ";
287 QCString result = externalLinkTarget();
288 if (result != "") setTarget = TRUE;
289 t << result << externalRef("",n->ref,FALSE);
293 t << "<a class=\"el\" ";
296 t << externalRef("",n->ref,TRUE);
301 t << "\" target=\"basefrm\">";
303 t << "\" target=\"_self\">";
309 t << convertToHtml(n->name);
311 if (!n->ref.isEmpty())
313 t << " [external]";
318 static void generateBriefDoc(FTextStream &t,Definition *def)
320 QCString brief = def->briefDescription(TRUE);
321 //printf("*** %p: generateBriefDoc(%s)='%s'\n",def,def->name().data(),brief.data());
322 if (!brief.isEmpty())
324 DocNode *root = validatingParseDoc(def->briefFile(),def->briefLine(),
325 def,0,brief,FALSE,FALSE,0,TRUE,TRUE);
326 QCString relPath = relativePathToRoot(def->getOutputFileBase());
327 HtmlCodeGenerator htmlGen(t,relPath);
328 HtmlDocVisitor *visitor = new HtmlDocVisitor(t,htmlGen,def);
329 root->accept(visitor);
335 static char compoundIcon(ClassDef *cd)
338 if (cd->getLanguage() == SrcLangExt_Slice)
340 if (cd->compoundType()==ClassDef::Interface)
344 else if (cd->compoundType()==ClassDef::Struct)
348 else if (cd->compoundType()==ClassDef::Exception)
356 void FTVHelp::generateTree(FTextStream &t, const QList<FTVNode> &nl,int level,int maxLevel,int &index)
358 QListIterator<FTVNode> nli(nl);
360 for (nli.toFirst();(n=nli.current());++nli)
362 t << "<tr id=\"row_" << generateIndentLabel(n,0) << "\"";
363 if ((index&1)==0) // even row
364 t << " class=\"even\"";
365 if (level>=maxLevel) // item invisible by default
366 t << " style=\"display:none;\"";
367 else // item visible by default
369 t << "><td class=\"entry\">";
370 bool nodeOpened = level+1<maxLevel;
371 generateIndent(t,n,nodeOpened);
374 if (n->def && n->def->definitionType()==Definition::TypeGroup)
378 else if (n->def && n->def->definitionType()==Definition::TypePage)
382 else if (n->def && n->def->definitionType()==Definition::TypeNamespace)
384 if (n->def->getLanguage() == SrcLangExt_Slice)
386 t << "<span class=\"icona\"><span class=\"icon\">M</span></span>";
390 t << "<span class=\"icona\"><span class=\"icon\">N</span></span>";
393 else if (n->def && n->def->definitionType()==Definition::TypeClass)
395 char icon=compoundIcon(dynamic_cast<ClassDef*>(n->def));
396 t << "<span class=\"icona\"><span class=\"icon\">" << icon << "</span></span>";
400 t << "<span id=\"img_" << generateIndentLabel(n,0)
401 << "\" class=\"iconf"
402 << (nodeOpened?"open":"closed")
403 << "\" onclick=\"toggleFolder('" << generateIndentLabel(n,0)
404 << "')\"> </span>";
407 t << "</td><td class=\"desc\">";
410 generateBriefDoc(t,n->def);
412 t << "</td></tr>" << endl;
414 generateTree(t,n->children,level+1,maxLevel,index);
419 if (n->def && n->def->definitionType()==Definition::TypeFile &&
420 ((FileDef*)n->def)->generateSourceFile())
422 srcRef = (FileDef*)n->def;
426 t << "<a href=\"" << srcRef->getSourceFileBase()
427 << Doxygen::htmlFileExtension
430 if (n->def && n->def->definitionType()==Definition::TypeGroup)
434 else if (n->def && n->def->definitionType()==Definition::TypePage)
438 else if (n->def && n->def->definitionType()==Definition::TypeNamespace)
440 if (n->def->getLanguage() == SrcLangExt_Slice)
442 t << "<span class=\"icona\"><span class=\"icon\">M</span></span>";
446 t << "<span class=\"icona\"><span class=\"icon\">N</span></span>";
449 else if (n->def && n->def->definitionType()==Definition::TypeClass)
451 char icon=compoundIcon(dynamic_cast<ClassDef*>(n->def));
452 t << "<span class=\"icona\"><span class=\"icon\">" << icon << "</span></span>";
456 t << "<span class=\"icondoc\"></span>";
463 t << "</td><td class=\"desc\">";
466 generateBriefDoc(t,n->def);
468 t << "</td></tr>" << endl;
473 //-----------------------------------------------------------
477 NavIndexEntry(const QCString &u,const QCString &p) : url(u), path(p) {}
482 class NavIndexEntryList : public QList<NavIndexEntry>
485 NavIndexEntryList() : QList<NavIndexEntry>() { setAutoDelete(TRUE); }
486 ~NavIndexEntryList() {}
488 int compareValues(const NavIndexEntry *item1,const NavIndexEntry *item2) const
490 // sort list based on url
491 return qstrcmp(item1->url,item2->url);
495 static QCString pathToNode(FTVNode *leaf,FTVNode *n)
500 result+=pathToNode(leaf,n->parent);
502 result+=QCString().setNum(n->index);
503 if (leaf!=n) result+=",";
507 static bool dupOfParent(const FTVNode *n)
509 if (n->parent==0) return FALSE;
510 if (n->file==n->parent->file) return TRUE;
514 static void generateJSLink(FTextStream &t,FTVNode *n)
516 if (n->file.isEmpty()) // no link
518 t << "\"" << convertToJSString(n->name) << "\", null, ";
520 else // link into other page
522 t << "\"" << convertToJSString(n->name) << "\", \"";
523 t << externalRef("",n->ref,TRUE);
529 static QCString convertFileId2Var(const QCString &fileId)
531 QCString varId = fileId;
532 int i=varId.findRev('/');
533 if (i>=0) varId = varId.mid(i+1);
534 return substitute(varId,"-","_");
537 static bool generateJSTree(NavIndexEntryList &navIndex,FTextStream &t,
538 const QList<FTVNode> &nl,int level,bool &first)
540 static QCString htmlOutput = Config_getString(HTML_OUTPUT);
542 indentStr.fill(' ',level*2);
544 QListIterator<FTVNode> nli(nl);
546 for (nli.toFirst();(n=nli.current());++nli)
548 // terminate previous entry
549 if (!first) t << "," << endl;
559 if (n->addToNavIndex) // add entry to the navigation index
561 if (n->def && n->def->definitionType()==Definition::TypeFile)
563 FileDef *fd = (FileDef*)n->def;
565 doc = fileVisibleInIndex(fd,src);
568 navIndex.append(new NavIndexEntry(node2URL(n,TRUE,FALSE),pathToNode(n,n)));
572 navIndex.append(new NavIndexEntry(node2URL(n,TRUE,TRUE),pathToNode(n,n)));
577 navIndex.append(new NavIndexEntry(node2URL(n),pathToNode(n,n)));
581 if (n->separateIndex) // store items in a separate file for dynamic loading
583 bool firstChild=TRUE;
584 t << indentStr << " [ ";
586 if (n->children.count()>0) // write children to separate file for dynamic loading
588 QCString fileId = n->file;
591 fileId+="_"+n->anchor;
597 QFile f(htmlOutput+"/"+fileId+".js");
598 if (f.open(IO_WriteOnly))
601 tt << "var " << convertFileId2Var(fileId) << " =" << endl;
602 generateJSTree(navIndex,tt,n->children,1,firstChild);
605 t << "\"" << fileId << "\" ]";
612 else // show items in this file
614 bool firstChild=TRUE;
615 t << indentStr << " [ ";
617 bool emptySection = !generateJSTree(navIndex,t,n->children,level+1,firstChild);
621 t << endl << indentStr << " ] ]";
627 static void generateJSNavTree(const QList<FTVNode> &nodeList)
629 QCString htmlOutput = Config_getString(HTML_OUTPUT);
630 QFile f(htmlOutput+"/navtreedata.js");
631 NavIndexEntryList navIndex;
632 if (f.open(IO_WriteOnly) /*&& fidx.open(IO_WriteOnly)*/)
634 //FTextStream tidx(&fidx);
635 //tidx << "var NAVTREEINDEX =" << endl;
636 //tidx << "{" << endl;
638 t << "/*\n@ @licstart The following is the entire license notice for the\n"
639 "JavaScript code in this file.\n\nCopyright (C) 1997-2017 by Dimitri van Heesch\n\n"
640 "This program is free software; you can redistribute it and/or modify\n"
641 "it under the terms of the GNU General Public License as published by\n"
642 "the Free Software Foundation; either version 2 of the License, or\n"
643 "(at your option) any later version.\n\n"
644 "This program is distributed in the hope that it will be useful,\n"
645 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
646 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
647 " GNU General Public License for more details.\n\n"
648 "You should have received a copy of the GNU General Public License along\n"
649 "with this program; if not, write to the Free Software Foundation, Inc.,\n"
650 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\n"
651 "@licend The above is the entire license notice\n"
652 "for the JavaScript code in this file\n"
654 t << "var NAVTREE =" << endl;
657 QCString &projName = Config_getString(PROJECT_NAME);
658 if (projName.isEmpty())
660 if (Doxygen::mainPage && !Doxygen::mainPage->title().isEmpty()) // Use title of main page as root
662 t << "\"" << convertToJSString(Doxygen::mainPage->title()) << "\", ";
664 else // Use default section title as root
666 LayoutNavEntry *lne = LayoutDocManager::instance().rootNavEntry()->find(LayoutNavEntry::MainPage);
667 t << "\"" << convertToJSString(lne->title()) << "\", ";
670 else // use PROJECT_NAME as root tree element
672 t << "\"" << convertToJSString(projName) << "\", ";
674 t << "\"index" << Doxygen::htmlFileExtension << "\", ";
676 // add special entry for index page
677 navIndex.append(new NavIndexEntry("index"+Doxygen::htmlFileExtension,""));
678 // related page index is written as a child of index.html, so add this as well
679 navIndex.append(new NavIndexEntry("pages"+Doxygen::htmlFileExtension,""));
682 generateJSTree(navIndex,t,nodeList,1,first);
687 t << endl << " ] ]" << endl;
688 t << "];" << endl << endl;
690 // write the navigation index (and sub-indices)
694 const int maxElemCount=250;
695 //QFile fidx(htmlOutput+"/navtreeindex.js");
696 QFile fsidx(htmlOutput+"/navtreeindex0.js");
697 if (/*fidx.open(IO_WriteOnly) &&*/ fsidx.open(IO_WriteOnly))
699 //FTextStream tidx(&fidx);
700 FTextStream tsidx(&fsidx);
701 t << "var NAVTREEINDEX =" << endl;
703 tsidx << "var NAVTREEINDEX" << subIndex << " =" << endl;
704 tsidx << "{" << endl;
705 QListIterator<NavIndexEntry> li(navIndex);
708 for (li.toFirst();(e=li.current());) // for each entry
720 t << "\"" << e->url << "\"";
722 tsidx << "\"" << e->url << "\":[" << e->path << "]";
724 if (li.current() && elemCount<maxElemCount-1) tsidx << ","; // not last entry
728 if (li.current() && elemCount>=maxElemCount) // switch to new sub-index
730 tsidx << "};" << endl;
734 fsidx.setName(htmlOutput+"/navtreeindex"+QCString().setNum(subIndex)+".js");
735 if (!fsidx.open(IO_WriteOnly)) break;
736 tsidx.setDevice(&fsidx);
737 tsidx << "var NAVTREEINDEX" << subIndex << " =" << endl;
738 tsidx << "{" << endl;
741 tsidx << "};" << endl;
742 t << endl << "];" << endl;
744 t << endl << "var SYNCONMSG = '" << theTranslator->trPanelSynchronisationTooltip(FALSE) << "';";
745 t << endl << "var SYNCOFFMSG = '" << theTranslator->trPanelSynchronisationTooltip(TRUE) << "';";
747 ResourceMgr::instance().copyResource("navtree.js",htmlOutput);
750 //-----------------------------------------------------------
753 void FTVHelp::generateTreeViewImages()
755 QCString dname=Config_getString(HTML_OUTPUT);
756 const ResourceMgr &rm = ResourceMgr::instance();
757 rm.copyResource("doc.luma",dname);
758 rm.copyResource("folderopen.luma",dname);
759 rm.copyResource("folderclosed.luma",dname);
760 rm.copyResource("splitbar.lum",dname);
764 void FTVHelp::generateTreeViewScripts()
766 QCString htmlOutput = Config_getString(HTML_OUTPUT);
768 // generate navtree.js & navtreeindex.js
769 generateJSNavTree(m_indentNodes[0]);
771 // copy resize.js & navtree.css
772 ResourceMgr::instance().copyResource("resize.js",htmlOutput);
773 ResourceMgr::instance().copyResource("navtree.css",htmlOutput);
776 // write tree inside page
777 void FTVHelp::generateTreeViewInline(FTextStream &t)
779 int preferredNumEntries = Config_getInt(HTML_INDEX_NUM_ENTRIES);
780 t << "<div class=\"directory\">\n";
781 QListIterator<FTVNode> li(m_indentNodes[0]);
784 for (;(n=li.current());++li)
786 if (n->children.count()>0)
788 d = n->computeTreeDepth(2);
789 if (d>depth) depth=d;
792 int preferredDepth = depth;
793 // write level selector
796 t << "<div class=\"levels\">[";
797 t << theTranslator->trDetailLevel();
800 for (i=1;i<=depth;i++)
802 t << "<span onclick=\"javascript:toggleLevel(" << i << ");\">" << i << "</span>";
806 if (preferredNumEntries>0)
809 for (int i=1;i<=depth;i++)
812 QListIterator<FTVNode> li(m_indentNodes[0]);
814 for (;(n=li.current());++li)
816 num+=n->numNodesAtLevel(0,i);
818 if (num<=preferredNumEntries)
829 //printf("preferred depth=%d\n",preferredDepth);
831 if (m_indentNodes[0].count())
833 t << "<table class=\"directory\">\n";
835 generateTree(t,m_indentNodes[0],0,preferredDepth,index);
839 t << "</div><!-- directory -->\n";
842 // write old style index.html and tree.html
843 void FTVHelp::generateTreeView()
845 generateTreeViewImages();
846 generateTreeViewScripts();