1 /*****************************************************************************
2 *   Copyright (C) 2004 by Jeroen Wijnhout (Jeroen.Wijnhout@kdemail.net)      *
3 *             (C) 2006-2018 by Michel Ludwig (michel.ludwig@kdemail.net)     *
4 *             (C) 2007 by Holger Danielsson (holger.danielsson@versanet.de)  *
5 ******************************************************************************/
6 
7 /***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "kiledocmanager.h"
17 
18 #include <QAbstractItemView>
19 #include <QApplication>
20 #include <QDir>
21 #include <QDropEvent>
22 #include <QFile>
23 #include <QLabel>
24 #include <QList>
25 #include <QMimeData>
26 #include <QMimeType>
27 #include <QMimeDatabase>
28 #include <QProgressDialog>
29 #include <QPushButton>
30 #include <QTemporaryFile>
31 #include <QTextCodec>
32 #include <QUrl>
33 
34 #include <KConfigGroup>
35 #include <KEncodingFileDialog>
36 #include <KIO/CopyJob>
37 #include <KIO/DeleteJob>
38 #include <KIO/FileCopyJob>
39 #include <KIO/StatJob>
40 #include <KJobWidgets>
41 #include <KLocalizedString>
42 #include <KMessageBox>
43 #include <KTextEditor/Document>
44 #include <KTextEditor/Editor>
45 #include <KTextEditor/View>
46 
47 #include "dialogs/cleandialog.h"
48 #include "dialogs/listselector.h"
49 #include "dialogs/managetemplatesdialog.h"
50 #include "dialogs/newfilewizard.h"
51 #include "dialogs/projectdialogs.h"
52 #include "documentinfo.h"
53 #include "errorhandler.h"
54 #include "kileconfig.h"
55 #include "kiledebug.h"
56 #include "kileinfo.h"
57 #include "kileproject.h"
58 #include "kilestdtools.h"
59 #include "kiletool_enums.h"
60 #include "kiletool.h"
61 #include "kiletoolmanager.h"
62 #include "kileviewmanager.h"
63 #include "livepreview.h"
64 #include "parser/parsermanager.h"
65 #include "scriptmanager.h"
66 #include "templates.h"
67 #include "utilities.h"
68 #include "widgets/filebrowserwidget.h"
69 #include "widgets/konsolewidget.h"
70 #include "widgets/projectview.h"
71 #include "widgets/structurewidget.h"
72 
73 /*
74  * Newly created text documents have an empty URL and a non-empty document name.
75  */
76 
77 #define MAX_NUMBER_OF_STORED_SETTINGS 50
78 
79 namespace KileDocument
80 {
81 
Manager(KileInfo * info,QObject * parent,const char * name)82 Manager::Manager(KileInfo *info, QObject *parent, const char *name) :
83     QObject(parent),
84     m_ki(info),
85     m_progressDialog(Q_NULLPTR),
86     m_currentlySavingAll(false),
87     m_currentlyOpeningFile(false)
88 {
89     setObjectName(name);
90     m_editor = KTextEditor::Editor::instance();
91     if(!m_editor) {
92         KMessageBox::error(m_ki->mainWindow(), i18n("No editor component found. Please check your KDE installation."),
93                            i18n("No editor component found."));
94     }
95 }
96 
~Manager()97 Manager::~Manager()
98 {
99     KILE_DEBUG_MAIN << "==KileDocument::Manager::~Manager()=========";
100     if(m_progressDialog.isNull()) {
101         delete m_progressDialog.data();
102     }
103 }
104 
readConfig()105 void Manager::readConfig()
106 {
107 }
108 
writeConfig()109 void Manager::writeConfig()
110 {
111 }
112 
trashDoc(TextInfo * docinfo,KTextEditor::Document * doc)113 void Manager::trashDoc(TextInfo *docinfo, KTextEditor::Document *doc /*= Q_NULLPTR */ )
114 {
115     KILE_DEBUG_MAIN << "==void Manager::trashDoc(" << docinfo->url().toLocalFile() << ")=====";
116 
117     if(m_ki->isOpen(docinfo->url())) {
118         return;
119     }
120 
121     if(doc) {
122         doc = docinfo->getDoc();
123     }
124 
125     //look for doc before we detach the docinfo
126     //if we do it the other way around, docFor will always return nil
127     if(!doc) {
128         doc = docFor(docinfo->url());
129     }
130 
131     KILE_DEBUG_MAIN << "DETACHING " << docinfo;
132     docinfo->detach();
133 
134     KILE_DEBUG_MAIN << "\tTRASHING " <<  doc;
135     if(!doc) {
136         return;
137     }
138 
139     KILE_DEBUG_MAIN << "just checking: docinfo->getDoc() =  " << docinfo->getDoc();
140     KILE_DEBUG_MAIN << "just checking: docFor(docinfo->url()) = " << docFor(docinfo->url());
141 
142     for (int i = 0; i < m_textInfoList.count(); ++i) {
143         if((m_textInfoList.at(i) != docinfo) && (m_textInfoList.at(i)->getDoc() == doc)) {
144             KMessageBox::information(0, i18n("The internal structure of Kile is corrupted (probably due to a bug in Kile). Please select Save All from the File menu and close Kile.\nThe Kile team apologizes for any inconvenience and would appreciate a bug report."));
145             qWarning() << "docinfo " << m_textInfoList.at(i) << " url " << m_textInfoList.at(i)->url().fileName() << " has a wild pointer!!!";
146         }
147     }
148 
149     KILE_DEBUG_MAIN << "DELETING doc";
150     delete doc;
151 }
152 
153 // update all Info's with changed user commands
updateInfos()154 void Manager::updateInfos()
155 {
156     for(QList<TextInfo*>::iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) {
157         (*it)->updateStructLevelInfo();
158     }
159 }
160 
isOpeningFile()161 bool Manager::isOpeningFile()
162 {
163     return m_currentlyOpeningFile;
164 }
165 
getEditor()166 KTextEditor::Editor* Manager::getEditor()
167 {
168     return m_editor;
169 }
170 
docFor(const QUrl & url)171 KTextEditor::Document* Manager::docFor(const QUrl &url)
172 {
173     for(QList<TextInfo*>::iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) {
174         TextInfo *info = *it;
175 
176         if(m_ki->similarOrEqualURL(info->url(), url)) {
177             return info->getDoc();
178         }
179     }
180 
181     return Q_NULLPTR;
182 }
183 
getInfo() const184 TextInfo* Manager::getInfo() const
185 {
186     KTextEditor::Document *doc = m_ki->activeTextDocument();
187     if(doc) {
188         return textInfoFor(doc);
189     }
190     else {
191         return Q_NULLPTR;
192     }
193 }
194 
textInfoFor(const QUrl & url)195 TextInfo* Manager::textInfoFor(const QUrl &url)
196 {
197     if(url.isEmpty()) {
198         return Q_NULLPTR;
199     }
200 
201     KILE_DEBUG_MAIN << "==KileInfo::textInfoFor(" << url << ")==========================";
202 
203     for(QList<TextInfo*>::iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) {
204         TextInfo *info = *it;
205 
206         if (info->url() == url) {
207             return info;
208         }
209     }
210 
211     // the URL might belong to a TextInfo* which currently doesn't have a KTextEditor::Document*
212     // associated with it, i.e. a project item which isn't open in the editor
213     for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) {
214         KileProjectItem *item = (*it)->item(url);
215 
216         // all project items (across different projects) that share a URL have the same TextInfo*;
217         // so, the first one we find is good enough
218         if(item) {
219             KileDocument::TextInfo *info = item->getInfo();
220             if(info) {
221                 return info;
222             }
223         }
224     }
225 
226     KILE_DEBUG_MAIN << "\tCOULD NOT find info for " << url;
227     return Q_NULLPTR;
228 }
229 
textInfoFor(KTextEditor::Document * doc) const230 TextInfo* Manager::textInfoFor(KTextEditor::Document* doc) const
231 {
232     if(!doc) {
233         return Q_NULLPTR;
234     }
235 
236     // TextInfo* objects that contain KTextEditor::Document* pointers must be open in the editor, i.e.
237     // we don't have to look through the project items
238     for(QList<TextInfo*>::const_iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) {
239         if((*it)->getDoc() == doc) {
240             return (*it);
241         }
242     }
243 
244     KILE_DEBUG_MAIN << "\tCOULD NOT find info for" << doc->url() << "by searching via a KTextEditor::Document*";
245     return Q_NULLPTR;
246 }
247 
urlFor(TextInfo * textInfo)248 QUrl Manager::urlFor(TextInfo* textInfo)
249 {
250     KileProjectItem *item = itemFor(textInfo);
251 
252     QUrl url;
253     if(item) {
254         url = item->url(); // all items with 'textInfo' share the same URL
255     }
256     else {
257         KTextEditor::Document *document = textInfo->getDoc();
258         if(document) {
259             url = document->url();
260         }
261     }
262     return url;
263 }
264 
projectForMember(const QUrl & memberUrl)265 KileProject* Manager::projectForMember(const QUrl &memberUrl)
266 {
267     for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) {
268         KileProject *project = *it;
269 
270         if(project->contains(memberUrl)) {
271             return project;
272         }
273     }
274     return Q_NULLPTR;
275 }
276 
projectFor(const QUrl & projecturl)277 KileProject* Manager::projectFor(const QUrl &projecturl)
278 {
279     //find project with url = projecturl
280     for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) {
281         KileProject *project = *it;
282         if(project->url() == projecturl) {
283             return project;
284         }
285     }
286 
287     return Q_NULLPTR;
288 }
289 
projectFor(const QString & name)290 KileProject* Manager::projectFor(const QString &name)
291 {
292     //find project with url = projecturl
293     for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) {
294         KileProject *project = *it;
295 
296         if (project->name() == name) {
297             return project;
298         }
299     }
300 
301     return Q_NULLPTR;
302 }
303 
itemFor(const QUrl & url,KileProject * project) const304 KileProjectItem* Manager::itemFor(const QUrl &url, KileProject *project /*=0L*/) const
305 {
306     if (!project) {
307         for(QList<KileProject*>::const_iterator it = m_projects.begin(); it != m_projects.end(); ++it) {
308             KileProject *project = *it;
309 
310             KileProjectItem *item = project->item(url);
311             if(item) {
312                 return item;
313             }
314         }
315         return Q_NULLPTR;
316     }
317     else {
318         return project->item(url);
319     }
320 }
321 
itemFor(TextInfo * docinfo,KileProject * project) const322 KileProjectItem* Manager::itemFor(TextInfo *docinfo, KileProject *project /*=0*/) const
323 {
324     if (!project) {
325         for(QList<KileProject*>::const_iterator it = m_projects.begin(); it != m_projects.end(); ++it) {
326             KileProject *project = *it;
327 
328             KileProjectItem *item = project->item(docinfo);
329             if(item) {
330                 return item;
331             }
332         }
333         return Q_NULLPTR;
334     }
335     else {
336         return project->item(docinfo);
337     }
338 }
339 
itemsFor(Info * docinfo) const340 QList<KileProjectItem*> Manager::itemsFor(Info *docinfo) const
341 {
342     if(!docinfo) {
343         return QList<KileProjectItem*>();
344     }
345 
346     KILE_DEBUG_MAIN << "==KileInfo::itemsFor(" << docinfo->url().fileName() << ")============";
347     QList<KileProjectItem*> list;
348     for(QList<KileProject*>::const_iterator it = m_projects.begin(); it != m_projects.end(); ++it) {
349         KileProject *project = *it;
350 
351         KILE_DEBUG_MAIN << "\tproject: " << (*it)->name();
352         if(project->contains(docinfo)) {
353             KILE_DEBUG_MAIN << "\t\tcontains";
354             list.append(project->item(docinfo));
355         }
356     }
357 
358     return list;
359 }
360 
itemsFor(const QUrl & url) const361 QList<KileProjectItem*> Manager::itemsFor(const QUrl &url) const
362 {
363     QList<KileProjectItem*> list;
364     for(QList<KileProject*>::const_iterator it = m_projects.begin(); it != m_projects.end(); ++it) {
365         KileProject *project = *it;
366 
367         if(project->contains(url)) {
368             list.append(project->item(url));
369         }
370     }
371 
372     return list;
373 }
374 
isProjectOpen()375 bool Manager::isProjectOpen()
376 {
377     return ( m_projects.count() > 0 );
378 }
379 
activeProject()380 KileProject* Manager::activeProject()
381 {
382     KTextEditor::Document *doc = m_ki->activeTextDocument();
383 
384     if (doc) {
385         return projectForMember(doc->url());
386     }
387     else {
388         return Q_NULLPTR;
389     }
390 }
391 
activeProjectItem()392 KileProjectItem* Manager::activeProjectItem()
393 {
394     KileProject *curpr = activeProject();
395     KTextEditor::Document *doc = m_ki->activeTextDocument();
396 
397     if (curpr && doc) {
398         QList<KileProjectItem*> list = curpr->items();
399 
400         for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
401             KileProjectItem *item = *it;
402 
403             if (item->url() == doc->url()) {
404                 return item;
405             }
406         }
407     }
408 
409     return Q_NULLPTR;
410 }
411 
createTextDocumentInfo(KileDocument::Type type,const QUrl & url,const QUrl & baseDirectory)412 TextInfo* Manager::createTextDocumentInfo(KileDocument::Type type, const QUrl &url, const QUrl& baseDirectory)
413 {
414     TextInfo *docinfo = Q_NULLPTR;
415 
416     // check whether this URL belongs to an opened project and a TextInfo* object has already
417     // been created for that URL
418     docinfo = textInfoFor(url);
419 
420     if(!docinfo) {
421         switch(type) {
422         case Undefined: // fall through
423         case Text:
424             KILE_DEBUG_MAIN << "CREATING TextInfo for " << url.url();
425             docinfo = new TextInfo(m_ki->extensions(),
426                                    m_ki->abbreviationManager(),
427                                    m_ki->parserManager());
428             break;
429         case LaTeX:
430             KILE_DEBUG_MAIN << "CREATING LaTeXInfo for " << url.url();
431             docinfo = new LaTeXInfo(m_ki->extensions(),
432                                     m_ki->abbreviationManager(),
433                                     m_ki->latexCommands(),
434                                     m_ki->editorExtension(),
435                                     m_ki->configurationManager(),
436                                     m_ki->codeCompletionManager(),
437                                     m_ki->livePreviewManager(),
438                                     m_ki->viewManager(),
439                                     m_ki->parserManager());
440             break;
441         case BibTeX:
442             KILE_DEBUG_MAIN << "CREATING BibInfo for " << url.url();
443             docinfo = new BibInfo(m_ki->extensions(),
444                                   m_ki->abbreviationManager(),
445                                   m_ki->parserManager(),
446                                   m_ki->latexCommands());
447             break;
448         case Script:
449             KILE_DEBUG_MAIN << "CREATING ScriptInfo for " << url.url();
450             docinfo = new ScriptInfo(m_ki->extensions(),
451                                      m_ki->abbreviationManager(),
452                                      m_ki->parserManager());
453             break;
454         }
455         docinfo->setBaseDirectory(baseDirectory);
456         emit(documentInfoCreated(docinfo));
457         m_textInfoList.append(docinfo);
458     }
459 
460     KILE_DEBUG_MAIN << "DOCINFO: returning " << docinfo << " " << docinfo->url().fileName();
461     return docinfo;
462 }
463 
recreateTextDocumentInfo(TextInfo * oldinfo)464 void Manager::recreateTextDocumentInfo(TextInfo *oldinfo)
465 {
466     QList<KileProjectItem*> list = itemsFor(oldinfo);
467     QUrl url = oldinfo->url();
468     TextInfo *newinfo = createTextDocumentInfo(m_ki->extensions()->determineDocumentType(url), url, oldinfo->getBaseDirectory());
469 
470     newinfo->setDoc(oldinfo->getDoc());
471 
472     for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
473         (*it)->setInfo(newinfo);
474     }
475 
476     removeTextDocumentInfo(oldinfo);
477 
478     emit(updateStructure(false, newinfo));
479 }
480 
removeTextDocumentInfo(TextInfo * docinfo,bool closingproject)481 bool Manager::removeTextDocumentInfo(TextInfo *docinfo, bool closingproject /* = false */)
482 {
483     KILE_DEBUG_MAIN << "==Manager::removeTextDocumentInfo(Info *docinfo)=====";
484     QList<KileProjectItem*> itms = itemsFor(docinfo);
485     bool oneItem = false;
486     if(itms.count() == 1) {
487         oneItem = true;
488     }
489 
490     if(itms.count() == 0 || ( closingproject && oneItem )) {
491         KILE_DEBUG_MAIN << "\tremoving " << docinfo <<  " count = " << m_textInfoList.count();
492 
493         // we still have to stop parsing for 'docinfo'
494         QUrl url = urlFor(docinfo);
495         if(url.isValid()) {
496             m_ki->parserManager()->stopDocumentParsing(url);
497         }
498 
499         m_textInfoList.removeAll(docinfo);
500 
501         emit(closingDocument(docinfo));
502 
503         cleanupDocumentInfoForProjectItems(docinfo);
504         delete docinfo;
505 
506         return true;
507     }
508 
509     KILE_DEBUG_MAIN << "\tnot removing " << docinfo;
510     return false;
511 }
512 
513 
createDocument(const QUrl & url,TextInfo * docinfo,const QString & encoding,const QString & mode,const QString & highlight)514 KTextEditor::Document* Manager::createDocument(const QUrl &url, TextInfo *docinfo, const QString& encoding,
515         const QString& mode,
516         const QString& highlight)
517 {
518     KILE_DEBUG_MAIN << "==KTextEditor::Document* Manager::createDocument()===========";
519 
520     KTextEditor::Document *doc = Q_NULLPTR;
521 
522     if(!m_editor) {
523         return Q_NULLPTR;
524     }
525 
526     doc = docFor(url);
527     if (doc) {
528         qWarning() << url << " already has a document!";
529         return doc;
530     }
531     doc = m_editor->createDocument(Q_NULLPTR);
532     KILE_DEBUG_MAIN << "appending document " <<  doc;
533 
534     connect(doc, &KTextEditor::Document::canceled, [=] (const QString &errMsg) {
535         if(!errMsg.isEmpty()) {
536             KMessageBox::error(m_ki->mainWindow(), i18n("The URL \"%1\" couldn't be opened.\n\n%2", url.toDisplayString(), errMsg),
537                                i18n("Cannot open URL"));
538         }
539         else {
540             KMessageBox::error(m_ki->mainWindow(), i18n("The URL \"%1\" couldn't be opened.", url.toDisplayString()), i18n("Cannot open URL"));
541         }
542     });
543 
544     docinfo->setDoc(doc); // do this here to set up all the signals correctly in 'TextInfo'
545     doc->setEncoding(encoding);
546 
547     KILE_DEBUG_MAIN << "url is = " << docinfo->url();
548 
549     if(!url.isEmpty()) {
550         bool r = doc->openUrl(url);
551         if(!r) {
552             KILE_WARNING_MAIN << "couldn't open the url" << url;
553             docinfo->detach();
554             delete doc;
555             return Q_NULLPTR;
556         }
557         // don't add scripts to the recent files
558         if(r && docinfo->getType() != Script) {
559             emit(addToRecentFiles(url));
560         }
561     }
562 
563     //handle changes of the document
564     connect(doc, &KTextEditor::Document::documentNameChanged, this, &KileDocument::Manager::documentNameChanged);
565     connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KileDocument::Manager::documentUrlChanged);
566     connect(doc, &KTextEditor::Document::readWriteChanged, this, &KileDocument::Manager::documentReadWriteStateChanged);
567 
568     connect(doc, &KTextEditor::Document::modifiedChanged, this, &KileDocument::Manager::newDocumentStatus);
569     KTextEditor::ModificationInterface *modificationInterface = qobject_cast<KTextEditor::ModificationInterface*>(doc);
570     if(modificationInterface) {
571         modificationInterface->setModifiedOnDiskWarning(true);
572         connect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)),
573                 this, SIGNAL(documentModificationStatusChanged(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)));
574     }
575 
576     if(!mode.isEmpty()) {
577         docinfo->setMode(mode);     // this ensures that mode passed with the mode parameter is actually used
578     }
579     if(!highlight.isEmpty()) {
580         docinfo->setHighlightingMode(highlight);
581     }
582 
583     {
584         // FIXME: the whole structure updating stuff needs to be rewritten; updates should originate from
585         //        the docinfo only, i.e. the structure view should just react to changes!
586 
587         // small 'trick' to select the right overloaded slot:
588         void (KileWidget::StructureWidget::*slot)(KileDocument::Info *) = &KileWidget::StructureWidget::update;
589         connect(docinfo, &KileDocument::Info::completed, m_ki->structureWidget(), slot);
590     }
591 
592     KILE_DEBUG_MAIN << "createDocument: url " << doc->url();
593     KILE_DEBUG_MAIN << "createDocument: SANITY check: " << (docinfo->getDoc() == docFor(docinfo->url()));
594     return doc;
595 }
596 
597 // WARNING: 'item' must have been set up with a TextInfo* object already
loadItem(KileDocument::Type type,KileProjectItem * item,const QString & text,bool openProjectItemViews)598 KTextEditor::View* Manager::loadItem(KileDocument::Type type, KileProjectItem *item, const QString & text, bool openProjectItemViews)
599 {
600     KTextEditor::View *view = Q_NULLPTR;
601 
602     KILE_DEBUG_MAIN << "==loadItem(" << item->url() << ")======";
603 
604     if(item->type() != KileProjectItem::Image) {
605         view = loadText(type, item->url(), item->encoding(), openProjectItemViews && item->isOpen(), item->mode(), item->highlight(), text);
606         KILE_DEBUG_MAIN << "\tloadItem: docfor = " << docFor(item->url());
607 
608         TextInfo *docinfo = item->getInfo();
609 
610         KILE_DEBUG_MAIN << "\tloadItem: docinfo = " << docinfo << " doc = " << docinfo->getDoc() << " docfor = " << docFor(docinfo->url());
611         if ( docinfo->getDoc() != docFor(docinfo->url()) ) qWarning() << "docinfo->getDoc() != docFor()";
612     }
613     else {
614         KILE_DEBUG_MAIN << "\tloadItem: no document generated";
615         TextInfo *docinfo = item->getInfo();
616 
617         if(!docFor(item->url())) {
618             docinfo->detach();
619             KILE_DEBUG_MAIN << "\t\t\tdetached";
620         }
621     }
622 
623     return view;
624 }
625 
loadText(KileDocument::Type type,const QUrl & url,const QString & encoding,bool create,const QString & mode,const QString & highlight,const QString & text,int index,const QUrl & baseDirectory)626 KTextEditor::View* Manager::loadText(KileDocument::Type type, const QUrl &url, const QString& encoding,
627                                      bool create,
628                                      const QString& mode,
629                                      const QString& highlight,
630                                      const QString& text,
631                                      int index,
632                                      const QUrl &baseDirectory)
633 {
634     KILE_DEBUG_MAIN << "==loadText(" << url.url() << ")=================";
635     //if doc already opened, update the structure view and return the view
636     if(!url.isEmpty() && m_ki->isOpen(url)) {
637         return m_ki->viewManager()->switchToTextView(url);
638     }
639 
640     TextInfo *docinfo = createTextDocumentInfo(type, url, baseDirectory);
641     KTextEditor::Document *doc = createDocument(url, docinfo, encoding, mode, highlight);
642     if(!doc) {
643         removeTextDocumentInfo(docinfo);
644         return Q_NULLPTR;
645     }
646 
647     m_ki->structureWidget()->clean(docinfo);
648 
649     if(!text.isEmpty()) {
650         doc->setText(text);
651     }
652 
653     if (doc && create) {
654         return m_ki->viewManager()->createTextView(docinfo, index);
655     }
656 
657     KILE_DEBUG_MAIN << "just after createView()";
658     KILE_DEBUG_MAIN << "\tdocinfo = " << docinfo << " doc = " << docinfo->getDoc() << " docfor = " << docFor(docinfo->url());
659 
660     return Q_NULLPTR;
661 }
662 
663 //FIXME: template stuff should be in own class
loadTemplate(TemplateItem * sel)664 KTextEditor::View* Manager::loadTemplate(TemplateItem *sel)
665 {
666     KILE_DEBUG_MAIN << "templateitem *sel = " << sel;
667     QString text;
668 
669     if(!sel) {
670         return Q_NULLPTR;
671     }
672 
673     if (sel->name() != KileTemplate::Manager::defaultEmptyTemplateCaption()
674             && sel->name() != KileTemplate::Manager::defaultEmptyLaTeXTemplateCaption()
675             && sel->name() != KileTemplate::Manager::defaultEmptyBibTeXTemplateCaption()) {
676         if(!m_editor) {
677             return Q_NULLPTR;
678         }
679         //create a new document to open the template in
680         KTextEditor::Document *tempdoc = m_editor->createDocument(Q_NULLPTR);
681 
682         if (!tempdoc->openUrl(QUrl::fromLocalFile(sel->path()))) {
683             KMessageBox::error(m_ki->mainWindow(), i18n("Could not find template: %1", sel->name()), i18n("File Not Found"));
684         }
685         else {
686             //substitute templates variables
687             text = tempdoc->text();
688             delete tempdoc;
689             replaceTemplateVariables(text);
690         }
691     }
692 
693     KileDocument::Type type = sel->type();
694     //always set the base directory for scripts
695     return createDocumentWithText(text, type, QString(), (type == KileDocument::Script ? QUrl::fromLocalFile(m_ki->scriptManager()->getLocalScriptDirectory()) : QUrl()));
696 }
697 
createDocumentWithText(const QString & text,KileDocument::Type type,const QString &,const QUrl & baseDirectory)698 KTextEditor::View* Manager::createDocumentWithText(const QString& text, KileDocument::Type type /* = KileDocument::Undefined */, const QString& /* extension */, const QUrl &baseDirectory)
699 {
700     KTextEditor::View *view = loadText(type, QUrl(), QString(), true, QString(), QString(), text, -1, baseDirectory);
701     if(view) {
702         //FIXME this shouldn't be necessary!!!
703         view->document()->setModified(true);
704         newDocumentStatus(view->document());
705     }
706 
707     return view;
708 }
709 
createNewJScript()710 KTextEditor::View* Manager::createNewJScript()
711 {
712     KTextEditor::View *view = createDocumentWithText(QString(), Script, "js", QUrl::fromLocalFile(m_ki->scriptManager()->getLocalScriptDirectory()));
713     emit(updateStructure(false, Q_NULLPTR));
714     emit(updateModeStatus());
715     return view;
716 }
717 
createNewLaTeXDocument()718 KTextEditor::View* Manager::createNewLaTeXDocument()
719 {
720     KTextEditor::View *view = createDocumentWithText(QString(), LaTeX);
721     emit(updateStructure(false, Q_NULLPTR));
722     emit(updateModeStatus());
723     return view;
724 }
725 
replaceTemplateVariables(QString & line)726 void Manager::replaceTemplateVariables(QString &line)
727 {
728     line=line.replace("$$AUTHOR$$", KileConfig::author());
729     line=line.replace("$$DOCUMENTCLASSOPTIONS$$", KileConfig::documentClassOptions());
730     if (!KileConfig::templateEncoding().isEmpty()) {
731         line=line.replace("$$INPUTENCODING$$", "\\usepackage["+ KileConfig::templateEncoding() +"]{inputenc}");
732     }
733     else {
734         line = line.remove("$$INPUTENCODING$$");
735     }
736 }
737 
createTemplate()738 void Manager::createTemplate()
739 {
740     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
741     if (view) {
742         if (view->document()->isModified()) {
743             KMessageBox::information(m_ki->mainWindow(),i18n("Please save the file first."));
744             return;
745         }
746     }
747     else {
748         KMessageBox::information(m_ki->mainWindow(),i18n("Open/create a document first."));
749         return;
750     }
751 
752     QUrl url = view->document()->url();
753     KileDocument::Type type = m_ki->extensions()->determineDocumentType(url);
754 
755     if(type == KileDocument::Undefined || type == KileDocument::Text) {
756         KMessageBox::information(m_ki->mainWindow(),i18n("A template for this type of document cannot be created."));
757         return;
758     }
759 
760     ManageTemplatesDialog mtd(m_ki->templateManager(), url, i18n("Create Template From Document"));
761     mtd.exec();
762 }
763 
removeTemplate()764 void Manager::removeTemplate()
765 {
766     ManageTemplatesDialog mtd(m_ki->templateManager(), i18n("Remove Template"));
767     mtd.exec();
768 }
769 
fileNew(KileDocument::Type type)770 void Manager::fileNew(KileDocument::Type type)
771 {
772     NewFileWizard *nfw = new NewFileWizard(m_ki->templateManager(), type, m_ki->mainWindow());
773     if(nfw->exec()) {
774         KTextEditor::View *view = loadTemplate(nfw->getSelection());
775         if(view) {
776             if(nfw->useWizard()) {
777                 emit(startWizard());
778             }
779             emit(updateStructure(false, Q_NULLPTR));
780             emit(updateModeStatus());
781         }
782     }
783     delete nfw;
784 }
785 
fileNewScript()786 void Manager::fileNewScript()
787 {
788     fileNew(KileDocument::Script);
789 }
790 
fileNew(const QUrl & url)791 void Manager::fileNew(const QUrl &url)
792 {
793     //create an empty file
794     QFile file(url.toLocalFile());
795     file.open(QIODevice::ReadWrite);
796     file.close();
797 
798     fileOpen(url, QString());
799 }
800 
fileOpen()801 void Manager::fileOpen()
802 {
803     //determine the starting dir for the file dialog
804     QString compileName = m_ki->getCompileName();
805     QString currentDir;
806     if(QFileInfo(compileName).exists()) {
807         currentDir = QFileInfo(compileName).absolutePath();
808     }
809     else {
810         currentDir = m_ki->fileSelector()->currentUrl().toLocalFile();
811     }
812 
813     // use a filter for fileOpen dialog
814     Extensions *extensions = m_ki->extensions();
815     QString filter = extensions->fileFilterKDEStyle(true, {KileDocument::Extensions::TEX,
816                      KileDocument::Extensions::PACKAGES,
817                      KileDocument::Extensions::BIB,
818                      KileDocument::Extensions::METAPOST
819                                                           });
820 
821     // try to get the current encoding, this is kind of ugly ...
822     QString encoding = m_ki->toolManager()->config()->group("Kate Document Defaults").readEntry("Encoding","");
823 
824     //get the URLs
825     KEncodingFileDialog::Result result = KEncodingFileDialog::getOpenUrlsAndEncoding(encoding, QUrl::fromLocalFile(currentDir), filter, m_ki->mainWindow(), i18n("Open Files"));
826 
827     //open them
828     const QList<QUrl>& urls = result.URLs;
829     for (QList<QUrl>::ConstIterator i = urls.begin(); i != urls.end(); ++i) {
830         const QUrl& url = *i;
831         if(m_ki->extensions()->isProjectFile(url)) { // this can happen... (bug 317432)
832             KILE_DEBUG_MAIN << "file is a project file:" << url;
833             projectOpen(url);
834             continue;
835         }
836 
837         fileOpen(url, result.encoding);
838     }
839 }
840 
fileSelected(const KFileItem & file)841 void Manager::fileSelected(const KFileItem& file)
842 {
843     fileSelected(file.url());
844 }
845 
fileSelected(const KileProjectItem * item)846 void Manager::fileSelected(const KileProjectItem * item)
847 {
848     fileOpen(item->url(), item->encoding());
849 }
850 
fileSelected(const QUrl & url)851 void Manager::fileSelected(const QUrl &url)
852 {
853     fileOpen(url, QString());
854 }
855 
saveURL(const QUrl & url)856 void Manager::saveURL(const QUrl &url)
857 {
858     KTextEditor::Document *doc = docFor(url);
859     if(doc) {
860         doc->save();
861     }
862 }
863 
newDocumentStatus(KTextEditor::Document * doc)864 void Manager::newDocumentStatus(KTextEditor::Document *doc)
865 {
866     KILE_DEBUG_MAIN << "void Manager::newDocumentStatus(Kate::Document)" << endl;
867     if(!doc) {
868         return;
869     }
870 
871     // sync terminal
872     m_ki->texKonsole()->sync();
873 
874     emit(documentModificationStatusChanged(doc, doc->isModified(), KTextEditor::ModificationInterface::OnDiskUnmodified));
875 }
876 
fileSaveAll(bool disUntitled)877 bool Manager::fileSaveAll(bool disUntitled)
878 {
879     // this can occur when autosaving should take place when we
880     // are still busy with it (KIO::NetAccess keeps the event loop running)
881     if(m_currentlySavingAll) {
882         return true;
883     }
884     m_currentlySavingAll = true;
885     KTextEditor::View *view = Q_NULLPTR;
886     QFileInfo fi;
887     bool oneSaveFailed = false;
888     QUrl url, backupUrl;
889 
890     KILE_DEBUG_MAIN << "===Kile::fileSaveAll(disUntitled = " << disUntitled <<")";
891 
892     for(int i = 0; i < m_ki->viewManager()->textViewCount(); ++i) {
893         view = m_ki->viewManager()->textView(i);
894 
895         if(view && view->document()->isModified()) {
896             url = view->document()->url();
897             fi.setFile(url.toLocalFile());
898 
899             if(!disUntitled || !(disUntitled && url.isEmpty()))  { // either we don't disregard untitled docs, or the doc has a title
900                 KILE_DEBUG_MAIN << "trying to save: " << url.toLocalFile();
901                 bool saveResult = view->document()->documentSave();
902                 fi.refresh();
903 
904                 if(!saveResult) {
905                     oneSaveFailed = true;
906                     m_ki->errorHandler()->printMessage(KileTool::Error,
907                                                        i18n("Kile encountered problems while saving the file %1. Do you have enough free disk space left?", url.toDisplayString()),
908                                                        i18n("Saving"));
909                 }
910             }
911         }
912     }
913 
914     /*
915      This may look superfluos but actually it is not, in the case of multiple modified docs it ensures that the structure view keeps synchronized with the currentTextView
916      And if we only have one masterdoc or none nothing goes wrong.
917     */
918     emit(updateStructure(false, Q_NULLPTR));
919     m_currentlySavingAll = false;
920     return !oneSaveFailed;
921 }
922 
fileSaveCompiledDocument()923 void Manager::fileSaveCompiledDocument()
924 {
925     const QString compiledDocumentFileName = m_ki->livePreviewManager()->getPreviewFile();
926 
927     QFileInfo fileInfo(compiledDocumentFileName);
928     if(!fileInfo.exists() || !fileInfo.isReadable()) {
929         KILE_WARNING_MAIN << "file doesn't exist or cannot be read:" << compiledDocumentFileName;
930         return;
931     }
932     QMimeDatabase db;
933 
934     QStringList nameFilters;
935     {
936         QMimeType detectedMimeType = db.mimeTypeForFile(fileInfo);
937         if(!detectedMimeType.isDefault()) { // All files (*)
938             nameFilters << detectedMimeType.filterString();
939         }
940     }
941     nameFilters << i18n("Any files (*)");
942 
943     QFileDialog *dlg = new QFileDialog(m_ki->mainWindow(), i18n("Save Compiled Document As..."));
944     dlg->setModal(true);
945     dlg->setNameFilters(nameFilters);
946     dlg->selectFile(fileInfo.fileName());
947     dlg->setAcceptMode(QFileDialog::AcceptSave);
948 
949     connect(dlg,  &QFileDialog::urlSelected,
950             this, [compiledDocumentFileName](const QUrl& url) {
951                       if(!url.isValid()) {
952                           return;
953                       }
954                       // the QFileDialog will take care of asking for overwrite permission (if the chosen file exists already)
955                       KIO::CopyJob *copyJob = KIO::copy(QUrl::fromLocalFile(compiledDocumentFileName), url, KIO::Overwrite);
956                       QObject::connect(copyJob, &KIO::CopyJob::finished, copyJob, &QObject::deleteLater);
957                   });
958     dlg->exec();
959 }
960 
fileOpen(const QUrl & url,const QString & encoding,int index)961 TextInfo* Manager::fileOpen(const QUrl &url, const QString& encoding, int index)
962 {
963     m_currentlyOpeningFile = true;
964     KILE_DEBUG_MAIN << "==Kile::fileOpen==========================";
965 
966     if(url.isLocalFile() && QFileInfo(url.toLocalFile()).isDir()) {
967         KILE_DEBUG_MAIN << "tried to open directory" << url;
968         KMessageBox::error(m_ki->mainWindow(), i18n("The URL \"%1\" cannot be opened\nas it is a directory.", url.toDisplayString()),
969                            i18n("Cannot open directory"));
970         m_currentlyOpeningFile = false; // has to be before the 'switchToTextView' call as
971         // it emits signals that are handled by the live preview manager
972         return Q_NULLPTR;
973     }
974 
975     KILE_DEBUG_MAIN << "url is " << url.url();
976     const QUrl realurl = KileUtilities::canonicalUrl(url);
977     KILE_DEBUG_MAIN << "canonical url is " << realurl.url();
978 
979     if(m_ki->isOpen(realurl)) {
980         m_currentlyOpeningFile = false; // has to be before the 'switchToTextView' call as
981         // it emits signals that are handled by the live preview manager
982         m_ki->viewManager()->switchToTextView(realurl);
983         return textInfoFor(realurl);
984     }
985 
986     KTextEditor::View *view = loadText(m_ki->extensions()->determineDocumentType(realurl), realurl, encoding, true, QString(), QString(), QString(), index);
987     if(!view) {
988         m_currentlyOpeningFile = false; // has to be before the 'switchToTextView' call as
989         // it emits signals that are handled by the live preview manager
990         return Q_NULLPTR;
991     }
992     QList<KileProjectItem*> itemList = itemsFor(realurl);
993     TextInfo *textInfo = textInfoFor(realurl);
994 
995     for(QList<KileProjectItem*>::iterator it = itemList.begin(); it != itemList.end(); ++it) {
996         (*it)->setInfo(textInfo);
997     }
998 
999     if(itemList.isEmpty()) {
1000         emit addToProjectView(realurl);
1001         loadDocumentAndViewSettings(textInfo);
1002     }
1003     else if(view) {
1004         KileProjectItem *item = itemList.first();
1005         item->loadDocumentAndViewSettings();
1006     }
1007 
1008     emit(updateStructure(false, Q_NULLPTR));
1009     emit(updateModeStatus());
1010     // update undefined references in this file
1011     emit(updateReferences(textInfoFor(realurl)));
1012     m_currentlyOpeningFile = false;
1013     emit documentOpened(textInfo);
1014     return textInfo;
1015 }
1016 
fileSave(KTextEditor::View * view)1017 bool Manager::fileSave(KTextEditor::View *view)
1018 {
1019     // the 'data' property can be set by the view manager
1020     QAction *action = dynamic_cast<QAction*>(QObject::sender());
1021     if(action) {
1022         QVariant var = action->data();
1023         if(!view && var.isValid()) {
1024             view = var.value<KTextEditor::View*>();
1025             // the 'data' property for the relevant actions is cleared
1026             // inside the view manager
1027         }
1028     }
1029     if(!view) {
1030         view = m_ki->viewManager()->currentTextView();
1031     }
1032     if(!view) {
1033         return false;
1034     }
1035     QUrl url = view->document()->url();
1036     if(url.isEmpty()) { // newly created document
1037         return fileSaveAs(view);
1038     }
1039     else {
1040         bool ret = view->document()->documentSave();
1041         emit(updateStructure(false, textInfoFor(view->document())));
1042         return ret;
1043     }
1044 }
1045 
fileSaveAs(KTextEditor::View * view)1046 bool Manager::fileSaveAs(KTextEditor::View* view)
1047 {
1048     // the 'data' property can be set by the view manager
1049     QAction *action = dynamic_cast<QAction*>(QObject::sender());
1050     if(action) {
1051         QVariant var = action->data();
1052         if(!view && var.isValid()) {
1053             view = var.value<KTextEditor::View*>();
1054             // the 'data' property for the relevant actions is cleared
1055             // inside the view manager
1056         }
1057     }
1058     if(!view) {
1059         view = m_ki->viewManager()->currentTextView();
1060     }
1061     if(!view) {
1062         return false;
1063     }
1064 
1065     KTextEditor::Document* doc = view->document();
1066     Q_ASSERT(doc);
1067     KileDocument::TextInfo* info = textInfoFor(doc);
1068     Q_ASSERT(info);
1069     QUrl startUrl = info->url();
1070     QUrl oldURL = info->url();
1071     if(startUrl.isEmpty()) {
1072         QUrl baseDirectory = info->getBaseDirectory();
1073         if(baseDirectory.isEmpty()) {
1074             startUrl = QUrl("kfiledialog:///KILE_LATEX_SAVE_DIR");
1075         }
1076         else {
1077             startUrl = baseDirectory;
1078         }
1079     }
1080 
1081     KILE_DEBUG_MAIN << "startUrl is " << startUrl;
1082     KEncodingFileDialog::Result result;
1083     QUrl saveURL;
1084     while(true) {
1085         QString filter = m_ki->extensions()->fileFilterKDEStyle(true, info->getFileFilter());
1086 
1087         result = KEncodingFileDialog::getSaveUrlAndEncoding(doc->encoding(), startUrl, filter, m_ki->mainWindow(), i18n("Save File"));
1088         if(result.URLs.isEmpty() || result.URLs.first().isEmpty()) {
1089             return false;
1090         }
1091         saveURL = result.URLs.first();
1092         if(info->getType() == KileDocument::LaTeX) {
1093             saveURL = Info::makeValidTeXURL(saveURL, m_ki->mainWindow(),
1094                                             m_ki->extensions()->isTexFile(saveURL), false); // don't check for file existence
1095         }
1096 
1097         if(!checkForFileOverwritePermission(saveURL)) {
1098             continue;
1099         }
1100         break;
1101     }
1102     doc->setEncoding(result.encoding);
1103     if(!doc->saveAs(saveURL)) {
1104         return false;
1105     }
1106     if(oldURL != saveURL) {
1107         if(info->isDocumentTypePromotionAllowed()) {
1108             recreateTextDocumentInfo(info);
1109             info = textInfoFor(doc);
1110         }
1111         m_ki->structureWidget()->updateUrl(info);
1112         emit addToRecentFiles(saveURL);
1113         emit addToProjectView(doc->url());
1114     }
1115     emit(documentSavedAs(view, info));
1116     return true;
1117 }
1118 
checkForFileOverwritePermission(const QUrl & url)1119 bool Manager::checkForFileOverwritePermission(const QUrl& url)
1120 {
1121     auto statJob = KIO::stat(url, KIO::StatJob::SourceSide, 0);
1122     KJobWidgets::setWindow(statJob, m_ki->mainWindow());
1123     if (statJob->exec()) { // check for writing possibility
1124         int r =  KMessageBox::warningContinueCancel(m_ki->mainWindow(), i18n("A file with the name \"%1\" exists already. Do you want to overwrite it?",
1125                                                     url.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite());
1126         if(r != KMessageBox::Continue) {
1127             return false;
1128         }
1129     }
1130     return true;
1131 }
1132 
fileCloseAllOthers(KTextEditor::View * currentView)1133 bool Manager::fileCloseAllOthers(KTextEditor::View *currentView)
1134 {
1135     // the 'data' property can be set by the view manager
1136     QAction *action = dynamic_cast<QAction*>(QObject::sender());
1137     if(action) {
1138         QVariant var = action->data();
1139         if(!currentView && var.isValid()) {
1140             // the 'data' property for the relevant actions is cleared
1141             // inside the view manager
1142             currentView = var.value<KTextEditor::View*>();
1143         }
1144     }
1145     if(!currentView) {
1146         currentView = m_ki->viewManager()->currentTextView();
1147     }
1148     if(!currentView) {
1149         return false;
1150     }
1151 
1152     QList<KTextEditor::View*> viewList;
1153     for(int i = 0; i < m_ki->viewManager()->textViewCount(); ++i) {
1154         KTextEditor::View *view = m_ki->viewManager()->textView(i);
1155         if(currentView == view) {
1156             continue;
1157         }
1158         viewList.push_back(view);
1159 
1160     }
1161     for(QList<KTextEditor::View*>::iterator it = viewList.begin();
1162             it != viewList.end(); ++it) {
1163         if (!fileClose(*it)) {
1164             return false;
1165         }
1166     }
1167     return true;
1168 }
1169 
fileCloseAll()1170 bool Manager::fileCloseAll()
1171 {
1172     KTextEditor::View * view = m_ki->viewManager()->currentTextView();
1173 
1174     //assumes one view per doc here
1175     while(m_ki->viewManager()->textViewCount() > 0) {
1176         view = m_ki->viewManager()->textView(0);
1177         if (!fileClose(view->document())) {
1178             return false;
1179         }
1180     }
1181     return true;
1182 }
1183 
fileClose(const QUrl & url)1184 bool Manager::fileClose(const QUrl &url)
1185 {
1186     KTextEditor::Document *doc = docFor(url);
1187     if(!doc) {
1188         return true;
1189     }
1190     else {
1191         return fileClose(doc);
1192     }
1193 }
1194 
fileClose(KTextEditor::View * view)1195 bool Manager::fileClose(KTextEditor::View *view)
1196 {
1197     // the 'data' property can be set by the view manager
1198     QAction *action = dynamic_cast<QAction*>(QObject::sender());
1199     if(action) {
1200         QVariant var = action->data();
1201         if(!view && var.isValid()) {
1202             view = var.value<KTextEditor::View*>();
1203             // the 'data' property for the relevant actions is cleared
1204             // inside the view manager
1205         }
1206     }
1207     if(!view) {
1208         view = m_ki->viewManager()->currentTextView();
1209     }
1210     if(!view) {
1211         return false;
1212     }
1213     return fileClose(view->document());
1214 }
1215 
fileClose(KTextEditor::Document * doc,bool closingproject)1216 bool Manager::fileClose(KTextEditor::Document *doc /* = 0L*/, bool closingproject /*= false*/)
1217 {
1218     KILE_DEBUG_MAIN << "==Kile::fileClose==========================";
1219 
1220     if(!doc) {
1221         doc = m_ki->activeTextDocument();
1222     }
1223 
1224     if(!doc) {
1225         return true;
1226     }
1227 
1228     //FIXME: remove from docinfo map, remove from dirwatch
1229     KILE_DEBUG_MAIN << "doc->url().toLocalFile()=" << doc->url().toLocalFile();
1230 
1231     const QUrl url = doc->url();
1232 
1233     TextInfo *docinfo= textInfoFor(doc);
1234     if(!docinfo) {
1235         qWarning() << "no DOCINFO for " << url.url();
1236         return true;
1237     }
1238     bool inProject = false;
1239     QList<KileProjectItem*> items = itemsFor(docinfo);
1240     for(QList<KileProjectItem*>::iterator it = items.begin(); it != items.end(); ++it) {
1241         KileProjectItem *item = *it;
1242 
1243         //FIXME: refactor here
1244         if(item && doc) {
1245             storeProjectItem(item, doc);
1246             inProject = true;
1247         }
1248     }
1249 
1250     if(!inProject) {
1251         KILE_DEBUG_MAIN << "not in project";
1252         saveDocumentAndViewSettings(docinfo);
1253     }
1254 
1255     if(doc->closeUrl()) {
1256         // docinfo may have been recreated from 'Untitled' doc to a named doc
1257         if(url.isEmpty()) {
1258             docinfo= textInfoFor(doc);
1259         }
1260 
1261         if(KileConfig::cleanUpAfterClose()) {
1262             cleanUpTempFiles(url, true); // yes we pass here url and not docinfo->url()
1263         }
1264 
1265         //FIXME: use signal/slot
1266         if( doc->views().count() > 0) {
1267             m_ki->viewManager()->removeView(doc->views().first());
1268         }
1269         //remove the decorations
1270 
1271         trashDoc(docinfo, doc);
1272         m_ki->structureWidget()->clean(docinfo);
1273         removeTextDocumentInfo(docinfo, closingproject);
1274 
1275         emit removeFromProjectView(url);
1276         emit updateModeStatus();
1277     }
1278     else {
1279         return false;
1280     }
1281 
1282     return true;
1283 }
1284 
buildProjectTree(const QUrl & url)1285 void Manager::buildProjectTree(const QUrl &url)
1286 {
1287     KileProject * project = projectFor(url);
1288 
1289     if (project) {
1290         buildProjectTree(project);
1291     }
1292 }
1293 
buildProjectTree(KileProject * project)1294 void Manager::buildProjectTree(KileProject *project)
1295 {
1296     if(!project) {
1297         project = activeProject();
1298     }
1299 
1300     if(!project) {
1301         project = selectProject(i18n("Refresh Project Tree"));
1302     }
1303 
1304     if (project) {
1305         //TODO: update structure for all docs
1306         project->buildProjectTree();
1307     }
1308     else if (m_projects.count() == 0) {
1309         KMessageBox::error(m_ki->mainWindow(), i18n("The current document is not associated to a project. Please activate a document that is associated to the project you want to build the tree for, then choose Refresh Project Tree again."),i18n( "Could Not Refresh Project Tree"));
1310     }
1311 }
1312 
projectNew()1313 void Manager::projectNew()
1314 {
1315     KileNewProjectDialog *dlg = new KileNewProjectDialog(m_ki->templateManager(), m_ki->extensions(), m_ki->mainWindow());
1316 
1317     if (dlg->exec())
1318     {
1319         TextInfo *newTextInfo = Q_NULLPTR;
1320 
1321         KileProject *project = dlg->project();
1322 
1323         //add the project file to the project
1324         KileProjectItem *item = new KileProjectItem(project, project->url());
1325         createTextInfoForProjectItem(item);
1326         item->setOpenState(false);
1327         projectOpenItem(item);
1328 
1329         if(dlg->createNewFile()) {
1330             m_currentlyOpeningFile = true; // don't let live preview interfere
1331 
1332             QString filename = dlg->file();
1333 
1334             //create the new document and fill it with the template
1335             KTextEditor::View *view = loadTemplate(dlg->getSelection());
1336 
1337             if(view) {
1338                 //derive the URL from the base url of the project
1339                 QUrl url = project->baseURL();
1340                 url = url.adjusted(QUrl::StripTrailingSlash);
1341                 url.setPath(url.path() + '/' + filename);
1342 
1343                 newTextInfo = textInfoFor(view->document());
1344 
1345                 //save the new file
1346                 //FIXME: this needs proper error handling
1347                 view->document()->saveAs(url);
1348                 emit(documentModificationStatusChanged(view->document(),
1349                                                        false, KTextEditor::ModificationInterface::OnDiskUnmodified));
1350 
1351                 //add this file to the project
1352                 item = new KileProjectItem(project, url);
1353                 item->setInfo(newTextInfo);
1354 
1355                 //docinfo->updateStruct(m_kwStructure->level());
1356                 emit(updateStructure(false, newTextInfo));
1357             }
1358 
1359             m_currentlyOpeningFile = false;
1360         }
1361 
1362         project->buildProjectTree();
1363         project->save();
1364         addProject(project);
1365 
1366         emit(updateModeStatus());
1367         emit(addToRecentProjects(project->url()));
1368 
1369         if(newTextInfo) {
1370             emit documentOpened(newTextInfo);
1371         }
1372     }
1373 }
1374 
addProject(KileProject * project)1375 void Manager::addProject(KileProject *project)
1376 {
1377     KILE_DEBUG_MAIN << "==void Manager::addProject(const KileProject *project)==========";
1378     m_projects.append(project);
1379     KILE_DEBUG_MAIN << "\tnow " << m_projects.count() << " projects";
1380     emit addToProjectView(project);
1381     connect(project, SIGNAL(projectTreeChanged(const KileProject*)), this, SIGNAL(projectTreeChanged(const KileProject*)));
1382 }
1383 
selectProject(const QString & caption)1384 KileProject* Manager::selectProject(const QString& caption)
1385 {
1386     QStringList list;
1387     for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) {
1388         list.append((*it)->name());
1389     }
1390 
1391     KileProject *project = Q_NULLPTR;
1392     QString name;
1393     if (list.count() > 1) {
1394         KileListSelector *dlg  = new KileListSelector(list, caption, i18n("Select Project"), true, m_ki->mainWindow());
1395         if (dlg->exec()) {
1396             if(!dlg->hasSelection()) {
1397                 return Q_NULLPTR;
1398             }
1399             name = dlg->selectedItems().first();
1400         }
1401         delete dlg;
1402     }
1403     else if (list.count() == 0) {
1404         return Q_NULLPTR;
1405     }
1406     else {
1407         name = m_projects.first()->name();
1408     }
1409 
1410     project = projectFor(name);
1411 
1412     return project;
1413 }
1414 
addToProject(const QUrl & url)1415 void Manager::addToProject(const QUrl &url)
1416 {
1417     KILE_DEBUG_MAIN << "===Kile::addToProject(const QUrl &url =" << url.url() << ")";
1418 
1419     KileProject *project = selectProject(i18n("Add to Project"));
1420 
1421     if (project) {
1422         addToProject(project, url);
1423     }
1424 }
1425 
addToProject(KileProject * project,const QUrl & url)1426 void Manager::addToProject(KileProject* project, const QUrl &url)
1427 {
1428     const QUrl realurl = KileUtilities::canonicalUrl(url);
1429     QFileInfo fi(realurl.toLocalFile());
1430 
1431     if (project->contains(realurl)) {
1432         m_ki->errorHandler()->printMessage(KileTool::Info,
1433                                            i18n("The file %1 is already member of the project %2", realurl.fileName(), project->name()),
1434                                            i18n("Add to Project"));
1435         return;
1436     }
1437     else if(!fi.exists() || !fi.isReadable())
1438     {
1439         m_ki->errorHandler()->printMessage(KileTool::Info,
1440                                            i18n("The file %1 can not be added because it does not exist or is not readable", realurl.fileName()),
1441                                            i18n("Add to Project"));
1442         return;
1443     }
1444 
1445     KileProjectItem *item = new KileProjectItem(project, realurl);
1446     createTextInfoForProjectItem(item);
1447     item->setOpenState(m_ki->isOpen(realurl));
1448     projectOpenItem(item);
1449     emit addToProjectView(item);
1450     buildProjectTree(project);
1451 }
1452 
removeFromProject(KileProjectItem * item)1453 void Manager::removeFromProject(KileProjectItem *item)
1454 {
1455     if (item && item->project()) {
1456         KILE_DEBUG_MAIN << "\tprojecturl = " << item->project()->url().toLocalFile() << ", url = " << item->url().toLocalFile();
1457 
1458         if (item->project()->url() == item->url()) {
1459             KMessageBox::error(m_ki->mainWindow(), i18n("This file is the project file, which holds all the information about your project.  As such, it cannot be removed from the project."), i18n("Cannot Remove File From Project"));
1460             return;
1461         }
1462 
1463         emit removeItemFromProjectView(item, m_ki->isOpen(item->url()));
1464 
1465         KileProject *project = item->project();
1466         project->remove(item);
1467 
1468         // update undefined references in all project files
1469         updateProjectReferences(project);
1470         project->buildProjectTree();
1471     }
1472 }
1473 
1474 // WARNING: 'item' must have been set up with a TextInfo* object already
projectOpenItem(KileProjectItem * item,bool openProjectItemViews)1475 void Manager::projectOpenItem(KileProjectItem *item, bool openProjectItemViews)
1476 {
1477     KILE_DEBUG_MAIN << "==Kile::projectOpenItem==========================";
1478     KILE_DEBUG_MAIN << "\titem:" << item->url().toLocalFile();
1479 
1480     if (m_ki->isOpen(item->url())) { //remove item from projectview (this file was opened before as a normal file)
1481         emit removeFromProjectView(item->url());
1482     }
1483 
1484     KileDocument::TextInfo* itemInfo = item->getInfo();
1485     Q_ASSERT(itemInfo);
1486 
1487     if(item->isOpen()) {
1488         KTextEditor::View *view = loadItem(m_ki->extensions()->determineDocumentType(item->url()), item, QString(), openProjectItemViews);
1489         if (view) {
1490             item->loadDocumentAndViewSettings();
1491         }
1492         // make sure that the item has been parsed, even if it isn't shown;
1493         // this is necessary to identify the correct LaTeX root document (bug 233667);
1494         m_ki->structureWidget()->update(itemInfo, true);
1495     }
1496     else if(item->type() == KileProjectItem::Source || item->type() == KileProjectItem::Package || item->type() == KileProjectItem::Bibliography) {
1497         // 'item' is not shown (and it is either a LaTeX source file or package), i.e. its
1498         // contents won't be loaded into a KTextEditor::Document; so, we have to do it:
1499         // we are loading the contents of the project item into the docinfo
1500         // for a moment
1501         itemInfo->setDocumentContents(loadTextURLContents(item->url(), item->encoding()));
1502         // in order to pass the contents to the parser
1503         m_ki->structureWidget()->update(itemInfo, true);
1504         // now we don't need the contents anymore
1505         itemInfo->setDocumentContents(QStringList());
1506     }
1507 }
1508 
createTextInfoForProjectItem(KileProjectItem * item)1509 void Manager::createTextInfoForProjectItem(KileProjectItem *item)
1510 {
1511     item->setInfo(createTextDocumentInfo(m_ki->extensions()->determineDocumentType(item->url()),
1512                                          item->url(), item->project()->baseURL()));
1513 }
1514 
projectOpen(const QUrl & url,int step,int max,bool openProjectItemViews)1515 void Manager::projectOpen(const QUrl &url, int step, int max, bool openProjectItemViews)
1516 {
1517     KILE_DEBUG_MAIN << "==Kile::projectOpen==========================";
1518     KILE_DEBUG_MAIN << "\tfilename: " << url.fileName();
1519 
1520     const QUrl realurl = KileUtilities::canonicalUrl(url);
1521 
1522     if(m_ki->projectIsOpen(realurl)) {
1523         if(m_progressDialog) {
1524             m_progressDialog->hide();
1525         }
1526 
1527         KMessageBox::information(m_ki->mainWindow(), i18n("<p>The project \"%1\" is already open.</p>"
1528                                  "<p>If you wanted to reload the project, close the project before you re-open it.</p>", url.fileName()),
1529                                  i18n("Project Already Open"));
1530         return;
1531     }
1532 
1533     QFileInfo fi(realurl.toLocalFile());
1534     if(!fi.isReadable()) {
1535         if(m_progressDialog) {
1536             m_progressDialog->hide();
1537         }
1538 
1539         if (KMessageBox::warningYesNo(m_ki->mainWindow(), i18n("<p>The project file for the project \"%1\" does not exist or it is not readable.</p>"
1540                                       "<p>Do you want to remove this project from the recent projects list?</p>",
1541                                       url.fileName()),
1542                                       i18n("Could Not Open Project"))  == KMessageBox::Yes) {
1543             emit(removeFromRecentProjects(realurl));
1544         }
1545         return;
1546     }
1547 
1548     if(!m_progressDialog) {
1549         createProgressDialog();
1550     }
1551 
1552     KileProject *kp = new KileProject(realurl, m_ki->extensions());
1553 
1554     if(!kp->appearsToBeValidProjectFile()) {
1555         if(m_progressDialog) {
1556             m_progressDialog->hide();
1557         }
1558 
1559         KMessageBox::sorry(m_ki->mainWindow(), i18n("<p>The file \"%1\" cannot be opened as it does not appear to be a project file.</p>",
1560                            url.fileName()),
1561                            i18n("Impossible to Open Project File"));
1562         delete kp;
1563         return;
1564     }
1565 
1566     if(kp->getProjectFileVersion() > KILE_PROJECTFILE_VERSION) {
1567         if(m_progressDialog) {
1568             m_progressDialog->hide();
1569         }
1570 
1571         KMessageBox::sorry(m_ki->mainWindow(), i18n("<p>The project \"%1\" cannot be opened as it was created <br/>by a newer version of Kile.</p>",
1572                            url.fileName()),
1573                            i18n("Impossible to Open Project"));
1574         delete kp;
1575         return;
1576     }
1577 
1578     if(!kp->isOfCurrentVersion()) {
1579         if(m_progressDialog) {
1580             m_progressDialog->hide();
1581         }
1582 
1583         if(KMessageBox::questionYesNo(m_ki->mainWindow(), i18n("<p>The project file \"%1\" was created by a previous version of Kile.<br/>"
1584                                       "It needs to be updated before it can be opened.</p>"
1585                                       "<p>Do you want to update it?</p>", url.fileName()),
1586                                       i18n("Project File Needs to be Updated"))  == KMessageBox::No) {
1587             delete kp;
1588             return;
1589         }
1590 
1591         if(!kp->migrateProjectFileToCurrentVersion()) {
1592             if (KMessageBox::warningYesNo(m_ki->mainWindow(), i18n("<p>The project file \"%1\" could be not updated.</p>"
1593                                           "<p>Do you want to remove this project from the recent projects list?</p>", url.fileName()),
1594                                           i18n("Could Not Update Project File"))  == KMessageBox::Yes) {
1595                 emit(removeFromRecentProjects(realurl));
1596             }
1597             delete kp;
1598             return;
1599         }
1600     }
1601 
1602     m_progressDialog->show();
1603 
1604     kp->load();
1605 
1606     if(kp->isInvalid()) {
1607         if(m_progressDialog) {
1608             m_progressDialog->hide();
1609         }
1610         delete kp;
1611         return;
1612     }
1613 
1614     emit(addToRecentProjects(realurl));
1615 
1616     QList<KileProjectItem*> list = kp->items();
1617     int project_steps = list.count();
1618     m_progressDialog->setMaximum(project_steps * max);
1619     project_steps *= step;
1620     m_progressDialog->setValue(project_steps);
1621 
1622     // open the project files in the correct order
1623     QVector<KileProjectItem*> givenPositionVector(list.count(), Q_NULLPTR);
1624     QList<KileProjectItem*> notCorrectlyOrderedList;
1625     for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
1626         KileProjectItem *item = *it;
1627         int order = item->order();
1628 
1629         if(order >= 0 && order >= list.count()) {
1630             order = -1;
1631         }
1632         if(!item->isOpen() || order < 0 || givenPositionVector[order] != Q_NULLPTR) {
1633             notCorrectlyOrderedList.push_back(item);
1634         }
1635         else {
1636             givenPositionVector[order] = item;
1637         }
1638     }
1639 
1640     QList<KileProjectItem*> orderedList;
1641     for(int i = 0; i < givenPositionVector.size(); ++i) {
1642         KileProjectItem *item = givenPositionVector[i];
1643         if(item) {
1644             orderedList.push_back(item);
1645         }
1646     }
1647     for(QList<KileProjectItem*>::iterator i = notCorrectlyOrderedList.begin(); i != notCorrectlyOrderedList.end(); ++i) {
1648         orderedList.push_back(*i);
1649     }
1650 
1651     addProject(kp);
1652 
1653     // for the parsing to work correctly, all ProjectItems need to have TextInfo* objects, but
1654     // the URL of 'item' might already be associated with a TextInfo* object; for example, through
1655     // a stand-alone document currently being open already, or through a project item that belongs to
1656     // a different project
1657     // => 'createTextDocumentInfo' will take care of that situation as well
1658     for (QList<KileProjectItem*>::iterator i = orderedList.begin(); i != orderedList.end(); ++i) {
1659         createTextInfoForProjectItem(*i);
1660     }
1661 
1662     unsigned int counter = 1;
1663     for (QList<KileProjectItem*>::iterator i = orderedList.begin(); i != orderedList.end(); ++i) {
1664         projectOpenItem(*i, openProjectItemViews);
1665         m_progressDialog->setValue(counter + project_steps);
1666         qApp->processEvents();
1667         ++counter;
1668     }
1669 
1670     kp->buildProjectTree();
1671 
1672     emit(updateStructure(false, Q_NULLPTR));
1673     emit(updateModeStatus());
1674 
1675     // update undefined references in all project files
1676     updateProjectReferences(kp);
1677 
1678     m_ki->viewManager()->switchToTextView(kp->lastDocument());
1679 
1680     emit(projectOpened(kp));
1681 }
1682 
1683 // as all labels are gathered in the project, we can check for unsolved references
updateProjectReferences(KileProject * project)1684 void Manager::updateProjectReferences(KileProject *project)
1685 {
1686     QList<KileProjectItem*> list = project->items();
1687     for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
1688         emit(updateReferences((*it)->getInfo()));
1689     }
1690 }
1691 
projectOpen()1692 void Manager::projectOpen()
1693 {
1694     KILE_DEBUG_MAIN << "==Kile::projectOpen==========================";
1695     QUrl url = QFileDialog::getOpenFileUrl(m_ki->mainWindow(), i18n("Open Project"),
1696                                            QUrl::fromLocalFile(KileConfig::defaultProjectLocation()),
1697                                            m_ki->extensions()->fileFilterQtStyle(false, {KileDocument::Extensions::KILE_PROJECT}));
1698 
1699     if(!url.isEmpty()) {
1700         projectOpen(url);
1701     }
1702 }
1703 
1704 
projectSave(KileProject * project)1705 void Manager::projectSave(KileProject *project /* = 0 */)
1706 {
1707     KILE_DEBUG_MAIN << "==Kile::projectSave==========================";
1708     if (!project) {
1709         //find the project that corresponds to the active doc
1710         project= activeProject();
1711     }
1712 
1713     if(!project) {
1714         project = selectProject(i18n("Save Project"));
1715     }
1716 
1717     if(project) {
1718         QList<KileProjectItem*> list = project->items();
1719         KTextEditor::Document *doc = Q_NULLPTR;
1720         KileProjectItem *item = Q_NULLPTR;
1721         TextInfo *docinfo = Q_NULLPTR;
1722 
1723         // determine the order in which the project items are opened
1724         QVector<KileProjectItem*> viewPositionVector(m_ki->viewManager()->getTabCount(), Q_NULLPTR);
1725         for(QList<KileProjectItem*>::iterator i = list.begin(); i != list.end(); ++i) {
1726             docinfo = (*i)->getInfo();
1727             if(docinfo) {
1728                 KTextEditor::View *view = m_ki->viewManager()->textView(docinfo);
1729                 if(view) {
1730                     int position = m_ki->viewManager()->tabIndexOf(view);
1731                     if(position >= 0 && position < viewPositionVector.size()) {
1732                         viewPositionVector[position] = *i;
1733                     }
1734                 }
1735             }
1736         }
1737         int position = 0;
1738         for(int i = 0; i < viewPositionVector.size(); ++i) {
1739             if(viewPositionVector[i] != Q_NULLPTR) {
1740                 viewPositionVector[i]->setOrder(position);
1741                 ++position;
1742             }
1743         }
1744 
1745         //update the open-state of the items
1746         for (QList<KileProjectItem*>::iterator i = list.begin(); i != list.end(); ++i) {
1747             item = *i;
1748             KILE_DEBUG_MAIN << "\tsetOpenState(" << (*i)->url().toLocalFile() << ") to " << m_ki->isOpen(item->url());
1749             item->setOpenState(m_ki->isOpen(item->url()));
1750             docinfo = item->getInfo();
1751 
1752             if(docinfo) {
1753                 doc = docinfo->getDoc();
1754             }
1755             if(doc) {
1756                 storeProjectItem(item, doc);
1757             }
1758 
1759             doc = Q_NULLPTR;
1760             docinfo = Q_NULLPTR;
1761         }
1762 
1763         project->save();
1764     }
1765     else {
1766         KMessageBox::error(m_ki->mainWindow(), i18n("The current document is not associated to a project. Please activate a document that is associated to the project you want to save, then choose Save Project again."),i18n( "Could Determine Active Project"));
1767     }
1768 }
1769 
projectAddFiles(const QUrl & url)1770 void Manager::projectAddFiles(const QUrl &url)
1771 {
1772     KileProject *project = projectFor(url);
1773 
1774     if (project) {
1775         projectAddFiles(project,url);
1776     }
1777 }
1778 
projectAddFiles(KileProject * project,const QUrl & fileUrl)1779 void Manager::projectAddFiles(KileProject *project,const QUrl &fileUrl)
1780 {
1781     KILE_DEBUG_MAIN << "==Kile::projectAddFiles()==========================";
1782     if(project == 0) {
1783         project = activeProject();
1784     }
1785 
1786     if(project == 0) {
1787         project = selectProject(i18n("Add Files to Project"));
1788     }
1789 
1790     if (project) {
1791         QString currentDir;
1792         if(fileUrl.isEmpty()) {
1793             currentDir = QFileInfo(project->url().path()).dir().dirName();
1794         }
1795         else {
1796             currentDir = fileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path();
1797         }
1798 
1799         KILE_DEBUG_MAIN << "currentDir is " << currentDir;
1800         QFileDialog *dlg = new QFileDialog(m_ki->mainWindow(), i18n("Add Files"), currentDir, m_ki->extensions()->fileFilterQtStyle(true, {}));
1801         dlg->setModal(true);
1802         dlg->setFileMode(QFileDialog::ExistingFiles);
1803         dlg->setLabelText(QFileDialog::Accept, i18n("Add"));
1804 
1805         if(dlg->exec()) {
1806             QList<QUrl> urls = dlg->selectedUrls();
1807             for(int i=0; i < urls.count(); ++i) {
1808                 addToProject(project, urls[i]);
1809             }
1810             // update undefined references in all project files
1811             updateProjectReferences(project);
1812         }
1813         delete dlg;
1814 
1815         //open them
1816     }
1817     else if (m_projects.count() == 0) {
1818         KMessageBox::error(m_ki->mainWindow(), i18n("There are no projects opened. Please open the project you want to add files to, then choose Add Files again."),i18n( "Could Not Determine Active Project"));
1819     }
1820 }
1821 
toggleArchive(KileProjectItem * item)1822 void Manager::toggleArchive(KileProjectItem *item)
1823 {
1824     item->setArchive(!item->archive());
1825 }
1826 
projectOptions(const QUrl & url)1827 void Manager::projectOptions(const QUrl &url)
1828 {
1829     KileProject *project = projectFor(url);
1830 
1831     if (project) {
1832         projectOptions(project);
1833     }
1834 }
1835 
projectOptions(KileProject * project)1836 void Manager::projectOptions(KileProject *project /* = 0*/)
1837 {
1838     KILE_DEBUG_MAIN << "==Kile::projectOptions==========================";
1839     if(!project) {
1840         project = activeProject();
1841     }
1842 
1843     if(!project) {
1844         project = selectProject(i18n("Project Options For"));
1845     }
1846 
1847     if (project) {
1848         KILE_DEBUG_MAIN << "\t" << project->name();
1849         KileProjectOptionsDialog *dlg = new KileProjectOptionsDialog(project, m_ki->extensions(), m_ki->mainWindow());
1850         dlg->exec();
1851     }
1852     else if (m_projects.count() == 0) {
1853         KMessageBox::error(m_ki->mainWindow(), i18n("The current document is not associated to a project. Please activate a document that is associated to the project you want to modify, then choose Project Options again."),i18n( "Could Not Determine Active Project"));
1854     }
1855 }
1856 
projectCloseAll()1857 bool Manager::projectCloseAll()
1858 {
1859     KILE_DEBUG_MAIN << "==Kile::projectCloseAll==========================";
1860 
1861     while(m_projects.size() > 0) {
1862         if(!projectClose(m_projects.first()->url())) {
1863             return false;
1864         }
1865     }
1866 
1867     return true;
1868 }
1869 
projectClose(const QUrl & url)1870 bool Manager::projectClose(const QUrl &url)
1871 {
1872     KILE_DEBUG_MAIN << "==Kile::projectClose==========================";
1873     KileProject *project = 0;
1874 
1875     if (url.isEmpty()) {
1876         project = activeProject();
1877 
1878         if (!project) {
1879             project = selectProject(i18n("Close Project"));
1880         }
1881     }
1882     else {
1883         project = projectFor(url);
1884     }
1885 
1886     if(project) {
1887         KILE_DEBUG_MAIN << "\tclosing:" << project->name();
1888         project->setLastDocument(QUrl::fromLocalFile(m_ki->getName()));
1889 
1890         projectSave(project);
1891 
1892         QList<KileProjectItem*> list = project->items();
1893 
1894         bool close = true;
1895         KTextEditor::Document *doc = Q_NULLPTR;
1896         TextInfo *docinfo = Q_NULLPTR;
1897         for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
1898             KileProjectItem *item = *it;
1899 
1900             doc = Q_NULLPTR;
1901             docinfo = item->getInfo();
1902             if (docinfo) {
1903                 doc = docinfo->getDoc();
1904             }
1905             else {
1906                 continue;
1907             }
1908             if (doc) {
1909                 KILE_DEBUG_MAIN << "\t\tclosing item " << doc->url().toLocalFile();
1910                 bool r = fileClose(doc, true);
1911                 close = close && r;
1912                 if (!close) {
1913                     break;
1914                 }
1915             }
1916             else {
1917                 // we still need to delete the TextInfo object
1918                 removeTextDocumentInfo(docinfo, true);
1919             }
1920         }
1921 
1922         if (close) {
1923             m_projects.removeAll(project);
1924             emit removeFromProjectView(project);
1925             delete project;
1926             emit(updateModeStatus());
1927             return true;
1928         }
1929         else
1930             return false;
1931     }
1932     else if (m_projects.count() == 0)
1933         KMessageBox::error(m_ki->mainWindow(), i18n("The current document is not associated to a project. Please activate a document that is associated to the project you want to close, then choose Close Project again."),i18n( "Could Not Close Project"));
1934 
1935     return true;
1936 }
1937 
storeProjectItem(KileProjectItem * item,KTextEditor::Document * doc)1938 void Manager::storeProjectItem(KileProjectItem *item, KTextEditor::Document *doc)
1939 {
1940     KILE_DEBUG_MAIN << "===Kile::storeProjectItem==============";
1941     KILE_DEBUG_MAIN << "\titem = " << item << ", doc = " << doc;
1942     item->setEncoding(doc->encoding());
1943     item->setMode(doc->mode());
1944     item->setHighlight(doc->highlightingMode());
1945     item->saveDocumentAndViewSettings();
1946 }
1947 
cleanUpTempFiles(const QUrl & url,bool silent)1948 void Manager::cleanUpTempFiles(const QUrl &url, bool silent)
1949 {
1950     KILE_DEBUG_MAIN << "===void Manager::cleanUpTempFiles(const QUrl " << url.toLocalFile() << ", bool " << silent << ")===";
1951 
1952     if( url.isEmpty() )
1953         return;
1954 
1955     QStringList extlist;
1956     QFileInfo fi(url.toLocalFile());
1957     const QStringList templist = KileConfig::cleanUpFileExtensions().split(' ');
1958     const QString fileName = fi.fileName();
1959     const QString dirPath = fi.absolutePath();
1960     const QString baseName = fi.completeBaseName();
1961 
1962     for (int i = 0; i < templist.count(); ++i) {
1963         fi.setFile( dirPath + '/' + baseName + templist[i] );
1964         if(fi.exists()) {
1965             extlist.append(templist[i]);
1966         }
1967     }
1968 
1969     if(!silent && fileName.isEmpty()) {
1970         return;
1971     }
1972 
1973     if (!silent && extlist.count() > 0) {
1974         KILE_DEBUG_MAIN << "not silent";
1975         KileDialog::Clean *dialog = new KileDialog::Clean(m_ki->mainWindow(), fileName, extlist);
1976         if (dialog->exec() == QDialog::Accepted) {
1977             extlist = dialog->cleanList();
1978         }
1979         else {
1980             delete dialog;
1981             return;
1982         }
1983 
1984         delete dialog;
1985     }
1986 
1987     if(extlist.count() == 0) {
1988         m_ki->errorHandler()->printMessage(KileTool::Warning, i18n("Nothing to clean for %1", fileName),
1989                                            i18n("Clean"));
1990     }
1991     else {
1992         for(int i = 0; i < extlist.count(); ++i) {
1993             QFile file(dirPath + '/' + baseName + extlist[i]);
1994             KILE_DEBUG_MAIN << "About to remove file = " << file.fileName();
1995             file.remove();
1996         }
1997         m_ki->errorHandler()->printMessage(KileTool::Info,
1998                                            i18n("Cleaning %1: %2", fileName, extlist.join(" ")),
1999                                            i18n("Clean"));
2000     }
2001 }
2002 
openDroppedURLs(QDropEvent * e)2003 void Manager::openDroppedURLs(QDropEvent *e) {
2004     QList<QUrl> urls = e->mimeData()->urls();
2005     Extensions *extensions = m_ki->extensions();
2006 
2007     for(QList<QUrl>::iterator i = urls.begin(); i != urls.end(); ++i) {
2008         QUrl url = *i;
2009         if(extensions->isProjectFile(url)) {
2010             projectOpen(url);
2011         }
2012         else {
2013             fileOpen(url);
2014         }
2015     }
2016 }
2017 
reloadXMLOnAllDocumentsAndViews()2018 void Manager::reloadXMLOnAllDocumentsAndViews()
2019 {
2020     for(QList<TextInfo*>::iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) {
2021         KTextEditor::Document *doc = (*it)->getDoc();
2022         // FIXME: 'doc' can be null, for example if it belongs to a project item
2023         //        which has been closed, but this should be improved in the sense
2024         //        that 'm_textInfoList' should only contain 'TextInfo' objects which
2025         //        contain valid pointers to 'KTextEditor::Document' objects.
2026         if(!doc) {
2027             continue;
2028         }
2029         doc->reloadXML();
2030         QList<KTextEditor::View*> views = doc->views();
2031         for(QList<KTextEditor::View*>::iterator viewIt = views.begin(); viewIt != views.end(); ++viewIt) {
2032             (*viewIt)->reloadXML();
2033         }
2034     }
2035 }
2036 
2037 
handleParsingComplete(const QUrl & url,KileParser::ParserOutput * output)2038 void Manager::handleParsingComplete(const QUrl &url, KileParser::ParserOutput* output)
2039 {
2040     KILE_DEBUG_MAIN << url << output;
2041     if(!output) {
2042         KILE_DEBUG_MAIN << "NULL output given";
2043         return;
2044     }
2045     KileDocument::TextInfo *textInfo = textInfoFor(url);
2046     if(!textInfo) {
2047         KileProjectItem* item = itemFor(url);
2048         if(item) {
2049             textInfo = item->getInfo();
2050         }
2051         if(!textInfo) {
2052             // this can happen for instance when the document is closed
2053             // while the parser is still running
2054             KILE_DEBUG_MAIN << "no TextInfo object found for" << url << "found";
2055             return;
2056         }
2057     }
2058     textInfo->installParserOutput(output);
2059     m_ki->structureWidget()->updateAfterParsing(textInfo, output->structureViewItems);
2060     delete(output);
2061 }
2062 
2063 // Show all opened projects and switch to another one, if you want
2064 
projectShow()2065 void Manager::projectShow()
2066 {
2067     if(m_projects.count() <= 1) {
2068         return;
2069     }
2070 
2071     // select the new project
2072     KileProject *project = selectProject(i18n("Switch Project"));
2073     if(!project || project==activeProject()) {
2074         return;
2075     }
2076 
2077     // get last opened document
2078     const QUrl lastdoc = project->lastDocument();
2079     KileProjectItem *docitem = (!lastdoc.isEmpty()) ? itemFor(lastdoc, project) : Q_NULLPTR;
2080 
2081     // if not, we search for the first opened tex file of this project
2082     // if no file is opened, we take the first tex file mentioned in the list
2083     KileProjectItem *first_texitem = Q_NULLPTR;
2084     if(!docitem) {
2085         QList<KileProjectItem*> list = project->items();
2086         for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
2087             KileProjectItem *item = *it;
2088 
2089             QString itempath = item->path();
2090 
2091             // called from QAction 'Show projects...': find the first opened
2092             // LaTeX document or, if that fails, any other opened file
2093             QStringList extlist = (m_ki->extensions()->latexDocuments() + ' ' + m_ki->extensions()->latexPackages()).split(' ');
2094             for(QStringList::Iterator it=extlist.begin(); it!=extlist.end(); ++it) {
2095                 if(itempath.indexOf( (*it), -(*it).length() ) >= 0)  {
2096                     if (m_ki->isOpen(item->url()))  {
2097                         docitem = item;
2098                         break;
2099                     }
2100                     else if(!first_texitem) {
2101                         first_texitem = item;
2102                     }
2103                 }
2104             }
2105             if(docitem) {
2106                 break;
2107             }
2108         }
2109     }
2110 
2111     // did we find one opened file or must we take the first entry
2112     if(!docitem) {
2113         if(!first_texitem) {
2114             return;
2115         }
2116         docitem = first_texitem;
2117     }
2118 
2119     // ok, we can switch to another project now
2120     if (m_ki->isOpen(docitem->url())) {
2121         m_ki->viewManager()->switchToTextView(docitem->url());
2122     }
2123     else {
2124         fileOpen(docitem->url(), docitem->encoding());
2125     }
2126 }
2127 
projectRemoveFiles()2128 void Manager::projectRemoveFiles()
2129 {
2130     QList<KileProjectItem*> itemsList = selectProjectFileItems(i18n("Select Files to Remove"));
2131     if(itemsList.count() > 0) {
2132         for(QList<KileProjectItem*>::iterator it = itemsList.begin(); it != itemsList.end(); ++it) {
2133             removeFromProject(*it);
2134         }
2135     }
2136 }
2137 
projectShowFiles()2138 void Manager::projectShowFiles()
2139 {
2140     KileProjectItem *item = selectProjectFileItem( i18n("Select File") );
2141     if(item) {
2142         if (item->type() == KileProjectItem::ProjectFile) {
2143             dontOpenWarning(item, i18n("Show Project Files"), i18n("project configuration file"));
2144         }
2145         else if(item->type() == KileProjectItem::Image) {
2146             dontOpenWarning(item, i18n("Show Project Files"), i18n("graphics file"));
2147         }
2148         else { // ok, we can switch to another file
2149             if  (m_ki->isOpen(item->url())) {
2150                 m_ki->viewManager()->switchToTextView(item->url());
2151             }
2152             else {
2153                 fileOpen(item->url(), item->encoding() );
2154             }
2155         }
2156     }
2157 }
2158 
projectOpenAllFiles()2159 void Manager::projectOpenAllFiles()
2160 {
2161     KileProject *project = selectProject(i18n("Select Project"));
2162     if(project) {
2163         projectOpenAllFiles(project->url());
2164     }
2165 }
2166 
projectOpenAllFiles(const QUrl & url)2167 void Manager::projectOpenAllFiles(const QUrl &url)
2168 {
2169     KileProject* project;
2170     KTextEditor::Document* doc = Q_NULLPTR;
2171 
2172     if(!url.isValid()) {
2173         return;
2174     }
2175     project = projectFor(url);
2176 
2177     if(!project)
2178         return;
2179 
2180 
2181     if(m_ki->viewManager()->currentTextView()) {
2182         doc = m_ki->viewManager()->currentTextView()->document();
2183     }
2184     // we remember the actual view, so the user gets the same view back after opening
2185 
2186     QList<KileProjectItem*> list = project->items();
2187     for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
2188         KileProjectItem *item = *it;
2189 
2190         if (item->type()==KileProjectItem::ProjectFile) {
2191             dontOpenWarning( item, i18n("Open All Project Files"), i18n("project configuration file") );
2192         }
2193         else if(item->type()==KileProjectItem::Image) {
2194             dontOpenWarning( item, i18n("Open All Project Files"), i18n("graphics file") );
2195         }
2196         else if(!m_ki->isOpen(item->url())) {
2197             fileOpen(item->url(), item->encoding());
2198         }
2199     }
2200 
2201     if(doc) { // we have a doc so switch back to original view
2202         m_ki->viewManager()->switchToTextView(doc->url());
2203     }
2204 }
2205 
getProjectFiles()2206 QStringList Manager::getProjectFiles()
2207 {
2208     QStringList filelist;
2209 
2210     KileProject *project = activeProject();
2211     if ( project )
2212     {
2213         QList<KileProjectItem*> list = project->items();
2214         for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
2215             KileProjectItem *item = *it;
2216 
2217             if(item->type() != KileProjectItem::ProjectFile && item->type() != KileProjectItem::Image) {
2218                 filelist << item->url().toLocalFile();
2219             }
2220         }
2221     }
2222     return filelist;
2223 }
2224 
dontOpenWarning(KileProjectItem * item,const QString & action,const QString & filetype)2225 void Manager::dontOpenWarning(KileProjectItem *item, const QString &action, const QString &filetype)
2226 {
2227     m_ki->errorHandler()->printMessage(KileTool::Info,
2228                                        i18n("not opened: %1 (%2)", item->url().toLocalFile(), filetype),
2229                                        action);
2230 }
2231 
selectProjectFileItem(const QString & label)2232 KileProjectItem* Manager::selectProjectFileItem(const QString &label)
2233 {
2234     // select a project
2235     KileProject *project = selectProject(i18n("Select Project"));
2236     if(!project) {
2237         return Q_NULLPTR;
2238     }
2239 
2240     // get a list of files
2241     QStringList filelist;
2242     QMap<QString, KileProjectItem*> map;
2243     QList<KileProjectItem*> list = project->items();
2244     for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
2245         KileProjectItem *item = *it;
2246 
2247         filelist << item->path();
2248         map[item->path()] = item;
2249     }
2250 
2251     // select one of these files
2252     KileProjectItem *item = Q_NULLPTR;
2253     KileListSelector *dlg  = new KileListSelector(filelist, i18n("Project Files"), label, true, m_ki->mainWindow());
2254     if(dlg->exec()) {
2255         if(dlg->hasSelection()) {
2256             QString name = dlg->selectedItems().first();
2257             if(map.contains(name)) {
2258                 item = map[name];
2259             }
2260             else {
2261                 KMessageBox::error(m_ki->mainWindow(), i18n("Could not determine the selected file."),i18n( "Project Error"));
2262             }
2263         }
2264     }
2265     delete dlg;
2266 
2267     return item;
2268 }
2269 
selectProjectFileItems(const QString & label)2270 QList<KileProjectItem*> Manager::selectProjectFileItems(const QString &label)
2271 {
2272     KileProject *project = selectProject(i18n("Select Project"));
2273     if(!project) {
2274         return QList<KileProjectItem*>();
2275     }
2276 
2277     QStringList filelist;
2278     QMap<QString,KileProjectItem *> map;
2279 
2280     QList<KileProjectItem*> list = project->items();
2281     for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) {
2282         KileProjectItem *item = *it;
2283 
2284         filelist << item->path();
2285         map[item->path()] = item;
2286     }
2287 
2288     QList<KileProjectItem*> itemsList;
2289 
2290     KileListSelector *dlg  = new KileListSelector(filelist, i18n("Project Files"), label, true, m_ki->mainWindow());
2291     dlg->setSelectionMode(QAbstractItemView::ExtendedSelection);
2292     if(dlg->exec()) {
2293         if(dlg->hasSelection()) {
2294             QStringList selectedfiles = dlg->selectedItems();
2295             for(QStringList::Iterator it = selectedfiles.begin(); it != selectedfiles.end(); ++it ) {
2296                 if(map.contains(*it)) {
2297                     itemsList.append(map[(*it)]);
2298                 }
2299                 else {
2300                     KMessageBox::error(m_ki->mainWindow(), i18n("Could not determine the selected file."), i18n( "Project Error"));
2301                 }
2302             }
2303         }
2304     }
2305     delete dlg;
2306 
2307     return itemsList;
2308 }
2309 
2310 // add a new file to the project
2311 //  - only when there is an active project
2312 //  - if the file doesn't already belong to it (checked by addToProject)
2313 
projectAddFile(QString filename,bool graphics)2314 void Manager::projectAddFile(QString filename, bool graphics)
2315 {
2316     KILE_DEBUG_MAIN << "===Kile::projectAddFile==============";
2317     KileProject *project = activeProject();
2318     if(!project) {
2319         return;
2320     }
2321 
2322     QFileInfo fi(filename);
2323     if(!fi.exists()) {
2324         if(graphics) {
2325             return;
2326         }
2327 
2328         // called from InputDialog after a \input- or \include command:
2329         //  - if the chosen file has an extension: accept
2330         //  - if not we add the default TeX extension: accept if it exists else reject
2331         QString ext = fi.completeSuffix();
2332         if ( ! ext.isEmpty() ) {
2333             return;
2334         }
2335 
2336         filename += m_ki->extensions()->latexDocumentDefault();
2337         if ( QFileInfo(filename).exists() ) {
2338             return;
2339         }
2340     }
2341 
2342     //ok, we have a project and an existing file
2343     KILE_DEBUG_MAIN << "\tadd file: " << filename;
2344     m_ki->viewManager()->updateStructure(false);
2345 
2346     QUrl url;
2347     url.setPath(filename);
2348     addToProject(project, url);
2349 }
2350 
cleanupDocumentInfoForProjectItems(KileDocument::Info * info)2351 void Manager::cleanupDocumentInfoForProjectItems(KileDocument::Info *info)
2352 {
2353     QList<KileProjectItem*> itemsList = itemsFor(info);
2354     for(QList<KileProjectItem*>::iterator it = itemsList.begin(); it != itemsList.end(); ++it) {
2355         (*it)->setInfo(Q_NULLPTR);
2356     }
2357 }
2358 
createProgressDialog()2359 void Manager::createProgressDialog()
2360 {
2361     //TODO this is a dangerous dialog and should be removed in the long-term:
2362     // the dialog disables all close events unless all files are loaded,
2363     // thus if there is a loading error, the only way to abort loading gracefully is to
2364     // terminate the application
2365     m_progressDialog = new KileWidget::ProgressDialog(m_ki->mainWindow());
2366     QLabel *label = new QLabel(m_progressDialog);
2367     label->setText(i18n("Opening Project..."));
2368     m_progressDialog->setLabel(label);
2369     m_progressDialog->setModal(true);
2370     m_progressDialog->setLabelText(i18n("Scanning project files..."));
2371     m_progressDialog->setAutoClose(true);
2372     m_progressDialog->setMinimumDuration(2000);
2373     m_progressDialog->hide();
2374 }
2375 
loadDocumentAndViewSettings(KileDocument::TextInfo * textInfo)2376 void Manager::loadDocumentAndViewSettings(KileDocument::TextInfo *textInfo)
2377 {
2378     KTextEditor::Document *document = textInfo->getDoc();
2379     if(!document) {
2380         return;
2381     }
2382 
2383     KConfigGroup configGroup = configGroupForDocumentSettings(document);
2384     if(!configGroup.exists()) {
2385         return;
2386     }
2387 
2388     document->readSessionConfig(configGroup, QSet<QString>() << "SkipEncoding" << "SkipUrl");
2389     {
2390         LaTeXInfo *latexInfo = dynamic_cast<LaTeXInfo*>(textInfo);
2391         if(latexInfo) {
2392             KileTool::LivePreviewManager::readLivePreviewStatusSettings(configGroup, latexInfo);
2393         }
2394     }
2395 
2396     {
2397         LaTeXOutputHandler *h = dynamic_cast<LaTeXOutputHandler*>(textInfo);
2398         if(h) {
2399             h->readBibliographyBackendSettings(configGroup);
2400         }
2401     }
2402 
2403     QList<KTextEditor::View*> viewList = document->views();
2404     int i = 0;
2405     for(QList<KTextEditor::View*>::iterator it = viewList.begin(); it != viewList.end(); ++it) {
2406         KTextEditor::View *view = *it;
2407         configGroup = configGroupForViewSettings(document, i);
2408         view->readSessionConfig(configGroup);
2409         ++i;
2410     }
2411 
2412 }
2413 
saveDocumentAndViewSettings(KileDocument::TextInfo * textInfo)2414 void Manager::saveDocumentAndViewSettings(KileDocument::TextInfo *textInfo)
2415 {
2416     KTextEditor::Document *document = textInfo->getDoc();
2417     if(!document) {
2418         return;
2419     }
2420 
2421     KConfigGroup configGroup = configGroupForDocumentSettings(document);
2422 
2423     QUrl url = document->url();
2424     url.setPassword(""); // we don't want the password to appear in the configuration file
2425     deleteDocumentAndViewSettingsGroups(url);
2426 
2427     document->writeSessionConfig(configGroup, QSet<QString>() << "SkipEncoding" << "SkipUrl");
2428     {
2429         LaTeXInfo *latexInfo = dynamic_cast<LaTeXInfo*>(textInfo);
2430         if(latexInfo) {
2431             KileTool::LivePreviewManager::writeLivePreviewStatusSettings(configGroup, latexInfo);
2432         }
2433     }
2434 
2435     {
2436         LaTeXOutputHandler *h = dynamic_cast<LaTeXOutputHandler*>(textInfo);
2437         if(h) {
2438             h->writeBibliographyBackendSettings(configGroup);
2439         }
2440     }
2441 
2442     QList<KTextEditor::View*> viewList = document->views();
2443     int i = 0;
2444     for(QList<KTextEditor::View*>::iterator it = viewList.begin(); it != viewList.end(); ++it) {
2445         configGroup = configGroupForViewSettings(document, i);
2446         (*it)->writeSessionConfig(configGroup);
2447         ++i;
2448     }
2449     // finally remove the config groups for the oldest documents that exceed MAX_NUMBER_OF_STORED_SETTINGS
2450     configGroup = KSharedConfig::openConfig()->group("Session Settings");
2451     QList<QUrl> urlList = QUrl::fromStringList(configGroup.readEntry("Saved Documents", QStringList()));
2452     urlList.removeAll(url);
2453     urlList.push_front(url);
2454     // remove excess elements
2455     if(urlList.length() > MAX_NUMBER_OF_STORED_SETTINGS) {
2456         int excessNumber = urlList.length() - MAX_NUMBER_OF_STORED_SETTINGS;
2457         for(; excessNumber > 0; --excessNumber) {
2458             QUrl url = urlList.takeLast();
2459             deleteDocumentAndViewSettingsGroups(url);
2460         }
2461     }
2462     configGroup.writeEntry("Documents", url);
2463     configGroup.writeEntry("Saved Documents", QUrl::toStringList(urlList));
2464 }
2465 
configGroupForDocumentSettings(KTextEditor::Document * doc) const2466 KConfigGroup Manager::configGroupForDocumentSettings(KTextEditor::Document *doc) const
2467 {
2468     return KSharedConfig::openConfig()->group(configGroupNameForDocumentSettings(doc->url()));
2469 }
2470 
configGroupNameForDocumentSettings(const QUrl & url) const2471 QString Manager::configGroupNameForDocumentSettings(const QUrl &url) const
2472 {
2473     QUrl url2 = url;
2474     url2.setPassword("");
2475     return "Document-Settings,URL=" + url2.url();
2476 }
2477 
configGroupForViewSettings(KTextEditor::Document * doc,int viewIndex) const2478 KConfigGroup Manager::configGroupForViewSettings(KTextEditor::Document *doc, int viewIndex) const
2479 {
2480     return KSharedConfig::openConfig()->group(configGroupNameForViewSettings(doc->url(), viewIndex));
2481 }
2482 
configGroupNameForViewSettings(const QUrl & url,int viewIndex) const2483 QString Manager::configGroupNameForViewSettings(const QUrl &url, int viewIndex) const
2484 {
2485     QUrl url2 = url;
2486     url2.setPassword("");
2487     return "View-Settings,View=" + QString::number(viewIndex) + ",URL=" + url2.url();
2488 }
2489 
deleteDocumentAndViewSettingsGroups(const QUrl & url)2490 void Manager::deleteDocumentAndViewSettingsGroups(const QUrl &url)
2491 {
2492     QString urlString = url.url();
2493     const QStringList groupList = KSharedConfig::openConfig()->groupList();
2494     for(auto groupName : groupList) {
2495         if(!KSharedConfig::openConfig()->hasGroup(groupName)) { // 'groupName' might have been deleted
2496             continue;                                       // work around bug 384039
2497         }
2498         if(groupName.startsWith(QLatin1String("Document-Settings"))
2499                 || groupName.startsWith(QLatin1String("View-Settings"))) {
2500             int urlIndex = groupName.indexOf("URL=");
2501             if(urlIndex >= 0 && groupName.mid(urlIndex + 4) == urlString) {
2502                 KSharedConfig::openConfig()->deleteGroup(groupName);
2503             }
2504         }
2505     }
2506 }
2507 
loadTextURLContents(const QUrl & url,const QString & encoding)2508 QStringList Manager::loadTextURLContents(const QUrl &url, const QString& encoding)
2509 {
2510     QTemporaryFile *temporaryFile = Q_NULLPTR;
2511     QString localFileName;
2512     if(url.isLocalFile()) {
2513         localFileName = url.path();
2514     }
2515     else { // only use KIO when we have to
2516         temporaryFile = new QTemporaryFile();
2517         if(!temporaryFile->open()) {
2518             KILE_DEBUG_MAIN << "Cannot create temporary file for" << url;
2519             delete temporaryFile;
2520             return QStringList();
2521         }
2522         localFileName = temporaryFile->fileName();
2523         auto downloadJob = KIO::file_copy(url, QUrl::fromLocalFile(localFileName), 0600, KIO::Overwrite);
2524         KJobWidgets::setWindow(downloadJob, m_ki->mainWindow());
2525         // FIXME: 'exec' should not be used!
2526         if (!downloadJob->exec()) {
2527             KILE_DEBUG_MAIN << "Cannot download resource: " << url;
2528             KILE_DEBUG_MAIN << downloadJob->errorString();
2529             delete temporaryFile;
2530             return QStringList();
2531         }
2532     }
2533 
2534     QFile localFile(localFileName);
2535 
2536     if (!localFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
2537         KILE_DEBUG_MAIN << "Cannot open source file: " << localFileName;
2538         delete temporaryFile;
2539         return QStringList();
2540     }
2541 
2542     QStringList res;
2543     QTextStream stream(&localFile);
2544     if(!encoding.isEmpty()) {
2545         stream.setCodec(encoding.toLatin1());
2546     }
2547     while(!stream.atEnd()) {
2548         res.append(stream.readLine());
2549     }
2550     delete temporaryFile;
2551     return res;
2552 }
2553 
2554 }
2555