fe6609eadb855ecbb5dcb0615eb762bd67a738ef
[platform/upstream/doxygen.git] / addon / doxywizard / expert.cpp
1 #include <QtGui>
2 #include <QtXml>
3 #include "expert.h"
4 #include "inputbool.h"
5 #include "inputstring.h"
6 #include "inputint.h"
7 #include "inputstring.h"
8 #include "inputstrlist.h"
9 #include "config.h"
10 #include "version.h"
11 #include "configdoc.h"
12 #include "settings.h"
13
14 #define SA(x) QString::fromAscii(x)
15
16 static QString convertToComment(const QString &s)
17 {
18   if (s.isEmpty()) 
19   {
20     return QString();
21   }
22   else
23   {
24     return SA("# ")+
25            s.trimmed().replace(SA("\n"),SA("\n# ")).replace(SA("# \n"), SA("#\n"))+
26            SA("\n");
27   }
28 }
29
30 void Expert::setHeader(const char *header)
31 {
32   m_header = SA(header);
33 }
34
35 void Expert::add(const char *name,const char *docs)
36 {
37   Input *opt = m_options[SA(name)];
38   if (opt)
39   {
40     opt->setTemplateDocs(SA(docs));
41   }
42 }
43
44 //------------------------------------------------------------------------------------
45
46 Expert::Expert()
47 {
48   m_treeWidget = new QTreeWidget;
49   m_treeWidget->setColumnCount(1);
50   m_topicStack = new QStackedWidget;
51   m_inShowHelp = FALSE;
52
53   QFile file(SA(":/config.xml"));
54   QString err;
55   int errLine,errCol;
56   QDomDocument configXml;
57   if (file.open(QIODevice::ReadOnly))
58   {
59     if (!configXml.setContent(&file,false,&err,&errLine,&errCol))
60     {
61       QString msg = tr("Error parsing internal config.xml at line %1 column %2.\n%3").
62                   arg(errLine).arg(errCol).arg(err);
63       QMessageBox::warning(this, tr("Error"), msg);
64       exit(1);
65     }
66   }
67   m_rootElement = configXml.documentElement();
68
69   createTopics(m_rootElement);
70   m_helper = new QTextBrowser;
71   m_helper->setReadOnly(true);
72   m_helper->setOpenExternalLinks(TRUE);
73   m_splitter = new QSplitter(Qt::Vertical);
74   m_splitter->addWidget(m_treeWidget);
75   m_splitter->addWidget(m_helper);
76
77   QWidget *rightSide = new QWidget;
78   QGridLayout *grid = new QGridLayout(rightSide);
79   m_prev = new QPushButton(tr("Previous"));
80   m_prev->setEnabled(false);
81   m_next = new QPushButton(tr("Next"));
82   grid->addWidget(m_topicStack,0,0,1,2);
83   grid->addWidget(m_prev,1,0,Qt::AlignLeft);
84   grid->addWidget(m_next,1,1,Qt::AlignRight);
85   grid->setColumnStretch(0,1);
86   grid->setRowStretch(0,1);
87
88   addWidget(m_splitter);
89   addWidget(rightSide);
90   connect(m_next,SIGNAL(clicked()),SLOT(nextTopic()));
91
92   connect(m_prev,SIGNAL(clicked()),SLOT(prevTopic()));
93
94   addConfigDocs(this);
95 }
96
97 Expert::~Expert()
98 {
99   QHashIterator<QString,Input*> i(m_options);
100   while (i.hasNext()) 
101   {
102     i.next();
103     delete i.value();
104   }
105 }
106
107 void Expert::createTopics(const QDomElement &rootElem)
108 {
109   QList<QTreeWidgetItem*> items;
110   QDomElement childElem = rootElem.firstChildElement();
111   while (!childElem.isNull())
112   {
113     if (childElem.tagName()==SA("group"))
114     {
115       // Remove _ from a group name like: Source_Browser
116       QString name = childElem.attribute(SA("name")).replace(SA("_"),SA(" "));
117       items.append(new QTreeWidgetItem((QTreeWidget*)0,QStringList(name)));
118       QWidget *widget = createTopicWidget(childElem);
119       m_topics[name] = widget;
120       m_topicStack->addWidget(widget);
121     }
122     childElem = childElem.nextSiblingElement();
123   }
124   m_treeWidget->setHeaderLabels(QStringList() << SA("Topics"));
125   m_treeWidget->insertTopLevelItems(0,items);
126   connect(m_treeWidget,
127           SIGNAL(currentItemChanged(QTreeWidgetItem *,QTreeWidgetItem *)),
128           this,
129           SLOT(activateTopic(QTreeWidgetItem *,QTreeWidgetItem *)));
130 }
131
132 static QString getDocsForNode(const QDomElement &child)
133 {
134   QString type = child.attribute(SA("type"));
135   QString docs = SA("");
136   // read documentation text
137   QDomElement docsVal = child.firstChildElement();
138   while (!docsVal.isNull())
139   {
140     if (docsVal.tagName()==SA("docs") &&
141         docsVal.attribute(SA("doxywizard")) != SA("0"))
142     {
143       for (QDomNode n = docsVal.firstChild(); !n.isNull(); n = n.nextSibling())
144       {
145         QDomText t = n.toText();
146         if (!t.isNull()) docs+=t.data();
147       }
148       docs += SA("<br/>");
149     }
150     docsVal = docsVal.nextSiblingElement();
151   }
152
153   // for an enum we list the values
154   if (type==SA("enum"))
155   {
156     docs += SA("<br/>");
157     docs += SA("Possible values are: ");
158     int numValues=0;
159     docsVal = child.firstChildElement();
160     while (!docsVal.isNull())
161     {
162       if (docsVal.tagName()==SA("value"))
163       {
164         numValues++;
165       }
166       docsVal = docsVal.nextSiblingElement();
167     }
168     int i=0;
169     docsVal = child.firstChildElement();
170     while (!docsVal.isNull())
171     {
172       if (docsVal.tagName()==SA("value"))
173       {
174         i++;
175         docs += SA("<code>") + docsVal.attribute(SA("name")) + SA("</code>");
176         QString desc = docsVal.attribute(SA("desc"));
177         if (!desc.isEmpty())
178         {
179           docs+= SA(" ")+desc;
180         }
181         if (i==numValues-1)
182         {
183           docs+=SA(" and ");
184         }
185         else if (i==numValues)
186         {
187           docs+=SA(".");
188         }
189         else
190         {
191           docs+=SA(", ");
192         }
193       }
194       docsVal = docsVal.nextSiblingElement();
195     }
196     docs+=SA("<br/>");
197     docs+=SA("<br/>");
198     docs+=SA(" The default value is: <code>")+
199           child.attribute(SA("defval"))+
200           SA("</code>.");
201     docs+= SA("<br/>");
202   }
203   else if (type==SA("int"))
204   {
205     docs+=SA("<br/>");
206     docs+=SA("Minimum value: ")+child.attribute(SA("minval"))+SA(", ");
207     docs+=SA("maximum value: ")+child.attribute(SA("maxval"))+SA(", ");
208     docs+=SA("default value: ")+child.attribute(SA("defval"))+SA(".");
209     docs+= SA("<br/>");
210   }
211   else if (type==SA("bool"))
212   {
213     docs+=SA("<br/>");
214     if (child.hasAttribute(SA("altdefval")))
215     {
216       docs+=SA(" The default value is: system dependent.");
217     }
218     else
219     {
220       QString defval = child.attribute(SA("defval"));
221       docs+=SA(" The default value is: <code>")+
222             (defval==SA("1")?SA("YES"):SA("NO"))+
223             SA("</code>.");
224     }
225     docs+= SA("<br/>");
226   }
227   else if (type==SA("list"))
228   {
229     if (child.attribute(SA("format"))==SA("string"))
230     {
231       int numValues = 0;
232       docsVal = child.firstChildElement();
233       while (!docsVal.isNull())
234       {
235         if (docsVal.tagName()==SA("value"))
236         {
237           QString showDocu = SA("");
238           if (docsVal.hasAttribute(SA("show_docu")))
239           {
240             showDocu = docsVal.attribute(SA("show_docu")).toLower();
241           }
242           if ((showDocu != SA("no")) && (docsVal.attribute(SA("name"))!=SA(""))) numValues++;
243         }
244         docsVal = docsVal.nextSiblingElement();
245       }
246       if (numValues>0)
247       {
248         int i = 0;
249         docsVal = child.firstChildElement();
250         while (!docsVal.isNull())
251         {
252           if (docsVal.tagName()==SA("value"))
253           {
254             QString showDocu = SA("");
255             if (docsVal.hasAttribute(SA("show_docu")))
256             {
257               showDocu = docsVal.attribute(SA("show_docu")).toLower();
258             }
259             if ((showDocu != SA("no")) && (docsVal.attribute(SA("name"))!=SA("")))
260             {
261               i++;
262               docs += SA("<code>") + docsVal.attribute(SA("name")) + SA("</code>");
263               QString desc = docsVal.attribute(SA("desc"));
264               if (desc != SA(""))
265               {
266                 docs += SA(" ") + desc;
267               }
268               if (i==numValues-1)
269               {
270                 docs += SA(" and ");
271               }
272               else if (i==numValues)
273               {
274                 docs += SA(".");
275               }
276               else
277               {
278                 docs += SA(", ");
279               }
280             }
281           }
282           docsVal = docsVal.nextSiblingElement();
283         }
284       }
285       // docs+= SA("<br/>");
286     }
287   }
288   else if (type==SA("string"))
289   {
290     QString defval = child.attribute(SA("defval"));
291     if (child.attribute(SA("format")) == SA("dir"))
292     {
293       if (defval != SA(""))
294       {
295         docs+=SA("<br/>");
296         docs += SA(" The default directory is: <code>") + defval + SA("</code>.");
297         docs += SA("<br/>");
298       }
299     }
300     else if (child.attribute(SA("format")) == SA("file"))
301     {
302       QString abspath = child.attribute(SA("abspath"));
303       if (defval != SA(""))
304       {
305         docs+=SA("<br/>");
306         if (abspath != SA("1"))
307         {
308           docs += SA(" The default file is: <code>") + defval + SA("</code>.");
309         }
310         else
311         {
312           docs += SA(" The default file (with absolute path) is: <code>") + defval + SA("</code>.");
313         }
314         docs += SA("<br/>");
315       }
316       else
317       {
318         if (abspath == SA("1"))
319         {
320           docs+=SA("<br/>");
321           docs += SA(" The file has to be specified with full path.");
322           docs += SA("<br/>");
323         }
324       }
325     }
326     else if (child.attribute(SA("format")) == SA("image"))
327     {
328       QString abspath = child.attribute(SA("abspath"));
329       if (defval != SA(""))
330       {
331         docs+=SA("<br/>");
332         if (abspath != SA("1"))
333         {
334           docs += SA(" The default image is: <code>") + defval + SA("</code>.");
335         }
336         else
337         {
338           docs += SA(" The default image (with absolute path) is: <code>") + defval + SA("</code>.");
339         }
340         docs += SA("<br/>");
341       }
342       else
343       {
344         if (abspath == SA("1"))
345         {
346           docs+=SA("<br/>");
347           docs += SA(" The image has to be specified with full path.");
348           docs += SA("<br/>");
349         }
350       }
351     }
352     else // if (child.attribute(SA("format")) == SA("string"))
353     {
354       if (defval != SA(""))
355       {
356         docs+=SA("<br/>");
357         docs += SA(" The default value is: <code>") + defval + SA("</code>.");
358         docs += SA("<br/>");
359       }
360     }
361   }
362   
363   if (child.hasAttribute(SA("depends")))
364   {
365     QString dependsOn = child.attribute(SA("depends"));
366     docs+=SA("<br/>");
367     docs+=  SA(" This tag requires that the tag \\ref cfg_");
368     docs+=  dependsOn.toLower();
369     docs+=  SA(" \"");
370     docs+=  dependsOn.toUpper();
371     docs+=  SA("\" is set to <code>YES</code>.");
372   }
373
374   // Remove / replace doxygen markup strings
375   // the regular expressions are hard to read so the intention will be given
376   QRegExp regexp;
377   // remove \n at end and replace by a space
378   regexp.setPattern(SA("\\n$"));
379   docs.replace(regexp,SA(" "));
380   // remove <br> at end
381   regexp.setPattern(SA("<br> *$"));
382   docs.replace(regexp,SA(" "));
383   // \c word -> <code>word</code>; word ends with ')', ',', '.' or ' '
384   regexp.setPattern(SA("\\\\c[ ]+([^ \\)]+)\\)"));
385   docs.replace(regexp,SA("<code>\\1</code>)"));
386
387   regexp.setPattern(SA("\\\\c[ ]+([^ ,]+),"));
388   docs.replace(regexp,SA("<code>\\1</code>,"));
389
390   regexp.setPattern(SA("\\\\c[ ]+([^ \\.]+)\\."));
391   docs.replace(regexp,SA("<code>\\1</code>."));
392
393   regexp.setPattern(SA("\\\\c[ ]+([^ ]+) "));
394   docs.replace(regexp,SA("<code>\\1</code> "));
395   // `word` -> <code>word</code>
396   docs.replace(SA("``"),SA(""));
397   regexp.setPattern(SA("`([^`]+)`"));
398   docs.replace(regexp,SA("<code>\\1</code>"));
399   // \ref key "desc" -> <code>desc</code>
400   regexp.setPattern(SA("\\\\ref[ ]+[^ ]+[ ]+\"([^ ]+)\""));
401   docs.replace(regexp,SA("<code>\\1</code> "));
402   //\ref specials
403   // \ref <key> -> description
404   regexp.setPattern(SA("\\\\ref[ ]+doxygen_usage"));
405   docs.replace(regexp,SA("\"Doxygen usage\""));
406   regexp.setPattern(SA("\\\\ref[ ]+extsearch"));
407   docs.replace(regexp,SA("\"External Indexing and Searching\""));
408   regexp.setPattern(SA("\\\\ref[ ]+external"));
409   docs.replace(regexp,SA("\"Linking to external documentation\""));
410   // fallback for not handled
411   docs.replace(SA("\\\\ref"),SA(""));
412   // \b word -> <b>word<\b>
413   regexp.setPattern(SA("\\\\b[ ]+([^ ]+) "));
414   docs.replace(regexp,SA("<b>\\1</b> "));
415   // \e word -> <em>word<\em>
416   regexp.setPattern(SA("\\\\e[ ]+([^ ]+) "));
417   docs.replace(regexp,SA("<em>\\1</em> "));
418   // \note -> <br>Note:
419   // @note -> <br>Note:
420   docs.replace(SA("\\note"),SA("<br>Note:"));
421   docs.replace(SA("@note"),SA("<br>Note:"));
422   // \#include -> #include
423   // \#undef -> #undef
424   docs.replace(SA("\\#include"),SA("#include"));
425   docs.replace(SA("\\#undef"),SA("#undef"));
426   // -# -> <br>-
427   // " - " -> <br>-
428   docs.replace(SA("-#"),SA("<br>-"));
429   docs.replace(SA(" - "),SA("<br>-"));
430   // \verbatim -> <pre>
431   // \endverbatim -> </pre>
432   docs.replace(SA("\\verbatim"),SA("<pre>"));
433   docs.replace(SA("\\endverbatim"),SA("</pre>"));
434   // \sa -> <br>See also:
435   // \par -> <br>
436   docs.replace(SA("\\sa"),SA("<br>See also:"));
437   docs.replace(SA("\\par"),SA("<br>"));
438   // 2xbackslash -> backslash
439   // \@ -> @
440   docs.replace(SA("\\\\"),SA("\\"));
441   docs.replace(SA("\\@"),SA("@"));
442   // \& -> &
443   // \$ -> $
444   docs.replace(SA("\\&"),SA("&"));
445   docs.replace(SA("\\$"),SA("$"));
446   // \< -> &lt;
447   // \> -> &gt;
448   docs.replace(SA("\\<"),SA("&lt;"));
449   docs.replace(SA("\\>"),SA("&gt;"));
450   regexp.setPattern(SA(" (http:[^ \\)]*)([ \\)])"));
451   docs.replace(regexp,SA(" <a href=\"\\1\">\\1</a>\\2"));
452   // LaTeX name as formula -> LaTeX
453   regexp.setPattern(SA("\\\\f\\$\\\\mbox\\{\\\\LaTeX\\}\\\\f\\$"));
454   docs.replace(regexp,SA("LaTeX"));
455   // Other forula's (now just 2) so explicitely mentioned.
456   regexp.setPattern(SA("\\\\f\\$2\\^\\{\\(16\\+\\\\mbox\\{LOOKUP\\\\_CACHE\\\\_SIZE\\}\\)\\}\\\\f\\$"));
457   docs.replace(regexp,SA("2^(16+LOOKUP_CACHE_SIZE)"));
458   regexp.setPattern(SA("\\\\f\\$2\\^\\{16\\} = 65536\\\\f\\$"));
459   docs.replace(regexp,SA("2^16=65536"));
460
461   return docs.trimmed();
462 }
463
464 QWidget *Expert::createTopicWidget(QDomElement &elem)
465 {
466   QScrollArea *area   = new QScrollArea;
467   QWidget     *topic  = new QWidget;
468   QGridLayout *layout = new QGridLayout(topic);
469   QDomElement child   = elem.firstChildElement();
470   int row=0;
471   while (!child.isNull())
472   {
473     QString setting = child.attribute(SA("setting"));
474     if (setting.isEmpty() || IS_SUPPORTED(setting.toAscii()))
475     {
476       QString type = child.attribute(SA("type"));
477       QString docs = getDocsForNode(child);
478       if (type==SA("bool"))
479       {
480         InputBool *boolOption = 
481           new InputBool(
482               layout,row,
483               child.attribute(SA("id")),
484               child.attribute(SA("defval"))==SA("1"),
485               docs
486               );
487         m_options.insert(
488             child.attribute(SA("id")),
489             boolOption
490             );
491         connect(boolOption,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*)));
492         connect(boolOption,SIGNAL(changed()),SIGNAL(changed()));
493       }
494       else if (type==SA("string"))
495       {
496         InputString::StringMode mode;
497         QString format = child.attribute(SA("format"));
498         if (format==SA("dir"))
499         {
500           mode = InputString::StringDir;
501         }
502         else if (format==SA("file"))
503         {
504           mode = InputString::StringFile;
505         }
506         else if (format==SA("image"))
507         {
508           mode = InputString::StringImage;
509         }
510         else // format=="string"
511         {
512           mode = InputString::StringFree;
513         }
514         InputString *stringOption = 
515           new InputString(
516               layout,row,
517               child.attribute(SA("id")),
518               child.attribute(SA("defval")),
519               mode,
520               docs,
521               child.attribute(SA("abspath"))
522               );
523         m_options.insert(
524             child.attribute(SA("id")),
525             stringOption
526             );
527         connect(stringOption,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*)));
528         connect(stringOption,SIGNAL(changed()),SIGNAL(changed()));
529       }
530       else if (type==SA("enum"))
531       {
532         InputString *enumList = new InputString(
533             layout,row,
534             child.attribute(SA("id")),
535             child.attribute(SA("defval")),
536             InputString::StringFixed,
537             docs
538             );
539         QDomElement enumVal = child.firstChildElement();
540         while (!enumVal.isNull())
541         {
542           if (enumVal.tagName()==SA("value"))
543           {
544             enumList->addValue(enumVal.attribute(SA("name")));
545           }
546           enumVal = enumVal.nextSiblingElement();
547         }
548         enumList->setDefault();
549
550         m_options.insert(child.attribute(SA("id")),enumList);
551         connect(enumList,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*)));
552         connect(enumList,SIGNAL(changed()),SIGNAL(changed()));
553       }
554       else if (type==SA("int"))
555       {
556         InputInt *intOption = 
557           new InputInt(
558               layout,row,
559               child.attribute(SA("id")),
560               child.attribute(SA("defval")).toInt(),
561               child.attribute(SA("minval")).toInt(),
562               child.attribute(SA("maxval")).toInt(),
563               docs
564               );
565         m_options.insert(
566             child.attribute(SA("id")),
567             intOption
568             );
569         connect(intOption,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*)));
570         connect(intOption,SIGNAL(changed()),SIGNAL(changed()));
571       }
572       else if (type==SA("list"))
573       {
574         InputStrList::ListMode mode;
575         QString format = child.attribute(SA("format"));
576         if (format==SA("dir"))
577         {
578           mode = InputStrList::ListDir;
579         }
580         else if (format==SA("file"))
581         {
582           mode = InputStrList::ListFile;
583         }
584         else if (format==SA("filedir"))
585         {
586           mode = InputStrList::ListFileDir;
587         }
588         else // format=="string"
589         {
590           mode = InputStrList::ListString;
591         }
592         QStringList sl;
593         QDomElement listVal = child.firstChildElement();
594         while (!listVal.isNull())
595         {
596           if (listVal.tagName()==SA("value"))
597           {
598             sl.append(listVal.attribute(SA("name")));
599           }
600           listVal = listVal.nextSiblingElement();
601         }
602         InputStrList *listOption = 
603           new InputStrList(
604               layout,row,
605               child.attribute(SA("id")),
606               sl,
607               mode,
608               docs
609               );
610         m_options.insert(
611             child.attribute(SA("id")),
612             listOption
613             );
614         connect(listOption,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*)));
615         connect(listOption,SIGNAL(changed()),SIGNAL(changed()));
616       }
617       else if (type==SA("obsolete"))
618       {
619         // ignore
620       }
621       else // should not happen
622       {
623         printf("Unsupported type %s\n",qPrintable(child.attribute(SA("type"))));
624       }
625     } // IS_SUPPORTED
626     child = child.nextSiblingElement();
627   }
628
629   // compute dependencies between options
630   child = elem.firstChildElement();
631   while (!child.isNull())
632   {
633     QString setting = child.attribute(SA("setting"));
634     QString dependsOn = child.attribute(SA("depends"));
635     QString id        = child.attribute(SA("id"));
636     if (!dependsOn.isEmpty() && 
637         (setting.isEmpty() || IS_SUPPORTED(setting.toAscii())))
638     {
639        Input *parentOption = m_options[dependsOn];
640        if (parentOption==0)
641        {
642          printf("%s has depends=%s that is not valid\n",
643              qPrintable(id),qPrintable(dependsOn));
644        }
645        Input *thisOption   = m_options[id];
646        Q_ASSERT(parentOption);
647        Q_ASSERT(thisOption);
648        if (parentOption && thisOption)
649        {
650          //printf("Adding dependency '%s' (%p)->'%s' (%p)\n",
651          //  qPrintable(dependsOn),parentOption,
652          //  qPrintable(id),thisOption);
653          parentOption->addDependency(thisOption);
654        }
655     }
656     child = child.nextSiblingElement();
657   }
658
659   // set initial dependencies
660   QHashIterator<QString,Input*> i(m_options);
661   while (i.hasNext()) 
662   {
663     i.next();
664     if (i.value())
665     {
666       i.value()->updateDependencies();
667     }
668   }
669
670   layout->setRowStretch(row,1);
671   layout->setColumnStretch(1,2);
672   layout->setSpacing(5);
673   topic->setLayout(layout);
674   area->setWidget(topic);
675   area->setWidgetResizable(true);
676   return area;
677 }
678
679 void Expert::activateTopic(QTreeWidgetItem *item,QTreeWidgetItem *)
680 {
681   if (item)
682   {
683     QWidget *w = m_topics[item->text(0)];
684     m_topicStack->setCurrentWidget(w);
685     m_prev->setEnabled(m_topicStack->currentIndex()!=0); 
686     m_next->setEnabled(m_topicStack->currentIndex()!=m_topicStack->count()-1); 
687   }
688 }
689
690 void Expert::loadSettings(QSettings *s)
691 {
692   QHashIterator<QString,Input*> i(m_options);
693   while (i.hasNext()) 
694   {
695     i.next();
696     QVariant var = s->value(SA("config/")+i.key());
697     if (i.value())
698     {
699       //printf("Loading key %s: type=%d value='%s'\n",qPrintable(i.key()),var.type(),qPrintable(var.toString()));
700       i.value()->value() = var;
701       i.value()->update();
702     }
703   }
704 }
705
706 void Expert::saveSettings(QSettings *s)
707 {
708   QHashIterator<QString,Input*> i(m_options);
709   while (i.hasNext()) 
710   {
711     i.next();
712     //printf("Saving key %s: type=%d value='%s'\n",qPrintable(i.key()),i.value()->value().type(),qPrintable(i.value()->value().toString()));
713     if (i.value())
714     {
715       s->setValue(SA("config/")+i.key(),i.value()->value());
716     }
717   }
718 }
719
720 void Expert::loadConfig(const QString &fileName)
721 {
722   //printf("Expert::loadConfig(%s)\n",qPrintable(fileName));
723   parseConfig(fileName,m_options);
724 }
725
726 void Expert::saveTopic(QTextStream &t,QDomElement &elem,QTextCodec *codec,
727                        bool brief)
728 {
729   if (!brief)
730   {
731     t << endl;
732   }
733   t << "#---------------------------------------------------------------------------" << endl;
734   t << "# " << elem.attribute(SA("docs")) << endl;
735   t << "#---------------------------------------------------------------------------" << endl;
736   // write options...
737   QDomElement childElem = elem.firstChildElement();
738   while (!childElem.isNull())
739   {
740     QString setting = childElem.attribute(SA("setting"));
741     QString type = childElem.attribute(SA("type"));
742     QString name = childElem.attribute(SA("id"));
743     if (setting.isEmpty() || IS_SUPPORTED(setting.toAscii()))
744     {
745       QHash<QString,Input*>::const_iterator i = m_options.find(name);
746       if (i!=m_options.end())
747       {
748         Input *option = i.value();
749         if (option && !brief)
750         {
751           t << endl;
752           t << convertToComment(option->templateDocs());
753           t << endl;
754         }
755         t << name.leftJustified(MAX_OPTION_LENGTH) << "= ";
756         if (option)
757         {
758           option->writeValue(t,codec);
759         }
760         t << endl;
761       }
762     }
763     childElem = childElem.nextSiblingElement();
764   }
765 }
766
767 bool Expert::writeConfig(QTextStream &t,bool brief)
768 {
769   // write global header
770   t << "# Doxyfile " << versionString << endl << endl; 
771   if (!brief)
772   {
773     t << convertToComment(m_header);
774   }
775
776   QTextCodec *codec = 0;
777   Input *option = m_options[QString::fromAscii("DOXYFILE_ENCODING")];
778   if (option)
779   {
780     codec = QTextCodec::codecForName(option->value().toString().toAscii());
781     if (codec==0) // fallback: use UTF-8
782     {
783       codec = QTextCodec::codecForName("UTF-8");
784     }
785   }
786   QDomElement childElem = m_rootElement.firstChildElement();
787   while (!childElem.isNull())
788   {
789     if (childElem.tagName()==SA("group"))
790     {
791       saveTopic(t,childElem,codec,brief);
792     }
793     childElem = childElem.nextSiblingElement();
794   }
795   return true;
796 }
797
798 QByteArray Expert::saveInnerState () const
799 {
800   return m_splitter->saveState();
801 }
802
803 bool Expert::restoreInnerState ( const QByteArray & state )
804 {
805   return m_splitter->restoreState(state);
806 }
807
808 void Expert::showHelp(Input *option)
809 {
810   if (!m_inShowHelp)
811   {
812     m_inShowHelp = TRUE;
813     m_helper->setText(
814         QString::fromAscii("<qt><b>")+option->id()+
815         QString::fromAscii("</b><br>")+
816         QString::fromAscii("<br/>")+
817         option->docs().
818         replace(QChar::fromAscii('\n'),QChar::fromAscii(' '))+
819         QString::fromAscii("</qt>")
820         );
821     m_inShowHelp = FALSE;
822   }
823 }
824
825 void Expert::nextTopic()
826 {
827   m_topicStack->setCurrentIndex(m_topicStack->currentIndex()+1);
828   m_next->setEnabled(m_topicStack->count()!=m_topicStack->currentIndex()+1);
829   m_prev->setEnabled(m_topicStack->currentIndex()!=0);
830   m_treeWidget->setCurrentItem(m_treeWidget->invisibleRootItem()->child(m_topicStack->currentIndex()));
831 }
832
833 void Expert::prevTopic()
834 {
835   m_topicStack->setCurrentIndex(m_topicStack->currentIndex()-1);
836   m_next->setEnabled(m_topicStack->count()!=m_topicStack->currentIndex()+1);
837   m_prev->setEnabled(m_topicStack->currentIndex()!=0);
838   m_treeWidget->setCurrentItem(m_treeWidget->invisibleRootItem()->child(m_topicStack->currentIndex()));
839 }
840
841 void Expert::resetToDefaults()
842 {
843   //printf("Expert::makeDefaults()\n");
844   QHashIterator<QString,Input*> i(m_options);
845   while (i.hasNext()) 
846   {
847     i.next();
848     if (i.value())
849     {
850       i.value()->reset();
851     }
852   }
853 }
854
855 static bool stringVariantToBool(const QVariant &v)
856 {
857   QString s = v.toString().toLower();
858   return s==QString::fromAscii("yes") || s==QString::fromAscii("true") || s==QString::fromAscii("1");
859
860
861 static bool getBoolOption(
862     const QHash<QString,Input*>&model,const QString &name)
863 {
864   Input *option = model[name];
865   Q_ASSERT(option!=0);
866   return stringVariantToBool(option->value());
867
868
869 static QString getStringOption(
870     const QHash<QString,Input*>&model,const QString &name)
871 {
872   Input *option = model[name];
873   Q_ASSERT(option!=0);
874   return option->value().toString();
875 }
876
877
878 bool Expert::htmlOutputPresent(const QString &workingDir) const
879 {
880   bool generateHtml = getBoolOption(m_options,QString::fromAscii("GENERATE_HTML"));
881   if (!generateHtml || workingDir.isEmpty()) return false;
882   QString indexFile = getHtmlOutputIndex(workingDir);
883   QFileInfo fi(indexFile);
884   return fi.exists() && fi.isFile();
885 }
886
887 QString Expert::getHtmlOutputIndex(const QString &workingDir) const
888 {
889   QString outputDir = getStringOption(m_options,QString::fromAscii("OUTPUT_DIRECTORY"));
890   QString htmlOutputDir = getStringOption(m_options,QString::fromAscii("HTML_OUTPUT"));
891   //printf("outputDir=%s\n",qPrintable(outputDir));
892   //printf("htmlOutputDir=%s\n",qPrintable(htmlOutputDir));
893   QString indexFile = workingDir;
894   if (QFileInfo(outputDir).isAbsolute()) // override
895   {
896     indexFile = outputDir;
897   }
898   else // append
899   { 
900     indexFile += QString::fromAscii("/")+outputDir;
901   }
902   if (QFileInfo(htmlOutputDir).isAbsolute()) // override
903   {
904     indexFile = htmlOutputDir;
905   }
906   else // append
907   {
908     indexFile += QString::fromAscii("/")+htmlOutputDir;
909   }
910   indexFile+=QString::fromAscii("/index.html");
911   return indexFile;
912 }
913
914 bool Expert::pdfOutputPresent(const QString &workingDir) const
915 {
916   bool generateLatex = getBoolOption(m_options,QString::fromAscii("GENERATE_LATEX"));
917   bool pdfLatex = getBoolOption(m_options,QString::fromAscii("USE_PDFLATEX"));
918   if (!generateLatex || !pdfLatex) return false;
919   QString latexOutput = getStringOption(m_options,QString::fromAscii("LATEX_OUTPUT"));
920   QString indexFile;
921   if (QFileInfo(latexOutput).isAbsolute())
922   {
923     indexFile = latexOutput+QString::fromAscii("/refman.pdf");
924   }
925   else
926   {
927     indexFile = workingDir+QString::fromAscii("/")+
928                 latexOutput+QString::fromAscii("/refman.pdf");
929   }
930   QFileInfo fi(indexFile);
931   return fi.exists() && fi.isFile();
932 }
933