File indexing completed on 2024-04-21 15:55:34

0001 /*************************************************************************
0002    Copyright (C) 2003 by Jeroen Wijnhout (Jeroen.Wijnhout@kdemail.net)
0003                  2008-2016 by Michel Ludwig (michel.ludwig@kdemail.net)
0004  *************************************************************************/
0005 
0006 /***************************************************************************
0007  *                                                                         *
0008  *   This program is free software; you can redistribute it and/or modify  *
0009  *   it under the terms of the GNU General Public License as published by  *
0010  *   the Free Software Foundation; either version 2 of the License, or     *
0011  *   (at your option) any later version.                                   *
0012  *                                                                         *
0013  ***************************************************************************/
0014 
0015 #include "errorhandler.h"
0016 
0017 #include <QFileInfo>
0018 #include <QHash>
0019 #include <QLabel>
0020 #include <QMenu>
0021 #include <QRegExp>
0022 #include <QTabWidget>
0023 #include <QToolBar>
0024 #include <QToolButton>
0025 
0026 #include <KActionCollection>
0027 #include <KLocalizedString>
0028 #include <QUrl>
0029 #include <KTextEditor/Document>
0030 #include <KTextEditor/View>
0031 #include <KSelectAction>
0032 #include "kiledocmanager.h"
0033 #include "kileinfo.h"
0034 #include "kileproject.h"
0035 #include "kiletool_enums.h"
0036 #include "kileviewmanager.h"
0037 #include "outputinfo.h"
0038 #include "utilities.h"
0039 #include "widgets/logwidget.h"
0040 #include "widgets/outputview.h"
0041 #include "widgets/sidebar.h"
0042 
0043 KileErrorHandler::KileErrorHandler(QObject *parent, KileInfo *info, KActionCollection *ac)
0044     : QObject(parent), m_ki(info), m_errorHanderToolBar(Q_NULLPTR), m_currentLaTeXOutputHandler(Q_NULLPTR)
0045 {
0046     setObjectName("ErrorHandler");
0047 
0048     createActions(ac);
0049     setOutputActionsEnabled(false);
0050 
0051     m_compilationResultLabel = new QLabel();
0052 
0053     m_mainLogWidget = new KileWidget::LogWidget();
0054     m_mainLogWidget->setFocusPolicy(Qt::ClickFocus);
0055     m_mainLogWidget->setMinimumHeight(40);
0056 
0057     connect(m_mainLogWidget, SIGNAL(outputInfoSelected(OutputInfo)),
0058             this, SLOT(jumpToProblem(OutputInfo)));
0059     connect(m_mainLogWidget, SIGNAL(showingErrorMessage(QWidget*)),
0060             this, SIGNAL(showingErrorMessage(QWidget*)));
0061     connect(m_mainLogWidget, SIGNAL(showingErrorMessage(QWidget*)),
0062             this, SLOT(showMessagesOutput()));
0063     m_errorLogWidget = new KileWidget::LogWidget(KileWidget::LogWidget::NoHideActions);
0064     connect(m_errorLogWidget, SIGNAL(outputInfoSelected(OutputInfo)),
0065             this, SLOT(jumpToProblem(OutputInfo)));
0066     m_warningLogWidget = new KileWidget::LogWidget(KileWidget::LogWidget::NoHideActions);
0067     connect(m_warningLogWidget, SIGNAL(outputInfoSelected(OutputInfo)),
0068             this, SLOT(jumpToProblem(OutputInfo)));
0069     m_badBoxLogWidget = new KileWidget::LogWidget(KileWidget::LogWidget::NoHideActions);
0070     connect(m_badBoxLogWidget, SIGNAL(outputInfoSelected(OutputInfo)),
0071             this, SLOT(jumpToProblem(OutputInfo)));
0072 
0073     // FIXME: suggestions for icons: utilities-log-viewer, script-error, dialog-warning
0074     m_outputTabWidget = new QTabWidget();
0075     m_outputTabWidget->setTabPosition(QTabWidget::South);
0076     m_outputTabWidget->setTabsClosable(false);
0077     m_outputTabWidget->addTab(m_mainLogWidget, i18n("Messages"));
0078     m_outputTabWidget->addTab(m_errorLogWidget, i18n("Errors"));
0079     m_outputTabWidget->addTab(m_warningLogWidget, i18n("Warnings"));
0080     m_outputTabWidget->addTab(m_badBoxLogWidget, i18n("BadBoxes"));
0081 
0082     connect(m_ki->viewManager(), SIGNAL(textViewActivated(KTextEditor::View*)),
0083             this, SLOT(updateCurrentLaTeXOutputHandler()));
0084     connect(m_ki->viewManager(), SIGNAL(textViewClosed(KTextEditor::View*,bool)),
0085             this, SLOT(updateCurrentLaTeXOutputHandler()));
0086     connect(m_ki->docManager(), SIGNAL(documentOpened(KileDocument::TextInfo*)),
0087             this, SLOT(updateCurrentLaTeXOutputHandler()));
0088     connect(m_ki->docManager(), SIGNAL(projectOpened(KileProject*)),
0089             this, SLOT(handleProjectOpened(KileProject*)));
0090 
0091     showMessagesOutput();
0092 }
0093 
0094 KileErrorHandler::~KileErrorHandler()
0095 {
0096 }
0097 
0098 void KileErrorHandler::createActions(KActionCollection *ac)
0099 {
0100     m_viewLogAction = ac->addAction("ViewLog", this, SLOT(ViewLog()));
0101     m_viewLogAction->setText(i18n("View Log File"));
0102     ac->setDefaultShortcut(m_viewLogAction, QKeySequence(Qt::ALT + Qt::Key_0));
0103     m_viewLogAction->setIcon(QIcon::fromTheme("viewlog"));
0104 
0105     m_previousErrorAction = ac->addAction("PreviousError", this, SLOT(PreviousError()));
0106     m_previousErrorAction->setText(i18n("Previous LaTeX Error"));
0107     m_previousErrorAction->setIcon(QIcon::fromTheme("errorprev"));
0108 
0109     m_nextErrorAction = ac->addAction("NextError", this, SLOT(NextError()));
0110     m_nextErrorAction->setText(i18n("Next LaTeX Error"));
0111     m_nextErrorAction->setIcon(QIcon::fromTheme("errornext"));
0112 
0113     m_previousWarningAction = ac->addAction("PreviousWarning", this, SLOT(PreviousWarning()));
0114     m_previousWarningAction->setText(i18n("Previous LaTeX Warning"));
0115     m_previousWarningAction->setIcon(QIcon::fromTheme("warnprev"));
0116 
0117     m_nextWarningAction = ac->addAction("NextWarning", this, SLOT(NextWarning()));
0118     m_nextWarningAction->setText(i18n("Next LaTeX Warnings"));
0119     m_nextWarningAction->setIcon(QIcon::fromTheme("warnnext"));
0120 
0121     m_previousBadBoxAction = ac->addAction("PreviousBadBox", this, SLOT(PreviousBadBox()));
0122     m_previousBadBoxAction->setText(i18n("Previous LaTeX BadBox"));
0123     m_previousBadBoxAction->setIcon(QIcon::fromTheme("bboxprev"));
0124 
0125     m_nextBadBoxAction = ac->addAction("NextBadBox", this, SLOT(NextBadBox()));
0126     m_nextBadBoxAction->setText(i18n("Next LaTeX BadBox"));
0127     m_nextBadBoxAction->setIcon(QIcon::fromTheme("bboxnext"));
0128 }
0129 
0130 void KileErrorHandler::setErrorHandlerToolBar(QToolBar *toolBar)
0131 {
0132     m_errorHanderToolBar = toolBar;
0133     toolBar->addAction(m_viewLogAction);
0134     toolBar->addAction(m_previousErrorAction);
0135     toolBar->addAction(m_nextErrorAction);
0136     toolBar->addAction(m_previousWarningAction);
0137     toolBar->addAction(m_nextWarningAction);
0138     toolBar->addAction(m_previousBadBoxAction);
0139     toolBar->addAction(m_nextBadBoxAction);
0140 
0141 }
0142 
0143 void KileErrorHandler::setOutputActionsEnabled(bool b)
0144 {
0145     m_viewLogAction->setEnabled(b);
0146     m_previousErrorAction->setEnabled(b);
0147     m_nextErrorAction->setEnabled(b);
0148     m_previousWarningAction->setEnabled(b);
0149     m_nextWarningAction->setEnabled(b);
0150     m_previousBadBoxAction->setEnabled(b);
0151     m_nextBadBoxAction->setEnabled(b);
0152 }
0153 
0154 QLabel* KileErrorHandler::compilationResultLabel()
0155 {
0156     return m_compilationResultLabel;
0157 }
0158 
0159 QWidget* KileErrorHandler::outputWidget()
0160 {
0161     return m_outputTabWidget;
0162 }
0163 
0164 bool KileErrorHandler::areMessagesShown() const
0165 {
0166     return m_mainLogWidget->isShowingOutput();
0167 }
0168 
0169 void KileErrorHandler::addEmptyLineToMessages()
0170 {
0171     m_mainLogWidget->addEmptyLine();
0172 }
0173 
0174 void KileErrorHandler::startToolLogOutput()
0175 {
0176     m_mainLogWidget->startToolLogOutput();
0177 }
0178 
0179 void KileErrorHandler::endToolLogOutput()
0180 {
0181     m_mainLogWidget->endToolLogOutput();
0182 }
0183 
0184 void KileErrorHandler::printMessage(const QString& message)
0185 {
0186     m_mainLogWidget->printMessage(message);
0187 }
0188 
0189 void KileErrorHandler::printMessage(int type, const QString& message, const QString &tool,
0190                                     const OutputInfo& outputInfo, bool allowSelection,
0191                                     bool scroll)
0192 {
0193     m_mainLogWidget->printMessage(type, message, tool, outputInfo, allowSelection, scroll);
0194 }
0195 
0196 void KileErrorHandler::printProblem(int type, const QString& problem, const OutputInfo& outputInfo)
0197 {
0198     m_mainLogWidget->printProblem(type, problem, outputInfo);
0199 }
0200 
0201 void KileErrorHandler::clearMessages()
0202 {
0203     m_mainLogWidget->clear();
0204 }
0205 
0206 int KileErrorHandler::currentOutputTabIndex()
0207 {
0208     return m_outputTabWidget->currentIndex();
0209 }
0210 
0211 void KileErrorHandler::setCurrentOutputTab(int i)
0212 {
0213     if(i < 0 || i >= m_outputTabWidget->count()) {
0214         return;
0215     }
0216     m_outputTabWidget->setCurrentIndex(i);
0217 }
0218 
0219 void KileErrorHandler::showMessagesOutput()
0220 {
0221     m_outputTabWidget->setCurrentWidget(m_mainLogWidget);
0222 }
0223 
0224 void KileErrorHandler::showErrorsOutput()
0225 {
0226     m_outputTabWidget->setCurrentWidget(m_errorLogWidget);
0227 }
0228 
0229 void KileErrorHandler::showWarningsOutput()
0230 {
0231     m_outputTabWidget->setCurrentWidget(m_warningLogWidget);
0232 }
0233 
0234 void KileErrorHandler::showBadBoxesOutput()
0235 {
0236     m_outputTabWidget->setCurrentWidget(m_badBoxLogWidget);
0237 }
0238 
0239 void KileErrorHandler::handleProjectOpened(KileProject *project)
0240 {
0241     connect(project, SIGNAL(aboutToBeDestroyed(KileProject*)),
0242             this, SLOT(updateCurrentLaTeXOutputHandler()),
0243             Qt::UniqueConnection);
0244     connect(project, SIGNAL(projectItemAdded(KileProject*,KileProjectItem*)),
0245             this, SLOT(updateCurrentLaTeXOutputHandler()),
0246             Qt::UniqueConnection);
0247     connect(project, SIGNAL(projectItemRemoved(KileProject*,KileProjectItem*)),
0248             this, SLOT(updateCurrentLaTeXOutputHandler()),
0249             Qt::UniqueConnection);
0250 }
0251 
0252 void KileErrorHandler::handleLaTeXToolDone(KileTool::Base *tool, int i, bool childToolSpawned)
0253 {
0254     Q_UNUSED(i);
0255 
0256     KileTool::LaTeX *latex = dynamic_cast<KileTool::LaTeX*>(tool);
0257     if(!latex) {
0258         return;
0259     }
0260     if(childToolSpawned) {
0261         return;
0262     }
0263     if(latex->latexOutputHandler() == m_currentLaTeXOutputHandler) {
0264         updateForCompilationResult();
0265     }
0266 }
0267 
0268 void KileErrorHandler::handleSpawnedChildTool(KileTool::Base *parent, KileTool::Base *child)
0269 {
0270     if(!dynamic_cast<KileTool::LaTeX*>(parent) || !dynamic_cast<KileTool::LaTeX*>(child)) {
0271         return;
0272     }
0273 
0274     connect(child, SIGNAL(done(KileTool::Base*,int,bool)),
0275             this, SLOT(handleLaTeXToolDone(KileTool::Base*,int,bool)));
0276 }
0277 
0278 void KileErrorHandler::updateCurrentLaTeXOutputHandler()
0279 {
0280     LaTeXOutputHandler *h = Q_NULLPTR;
0281     m_ki->getCompileName(false, &h);
0282     if(h == m_currentLaTeXOutputHandler) {
0283         return;
0284     }
0285     m_currentLaTeXOutputHandler = h;
0286 
0287     if(!m_currentLaTeXOutputHandler) {
0288         setOutputActionsEnabled(false);
0289         clearErrorOutput();
0290     }
0291     else {
0292         setOutputActionsEnabled(true);
0293         updateForCompilationResult();
0294     }
0295     emit(currentLaTeXOutputHandlerChanged(m_currentLaTeXOutputHandler));
0296 }
0297 
0298 void KileErrorHandler::updateForCompilationResult()
0299 {
0300     if(!m_currentLaTeXOutputHandler) {
0301         return;
0302     }
0303     m_errorLogWidget->clear();
0304     displayProblemsInLogWidget(m_errorLogWidget, m_currentLaTeXOutputHandler->outputList(), KileErrorHandler::OnlyErrors);
0305     m_warningLogWidget->clear();
0306     displayProblemsInLogWidget(m_warningLogWidget, m_currentLaTeXOutputHandler->outputList(), KileErrorHandler::OnlyWarnings);
0307     m_badBoxLogWidget->clear();
0308     displayProblemsInLogWidget(m_badBoxLogWidget, m_currentLaTeXOutputHandler->outputList(), KileErrorHandler::OnlyBadBoxes);
0309 
0310     const int nErrors = m_currentLaTeXOutputHandler->numberOfErrors();
0311     const int nWarnings = m_currentLaTeXOutputHandler->numberOfWarnings();
0312     const int nBadBoxes = m_currentLaTeXOutputHandler->numberOfBadBoxes();
0313     QString errorString, warningString, badBoxString;
0314 
0315     if(nErrors >= 0) {
0316         errorString = i18n("Errors: %1", nErrors);
0317     }
0318     if(nWarnings >= 0) {
0319         warningString = i18n("Warnings: %1", nWarnings);
0320     }
0321     if(nBadBoxes >= 0) {
0322         badBoxString = i18n("BadBoxes: %1", nBadBoxes);
0323     }
0324 
0325     m_compilationResultLabel->setText(i18nc("Result of the compilation w.r.t. number of errors/warnings/badboxes",
0326                                             "%1  %2  %3", errorString, warningString, badBoxString));
0327 }
0328 
0329 void KileErrorHandler::clearErrorOutput()
0330 {
0331     m_compilationResultLabel->clear();
0332     m_errorLogWidget->clear();
0333     m_warningLogWidget->clear();
0334     m_badBoxLogWidget->clear();
0335 }
0336 
0337 void KileErrorHandler::setMostRecentLogInformation(const QString& logFile, const LatexOutputInfoArray& outputInfoList)
0338 {
0339     Q_UNUSED(logFile);
0340 
0341     // add them to the log widget
0342     displayProblemsInMainLogWidget(outputInfoList);
0343 }
0344 
0345 void KileErrorHandler::displayProblemsInMainLogWidget(const LatexOutputInfoArray& infoList)
0346 {
0347     displayProblemsInLogWidget(m_mainLogWidget, infoList);
0348 }
0349 
0350 void KileErrorHandler::displayProblemsInLogWidget(KileWidget::LogWidget *logWidget, const LatexOutputInfoArray& infoList, ProblemType problemType)
0351 {
0352     QString message;
0353     int type = KileTool::Info;
0354 
0355     //print detailed error info
0356     logWidget->setUpdatesEnabled(false);
0357 
0358     for(QList<LatexOutputInfo>::const_iterator i = infoList.begin();
0359             i != infoList.end(); ++i) {
0360 
0361         const LatexOutputInfo& info = *i;
0362         message = info.source() + ':' + QString::number(info.sourceLine()) + ':' + info.message();
0363         switch(info.type()) {
0364         case LatexOutputInfo::itmBadBox:
0365             if(problemType == AllProblems || problemType == OnlyBadBoxes) {
0366                 type = KileTool::ProblemBadBox;
0367             }
0368             else {
0369                 continue;
0370             }
0371             break;
0372         case LatexOutputInfo::itmError:
0373             if(problemType == AllProblems || problemType == OnlyErrors) {
0374                 type = KileTool::ProblemError;
0375             }
0376             else {
0377                 continue;
0378             }
0379             break;
0380         case LatexOutputInfo::itmWarning:
0381             if(problemType == AllProblems || problemType == OnlyWarnings) {
0382                 type = KileTool::ProblemWarning;
0383             }
0384             else {
0385                 continue;
0386             }
0387             break;
0388         default:
0389             type = KileTool::Info;
0390             break;
0391         }
0392         KileWidget::LogWidget::ProblemInformation problem;
0393         problem.type = type;
0394         problem.message = message;
0395         problem.outputInfo = info;
0396         logWidget->printMessage(type, message, QString(), info, false, false);
0397     }
0398 
0399     logWidget->setUpdatesEnabled(true);
0400     logWidget->scrollToBottom();
0401 }
0402 
0403 void KileErrorHandler::printNoInformationAvailable()
0404 {
0405     m_mainLogWidget->printMessage(i18n("No information about warnings or errors is available."));
0406 }
0407 
0408 void KileErrorHandler::ViewLog()
0409 {
0410     if(!m_currentLaTeXOutputHandler) {
0411         printNoInformationAvailable();
0412         return;
0413     }
0414 
0415     const LatexOutputInfoArray& outputInfoList = m_currentLaTeXOutputHandler->outputList();
0416 
0417     KileWidget::LogWidget *logWidget = m_mainLogWidget;
0418     m_ki->focusLog();
0419 
0420     QFile logFile(m_currentLaTeXOutputHandler->logFile());
0421     if(!m_currentLaTeXOutputHandler->logFile().isEmpty() && logFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
0422         QHash<int, OutputInfo> hash;
0423 
0424         for(QList<LatexOutputInfo>::const_iterator i = outputInfoList.begin();
0425                 i != outputInfoList.end(); ++i) {
0426             LatexOutputInfo info = *i;
0427             hash[info.outputLine()] = info;
0428         }
0429 
0430         QTextStream textStream(&logFile);
0431 
0432         for(int lineNumber = 0; !textStream.atEnd(); ++lineNumber) {
0433             int type = -1;
0434             const QString line = textStream.readLine();
0435             if(hash.find(lineNumber) != hash.end()) {
0436                 switch(hash[lineNumber].type()) {
0437                 case LatexOutputInfo::itmError:
0438                     type = KileTool::Error;
0439                     break;
0440                 case LatexOutputInfo::itmWarning:
0441                     type = KileTool::Warning;
0442                     break;
0443                 case LatexOutputInfo::itmBadBox:
0444                     type = KileTool::ProblemBadBox;
0445                     break;
0446                 }
0447             }
0448             // don't scroll to the item as this will lead to severely degraded performance
0449             logWidget->printMessage(type, line, QString(), hash[lineNumber], true, false);
0450         }
0451 
0452         logWidget->scrollToBottom();
0453     }
0454     else {
0455         logWidget->printProblem(KileTool::Error, i18n("Cannot open log file; did you run LaTeX?"));
0456     }
0457 }
0458 
0459 void KileErrorHandler::jumpToFirstError()
0460 {
0461     if(!m_currentLaTeXOutputHandler) {
0462         printNoInformationAvailable();
0463         return;
0464     }
0465 
0466     const LatexOutputInfoArray& outputInfoList = m_currentLaTeXOutputHandler->outputList();
0467 
0468     int sz = outputInfoList.size();
0469     for(int i = 0; i < sz; ++i) {
0470         if(outputInfoList[i].type() == LatexOutputInfo::itmError) {
0471             // this has to be before calling 'jumpToProblem' as this might change 'm_currentLaTeXOutputHandler'
0472             m_currentLaTeXOutputHandler->setCurrentError(i);
0473             jumpToProblem(outputInfoList[i]);
0474             break;
0475         }
0476     }
0477 }
0478 
0479 void KileErrorHandler::jumpToProblem(const OutputInfo& info)
0480 {
0481     QString file = m_ki->getFullFromPrettyName(info, info.source());
0482 
0483     if(!file.isEmpty()) {
0484         m_ki->docManager()->fileOpen(QUrl::fromLocalFile(file));
0485         int line = (info.sourceLine() > 0) ? (info.sourceLine() - 1) : 0;
0486 
0487         KTextEditor::Document *doc = m_ki->docManager()->docFor(QUrl::fromLocalFile(file));
0488         if(doc) {
0489             KTextEditor::View* view = doc->views().first();
0490             if(view) {
0491                 view->setCursorPosition(KTextEditor::Cursor(line, 0));
0492             }
0493         }
0494     }
0495 }
0496 
0497 void KileErrorHandler::jumpToProblem(int type, bool forward)
0498 {
0499     if(!m_currentLaTeXOutputHandler) {
0500         printNoInformationAvailable();
0501         return;
0502     }
0503 
0504     const LatexOutputInfoArray& outputInfoList = m_currentLaTeXOutputHandler->outputList();
0505 
0506     if (!outputInfoList.isEmpty()) {
0507         int sz = outputInfoList.size();
0508         int pl = forward ? 1 : -1;
0509         bool found = false;
0510 
0511         //look for next problem of requested type
0512         for(int i = 0; i < sz; ++i) {
0513             //always look at the whole outputInfo array, but start
0514             //at the problem adjacent to the current error
0515             //if we go beyond the bounds of the array we use
0516             //a simple "modulo" calculation to get within bounds again
0517             int index = (m_currentLaTeXOutputHandler->currentError() + (i + 1) *pl) % sz;
0518             while(index < 0) {
0519                 index += sz;
0520             }
0521 
0522             if(outputInfoList[index].type() == type) {
0523                 m_currentLaTeXOutputHandler->setCurrentError(index);
0524                 found = true;
0525                 break;
0526             }
0527         }
0528 
0529         if(!found) {
0530             return;
0531         }
0532 
0533         //If the log file is being viewed, use this to jump to the errors,
0534         //otherwise, use the error summary display
0535         m_mainLogWidget->highlight(outputInfoList[m_currentLaTeXOutputHandler->currentError()]);
0536 
0537         jumpToProblem(outputInfoList[m_currentLaTeXOutputHandler->currentError()]);
0538     }
0539 
0540     if(outputInfoList.isEmpty()) {
0541         m_mainLogWidget->printMessage(i18n("No LaTeX warnings/errors detected."));
0542     }
0543 }
0544 
0545 void KileErrorHandler::NextError()
0546 {
0547     jumpToProblem(LatexOutputInfo::itmError, true);
0548 }
0549 
0550 void KileErrorHandler::PreviousError()
0551 {
0552     jumpToProblem(LatexOutputInfo::itmError, false);
0553 }
0554 
0555 void KileErrorHandler::NextWarning()
0556 {
0557     jumpToProblem(LatexOutputInfo::itmWarning, true);
0558 }
0559 
0560 void KileErrorHandler::PreviousWarning()
0561 {
0562     jumpToProblem(LatexOutputInfo::itmWarning, false);
0563 }
0564 
0565 void KileErrorHandler::NextBadBox()
0566 {
0567     jumpToProblem(LatexOutputInfo::itmBadBox, true);
0568 }
0569 
0570 void KileErrorHandler::PreviousBadBox()
0571 {
0572     jumpToProblem(LatexOutputInfo::itmBadBox, false);
0573 }
0574