Fix for UBSan build
[platform/upstream/doxygen.git] / src / htmlhelp.cpp
1 /******************************************************************************
2  *
3  * 
4  *
5  * Copyright (C) 1997-2012 by Dimitri van Heesch.
6  *
7  * Permission to use, copy, modify, and distribute this software and its
8  * documentation under the terms of the GNU General Public License is hereby 
9  * granted. No representations are made about the suitability of this software 
10  * for any purpose. It is provided "as is" without express or implied warranty.
11  * See the GNU General Public License for more details.
12  *
13  * Documents produced by Doxygen are derivative works derived from the
14  * input used in their production; they are not affected by this license.
15  *
16  * The original version of this file is largely based on a contribution from
17  * Harm van der Heijden.
18  */
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <qlist.h>
23 #include <qdict.h>
24 #include <qregexp.h>
25 #include "qtextcodec.h"
26 #include "sortdict.h"
27
28 #include "htmlhelp.h"
29 #include "config.h"
30 #include "message.h"
31 #include "doxygen.h"
32 #include "language.h"
33 #include "portable.h"
34
35 //----------------------------------------------------------------------------
36
37 /** Class representing a field in the HTML help index. */
38 struct IndexField
39 {
40   QCString name;
41   QCString url;
42   QCString anchor;
43   bool     link;
44   bool     reversed;
45 };
46
47 /** Sorted dictionary of IndexField objects. */
48 class IndexFieldSDict : public SDict<IndexField>
49 {
50   public:
51     IndexFieldSDict() : SDict<IndexField>(17) {}
52    ~IndexFieldSDict() {}
53     int compareItems(GCI item1, GCI item2)
54     {
55       return stricmp(((IndexField *)item1)->name,((IndexField *)item2)->name);
56     }
57 };
58
59 /** A helper class for HtmlHelp that manages a two level index in 
60  *  alphabetical order.
61  */
62 class HtmlHelpIndex
63 {
64   public:
65     HtmlHelpIndex(HtmlHelp *help);
66    ~HtmlHelpIndex();
67     void addItem(const char *first,const char *second, 
68                  const char *url, const char *anchor,
69                  bool hasLink,bool reversed);
70     void writeFields(FTextStream &t);
71   private:
72     IndexFieldSDict *dict;   
73     HtmlHelp *m_help;
74 };
75
76 /*! Constructs a new HtmlHelp index */
77 HtmlHelpIndex::HtmlHelpIndex(HtmlHelp *help) : m_help(help)
78 {
79   dict = new IndexFieldSDict;
80   dict->setAutoDelete(TRUE);
81 }
82
83 /*! Destroys the HtmlHelp index */
84 HtmlHelpIndex::~HtmlHelpIndex()
85 {
86   delete dict;
87 }
88
89 /*! Stores an item in the index if it is not already present. 
90  *  Items are stored in alphetical order, by sorting on the
91  *  concatenation of \a level1 and \a level2 (if present).
92  *
93  *  \param level1 the string at level 1 in the index.
94  *  \param level2 the string at level 2 in the index (or 0 if not applicable).
95  *  \param url the url of the documentation (without .html extension).
96  *  \param anchor the anchor of the documentation within the page.
97  *  \param hasLink if true, the url (without anchor) can be used in the 
98  *         level1 item, when writing the header of a list of level2 items.
99  *  \param reversed TRUE if level1 is the member name and level2 the compound
100  *         name.
101  */
102 void HtmlHelpIndex::addItem(const char *level1,const char *level2,
103                        const char *url,const char *anchor,bool hasLink,
104                        bool reversed)
105 {
106   QCString key = level1; 
107   if (level2) key+= (QCString)"?" + level2;
108   if (key.find(QRegExp("@[0-9]+"))!=-1) // skip anonymous stuff
109   {
110     return;
111   }
112   if (dict->find(key)==0) // new key
113   {
114     //printf(">>>>>>>>> HtmlHelpIndex::addItem(%s,%s,%s,%s)\n",
115     //      level1,level2,url,anchor);
116     IndexField *f = new IndexField;
117     f->name     = key;
118     f->url      = url;
119     f->anchor   = anchor;
120     f->link     = hasLink;
121     f->reversed = reversed;
122     dict->append(key,f);
123   }
124 }
125
126 static QCString field2URL(const IndexField *f,bool checkReversed)
127 {
128   QCString result = f->url + Doxygen::htmlFileExtension;
129   if (!f->anchor.isEmpty() && (!checkReversed || f->reversed)) 
130   {
131     result+="#"+f->anchor;  
132   }
133   return result;
134 }
135
136 /*! Writes the sorted list of index items into a html like list.
137  *
138  *  An list of calls with <code>name = level1,level2</code> as follows:
139  *  <pre>
140  *    a1,b1
141  *    a1,b2
142  *    a2,b1
143  *    a2,b2
144  *    a3
145  *    a4,b1
146  *  </pre>
147  *
148  *  Will result in the following list:
149  *
150  *  <pre>
151  *    a1       -> link to url if hasLink==TRUE
152  *      b1     -> link to url#anchor
153  *      b2     -> link to url#anchor
154  *    a2       -> link to url if hasLink==TRUE
155  *      b1     -> link to url#anchor
156  *      b2     -> link to url#anchor
157  *    a3       -> link to url if hasLink==TRUE
158  *    a4       -> link to url if hasLink==TRUE
159  *      b1     -> link to url#anchor 
160  *  </pre>
161  */
162 void HtmlHelpIndex::writeFields(FTextStream &t)
163 {
164   dict->sort();
165   IndexFieldSDict::Iterator ifli(*dict);
166   IndexField *f;
167   QCString lastLevel1;
168   bool level2Started=FALSE;
169   for (;(f=ifli.current());++ifli)
170   {
171     QCString level1,level2;
172     int i;
173     if ((i=f->name.find('?'))!=-1)
174     {
175       level1 = f->name.left(i);
176       level2 = f->name.right(f->name.length()-i-1); 
177     }
178     else
179     {
180       level1  = f->name.copy();
181     }
182
183     if (level1!=lastLevel1)
184     { // finish old list at level 2
185       if (level2Started) t << "  </UL>" << endl;
186       level2Started=FALSE;
187     
188       // <Antony>
189       // Added this code so that an item with only one subitem is written
190       // without any subitem.
191       // For example:
192       //   a1, b1 -> will create only a1, not separate subitem for b1
193       //   a2, b2
194       //   a2, b3
195       QCString nextLevel1;
196       IndexField* fnext = ++ifli;
197       if (fnext)
198       {
199         nextLevel1 = fnext->name.left(fnext->name.find('?'));
200         --ifli;
201       }
202       if (level1 != nextLevel1)
203       {
204         level2 = "";
205       }
206       // </Antony>
207
208       if (level2.isEmpty())
209       {
210         t << "  <LI><OBJECT type=\"text/sitemap\">";
211         t << "<param name=\"Local\" value=\"" << field2URL(f,TRUE);
212         t << "\">";
213         t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
214            "</OBJECT>\n";
215       }
216       else
217       {
218         if (f->link)
219         {
220           t << "  <LI><OBJECT type=\"text/sitemap\">";
221           t << "<param name=\"Local\" value=\"" << field2URL(f,TRUE);
222           t << "\">";
223           t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
224                "</OBJECT>\n";
225         }
226         else
227         {
228           t << "  <LI><OBJECT type=\"text/sitemap\">";
229           t << "<param name=\"See Also\" value=\"" << m_help->recode(level1) << "\">";
230           t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
231                "</OBJECT>\n";
232         }
233       }
234     }
235     if (!level2Started && !level2.isEmpty())
236     { // start new list at level 2
237       t << "  <UL>" << endl;
238       level2Started=TRUE;
239     }
240     else if (level2Started && level2.isEmpty())
241     { // end list at level 2
242       t << "  </UL>" << endl;
243       level2Started=FALSE;
244     }
245     if (level2Started)
246     {
247       t << "    <LI><OBJECT type=\"text/sitemap\">";
248       t << "<param name=\"Local\" value=\"" << field2URL(f,FALSE);
249       t << "\">";
250       t << "<param name=\"Name\" value=\"" << m_help->recode(level2) << "\">"
251          "</OBJECT>\n";
252     }
253     lastLevel1 = level1.copy();
254   } 
255   if (level2Started) t << "  </UL>" << endl;
256 }
257
258 //----------------------------------------------------------------------------
259
260 HtmlHelp *HtmlHelp::theInstance = 0;
261
262 /*! Constructs an html object. 
263  *  The object has to be \link initialize() initialized\endlink before it can 
264  *  be used.
265  */
266 HtmlHelp::HtmlHelp() : indexFileDict(1009)
267 {
268   /* initial depth */
269   dc = 0;
270   cf = kf = 0;
271   index = new HtmlHelpIndex(this);
272   m_fromUtf8 = (void *)(-1);
273 }
274
275 HtmlHelp::~HtmlHelp()
276 {
277   if (m_fromUtf8!=(void *)(-1))   portable_iconv_close(m_fromUtf8);
278 }
279 #if 0
280 /*! return a reference to the one and only instance of this class. 
281  */
282 HtmlHelp *HtmlHelp::getInstance()
283 {
284   if (theInstance==0) theInstance = new HtmlHelp;
285   return theInstance;
286 }
287 #endif
288
289 static QDict<QCString> s_languageDict;
290
291 /*! This will create a contents file (index.hhc) and a index file (index.hhk)
292  *  and write the header of those files. 
293  *  It also creates a project file (index.hhp)
294  *  \sa finalize()
295  */
296 void HtmlHelp::initialize()
297 {
298   const char *str = Config_getString("CHM_INDEX_ENCODING");
299   if (!str) str = "CP1250"; // use safe and likely default
300   m_fromUtf8 = portable_iconv_open(str,"UTF-8"); 
301   if (m_fromUtf8==(void *)(-1))
302   {
303     err("Error: unsupported character conversion for CHM_INDEX_ENCODING: '%s'->'UTF-8'\n", str);
304     exit(1);
305   }
306
307   /* open the contents file */
308   QCString fName = Config_getString("HTML_OUTPUT") + "/index.hhc";
309   cf = new QFile(fName);
310   if (!cf->open(IO_WriteOnly))
311   {
312     err("Could not open file %s for writing\n",fName.data());
313     exit(1);
314   }
315   /* Write the header of the contents file */
316   cts.setDevice(cf);
317   cts << "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
318          "<HTML><HEAD></HEAD><BODY>\n"
319          "<OBJECT type=\"text/site properties\">\n"
320          "<param name=\"FrameName\" value=\"right\">\n"
321          "</OBJECT>\n"
322          "<UL>\n";
323   
324   /* open the contents file */
325   fName = Config_getString("HTML_OUTPUT") + "/index.hhk";
326   kf = new QFile(fName);
327   if (!kf->open(IO_WriteOnly))
328   {
329     err("Could not open file %s for writing\n",fName.data());
330     exit(1);
331   }
332   /* Write the header of the contents file */
333   kts.setDevice(kf);
334   kts << "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
335          "<HTML><HEAD></HEAD><BODY>\n"
336          "<OBJECT type=\"text/site properties\">\n"
337          "<param name=\"FrameName\" value=\"right\">\n"
338          "</OBJECT>\n"
339          "<UL>\n";
340
341   /* language codes for Html help
342      0x405 Czech
343      0x406 Danish
344      0x413 Dutch
345      0xC09 English (Australia)
346      0x809 English (Britain)
347      0x1009 English (Canada)
348      0x1809 English (Ireland)
349      0x1409 English (New Zealand)
350      0x1C09 English (South Africa)
351      0x409 English (United States)
352      0x40B Finnish
353      0x40C French
354      0x407 German
355      0x408 Greece
356      0x40E Hungarian
357      0x410 Italian
358      0x814 Norwegian
359      0x415 Polish
360      0x816 Portuguese(Portugal)
361      0x416 Portuguese(Brazil)
362      0x419 Russian
363      0x80A Spanish(Mexico)
364      0xC0A Spanish(Modern Sort)
365      0x40A Spanish(Traditional Sort)
366      0x41D Swedish
367      0x41F Turkey
368      0x411 Japanese
369      0x412 Korean
370      0x804 Chinese (PRC)
371      0x404 Chinese (Taiwan)
372
373      New LCIDs:
374          0x421 Indonesian
375          0x41A Croatian
376          0x418 Romanian
377          0x424 Slovenian
378          0x41B Slovak
379          0x422 Ukrainian
380          0x81A Serbian (Serbia, Latin)
381          0x403 Catalan
382          0x427 Lithuanian
383          0x436 Afrikaans
384          0x42A Vietnamese
385          0x429 Persian (Iran)
386          0xC01 Arabic (Egypt) - I don't know which version of arabic is used inside translator_ar.h ,
387      so I have chosen Egypt at random
388
389   */
390   s_languageDict.setAutoDelete(TRUE);
391   s_languageDict.clear();
392   s_languageDict.insert("czech",       new QCString("0x405 Czech"));
393   s_languageDict.insert("danish",      new QCString("0x406 Danish"));
394   s_languageDict.insert("dutch",       new QCString("0x413 Dutch"));
395   s_languageDict.insert("finnish",     new QCString("0x40B Finnish"));
396   s_languageDict.insert("french",      new QCString("0x40C French"));
397   s_languageDict.insert("german",      new QCString("0x407 German"));
398   s_languageDict.insert("greek",       new QCString("0x408 Greece"));
399   s_languageDict.insert("hungarian",   new QCString("0x40E Hungarian"));
400   s_languageDict.insert("italian",     new QCString("0x410 Italian"));
401   s_languageDict.insert("norwegian",   new QCString("0x814 Norwegian"));
402   s_languageDict.insert("polish",      new QCString("0x415 Polish"));
403   s_languageDict.insert("portuguese",  new QCString("0x816 Portuguese(Portugal)"));
404   s_languageDict.insert("brazil",      new QCString("0x416 Portuguese(Brazil)"));
405   s_languageDict.insert("russian",     new QCString("0x419 Russian"));
406   s_languageDict.insert("spanish",     new QCString("0x40A Spanish(Traditional Sort)"));
407   s_languageDict.insert("swedish",     new QCString("0x41D Swedish"));
408   s_languageDict.insert("turkish",     new QCString("0x41F Turkey"));
409   s_languageDict.insert("japanese",    new QCString("0x411 Japanese"));
410   s_languageDict.insert("japanese-en", new QCString("0x411 Japanese"));
411   s_languageDict.insert("korean",      new QCString("0x412 Korean"));
412   s_languageDict.insert("korean-en",   new QCString("0x412 Korean"));
413   s_languageDict.insert("chinese",     new QCString("0x804 Chinese (PRC)"));
414   s_languageDict.insert("chinese-traditional", new QCString("0x404 Chinese (Taiwan)"));
415
416   // new LCIDs
417   s_languageDict.insert("indonesian",  new QCString("0x412 Indonesian"));
418   s_languageDict.insert("croatian",    new QCString("0x41A Croatian"));
419   s_languageDict.insert("romanian",    new QCString("0x418 Romanian"));
420   s_languageDict.insert("slovene",     new QCString("0x424 Slovenian"));
421   s_languageDict.insert("slovak",      new QCString("0x41B Slovak"));
422   s_languageDict.insert("ukrainian",   new QCString("0x422 Ukrainian"));
423   s_languageDict.insert("serbian",     new QCString("0x81A Serbian (Serbia, Latin)"));
424   s_languageDict.insert("catalan",     new QCString("0x403 Catalan"));
425   s_languageDict.insert("lithuanian",  new QCString("0x427 Lithuanian"));
426   s_languageDict.insert("afrikaans",   new QCString("0x436 Afrikaans"));
427   s_languageDict.insert("vietnamese",  new QCString("0x42A Vietnamese"));
428   s_languageDict.insert("persian",     new QCString("0x429 Persian (Iran)"));
429   s_languageDict.insert("arabic",      new QCString("0xC01 Arabic (Egypt)"));
430 }
431
432
433 static QCString getLanguageString()
434 {
435   if (!theTranslator->idLanguage().isEmpty())
436   {
437     QCString *s = s_languageDict[theTranslator->idLanguage()];
438     if (s)
439     {
440       return *s;
441     }
442   }
443   // default language
444   return "0x409 English (United States)";
445 }
446   
447
448
449 void HtmlHelp::createProjectFile()
450 {
451   /* Write the project file */
452   QCString fName = Config_getString("HTML_OUTPUT") + "/index.hhp";
453   QFile f(fName);
454   if (f.open(IO_WriteOnly))
455   {
456     FTextStream t(&f);
457     
458     QCString indexName="index"+Doxygen::htmlFileExtension;
459     //if (Config_getBool("GENERATE_TREEVIEW")) indexName="main"+Doxygen::htmlFileExtension;
460     t << "[OPTIONS]\n";
461     if (!Config_getString("CHM_FILE").isEmpty())
462     {
463       t << "Compiled file=" << Config_getString("CHM_FILE") << "\n";
464     }
465     t << "Compatibility=1.1\n"
466          "Full-text search=Yes\n"
467          "Contents file=index.hhc\n"
468          "Default Window=main\n"
469          "Default topic=" << indexName << "\n"
470          "Index file=index.hhk\n"
471          "Language=" << getLanguageString() << endl;
472     if (Config_getBool("BINARY_TOC")) t << "Binary TOC=YES\n";
473     if (Config_getBool("GENERATE_CHI")) t << "Create CHI file=YES\n";
474     t << "Title=" << recode(Config_getString("PROJECT_NAME")) << endl << endl;
475     
476     t << "[WINDOWS]" << endl;
477
478     // NOTE: the 0x10387e number is a set of bits specifying the buttons
479     //       which should appear in the CHM viewer; that specific value
480     //       means "show all buttons including the font-size one";
481     //       the font-size one is not normally settable by the HTML Help Workshop
482     //       utility but the way to set it is described here:
483     //          http://support.microsoft.com/?scid=kb%3Ben-us%3B240062&x=17&y=18
484     t << "main=\"" << recode(Config_getString("PROJECT_NAME")) << "\",\"index.hhc\","
485          "\"index.hhk\",\"" << indexName << "\",\"" << 
486          indexName << "\",,,,,0x23520,,0x10387e,,,,,,,,0" << endl << endl;
487     
488     t << "[FILES]" << endl;
489     char *s = indexFiles.first();
490     while (s)
491     {
492       t << s << endl;
493       s = indexFiles.next();
494     }
495 #if 0
496     // items not found by the html help compiler scan.
497     t << "tabs.css" << endl;
498     t << "tab_a.png" << endl;
499     t << "tab_b.png" << endl;
500     t << "tab_h.png" << endl;
501     t << "tab_s.png" << endl;
502     t << "nav_h.png" << endl;
503     t << "nav_f.png" << endl;
504     t << "bc_s.png" << endl;
505     if (Config_getBool("HTML_DYNAMIC_SECTIONS"))
506     {
507       t << "open.png" << endl;
508       t << "closed.png" << endl;
509     }
510     if (Config_getBool("GENERATE_HTMLHELP"))
511     {
512       t << "ftv2blank.png" << endl;
513       t << "ftv2doc.png" << endl;
514       t << "ftv2folderclosed.png" << endl;
515       t << "ftv2folderopen.png" << endl;
516       t << "ftv2lastnode.png" << endl;
517       t << "ftv2link.png" << endl;
518       t << "ftv2mlastnode.png" << endl;
519       t << "ftv2mnode.png" << endl;
520       t << "ftv2node.png" << endl;
521       t << "ftv2plastnode.png" << endl;
522       t << "ftv2pnode.png" << endl;
523       t << "ftv2vertline.png" << endl;
524     }
525     if (Config_getBool("SEARCHENGINE"))
526     {
527       t << "search_l.png" << endl;
528       t << "search_m.png" << endl;
529       t << "search_r.png" << endl;
530       if (Config_getBool("SERVER_BASED_SEARCH"))
531       {
532         t << "mag.png" << endl;
533       }
534       else
535       {
536         t << "mag_sel.png" << endl;
537         t << "close.png" << endl;
538       }
539     }
540 #endif
541     uint i;
542     for (i=0;i<imageFiles.count();i++)
543     {
544       t << imageFiles.at(i) << endl;
545     }
546     f.close();
547   }
548   else
549   {
550     err("Could not open file %s for writing\n",fName.data());
551   }
552 }
553
554 void HtmlHelp::addIndexFile(const char *s)
555 {
556   if (indexFileDict.find(s)==0)
557   {
558     indexFiles.append(s);
559     indexFileDict.insert(s,(void *)0x8);
560   }
561 }
562
563 /*! Finalizes the HTML help. This will finish and close the
564  *  contents file (index.hhc) and the index file (index.hhk).
565  *  \sa initialize()
566  */
567 void HtmlHelp::finalize()
568 {
569   // end the contents file
570   cts << "</UL>\n";
571   cts << "</BODY>\n";
572   cts << "</HTML>\n";
573   cts.unsetDevice();
574   cf->close();
575   delete cf;
576   
577   index->writeFields(kts);
578   
579   // end the index file
580   kts << "</UL>\n";
581   kts << "</BODY>\n";
582   kts << "</HTML>\n";
583   kts.unsetDevice();
584   kf->close();
585   delete kf;
586
587   createProjectFile();
588   s_languageDict.clear();
589 }
590
591 /*! Increase the level of the contents hierarchy. 
592  *  This will start a new unnumbered HTML list in contents file.
593  *  \sa decContentsDepth()
594  */
595 void HtmlHelp::incContentsDepth()
596 {
597   int i; for (i=0;i<dc+1;i++) cts << "  ";
598   cts << "<UL>\n";
599   ++dc;
600 }
601
602 /*! Decrease the level of the contents hierarchy.
603  *  This will end the unnumber HTML list.
604  *  \sa incContentsDepth()
605  */
606 void HtmlHelp::decContentsDepth()
607 {
608   int i; for (i=0;i<dc;i++) cts << "  ";
609   cts << "</UL>\n";
610   --dc;
611 }
612
613 QCString HtmlHelp::recode(const QCString &s) 
614 {
615   int iSize        = s.length();
616   int oSize        = iSize*4+1;
617   QCString output(oSize);
618   size_t iLeft     = iSize;
619   size_t oLeft     = oSize;
620   char *iPtr       = s.data();
621   char *oPtr       = output.data();
622   if (!portable_iconv(m_fromUtf8,&iPtr,&iLeft,&oPtr,&oLeft))
623   {
624     oSize -= oLeft;
625     output.resize(oSize+1);
626     output.at(oSize)='\0';
627     return output;
628   }
629   else
630   {
631     return s;
632   }
633 }
634
635 /*! Add an list item to the contents file.
636  *  \param isDir boolean indicating if this is a dir or file entry
637  *  \param name the name of the item.
638  *  \param ref  the URL of to the item.
639  *  \param file the file in which the item is defined.
640  *  \param anchor the anchor of the item.
641  *  \param separateIndex not used.
642  *  \param addToNavIndex not used.
643  *  \param def not used.
644  */
645 void HtmlHelp::addContentsItem(bool isDir,
646                                const char *name,
647                                const char * /*ref*/, 
648                                const char *file,
649                                const char *anchor,
650                                bool /* separateIndex */,
651                                bool /* addToNavIndex */,
652                                Definition * /* def */)
653 {
654   // If we're using a binary toc then folders cannot have links. 
655   if(Config_getBool("BINARY_TOC") && isDir) 
656   {
657     file = 0;
658     anchor = 0;
659   }
660   int i; for (i=0;i<dc;i++) cts << "  ";
661   cts << "<LI><OBJECT type=\"text/sitemap\">";
662   cts << "<param name=\"Name\" value=\"" << recode(name) << "\">";
663   if (file)      // made file optional param - KPW
664   {
665     if (file && (file[0]=='!' || file[0]=='^')) // special markers for user defined URLs
666     {
667       cts << "<param name=\"";
668       if (file[0]=='^') cts << "URL"; else cts << "Local";
669       cts << "\" value=\"";
670       cts << &file[1];
671     }
672     else
673     {
674       cts << "<param name=\"Local\" value=\"";
675       cts << file << Doxygen::htmlFileExtension;
676       if (anchor) cts << "#" << anchor;  
677     }
678     cts << "\">";
679   }
680   cts << "<param name=\"ImageNumber\" value=\"";
681   if (isDir)  // added - KPW
682   {
683     cts << (int)BOOK_CLOSED ;
684   }
685   else
686   {
687     cts << (int)TEXT;
688   }
689   cts << "\">";
690   cts << "</OBJECT>\n";
691 }
692
693
694 void HtmlHelp::addIndexItem(Definition *context,MemberDef *md,
695                             const char *word)
696 {
697   if (md)
698   {
699     static bool separateMemberPages = Config_getBool("SEPARATE_MEMBER_PAGES");
700     if (context==0) // global member
701     {
702       if (md->getGroupDef())
703         context = md->getGroupDef();
704       else if (md->getFileDef())
705         context = md->getFileDef();
706     }
707     if (context==0) return; // should not happen
708
709     QCString cfname  = md->getOutputFileBase();
710     QCString cfiname = context->getOutputFileBase();
711     QCString level1  = context->name();
712     QCString level2  = md->name();
713     QCString contRef = separateMemberPages ? cfname : cfiname;
714     QCString memRef  = cfname;
715     QCString anchor  = md->anchor();
716     index->addItem(level1,level2,contRef,anchor,TRUE,FALSE);
717     index->addItem(level2,level1,memRef,anchor,TRUE,TRUE);
718   }
719   else if (context)
720   {
721     QCString level1  = word ? QCString(word) : context->name();
722     index->addItem(level1,0,context->getOutputFileBase(),0,TRUE,FALSE);
723   }
724 }
725
726 void HtmlHelp::addImageFile(const char *fileName)
727 {
728   imageFiles.append(fileName);
729 }
730