1 /********************************************************************************
2   Copyright (C) 2011-2018 by Michel Ludwig (michel.ludwig@kdemail.net)
3  ********************************************************************************/
4 
5 /***************************************************************************
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU General Public License as published by  *
9  *   the Free Software Foundation; either version 2 of the License, or     *
10  *   (at your option) any later version.                                   *
11  *                                                                         *
12  ***************************************************************************/
13 
14 #include "livepreview.h"
15 #include "config.h"
16 
17 #include <algorithm>
18 
19 #include <QCryptographicHash>
20 #include <QDir>
21 #include <QHBoxLayout>
22 #include <QMap>
23 #include <QFile>
24 #include <QFileInfo>
25 #include <QStandardPaths>
26 #include <QTextCodec>
27 #include <QTextStream>
28 #include <QTimer>
29 #include <QTemporaryDir>
30 
31 #include <KActionCollection>
32 #include <KIconLoader>
33 #include <KLocalizedString>
34 #include <KTextEditor/Application>
35 #include <KTextEditor/CodeCompletionInterface>
36 #include <KTextEditor/Document>
37 #include <KTextEditor/MainWindow>
38 #include <KTextEditor/View>
39 #include <KToolBar>
40 #include <KParts/MainWindow>
41 #include <KXMLGUIFactory>
42 
43 #include <okular/interfaces/viewerinterface.h>
44 
45 #include "errorhandler.h"
46 #include "kiledebug.h"
47 #include "kiletool_enums.h"
48 #include "kiledocmanager.h"
49 #include "kileviewmanager.h"
50 
51 //TODO: it still has to be checked whether it is necessary to use LaTeXInfo objects
52 
53 namespace KileTool
54 {
55 
56 class LivePreviewManager::PreviewInformation {
57 public:
PreviewInformation()58     PreviewInformation()
59         : lastSynchronizationCursor(-1, -1)
60     {
61         initTemporaryDirectory();
62     }
63 
~PreviewInformation()64     ~PreviewInformation() {
65         delete m_tempDir;
66     }
67 
getTempDir() const68     QString getTempDir() const {
69         return m_tempDir->path();
70     }
71 
clearPreviewPathMappings()72     void clearPreviewPathMappings() {
73         pathToPreviewPathHash.clear();
74         previewPathToPathHash.clear();
75     }
76 
createSubDirectoriesForProject(KileProject * project,bool * containsInvalidRelativeItem=Q_NULLPTR)77     bool createSubDirectoriesForProject(KileProject *project, bool *containsInvalidRelativeItem = Q_NULLPTR) {
78         if(containsInvalidRelativeItem) {
79             *containsInvalidRelativeItem = false;
80         }
81         const QList<KileProjectItem*> items = project->items();
82         const QString tempCanonicalDir = QDir(m_tempDir->path()).canonicalPath();
83         if(tempCanonicalDir.isEmpty()) {
84             return false;
85         }
86         for(KileProjectItem *item : items) {
87             bool successful = true;
88             const QString itemRelativeDir = QFileInfo(tempCanonicalDir + '/' + item->path()).path();
89             const QString itemAbsolutePath = QDir(itemRelativeDir).absolutePath();
90             if(itemAbsolutePath.isEmpty()) {
91                 successful = false;
92             }
93             else if(!itemAbsolutePath.startsWith(tempCanonicalDir)) {
94                 if(containsInvalidRelativeItem) {
95                     *containsInvalidRelativeItem = true;
96                 }
97                 successful = false; // we don't want to create directories below 'm_tempDir->name()'
98             }
99             else {
100                 successful = QDir().mkpath(itemAbsolutePath);
101             }
102             if(!successful) {
103                 return false;
104             }
105         }
106         return true;
107     }
108 
setLastSynchronizationCursor(int line,int col)109     void setLastSynchronizationCursor(int line, int col)
110     {
111         lastSynchronizationCursor.setLine(line);
112         lastSynchronizationCursor.setColumn(col);
113     }
114 
115 private:
116     QTemporaryDir *m_tempDir;
117 
initTemporaryDirectory()118     void initTemporaryDirectory() {
119         m_tempDir = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + "kile-livepreview");
120     }
121 
122 public:
123     QHash<QString, QString> pathToPreviewPathHash;
124     QHash<QString, QString> previewPathToPathHash;
125     QString previewFile;
126     QHash<KileDocument::TextInfo*, QByteArray> textHash;
127     KTextEditor::Cursor lastSynchronizationCursor;
128 };
129 
LivePreviewManager(KileInfo * ki,KActionCollection * ac)130 LivePreviewManager::LivePreviewManager(KileInfo *ki, KActionCollection *ac)
131     : m_ki(ki),
132       m_bootUpMode(true),
133       m_previewStatusLed(Q_NULLPTR),
134       m_previewForCurrentDocumentAction(Q_NULLPTR),
135       m_recompileLivePreviewAction(Q_NULLPTR),
136       m_runningLaTeXInfo(Q_NULLPTR), m_runningTextView(Q_NULLPTR), m_runningProject(Q_NULLPTR),
137       m_runningPreviewInformation(Q_NULLPTR), m_shownPreviewInformation(Q_NULLPTR), m_masterDocumentPreviewInformation(Q_NULLPTR)
138 {
139     connect(m_ki->viewManager(), SIGNAL(textViewActivated(KTextEditor::View*)),
140             this, SLOT(handleTextViewActivated(KTextEditor::View*)));
141     connect(m_ki->viewManager(), SIGNAL(textViewClosed(KTextEditor::View*,bool)),
142             this, SLOT(handleTextViewClosed(KTextEditor::View*,bool)));
143     connect(m_ki->toolManager(), SIGNAL(childToolSpawned(KileTool::Base*,KileTool::Base*)),
144             this, SLOT(handleSpawnedChildTool(KileTool::Base*,KileTool::Base*)));
145     connect(m_ki->docManager(), SIGNAL(documentSavedAs(KTextEditor::View*,KileDocument::TextInfo*)),
146             this, SLOT(handleDocumentSavedAs(KTextEditor::View*,KileDocument::TextInfo*)));
147     connect(m_ki->docManager(), SIGNAL(documentOpened(KileDocument::TextInfo*)),
148             this, SLOT(handleDocumentOpened(KileDocument::TextInfo*)));
149     connect(m_ki->docManager(), SIGNAL(projectOpened(KileProject*)),
150             this, SLOT(handleProjectOpened(KileProject*)));
151 
152     createActions(ac);
153     populateViewerControlToolBar();
154 
155     m_ledBlinkingTimer = new QTimer(this);
156     m_ledBlinkingTimer->setSingleShot(false);
157     m_ledBlinkingTimer->setInterval(500);
158     connect(m_ledBlinkingTimer, SIGNAL(timeout()), m_previewStatusLed, SLOT(toggle()));
159 
160     m_documentChangedTimer = new QTimer(this);
161     m_documentChangedTimer->setSingleShot(true);
162     connect(m_documentChangedTimer, SIGNAL(timeout()), this, SLOT(handleDocumentModificationTimerTimeout()));
163 
164     showPreviewDisabled();
165 }
166 
~LivePreviewManager()167 LivePreviewManager::~LivePreviewManager()
168 {
169     KILE_DEBUG_MAIN;
170 
171     qDeleteAll(m_livePreviewToolActionList);
172     m_livePreviewToolActionList.clear();
173 
174     deleteAllLivePreviewInformation();
175 }
176 
disableBootUpMode()177 void LivePreviewManager::disableBootUpMode()
178 {
179     m_bootUpMode = false;
180     recompileLivePreview();
181 }
182 
createActions(KActionCollection * ac)183 void LivePreviewManager::createActions(KActionCollection *ac)
184 {
185 
186     m_livePreviewToolActionGroup = new QActionGroup(ac);
187 
188     m_previewForCurrentDocumentAction = new KToggleAction(QIcon::fromTheme("document-preview"), i18n("Live Preview for Current Document or Project"), this);
189     m_previewForCurrentDocumentAction->setChecked(true);
190     connect(m_previewForCurrentDocumentAction, SIGNAL(triggered(bool)), this, SLOT(previewForCurrentDocumentActionTriggered(bool)));
191     ac->addAction("live_preview_for_current_document", m_previewForCurrentDocumentAction);
192 
193     m_recompileLivePreviewAction = new QAction(i18n("Recompile Live Preview"), this);
194     connect(m_recompileLivePreviewAction, SIGNAL(triggered()), this, SLOT(recompileLivePreview()));
195     ac->addAction("live_preview_recompile", m_recompileLivePreviewAction);
196 
197     {
198         QAction *action = new QAction(i18n("Save Compiled Document..."), this);
199         connect(action, &QAction::triggered, m_ki->docManager(), &KileDocument::Manager::fileSaveCompiledDocument);
200         ac->addAction("file_save_compiled_document", action);
201         connect(this, &KileTool::LivePreviewManager::livePreviewSuccessful, action, [=]() { action->setEnabled(true); });
202         connect(this, &KileTool::LivePreviewManager::livePreviewRunning, action, [=]() { action->setEnabled(false); });
203         connect(this, &KileTool::LivePreviewManager::livePreviewStopped, action, [=]() { action->setEnabled(false); });
204     }
205 }
206 
previewForCurrentDocumentActionTriggered(bool b)207 void LivePreviewManager::previewForCurrentDocumentActionTriggered(bool b)
208 {
209     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
210         return;
211     }
212     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
213     if(!view) {
214         return;
215     }
216     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
217     if(!latexInfo) {
218         return;
219     }
220     LivePreviewUserStatusHandler *userStatusHandler;
221     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
222     Q_ASSERT(userStatusHandler);
223 
224     userStatusHandler->setLivePreviewEnabled(b);
225 
226     if(b) {
227         showPreviewCompileIfNecessary(latexInfo, view);
228     }
229     else {
230         disablePreview();
231     }
232 }
233 
livePreviewToolActionTriggered()234 void LivePreviewManager::livePreviewToolActionTriggered()
235 {
236     QAction *action = dynamic_cast<QAction*>(sender());
237     if(!action) {
238         KILE_DEBUG_MAIN << "slot called from wrong object!!";
239         return;
240     }
241     if(!m_actionToLivePreviewToolHash.contains(action)) {
242         KILE_DEBUG_MAIN << "action not found in hash!!";
243         return;
244     }
245     const ToolConfigPair p = m_actionToLivePreviewToolHash[action];
246     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
247     if(!view) {
248         KILE_DEBUG_MAIN << "no text view open!";
249         return;
250     }
251     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
252     if(!latexInfo) {
253         KILE_DEBUG_MAIN << "current view is not LaTeX-compatible!";
254         return;
255     }
256 
257     LivePreviewUserStatusHandler *userStatusHandler;
258     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
259     if(!userStatusHandler) {
260         KILE_DEBUG_MAIN << "no preview information found!";
261         return;
262     }
263     const bool changed = userStatusHandler->setLivePreviewTool(p);
264     if(changed) {
265         recompileLivePreview();
266     }
267 }
268 
updateLivePreviewToolActions(LivePreviewUserStatusHandler * userStatusHandler)269 void LivePreviewManager::updateLivePreviewToolActions(LivePreviewUserStatusHandler *userStatusHandler)
270 {
271     setLivePreviewToolActionsEnabled(true);
272     const ToolConfigPair p = userStatusHandler->livePreviewTool();
273     if(!m_livePreviewToolToActionHash.contains(p)) {
274         return;
275     }
276     m_livePreviewToolToActionHash[p]->setChecked(true);
277 }
278 
setLivePreviewToolActionsEnabled(bool b)279 void LivePreviewManager::setLivePreviewToolActionsEnabled(bool b)
280 {
281     Q_FOREACH(QAction *action, m_livePreviewToolActionList) {
282         action->setEnabled(b);
283     }
284 }
285 
buildLivePreviewMenu(KConfig * config)286 void LivePreviewManager::buildLivePreviewMenu(KConfig *config)
287 {
288     QMenu *menu = dynamic_cast<QMenu*>(m_ki->mainWindow()->guiFactory()->container("menu_livepreview", m_ki->mainWindow()));
289     if(!menu) {
290         KILE_DEBUG_MAIN << "live preview menu not found!!";
291         return;
292     }
293 
294     qDeleteAll(m_livePreviewToolActionList);
295     m_livePreviewToolActionList.clear();
296     m_livePreviewToolToActionHash.clear();
297     m_actionToLivePreviewToolHash.clear();
298 
299     // necessary as it will be disabled otherwise in 'kile.cpp' (as it's empty initially)
300     menu->setEnabled(true);
301     menu->clear();
302     menu->addAction(m_previewForCurrentDocumentAction);
303     menu->addSeparator();
304 
305     QList<ToolConfigPair> toolList = toolsWithConfigurationsBasedOnClass(config, "LaTeXLivePreview");
306     std::sort(toolList.begin(), toolList.end());
307     for(QList<ToolConfigPair>::iterator i = toolList.begin(); i != toolList.end(); ++i) {
308         const QString shortToolName = QString((*i).first).remove("LivePreview-");
309         QAction *action = new KToggleAction(ToolConfigPair::userStringRepresentation(shortToolName, (*i).second), this);
310 
311         m_livePreviewToolActionGroup->addAction(action);
312         connect(action, SIGNAL(triggered()), this, SLOT(livePreviewToolActionTriggered()));
313         m_livePreviewToolActionList.push_back(action);
314         m_livePreviewToolToActionHash[*i] = action;
315         m_actionToLivePreviewToolHash[action] = *i;
316         menu->addAction(action);
317     }
318     menu->addSeparator();
319     menu->addAction(m_recompileLivePreviewAction);
320 }
321 
getPreviewFile() const322 QString LivePreviewManager::getPreviewFile() const
323 {
324     if(!m_shownPreviewInformation) {
325         return QString();
326     }
327     return m_shownPreviewInformation->previewFile;
328 }
329 
isLivePreviewEnabledForCurrentDocument()330 bool LivePreviewManager::isLivePreviewEnabledForCurrentDocument()
331 {
332     return m_previewForCurrentDocumentAction->isChecked();
333 }
334 
setLivePreviewEnabledForCurrentDocument(bool b)335 void LivePreviewManager::setLivePreviewEnabledForCurrentDocument(bool b)
336 {
337     m_previewForCurrentDocumentAction->setChecked(b);
338     previewForCurrentDocumentActionTriggered(b);
339 }
340 
disablePreview()341 void LivePreviewManager::disablePreview()
342 {
343     stopAndClearPreview();
344     setLivePreviewToolActionsEnabled(false);
345     m_previewForCurrentDocumentAction->setChecked(false);
346     m_ki->viewManager()->setLivePreviewModeForDocumentViewer(false);
347 }
348 
stopAndClearPreview()349 void LivePreviewManager::stopAndClearPreview()
350 {
351     KILE_DEBUG_MAIN;
352     stopLivePreview();
353     clearLivePreview();
354 }
355 
clearLivePreview()356 void LivePreviewManager::clearLivePreview()
357 {
358     KILE_DEBUG_MAIN;
359     showPreviewDisabled();
360 
361     KParts::ReadOnlyPart *viewerPart = m_ki->viewManager()->viewerPart();
362     if(m_shownPreviewInformation && viewerPart->url() == QUrl::fromLocalFile(m_shownPreviewInformation->previewFile)) {
363         viewerPart->closeUrl();
364     }
365     m_shownPreviewInformation = Q_NULLPTR;
366     emit(livePreviewStopped());
367 }
368 
stopLivePreview()369 void LivePreviewManager::stopLivePreview()
370 {
371     m_documentChangedTimer->stop();
372     m_ki->toolManager()->stopLivePreview();
373 
374     clearRunningLivePreviewInformation();
375 }
376 
clearRunningLivePreviewInformation()377 void LivePreviewManager::clearRunningLivePreviewInformation()
378 {
379     m_runningPathToPreviewPathHash.clear();
380     m_runningPreviewPathToPathHash.clear();
381     m_runningPreviewFile.clear();
382     m_runningLaTeXInfo = Q_NULLPTR;
383     m_runningProject = Q_NULLPTR;
384     m_runningTextView = Q_NULLPTR;
385     m_runningPreviewInformation = Q_NULLPTR;
386     m_runningTextHash.clear();
387 }
388 
deleteAllLivePreviewInformation()389 void LivePreviewManager::deleteAllLivePreviewInformation()
390 {
391     // first, we have to make sure that nothing is shown anymore,
392     // and that no preview is running
393     stopAndClearPreview();
394 
395     disablePreview();
396 
397     // and now we can delete all the 'PreviewInformation' objects
398     delete m_masterDocumentPreviewInformation;
399     m_masterDocumentPreviewInformation = Q_NULLPTR;
400 
401     for(QHash<KileDocument::LaTeXInfo*, PreviewInformation*>::iterator i = m_latexInfoToPreviewInformationHash.begin();
402             i != m_latexInfoToPreviewInformationHash.end(); ++i) {
403         delete i.value();
404     }
405 
406     for(QHash<KileProject*,PreviewInformation*>::iterator i = m_projectToPreviewInformationHash.begin();
407             i != m_projectToPreviewInformationHash.end(); ++i) {
408         delete i.value();
409     }
410     m_latexInfoToPreviewInformationHash.clear();
411     m_projectToPreviewInformationHash.clear();
412 }
413 
readConfig(KConfig * config)414 void LivePreviewManager::readConfig(KConfig *config)
415 {
416     Q_UNUSED(config);
417 
418     buildLivePreviewMenu(config);
419 
420     m_previewForCurrentDocumentAction->setEnabled(KileConfig::livePreviewEnabled());
421     m_previewStatusLed->setEnabled(KileConfig::livePreviewEnabled());
422 
423     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
424         deleteAllLivePreviewInformation();
425     }
426     else {
427         refreshLivePreview(); // e.g. in case the live preview was disabled and no preview is
428         // currently shown
429     }
430 }
431 
writeConfig()432 void LivePreviewManager::writeConfig()
433 {
434 }
435 
readLivePreviewStatusSettings(KConfigGroup & configGroup,LivePreviewUserStatusHandler * handler)436 void LivePreviewManager::readLivePreviewStatusSettings(KConfigGroup &configGroup, LivePreviewUserStatusHandler *handler)
437 {
438     // the prefix 'kile_' is necessary as these settings might be written into a config group that is also modified
439     // by KatePart
440     if(configGroup.readEntry("kile_livePreviewStatusUserSpecified", false)) {
441         handler->setLivePreviewEnabled(configGroup.readEntry("kile_livePreviewEnabled", true));
442     }
443 
444     const QString livePreviewToolConfigString = configGroup.readEntry("kile_livePreviewTool", "");
445     if(livePreviewToolConfigString.isEmpty()) {
446         handler->setLivePreviewTool(ToolConfigPair(LIVEPREVIEW_DEFAULT_TOOL_NAME, DEFAULT_TOOL_CONFIGURATION));
447     }
448     else {
449         handler->setLivePreviewTool(ToolConfigPair::fromConfigStringRepresentation(livePreviewToolConfigString));
450     }
451 }
452 
writeLivePreviewStatusSettings(KConfigGroup & configGroup,LivePreviewUserStatusHandler * handler)453 void LivePreviewManager::writeLivePreviewStatusSettings(KConfigGroup &configGroup, LivePreviewUserStatusHandler *handler)
454 {
455     configGroup.writeEntry("kile_livePreviewTool", handler->livePreviewTool().configStringRepresentation());
456     configGroup.writeEntry("kile_livePreviewEnabled", handler->isLivePreviewEnabled());
457     configGroup.writeEntry("kile_livePreviewStatusUserSpecified", handler->userSpecifiedLivePreviewStatus());
458 }
459 
populateViewerControlToolBar()460 void LivePreviewManager::populateViewerControlToolBar()
461 {
462     KToolBar* viewerControlToolBar = m_ki->viewManager()->getViewerControlToolBar();
463     viewerControlToolBar->addAction(m_previewForCurrentDocumentAction);
464 
465     m_previewStatusLed = new KLed(viewerControlToolBar);
466     m_previewStatusLed->setShape(KLed::Circular);
467     m_previewStatusLed->setLook(KLed::Flat);
468     viewerControlToolBar->addWidget(m_previewStatusLed);
469 }
470 
handleMasterDocumentChanged()471 void LivePreviewManager::handleMasterDocumentChanged()
472 {
473     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
474         return;
475     }
476 
477     deleteAllLivePreviewInformation();
478     refreshLivePreview();
479 }
480 
handleTextChanged(KTextEditor::Document * doc)481 void LivePreviewManager::handleTextChanged(KTextEditor::Document *doc)
482 {
483     if(m_bootUpMode || !KileConfig::livePreviewEnabled()
484                     || !isLivePreviewEnabledForCurrentDocument()) {
485         return;
486     }
487 
488     KILE_DEBUG_MAIN;
489     if(!isCurrentDocumentOrProject(doc)) {
490         return;
491     }
492 
493     stopLivePreview();
494     showPreviewOutOfDate();
495 
496     if(!KileConfig::livePreviewCompileOnlyAfterSaving()) {
497         m_documentChangedTimer->start(KileConfig::livePreviewCompilationDelay());
498     }
499 }
500 
handleDocumentSavedOrUploaded(KTextEditor::Document * doc,bool savedAs)501 void LivePreviewManager::handleDocumentSavedOrUploaded(KTextEditor::Document *doc, bool savedAs)
502 {
503     Q_UNUSED(savedAs);
504 
505     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
506         return;
507     }
508 
509     KILE_DEBUG_MAIN;
510 
511     if(!KileConfig::livePreviewCompileOnlyAfterSaving()) {
512         return;
513     }
514 
515     if(!isCurrentDocumentOrProject(doc)) {
516         return;
517     }
518     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
519     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
520     if(!latexInfo) {
521         return;
522     }
523 
524     LivePreviewUserStatusHandler *userStatusHandler;
525     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
526     Q_ASSERT(userStatusHandler);
527     if(userStatusHandler->isLivePreviewEnabled()) {
528         showPreviewCompileIfNecessary(latexInfo, view);
529     }
530 }
531 
handleDocumentModificationTimerTimeout()532 void LivePreviewManager::handleDocumentModificationTimerTimeout()
533 {
534     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
535         return;
536     }
537 
538     KILE_DEBUG_MAIN;
539 
540     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
541     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
542     if(!latexInfo) {
543         return;
544     }
545 
546     KTextEditor::CodeCompletionInterface *codeCompletionInterface = qobject_cast<KTextEditor::CodeCompletionInterface*>(view);
547 
548     // if the code completion box is currently shown, we don't trigger an update of the preview
549     // as this will cause the document to be saved and the completion box to be hidden as a consequence
550     if(codeCompletionInterface && codeCompletionInterface->isCompletionActive()) {
551         m_documentChangedTimer->start();
552         return;
553     }
554 
555     LivePreviewUserStatusHandler *userStatusHandler;
556     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
557     Q_ASSERT(userStatusHandler);
558     if(userStatusHandler->isLivePreviewEnabled()) {
559         compilePreview(latexInfo, view);
560     }
561 }
562 
showPreviewDisabled()563 void LivePreviewManager::showPreviewDisabled()
564 {
565     KILE_DEBUG_MAIN;
566     m_ledBlinkingTimer->stop();
567     if(m_previewStatusLed) {
568         m_previewStatusLed->off();
569     }
570 }
571 
showPreviewRunning()572 void LivePreviewManager::showPreviewRunning()
573 {
574     KILE_DEBUG_MAIN;
575     if(m_previewStatusLed) {
576         m_previewStatusLed->setColor(QColor(Qt::yellow));
577         m_previewStatusLed->off();
578     }
579     m_ledBlinkingTimer->start();
580 }
581 
showPreviewFailed()582 void LivePreviewManager::showPreviewFailed()
583 {
584     KILE_DEBUG_MAIN;
585     m_ledBlinkingTimer->stop();
586     if(m_previewStatusLed) {
587         m_previewStatusLed->on();
588         m_previewStatusLed->setColor(QColor(Qt::red));
589     }
590 }
591 
showPreviewSuccessful()592 void LivePreviewManager::showPreviewSuccessful()
593 {
594     KILE_DEBUG_MAIN;
595     m_ledBlinkingTimer->stop();
596     if(m_previewStatusLed) {
597         m_previewStatusLed->on();
598         m_previewStatusLed->setColor(QColor(Qt::green));
599     }
600 }
601 
showPreviewOutOfDate()602 void LivePreviewManager::showPreviewOutOfDate()
603 {
604     KILE_DEBUG_MAIN;
605     m_ledBlinkingTimer->stop();
606     if(m_previewStatusLed) {
607         m_previewStatusLed->on();
608         m_previewStatusLed->setColor(QColor(Qt::yellow));
609     }
610 
611 }
612 
613 // If a LaTeXInfo* pointer is passed as first argument, it is guaranteed that '*userStatusHandler' won't be Q_NULLPTR.
findPreviewInformation(KileDocument::TextInfo * textInfo,KileProject ** locatedProject,LivePreviewUserStatusHandler ** userStatusHandler,LaTeXOutputHandler ** latexOutputHandler)614 LivePreviewManager::PreviewInformation* LivePreviewManager::findPreviewInformation(KileDocument::TextInfo *textInfo,
615         KileProject* *locatedProject,
616         LivePreviewUserStatusHandler* *userStatusHandler,
617         LaTeXOutputHandler* *latexOutputHandler)
618 {
619     const QString masterDocumentFileName = m_ki->getMasterDocumentFileName();
620     if(locatedProject) {
621         *locatedProject = Q_NULLPTR;
622     }
623     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(textInfo);
624     if(userStatusHandler) {
625         *userStatusHandler = latexInfo;
626     }
627     if(latexOutputHandler) {
628         *latexOutputHandler = latexInfo;
629     }
630     if(!masterDocumentFileName.isEmpty()) {
631         KILE_DEBUG_MAIN << "master document defined";
632         return m_masterDocumentPreviewInformation;
633     }
634     KileProject *project = m_ki->docManager()->projectForMember(textInfo->url());
635     if(project) {
636         KILE_DEBUG_MAIN << "part of a project";
637         if(locatedProject) {
638             *locatedProject = project;
639         }
640         if(userStatusHandler) {
641             *userStatusHandler = project;
642         }
643         if(latexOutputHandler) {
644             *latexOutputHandler = project;
645         }
646         if(m_projectToPreviewInformationHash.contains(project)) {
647             KILE_DEBUG_MAIN << "project found";
648             return m_projectToPreviewInformationHash[project];
649         }
650         else {
651             KILE_DEBUG_MAIN << "project not found";
652             return Q_NULLPTR;
653         }
654     }
655     else if(latexInfo && m_latexInfoToPreviewInformationHash.contains(latexInfo)) {
656         KILE_DEBUG_MAIN << "not part of a project";
657         return m_latexInfoToPreviewInformationHash[latexInfo];
658     }
659     else {
660         KILE_DEBUG_MAIN << "not found";
661         return Q_NULLPTR;
662     }
663 }
664 
isCurrentDocumentOrProject(KTextEditor::Document * doc)665 bool LivePreviewManager::isCurrentDocumentOrProject(KTextEditor::Document *doc)
666 {
667     const KTextEditor::View *currentView = m_ki->viewManager()->currentTextView();
668 
669     if(currentView->document() != doc) {
670         const KileProject *project = m_ki->docManager()->projectForMember(doc->url());
671         const KileProject *currentProject = m_ki->docManager()->activeProject();
672         if(!currentProject || (project != currentProject)) {
673             return false;
674         }
675     }
676 
677     return true;
678 }
679 
showCursorPositionInDocumentViewer()680 void LivePreviewManager::showCursorPositionInDocumentViewer()
681 {
682     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
683     if(!view) {
684         return;
685     }
686     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
687     if(!latexInfo) {
688         return;
689     }
690     LivePreviewUserStatusHandler *userStatusHandler = Q_NULLPTR;
691     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
692     if(!userStatusHandler->isLivePreviewEnabled()) {
693         return;
694     }
695 
696     synchronizeViewWithCursor(latexInfo, view, view->cursorPosition(), true); // called from a cursor position change
697 }
698 
699 // Note: this method won't open a document again if it's open already
ensureDocumentIsOpenInViewer(PreviewInformation * previewInformation,bool * hadToOpen)700 bool LivePreviewManager::ensureDocumentIsOpenInViewer(PreviewInformation *previewInformation, bool *hadToOpen)
701 {
702     if(hadToOpen) {
703         *hadToOpen = false;
704     }
705     const QFile previewFileInfo(previewInformation->previewFile);
706     if(!m_ki->viewManager()->viewerPart() || !previewFileInfo.exists() || previewFileInfo.size() == 0) {
707         return false;
708     }
709     const QUrl previewUrl(QUrl::fromLocalFile(previewInformation->previewFile));
710     if(m_ki->viewManager()->viewerPart()->url().isEmpty() || m_ki->viewManager()->viewerPart()->url() != previewUrl) {
711         KILE_DEBUG_MAIN << "loading again";
712         if(m_ki->viewManager()->viewerPart()->openUrl(previewUrl)) {
713             if(hadToOpen) {
714                 *hadToOpen = true;
715             }
716             // don't forget this
717             m_shownPreviewInformation = previewInformation;
718             return true;
719         }
720         else {
721             m_shownPreviewInformation = Q_NULLPTR;
722             return false;
723         }
724     }
725     return true;
726 }
727 
synchronizeViewWithCursor(KileDocument::TextInfo * textInfo,KTextEditor::View * view,const KTextEditor::Cursor & newPosition,bool calledFromCursorPositionChange)728 void LivePreviewManager::synchronizeViewWithCursor(KileDocument::TextInfo *textInfo, KTextEditor::View *view,
729         const KTextEditor::Cursor& newPosition,
730         bool calledFromCursorPositionChange)
731 {
732     Q_UNUSED(view);
733     KILE_DEBUG_MAIN << "new position " << newPosition;
734 
735     PreviewInformation *previewInformation = findPreviewInformation(textInfo);
736     if(!previewInformation) {
737         KILE_DEBUG_MAIN << "couldn't find preview information for" << textInfo;
738         return;
739     }
740 
741     QFileInfo updatedFileInfo(textInfo->getDoc()->url().toLocalFile());
742     QString filePath;
743     if(previewInformation->pathToPreviewPathHash.contains(updatedFileInfo.absoluteFilePath())) {
744         KILE_DEBUG_MAIN << "found";
745         filePath = previewInformation->pathToPreviewPathHash[updatedFileInfo.absoluteFilePath()];
746     }
747     else {
748         KILE_DEBUG_MAIN << "not found";
749         filePath = textInfo->getDoc()->url().toLocalFile();
750     }
751     KILE_DEBUG_MAIN << "filePath" << filePath;
752 
753     KILE_DEBUG_MAIN << "previewFile" << previewInformation->previewFile;
754 
755     if(!m_ki->viewManager()->viewerPart() || !QFile::exists(previewInformation->previewFile)) {
756         return;
757     }
758 
759     KILE_DEBUG_MAIN << "url" << m_ki->viewManager()->viewerPart()->url();
760 
761     if(!ensureDocumentIsOpenInViewer(previewInformation)) {
762         clearLivePreview();
763         // must happen after the call to 'clearLivePreview' only
764         showPreviewFailed();
765         emit(livePreviewStopped());
766         return;
767     }
768 
769 
770     // to increase the performance, if 'calledFromCursorPositionChange' is true, we only synchronize when the cursor line
771     // has changed from the last synchronization
772     // NOTE: the performance of SyncTeX has to be improved if changes in cursor columns should be taken into account as
773     //       well (bug 305254)
774     if(!calledFromCursorPositionChange || (previewInformation->lastSynchronizationCursor.line() != newPosition.line())) {
775         m_ki->viewManager()->showSourceLocationInDocumentViewer(filePath, newPosition.line(), newPosition.column());
776         previewInformation->setLastSynchronizationCursor(newPosition.line(), newPosition.column());
777     }
778 }
779 
reloadDocumentInViewer()780 void LivePreviewManager::reloadDocumentInViewer()
781 {
782     if(!m_ki->viewManager()->viewerPart()) {
783         return;
784     }
785 
786     //FIXME ideally, this method should be integrated in an interface extending Okular...
787     QMetaObject::invokeMethod(m_ki->viewManager()->viewerPart(), "reload");
788 }
789 
790 
computeHashOfDocument(KTextEditor::Document * doc)791 static QByteArray computeHashOfDocument(KTextEditor::Document *doc)
792 {
793     QCryptographicHash cryptographicHash(QCryptographicHash::Sha1);
794     cryptographicHash.addData(doc->text().toUtf8());
795     // allows to catch situations when the URL of the document has changed,
796     // e.g. after a save-as operation, which breaks the handling of source
797     // references for the displayed document
798     cryptographicHash.addData(doc->url().toEncoded());
799 
800     return cryptographicHash.result();
801 }
802 
fillTextHashForProject(KileProject * project,QHash<KileDocument::TextInfo *,QByteArray> & textHash)803 static void fillTextHashForProject(KileProject *project, QHash<KileDocument::TextInfo*, QByteArray> &textHash)
804 {
805     QList<KileProjectItem*> list = project->items();
806     for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
807         KileProjectItem *item = *it;
808 
809         KileDocument::TextInfo *textInfo = item->getInfo();
810         if(!textInfo) {
811             continue;
812         }
813         KTextEditor::Document *document = textInfo->getDoc();
814         if(!document) {
815             continue;
816         }
817         textHash[textInfo] = computeHashOfDocument(document);
818     }
819 }
820 
fillTextHashForMasterDocument(QHash<KileDocument::TextInfo *,QByteArray> & textHash)821 void LivePreviewManager::fillTextHashForMasterDocument(QHash<KileDocument::TextInfo*, QByteArray> &textHash)
822 {
823     // we compute hashes over all the opened files
824     QList<KileDocument::TextInfo*> textDocumentInfos = m_ki->docManager()->textDocumentInfos();
825     for(QList<KileDocument::TextInfo*>::iterator it = textDocumentInfos.begin(); it != textDocumentInfos.end(); ++it) {
826         KileDocument::TextInfo *textInfo = *it;
827         if(!textInfo) {
828             continue;
829         }
830         KTextEditor::Document *document = textInfo->getDoc();
831         if(!document) {
832             continue;
833         }
834         textHash[textInfo] = computeHashOfDocument(document);
835     }
836 }
837 
showPreviewCompileIfNecessary(KileDocument::LaTeXInfo * latexInfo,KTextEditor::View * view)838 void LivePreviewManager::showPreviewCompileIfNecessary(KileDocument::LaTeXInfo *latexInfo, KTextEditor::View *view)
839 {
840     KILE_DEBUG_MAIN;
841     // first, stop any running live preview
842     stopLivePreview();
843 
844     KileProject *project = Q_NULLPTR;
845     LivePreviewUserStatusHandler *userStatusHandler = Q_NULLPTR;
846     PreviewInformation *previewInformation = findPreviewInformation(latexInfo, &project, &userStatusHandler);
847     if(!previewInformation) {
848         KILE_DEBUG_MAIN << "not found";
849         compilePreview(latexInfo, view);
850     }
851     else {
852         Q_ASSERT(userStatusHandler);
853         updateLivePreviewToolActions(userStatusHandler);
854         QHash<KileDocument::TextInfo*, QByteArray> newHash;
855 // 		QString fileName;
856 // 		QFileInfo fileInfo(view->document()->url().path());
857 // 		if(previewInformation->pathToPreviewPathHash.contains(fileInfo.absoluteFilePath())) {
858 // 			KILE_DEBUG_MAIN << "contains";
859 // 			fileName = previewInformation->pathToPreviewPathHash[fileInfo.absoluteFilePath()];
860 // 		}
861 // 		else {
862 // 			KILE_DEBUG_MAIN << "does not contain";
863 // 			fileName = fileInfo.absoluteFilePath();
864 // 		}
865 // 		KILE_DEBUG_MAIN << "fileName:" << fileName;
866         bool masterDocumentSet = !m_ki->getMasterDocumentFileName().isEmpty();
867 
868         if(masterDocumentSet) {
869             fillTextHashForMasterDocument(newHash);
870         }
871         else if(project) {
872             fillTextHashForProject(project, newHash);
873         }
874         else {
875             newHash[latexInfo] = computeHashOfDocument(view->document());
876         }
877 
878         if(newHash != previewInformation->textHash || !QFile::exists(previewInformation->previewFile)) {
879             KILE_DEBUG_MAIN << "hashes don't match";
880             compilePreview(latexInfo, view);
881         }
882         else {
883             KILE_DEBUG_MAIN << "hashes match";
884             showPreviewSuccessful();
885             synchronizeViewWithCursor(latexInfo, view, view->cursorPosition());
886             emit(livePreviewSuccessful());
887         }
888     }
889 }
890 
compilePreview(KileDocument::LaTeXInfo * latexInfo,KTextEditor::View * view)891 void LivePreviewManager::compilePreview(KileDocument::LaTeXInfo *latexInfo, KTextEditor::View *view)
892 {
893     KILE_DEBUG_MAIN << "updating preview";
894     m_ki->viewManager()->setLivePreviewModeForDocumentViewer(true);
895     m_runningPathToPreviewPathHash.clear();
896     m_runningPreviewPathToPathHash.clear();
897 
898     //CAUTION: as saving launches an event loop, we don't want 'compilePreview'
899     //         to be called from within 'compilePreview'
900     m_documentChangedTimer->blockSignals(true);
901     bool saveResult = m_ki->docManager()->fileSaveAll();
902     m_documentChangedTimer->blockSignals(false);
903     // first, we have to save the documents
904     if(!saveResult) {
905         displayErrorMessage(i18n("Some documents could not be saved correctly"));
906         return;
907     }
908 
909     // document is new and hasn't been saved yet at all
910     if(view->document()->url().isEmpty()) {
911         displayErrorMessage(i18n("The document must have been saved before the live preview can be started"));
912         return;
913     }
914 
915     // first, stop any running live preview
916     stopLivePreview();
917 
918     KileProject *project = Q_NULLPTR;
919     LivePreviewUserStatusHandler *userStatusHandler;
920     LaTeXOutputHandler *latexOutputHandler;
921     PreviewInformation *previewInformation = findPreviewInformation(latexInfo, &project, &userStatusHandler, &latexOutputHandler);
922     Q_ASSERT(userStatusHandler);
923     Q_ASSERT(latexOutputHandler);
924     if(!previewInformation) {
925         previewInformation = new PreviewInformation();
926         if(!m_ki->getMasterDocumentFileName().isEmpty()) {
927             m_masterDocumentPreviewInformation = previewInformation;
928         }
929         else if(project) {
930             bool containsInvalidRelativeItem = false;
931             // in the case of a project, we might have to create a similar subdirectory
932             // structure as it is present in the real project in order for LaTeX
933             // to work correctly
934             if(!previewInformation->createSubDirectoriesForProject(project, &containsInvalidRelativeItem)) {
935                 userStatusHandler->setLivePreviewEnabled(false);
936                 if(containsInvalidRelativeItem) {
937                     displayErrorMessage(i18n("The location of one project item is not relative to the project's base directory\n"
938                                              "Live preview for this project has been disabled"), true);
939                 }
940                 else {
941                     displayErrorMessage(i18n("Failed to create the subdirectory structure"));
942                 }
943                 delete previewInformation;
944                 disablePreview();
945                 return;
946             }
947             m_projectToPreviewInformationHash[project] = previewInformation;
948         }
949         else {
950             m_latexInfoToPreviewInformationHash[latexInfo] = previewInformation;
951         }
952     }
953 
954     connect(latexInfo, SIGNAL(aboutToBeDestroyed(KileDocument::TextInfo*)),
955             this, SLOT(removeLaTeXInfo(KileDocument::TextInfo*)),
956             Qt::UniqueConnection);
957 
958     if(project) {
959         handleProjectOpened(project); // create the necessary signal-slot connections
960     }
961 
962     updateLivePreviewToolActions(userStatusHandler);
963     KileTool::LivePreviewLaTeX *latex = dynamic_cast<KileTool::LivePreviewLaTeX *>(m_ki->toolManager()->createTool(userStatusHandler->livePreviewTool(),
964                                         false));
965     if(!latex) {
966         KILE_DEBUG_MAIN<< "couldn't create the live preview tool";
967         return;
968     }
969 
970     // important!
971     latex->setPartOfLivePreview();
972     connect(latex, SIGNAL(done(KileTool::Base*,int,bool)), this, SLOT(toolDone(KileTool::Base*,int,bool)));
973     connect(latex, SIGNAL(destroyed()), this, SLOT(toolDestroyed()));
974 
975     QFileInfo fileInfo;
976     const bool masterDocumentSet = !m_ki->getMasterDocumentFileName().isEmpty();
977     if(masterDocumentSet) {
978         fileInfo = QFileInfo(m_ki->getMasterDocumentFileName());
979     }
980     else if(project) {
981         fileInfo = QFileInfo(m_ki->getCompileNameForProject(project));
982     }
983     else {
984         fileInfo = QFileInfo(m_ki->getCompileName());
985     }
986 
987     const QString inputDir = previewInformation->getTempDir() + LIST_SEPARATOR + fileInfo.absolutePath();
988 
989     // set value of texinput path (only for LivePreviewManager tools)
990     QString texInputPath = KileConfig::teXPaths();
991     if(!texInputPath.isEmpty()) {
992         texInputPath = inputDir + LIST_SEPARATOR + texInputPath;
993     }
994     else {
995         texInputPath = inputDir;
996     }
997     latex->setTeXInputPaths(texInputPath);
998 
999     QString bibInputPath = KileConfig::bibInputPaths();
1000     if(!bibInputPath.isEmpty()) {
1001         bibInputPath = inputDir + LIST_SEPARATOR + bibInputPath;
1002     }
1003     else {
1004         bibInputPath = inputDir;
1005     }
1006     latex->setBibInputPaths(bibInputPath);
1007 
1008     QString bstInputPath = KileConfig::bstInputPaths();
1009     if(!bstInputPath.isEmpty()) {
1010         bstInputPath = inputDir + LIST_SEPARATOR + bstInputPath;
1011     }
1012     else {
1013         bstInputPath = inputDir;
1014     }
1015     latex->setBstInputPaths(bstInputPath);
1016 
1017 // 	m_runningPathToPreviewPathHash[fileInfo.absoluteFilePath()] = tempFile;
1018 // 	m_runningPreviewPathToPathHash[tempFile] = fileInfo.absoluteFilePath();
1019 
1020     // don't emit the 'requestSaveAll' signal
1021 // 	latex->removeFlag(EmitSaveAllSignal);
1022 
1023     latex->setTargetDir(previewInformation->getTempDir());
1024     latex->setSource(fileInfo.absoluteFilePath(), fileInfo.absolutePath());
1025     latex->setLaTeXOutputHandler(latexOutputHandler);
1026 
1027     latex->prepareToRun();
1028 // 	latex->launcher()->setWorkingDirectory(previewInformation->getTempDir());
1029     KILE_DEBUG_MAIN << "dir:" << previewInformation->getTempDir();
1030 
1031     m_runningTextView = view;
1032     m_runningLaTeXInfo = latexInfo;
1033     m_runningProject = project;
1034     m_runningPreviewFile = previewInformation->getTempDir() + '/' + latex->target();
1035     m_runningTextHash.clear();
1036     if(masterDocumentSet) {
1037         fillTextHashForMasterDocument(m_runningTextHash);
1038     }
1039     else if(project) {
1040         fillTextHashForProject(project, m_runningTextHash);
1041     }
1042     else {
1043         m_runningTextHash[latexInfo] = computeHashOfDocument(latexInfo->getDoc());
1044     }
1045     m_runningPreviewInformation = previewInformation;
1046     showPreviewRunning();
1047 
1048     // finally, run the tool
1049     m_ki->toolManager()->run(latex);
1050     emit(livePreviewRunning());
1051 }
1052 
isLivePreviewActive() const1053 bool LivePreviewManager::isLivePreviewActive() const
1054 {
1055     KParts::ReadOnlyPart *viewerPart = m_ki->viewManager()->viewerPart();
1056 
1057     return m_runningPreviewInformation
1058            || (m_shownPreviewInformation
1059                && viewerPart
1060                && viewerPart->url() == QUrl::fromLocalFile(m_shownPreviewInformation->previewFile));
1061 }
1062 
isLivePreviewPossible() const1063 bool LivePreviewManager::isLivePreviewPossible() const
1064 {
1065     return true;
1066 }
1067 
handleDocumentOpened(KileDocument::TextInfo * info)1068 void LivePreviewManager::handleDocumentOpened(KileDocument::TextInfo *info)
1069 {
1070     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1071         return;
1072     }
1073 
1074     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
1075     if(view && view->document() == info->getDoc()) {
1076         handleTextViewActivated(view);
1077     }
1078 }
1079 
handleTextViewActivated(KTextEditor::View * view,bool clearPreview,bool forceCompilation)1080 void LivePreviewManager::handleTextViewActivated(KTextEditor::View *view, bool clearPreview, bool forceCompilation)
1081 {
1082     // when a file is currently being opened, we don't react to the view activation signal as the correct live preview
1083     // tools might not be loaded yet for the document that belongs to 'view'
1084     if(m_bootUpMode || !KileConfig::livePreviewEnabled() || m_ki->docManager()->isOpeningFile()) {
1085         return;
1086     }
1087     if(clearPreview) {
1088         stopAndClearPreview();
1089     }
1090     else {
1091         stopLivePreview();
1092     }
1093     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(view->document()));
1094     if(!latexInfo) {
1095         return;
1096     }
1097     m_documentChangedTimer->stop();
1098 
1099     LivePreviewUserStatusHandler *userStatusHandler = Q_NULLPTR;
1100     findPreviewInformation(latexInfo, Q_NULLPTR, &userStatusHandler);
1101     Q_ASSERT(userStatusHandler);
1102     const bool livePreviewActive = userStatusHandler->isLivePreviewEnabled();
1103     updateLivePreviewToolActions(userStatusHandler);
1104     // update the state of the live preview control button
1105     m_previewForCurrentDocumentAction->setChecked(livePreviewActive);
1106 
1107     if(!livePreviewActive) {
1108         disablePreview();
1109     }
1110     else {
1111         if(forceCompilation) {
1112             compilePreview(latexInfo, view);
1113         }
1114         else {
1115             showPreviewCompileIfNecessary(latexInfo, view);
1116         }
1117     }
1118 }
1119 
handleTextViewClosed(KTextEditor::View * view,bool wasActiveView)1120 void LivePreviewManager::handleTextViewClosed(KTextEditor::View *view, bool wasActiveView)
1121 {
1122     Q_UNUSED(view);
1123     Q_UNUSED(wasActiveView);
1124 
1125     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1126         return;
1127     }
1128 
1129     // check if there is still an open editor tab
1130     if(!KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView()) {
1131         stopAndClearPreview();
1132     }
1133 }
1134 
refreshLivePreview()1135 void LivePreviewManager::refreshLivePreview()
1136 {
1137     KTextEditor::View *textView = m_ki->viewManager()->currentTextView();
1138     if(!textView) {
1139         KILE_DEBUG_MAIN << "no text view is shown; hence, no preview can be shown";
1140         return;
1141     }
1142     handleTextViewActivated(textView, false); // don't automatically clear the preview
1143 }
1144 
recompileLivePreview()1145 void LivePreviewManager::recompileLivePreview()
1146 {
1147     KTextEditor::View *textView = m_ki->viewManager()->currentTextView();
1148     if(!textView) {
1149         KILE_DEBUG_MAIN << "no text view is shown; hence, no preview can be shown";
1150         return;
1151     }
1152     handleTextViewActivated(textView, false, true); // don't automatically clear the preview but force compilation
1153 }
1154 
removeLaTeXInfo(KileDocument::TextInfo * textInfo)1155 void LivePreviewManager::removeLaTeXInfo(KileDocument::TextInfo *textInfo)
1156 {
1157     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(textInfo);
1158     if(!latexInfo) {
1159         return;
1160     }
1161 
1162     if(!m_latexInfoToPreviewInformationHash.contains(latexInfo)) {
1163         return; // nothing to be done
1164     }
1165 
1166     PreviewInformation *previewInformation = m_latexInfoToPreviewInformationHash[latexInfo];
1167 
1168     if(m_runningLaTeXInfo == latexInfo) {
1169         stopLivePreview();
1170     }
1171 
1172     if(previewInformation == m_shownPreviewInformation) {
1173         clearLivePreview();
1174     }
1175 
1176     m_latexInfoToPreviewInformationHash.remove(latexInfo);
1177     delete previewInformation;
1178 }
1179 
removeProject(KileProject * project)1180 void LivePreviewManager::removeProject(KileProject *project)
1181 {
1182     if(!m_projectToPreviewInformationHash.contains(project)) {
1183         return; // nothing to be done
1184     }
1185 
1186     PreviewInformation *previewInformation = m_projectToPreviewInformationHash[project];
1187 
1188     if(m_runningProject == project) {
1189         stopLivePreview();
1190     }
1191 
1192     if(previewInformation == m_shownPreviewInformation) {
1193         clearLivePreview();
1194     }
1195 
1196     m_projectToPreviewInformationHash.remove(project);
1197     delete previewInformation;
1198 }
1199 
handleProjectOpened(KileProject * project)1200 void LivePreviewManager::handleProjectOpened(KileProject *project)
1201 {
1202     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1203         return;
1204     }
1205 
1206     connect(project, SIGNAL(aboutToBeDestroyed(KileProject*)),
1207             this, SLOT(removeProject(KileProject*)),
1208             Qt::UniqueConnection);
1209     connect(project, SIGNAL(projectItemAdded(KileProject*,KileProjectItem*)),
1210             this, SLOT(handleProjectItemAdded(KileProject*,KileProjectItem*)),
1211             Qt::UniqueConnection);
1212     connect(project, SIGNAL(projectItemRemoved(KileProject*,KileProjectItem*)),
1213             this, SLOT(handleProjectItemRemoved(KileProject*,KileProjectItem*)),
1214             Qt::UniqueConnection);
1215 }
1216 
handleProjectItemAdditionOrRemoval(KileProject * project,KileProjectItem * item)1217 void LivePreviewManager::handleProjectItemAdditionOrRemoval(KileProject *project, KileProjectItem *item)
1218 {
1219     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1220         return;
1221     }
1222 
1223     KILE_DEBUG_MAIN;
1224     bool previewNeedsToBeRefreshed = false;
1225 
1226     // we can't use TextInfo pointers here as they might not be set in 'item' yet
1227     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(m_ki->docManager()->textInfoFor(item->url()));
1228     if(latexInfo && m_latexInfoToPreviewInformationHash.contains(latexInfo)) {
1229         PreviewInformation *previewInformation = m_latexInfoToPreviewInformationHash[latexInfo];
1230         if(previewInformation == m_shownPreviewInformation) {
1231             previewNeedsToBeRefreshed = true;
1232         }
1233         removeLaTeXInfo(latexInfo);
1234     }
1235 
1236     if(m_projectToPreviewInformationHash.contains(project)) {
1237         PreviewInformation *previewInformation = m_projectToPreviewInformationHash[project];
1238         if(previewInformation == m_shownPreviewInformation) {
1239             previewNeedsToBeRefreshed = true;
1240         }
1241         removeProject(project);
1242     }
1243 
1244     // finally, check whether the currently activated text view is the 'modified' project item
1245     if(!previewNeedsToBeRefreshed) {
1246         KTextEditor::View *view = m_ki->viewManager()->currentTextView();
1247         // we can't use TextInfo pointers here as they might not be set in 'item' yet
1248         if(view && (view->document()->url() == item->url())) {
1249             previewNeedsToBeRefreshed = true;
1250         }
1251     }
1252 
1253     KILE_DEBUG_MAIN << "previewNeedsToBeRefreshed" << previewNeedsToBeRefreshed;
1254     if(previewNeedsToBeRefreshed) {
1255         // we can't do this here directly as 'item' might not be fully set up yet (e.g., if it has been added)
1256         QTimer::singleShot(0, this, SLOT(refreshLivePreview()));
1257     }
1258 }
1259 
handleProjectItemAdded(KileProject * project,KileProjectItem * item)1260 void LivePreviewManager::handleProjectItemAdded(KileProject *project, KileProjectItem *item)
1261 {
1262     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1263         return;
1264     }
1265     KILE_DEBUG_MAIN;
1266 
1267     // the directory structure in the temporary directory will be updated when
1268     // 'compilePreview' is called; 'handleProjectItemAdditionOrRemoval' will delete
1269     // PreviewInformation objects
1270     handleProjectItemAdditionOrRemoval(project, item);
1271 }
1272 
handleProjectItemRemoved(KileProject * project,KileProjectItem * item)1273 void LivePreviewManager::handleProjectItemRemoved(KileProject *project, KileProjectItem *item)
1274 {
1275     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1276         return;
1277     }
1278 
1279     KILE_DEBUG_MAIN;
1280     handleProjectItemAdditionOrRemoval(project, item);
1281 }
1282 
handleDocumentSavedAs(KTextEditor::View * view,KileDocument::TextInfo * info)1283 void LivePreviewManager::handleDocumentSavedAs(KTextEditor::View *view, KileDocument::TextInfo *info)
1284 {
1285     Q_UNUSED(info);
1286 
1287     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1288         return;
1289     }
1290 
1291     KTextEditor::View *currentTextView = m_ki->viewManager()->currentTextView();
1292     if(view != currentTextView) { // might maybe happen at some point...
1293         // preview will be refreshed the next time that view is activated as the hashes don't
1294         // match anymore
1295         return;
1296     }
1297     refreshLivePreview();
1298 }
1299 
toolDestroyed()1300 void LivePreviewManager::toolDestroyed()
1301 {
1302     KILE_DEBUG_MAIN << "\tLivePreviewManager: tool destroyed" << endl;
1303 }
1304 
handleSpawnedChildTool(KileTool::Base * parent,KileTool::Base * child)1305 void LivePreviewManager::handleSpawnedChildTool(KileTool::Base *parent, KileTool::Base *child)
1306 {
1307     Q_UNUSED(parent);
1308 
1309     if(m_bootUpMode || !KileConfig::livePreviewEnabled()) {
1310         return;
1311     }
1312 
1313     KILE_DEBUG_MAIN;
1314     // only connect the signal for tools that are part of live preview!
1315     if(parent->isPartOfLivePreview()) {
1316         connect(child, SIGNAL(done(KileTool::Base*,int,bool)), this, SLOT(childToolDone(KileTool::Base*,int,bool)));
1317     }
1318 }
1319 
toolDone(KileTool::Base * base,int i,bool childToolSpawned)1320 void LivePreviewManager::toolDone(KileTool::Base *base, int i, bool childToolSpawned)
1321 {
1322     KILE_DEBUG_MAIN << "\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << i << endl;
1323     KILE_DEBUG_MAIN << "\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << i << endl;
1324     KILE_DEBUG_MAIN << "\tLivePreviewManager: tool done" << base->name() << i << childToolSpawned <<  endl;
1325     if(i != Success) {
1326         KILE_DEBUG_MAIN << "tool didn't return successfully, doing nothing";
1327         showPreviewFailed();
1328         clearRunningLivePreviewInformation();
1329         emit(livePreviewStopped());
1330     }
1331     // a LaTeX variant must have finished for the preview to be complete
1332     else if(!childToolSpawned && dynamic_cast<KileTool::LaTeX*>(base)) {
1333         updatePreviewInformationAfterCompilationFinished();
1334         clearRunningLivePreviewInformation();
1335     }
1336 }
1337 
childToolDone(KileTool::Base * base,int i,bool childToolSpawned)1338 void LivePreviewManager::childToolDone(KileTool::Base *base, int i, bool childToolSpawned)
1339 {
1340     KILE_DEBUG_MAIN << "\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << i << endl;
1341     KILE_DEBUG_MAIN << "\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << i << endl;
1342     KILE_DEBUG_MAIN << "\tLivePreviewManager: child tool done" << base->name() << i << childToolSpawned << endl;
1343     if(!m_ki->viewManager()->viewerPart()) {
1344         return;
1345     }
1346     if(i != Success) {
1347         KILE_DEBUG_MAIN << "tool didn't return successfully, doing nothing";
1348         showPreviewFailed();
1349         clearRunningLivePreviewInformation();
1350         emit(livePreviewStopped());
1351     }
1352     // a LaTeX variant must have finished for the preview to be complete
1353     else if(!childToolSpawned && dynamic_cast<KileTool::LaTeX*>(base)) {
1354         updatePreviewInformationAfterCompilationFinished();
1355         clearRunningLivePreviewInformation();
1356     }
1357 }
1358 
updatePreviewInformationAfterCompilationFinished()1359 void LivePreviewManager::updatePreviewInformationAfterCompilationFinished()
1360 {
1361     if(!m_runningPreviewInformation) { // LivePreview has been stopped in the meantime
1362         return;
1363     }
1364 
1365     m_shownPreviewInformation = m_runningPreviewInformation;
1366     m_shownPreviewInformation->pathToPreviewPathHash = m_runningPathToPreviewPathHash;
1367     m_shownPreviewInformation->previewPathToPathHash = m_runningPreviewPathToPathHash;
1368     m_shownPreviewInformation->textHash = m_runningTextHash;
1369     m_shownPreviewInformation->previewFile = m_runningPreviewFile;
1370 
1371     m_runningPreviewInformation = Q_NULLPTR;
1372 
1373     bool hadToOpen = false;
1374     if(!ensureDocumentIsOpenInViewer(m_shownPreviewInformation, &hadToOpen)) {
1375         clearLivePreview();
1376         // must happen after the call to 'clearLivePreview' only
1377         showPreviewFailed();
1378         emit(livePreviewStopped());
1379         return;
1380     }
1381 
1382     // as 'ensureDocumentIsOpenInViewer' won't reload when the document is open
1383     // already, we have to do it here
1384     if(!hadToOpen) {
1385         reloadDocumentInViewer();
1386     }
1387 
1388     if(m_ki->viewManager()->isSynchronisingCursorWithDocumentViewer()) {
1389         synchronizeViewWithCursor(m_runningLaTeXInfo, m_runningTextView, m_runningTextView->cursorPosition());
1390     }
1391 
1392     showPreviewSuccessful();
1393     emit(livePreviewSuccessful());
1394 }
1395 
displayErrorMessage(const QString & text,bool clearFirst)1396 void LivePreviewManager::displayErrorMessage(const QString &text, bool clearFirst)
1397 {
1398     if(clearFirst) {
1399         m_ki->errorHandler()->clearMessages();
1400     }
1401     m_ki->errorHandler()->printMessage(KileTool::Error, text, i18n("LivePreview"));
1402 }
1403 
1404 }
1405 
1406