1 /******************************************************************************
2  * ftvhelp.cpp,v 1.0 2000/09/06 16:09:00
3  *
4  * Copyright (C) 1997-2015 by Dimitri van Heesch.
5  *
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.
11  *
12  * Documents produced by Doxygen are derivative works derived from the
13  * input used in their production; they are not affected by this license.
14  *
15  * Original version contributed by Kenney Wong <kwong@ea.com>
16  * Modified by Dimitri van Heesch
17  *
18  * Folder Tree View for offline help on browsers that do not support HTML Help.
19  */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <algorithm>
24 
25 #include "ftvhelp.h"
26 #include "config.h"
27 #include "message.h"
28 #include "doxygen.h"
29 #include "language.h"
30 #include "htmlgen.h"
31 #include "layout.h"
32 #include "pagedef.h"
33 #include "docparser.h"
34 #include "htmldocvisitor.h"
35 #include "filedef.h"
36 #include "classdef.h"
37 #include "util.h"
38 #include "resourcemgr.h"
39 
40 static int folderId=1;
41 
42 const char *JAVASCRIPT_LICENSE_TEXT = R"LIC(/*
43  @licstart  The following is the entire license notice for the JavaScript code in this file.
44 
45  The MIT License (MIT)
46 
47  Copyright (C) 1997-2020 by Dimitri van Heesch
48 
49  Permission is hereby granted, free of charge, to any person obtaining a copy of this software
50  and associated documentation files (the "Software"), to deal in the Software without restriction,
51  including without limitation the rights to use, copy, modify, merge, publish, distribute,
52  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
53  furnished to do so, subject to the following conditions:
54 
55  The above copyright notice and this permission notice shall be included in all copies or
56  substantial portions of the Software.
57 
58  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
59  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
60  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
61  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
62  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
63 
64  @licend  The above is the entire license notice for the JavaScript code in this file
65 */
66 )LIC";
67 
68 struct FTVNode
69 {
FTVNodeFTVNode70   FTVNode(bool dir,const QCString &r,const QCString &f,const QCString &a,
71           const QCString &n,bool sepIndex,bool navIndex,const Definition *df)
72     : isLast(TRUE), isDir(dir),ref(r),file(f),anchor(a),name(n), index(0),
73       parent(0), separateIndex(sepIndex), addToNavIndex(navIndex),
74       def(df) {}
~FTVNodeFTVNode75  ~FTVNode() { for (const auto &child : children) delete child; }
76   int computeTreeDepth(int level) const;
77   int numNodesAtLevel(int level,int maxLevel) const;
78   bool isLast;
79   bool isDir;
80   QCString ref;
81   QCString file;
82   QCString anchor;
83   QCString name;
84   int index;
85   std::vector<FTVNode*> children;
86   FTVNode *parent;
87   bool separateIndex;
88   bool addToNavIndex;
89   const Definition *def;
90 };
91 
computeTreeDepth(int level) const92 int FTVNode::computeTreeDepth(int level) const
93 {
94   int maxDepth=level;
95   for (const auto &n : children)
96   {
97     if (!n->children.empty())
98     {
99       int d = n->computeTreeDepth(level+1);
100       if (d>maxDepth) maxDepth=d;
101     }
102   }
103   return maxDepth;
104 }
105 
numNodesAtLevel(int level,int maxLevel) const106 int FTVNode::numNodesAtLevel(int level,int maxLevel) const
107 {
108   int num=0;
109   if (level<maxLevel)
110   {
111     num++; // this node
112     for (const auto &n : children)
113     {
114       num+=n->numNodesAtLevel(level+1,maxLevel);
115     }
116   }
117   return num;
118 }
119 
120 //----------------------------------------------------------------------------
121 
122 /*! Constructs an ftv help object.
123  *  The object has to be \link initialize() initialized\endlink before it can
124  *  be used.
125  */
FTVHelp(bool TLI)126 FTVHelp::FTVHelp(bool TLI)
127 {
128   /* initial depth */
129   m_indentNodes.resize(1);
130   m_indent=0;
131   m_topLevelIndex = TLI;
132 }
133 
134 /*! Destroys the ftv help object. */
~FTVHelp()135 FTVHelp::~FTVHelp()
136 {
137   for (auto &idx : m_indentNodes)
138   {
139     for (auto &n : idx)
140     {
141       delete n;
142     }
143     idx.clear();
144   }
145   m_indentNodes.clear();
146 }
147 
148 /*! This will create a folder tree view table of contents file (tree.js).
149  *  \sa finalize()
150  */
initialize()151 void FTVHelp::initialize()
152 {
153 }
154 
155 /*! Finalizes the FTV help. This will finish and close the
156  *  contents file (index.js).
157  *  \sa initialize()
158  */
finalize()159 void FTVHelp::finalize()
160 {
161   generateTreeView();
162 }
163 
164 /*! Increase the level of the contents hierarchy.
165  *  This will start a new sublist in contents file.
166  *  \sa decContentsDepth()
167  */
incContentsDepth()168 void FTVHelp::incContentsDepth()
169 {
170   //printf("%p: incContentsDepth() indent=%d\n",this,m_indent);
171   m_indent++;
172   m_indentNodes.resize(m_indent+1);
173 }
174 
175 /*! Decrease the level of the contents hierarchy.
176  *  This will end the current sublist.
177  *  \sa incContentsDepth()
178  */
decContentsDepth()179 void FTVHelp::decContentsDepth()
180 {
181   //printf("%p: decContentsDepth() indent=%d\n",this,m_indent);
182   ASSERT(m_indent>0);
183   if (m_indent>0)
184   {
185     m_indent--;
186     std::vector<FTVNode*> &nl = m_indentNodes[m_indent];
187     if (!nl.empty())
188     {
189       FTVNode *parent = nl.back();
190       std::vector<FTVNode*> &children = m_indentNodes[m_indent+1];
191       for (const auto &child : children)
192       {
193         parent->children.push_back(child);
194       }
195       children.clear();
196     }
197   }
198 }
199 
200 /*! Add a list item to the contents file.
201  *  \param isDir TRUE if the item is a directory, FALSE if it is a text
202  *  \param name the name of the item.
203  *  \param ref  the URL of to the item.
204  *  \param file the file containing the definition of the item
205  *  \param anchor the anchor within the file.
206  *  \param separateIndex put the entries in a separate index file
207  *  \param addToNavIndex add this entry to the quick navigation index
208  *  \param def Definition corresponding to this entry
209  */
addContentsItem(bool isDir,const QCString & name,const QCString & ref,const QCString & file,const QCString & anchor,bool separateIndex,bool addToNavIndex,const Definition * def)210 void FTVHelp::addContentsItem(bool isDir,
211                               const QCString &name,
212                               const QCString &ref,
213                               const QCString &file,
214                               const QCString &anchor,
215                               bool separateIndex,
216                               bool addToNavIndex,
217                               const Definition *def
218                               )
219 {
220   //printf("%p: m_indent=%d addContentsItem(%s,%s,%s,%s)\n",this,m_indent,name,ref,file,anchor);
221   std::vector<FTVNode*> &nl = m_indentNodes[m_indent];
222   FTVNode *newNode = new FTVNode(isDir,ref,file,anchor,name,separateIndex,addToNavIndex,def);
223   if (!nl.empty())
224   {
225     nl.back()->isLast=FALSE;
226   }
227   nl.push_back(newNode);
228   newNode->index = static_cast<int>(nl.size()-1);
229   if (m_indent>0)
230   {
231     std::vector<FTVNode*> &pnl = m_indentNodes[m_indent-1];
232     if (!pnl.empty())
233     {
234       newNode->parent = pnl.back();
235     }
236   }
237 }
238 
node2URL(const FTVNode * n,bool overruleFile=FALSE,bool srcLink=FALSE)239 static QCString node2URL(const FTVNode *n,bool overruleFile=FALSE,bool srcLink=FALSE)
240 {
241   QCString url = n->file;
242   if (!url.isEmpty() && url.at(0)=='!')  // relative URL
243   {
244     // remove leading !
245     url = url.mid(1);
246   }
247   else if (!url.isEmpty() && url.at(0)=='^') // absolute URL
248   {
249     // skip, keep ^ in the output
250   }
251   else // local file (with optional anchor)
252   {
253     if (overruleFile && n->def && n->def->definitionType()==Definition::TypeFile)
254     {
255       const FileDef *fd = toFileDef(n->def);
256       if (srcLink)
257       {
258         url = fd->getSourceFileBase();
259       }
260       else
261       {
262         url = fd->getOutputFileBase();
263       }
264     }
265     url = addHtmlExtensionIfMissing(url);
266     if (!n->anchor.isEmpty()) url+="#"+n->anchor;
267   }
268   return url;
269 }
270 
generateIndentLabel(FTVNode * n,int level)271 QCString FTVHelp::generateIndentLabel(FTVNode *n,int level)
272 {
273   QCString result;
274   if (n->parent)
275   {
276     result=generateIndentLabel(n->parent,level+1);
277   }
278   result+=QCString().setNum(n->index)+"_";
279   return result;
280 }
281 
generateIndent(TextStream & t,FTVNode * n,bool opened)282 void FTVHelp::generateIndent(TextStream &t, FTVNode *n,bool opened)
283 {
284   int indent=0;
285   FTVNode *p = n->parent;
286   while (p) { indent++; p=p->parent; }
287   if (n->isDir)
288   {
289     QCString dir = opened ? "&#9660;" : "&#9658;";
290     t << "<span style=\"width:" << (indent*16) << "px;display:inline-block;\">&#160;</span>"
291       << "<span id=\"arr_" << generateIndentLabel(n,0) << "\" class=\"arrow\" ";
292     t << "onclick=\"toggleFolder('" << generateIndentLabel(n,0) << "')\"";
293     t << ">" << dir
294       << "</span>";
295   }
296   else
297   {
298     t << "<span style=\"width:" << ((indent+1)*16) << "px;display:inline-block;\">&#160;</span>";
299   }
300 }
301 
generateLink(TextStream & t,FTVNode * n)302 void FTVHelp::generateLink(TextStream &t,FTVNode *n)
303 {
304   //printf("FTVHelp::generateLink(ref=%s,file=%s,anchor=%s\n",
305   //    qPrint(n->ref),qPrint(n->file),qPrint(n->anchor));
306   bool setTarget = FALSE;
307   if (n->file.isEmpty()) // no link
308   {
309     t << "<b>" << convertToHtml(n->name) << "</b>";
310   }
311   else // link into other frame
312   {
313     if (!n->ref.isEmpty()) // link to entity imported via tag file
314     {
315       t << "<a class=\"elRef\" ";
316       QCString result = externalLinkTarget();
317       if (result != "") setTarget = TRUE;
318       t << result;
319     }
320     else // local link
321     {
322       t << "<a class=\"el\" ";
323     }
324     t << "href=\"";
325     t << externalRef("",n->ref,TRUE);
326     t << node2URL(n);
327     if (!setTarget)
328     {
329       if (m_topLevelIndex)
330         t << "\" target=\"basefrm\">";
331       else
332         t << "\" target=\"_self\">";
333     }
334     else
335     {
336       t << "\">";
337     }
338     t << convertToHtml(n->name);
339     t << "</a>";
340     if (!n->ref.isEmpty())
341     {
342       t << "&#160;[external]";
343     }
344   }
345 }
346 
generateBriefDoc(TextStream & t,const Definition * def)347 static void generateBriefDoc(TextStream &t,const Definition *def)
348 {
349   QCString brief = def->briefDescription(TRUE);
350   //printf("*** %p: generateBriefDoc(%s)='%s'\n",def,qPrint(def->name()),qPrint(brief));
351   if (!brief.isEmpty())
352   {
353     std::unique_ptr<IDocParser> parser { createDocParser() };
354     std::unique_ptr<DocRoot>    root   { validatingParseDoc(*parser.get(),
355                                          def->briefFile(),def->briefLine(),
356                                          def,0,brief,FALSE,FALSE,
357                                          QCString(),TRUE,TRUE,Config_getBool(MARKDOWN_SUPPORT)) };
358     QCString relPath = relativePathToRoot(def->getOutputFileBase());
359     HtmlCodeGenerator htmlGen(t,relPath);
360     auto visitor = std::make_unique<HtmlDocVisitor>(t,htmlGen,def);
361     root->accept(visitor.get());
362   }
363 }
364 
compoundIcon(const ClassDef * cd)365 static char compoundIcon(const ClassDef *cd)
366 {
367   char icon='C';
368   if (cd->getLanguage() == SrcLangExt_Slice)
369   {
370     if (cd->compoundType()==ClassDef::Interface)
371     {
372       icon='I';
373     }
374     else if (cd->compoundType()==ClassDef::Struct)
375     {
376       icon='S';
377     }
378     else if (cd->compoundType()==ClassDef::Exception)
379     {
380       icon='E';
381     }
382   }
383   return icon;
384 }
385 
generateTree(TextStream & t,const std::vector<FTVNode * > & nl,int level,int maxLevel,int & index)386 void FTVHelp::generateTree(TextStream &t, const std::vector<FTVNode*> &nl,int level,int maxLevel,int &index)
387 {
388   for (const auto &n : nl)
389   {
390     t << "<tr id=\"row_" << generateIndentLabel(n,0) << "\"";
391     if ((index&1)==0) // even row
392       t << " class=\"even\"";
393     if (level>=maxLevel) // item invisible by default
394       t << " style=\"display:none;\"";
395     else // item visible by default
396       index++;
397     t << "><td class=\"entry\">";
398     bool nodeOpened = level+1<maxLevel;
399     generateIndent(t,n,nodeOpened);
400     if (n->isDir)
401     {
402       if (n->def && n->def->definitionType()==Definition::TypeGroup)
403       {
404         // no icon
405       }
406       else if (n->def && n->def->definitionType()==Definition::TypePage)
407       {
408         // no icon
409       }
410       else if (n->def && n->def->definitionType()==Definition::TypeNamespace)
411       {
412         if (n->def->getLanguage() == SrcLangExt_Slice)
413         {
414           t << "<span class=\"icona\"><span class=\"icon\">M</span></span>";
415         }
416         else
417         {
418           t << "<span class=\"icona\"><span class=\"icon\">N</span></span>";
419         }
420       }
421       else if (n->def && n->def->definitionType()==Definition::TypeClass)
422       {
423         char icon=compoundIcon(toClassDef(n->def));
424         t << "<span class=\"icona\"><span class=\"icon\">" << icon << "</span></span>";
425       }
426       else
427       {
428         t << "<span id=\"img_" << generateIndentLabel(n,0)
429           << "\" class=\"iconf"
430           << (nodeOpened?"open":"closed")
431           << "\" onclick=\"toggleFolder('" << generateIndentLabel(n,0)
432           << "')\">&#160;</span>";
433       }
434       generateLink(t,n);
435       t << "</td><td class=\"desc\">";
436       if (n->def)
437       {
438         generateBriefDoc(t,n->def);
439       }
440       t << "</td></tr>\n";
441       folderId++;
442       generateTree(t,n->children,level+1,maxLevel,index);
443     }
444     else // leaf node
445     {
446       const FileDef *srcRef=0;
447       if (n->def && n->def->definitionType()==Definition::TypeFile &&
448           (toFileDef(n->def))->generateSourceFile())
449       {
450         srcRef = toFileDef(n->def);
451       }
452       if (srcRef)
453       {
454         t << "<a href=\"" << addHtmlExtensionIfMissing(srcRef->getSourceFileBase())
455           << "\">";
456       }
457       if (n->def && n->def->definitionType()==Definition::TypeGroup)
458       {
459         // no icon
460       }
461       else if (n->def && n->def->definitionType()==Definition::TypePage)
462       {
463         // no icon
464       }
465       else if (n->def && n->def->definitionType()==Definition::TypeNamespace)
466       {
467         if (n->def->getLanguage() == SrcLangExt_Slice)
468         {
469           t << "<span class=\"icona\"><span class=\"icon\">M</span></span>";
470         }
471         else
472         {
473           t << "<span class=\"icona\"><span class=\"icon\">N</span></span>";
474         }
475       }
476       else if (n->def && n->def->definitionType()==Definition::TypeClass)
477       {
478         char icon=compoundIcon(toClassDef(n->def));
479         t << "<span class=\"icona\"><span class=\"icon\">" << icon << "</span></span>";
480       }
481       else if (n->def && n->def->definitionType()==Definition::TypeConcept)
482       {
483         t << "<span class=\"icona\"><span class=\"icon\">R</span></span>";
484       }
485       else if (n->def && n->def->definitionType()==Definition::TypeDir)
486       {
487         t << "<span class=\"iconfclosed\"></span>";
488       }
489       else
490       {
491         t << "<span class=\"icondoc\"></span>";
492       }
493       if (srcRef)
494       {
495         t << "</a>";
496       }
497       generateLink(t,n);
498       t << "</td><td class=\"desc\">";
499       if (n->def)
500       {
501         generateBriefDoc(t,n->def);
502       }
503       t << "</td></tr>\n";
504     }
505   }
506 }
507 
508 //-----------------------------------------------------------
509 
510 struct NavIndexEntry
511 {
NavIndexEntryNavIndexEntry512   NavIndexEntry(const QCString &u,const QCString &p) : url(u), path(p) {}
513   QCString url;
514   QCString path;
515 };
516 
517 class NavIndexEntryList : public std::vector<NavIndexEntry>
518 {
519 };
520 
pathToNode(const FTVNode * leaf,const FTVNode * n)521 static QCString pathToNode(const FTVNode *leaf,const FTVNode *n)
522 {
523   QCString result;
524   if (n->parent)
525   {
526     result+=pathToNode(leaf,n->parent);
527   }
528   result+=QCString().setNum(n->index);
529   if (leaf!=n) result+=",";
530   return result;
531 }
532 
dupOfParent(const FTVNode * n)533 static bool dupOfParent(const FTVNode *n)
534 {
535   if (n->parent==0) return FALSE;
536   if (n->file==n->parent->file) return TRUE;
537   return FALSE;
538 }
539 
generateJSLink(TextStream & t,const FTVNode * n)540 static void generateJSLink(TextStream &t,const FTVNode *n)
541 {
542   if (n->file.isEmpty()) // no link
543   {
544     t << "\"" << convertToJSString(n->name) << "\", null, ";
545   }
546   else // link into other page
547   {
548     t << "\"" << convertToJSString(n->name) << "\", \"";
549     t << externalRef("",n->ref,TRUE);
550     t << node2URL(n);
551     t << "\", ";
552   }
553 }
554 
convertFileId2Var(const QCString & fileId)555 static QCString convertFileId2Var(const QCString &fileId)
556 {
557   QCString varId = fileId;
558   int i=varId.findRev('/');
559   if (i>=0) varId = varId.mid(i+1);
560   return substitute(varId,"-","_");
561 }
562 
generateJSTree(NavIndexEntryList & navIndex,TextStream & t,const std::vector<FTVNode * > & nl,int level,bool & first)563 static bool generateJSTree(NavIndexEntryList &navIndex,TextStream &t,
564                            const std::vector<FTVNode*> &nl,int level,bool &first)
565 {
566   static QCString htmlOutput = Config_getString(HTML_OUTPUT);
567   QCString indentStr;
568   indentStr.fill(' ',level*2);
569   bool found=FALSE;
570   for (const auto &n : nl)
571   {
572     // terminate previous entry
573     if (!first) t << ",\n";
574     first=FALSE;
575 
576     // start entry
577     if (!found)
578     {
579       t << "[\n";
580     }
581     found=TRUE;
582 
583     if (n->addToNavIndex) // add entry to the navigation index
584     {
585       if (n->def && n->def->definitionType()==Definition::TypeFile)
586       {
587         const FileDef *fd = toFileDef(n->def);
588         bool doc,src;
589         doc = fileVisibleInIndex(fd,src);
590         if (doc)
591         {
592           navIndex.emplace_back(node2URL(n,TRUE,FALSE),pathToNode(n,n));
593         }
594         if (src)
595         {
596           navIndex.emplace_back(node2URL(n,TRUE,TRUE),pathToNode(n,n));
597         }
598       }
599       else
600       {
601         navIndex.emplace_back(node2URL(n),pathToNode(n,n));
602       }
603     }
604 
605     if (n->separateIndex) // store items in a separate file for dynamic loading
606     {
607       bool firstChild=TRUE;
608       t << indentStr << "  [ ";
609       generateJSLink(t,n);
610       if (!n->children.empty()) // write children to separate file for dynamic loading
611       {
612         QCString fileId = n->file;
613         if (!n->anchor.isEmpty())
614         {
615           fileId+="_"+n->anchor;
616         }
617         if (dupOfParent(n))
618         {
619           fileId+="_dup";
620         }
621         QCString fileName = htmlOutput+"/"+fileId+".js";
622         std::ofstream f(fileName.str(),std::ofstream::out | std::ofstream::binary);
623         if (f.is_open())
624         {
625           TextStream tt(&f);
626           tt << "var " << convertFileId2Var(fileId) << " =\n";
627           generateJSTree(navIndex,tt,n->children,1,firstChild);
628           tt << "\n];";
629         }
630         f.close();
631         t << "\"" << fileId << "\" ]";
632       }
633       else // no children
634       {
635         t << "null ]";
636       }
637     }
638     else // show items in this file
639     {
640       bool firstChild=TRUE;
641       t << indentStr << "  [ ";
642       generateJSLink(t,n);
643       bool emptySection = !generateJSTree(navIndex,t,n->children,level+1,firstChild);
644       if (emptySection)
645         t << "null ]";
646       else
647         t << "\n" << indentStr << "  ] ]";
648     }
649   }
650   return found;
651 }
652 
generateJSNavTree(const std::vector<FTVNode * > & nodeList)653 static void generateJSNavTree(const std::vector<FTVNode*> &nodeList)
654 {
655   QCString htmlOutput = Config_getString(HTML_OUTPUT);
656   std::ofstream f(htmlOutput.str()+"/navtreedata.js",std::ofstream::out | std::ofstream::binary);
657   NavIndexEntryList navIndex;
658   if (f.is_open())
659   {
660     TextStream t(&f);
661     //TextStream tidx(&fidx);
662     //tidx << "var NAVTREEINDEX =\n";
663     //tidx << "{\n";
664     t << JAVASCRIPT_LICENSE_TEXT;
665     t << "var NAVTREE =\n";
666     t << "[\n";
667     t << "  [ ";
668     QCString projName = Config_getString(PROJECT_NAME);
669     if (projName.isEmpty())
670     {
671       if (mainPageHasTitle()) // Use title of main page as root
672       {
673         t << "\"" << convertToJSString(Doxygen::mainPage->title()) << "\", ";
674       }
675       else // Use default section title as root
676       {
677         LayoutNavEntry *lne = LayoutDocManager::instance().rootNavEntry()->find(LayoutNavEntry::MainPage);
678         t << "\"" << convertToJSString(lne->title()) << "\", ";
679       }
680     }
681     else // use PROJECT_NAME as root tree element
682     {
683       t << "\"" << convertToJSString(projName) << "\", ";
684     }
685     t << "\"index" << Doxygen::htmlFileExtension << "\", ";
686 
687     // add special entry for index page
688     navIndex.emplace_back("index"+Doxygen::htmlFileExtension,"");
689     // related page index is written as a child of index.html, so add this as well
690     navIndex.emplace_back("pages"+Doxygen::htmlFileExtension,"");
691 
692     bool first=TRUE;
693     generateJSTree(navIndex,t,nodeList,1,first);
694 
695     if (first)
696       t << "]\n";
697     else
698       t << "\n  ] ]\n";
699     t << "];\n\n";
700 
701     // write the navigation index (and sub-indices)
702     std::sort(navIndex.begin(),navIndex.end(),[](const auto &n1,const auto &n2)
703         { return !n1.url.isEmpty() && (n2.url.isEmpty() || (n1.url<n2.url)); });
704 
705     int subIndex=0;
706     int elemCount=0;
707     const int maxElemCount=250;
708     std::ofstream tsidx(htmlOutput.str()+"/navtreeindex0.js",std::ofstream::out | std::ofstream::binary);
709     if (tsidx.is_open())
710     {
711       t << "var NAVTREEINDEX =\n";
712       t << "[\n";
713       tsidx << "var NAVTREEINDEX" << subIndex << " =\n";
714       tsidx << "{\n";
715       first=TRUE;
716       auto it = navIndex.begin();
717       while (it!=navIndex.end())
718       {
719         const NavIndexEntry &e = *it;
720         if (elemCount==0)
721         {
722           if (!first)
723           {
724             t << ",\n";
725           }
726           else
727           {
728             first=FALSE;
729           }
730           t << "\"" << e.url << "\"";
731         }
732         tsidx << "\"" << e.url << "\":[" << e.path << "]";
733         ++it;
734         if (it!=navIndex.end() && elemCount<maxElemCount-1) tsidx << ","; // not last entry
735         tsidx << "\n";
736 
737         elemCount++;
738         if (it!=navIndex.end() && elemCount>=maxElemCount) // switch to new sub-index
739         {
740           tsidx << "};\n";
741           elemCount=0;
742           tsidx.close();
743           subIndex++;
744           QCString fileName = htmlOutput+"/navtreeindex"+QCString().setNum(subIndex)+".js";
745           tsidx.open(fileName.str(),std::ofstream::out | std::ofstream::binary);
746           if (!tsidx.is_open()) break;
747           tsidx << "var NAVTREEINDEX" << subIndex << " =\n";
748           tsidx << "{\n";
749         }
750       }
751       tsidx << "};\n";
752       t << "\n];\n";
753     }
754     t << "\nvar SYNCONMSG = '"  << theTranslator->trPanelSynchronisationTooltip(FALSE) << "';";
755     t << "\nvar SYNCOFFMSG = '" << theTranslator->trPanelSynchronisationTooltip(TRUE)  << "';";
756   }
757   ResourceMgr::instance().copyResource("navtree.js",htmlOutput);
758 }
759 
760 //-----------------------------------------------------------
761 
762 // new style images
generateTreeViewImages()763 void FTVHelp::generateTreeViewImages()
764 {
765   QCString dname=Config_getString(HTML_OUTPUT);
766   const ResourceMgr &rm = ResourceMgr::instance();
767   rm.copyResource("doc.luma",dname);
768   rm.copyResource("folderopen.luma",dname);
769   rm.copyResource("folderclosed.luma",dname);
770   rm.copyResource("splitbar.lum",dname);
771 }
772 
773 // new style scripts
generateTreeViewScripts()774 void FTVHelp::generateTreeViewScripts()
775 {
776   QCString htmlOutput = Config_getString(HTML_OUTPUT);
777 
778   // generate navtree.js & navtreeindex.js
779   generateJSNavTree(m_indentNodes[0]);
780 
781   // copy resize.js & navtree.css
782   ResourceMgr::instance().copyResource("resize.js",htmlOutput);
783   ResourceMgr::instance().copyResource("navtree.css",htmlOutput);
784 }
785 
786 // write tree inside page
generateTreeViewInline(TextStream & t)787 void FTVHelp::generateTreeViewInline(TextStream &t)
788 {
789   int preferredNumEntries = Config_getInt(HTML_INDEX_NUM_ENTRIES);
790   t << "<div class=\"directory\">\n";
791   int d=1, depth=1;
792   for (const auto &n : m_indentNodes[0])
793   {
794     if (!n->children.empty())
795     {
796       d = n->computeTreeDepth(2);
797       if (d>depth) depth=d;
798     }
799   }
800   int preferredDepth = depth;
801   // write level selector
802   if (depth>1)
803   {
804     t << "<div class=\"levels\">[";
805     t << theTranslator->trDetailLevel();
806     t << " ";
807     for (int i=1;i<=depth;i++)
808     {
809       t << "<span onclick=\"javascript:toggleLevel(" << i << ");\">" << i << "</span>";
810     }
811     t << "]</div>";
812 
813     if (preferredNumEntries>0)
814     {
815       preferredDepth=1;
816       for (int i=1;i<=depth;i++)
817       {
818         int num=0;
819         for (const auto &n : m_indentNodes[0])
820         {
821           num+=n->numNodesAtLevel(0,i);
822         }
823         if (num<=preferredNumEntries)
824         {
825           preferredDepth=i;
826         }
827         else
828         {
829           break;
830         }
831       }
832     }
833   }
834   //printf("preferred depth=%d\n",preferredDepth);
835 
836   if (!m_indentNodes[0].empty())
837   {
838     t << "<table class=\"directory\">\n";
839     int index=0;
840     generateTree(t,m_indentNodes[0],0,preferredDepth,index);
841     t << "</table>\n";
842   }
843 
844   t << "</div><!-- directory -->\n";
845 }
846 
847 // write old style index.html and tree.html
generateTreeView()848 void FTVHelp::generateTreeView()
849 {
850   generateTreeViewImages();
851   generateTreeViewScripts();
852 }
853