|
1 #include "expert.h" |
|
2 #include "inputbool.h" |
|
3 #include "inputstring.h" |
|
4 #include "inputint.h" |
|
5 #include "inputstring.h" |
|
6 #include "inputstrlist.h" |
|
7 #include <QtGui> |
|
8 #include <QtXml> |
|
9 #include "config.h" |
|
10 #include "version.h" |
|
11 |
|
12 #undef SA |
|
13 #define SA(x) QString::fromAscii(x) |
|
14 |
|
15 static QString convertToComment(const QString &s) |
|
16 { |
|
17 if (s.isEmpty()) |
|
18 { |
|
19 return QString(); |
|
20 } |
|
21 else |
|
22 { |
|
23 return SA("# ")+ |
|
24 s.trimmed().replace(SA("\n"),SA("\n# "))+ |
|
25 SA("\n"); |
|
26 } |
|
27 } |
|
28 |
|
29 //------------------------------------------------------------------------------------ |
|
30 |
|
31 Expert::Expert() |
|
32 { |
|
33 m_treeWidget = new QTreeWidget; |
|
34 m_treeWidget->setColumnCount(1); |
|
35 m_topicStack = new QStackedWidget; |
|
36 |
|
37 QFile file(SA(":/config.xml")); |
|
38 QString err; |
|
39 int errLine,errCol; |
|
40 QDomDocument configXml; |
|
41 if (file.open(QIODevice::ReadOnly)) |
|
42 { |
|
43 if (!configXml.setContent(&file,false,&err,&errLine,&errCol)) |
|
44 { |
|
45 QString msg = tr("Error parsing internal config.xml at line %1 column %2.\n%3"). |
|
46 arg(errLine).arg(errCol).arg(err); |
|
47 QMessageBox::warning(this, tr("Error"), msg); |
|
48 exit(1); |
|
49 } |
|
50 } |
|
51 m_rootElement = configXml.documentElement(); |
|
52 |
|
53 createTopics(m_rootElement); |
|
54 m_helper = new QTextEdit; |
|
55 m_helper->setReadOnly(true); |
|
56 m_splitter = new QSplitter(Qt::Vertical); |
|
57 m_splitter->addWidget(m_treeWidget); |
|
58 m_splitter->addWidget(m_helper); |
|
59 |
|
60 QWidget *rightSide = new QWidget; |
|
61 QGridLayout *grid = new QGridLayout(rightSide); |
|
62 m_prev = new QPushButton(tr("Previous")); |
|
63 m_prev->setEnabled(false); |
|
64 m_next = new QPushButton(tr("Next")); |
|
65 grid->addWidget(m_topicStack,0,0,1,2); |
|
66 grid->addWidget(m_prev,1,0,Qt::AlignLeft); |
|
67 grid->addWidget(m_next,1,1,Qt::AlignRight); |
|
68 grid->setColumnStretch(0,1); |
|
69 grid->setRowStretch(0,1); |
|
70 |
|
71 addWidget(m_splitter); |
|
72 addWidget(rightSide); |
|
73 connect(m_next,SIGNAL(clicked()),SLOT(nextTopic())); |
|
74 |
|
75 connect(m_prev,SIGNAL(clicked()),SLOT(prevTopic())); |
|
76 } |
|
77 |
|
78 Expert::~Expert() |
|
79 { |
|
80 QHashIterator<QString,Input*> i(m_options); |
|
81 while (i.hasNext()) |
|
82 { |
|
83 i.next(); |
|
84 delete i.value(); |
|
85 } |
|
86 } |
|
87 |
|
88 void Expert::createTopics(const QDomElement &rootElem) |
|
89 { |
|
90 QList<QTreeWidgetItem*> items; |
|
91 QDomElement childElem = rootElem.firstChildElement(); |
|
92 while (!childElem.isNull()) |
|
93 { |
|
94 if (childElem.tagName()==SA("group")) |
|
95 { |
|
96 QString name = childElem.attribute(SA("name")); |
|
97 items.append(new QTreeWidgetItem((QTreeWidget*)0,QStringList(name))); |
|
98 QWidget *widget = createTopicWidget(childElem); |
|
99 m_topics[name] = widget; |
|
100 m_topicStack->addWidget(widget); |
|
101 } |
|
102 childElem = childElem.nextSiblingElement(); |
|
103 } |
|
104 m_treeWidget->setHeaderLabels(QStringList() << SA("Topics")); |
|
105 m_treeWidget->insertTopLevelItems(0,items); |
|
106 connect(m_treeWidget, |
|
107 SIGNAL(currentItemChanged(QTreeWidgetItem *,QTreeWidgetItem *)), |
|
108 this, |
|
109 SLOT(activateTopic(QTreeWidgetItem *,QTreeWidgetItem *))); |
|
110 } |
|
111 |
|
112 |
|
113 QWidget *Expert::createTopicWidget(QDomElement &elem) |
|
114 { |
|
115 QScrollArea *area = new QScrollArea; |
|
116 QWidget *topic = new QWidget; |
|
117 QGridLayout *layout = new QGridLayout(topic); |
|
118 QDomElement child = elem.firstChildElement(); |
|
119 int row=0; |
|
120 while (!child.isNull()) |
|
121 { |
|
122 QString type = child.attribute(SA("type")); |
|
123 if (type==SA("bool")) |
|
124 { |
|
125 InputBool *boolOption = |
|
126 new InputBool( |
|
127 layout,row, |
|
128 child.attribute(SA("id")), |
|
129 child.attribute(SA("defval"))==SA("1"), |
|
130 child.attribute(SA("docs")) |
|
131 ); |
|
132 m_options.insert( |
|
133 child.attribute(SA("id")), |
|
134 boolOption |
|
135 ); |
|
136 connect(boolOption,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*))); |
|
137 connect(boolOption,SIGNAL(changed()),SIGNAL(changed())); |
|
138 } |
|
139 else if (type==SA("string")) |
|
140 { |
|
141 InputString::StringMode mode; |
|
142 QString format = child.attribute(SA("format")); |
|
143 if (format==SA("dir")) |
|
144 { |
|
145 mode = InputString::StringDir; |
|
146 } |
|
147 else if (format==SA("file")) |
|
148 { |
|
149 mode = InputString::StringFile; |
|
150 } |
|
151 else // format=="string" |
|
152 { |
|
153 mode = InputString::StringFree; |
|
154 } |
|
155 InputString *stringOption = |
|
156 new InputString( |
|
157 layout,row, |
|
158 child.attribute(SA("id")), |
|
159 child.attribute(SA("defval")), |
|
160 mode, |
|
161 child.attribute(SA("docs")), |
|
162 child.attribute(SA("abspath")) |
|
163 ); |
|
164 m_options.insert( |
|
165 child.attribute(SA("id")), |
|
166 stringOption |
|
167 ); |
|
168 connect(stringOption,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*))); |
|
169 connect(stringOption,SIGNAL(changed()),SIGNAL(changed())); |
|
170 } |
|
171 else if (type==SA("enum")) |
|
172 { |
|
173 InputString *enumList = new InputString( |
|
174 layout,row, |
|
175 child.attribute(SA("id")), |
|
176 child.attribute(SA("defval")), |
|
177 InputString::StringFixed, |
|
178 child.attribute(SA("docs")) |
|
179 ); |
|
180 QDomElement enumVal = child.firstChildElement(); |
|
181 while (!enumVal.isNull()) |
|
182 { |
|
183 enumList->addValue(enumVal.attribute(SA("name"))); |
|
184 enumVal = enumVal.nextSiblingElement(); |
|
185 } |
|
186 enumList->setDefault(); |
|
187 |
|
188 m_options.insert(child.attribute(SA("id")),enumList); |
|
189 connect(enumList,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*))); |
|
190 connect(enumList,SIGNAL(changed()),SIGNAL(changed())); |
|
191 } |
|
192 else if (type==SA("int")) |
|
193 { |
|
194 InputInt *intOption = |
|
195 new InputInt( |
|
196 layout,row, |
|
197 child.attribute(SA("id")), |
|
198 child.attribute(SA("defval")).toInt(), |
|
199 child.attribute(SA("minval")).toInt(), |
|
200 child.attribute(SA("maxval")).toInt(), |
|
201 child.attribute(SA("docs")) |
|
202 ); |
|
203 m_options.insert( |
|
204 child.attribute(SA("id")), |
|
205 intOption |
|
206 ); |
|
207 connect(intOption,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*))); |
|
208 connect(intOption,SIGNAL(changed()),SIGNAL(changed())); |
|
209 } |
|
210 else if (type==SA("list")) |
|
211 { |
|
212 InputStrList::ListMode mode; |
|
213 QString format = child.attribute(SA("format")); |
|
214 if (format==SA("dir")) |
|
215 { |
|
216 mode = InputStrList::ListDir; |
|
217 } |
|
218 else if (format==SA("file")) |
|
219 { |
|
220 mode = InputStrList::ListFile; |
|
221 } |
|
222 else if (format==SA("filedir")) |
|
223 { |
|
224 mode = InputStrList::ListFileDir; |
|
225 } |
|
226 else // format=="string" |
|
227 { |
|
228 mode = InputStrList::ListString; |
|
229 } |
|
230 QStringList sl; |
|
231 QDomElement listVal = child.firstChildElement(); |
|
232 while (!listVal.isNull()) |
|
233 { |
|
234 sl.append(listVal.attribute(SA("name"))); |
|
235 listVal = listVal.nextSiblingElement(); |
|
236 } |
|
237 InputStrList *listOption = |
|
238 new InputStrList( |
|
239 layout,row, |
|
240 child.attribute(SA("id")), |
|
241 sl, |
|
242 mode, |
|
243 child.attribute(SA("docs")) |
|
244 ); |
|
245 m_options.insert( |
|
246 child.attribute(SA("id")), |
|
247 listOption |
|
248 ); |
|
249 connect(listOption,SIGNAL(showHelp(Input*)),SLOT(showHelp(Input*))); |
|
250 connect(listOption,SIGNAL(changed()),SIGNAL(changed())); |
|
251 } |
|
252 else if (type==SA("obsolete")) |
|
253 { |
|
254 // ignore |
|
255 } |
|
256 else // should not happen |
|
257 { |
|
258 printf("Unsupported type %s\n",qPrintable(child.attribute(SA("type")))); |
|
259 } |
|
260 child = child.nextSiblingElement(); |
|
261 } |
|
262 |
|
263 // compute dependencies between options |
|
264 child = elem.firstChildElement(); |
|
265 while (!child.isNull()) |
|
266 { |
|
267 QString dependsOn = child.attribute(SA("depends")); |
|
268 QString id = child.attribute(SA("id")); |
|
269 if (!dependsOn.isEmpty()) |
|
270 { |
|
271 Input *parentOption = m_options[dependsOn]; |
|
272 Input *thisOption = m_options[id]; |
|
273 Q_ASSERT(parentOption); |
|
274 Q_ASSERT(thisOption); |
|
275 if (parentOption && thisOption) |
|
276 { |
|
277 //printf("Adding dependency '%s' (%p)->'%s' (%p)\n", |
|
278 // qPrintable(dependsOn),parentOption, |
|
279 // qPrintable(id),thisOption); |
|
280 parentOption->addDependency(thisOption); |
|
281 } |
|
282 } |
|
283 child = child.nextSiblingElement(); |
|
284 } |
|
285 |
|
286 // set initial dependencies |
|
287 QHashIterator<QString,Input*> i(m_options); |
|
288 while (i.hasNext()) |
|
289 { |
|
290 i.next(); |
|
291 if (i.value()) |
|
292 { |
|
293 i.value()->updateDependencies(); |
|
294 } |
|
295 } |
|
296 |
|
297 layout->setRowStretch(row,1); |
|
298 layout->setColumnStretch(1,2); |
|
299 layout->setSpacing(5); |
|
300 topic->setLayout(layout); |
|
301 area->setWidget(topic); |
|
302 area->setWidgetResizable(true); |
|
303 return area; |
|
304 } |
|
305 |
|
306 void Expert::activateTopic(QTreeWidgetItem *item,QTreeWidgetItem *) |
|
307 { |
|
308 if (item) |
|
309 { |
|
310 QWidget *w = m_topics[item->text(0)]; |
|
311 m_topicStack->setCurrentWidget(w); |
|
312 m_prev->setEnabled(m_topicStack->currentIndex()!=0); |
|
313 m_next->setEnabled(m_topicStack->currentIndex()!=m_topicStack->count()-1); |
|
314 } |
|
315 } |
|
316 |
|
317 void Expert::loadSettings(QSettings *s) |
|
318 { |
|
319 QHashIterator<QString,Input*> i(m_options); |
|
320 while (i.hasNext()) |
|
321 { |
|
322 i.next(); |
|
323 QVariant var = s->value(SA("config/")+i.key()); |
|
324 //printf("Loading key %s: type=%d\n",qPrintable(i.key()),var.type()); |
|
325 if (i.value()) |
|
326 { |
|
327 i.value()->value() = var; |
|
328 i.value()->update(); |
|
329 } |
|
330 } |
|
331 } |
|
332 |
|
333 void Expert::saveSettings(QSettings *s) |
|
334 { |
|
335 QHashIterator<QString,Input*> i(m_options); |
|
336 while (i.hasNext()) |
|
337 { |
|
338 i.next(); |
|
339 if (i.value()) |
|
340 { |
|
341 s->value(SA("config/")+i.key(),i.value()->value()); |
|
342 } |
|
343 } |
|
344 } |
|
345 |
|
346 void Expert::loadConfig(const QString &fileName) |
|
347 { |
|
348 //printf("Expert::loadConfig(%s)\n",qPrintable(fileName)); |
|
349 parseConfig(fileName,m_options); |
|
350 } |
|
351 |
|
352 void Expert::saveTopic(QTextStream &t,QDomElement &elem,QTextCodec *codec, |
|
353 bool brief) |
|
354 { |
|
355 // write group header |
|
356 t << endl; |
|
357 t << "#---------------------------------------------------------------------------" << endl; |
|
358 t << "# " << elem.attribute(SA("docs")) << endl; |
|
359 t << "#---------------------------------------------------------------------------" << endl; |
|
360 |
|
361 // write options... |
|
362 QDomElement childElem = elem.firstChildElement(); |
|
363 while (!childElem.isNull()) |
|
364 { |
|
365 QString type = childElem.attribute(SA("type")); |
|
366 QString name = childElem.attribute(SA("id")); |
|
367 QHash<QString,Input*>::const_iterator i = m_options.find(name); |
|
368 if (i!=m_options.end()) |
|
369 { |
|
370 Input *option = i.value(); |
|
371 if (!brief) |
|
372 { |
|
373 t << endl; |
|
374 t << convertToComment(childElem.attribute(SA("docs"))); |
|
375 t << endl; |
|
376 } |
|
377 t << name.leftJustified(23) << "= "; |
|
378 if (option) |
|
379 { |
|
380 option->writeValue(t,codec); |
|
381 } |
|
382 t << endl; |
|
383 } |
|
384 childElem = childElem.nextSiblingElement(); |
|
385 } |
|
386 |
|
387 } |
|
388 |
|
389 bool Expert::writeConfig(QTextStream &t,bool brief) |
|
390 { |
|
391 if (!brief) |
|
392 { |
|
393 // write global header |
|
394 t << "# Doxyfile " << versionString << endl << endl; // TODO: add version |
|
395 t << "# This file describes the settings to be used by the documentation system\n"; |
|
396 t << "# doxygen (www.doxygen.org) for a project\n"; |
|
397 t << "#\n"; |
|
398 t << "# All text after a hash (#) is considered a comment and will be ignored\n"; |
|
399 t << "# The format is:\n"; |
|
400 t << "# TAG = value [value, ...]\n"; |
|
401 t << "# For lists items can also be appended using:\n"; |
|
402 t << "# TAG += value [value, ...]\n"; |
|
403 t << "# Values that contain spaces should be placed between quotes (\" \")\n"; |
|
404 } |
|
405 |
|
406 QTextCodec *codec = 0; |
|
407 Input *option = m_options[QString::fromAscii("DOXYFILE_ENCODING")]; |
|
408 if (option) |
|
409 { |
|
410 codec = QTextCodec::codecForName(option->value().toString().toAscii()); |
|
411 if (codec==0) // fallback: use UTF-8 |
|
412 { |
|
413 codec = QTextCodec::codecForName("UTF-8"); |
|
414 } |
|
415 } |
|
416 QDomElement childElem = m_rootElement.firstChildElement(); |
|
417 while (!childElem.isNull()) |
|
418 { |
|
419 saveTopic(t,childElem,codec,brief); |
|
420 childElem = childElem.nextSiblingElement(); |
|
421 } |
|
422 return true; |
|
423 } |
|
424 |
|
425 QByteArray Expert::saveInnerState () const |
|
426 { |
|
427 return m_splitter->saveState(); |
|
428 } |
|
429 |
|
430 bool Expert::restoreInnerState ( const QByteArray & state ) |
|
431 { |
|
432 return m_splitter->restoreState(state); |
|
433 } |
|
434 |
|
435 void Expert::showHelp(Input *option) |
|
436 { |
|
437 m_helper->setText( |
|
438 QString::fromAscii("<qt><b>")+option->id()+ |
|
439 QString::fromAscii("</b><br>")+ |
|
440 option->docs(). |
|
441 replace(QChar::fromAscii('\n'),QChar::fromAscii(' '))+ |
|
442 QString::fromAscii("<qt>") |
|
443 ); |
|
444 } |
|
445 |
|
446 void Expert::nextTopic() |
|
447 { |
|
448 m_topicStack->setCurrentIndex(m_topicStack->currentIndex()+1); |
|
449 m_next->setEnabled(m_topicStack->count()!=m_topicStack->currentIndex()+1); |
|
450 m_prev->setEnabled(m_topicStack->currentIndex()!=0); |
|
451 m_treeWidget->setCurrentItem(m_treeWidget->invisibleRootItem()->child(m_topicStack->currentIndex())); |
|
452 } |
|
453 |
|
454 void Expert::prevTopic() |
|
455 { |
|
456 m_topicStack->setCurrentIndex(m_topicStack->currentIndex()-1); |
|
457 m_next->setEnabled(m_topicStack->count()!=m_topicStack->currentIndex()+1); |
|
458 m_prev->setEnabled(m_topicStack->currentIndex()!=0); |
|
459 m_treeWidget->setCurrentItem(m_treeWidget->invisibleRootItem()->child(m_topicStack->currentIndex())); |
|
460 } |
|
461 |
|
462 void Expert::resetToDefaults() |
|
463 { |
|
464 //printf("Expert::makeDefaults()\n"); |
|
465 QHashIterator<QString,Input*> i(m_options); |
|
466 while (i.hasNext()) |
|
467 { |
|
468 i.next(); |
|
469 if (i.value()) |
|
470 { |
|
471 i.value()->reset(); |
|
472 } |
|
473 } |
|
474 } |
|
475 |
|
476 static bool stringVariantToBool(const QVariant &v) |
|
477 { |
|
478 QString s = v.toString().toLower(); |
|
479 return s==QString::fromAscii("yes") || s==QString::fromAscii("true") || s==QString::fromAscii("1"); |
|
480 } |
|
481 |
|
482 static bool getBoolOption( |
|
483 const QHash<QString,Input*>&model,const QString &name) |
|
484 { |
|
485 Input *option = model[name]; |
|
486 Q_ASSERT(option!=0); |
|
487 return stringVariantToBool(option->value()); |
|
488 } |
|
489 |
|
490 static QString getStringOption( |
|
491 const QHash<QString,Input*>&model,const QString &name) |
|
492 { |
|
493 Input *option = model[name]; |
|
494 Q_ASSERT(option!=0); |
|
495 return option->value().toString(); |
|
496 } |
|
497 |
|
498 |
|
499 bool Expert::htmlOutputPresent(const QString &workingDir) const |
|
500 { |
|
501 bool generateHtml = getBoolOption(m_options,QString::fromAscii("GENERATE_HTML")); |
|
502 if (!generateHtml || workingDir.isEmpty()) return false; |
|
503 QString indexFile = getHtmlOutputIndex(workingDir); |
|
504 QFileInfo fi(indexFile); |
|
505 return fi.exists() && fi.isFile(); |
|
506 } |
|
507 |
|
508 QString Expert::getHtmlOutputIndex(const QString &workingDir) const |
|
509 { |
|
510 QString outputDir = getStringOption(m_options,QString::fromAscii("OUTPUT_DIRECTORY")); |
|
511 QString htmlOutputDir = getStringOption(m_options,QString::fromAscii("HTML_OUTPUT")); |
|
512 //printf("outputDir=%s\n",qPrintable(outputDir)); |
|
513 //printf("htmlOutputDir=%s\n",qPrintable(htmlOutputDir)); |
|
514 QString indexFile = workingDir; |
|
515 if (QFileInfo(outputDir).isAbsolute()) // override |
|
516 { |
|
517 indexFile = outputDir; |
|
518 } |
|
519 else // append |
|
520 { |
|
521 indexFile += QString::fromAscii("/")+outputDir; |
|
522 } |
|
523 if (QFileInfo(htmlOutputDir).isAbsolute()) // override |
|
524 { |
|
525 indexFile = htmlOutputDir; |
|
526 } |
|
527 else // append |
|
528 { |
|
529 indexFile += QString::fromAscii("/")+htmlOutputDir; |
|
530 } |
|
531 indexFile+=QString::fromAscii("/index.html"); |
|
532 return indexFile; |
|
533 } |
|
534 |
|
535 bool Expert::pdfOutputPresent(const QString &workingDir) const |
|
536 { |
|
537 bool generateLatex = getBoolOption(m_options,QString::fromAscii("GENERATE_LATEX")); |
|
538 bool pdfLatex = getBoolOption(m_options,QString::fromAscii("USE_PDFLATEX")); |
|
539 if (!generateLatex || !pdfLatex) return false; |
|
540 QString latexOutput = getStringOption(m_options,QString::fromAscii("LATEX_OUTPUT")); |
|
541 QString indexFile; |
|
542 if (QFileInfo(latexOutput).isAbsolute()) |
|
543 { |
|
544 indexFile = latexOutput+QString::fromAscii("/refman.pdf"); |
|
545 } |
|
546 else |
|
547 { |
|
548 indexFile = workingDir+QString::fromAscii("/")+ |
|
549 latexOutput+QString::fromAscii("/refman.pdf"); |
|
550 } |
|
551 QFileInfo fi(indexFile); |
|
552 return fi.exists() && fi.isFile(); |
|
553 } |
|
554 |