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