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