00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014 #include "doxygenpart.h"
00015 #include "doxygenconfigwidget.h"
00016 #include "configwidgetproxy.h"
00017 #include "config.h"
00018
00019 #include <kdevmainwindow.h>
00020 #include <kdevproject.h>
00021 #include <kdevmakefrontend.h>
00022 #include <kdevcore.h>
00023 #include <codemodel.h>
00024 #include <codemodel_utils.h>
00025 #include <domutil.h>
00026
00027 #include <kdebug.h>
00028 #include <klocale.h>
00029 #include <kdevgenericfactory.h>
00030 #include <kaction.h>
00031 #include <kmessagebox.h>
00032 #include <kmainwindow.h>
00033 #include <kparts/part.h>
00034 #include <ktexteditor/document.h>
00035 #include <ktexteditor/viewcursorinterface.h>
00036 #include <ktexteditor/editinterface.h>
00037 #include <partcontroller.h>
00038 #include <kdialogbase.h>
00039
00040 #include <qvbox.h>
00041 #include <qfile.h>
00042 #include <qtextstream.h>
00043 #include <qpopupmenu.h>
00044 #include <qfileinfo.h>
00045
00046 #define PROJECTOPTIONS 1
00047
00048 typedef KDevGenericFactory<DoxygenPart> DoxygenFactory;
00049 static const KAboutData data("kdevdoxygen", I18N_NOOP("Doxygen"), "1.0");
00050 K_EXPORT_COMPONENT_FACTORY( libkdevdoxygen, DoxygenFactory( &data ) )
00051
00052 DoxygenPart::DoxygenPart(QObject *parent, const char *name, const QStringList &)
00053 : KDevPlugin("Doxgen", "kdevelop", parent, name ? name : "DoxygenPart"), m_activeEditor(0), m_cursor(0)
00054 {
00055 setInstance(DoxygenFactory::instance());
00056 setXMLFile("kdevdoxygen.rc");
00057
00058 KAction *action;
00059 action = new KAction( i18n("Build API Documentation"), 0,
00060 this, SLOT(slotDoxygen()),
00061 actionCollection(), "build_doxygen" );
00062 action->setToolTip(i18n("Build API documentation"));
00063 action->setWhatsThis(i18n("<b>Build API documentation</b><p>Runs doxygen on a project Doxyfile to generate API documentation. "
00064 "If the search engine is enabled in Doxyfile, this also runs doxytag to create it."));
00065
00066 action = new KAction( i18n("Clean API Documentation"), 0,
00067 this, SLOT(slotDoxClean()),
00068 actionCollection(), "clean_doxygen" );
00069 action->setToolTip(i18n("Clean API documentation"));
00070 action->setWhatsThis(i18n("<b>Clean API documentation</b><p>Removes all generated by doxygen files."));
00071
00072
00073
00074 _configProxy = new ConfigWidgetProxy( core() );
00075 _configProxy->createProjectConfigPage( i18n("Doxygen"), PROJECTOPTIONS, icon() );
00076 connect( _configProxy, SIGNAL(insertConfigWidget(const KDialogBase*, QWidget*, unsigned int )),
00077 this, SLOT(insertConfigWidget(const KDialogBase*, QWidget*, unsigned int )) );
00078
00079 m_actionDocumentFunction = new KAction(i18n("Document Current Function"), 0, CTRL+SHIFT+Key_S, this, SLOT(slotDocumentFunction()), actionCollection(), "edit_document_function");
00080 m_actionDocumentFunction->setToolTip( i18n("Create a documentation template above a function"));
00081 m_actionDocumentFunction->setWhatsThis(i18n("<b>Document Current Function</b><p>Creates a documentation template according to a function's signature above a function definition/declaration."));
00082
00083 m_tmpDir.setAutoDelete(true);
00084 connect(&m_process, SIGNAL(processExited(KProcess*)), this, SLOT(slotPreviewProcessExited()));
00085 connect( partController(), SIGNAL(activePartChanged(KParts::Part*)), this, SLOT(slotActivePartChanged(KParts::Part* )));
00086 m_actionPreview = new KAction(i18n("Preview Doxygen Output"), 0, CTRL+ALT+Key_P, this, SLOT(slotRunPreview()), actionCollection(), "show_preview_doxygen_output");
00087 m_actionPreview->setToolTip( i18n("Show a preview of the Doxygen output of this file") );
00088 m_actionPreview->setWhatsThis( i18n("<b>Preview Doxygen output</b><p>Runs Doxygen over the current file and shows the created index.html.") );
00089
00090
00091 adjustDoxyfile();
00092 QString fileName = project()->projectDirectory() + "/Doxyfile";
00093
00094 QFile file(fileName);
00095 if (file.open(IO_ReadOnly)) {
00096 QTextStream is(&file);
00097
00098 Config::instance()->parse(QFile::encodeName(fileName));
00099 Config::instance()->convertStrToVal();
00100
00101 file.close();
00102 }
00103 }
00104
00105
00106 DoxygenPart::~DoxygenPart()
00107 {
00108 delete _configProxy;
00109 }
00110
00111 void DoxygenPart::insertConfigWidget( const KDialogBase * dlg, QWidget * page, unsigned int pagenumber )
00112 {
00113 if ( pagenumber == PROJECTOPTIONS )
00114 {
00115 adjustDoxyfile();
00116
00117 DoxygenConfigWidget *w = new DoxygenConfigWidget(project()->projectDirectory() + "/Doxyfile", page );
00118 connect( dlg, SIGNAL(okClicked()), w, SLOT(accept()) );
00119 }
00120 }
00121
00125 void DoxygenPart::adjustDoxyfile()
00126 {
00127 QString fileName = project()->projectDirectory() + "/Doxyfile";
00128 if (QFile::exists(fileName))
00129 return;
00130
00131
00132 Config::instance()->init();
00133
00134
00135 Config::instance()->check();
00136
00137
00138 ConfigString *name = dynamic_cast<ConfigString*>(Config::instance()->get("PROJECT_NAME"));
00139 if (name)
00140 {
00141 name->setDefaultValue(project()->projectName().latin1());
00142 name->init();
00143 }
00144
00145
00146 ConfigString *version = dynamic_cast<ConfigString*>(Config::instance()->get("PROJECT_NUMBER"));
00147 if (version)
00148 {
00149 version->setDefaultValue(DomUtil::readEntry(*projectDom(), "/general/version").latin1());
00150 version->init();
00151 }
00152
00153
00154 ConfigList *input_files = dynamic_cast<ConfigList*>(Config::instance()->get("INPUT"));
00155 if (input_files)
00156 {
00157 input_files->init();
00158 input_files->addValue(QFile::encodeName(project()->projectDirectory()));
00159 }
00160
00161
00162 ConfigList *patterns = dynamic_cast<ConfigList*>(Config::instance()->get("FILE_PATTERNS"));
00163 if (patterns)
00164 {
00165
00166
00167
00168
00169 patterns->addValue("*.C");
00170 patterns->addValue("*.H");
00171 patterns->addValue("*.tlh");
00172 patterns->addValue("*.diff");
00173 patterns->addValue("*.patch");
00174 patterns->addValue("*.moc");
00175 patterns->addValue("*.xpm");
00176 patterns->addValue("*.dox");
00177 }
00178
00179
00180 ConfigBool *recursive = dynamic_cast<ConfigBool*>(Config::instance()->get("RECURSIVE"));
00181 if (recursive)
00182 {
00183 recursive->setValueString("yes");
00184 }
00185
00186
00187 ConfigBool *gen_xml = dynamic_cast<ConfigBool*>(Config::instance()->get("GENERATE_XML"));
00188 if (gen_xml)
00189 {
00190 gen_xml->setValueString("yes");
00191 }
00192
00193
00194 ConfigString *gen_tag = dynamic_cast<ConfigString*>(Config::instance()->get("GENERATE_TAGFILE"));
00195 if (gen_tag)
00196 {
00197 gen_tag->setDefaultValue(QString(project()->projectName().remove(".kdevelop")+".tag").latin1());
00198 gen_tag->init();
00199 }
00200
00201
00202 QFile f2(fileName);
00203 if (!f2.open(IO_WriteOnly))
00204 KMessageBox::information(mainWindow()->main(), i18n("Cannot write Doxyfile."));
00205 else
00206 {
00207 Config::instance()->writeTemplate(&f2, true, true);
00208
00209 f2.close();
00210 }
00211 }
00212
00213
00214 void DoxygenPart::slotDoxygen()
00215 {
00216 bool searchDatabase = false;
00217 QString outputDirectory;
00218 QString htmlDirectory;
00219
00220 adjustDoxyfile();
00221
00222 QString fileName = project()->projectDirectory() + "/Doxyfile";
00223
00224 Config::instance()->init();
00225
00226 QFile f(fileName);
00227 if (f.open(IO_ReadOnly))
00228 {
00229 QTextStream is(&f);
00230
00231 Config::instance()->parse(QFile::encodeName(fileName));
00232 Config::instance()->convertStrToVal();
00233
00234 f.close();
00235 }
00236
00237
00238 ConfigBool *search = dynamic_cast<ConfigBool*>(Config::instance()->get("SEARCHENGINE"));
00239 if (search)
00240 {
00241 searchDatabase = Config_getBool("SEARCHENGINE");
00242
00243 if (searchDatabase)
00244 {
00245
00246 outputDirectory = Config_getString("OUTPUT_DIRECTORY");
00247 if ( outputDirectory.isEmpty() == false )
00248 outputDirectory += "/";
00249 htmlDirectory = Config_getString("HTML_OUTPUT");
00250 if ( htmlDirectory.isEmpty() == true )
00251 htmlDirectory = "html";
00252 htmlDirectory.prepend(outputDirectory);
00253 }
00254 }
00255
00256 QString dir = project()->projectDirectory();
00257 QString cmdline = "cd ";
00258 cmdline += KShellProcess::quote( dir );
00259 cmdline += " && doxygen Doxyfile";
00260 if (searchDatabase)
00261 {
00262
00263 if ( htmlDirectory.length() > 0 )
00264 cmdline += " && cd " + KShellProcess::quote( htmlDirectory );
00265 cmdline += " && doxytag -s search.idx ";
00266 }
00267
00268 kdDebug(9026) << "Doxygen command line: " << cmdline << endl;
00269
00270 makeFrontend()->queueCommand(dir, cmdline);
00271 }
00272
00273
00274 void DoxygenPart::slotDoxClean()
00275 {
00276 bool could_be_dirty = false;
00277
00278 QString outputDirectory = Config_getString("OUTPUT_DIRECTORY");
00279 if ( outputDirectory.isEmpty() )
00280 outputDirectory = project()->projectDirectory();
00281 if ( outputDirectory.right(1) != "/" )
00282 outputDirectory += "/";
00283 QString cmdline = "cd " + KShellProcess::quote( outputDirectory );
00284
00285 if ( Config_getBool("GENERATE_HTML") ) {
00286 QString htmlDirectory = Config_getString("HTML_OUTPUT");
00287 if ( htmlDirectory.isEmpty() )
00288 htmlDirectory = "html";
00289 if ( htmlDirectory.right(1) != "/" )
00290 htmlDirectory += "/";
00291 cmdline += " && rm -f " + KShellProcess::quote( htmlDirectory ) + "*";
00292 could_be_dirty= true;
00293 }
00294
00295 if ( Config_getBool("GENERATE_LATEX") ) {
00296 QString latexDirectory = Config_getString("LATEX_OUTPUT");
00297 if ( latexDirectory.isEmpty() )
00298 latexDirectory = "latex";
00299 if ( latexDirectory.right(1) != "/" )
00300 latexDirectory += "/";
00301 cmdline += " && rm -f " + KShellProcess::quote( latexDirectory ) + "*";
00302 could_be_dirty= true;
00303 }
00304
00305 if ( Config_getBool("GENERATE_RTF") ) {
00306 QString rtfDirectory = Config_getString("RTF_OUTPUT");
00307 if ( rtfDirectory.isEmpty() )
00308 rtfDirectory = "rtf";
00309 if ( rtfDirectory.right(1) != "/" )
00310 rtfDirectory += "/";
00311 cmdline += " && rm -f " + KShellProcess::quote( rtfDirectory ) + "*";
00312 could_be_dirty= true;
00313 }
00314
00315 if ( Config_getBool("GENERATE_MAN") ) {
00316 QString manDirectory = Config_getString("MAN_OUTPUT");
00317 if ( manDirectory.isEmpty() )
00318 manDirectory = "man";
00319 if ( manDirectory.right(1) != "/" )
00320 manDirectory += "/";
00321 cmdline += " && rm -f " + KShellProcess::quote( manDirectory ) + "*";
00322 could_be_dirty= true;
00323 }
00324
00325 if ( Config_getBool("GENERATE_XML") ) {
00326 QString xmlDirectory = Config_getString("XML_OUTPUT");
00327 if ( xmlDirectory.isEmpty() )
00328 xmlDirectory = "xml";
00329 if ( xmlDirectory.right(1) != "/" )
00330 xmlDirectory += "/";
00331 cmdline += " && rm -f " + KShellProcess::quote( xmlDirectory ) + "*";
00332 could_be_dirty= true;
00333 }
00334
00335 if (could_be_dirty) {
00336 kdDebug(9026) << "Cleaning Doxygen generated API documentation using: " << cmdline << endl;
00337 makeFrontend()->queueCommand(KShellProcess::quote(project()->projectDirectory()), cmdline);
00338 }
00339 else
00340 kdDebug(9026) << "No Doxygen generated API documentation exists. There's nothing to clean!" << endl;
00341
00342 }
00343
00344 void DoxygenPart::slotPreviewProcessExited( )
00345 {
00346 partController()->showDocument(KURL(m_tmpDir.name()+"html/index.html"));
00347 }
00348
00349 void DoxygenPart::slotRunPreview( )
00350 {
00351 if (m_file.isNull())
00352 return;
00353
00354 if (m_process.isRunning()) {
00355 if ( KMessageBox::warningYesNo(mainWindow()->main(), i18n("Previous Doxygen process is still running.\nDo you want to cancel that process?")) == KMessageBox::Yes )
00356 m_process.kill();
00357 else
00358 return;
00359 }
00360
00361 m_process.clearArguments();
00362
00363 m_tmpDir.unlink();
00364 m_tmpDir = KTempDir();
00365 m_tmpDir.setAutoDelete(true);
00366
00367 Config* config = Config::instance();
00368
00369 ConfigString* poDir = dynamic_cast<ConfigString*>(config->get("OUTPUT_DIRECTORY"));
00370 ConfigList* pInput = dynamic_cast<ConfigList*>(config->get("INPUT"));
00371 ConfigString* pHeader = dynamic_cast<ConfigString*>(config->get("HTML_HEADER"));
00372 ConfigString* pFooter = dynamic_cast<ConfigString*>(config->get("HTML_FOOTER"));
00373 ConfigString* pStyle = dynamic_cast<ConfigString*>(config->get("HTML_STYLESHEET"));
00374
00375
00376 QCString dirVal;
00377 if (poDir != 0) {
00378 dirVal = *poDir->valueRef();
00379 *poDir->valueRef() = m_tmpDir.name().ascii();
00380 }
00381
00382 QStrList inputVal;
00383 if (pInput != 0) {
00384 inputVal = *pInput->valueRef();
00385 QStrList xl;
00386 xl.append(m_file.ascii());
00387 *pInput->valueRef() = xl;
00388 } else {
00389 config->addList("INPUT", "# The INPUT tag can be used to specify the files and/or directories that contain\n"
00390 "# documented source files. You may enter file names like \"myfile.cpp\" or\n"
00391 "# directories like \"/usr/src/myproject\". Separate the files or directories\n"
00392 "# with spaces.");
00393 pInput = dynamic_cast<ConfigList*>(config->get("INPUT"));
00394 QStrList xl;
00395 xl.append(m_file.ascii());
00396 *pInput->valueRef() = xl;
00397 }
00398
00399 QCString header;
00400 QCString footer;
00401 QCString stylesheet;
00402
00403 QString projectDir = project()->projectDirectory();
00404 if (pHeader != 0 && !pHeader->valueRef()->isEmpty()){
00405 header = *pHeader->valueRef();
00406 QFileInfo info (header);
00407 if (info.isRelative())
00408 *pHeader->valueRef() = QString(projectDir + "/" + QString(header)).ascii();
00409 else
00410 header = 0;
00411 }
00412
00413 if (pFooter != 0 && !pFooter->valueRef()->isEmpty()){
00414 footer = *pFooter->valueRef();
00415 QFileInfo info (footer);
00416 if (info.isRelative())
00417 *pFooter->valueRef() = QString(projectDir + "/" + QString(footer)).ascii();
00418 else
00419 footer = 0;
00420 }
00421
00422 if (pStyle != 0 && !pStyle->valueRef()->isEmpty()){
00423 stylesheet = *pStyle->valueRef();
00424 QFileInfo info (stylesheet);
00425 if (info.isRelative())
00426 *pStyle->valueRef() = QString(projectDir +"/" + QString(stylesheet)).ascii();
00427 else
00428 stylesheet = 0;
00429 }
00430
00431 QFile file(m_tmpDir.name() +"PreviewDoxyfile");
00432 if (!file.open(IO_WriteOnly)){
00433
00434 if (pInput != 0)
00435 *pInput->valueRef() = inputVal;
00436
00437 if (poDir != 0)
00438 *poDir->valueRef() = dirVal;
00439
00440 KMessageBox::error(mainWindow()->main(), i18n("Cannot create temporary file '%1'").arg(file.name()));
00441 return;
00442 }
00443
00444 config->writeTemplate(&file, false, false);
00445
00446 if (inputVal.count() == 0)
00447 *pInput->valueRef() = QStrList();
00448 else
00449 *pInput->valueRef() = inputVal;
00450
00451 if (poDir != 0)
00452 *poDir->valueRef() = dirVal;
00453
00454 if (pHeader != 0 && !header.isNull())
00455 *pHeader->valueRef() = header;
00456
00457 if (pFooter != 0 && !footer.isNull())
00458 *pFooter->valueRef() = footer;
00459
00460 if (pStyle != 0 && !stylesheet.isNull())
00461 *pStyle->valueRef() = stylesheet;
00462
00463 m_process << "doxygen" << file.name().ascii();
00464 m_process.start(KProcess::NotifyOnExit, KProcess::NoCommunication);
00465
00466 }
00467
00468 void DoxygenPart::slotActivePartChanged( KParts::Part * part )
00469 {
00470
00471 KTextEditor::Document* doc = dynamic_cast<KTextEditor::Document*>(part);
00472 if (doc != 0)
00473 m_file = doc->url().path();
00474 else
00475 m_file = QString::null;
00476
00477 m_activeEditor = dynamic_cast<KTextEditor::EditInterface*>(part);
00478 m_cursor = part ? dynamic_cast<KTextEditor::ViewCursorInterface*>(part->widget()) : 0;
00479 }
00480
00481 void DoxygenPart::slotDocumentFunction(){
00482 if (m_activeEditor != 0 && m_cursor != 0){
00483 if ( codeModel()->hasFile( m_file ) ) {
00484 unsigned int cursorLine, cursorCol;
00485 m_cursor->cursorPosition(&cursorLine, &cursorCol);
00486
00487 FunctionDom function = 0;
00488 FunctionDefinitionDom functionDef = 0;
00489
00490 FileDom file = codeModel()->fileByName( m_file );
00491
00492 FunctionList functionList = CodeModelUtils::allFunctions(file);
00493 FunctionList::ConstIterator theend = functionList.end();
00494 for( FunctionList::ConstIterator ci = functionList.begin(); ci!= theend; ++ci ){
00495 int sline, scol;
00496 int eline, ecol;
00497 (*ci)->getStartPosition(&sline, &scol);
00498 (*ci)->getEndPosition(&eline, &ecol);
00499 if(cursorLine >= sline && cursorLine <= eline )
00500 function = *ci;
00501 }
00502 if (function == 0){
00503 FunctionDefinitionList functionDefList = CodeModelUtils::allFunctionDefinitionsDetailed(file).functionList;
00504 FunctionDefinitionList::ConstIterator theend = functionDefList.end();
00505 for( FunctionDefinitionList::ConstIterator ci = functionDefList.begin(); ci!= theend; ++ci ){
00506 int sline, scol;
00507 int eline, ecol;
00508 (*ci)->getStartPosition(&sline, &scol);
00509 (*ci)->getEndPosition(&eline, &ecol);
00510 if(cursorLine >= sline && cursorLine <= eline)
00511 functionDef = *ci;
00512 }
00513 }
00514
00515 int line, col;
00516 if (function != 0)
00517 function->getStartPosition(&line, &col);
00518 else if (functionDef != 0)
00519 functionDef->getStartPosition(&line, &col);
00520 else
00521 return;
00522 QString funcLine = m_activeEditor->textLine(line);
00523 unsigned int pos = 0;
00524 unsigned int length = funcLine.length();
00525 while (pos < length && funcLine.at(pos).isSpace())
00526 ++pos;
00527
00528 QString indentChars = funcLine.left(pos);
00529 QString text = indentChars + "\n";
00544 m_activeEditor->insertText(line, 0, text);
00545 m_cursor->setCursorPosition( line + 1, indentChars.length() + 3);
00546 }
00547 }
00548 }
00549
00550
00551 #include "doxygenpart.moc"