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

0001 /********************************************************************************
0002   Copyright (C) 2011-2022 by Michel Ludwig (michel.ludwig@kdemail.net)
0003  ********************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or modify  *
0008  *   it under the terms of the GNU General Public License as published by  *
0009  *   the Free Software Foundation; either version 2 of the License, or     *
0010  *   (at your option) any later version.                                   *
0011  *                                                                         *
0012  ***************************************************************************/
0013 
0014 #include "livepreview.h"
0015 #include "config.h"
0016 
0017 #include <algorithm>
0018 
0019 #include <QCryptographicHash>
0020 #include <QDir>
0021 #include <QHBoxLayout>
0022 #include <QMap>
0023 #include <QFile>
0024 #include <QFileInfo>
0025 #include <QStandardPaths>
0026 #include <QTextCodec>
0027 #include <QTextStream>
0028 #include <QTimer>
0029 #include <QTemporaryDir>
0030 
0031 #include <KActionCollection>
0032 #include <KLocalizedString>
0033 #include <KTextEditor/Application>
0034 #include <KTextEditor/CodeCompletionInterface>
0035 #include <KTextEditor/Document>
0036 #include <KTextEditor/MainWindow>
0037 #include <KTextEditor/View>
0038 #include <KToolBar>
0039 #include <KParts/MainWindow>
0040 #include <KXMLGUIFactory>
0041 
0042 #include <okular/interfaces/viewerinterface.h>
0043 
0044 #include "errorhandler.h"
0045 #include "kiledebug.h"
0046 #include "kiletool_enums.h"
0047 #include "kiledocmanager.h"
0048 #include "kileviewmanager.h"
0049 
0050 //TODO: it still has to be checked whether it is necessary to use LaTeXInfo objects
0051 
0052 namespace KileTool
0053 {
0054 
0055 class LivePreviewManager::PreviewInformation {
0056 public:
0057     PreviewInformation()
0058         : lastSynchronizationCursor(-1, -1)
0059     {
0060         initTemporaryDirectory();
0061     }
0062 
0063     ~PreviewInformation() {
0064         delete m_tempDir;
0065     }
0066 
0067     QString getTempDir() const {
0068         return m_tempDir->path();
0069     }
0070 
0071     void clearPreviewPathMappings() {
0072         pathToPreviewPathHash.clear();
0073         previewPathToPathHash.clear();
0074     }
0075 
0076     bool createSubDirectoriesForProject(KileProject *project, bool *containsInvalidRelativeItem = Q_NULLPTR) {
0077         if(containsInvalidRelativeItem) {
0078             *containsInvalidRelativeItem = false;
0079         }
0080         const QList<KileProjectItem*> items = project->items();
0081         const QString tempCanonicalDir = QDir(m_tempDir->path()).canonicalPath();
0082         if(tempCanonicalDir.isEmpty()) {
0083             return false;
0084         }
0085         for(KileProjectItem *item : items) {
0086             bool successful = true;
0087             const QString itemRelativeDir = QFileInfo(tempCanonicalDir + '/' + item->path()).path();
0088             const QString itemAbsolutePath = QDir(itemRelativeDir).absolutePath();
0089             if(itemAbsolutePath.isEmpty()) {
0090                 successful = false;
0091             }
0092             else if(!itemAbsolutePath.startsWith(tempCanonicalDir)) {
0093                 if(containsInvalidRelativeItem) {
0094                     *containsInvalidRelativeItem = true;
0095                 }
0096                 successful = false; // we don't want to create directories below 'm_tempDir->name()'
0097             }
0098             else {
0099                 successful = QDir().mkpath(itemAbsolutePath);
0100             }
0101             if(!successful) {
0102                 return false;
0103             }
0104         }
0105         return true;
0106     }
0107 
0108     void setLastSynchronizationCursor(int line, int col)
0109     {
0110         lastSynchronizationCursor.setLine(line);
0111         lastSynchronizationCursor.setColumn(col);
0112     }
0113 
0114 private:
0115     QTemporaryDir *m_tempDir;
0116 
0117     void initTemporaryDirectory() {
0118         m_tempDir = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + "kile-livepreview");
0119     }
0120 
0121 public:
0122     QHash<QString, QString> pathToPreviewPathHash;
0123     QHash<QString, QString> previewPathToPathHash;
0124     QString previewFile;
0125     QHash<KileDocument::TextInfo*, QByteArray> textHash;
0126     KTextEditor::Cursor lastSynchronizationCursor;
0127 };
0128 
0129 LivePreviewManager::LivePreviewManager(KileInfo *ki, KActionCollection *ac)
0130     : m_ki(ki),
0131       m_bootUpMode(true),
0132       m_previewStatusLed(Q_NULLPTR),
0133       m_previewForCurrentDocumentAction(Q_NULLPTR),
0134       m_recompileLivePreviewAction(Q_NULLPTR),
0135       m_runningLaTeXInfo(Q_NULLPTR), m_runningTextView(Q_NULLPTR), m_runningProject(Q_NULLPTR),
0136       m_runningPreviewInformation(Q_NULLPTR), m_shownPreviewInformation(Q_NULLPTR), m_masterDocumentPreviewInformation(Q_NULLPTR)
0137 {
0138     connect(m_ki->viewManager(), SIGNAL(textViewActivated(KTextEditor::View*)),
0139             this, SLOT(handleTextViewActivated(KTextEditor::View*)));
0140     connect(m_ki->viewManager(), SIGNAL(textViewClosed(KTextEditor::View*,bool)),
0141             this, SLOT(handleTextViewClosed(KTextEditor::View*,bool)));
0142     connect(m_ki->toolManager(), SIGNAL(childToolSpawned(KileTool::Base*,KileTool::Base*)),
0143             this, SLOT(handleSpawnedChildTool(KileTool::Base*,KileTool::Base*)));
0144     connect(m_ki->docManager(), SIGNAL(documentSavedAs(KTextEditor::View*,KileDocument::TextInfo*)),
0145             this, SLOT(handleDocumentSavedAs(KTextEditor::View*,KileDocument::TextInfo*)));
0146     connect(m_ki->docManager(), SIGNAL(documentOpened(KileDocument::TextInfo*)),
0147             this, SLOT(handleDocumentOpened(KileDocument::TextInfo*)));
0148     connect(m_ki->docManager(), SIGNAL(projectOpened(KileProject*)),
0149             this, SLOT(handleProjectOpened(KileProject*)));
0150 
0151     createActions(ac);
0152     populateViewerControlToolBar();
0153 
0154     m_ledBlinkingTimer = new QTimer(this);
0155     m_ledBlinkingTimer->setSingleShot(false);
0156     m_ledBlinkingTimer->setInterval(500);
0157     connect(m_ledBlinkingTimer, SIGNAL(timeout()), m_previewStatusLed, SLOT(toggle()));
0158 
0159     m_documentChangedTimer = new QTimer(this);
0160     m_documentChangedTimer->setSingleShot(true);
0161     connect(m_documentChangedTimer, SIGNAL(timeout()), this, SLOT(handleDocumentModificationTimerTimeout()));
0162 
0163     showPreviewDisabled();
0164 }
0165 
0166 LivePreviewManager::~LivePreviewManager()
0167 {
0168     KILE_DEBUG_MAIN;
0169 
0170     qDeleteAll(m_livePreviewToolActionList);
0171     m_livePreviewToolActionList.clear();
0172 
0173     deleteAllLivePreviewInformation();
0174 }
0175 
0176 void LivePreviewManager::disableBootUpMode()
0177 {
0178     m_bootUpMode = false;
0179     recompileLivePreview();
0180 }
0181 
0182 void LivePreviewManager::createActions(KActionCollection *ac)
0183 {
0184 
0185     m_livePreviewToolActionGroup = new QActionGroup(ac);
0186 
0187     m_previewForCurrentDocumentAction = new KToggleAction(QIcon::fromTheme("document-preview"), i18n("Live Preview for Current Document or Project"), this);
0188     m_previewForCurrentDocumentAction->setChecked(true);
0189     connect(m_previewForCurrentDocumentAction, SIGNAL(triggered(bool)), this, SLOT(previewForCurrentDocumentActionTriggered(bool)));
0190     ac->addAction("live_preview_for_current_document", m_previewForCurrentDocumentAction);
0191 
0192     m_recompileLivePreviewAction = new QAction(i18n("Recompile Live Preview"), this);
0193     connect(m_recompileLivePreviewAction, SIGNAL(triggered()), this, SLOT(recompileLivePreview()));
0194     ac->addAction("live_preview_recompile", m_recompileLivePreviewAction);
0195 
0196     {
0197         QAction *action = new QAction(i18n("Save Compiled Document..."), this);
0198         connect(action, &QAction::triggered, m_ki->docManager(), &KileDocument::Manager::fileSaveCompiledDocument);
0199         ac->addAction("file_save_compiled_document", action);
0200         connect(this, &KileTool::LivePreviewManager::livePreviewSuccessful, action, [=]() { action->setEnabled(true); });
0201         connect(this, &KileTool::LivePreviewManager::livePreviewRunning, action, [=]() { action->setEnabled(false); });
0202         connect(this, &KileTool::LivePreviewManager::livePreviewStopped, action, [=]() { action->setEnabled(false); });
0203     }
0204 }
0205 
0206 void LivePreviewManager::previewForCurrentDocumentActionTriggered(bool b)
0207 {
0208     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
0209         return;
0210     }
0211     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
0212     if(!view) {
0213         return;
0214     }
0215     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
0216     if(!latexInfo) {
0217         return;
0218     }
0219     LivePreviewUserStatusHandler *userStatusHandler;
0220     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
0221     Q_ASSERT(userStatusHandler);
0222 
0223     userStatusHandler->setLivePreviewEnabled(b);
0224 
0225     if(b) {
0226         showPreviewCompileIfNecessary(latexInfo, view);
0227     }
0228     else {
0229         disablePreview();
0230     }
0231 }
0232 
0233 void LivePreviewManager::livePreviewToolActionTriggered()
0234 {
0235     QAction *action = dynamic_cast<QAction*>(sender());
0236     if(!action) {
0237         KILE_DEBUG_MAIN << "slot called from wrong object!!";
0238         return;
0239     }
0240     if(!m_actionToLivePreviewToolHash.contains(action)) {
0241         KILE_DEBUG_MAIN << "action not found in hash!!";
0242         return;
0243     }
0244     const ToolConfigPair p = m_actionToLivePreviewToolHash[action];
0245     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
0246     if(!view) {
0247         KILE_DEBUG_MAIN << "no text view open!";
0248         return;
0249     }
0250     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
0251     if(!latexInfo) {
0252         KILE_DEBUG_MAIN << "current view is not LaTeX-compatible!";
0253         return;
0254     }
0255 
0256     LivePreviewUserStatusHandler *userStatusHandler;
0257     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
0258     if(!userStatusHandler) {
0259         KILE_DEBUG_MAIN << "no preview information found!";
0260         return;
0261     }
0262     const bool changed = userStatusHandler->setLivePreviewTool(p);
0263     if(changed) {
0264         recompileLivePreview();
0265     }
0266 }
0267 
0268 void LivePreviewManager::updateLivePreviewToolActions(LivePreviewUserStatusHandler *userStatusHandler)
0269 {
0270     setLivePreviewToolActionsEnabled(true);
0271     const ToolConfigPair p = userStatusHandler->livePreviewTool();
0272     if(!m_livePreviewToolToActionHash.contains(p)) {
0273         return;
0274     }
0275     m_livePreviewToolToActionHash[p]->setChecked(true);
0276 }
0277 
0278 void LivePreviewManager::setLivePreviewToolActionsEnabled(bool b)
0279 {
0280     for(QAction *action : m_livePreviewToolActionList) {
0281         action->setEnabled(b);
0282     }
0283 }
0284 
0285 void LivePreviewManager::buildLivePreviewMenu(KConfig *config)
0286 {
0287     QMenu *menu = dynamic_cast<QMenu*>(m_ki->mainWindow()->guiFactory()->container("menu_livepreview", m_ki->mainWindow()));
0288     if(!menu) {
0289         KILE_DEBUG_MAIN << "live preview menu not found!!";
0290         return;
0291     }
0292 
0293     qDeleteAll(m_livePreviewToolActionList);
0294     m_livePreviewToolActionList.clear();
0295     m_livePreviewToolToActionHash.clear();
0296     m_actionToLivePreviewToolHash.clear();
0297 
0298     // necessary as it will be disabled otherwise in 'kile.cpp' (as it's empty initially)
0299     menu->setEnabled(true);
0300     menu->clear();
0301     menu->addAction(m_previewForCurrentDocumentAction);
0302     menu->addSeparator();
0303 
0304     QList<ToolConfigPair> toolListConfig = toolsWithConfigurationsBasedOnClass(config, "LaTeXLivePreview");
0305     std::sort(toolListConfig.begin(), toolListConfig.end());
0306     for(QList<ToolConfigPair>::iterator i = toolListConfig.begin(); i != toolListConfig.end(); ++i) {
0307         const QString shortToolName = QString((*i).first).remove("LivePreview-");
0308         QAction *action = new KToggleAction(ToolConfigPair::userStringRepresentation(shortToolName, (*i).second), this);
0309 
0310         m_livePreviewToolActionGroup->addAction(action);
0311         connect(action, SIGNAL(triggered()), this, SLOT(livePreviewToolActionTriggered()));
0312         m_livePreviewToolActionList.push_back(action);
0313         m_livePreviewToolToActionHash[*i] = action;
0314         m_actionToLivePreviewToolHash[action] = *i;
0315         menu->addAction(action);
0316     }
0317     menu->addSeparator();
0318     menu->addAction(m_recompileLivePreviewAction);
0319 }
0320 
0321 QString LivePreviewManager::getPreviewFile() const
0322 {
0323     if(!m_shownPreviewInformation) {
0324         return QString();
0325     }
0326     return m_shownPreviewInformation->previewFile;
0327 }
0328 
0329 bool LivePreviewManager::isLivePreviewEnabledForCurrentDocument()
0330 {
0331     return m_previewForCurrentDocumentAction->isChecked();
0332 }
0333 
0334 void LivePreviewManager::setLivePreviewEnabledForCurrentDocument(bool b)
0335 {
0336     m_previewForCurrentDocumentAction->setChecked(b);
0337     previewForCurrentDocumentActionTriggered(b);
0338 }
0339 
0340 void LivePreviewManager::disablePreview()
0341 {
0342     stopAndClearPreview();
0343     setLivePreviewToolActionsEnabled(false);
0344     m_previewForCurrentDocumentAction->setChecked(false);
0345     m_ki->viewManager()->setLivePreviewModeForDocumentViewer(false);
0346 }
0347 
0348 void LivePreviewManager::stopAndClearPreview()
0349 {
0350     KILE_DEBUG_MAIN;
0351     stopLivePreview();
0352     clearLivePreview();
0353 }
0354 
0355 void LivePreviewManager::clearLivePreview()
0356 {
0357     KILE_DEBUG_MAIN;
0358     showPreviewDisabled();
0359 
0360     KParts::ReadOnlyPart *viewerPart = m_ki->viewManager()->viewerPart();
0361     if(m_shownPreviewInformation && viewerPart->url() == QUrl::fromLocalFile(m_shownPreviewInformation->previewFile)) {
0362         viewerPart->closeUrl();
0363     }
0364     m_shownPreviewInformation = Q_NULLPTR;
0365     emit(livePreviewStopped());
0366 }
0367 
0368 void LivePreviewManager::stopLivePreview()
0369 {
0370     m_documentChangedTimer->stop();
0371     m_ki->toolManager()->stopLivePreview();
0372 
0373     clearRunningLivePreviewInformation();
0374 }
0375 
0376 void LivePreviewManager::clearRunningLivePreviewInformation()
0377 {
0378     m_runningPathToPreviewPathHash.clear();
0379     m_runningPreviewPathToPathHash.clear();
0380     m_runningPreviewFile.clear();
0381     m_runningLaTeXInfo = Q_NULLPTR;
0382     m_runningProject = Q_NULLPTR;
0383     m_runningTextView = Q_NULLPTR;
0384     m_runningPreviewInformation = Q_NULLPTR;
0385     m_runningTextHash.clear();
0386 }
0387 
0388 void LivePreviewManager::deleteAllLivePreviewInformation()
0389 {
0390     // first, we have to make sure that nothing is shown anymore,
0391     // and that no preview is running
0392     stopAndClearPreview();
0393 
0394     disablePreview();
0395 
0396     // and now we can delete all the 'PreviewInformation' objects
0397     delete m_masterDocumentPreviewInformation;
0398     m_masterDocumentPreviewInformation = Q_NULLPTR;
0399 
0400     for(QHash<KileDocument::LaTeXInfo*, PreviewInformation*>::iterator i = m_latexInfoToPreviewInformationHash.begin();
0401             i != m_latexInfoToPreviewInformationHash.end(); ++i) {
0402         delete i.value();
0403     }
0404 
0405     for(QHash<KileProject*,PreviewInformation*>::iterator i = m_projectToPreviewInformationHash.begin();
0406             i != m_projectToPreviewInformationHash.end(); ++i) {
0407         delete i.value();
0408     }
0409     m_latexInfoToPreviewInformationHash.clear();
0410     m_projectToPreviewInformationHash.clear();
0411 }
0412 
0413 void LivePreviewManager::readConfig(KConfig *config)
0414 {
0415     Q_UNUSED(config);
0416 
0417     buildLivePreviewMenu(config);
0418 
0419     m_previewForCurrentDocumentAction->setEnabled(KileConfig::livePreviewEnabled());
0420     m_previewStatusLed->setEnabled(KileConfig::livePreviewEnabled());
0421 
0422     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
0423         deleteAllLivePreviewInformation();
0424     }
0425     else {
0426         refreshLivePreview(); // e.g. in case the live preview was disabled and no preview is
0427         // currently shown
0428     }
0429 }
0430 
0431 void LivePreviewManager::writeConfig()
0432 {
0433 }
0434 
0435 void LivePreviewManager::readLivePreviewStatusSettings(KConfigGroup &configGroup, LivePreviewUserStatusHandler *handler)
0436 {
0437     // the prefix 'kile_' is necessary as these settings might be written into a config group that is also modified
0438     // by KatePart
0439     if(configGroup.readEntry("kile_livePreviewStatusUserSpecified", false)) {
0440         handler->setLivePreviewEnabled(configGroup.readEntry("kile_livePreviewEnabled", true));
0441     }
0442 
0443     const QString livePreviewToolConfigString = configGroup.readEntry("kile_livePreviewTool", "");
0444     if(livePreviewToolConfigString.isEmpty()) {
0445         // if nothing is set for this fallback to the configured global default, otherwise to the hardcoded default
0446         QString defaultToolName = KileConfig::livePreviewDefaultTool();
0447         if(defaultToolName.isEmpty()) {
0448             defaultToolName = LIVEPREVIEW_DEFAULT_TOOL_NAME;
0449         }
0450         KileTool::ToolConfigPair defaultTool = KileTool::ToolConfigPair::fromConfigStringRepresentation(defaultToolName);
0451         handler->setLivePreviewTool(defaultTool);
0452     }
0453     else {
0454         handler->setLivePreviewTool(ToolConfigPair::fromConfigStringRepresentation(livePreviewToolConfigString));
0455     }
0456 }
0457 
0458 void LivePreviewManager::writeLivePreviewStatusSettings(KConfigGroup &configGroup, LivePreviewUserStatusHandler *handler)
0459 {
0460     configGroup.writeEntry("kile_livePreviewTool", handler->livePreviewTool().configStringRepresentation());
0461     configGroup.writeEntry("kile_livePreviewEnabled", handler->isLivePreviewEnabled());
0462     configGroup.writeEntry("kile_livePreviewStatusUserSpecified", handler->userSpecifiedLivePreviewStatus());
0463 }
0464 
0465 void LivePreviewManager::populateViewerControlToolBar()
0466 {
0467     KToolBar* viewerControlToolBar = m_ki->viewManager()->getViewerControlToolBar();
0468     viewerControlToolBar->addAction(m_previewForCurrentDocumentAction);
0469 
0470     m_previewStatusLed = new KLed(viewerControlToolBar);
0471     m_previewStatusLed->setShape(KLed::Circular);
0472     m_previewStatusLed->setLook(KLed::Flat);
0473     viewerControlToolBar->addWidget(m_previewStatusLed);
0474 }
0475 
0476 void LivePreviewManager::handleMasterDocumentChanged()
0477 {
0478     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
0479         return;
0480     }
0481 
0482     deleteAllLivePreviewInformation();
0483     refreshLivePreview();
0484 }
0485 
0486 void LivePreviewManager::handleTextChanged(KTextEditor::Document *doc)
0487 {
0488     if(m_bootUpMode || !KileConfig::livePreviewEnabled()
0489                     || !isLivePreviewEnabledForCurrentDocument()) {
0490         return;
0491     }
0492 
0493     KILE_DEBUG_MAIN;
0494     if(!isCurrentDocumentOrProject(doc)) {
0495         return;
0496     }
0497 
0498     stopLivePreview();
0499     showPreviewOutOfDate();
0500 
0501     if(!KileConfig::livePreviewCompileOnlyAfterSaving()) {
0502         m_documentChangedTimer->start(KileConfig::livePreviewCompilationDelay());
0503     }
0504 }
0505 
0506 void LivePreviewManager::handleDocumentSavedOrUploaded(KTextEditor::Document *doc, bool savedAs)
0507 {
0508     Q_UNUSED(savedAs);
0509 
0510     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
0511         return;
0512     }
0513 
0514     KILE_DEBUG_MAIN;
0515 
0516     if(!KileConfig::livePreviewCompileOnlyAfterSaving()) {
0517         return;
0518     }
0519 
0520     if(!isCurrentDocumentOrProject(doc)) {
0521         return;
0522     }
0523     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
0524     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
0525     if(!latexInfo) {
0526         return;
0527     }
0528 
0529     LivePreviewUserStatusHandler *userStatusHandler;
0530     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
0531     Q_ASSERT(userStatusHandler);
0532     if(userStatusHandler->isLivePreviewEnabled()) {
0533         showPreviewCompileIfNecessary(latexInfo, view);
0534     }
0535 }
0536 
0537 void LivePreviewManager::handleDocumentModificationTimerTimeout()
0538 {
0539     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
0540         return;
0541     }
0542 
0543     KILE_DEBUG_MAIN;
0544 
0545     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
0546     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
0547     if(!latexInfo) {
0548         return;
0549     }
0550 
0551     KTextEditor::CodeCompletionInterface *codeCompletionInterface = qobject_cast<KTextEditor::CodeCompletionInterface*>(view);
0552 
0553     // if the code completion box is currently shown, we don't trigger an update of the preview
0554     // as this will cause the document to be saved and the completion box to be hidden as a consequence
0555     if(codeCompletionInterface && codeCompletionInterface->isCompletionActive()) {
0556         m_documentChangedTimer->start();
0557         return;
0558     }
0559 
0560     LivePreviewUserStatusHandler *userStatusHandler;
0561     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
0562     Q_ASSERT(userStatusHandler);
0563     if(userStatusHandler->isLivePreviewEnabled()) {
0564         compilePreview(latexInfo, view);
0565     }
0566 }
0567 
0568 void LivePreviewManager::showPreviewDisabled()
0569 {
0570     KILE_DEBUG_MAIN;
0571     m_ledBlinkingTimer->stop();
0572     if(m_previewStatusLed) {
0573         m_previewStatusLed->off();
0574     }
0575 }
0576 
0577 void LivePreviewManager::showPreviewRunning()
0578 {
0579     KILE_DEBUG_MAIN;
0580     if(m_previewStatusLed) {
0581         m_previewStatusLed->setColor(QColor(Qt::yellow));
0582         m_previewStatusLed->off();
0583     }
0584     m_ledBlinkingTimer->start();
0585 }
0586 
0587 void LivePreviewManager::showPreviewFailed()
0588 {
0589     KILE_DEBUG_MAIN;
0590     m_ledBlinkingTimer->stop();
0591     if(m_previewStatusLed) {
0592         m_previewStatusLed->on();
0593         m_previewStatusLed->setColor(QColor(Qt::red));
0594     }
0595 }
0596 
0597 void LivePreviewManager::showPreviewSuccessful()
0598 {
0599     KILE_DEBUG_MAIN;
0600     m_ledBlinkingTimer->stop();
0601     if(m_previewStatusLed) {
0602         m_previewStatusLed->on();
0603         m_previewStatusLed->setColor(QColor(Qt::green));
0604     }
0605 }
0606 
0607 void LivePreviewManager::showPreviewOutOfDate()
0608 {
0609     KILE_DEBUG_MAIN;
0610     m_ledBlinkingTimer->stop();
0611     if(m_previewStatusLed) {
0612         m_previewStatusLed->on();
0613         m_previewStatusLed->setColor(QColor(Qt::yellow));
0614     }
0615 
0616 }
0617 
0618 // If a LaTeXInfo* pointer is passed as first argument, it is guaranteed that '*userStatusHandler' won't be Q_NULLPTR.
0619 LivePreviewManager::PreviewInformation* LivePreviewManager::findPreviewInformation(KileDocument::TextInfo *textInfo,
0620         KileProject* *locatedProject,
0621         LivePreviewUserStatusHandler* *userStatusHandler,
0622         LaTeXOutputHandler* *latexOutputHandler)
0623 {
0624     const QString masterDocumentFileName = m_ki->getMasterDocumentFileName();
0625     if(locatedProject) {
0626         *locatedProject = Q_NULLPTR;
0627     }
0628     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(textInfo);
0629     if(userStatusHandler) {
0630         *userStatusHandler = latexInfo;
0631     }
0632     if(latexOutputHandler) {
0633         *latexOutputHandler = latexInfo;
0634     }
0635     if(!masterDocumentFileName.isEmpty()) {
0636         KILE_DEBUG_MAIN << "master document defined";
0637         return m_masterDocumentPreviewInformation;
0638     }
0639     KileProject *project = m_ki->docManager()->projectForMember(textInfo->url());
0640     if(project) {
0641         KILE_DEBUG_MAIN << "part of a project";
0642         if(locatedProject) {
0643             *locatedProject = project;
0644         }
0645         if(userStatusHandler) {
0646             *userStatusHandler = project;
0647         }
0648         if(latexOutputHandler) {
0649             *latexOutputHandler = project;
0650         }
0651         if(m_projectToPreviewInformationHash.contains(project)) {
0652             KILE_DEBUG_MAIN << "project found";
0653             return m_projectToPreviewInformationHash[project];
0654         }
0655         else {
0656             KILE_DEBUG_MAIN << "project not found";
0657             return Q_NULLPTR;
0658         }
0659     }
0660     else if(latexInfo && m_latexInfoToPreviewInformationHash.contains(latexInfo)) {
0661         KILE_DEBUG_MAIN << "not part of a project";
0662         return m_latexInfoToPreviewInformationHash[latexInfo];
0663     }
0664     else {
0665         KILE_DEBUG_MAIN << "not found";
0666         return Q_NULLPTR;
0667     }
0668 }
0669 
0670 bool LivePreviewManager::isCurrentDocumentOrProject(KTextEditor::Document *doc)
0671 {
0672     const KTextEditor::View *currentView = m_ki->viewManager()->currentTextView();
0673 
0674     if(currentView->document() != doc) {
0675         const KileProject *project = m_ki->docManager()->projectForMember(doc->url());
0676         const KileProject *currentProject = m_ki->docManager()->activeProject();
0677         if(!currentProject || (project != currentProject)) {
0678             return false;
0679         }
0680     }
0681 
0682     return true;
0683 }
0684 
0685 void LivePreviewManager::showCursorPositionInDocumentViewer()
0686 {
0687     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
0688     if(!view) {
0689         return;
0690     }
0691     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
0692     if(!latexInfo) {
0693         return;
0694     }
0695     LivePreviewUserStatusHandler *userStatusHandler = Q_NULLPTR;
0696     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
0697     if(!userStatusHandler->isLivePreviewEnabled()) {
0698         return;
0699     }
0700 
0701     synchronizeViewWithCursor(latexInfo, view, view->cursorPosition(), true); // called from a cursor position change
0702 }
0703 
0704 // Note: this method won't open a document again if it's open already
0705 bool LivePreviewManager::ensureDocumentIsOpenInViewer(PreviewInformation *previewInformation, bool *hadToOpen)
0706 {
0707     if(hadToOpen) {
0708         *hadToOpen = false;
0709     }
0710     const QFile previewFileInfo(previewInformation->previewFile);
0711     if(!m_ki->viewManager()->viewerPart() || !previewFileInfo.exists() || previewFileInfo.size() == 0) {
0712         return false;
0713     }
0714     const QUrl previewUrl(QUrl::fromLocalFile(previewInformation->previewFile));
0715     if(m_ki->viewManager()->viewerPart()->url().isEmpty() || m_ki->viewManager()->viewerPart()->url() != previewUrl) {
0716         KILE_DEBUG_MAIN << "loading again";
0717         if(m_ki->viewManager()->viewerPart()->openUrl(previewUrl)) {
0718             if(hadToOpen) {
0719                 *hadToOpen = true;
0720             }
0721             // don't forget this
0722             m_shownPreviewInformation = previewInformation;
0723             return true;
0724         }
0725         else {
0726             m_shownPreviewInformation = Q_NULLPTR;
0727             return false;
0728         }
0729     }
0730     return true;
0731 }
0732 
0733 void LivePreviewManager::synchronizeViewWithCursor(KileDocument::TextInfo *textInfo, KTextEditor::View *view,
0734         const KTextEditor::Cursor& newPosition,
0735         bool calledFromCursorPositionChange)
0736 {
0737     Q_UNUSED(view);
0738     KILE_DEBUG_MAIN << "new position " << newPosition;
0739 
0740     PreviewInformation *previewInformation = findPreviewInformation(textInfo);
0741     if(!previewInformation) {
0742         KILE_DEBUG_MAIN << "couldn't find preview information for" << textInfo;
0743         return;
0744     }
0745 
0746     QFileInfo updatedFileInfo(textInfo->getDoc()->url().toLocalFile());
0747     QString filePath;
0748     if(previewInformation->pathToPreviewPathHash.contains(updatedFileInfo.absoluteFilePath())) {
0749         KILE_DEBUG_MAIN << "found";
0750         filePath = previewInformation->pathToPreviewPathHash[updatedFileInfo.absoluteFilePath()];
0751     }
0752     else {
0753         KILE_DEBUG_MAIN << "not found";
0754         filePath = textInfo->getDoc()->url().toLocalFile();
0755     }
0756     KILE_DEBUG_MAIN << "filePath" << filePath;
0757 
0758     KILE_DEBUG_MAIN << "previewFile" << previewInformation->previewFile;
0759 
0760     if(!m_ki->viewManager()->viewerPart() || !QFile::exists(previewInformation->previewFile)) {
0761         return;
0762     }
0763 
0764     KILE_DEBUG_MAIN << "url" << m_ki->viewManager()->viewerPart()->url();
0765 
0766     if(!ensureDocumentIsOpenInViewer(previewInformation)) {
0767         clearLivePreview();
0768         // must happen after the call to 'clearLivePreview' only
0769         showPreviewFailed();
0770         emit(livePreviewStopped());
0771         return;
0772     }
0773 
0774 
0775     // to increase the performance, if 'calledFromCursorPositionChange' is true, we only synchronize when the cursor line
0776     // has changed from the last synchronization
0777     // NOTE: the performance of SyncTeX has to be improved if changes in cursor columns should be taken into account as
0778     //       well (bug 305254)
0779     if(!calledFromCursorPositionChange || (previewInformation->lastSynchronizationCursor.line() != newPosition.line())) {
0780         m_ki->viewManager()->showSourceLocationInDocumentViewer(filePath, newPosition.line(), newPosition.column());
0781         previewInformation->setLastSynchronizationCursor(newPosition.line(), newPosition.column());
0782     }
0783 }
0784 
0785 void LivePreviewManager::reloadDocumentInViewer()
0786 {
0787     if(!m_ki->viewManager()->viewerPart()) {
0788         return;
0789     }
0790 
0791     //FIXME ideally, this method should be integrated in an interface extending Okular...
0792     QMetaObject::invokeMethod(m_ki->viewManager()->viewerPart(), "reload");
0793 }
0794 
0795 
0796 static QByteArray computeHashOfDocument(KTextEditor::Document *doc)
0797 {
0798     QCryptographicHash cryptographicHash(QCryptographicHash::Sha1);
0799     cryptographicHash.addData(doc->text().toUtf8());
0800     // allows to catch situations when the URL of the document has changed,
0801     // e.g. after a save-as operation, which breaks the handling of source
0802     // references for the displayed document
0803     cryptographicHash.addData(doc->url().toEncoded());
0804 
0805     return cryptographicHash.result();
0806 }
0807 
0808 static void fillTextHashForProject(KileProject *project, QHash<KileDocument::TextInfo*, QByteArray> &textHash)
0809 {
0810     QList<KileProjectItem*> list = project->items();
0811     for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
0812         KileProjectItem *item = *it;
0813 
0814         KileDocument::TextInfo *textInfo = item->getInfo();
0815         if(!textInfo) {
0816             continue;
0817         }
0818         KTextEditor::Document *document = textInfo->getDoc();
0819         if(!document) {
0820             continue;
0821         }
0822         textHash[textInfo] = computeHashOfDocument(document);
0823     }
0824 }
0825 
0826 void LivePreviewManager::fillTextHashForMasterDocument(QHash<KileDocument::TextInfo*, QByteArray> &textHash)
0827 {
0828     // we compute hashes over all the opened files
0829     QList<KileDocument::TextInfo*> textDocumentInfos = m_ki->docManager()->textDocumentInfos();
0830     for(QList<KileDocument::TextInfo*>::iterator it = textDocumentInfos.begin(); it != textDocumentInfos.end(); ++it) {
0831         KileDocument::TextInfo *textInfo = *it;
0832         if(!textInfo) {
0833             continue;
0834         }
0835         KTextEditor::Document *document = textInfo->getDoc();
0836         if(!document) {
0837             continue;
0838         }
0839         textHash[textInfo] = computeHashOfDocument(document);
0840     }
0841 }
0842 
0843 void LivePreviewManager::showPreviewCompileIfNecessary(KileDocument::LaTeXInfo *latexInfo, KTextEditor::View *view)
0844 {
0845     KILE_DEBUG_MAIN;
0846     // first, stop any running live preview
0847     stopLivePreview();
0848 
0849     KileProject *project = Q_NULLPTR;
0850     LivePreviewUserStatusHandler *userStatusHandler = Q_NULLPTR;
0851     PreviewInformation *previewInformation = findPreviewInformation(latexInfo, &project, &userStatusHandler);
0852     if(!previewInformation) {
0853         KILE_DEBUG_MAIN << "not found";
0854         compilePreview(latexInfo, view);
0855     }
0856     else {
0857         Q_ASSERT(userStatusHandler);
0858         updateLivePreviewToolActions(userStatusHandler);
0859         QHash<KileDocument::TextInfo*, QByteArray> newHash;
0860 //      QString fileName;
0861 //      QFileInfo fileInfo(view->document()->url().path());
0862 //      if(previewInformation->pathToPreviewPathHash.contains(fileInfo.absoluteFilePath())) {
0863 //          KILE_DEBUG_MAIN << "contains";
0864 //          fileName = previewInformation->pathToPreviewPathHash[fileInfo.absoluteFilePath()];
0865 //      }
0866 //      else {
0867 //          KILE_DEBUG_MAIN << "does not contain";
0868 //          fileName = fileInfo.absoluteFilePath();
0869 //      }
0870 //      KILE_DEBUG_MAIN << "fileName:" << fileName;
0871         bool masterDocumentSet = !m_ki->getMasterDocumentFileName().isEmpty();
0872 
0873         if(masterDocumentSet) {
0874             fillTextHashForMasterDocument(newHash);
0875         }
0876         else if(project) {
0877             fillTextHashForProject(project, newHash);
0878         }
0879         else {
0880             newHash[latexInfo] = computeHashOfDocument(view->document());
0881         }
0882 
0883         if(newHash != previewInformation->textHash || !QFile::exists(previewInformation->previewFile)) {
0884             KILE_DEBUG_MAIN << "hashes don't match";
0885             compilePreview(latexInfo, view);
0886         }
0887         else {
0888             KILE_DEBUG_MAIN << "hashes match";
0889             showPreviewSuccessful();
0890             synchronizeViewWithCursor(latexInfo, view, view->cursorPosition());
0891             emit(livePreviewSuccessful());
0892         }
0893     }
0894 }
0895 
0896 void LivePreviewManager::compilePreview(KileDocument::LaTeXInfo *latexInfo, KTextEditor::View *view)
0897 {
0898     KILE_DEBUG_MAIN << "updating preview";
0899     m_ki->viewManager()->setLivePreviewModeForDocumentViewer(true);
0900     m_runningPathToPreviewPathHash.clear();
0901     m_runningPreviewPathToPathHash.clear();
0902 
0903     //CAUTION: as saving launches an event loop, we don't want 'compilePreview'
0904     //         to be called from within 'compilePreview'
0905     m_documentChangedTimer->blockSignals(true);
0906     bool saveResult = m_ki->docManager()->fileSaveAll();
0907     m_documentChangedTimer->blockSignals(false);
0908     // first, we have to save the documents
0909     if(!saveResult) {
0910         displayErrorMessage(i18n("Some documents could not be saved correctly"));
0911         return;
0912     }
0913 
0914     // document is new and hasn't been saved yet at all
0915     if(view->document()->url().isEmpty()) {
0916         displayErrorMessage(i18n("The document must have been saved before the live preview can be started"));
0917         return;
0918     }
0919 
0920     // first, stop any running live preview
0921     stopLivePreview();
0922 
0923     KileProject *project = Q_NULLPTR;
0924     LivePreviewUserStatusHandler *userStatusHandler;
0925     LaTeXOutputHandler *latexOutputHandler;
0926     PreviewInformation *previewInformation = findPreviewInformation(latexInfo, &project, &userStatusHandler, &latexOutputHandler);
0927     Q_ASSERT(userStatusHandler);
0928     Q_ASSERT(latexOutputHandler);
0929     if(!previewInformation) {
0930         previewInformation = new PreviewInformation();
0931         if(!m_ki->getMasterDocumentFileName().isEmpty()) {
0932             m_masterDocumentPreviewInformation = previewInformation;
0933         }
0934         else if(project) {
0935             bool containsInvalidRelativeItem = false;
0936             // in the case of a project, we might have to create a similar subdirectory
0937             // structure as it is present in the real project in order for LaTeX
0938             // to work correctly
0939             if(!previewInformation->createSubDirectoriesForProject(project, &containsInvalidRelativeItem)) {
0940                 userStatusHandler->setLivePreviewEnabled(false);
0941                 if(containsInvalidRelativeItem) {
0942                     displayErrorMessage(i18n("The location of one project item is not relative to the project's base directory\n"
0943                                              "Live preview for this project has been disabled"), true);
0944                 }
0945                 else {
0946                     displayErrorMessage(i18n("Failed to create the subdirectory structure"));
0947                 }
0948                 delete previewInformation;
0949                 disablePreview();
0950                 return;
0951             }
0952             m_projectToPreviewInformationHash[project] = previewInformation;
0953         }
0954         else {
0955             m_latexInfoToPreviewInformationHash[latexInfo] = previewInformation;
0956         }
0957     }
0958 
0959     connect(latexInfo, SIGNAL(aboutToBeDestroyed(KileDocument::TextInfo*)),
0960             this, SLOT(removeLaTeXInfo(KileDocument::TextInfo*)),
0961             Qt::UniqueConnection);
0962 
0963     if(project) {
0964         handleProjectOpened(project); // create the necessary signal-slot connections
0965     }
0966 
0967     updateLivePreviewToolActions(userStatusHandler);
0968     KileTool::LivePreviewLaTeX *latex = dynamic_cast<KileTool::LivePreviewLaTeX *>(m_ki->toolManager()->createTool(userStatusHandler->livePreviewTool(),
0969                                         false));
0970     if(!latex) {
0971         KILE_DEBUG_MAIN<< "couldn't create the live preview tool";
0972         return;
0973     }
0974 
0975     // important!
0976     latex->setPartOfLivePreview();
0977     connect(latex, SIGNAL(done(KileTool::Base*,int,bool)), this, SLOT(toolDone(KileTool::Base*,int,bool)));
0978     connect(latex, SIGNAL(destroyed()), this, SLOT(toolDestroyed()));
0979 
0980     QFileInfo fileInfo;
0981     const bool masterDocumentSet = !m_ki->getMasterDocumentFileName().isEmpty();
0982     if(masterDocumentSet) {
0983         fileInfo = QFileInfo(m_ki->getMasterDocumentFileName());
0984     }
0985     else if(project) {
0986         fileInfo = QFileInfo(m_ki->getCompileNameForProject(project));
0987     }
0988     else {
0989         fileInfo = QFileInfo(m_ki->getCompileName());
0990     }
0991 
0992     const QString inputDir = previewInformation->getTempDir() + LIST_SEPARATOR + fileInfo.absolutePath();
0993 
0994     // set value of texinput path (only for LivePreviewManager tools)
0995     QString texInputPath = KileConfig::teXPaths();
0996     if(!texInputPath.isEmpty()) {
0997         texInputPath = inputDir + LIST_SEPARATOR + texInputPath;
0998     }
0999     else {
1000         texInputPath = inputDir;
1001     }
1002     latex->setTeXInputPaths(texInputPath);
1003 
1004     QString bibInputPath = KileConfig::bibInputPaths();
1005     if(!bibInputPath.isEmpty()) {
1006         bibInputPath = inputDir + LIST_SEPARATOR + bibInputPath;
1007     }
1008     else {
1009         bibInputPath = inputDir;
1010     }
1011     latex->setBibInputPaths(bibInputPath);
1012 
1013     QString bstInputPath = KileConfig::bstInputPaths();
1014     if(!bstInputPath.isEmpty()) {
1015         bstInputPath = inputDir + LIST_SEPARATOR + bstInputPath;
1016     }
1017     else {
1018         bstInputPath = inputDir;
1019     }
1020     latex->setBstInputPaths(bstInputPath);
1021 
1022 //  m_runningPathToPreviewPathHash[fileInfo.absoluteFilePath()] = tempFile;
1023 //  m_runningPreviewPathToPathHash[tempFile] = fileInfo.absoluteFilePath();
1024 
1025     // don't emit the 'requestSaveAll' signal
1026 //  latex->removeFlag(EmitSaveAllSignal);
1027 
1028     latex->setTargetDir(previewInformation->getTempDir());
1029     latex->setSource(fileInfo.absoluteFilePath(), fileInfo.absolutePath());
1030     latex->setLaTeXOutputHandler(latexOutputHandler);
1031 
1032     latex->prepareToRun();
1033 //  latex->launcher()->setWorkingDirectory(previewInformation->getTempDir());
1034     KILE_DEBUG_MAIN << "dir:" << previewInformation->getTempDir();
1035 
1036     m_runningTextView = view;
1037     m_runningLaTeXInfo = latexInfo;
1038     m_runningProject = project;
1039     m_runningPreviewFile = previewInformation->getTempDir() + '/' + latex->target();
1040     m_runningTextHash.clear();
1041     if(masterDocumentSet) {
1042         fillTextHashForMasterDocument(m_runningTextHash);
1043     }
1044     else if(project) {
1045         fillTextHashForProject(project, m_runningTextHash);
1046     }
1047     else {
1048         m_runningTextHash[latexInfo] = computeHashOfDocument(latexInfo->getDoc());
1049     }
1050     m_runningPreviewInformation = previewInformation;
1051     showPreviewRunning();
1052 
1053     // finally, run the tool
1054     m_ki->toolManager()->run(latex);
1055     emit(livePreviewRunning());
1056 }
1057 
1058 bool LivePreviewManager::isLivePreviewActive() const
1059 {
1060     KParts::ReadOnlyPart *viewerPart = m_ki->viewManager()->viewerPart();
1061 
1062     return m_runningPreviewInformation
1063            || (m_shownPreviewInformation
1064                && viewerPart
1065                && viewerPart->url() == QUrl::fromLocalFile(m_shownPreviewInformation->previewFile));
1066 }
1067 
1068 bool LivePreviewManager::isLivePreviewPossible() const
1069 {
1070     return true;
1071 }
1072 
1073 void LivePreviewManager::handleDocumentOpened(KileDocument::TextInfo *info)
1074 {
1075     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1076         return;
1077     }
1078 
1079     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
1080     if(view && view->document() == info->getDoc()) {
1081         handleTextViewActivated(view);
1082     }
1083 }
1084 
1085 void LivePreviewManager::handleTextViewActivated(KTextEditor::View *view, bool clearPreview, bool forceCompilation)
1086 {
1087     // when a file is currently being opened, we don't react to the view activation signal as the correct live preview
1088     // tools might not be loaded yet for the document that belongs to 'view'
1089     if(m_bootUpMode || !KileConfig::livePreviewEnabled() || m_ki->docManager()->isOpeningFile()) {
1090         return;
1091     }
1092     if(clearPreview) {
1093         stopAndClearPreview();
1094     }
1095     else {
1096         stopLivePreview();
1097     }
1098     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
1099     if(!latexInfo) {
1100         return;
1101     }
1102     m_documentChangedTimer->stop();
1103 
1104     LivePreviewUserStatusHandler *userStatusHandler = Q_NULLPTR;
1105     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
1106     Q_ASSERT(userStatusHandler);
1107     const bool livePreviewActive = userStatusHandler->isLivePreviewEnabled();
1108     updateLivePreviewToolActions(userStatusHandler);
1109     // update the state of the live preview control button
1110     m_previewForCurrentDocumentAction->setChecked(livePreviewActive);
1111 
1112     if(!livePreviewActive) {
1113         disablePreview();
1114     }
1115     else {
1116         if(forceCompilation) {
1117             compilePreview(latexInfo, view);
1118         }
1119         else {
1120             showPreviewCompileIfNecessary(latexInfo, view);
1121         }
1122     }
1123 }
1124 
1125 void LivePreviewManager::handleTextViewClosed(KTextEditor::View *view, bool wasActiveView)
1126 {
1127     Q_UNUSED(view);
1128     Q_UNUSED(wasActiveView);
1129 
1130     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1131         return;
1132     }
1133 
1134     // check if there is still an open editor tab
1135     if(!KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView()) {
1136         stopAndClearPreview();
1137     }
1138 }
1139 
1140 void LivePreviewManager::refreshLivePreview()
1141 {
1142     KTextEditor::View *textView = m_ki->viewManager()->currentTextView();
1143     if(!textView) {
1144         KILE_DEBUG_MAIN << "no text view is shown; hence, no preview can be shown";
1145         return;
1146     }
1147     handleTextViewActivated(textView, false); // don't automatically clear the preview
1148 }
1149 
1150 void LivePreviewManager::recompileLivePreview()
1151 {
1152     KTextEditor::View *textView = m_ki->viewManager()->currentTextView();
1153     if(!textView) {
1154         KILE_DEBUG_MAIN << "no text view is shown; hence, no preview can be shown";
1155         return;
1156     }
1157     handleTextViewActivated(textView, false, true); // don't automatically clear the preview but force compilation
1158 }
1159 
1160 void LivePreviewManager::removeLaTeXInfo(KileDocument::TextInfo *textInfo)
1161 {
1162     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(textInfo);
1163     if(!latexInfo) {
1164         return;
1165     }
1166 
1167     if(!m_latexInfoToPreviewInformationHash.contains(latexInfo)) {
1168         return; // nothing to be done
1169     }
1170 
1171     PreviewInformation *previewInformation = m_latexInfoToPreviewInformationHash[latexInfo];
1172 
1173     if(m_runningLaTeXInfo == latexInfo) {
1174         stopLivePreview();
1175     }
1176 
1177     if(previewInformation == m_shownPreviewInformation) {
1178         clearLivePreview();
1179     }
1180 
1181     m_latexInfoToPreviewInformationHash.remove(latexInfo);
1182     delete previewInformation;
1183 }
1184 
1185 void LivePreviewManager::removeProject(KileProject *project)
1186 {
1187     if(!m_projectToPreviewInformationHash.contains(project)) {
1188         return; // nothing to be done
1189     }
1190 
1191     PreviewInformation *previewInformation = m_projectToPreviewInformationHash[project];
1192 
1193     if(m_runningProject == project) {
1194         stopLivePreview();
1195     }
1196 
1197     if(previewInformation == m_shownPreviewInformation) {
1198         clearLivePreview();
1199     }
1200 
1201     m_projectToPreviewInformationHash.remove(project);
1202     delete previewInformation;
1203 }
1204 
1205 void LivePreviewManager::handleProjectOpened(KileProject *project)
1206 {
1207     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1208         return;
1209     }
1210 
1211     connect(project, SIGNAL(aboutToBeDestroyed(KileProject*)),
1212             this, SLOT(removeProject(KileProject*)),
1213             Qt::UniqueConnection);
1214     connect(project, SIGNAL(projectItemAdded(KileProject*,KileProjectItem*)),
1215             this, SLOT(handleProjectItemAdded(KileProject*,KileProjectItem*)),
1216             Qt::UniqueConnection);
1217     connect(project, SIGNAL(projectItemRemoved(KileProject*,KileProjectItem*)),
1218             this, SLOT(handleProjectItemRemoved(KileProject*,KileProjectItem*)),
1219             Qt::UniqueConnection);
1220 }
1221 
1222 void LivePreviewManager::handleProjectItemAdditionOrRemoval(KileProject *project, KileProjectItem *item)
1223 {
1224     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1225         return;
1226     }
1227 
1228     KILE_DEBUG_MAIN;
1229     bool previewNeedsToBeRefreshed = false;
1230 
1231     // we can't use TextInfo pointers here as they might not be set in 'item' yet
1232     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(item->url()));
1233     if(latexInfo && m_latexInfoToPreviewInformationHash.contains(latexInfo)) {
1234         PreviewInformation *previewInformation = m_latexInfoToPreviewInformationHash[latexInfo];
1235         if(previewInformation == m_shownPreviewInformation) {
1236             previewNeedsToBeRefreshed = true;
1237         }
1238         removeLaTeXInfo(latexInfo);
1239     }
1240 
1241     if(m_projectToPreviewInformationHash.contains(project)) {
1242         PreviewInformation *previewInformation = m_projectToPreviewInformationHash[project];
1243         if(previewInformation == m_shownPreviewInformation) {
1244             previewNeedsToBeRefreshed = true;
1245         }
1246         removeProject(project);
1247     }
1248 
1249     // finally, check whether the currently activated text view is the 'modified' project item
1250     if(!previewNeedsToBeRefreshed) {
1251         KTextEditor::View *view = m_ki->viewManager()->currentTextView();
1252         // we can't use TextInfo pointers here as they might not be set in 'item' yet
1253         if(view && (view->document()->url() == item->url())) {
1254             previewNeedsToBeRefreshed = true;
1255         }
1256     }
1257 
1258     KILE_DEBUG_MAIN << "previewNeedsToBeRefreshed" << previewNeedsToBeRefreshed;
1259     if(previewNeedsToBeRefreshed) {
1260         // we can't do this here directly as 'item' might not be fully set up yet (e.g., if it has been added)
1261         QTimer::singleShot(0, this, SLOT(refreshLivePreview()));
1262     }
1263 }
1264 
1265 void LivePreviewManager::handleProjectItemAdded(KileProject *project, KileProjectItem *item)
1266 {
1267     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1268         return;
1269     }
1270     KILE_DEBUG_MAIN;
1271 
1272     // the directory structure in the temporary directory will be updated when
1273     // 'compilePreview' is called; 'handleProjectItemAdditionOrRemoval' will delete
1274     // PreviewInformation objects
1275     handleProjectItemAdditionOrRemoval(project, item);
1276 }
1277 
1278 void LivePreviewManager::handleProjectItemRemoved(KileProject *project, KileProjectItem *item)
1279 {
1280     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1281         return;
1282     }
1283 
1284     KILE_DEBUG_MAIN;
1285     handleProjectItemAdditionOrRemoval(project, item);
1286 }
1287 
1288 void LivePreviewManager::handleDocumentSavedAs(KTextEditor::View *view, KileDocument::TextInfo *info)
1289 {
1290     Q_UNUSED(info);
1291 
1292     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1293         return;
1294     }
1295 
1296     KTextEditor::View *currentTextView = m_ki->viewManager()->currentTextView();
1297     if(view != currentTextView) { // might maybe happen at some point...
1298         // preview will be refreshed the next time that view is activated as the hashes don't
1299         // match anymore
1300         return;
1301     }
1302     refreshLivePreview();
1303 }
1304 
1305 void LivePreviewManager::toolDestroyed()
1306 {
1307     KILE_DEBUG_MAIN << "\tLivePreviewManager: tool destroyed" << Qt::endl;
1308 }
1309 
1310 void LivePreviewManager::handleSpawnedChildTool(KileTool::Base *parent, KileTool::Base *child)
1311 {
1312     Q_UNUSED(parent);
1313 
1314     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1315         return;
1316     }
1317 
1318     KILE_DEBUG_MAIN;
1319     // only connect the signal for tools that are part of live preview!
1320     if(parent->isPartOfLivePreview()) {
1321         connect(child, SIGNAL(done(KileTool::Base*,int,bool)), this, SLOT(childToolDone(KileTool::Base*,int,bool)));
1322     }
1323 }
1324 
1325 void LivePreviewManager::toolDone(KileTool::Base *base, int i, bool childToolSpawned)
1326 {
1327     KILE_DEBUG_MAIN << "\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << i << Qt::endl;
1328     KILE_DEBUG_MAIN << "\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << i << Qt::endl;
1329     KILE_DEBUG_MAIN << "\tLivePreviewManager: tool done" << base->name() << i << childToolSpawned << Qt::endl;
1330     if(i != Success) {
1331         KILE_DEBUG_MAIN << "tool didn't return successfully, doing nothing";
1332         showPreviewFailed();
1333         clearRunningLivePreviewInformation();
1334         emit(livePreviewStopped());
1335     }
1336     // a LaTeX variant must have finished for the preview to be complete
1337     else if(!childToolSpawned && dynamic_cast<KileTool::LaTeX*>(base)) {
1338         updatePreviewInformationAfterCompilationFinished();
1339         clearRunningLivePreviewInformation();
1340     }
1341 }
1342 
1343 void LivePreviewManager::childToolDone(KileTool::Base *base, int i, bool childToolSpawned)
1344 {
1345     KILE_DEBUG_MAIN << "\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << i << Qt::endl;
1346     KILE_DEBUG_MAIN << "\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << i << Qt::endl;
1347     KILE_DEBUG_MAIN << "\tLivePreviewManager: child tool done" << base->name() << i << childToolSpawned << Qt::endl;
1348     if(!m_ki->viewManager()->viewerPart()) {
1349         return;
1350     }
1351     if(i != Success) {
1352         KILE_DEBUG_MAIN << "tool didn't return successfully, doing nothing";
1353         showPreviewFailed();
1354         clearRunningLivePreviewInformation();
1355         emit(livePreviewStopped());
1356     }
1357     // a LaTeX variant must have finished for the preview to be complete
1358     else if(!childToolSpawned && dynamic_cast<KileTool::LaTeX*>(base)) {
1359         updatePreviewInformationAfterCompilationFinished();
1360         clearRunningLivePreviewInformation();
1361     }
1362 }
1363 
1364 void LivePreviewManager::updatePreviewInformationAfterCompilationFinished()
1365 {
1366     if(!m_runningPreviewInformation) { // LivePreview has been stopped in the meantime
1367         return;
1368     }
1369 
1370     m_shownPreviewInformation = m_runningPreviewInformation;
1371     m_shownPreviewInformation->pathToPreviewPathHash = m_runningPathToPreviewPathHash;
1372     m_shownPreviewInformation->previewPathToPathHash = m_runningPreviewPathToPathHash;
1373     m_shownPreviewInformation->textHash = m_runningTextHash;
1374     m_shownPreviewInformation->previewFile = m_runningPreviewFile;
1375 
1376     m_runningPreviewInformation = Q_NULLPTR;
1377 
1378     bool hadToOpen = false;
1379     if(!ensureDocumentIsOpenInViewer(m_shownPreviewInformation, &hadToOpen)) {
1380         clearLivePreview();
1381         // must happen after the call to 'clearLivePreview' only
1382         showPreviewFailed();
1383         emit(livePreviewStopped());
1384         return;
1385     }
1386 
1387     // as 'ensureDocumentIsOpenInViewer' won't reload when the document is open
1388     // already, we have to do it here
1389     if(!hadToOpen) {
1390         reloadDocumentInViewer();
1391     }
1392 
1393     if(m_ki->viewManager()->isSynchronisingCursorWithDocumentViewer()) {
1394         synchronizeViewWithCursor(m_runningLaTeXInfo, m_runningTextView, m_runningTextView->cursorPosition());
1395     }
1396 
1397     showPreviewSuccessful();
1398     emit(livePreviewSuccessful());
1399 }
1400 
1401 void LivePreviewManager::displayErrorMessage(const QString &text, bool clearFirst)
1402 {
1403     if(clearFirst) {
1404         m_ki->errorHandler()->clearMessages();
1405     }
1406     m_ki->errorHandler()->printMessage(KileTool::Error, text, i18n("LivePreview"));
1407 }
1408 
1409 }
1410