Imported Upstream version 1.8.8
[platform/upstream/doxygen.git] / addon / doxywizard / doxywizard.cpp
1 #include <QtGui>
2 #include "doxywizard.h"
3 #include "version.h"
4 #include "expert.h"
5 #include "wizard.h"
6
7 #ifdef WIN32
8 #include <windows.h>
9 #endif
10
11 #define MAX_RECENT_FILES 10
12
13 const int messageTimeout = 5000; //!< status bar message timeout in millisec.
14
15 MainWindow &MainWindow::instance()
16 {
17   static MainWindow *theInstance = new MainWindow;
18   return *theInstance;
19 }
20
21 MainWindow::MainWindow()
22   : m_settings(QString::fromAscii("Doxygen.org"), QString::fromAscii("Doxywizard"))
23 {
24   QMenu *file = menuBar()->addMenu(tr("File"));
25   file->addAction(tr("Open..."), 
26                   this, SLOT(openConfig()), Qt::CTRL+Qt::Key_O);
27   m_recentMenu = file->addMenu(tr("Open recent"));
28   file->addAction(tr("Save"), 
29                   this, SLOT(saveConfig()), Qt::CTRL+Qt::Key_S);
30   file->addAction(tr("Save as..."), 
31                   this, SLOT(saveConfigAs()), Qt::SHIFT+Qt::CTRL+Qt::Key_S);
32   file->addAction(tr("Quit"),  
33                   this, SLOT(quit()), Qt::CTRL+Qt::Key_Q);
34
35   QMenu *settings = menuBar()->addMenu(tr("Settings"));
36   settings->addAction(tr("Reset to factory defaults"),
37                   this,SLOT(resetToDefaults()));
38   settings->addAction(tr("Use current settings at startup"),
39                   this,SLOT(makeDefaults()));
40   settings->addAction(tr("Clear recent list"),
41                   this,SLOT(clearRecent()));
42
43   QMenu *help = menuBar()->addMenu(tr("Help"));
44   help->addAction(tr("Online manual"), 
45                   this, SLOT(manual()), Qt::Key_F1);
46   help->addAction(tr("About"), 
47                   this, SLOT(about()) );
48
49   m_expert = new Expert;
50   m_wizard = new Wizard(m_expert->modelData());
51
52   // ----------- top part ------------------
53   QWidget *topPart = new QWidget;
54   QVBoxLayout *rowLayout = new QVBoxLayout(topPart);
55
56   // select working directory
57   QHBoxLayout *dirLayout = new QHBoxLayout;
58   m_workingDir = new QLineEdit;
59   m_selWorkingDir = new QPushButton(tr("Select..."));
60   dirLayout->addWidget(m_workingDir);
61   dirLayout->addWidget(m_selWorkingDir);
62
63   //------------- bottom part --------------
64   QWidget *runTab = new QWidget;
65   QVBoxLayout *runTabLayout = new QVBoxLayout(runTab);
66
67   // run doxygen
68   QHBoxLayout *runLayout = new QHBoxLayout;
69   m_run = new QPushButton(tr("Run doxygen"));
70   m_run->setEnabled(false);
71   m_runStatus = new QLabel(tr("Status: not running"));
72   m_saveLog = new QPushButton(tr("Save log..."));
73   m_saveLog->setEnabled(false);
74   QPushButton *showSettings = new QPushButton(tr("Show configuration"));
75   runLayout->addWidget(m_run);
76   runLayout->addWidget(m_runStatus);
77   runLayout->addStretch(1);
78   runLayout->addWidget(showSettings);
79   runLayout->addWidget(m_saveLog);
80
81   // output produced by doxygen
82   runTabLayout->addLayout(runLayout);
83   runTabLayout->addWidget(new QLabel(tr("Output produced by doxygen")));
84   QGridLayout *grid = new QGridLayout;
85   m_outputLog = new QTextEdit;
86   m_outputLog->setReadOnly(true);
87   m_outputLog->setFontFamily(QString::fromAscii("courier"));
88   m_outputLog->setMinimumWidth(600);
89   grid->addWidget(m_outputLog,0,0);
90   grid->setColumnStretch(0,1);
91   grid->setRowStretch(0,1);
92   QHBoxLayout *launchLayout = new QHBoxLayout;
93   m_launchHtml = new QPushButton(tr("Show HTML output"));
94   launchLayout->addWidget(m_launchHtml);
95
96   launchLayout->addStretch(1);
97   grid->addLayout(launchLayout,1,0);
98   runTabLayout->addLayout(grid);
99
100   m_tabs = new QTabWidget;
101   m_tabs->addTab(m_wizard,tr("Wizard"));
102   m_tabs->addTab(m_expert,tr("Expert"));
103   m_tabs->addTab(runTab,tr("Run"));
104
105   rowLayout->addWidget(new QLabel(tr("Step 1: Specify the working directory from which doxygen will run")));
106   rowLayout->addLayout(dirLayout);
107   rowLayout->addWidget(new QLabel(tr("Step 2: Configure doxygen using the Wizard and/or Expert tab, then switch to the Run tab to generate the documentation")));
108   rowLayout->addWidget(m_tabs);
109
110   setCentralWidget(topPart);
111   statusBar()->showMessage(tr("Welcome to Doxygen"),messageTimeout);
112
113   m_runProcess = new QProcess;
114   m_running = false;
115   m_timer = new QTimer;
116
117   // connect signals and slots
118   connect(m_tabs,SIGNAL(currentChanged(int)),SLOT(selectTab(int)));
119   connect(m_selWorkingDir,SIGNAL(clicked()),SLOT(selectWorkingDir()));
120   connect(m_recentMenu,SIGNAL(triggered(QAction*)),SLOT(openRecent(QAction*)));
121   connect(m_workingDir,SIGNAL(returnPressed()),SLOT(updateWorkingDir()));
122   connect(m_runProcess,SIGNAL(readyReadStandardOutput()),SLOT(readStdout()));
123   connect(m_runProcess,SIGNAL(finished(int, QProcess::ExitStatus)),SLOT(runComplete()));
124   connect(m_timer,SIGNAL(timeout()),SLOT(readStdout()));
125   connect(m_run,SIGNAL(clicked()),SLOT(runDoxygen()));
126   connect(m_launchHtml,SIGNAL(clicked()),SLOT(showHtmlOutput()));
127   connect(m_saveLog,SIGNAL(clicked()),SLOT(saveLog()));
128   connect(showSettings,SIGNAL(clicked()),SLOT(showSettings()));
129   connect(m_expert,SIGNAL(changed()),SLOT(configChanged()));
130   connect(m_wizard,SIGNAL(done()),SLOT(selectRunTab()));
131   connect(m_expert,SIGNAL(done()),SLOT(selectRunTab()));
132
133   loadSettings();
134   updateLaunchButtonState();
135   m_modified = false;
136   updateTitle();
137   m_wizard->refresh();
138 }
139
140 void MainWindow::closeEvent(QCloseEvent *event)
141 {
142   if (discardUnsavedChanges())
143   {
144     saveSettings();
145     event->accept();
146   }
147   else
148   {
149     event->ignore();
150   }
151 }
152
153 void MainWindow::quit()
154 {
155   if (discardUnsavedChanges())
156   {
157     saveSettings();
158   }
159   QApplication::exit(0);
160 }
161
162 void MainWindow::setWorkingDir(const QString &dirName)
163 {
164     QDir::setCurrent(dirName);
165     m_workingDir->setText(dirName);
166     m_run->setEnabled(!dirName.isEmpty());
167 }
168
169 void MainWindow::selectWorkingDir()
170 {
171   QString dirName = QFileDialog::getExistingDirectory(this,
172         tr("Select working directory"),m_workingDir->text());
173   if (!dirName.isEmpty())
174   {
175     setWorkingDir(dirName);
176   }
177 }
178
179 void MainWindow::updateWorkingDir()
180 {
181   setWorkingDir(m_workingDir->text());
182 }
183
184 void MainWindow::manual()
185 {
186   QDesktopServices::openUrl(QUrl(QString::fromAscii("http://www.doxygen.org/manual.html")));
187 }
188
189 void MainWindow::about()
190 {
191   QString msg;
192   QTextStream t(&msg,QIODevice::WriteOnly);
193   t << QString::fromAscii("<qt><center>A tool to configure and run doxygen version ")+
194        QString::fromAscii(versionString)+
195        QString::fromAscii(" on your source files.</center><p><br>"
196        "<center>Written by<br> Dimitri van Heesch<br>&copy; 2000-2014</center><p>"
197        "</qt>");
198   QMessageBox::about(this,tr("Doxygen GUI"),msg);
199 }
200
201 void MainWindow::openConfig()
202 {
203   if (discardUnsavedChanges(false))
204   {
205     QString fn = QFileDialog::getOpenFileName(this,
206         tr("Open configuration file"),
207         m_workingDir->text());
208     if (!fn.isEmpty())
209     {
210       loadConfigFromFile(fn);
211     }
212   }
213 }
214
215 void MainWindow::updateConfigFileName(const QString &fileName)
216 {
217   if (m_fileName!=fileName)
218   {
219     m_fileName = fileName;
220     QString curPath = QFileInfo(fileName).path();
221     setWorkingDir(curPath);
222     addRecentFile(fileName);
223     updateTitle();
224   }
225 }
226
227 void MainWindow::loadConfigFromFile(const QString & fileName)
228 {
229   // save full path info of original file
230   QString absFileName = QFileInfo(fileName).absoluteFilePath();
231   // updates the current directory
232   updateConfigFileName(fileName);
233   // open the specified configuration file
234   m_expert->loadConfig(absFileName);
235   m_wizard->refresh();
236   updateLaunchButtonState();
237   m_modified = false;
238   updateTitle();
239 }
240
241 void MainWindow::saveConfig(const QString &fileName)
242 {
243   if (fileName.isEmpty()) return;
244   QFile f(fileName);
245   if (!f.open(QIODevice::WriteOnly)) 
246   {
247     QMessageBox::warning(this,
248         tr("Error saving"),
249         tr("Error: cannot open the file ")+fileName+tr(" for writing!\n")+
250         tr("Reason given: ")+f.error());
251     return;
252   }
253   QTextStream t(&f);
254   m_expert->writeConfig(t,false);
255   updateConfigFileName(fileName);
256   m_modified = false;
257   updateTitle();
258 }
259
260 bool MainWindow::saveConfig()
261 {
262   if (m_fileName.isEmpty())
263   {
264     return saveConfigAs();
265   }
266   else
267   {
268     saveConfig(m_fileName);
269     return true;
270   }
271 }
272
273 bool MainWindow::saveConfigAs()
274 {
275   QString fileName = QFileDialog::getSaveFileName(this, QString(), 
276              m_workingDir->text()+QString::fromAscii("/Doxyfile"));
277   if (fileName.isEmpty()) return false;
278   saveConfig(fileName);
279   return true;
280 }
281
282 void MainWindow::makeDefaults()
283 {
284   if (QMessageBox::question(this,tr("Use current setting at startup?"),
285                         tr("Do you want to save the current settings "
286                            "and use them next time Doxywizard starts?"),
287                         QMessageBox::Save|
288                         QMessageBox::Cancel)==QMessageBox::Save)
289   {
290     //printf("MainWindow:makeDefaults()\n");
291     m_expert->saveSettings(&m_settings);
292     m_settings.setValue(QString::fromAscii("wizard/loadsettings"), true);
293     m_settings.sync();
294   }
295 }
296
297 void MainWindow::clearRecent()
298 {
299   if (QMessageBox::question(this,tr("Clear the list of recent files?"),
300                         tr("Do you want to clear the list of recently "
301                            "loaded configuration files?"),
302                         QMessageBox::Yes|
303                         QMessageBox::Cancel)==QMessageBox::Yes)
304   {
305     m_recentMenu->clear();
306     m_recentFiles.clear();
307     for (int i=0;i<MAX_RECENT_FILES;i++)
308     {
309       m_settings.setValue(QString().sprintf("recent/config%d",i++),QString::fromAscii(""));
310     }
311     m_settings.sync();
312   }
313   
314 }
315
316 void MainWindow::resetToDefaults()
317 {
318   if (QMessageBox::question(this,tr("Reset settings to their default values?"),
319                         tr("Do you want to revert all settings back "
320                            "to their original values?"),
321                         QMessageBox::Reset|
322                         QMessageBox::Cancel)==QMessageBox::Reset)
323   {
324     //printf("MainWindow:resetToDefaults()\n");
325     m_expert->resetToDefaults();
326     m_settings.setValue(QString::fromAscii("wizard/loadsettings"), false);
327     m_settings.sync();
328     m_wizard->refresh();
329   }
330 }
331
332 void MainWindow::loadSettings()
333 {
334   QVariant geometry     = m_settings.value(QString::fromAscii("main/geometry"), QVariant::Invalid);
335   QVariant state        = m_settings.value(QString::fromAscii("main/state"),    QVariant::Invalid);
336   QVariant wizState     = m_settings.value(QString::fromAscii("wizard/state"),  QVariant::Invalid);
337   QVariant loadSettings = m_settings.value(QString::fromAscii("wizard/loadsettings"),  QVariant::Invalid);
338   QVariant workingDir   = m_settings.value(QString::fromAscii("wizard/workingdir"), QVariant::Invalid);
339
340   if (geometry  !=QVariant::Invalid) restoreGeometry(geometry.toByteArray());
341   if (state     !=QVariant::Invalid) restoreState   (state.toByteArray());
342   if (wizState  !=QVariant::Invalid) m_wizard->restoreState(wizState.toByteArray());
343   if (loadSettings!=QVariant::Invalid && loadSettings.toBool())
344   {
345     m_expert->loadSettings(&m_settings);
346     if (workingDir!=QVariant::Invalid && QDir(workingDir.toString()).exists())
347     {
348       setWorkingDir(workingDir.toString());
349     }
350   }
351
352   for (int i=0;i<MAX_RECENT_FILES;i++)
353   {
354     QString entry = m_settings.value(QString().sprintf("recent/config%d",i)).toString();
355     if (!entry.isEmpty() && QFileInfo(entry).exists())
356     {
357       addRecentFile(entry);
358     }
359   }
360
361 }
362
363 void MainWindow::saveSettings()
364 {
365   QSettings settings(QString::fromAscii("Doxygen.org"), QString::fromAscii("Doxywizard"));
366
367   m_settings.setValue(QString::fromAscii("main/geometry"), saveGeometry());
368   m_settings.setValue(QString::fromAscii("main/state"),    saveState());
369   m_settings.setValue(QString::fromAscii("wizard/state"),  m_wizard->saveState());
370   m_settings.setValue(QString::fromAscii("wizard/workingdir"), m_workingDir->text());
371 }
372
373 void MainWindow::selectTab(int id)
374 {
375   if (id==0) m_wizard->refresh();
376   else if (id==1) m_expert->refresh();
377 }
378
379 void MainWindow::selectRunTab()
380 {
381   m_tabs->setCurrentIndex(2);
382 }
383
384 void MainWindow::addRecentFile(const QString &fileName)
385 {
386   int i=m_recentFiles.indexOf(fileName);
387   if (i!=-1) m_recentFiles.removeAt(i);
388   
389   // not found
390   if (m_recentFiles.count() < MAX_RECENT_FILES) // append
391   {
392     m_recentFiles.prepend(fileName);
393   }
394   else // add + drop last item
395   {
396     m_recentFiles.removeLast();
397     m_recentFiles.prepend(fileName);
398   }
399   m_recentMenu->clear();
400   i=0;
401   foreach( QString str, m_recentFiles ) 
402   {
403     m_recentMenu->addAction(str);
404     m_settings.setValue(QString().sprintf("recent/config%d",i++),str);
405   }
406   for (;i<MAX_RECENT_FILES;i++)
407   {
408     m_settings.setValue(QString().sprintf("recent/config%d",i++),QString::fromAscii(""));
409   }
410 }
411
412 void MainWindow::openRecent(QAction *action)
413 {
414   if (discardUnsavedChanges(false))
415   {
416     loadConfigFromFile(action->text());
417   }
418 }
419
420 void MainWindow::runDoxygen()
421 {
422   if (!m_running)
423   {
424     QString doxygenPath; 
425 #if defined(Q_OS_MACX)
426     doxygenPath = qApp->applicationDirPath()+QString::fromAscii("/../Resources/");
427     qDebug() << tr("Doxygen path: ") << doxygenPath;
428     if ( !QFile(doxygenPath + QString::fromAscii("doxygen")).exists() ) 
429     {
430       // No doygen binary in the resources, if there is a system doxygen binary, use that instead
431       if ( QFile(QString::fromAscii("/usr/local/bin/doxygen")).exists() )
432       {
433         doxygenPath = QString::fromAscii("/usr/local/bin/");
434       }
435       else 
436       {
437         qDebug() << tr("Can't find the doxygen command, make sure it's in your $$PATH");
438         doxygenPath = QString::fromAscii("");
439       }
440     }
441     qDebug() << tr("Getting doxygen from: ") << doxygenPath;
442 #endif
443
444     m_runProcess->setReadChannel(QProcess::StandardOutput);
445     m_runProcess->setProcessChannelMode(QProcess::MergedChannels);
446     m_runProcess->setWorkingDirectory(m_workingDir->text());
447     QStringList env=QProcess::systemEnvironment();
448     // set PWD environment variable to m_workingDir
449     env.replaceInStrings(QRegExp(QString::fromAscii("^PWD=(.*)"),Qt::CaseInsensitive), 
450                          QString::fromAscii("PWD=")+m_workingDir->text());
451     m_runProcess->setEnvironment(env);
452
453     QStringList args;
454     args << QString::fromAscii("-b"); // make stdout unbuffered
455     args << QString::fromAscii("-");  // read config from stdin
456
457     m_outputLog->clear();
458     m_runProcess->start(doxygenPath + QString::fromAscii("doxygen"), args);
459
460     if (!m_runProcess->waitForStarted())
461     {
462       m_outputLog->append(QString::fromAscii("*** Failed to run doxygen\n"));
463       return;
464     }
465     QTextStream t(m_runProcess);
466     m_expert->writeConfig(t,false);
467     m_runProcess->closeWriteChannel();
468
469     if (m_runProcess->state() == QProcess::NotRunning)
470     {
471       m_outputLog->append(QString::fromAscii("*** Failed to run doxygen\n"));
472     }
473     else
474     {
475       m_saveLog->setEnabled(false);
476       m_running=true;
477       m_run->setText(tr("Stop doxygen"));
478       m_runStatus->setText(tr("Status: running"));
479       m_timer->start(1000);
480     }
481   }
482   else
483   {
484     m_running=false;
485     m_run->setText(tr("Run doxygen"));
486     m_runStatus->setText(tr("Status: not running"));
487     m_runProcess->kill();
488     m_timer->stop();
489     //updateRunnable(m_workingDir->text());
490   }
491 }
492
493 void MainWindow::readStdout()
494 {
495   if (m_running)
496   {
497     QByteArray data = m_runProcess->readAllStandardOutput();
498     QString text = QString::fromUtf8(data);
499     if (!text.isEmpty())
500     {
501       m_outputLog->append(text.trimmed());
502     }
503   }
504 }
505
506 void MainWindow::runComplete()
507 {
508   if (m_running)
509   {
510     m_outputLog->append(tr("*** Doxygen has finished\n"));
511   }
512   else
513   {
514     m_outputLog->append(tr("*** Cancelled by user\n"));
515   }
516   m_outputLog->ensureCursorVisible();
517   m_run->setText(tr("Run doxygen"));
518   m_runStatus->setText(tr("Status: not running"));
519   m_running=false;
520   updateLaunchButtonState();
521   //updateRunnable(m_workingDir->text());
522   m_saveLog->setEnabled(true);
523 }
524
525 void MainWindow::updateLaunchButtonState()
526 {
527   m_launchHtml->setEnabled(m_expert->htmlOutputPresent(m_workingDir->text()));
528 #if 0
529   m_launchPdf->setEnabled(m_expert->pdfOutputPresent(m_workingDir->text()));
530 #endif
531 }
532
533 void MainWindow::showHtmlOutput()
534 {
535   QString indexFile = m_expert->getHtmlOutputIndex(m_workingDir->text());
536   QFileInfo fi(indexFile);
537   // TODO: the following doesn't seem to work with IE
538 #ifdef WIN32
539   //QString indexUrl(QString::fromAscii("file:///"));
540   ShellExecute(NULL, L"open", (LPCWSTR)fi.absoluteFilePath().utf16(), NULL, NULL, SW_SHOWNORMAL);
541 #else
542   QString indexUrl(QString::fromAscii("file://"));
543   indexUrl+=fi.absoluteFilePath();
544   QDesktopServices::openUrl(QUrl(indexUrl));
545 #endif
546 }
547
548 void MainWindow::saveLog()
549 {
550   QString fn = QFileDialog::getSaveFileName(this, tr("Save log file"), 
551         m_workingDir->text()+
552         QString::fromAscii("/doxygen_log.txt"));
553   if (!fn.isEmpty())
554   {
555     QFile f(fn);
556     if (f.open(QIODevice::WriteOnly))
557     {
558       QTextStream t(&f);
559       t << m_outputLog->toPlainText();
560       statusBar()->showMessage(tr("Output log saved"),messageTimeout);
561     }
562     else
563     {
564       QMessageBox::warning(0,tr("Warning"),
565           tr("Cannot open file ")+fn+tr(" for writing. Nothing saved!"),tr("ok"));
566     }
567   }
568 }
569
570 void MainWindow::showSettings()
571 {
572   QString text;
573   QTextStream t(&text);
574   m_expert->writeConfig(t,true);
575   m_outputLog->clear();
576   m_outputLog->append(text);
577   m_outputLog->ensureCursorVisible();
578   m_saveLog->setEnabled(true);
579 }
580
581 void MainWindow::configChanged()
582 {
583   m_modified = true;
584   updateTitle();
585 }
586
587 void MainWindow::updateTitle()
588 {
589   QString title = tr("Doxygen GUI frontend");
590   if (m_modified)
591   {
592     title+=QString::fromAscii(" +");
593   }
594   if (!m_fileName.isEmpty())
595   {
596     title+=QString::fromAscii(" (")+m_fileName+QString::fromAscii(")");
597   }
598   setWindowTitle(title);
599 }
600
601 bool MainWindow::discardUnsavedChanges(bool saveOption)
602 {
603   if (m_modified)
604   {
605     QMessageBox::StandardButton button;
606     if (saveOption)
607     {
608       button = QMessageBox::question(this,
609           tr("Unsaved changes"),
610           tr("Unsaved changes will be lost! Do you want to save the configuration file?"),
611           QMessageBox::Save    |
612           QMessageBox::Discard |
613           QMessageBox::Cancel
614           );
615       if (button==QMessageBox::Save)
616       {
617         return saveConfig();
618       }
619     }
620     else
621     {
622       button = QMessageBox::question(this,
623           tr("Unsaved changes"),
624           tr("Unsaved changes will be lost! Do you want to continue?"),
625           QMessageBox::Discard |
626           QMessageBox::Cancel
627           );
628     }
629     return button==QMessageBox::Discard;
630   }
631   return true;
632 }
633
634 //-----------------------------------------------------------------------
635 int main(int argc,char **argv)
636 {
637   QApplication a(argc,argv);
638   if (argc == 2)
639   {
640     if (!qstrcmp(argv[1],"--help"))
641     {
642       QMessageBox msgBox;
643       msgBox.setText(QString().sprintf("Usage: %s [config file]",argv[0]));
644       msgBox.exec();
645       exit(0);
646     }
647   }
648   if (argc > 2)
649   {
650     QMessageBox msgBox;
651     msgBox.setText(QString().sprintf("Too many arguments specified\n\nUsage: %s [config file]",argv[0]));
652     msgBox.exec();
653     exit(1);
654   }
655   else
656   {
657     MainWindow &main = MainWindow::instance();
658     if (argc==2 && argv[1][0]!='-') // name of config file as an argument
659     {
660       main.loadConfigFromFile(QString::fromLocal8Bit(argv[1]));
661     }
662     main.show();
663     return a.exec();
664   }
665 }