File indexing completed on 2024-04-21 15:55:39
0001 /***************************************************************************** 0002 * Copyright (C) 2004 by Jeroen Wijnhout (Jeroen.Wijnhout@kdemail.net) * 0003 * (C) 2006-2018 by Michel Ludwig (michel.ludwig@kdemail.net) * 0004 * (C) 2007 by Holger Danielsson (holger.danielsson@versanet.de) * 0005 ******************************************************************************/ 0006 0007 /*************************************************************************** 0008 * * 0009 * This program is free software; you can redistribute it and/or modify * 0010 * it under the terms of the GNU General Public License as published by * 0011 * the Free Software Foundation; either version 2 of the License, or * 0012 * (at your option) any later version. * 0013 * * 0014 ***************************************************************************/ 0015 0016 #include "kiledocmanager.h" 0017 0018 #include <QAbstractItemView> 0019 #include <QApplication> 0020 #include <QDir> 0021 #include <QDropEvent> 0022 #include <QFile> 0023 #include <QLabel> 0024 #include <QList> 0025 #include <QMimeData> 0026 #include <QMimeType> 0027 #include <QMimeDatabase> 0028 #include <QProgressDialog> 0029 #include <QPushButton> 0030 #include <QTemporaryFile> 0031 #include <QTextCodec> 0032 #include <QUrl> 0033 0034 #include <KConfigGroup> 0035 #include <KEncodingFileDialog> 0036 #include <KIO/CopyJob> 0037 #include <KIO/DeleteJob> 0038 #include <KIO/FileCopyJob> 0039 #include <KIO/StatJob> 0040 #include <KJobWidgets> 0041 #include <KLocalizedString> 0042 #include <KMessageBox> 0043 #include <KTextEditor/Document> 0044 #include <KTextEditor/Editor> 0045 #include <KTextEditor/View> 0046 0047 #include "dialogs/cleandialog.h" 0048 #include "dialogs/listselector.h" 0049 #include "dialogs/managetemplatesdialog.h" 0050 #include "dialogs/newfilewizard.h" 0051 #include "dialogs/projectdialogs.h" 0052 #include "documentinfo.h" 0053 #include "errorhandler.h" 0054 #include "kileconfig.h" 0055 #include "kiledebug.h" 0056 #include "kileinfo.h" 0057 #include "kileproject.h" 0058 #include "kilestdtools.h" 0059 #include "kiletool_enums.h" 0060 #include "kiletool.h" 0061 #include "kiletoolmanager.h" 0062 #include "kileviewmanager.h" 0063 #include "livepreview.h" 0064 #include "parser/parsermanager.h" 0065 #include "scriptmanager.h" 0066 #include "templates.h" 0067 #include "utilities.h" 0068 #include "widgets/filebrowserwidget.h" 0069 #include "widgets/konsolewidget.h" 0070 #include "widgets/projectview.h" 0071 #include "widgets/structurewidget.h" 0072 0073 /* 0074 * Newly created text documents have an empty URL and a non-empty document name. 0075 */ 0076 0077 #define MAX_NUMBER_OF_STORED_SETTINGS 50 0078 0079 namespace KileDocument 0080 { 0081 0082 Manager::Manager(KileInfo *info, QObject *parent, const char *name) : 0083 QObject(parent), 0084 m_ki(info), 0085 m_progressDialog(Q_NULLPTR), 0086 m_currentlySavingAll(false), 0087 m_currentlyOpeningFile(false) 0088 { 0089 setObjectName(name); 0090 m_editor = KTextEditor::Editor::instance(); 0091 if(!m_editor) { 0092 KMessageBox::error(m_ki->mainWindow(), i18n("No editor component found. Please check your KDE installation."), 0093 i18n("No editor component found.")); 0094 } 0095 } 0096 0097 Manager::~Manager() 0098 { 0099 KILE_DEBUG_MAIN << "==KileDocument::Manager::~Manager()========="; 0100 if(m_progressDialog.isNull()) { 0101 delete m_progressDialog.data(); 0102 } 0103 } 0104 0105 void Manager::readConfig() 0106 { 0107 } 0108 0109 void Manager::writeConfig() 0110 { 0111 } 0112 0113 void Manager::trashDoc(TextInfo *docinfo, KTextEditor::Document *doc /*= Q_NULLPTR */ ) 0114 { 0115 KILE_DEBUG_MAIN << "==void Manager::trashDoc(" << docinfo->url().toLocalFile() << ")====="; 0116 0117 if(m_ki->isOpen(docinfo->url())) { 0118 return; 0119 } 0120 0121 if(doc) { 0122 doc = docinfo->getDoc(); 0123 } 0124 0125 //look for doc before we detach the docinfo 0126 //if we do it the other way around, docFor will always return nil 0127 if(!doc) { 0128 doc = docFor(docinfo->url()); 0129 } 0130 0131 KILE_DEBUG_MAIN << "DETACHING " << docinfo; 0132 docinfo->detach(); 0133 0134 KILE_DEBUG_MAIN << "\tTRASHING " << doc; 0135 if(!doc) { 0136 return; 0137 } 0138 0139 KILE_DEBUG_MAIN << "just checking: docinfo->getDoc() = " << docinfo->getDoc(); 0140 KILE_DEBUG_MAIN << "just checking: docFor(docinfo->url()) = " << docFor(docinfo->url()); 0141 0142 for (int i = 0; i < m_textInfoList.count(); ++i) { 0143 if((m_textInfoList.at(i) != docinfo) && (m_textInfoList.at(i)->getDoc() == doc)) { 0144 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.")); 0145 qWarning() << "docinfo " << m_textInfoList.at(i) << " url " << m_textInfoList.at(i)->url().fileName() << " has a wild pointer!!!"; 0146 } 0147 } 0148 0149 KILE_DEBUG_MAIN << "DELETING doc"; 0150 delete doc; 0151 } 0152 0153 // update all Info's with changed user commands 0154 void Manager::updateInfos() 0155 { 0156 for(QList<TextInfo*>::iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) { 0157 (*it)->updateStructLevelInfo(); 0158 } 0159 } 0160 0161 bool Manager::isOpeningFile() 0162 { 0163 return m_currentlyOpeningFile; 0164 } 0165 0166 KTextEditor::Editor* Manager::getEditor() 0167 { 0168 return m_editor; 0169 } 0170 0171 KTextEditor::Document* Manager::docFor(const QUrl &url) 0172 { 0173 for(QList<TextInfo*>::iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) { 0174 TextInfo *info = *it; 0175 0176 if(m_ki->similarOrEqualURL(info->url(), url)) { 0177 return info->getDoc(); 0178 } 0179 } 0180 0181 return Q_NULLPTR; 0182 } 0183 0184 TextInfo* Manager::getInfo() const 0185 { 0186 KTextEditor::Document *doc = m_ki->activeTextDocument(); 0187 if(doc) { 0188 return textInfoFor(doc); 0189 } 0190 else { 0191 return Q_NULLPTR; 0192 } 0193 } 0194 0195 TextInfo* Manager::textInfoFor(const QUrl &url) 0196 { 0197 if(url.isEmpty()) { 0198 return Q_NULLPTR; 0199 } 0200 0201 KILE_DEBUG_MAIN << "==KileInfo::textInfoFor(" << url << ")=========================="; 0202 0203 for(QList<TextInfo*>::iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) { 0204 TextInfo *info = *it; 0205 0206 if (info->url() == url) { 0207 return info; 0208 } 0209 } 0210 0211 // the URL might belong to a TextInfo* which currently doesn't have a KTextEditor::Document* 0212 // associated with it, i.e. a project item which isn't open in the editor 0213 for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) { 0214 KileProjectItem *item = (*it)->item(url); 0215 0216 // all project items (across different projects) that share a URL have the same TextInfo*; 0217 // so, the first one we find is good enough 0218 if(item) { 0219 KileDocument::TextInfo *info = item->getInfo(); 0220 if(info) { 0221 return info; 0222 } 0223 } 0224 } 0225 0226 KILE_DEBUG_MAIN << "\tCOULD NOT find info for " << url; 0227 return Q_NULLPTR; 0228 } 0229 0230 TextInfo* Manager::textInfoFor(KTextEditor::Document* doc) const 0231 { 0232 if(!doc) { 0233 return Q_NULLPTR; 0234 } 0235 0236 // TextInfo* objects that contain KTextEditor::Document* pointers must be open in the editor, i.e. 0237 // we don't have to look through the project items 0238 for(QList<TextInfo*>::const_iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) { 0239 if((*it)->getDoc() == doc) { 0240 return (*it); 0241 } 0242 } 0243 0244 KILE_DEBUG_MAIN << "\tCOULD NOT find info for" << doc->url() << "by searching via a KTextEditor::Document*"; 0245 return Q_NULLPTR; 0246 } 0247 0248 QUrl Manager::urlFor(TextInfo* textInfo) 0249 { 0250 KileProjectItem *item = itemFor(textInfo); 0251 0252 QUrl url; 0253 if(item) { 0254 url = item->url(); // all items with 'textInfo' share the same URL 0255 } 0256 else { 0257 KTextEditor::Document *document = textInfo->getDoc(); 0258 if(document) { 0259 url = document->url(); 0260 } 0261 } 0262 return url; 0263 } 0264 0265 KileProject* Manager::projectForMember(const QUrl &memberUrl) 0266 { 0267 for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) { 0268 KileProject *project = *it; 0269 0270 if(project->contains(memberUrl)) { 0271 return project; 0272 } 0273 } 0274 return Q_NULLPTR; 0275 } 0276 0277 KileProject* Manager::projectFor(const QUrl &projecturl) 0278 { 0279 //find project with url = projecturl 0280 for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) { 0281 KileProject *project = *it; 0282 if(project->url() == projecturl) { 0283 return project; 0284 } 0285 } 0286 0287 return Q_NULLPTR; 0288 } 0289 0290 KileProject* Manager::projectFor(const QString &name) 0291 { 0292 //find project with url = projecturl 0293 for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) { 0294 KileProject *project = *it; 0295 0296 if (project->name() == name) { 0297 return project; 0298 } 0299 } 0300 0301 return Q_NULLPTR; 0302 } 0303 0304 KileProjectItem* Manager::itemFor(const QUrl &url, KileProject *project /*=0L*/) const 0305 { 0306 if (!project) { 0307 for(QList<KileProject*>::const_iterator it = m_projects.begin(); it != m_projects.end(); ++it) { 0308 KileProject *curProject = *it; 0309 0310 KileProjectItem *item = curProject->item(url); 0311 if(item) { 0312 return item; 0313 } 0314 } 0315 return Q_NULLPTR; 0316 } 0317 else { 0318 return project->item(url); 0319 } 0320 } 0321 0322 KileProjectItem* Manager::itemFor(TextInfo *docinfo, KileProject *project /*=0*/) const 0323 { 0324 if (!project) { 0325 for(QList<KileProject*>::const_iterator it = m_projects.begin(); it != m_projects.end(); ++it) { 0326 KileProject *curProject = *it; 0327 0328 KileProjectItem *item = curProject->item(docinfo); 0329 if(item) { 0330 return item; 0331 } 0332 } 0333 return Q_NULLPTR; 0334 } 0335 else { 0336 return project->item(docinfo); 0337 } 0338 } 0339 0340 QList<KileProjectItem*> Manager::itemsFor(Info *docinfo) const 0341 { 0342 if(!docinfo) { 0343 return QList<KileProjectItem*>(); 0344 } 0345 0346 KILE_DEBUG_MAIN << "==KileInfo::itemsFor(" << docinfo->url().fileName() << ")============"; 0347 QList<KileProjectItem*> list; 0348 for(QList<KileProject*>::const_iterator it = m_projects.begin(); it != m_projects.end(); ++it) { 0349 KileProject *project = *it; 0350 0351 KILE_DEBUG_MAIN << "\tproject: " << (*it)->name(); 0352 if(project->contains(docinfo)) { 0353 KILE_DEBUG_MAIN << "\t\tcontains"; 0354 list.append(project->item(docinfo)); 0355 } 0356 } 0357 0358 return list; 0359 } 0360 0361 QList<KileProjectItem*> Manager::itemsFor(const QUrl &url) const 0362 { 0363 QList<KileProjectItem*> list; 0364 for(QList<KileProject*>::const_iterator it = m_projects.begin(); it != m_projects.end(); ++it) { 0365 KileProject *project = *it; 0366 0367 if(project->contains(url)) { 0368 list.append(project->item(url)); 0369 } 0370 } 0371 0372 return list; 0373 } 0374 0375 bool Manager::isProjectOpen() 0376 { 0377 return ( m_projects.count() > 0 ); 0378 } 0379 0380 KileProject* Manager::activeProject() 0381 { 0382 KTextEditor::Document *doc = m_ki->activeTextDocument(); 0383 0384 if (doc) { 0385 return projectForMember(doc->url()); 0386 } 0387 else { 0388 return Q_NULLPTR; 0389 } 0390 } 0391 0392 KileProjectItem* Manager::activeProjectItem() 0393 { 0394 KileProject *curpr = activeProject(); 0395 KTextEditor::Document *doc = m_ki->activeTextDocument(); 0396 0397 if (curpr && doc) { 0398 QList<KileProjectItem*> list = curpr->items(); 0399 0400 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 0401 KileProjectItem *item = *it; 0402 0403 if (item->url() == doc->url()) { 0404 return item; 0405 } 0406 } 0407 } 0408 0409 return Q_NULLPTR; 0410 } 0411 0412 TextInfo* Manager::createTextDocumentInfo(KileDocument::Type type, const QUrl &url, const QUrl& baseDirectory) 0413 { 0414 TextInfo *docinfo = Q_NULLPTR; 0415 0416 // check whether this URL belongs to an opened project and a TextInfo* object has already 0417 // been created for that URL 0418 docinfo = textInfoFor(url); 0419 0420 if(!docinfo) { 0421 switch(type) { 0422 case LaTeX: 0423 KILE_DEBUG_MAIN << "CREATING LaTeXInfo for " << url.url(); 0424 docinfo = new LaTeXInfo(m_ki->extensions(), 0425 m_ki->abbreviationManager(), 0426 m_ki->latexCommands(), 0427 m_ki->editorExtension(), 0428 m_ki->configurationManager(), 0429 m_ki->codeCompletionManager(), 0430 m_ki->livePreviewManager(), 0431 m_ki->viewManager(), 0432 m_ki->parserManager()); 0433 break; 0434 case BibTeX: 0435 KILE_DEBUG_MAIN << "CREATING BibInfo for " << url.url(); 0436 docinfo = new BibInfo(m_ki->extensions(), 0437 m_ki->abbreviationManager(), 0438 m_ki->parserManager(), 0439 m_ki->latexCommands()); 0440 break; 0441 case Script: 0442 KILE_DEBUG_MAIN << "CREATING ScriptInfo for " << url.url(); 0443 docinfo = new ScriptInfo(m_ki->extensions(), 0444 m_ki->abbreviationManager(), 0445 m_ki->parserManager()); 0446 break; 0447 case Undefined: // fall through 0448 case Text: // fall through 0449 default: 0450 KILE_DEBUG_MAIN << "CREATING TextInfo for " << url.url(); 0451 docinfo = new TextInfo(m_ki->extensions(), 0452 m_ki->abbreviationManager(), 0453 m_ki->parserManager()); 0454 } 0455 docinfo->setBaseDirectory(baseDirectory); 0456 emit(documentInfoCreated(docinfo)); 0457 m_textInfoList.append(docinfo); 0458 } 0459 0460 KILE_DEBUG_MAIN << "DOCINFO: returning " << docinfo << " " << docinfo->url().fileName(); 0461 return docinfo; 0462 } 0463 0464 void Manager::recreateTextDocumentInfo(TextInfo *oldinfo) 0465 { 0466 QList<KileProjectItem*> list = itemsFor(oldinfo); 0467 QUrl url = oldinfo->url(); 0468 TextInfo *newinfo = createTextDocumentInfo(m_ki->extensions()->determineDocumentType(url), url, oldinfo->getBaseDirectory()); 0469 0470 newinfo->setDoc(oldinfo->getDoc()); 0471 0472 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 0473 (*it)->setInfo(newinfo); 0474 } 0475 0476 removeTextDocumentInfo(oldinfo); 0477 0478 emit(updateStructure(false, newinfo)); 0479 } 0480 0481 bool Manager::removeTextDocumentInfo(TextInfo *docinfo, bool closingproject /* = false */) 0482 { 0483 KILE_DEBUG_MAIN << "==Manager::removeTextDocumentInfo(Info *docinfo)====="; 0484 QList<KileProjectItem*> itms = itemsFor(docinfo); 0485 bool oneItem = false; 0486 if(itms.count() == 1) { 0487 oneItem = true; 0488 } 0489 0490 if(itms.count() == 0 || ( closingproject && oneItem )) { 0491 KILE_DEBUG_MAIN << "\tremoving " << docinfo << " count = " << m_textInfoList.count(); 0492 0493 // we still have to stop parsing for 'docinfo' 0494 QUrl url = urlFor(docinfo); 0495 if(url.isValid()) { 0496 m_ki->parserManager()->stopDocumentParsing(url); 0497 } 0498 0499 m_textInfoList.removeAll(docinfo); 0500 0501 emit(closingDocument(docinfo)); 0502 0503 cleanupDocumentInfoForProjectItems(docinfo); 0504 delete docinfo; 0505 0506 return true; 0507 } 0508 0509 KILE_DEBUG_MAIN << "\tnot removing " << docinfo; 0510 return false; 0511 } 0512 0513 0514 KTextEditor::Document* Manager::createDocument(const QUrl &url, TextInfo *docinfo, const QString& encoding, 0515 const QString& mode, 0516 const QString& highlight) 0517 { 0518 KILE_DEBUG_MAIN << "==KTextEditor::Document* Manager::createDocument()==========="; 0519 0520 KTextEditor::Document *doc = Q_NULLPTR; 0521 0522 if(!m_editor) { 0523 return Q_NULLPTR; 0524 } 0525 0526 doc = docFor(url); 0527 if (doc) { 0528 qWarning() << url << " already has a document!"; 0529 return doc; 0530 } 0531 doc = m_editor->createDocument(Q_NULLPTR); 0532 KILE_DEBUG_MAIN << "appending document " << doc; 0533 0534 connect(doc, &KTextEditor::Document::canceled, [=] (const QString &errMsg) { 0535 if(!errMsg.isEmpty()) { 0536 KMessageBox::error(m_ki->mainWindow(), i18n("The URL \"%1\" couldn't be opened.\n\n%2", url.toDisplayString(), errMsg), 0537 i18n("Cannot open URL")); 0538 } 0539 else { 0540 KMessageBox::error(m_ki->mainWindow(), i18n("The URL \"%1\" couldn't be opened.", url.toDisplayString()), i18n("Cannot open URL")); 0541 } 0542 }); 0543 0544 docinfo->setDoc(doc); // do this here to set up all the signals correctly in 'TextInfo' 0545 doc->setEncoding(encoding); 0546 0547 KILE_DEBUG_MAIN << "url is = " << docinfo->url(); 0548 0549 if(!url.isEmpty()) { 0550 bool r = doc->openUrl(url); 0551 if(!r) { 0552 KILE_WARNING_MAIN << "couldn't open the url" << url; 0553 docinfo->detach(); 0554 delete doc; 0555 return Q_NULLPTR; 0556 } 0557 // don't add scripts to the recent files 0558 if(r && docinfo->getType() != Script) { 0559 emit(addToRecentFiles(url)); 0560 } 0561 } 0562 0563 //handle changes of the document 0564 connect(doc, &KTextEditor::Document::documentNameChanged, this, &KileDocument::Manager::documentNameChanged); 0565 connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KileDocument::Manager::documentUrlChanged); 0566 connect(doc, &KTextEditor::Document::readWriteChanged, this, &KileDocument::Manager::documentReadWriteStateChanged); 0567 0568 connect(doc, &KTextEditor::Document::modifiedChanged, this, &KileDocument::Manager::newDocumentStatus); 0569 KTextEditor::ModificationInterface *modificationInterface = qobject_cast<KTextEditor::ModificationInterface*>(doc); 0570 if(modificationInterface) { 0571 modificationInterface->setModifiedOnDiskWarning(true); 0572 connect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), 0573 this, SIGNAL(documentModificationStatusChanged(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); 0574 } 0575 0576 if(!mode.isEmpty()) { 0577 docinfo->setMode(mode); // this ensures that mode passed with the mode parameter is actually used 0578 } 0579 if(!highlight.isEmpty()) { 0580 docinfo->setHighlightingMode(highlight); 0581 } 0582 0583 { 0584 // FIXME: the whole structure updating stuff needs to be rewritten; updates should originate from 0585 // the docinfo only, i.e. the structure view should just react to changes! 0586 0587 // small 'trick' to select the right overloaded slot: 0588 void (KileWidget::StructureWidget::*slot)(KileDocument::Info *) = &KileWidget::StructureWidget::update; 0589 connect(docinfo, &KileDocument::Info::completed, m_ki->structureWidget(), slot); 0590 } 0591 0592 KILE_DEBUG_MAIN << "createDocument: url " << doc->url(); 0593 KILE_DEBUG_MAIN << "createDocument: SANITY check: " << (docinfo->getDoc() == docFor(docinfo->url())); 0594 return doc; 0595 } 0596 0597 // WARNING: 'item' must have been set up with a TextInfo* object already 0598 KTextEditor::View* Manager::loadItem(KileDocument::Type type, KileProjectItem *item, const QString & text, bool openProjectItemViews) 0599 { 0600 KTextEditor::View *view = Q_NULLPTR; 0601 0602 KILE_DEBUG_MAIN << "==loadItem(" << item->url() << ")======"; 0603 0604 if(item->type() != KileProjectItem::Image) { 0605 view = loadText(type, item->url(), item->encoding(), openProjectItemViews && item->isOpen(), item->mode(), item->highlight(), text); 0606 KILE_DEBUG_MAIN << "\tloadItem: docfor = " << docFor(item->url()); 0607 0608 TextInfo *docinfo = item->getInfo(); 0609 0610 KILE_DEBUG_MAIN << "\tloadItem: docinfo = " << docinfo << " doc = " << docinfo->getDoc() << " docfor = " << docFor(docinfo->url()); 0611 if ( docinfo->getDoc() != docFor(docinfo->url()) ) qWarning() << "docinfo->getDoc() != docFor()"; 0612 } 0613 else { 0614 KILE_DEBUG_MAIN << "\tloadItem: no document generated"; 0615 TextInfo *docinfo = item->getInfo(); 0616 0617 if(!docFor(item->url())) { 0618 docinfo->detach(); 0619 KILE_DEBUG_MAIN << "\t\t\tdetached"; 0620 } 0621 } 0622 0623 return view; 0624 } 0625 0626 KTextEditor::View* Manager::loadText(KileDocument::Type type, const QUrl &url, const QString& encoding, 0627 bool create, 0628 const QString& mode, 0629 const QString& highlight, 0630 const QString& text, 0631 int index, 0632 const QUrl &baseDirectory) 0633 { 0634 KILE_DEBUG_MAIN << "==loadText(" << url.url() << ")================="; 0635 //if doc already opened, update the structure view and return the view 0636 if(!url.isEmpty() && m_ki->isOpen(url)) { 0637 return m_ki->viewManager()->switchToTextView(url); 0638 } 0639 0640 TextInfo *docinfo = createTextDocumentInfo(type, url, baseDirectory); 0641 KTextEditor::Document *doc = createDocument(url, docinfo, encoding, mode, highlight); 0642 if(!doc) { 0643 removeTextDocumentInfo(docinfo); 0644 return Q_NULLPTR; 0645 } 0646 0647 m_ki->structureWidget()->clean(docinfo); 0648 0649 if(!text.isEmpty()) { 0650 doc->setText(text); 0651 } 0652 0653 if (doc && create) { 0654 return m_ki->viewManager()->createTextView(docinfo, index); 0655 } 0656 0657 KILE_DEBUG_MAIN << "just after createView()"; 0658 KILE_DEBUG_MAIN << "\tdocinfo = " << docinfo << " doc = " << docinfo->getDoc() << " docfor = " << docFor(docinfo->url()); 0659 0660 return Q_NULLPTR; 0661 } 0662 0663 //FIXME: template stuff should be in own class 0664 KTextEditor::View* Manager::loadTemplate(TemplateItem *sel) 0665 { 0666 KILE_DEBUG_MAIN << "templateitem *sel = " << sel; 0667 QString text; 0668 0669 if(!sel) { 0670 return Q_NULLPTR; 0671 } 0672 0673 if (sel->name() != KileTemplate::Manager::defaultEmptyTemplateCaption() 0674 && sel->name() != KileTemplate::Manager::defaultEmptyLaTeXTemplateCaption() 0675 && sel->name() != KileTemplate::Manager::defaultEmptyBibTeXTemplateCaption()) { 0676 if(!m_editor) { 0677 return Q_NULLPTR; 0678 } 0679 //create a new document to open the template in 0680 KTextEditor::Document *tempdoc = m_editor->createDocument(Q_NULLPTR); 0681 0682 if (!tempdoc->openUrl(QUrl::fromLocalFile(sel->path()))) { 0683 KMessageBox::error(m_ki->mainWindow(), i18n("Could not find template: %1", sel->name()), i18n("File Not Found")); 0684 } 0685 else { 0686 //substitute templates variables 0687 text = tempdoc->text(); 0688 delete tempdoc; 0689 replaceTemplateVariables(text); 0690 } 0691 } 0692 0693 KileDocument::Type type = sel->type(); 0694 //always set the base directory for scripts 0695 return createDocumentWithText(text, type, QString(), (type == KileDocument::Script ? QUrl::fromLocalFile(m_ki->scriptManager()->getLocalScriptDirectory()) : QUrl())); 0696 } 0697 0698 KTextEditor::View* Manager::createDocumentWithText(const QString& text, KileDocument::Type type /* = KileDocument::Undefined */, const QString& /* extension */, const QUrl &baseDirectory) 0699 { 0700 KTextEditor::View *view = loadText(type, QUrl(), QString(), true, QString(), QString(), text, -1, baseDirectory); 0701 if(view) { 0702 //FIXME this shouldn't be necessary!!! 0703 view->document()->setModified(true); 0704 newDocumentStatus(view->document()); 0705 } 0706 0707 return view; 0708 } 0709 0710 KTextEditor::View* Manager::createNewJScript() 0711 { 0712 KTextEditor::View *view = createDocumentWithText(QString(), Script, "js", QUrl::fromLocalFile(m_ki->scriptManager()->getLocalScriptDirectory())); 0713 emit(updateStructure(false, Q_NULLPTR)); 0714 emit(updateModeStatus()); 0715 return view; 0716 } 0717 0718 KTextEditor::View* Manager::createNewLaTeXDocument() 0719 { 0720 KTextEditor::View *view = createDocumentWithText(QString(), LaTeX); 0721 emit(updateStructure(false, Q_NULLPTR)); 0722 emit(updateModeStatus()); 0723 return view; 0724 } 0725 0726 void Manager::replaceTemplateVariables(QString &line) 0727 { 0728 line=line.replace("$$AUTHOR$$", KileConfig::author()); 0729 line=line.replace("$$DOCUMENTCLASSOPTIONS$$", KileConfig::documentClassOptions()); 0730 if (!KileConfig::templateEncoding().isEmpty()) { 0731 line=line.replace("$$INPUTENCODING$$", "\\usepackage["+ KileConfig::templateEncoding() +"]{inputenc}"); 0732 } 0733 else { 0734 line = line.remove("$$INPUTENCODING$$"); 0735 } 0736 } 0737 0738 void Manager::createTemplate() 0739 { 0740 KTextEditor::View *view = m_ki->viewManager()->currentTextView(); 0741 if (view) { 0742 if (view->document()->isModified()) { 0743 KMessageBox::information(m_ki->mainWindow(),i18n("Please save the file first.")); 0744 return; 0745 } 0746 } 0747 else { 0748 KMessageBox::information(m_ki->mainWindow(),i18n("Open/create a document first.")); 0749 return; 0750 } 0751 0752 QUrl url = view->document()->url(); 0753 KileDocument::Type type = m_ki->extensions()->determineDocumentType(url); 0754 0755 if(type == KileDocument::Undefined || type == KileDocument::Text) { 0756 KMessageBox::information(m_ki->mainWindow(),i18n("A template for this type of document cannot be created.")); 0757 return; 0758 } 0759 0760 ManageTemplatesDialog mtd(m_ki->templateManager(), url, i18n("Create Template From Document")); 0761 mtd.exec(); 0762 } 0763 0764 void Manager::removeTemplate() 0765 { 0766 ManageTemplatesDialog mtd(m_ki->templateManager(), i18n("Remove Template")); 0767 mtd.exec(); 0768 } 0769 0770 void Manager::fileNew(KileDocument::Type type) 0771 { 0772 NewFileWizard *nfw = new NewFileWizard(m_ki->templateManager(), type, m_ki->mainWindow()); 0773 if(nfw->exec()) { 0774 KTextEditor::View *view = loadTemplate(nfw->getSelection()); 0775 if(view) { 0776 if(nfw->useWizard()) { 0777 emit(startWizard()); 0778 } 0779 emit(updateStructure(false, Q_NULLPTR)); 0780 emit(updateModeStatus()); 0781 } 0782 } 0783 delete nfw; 0784 } 0785 0786 void Manager::fileNewScript() 0787 { 0788 fileNew(KileDocument::Script); 0789 } 0790 0791 void Manager::fileNew(const QUrl &url) 0792 { 0793 QFile file(url.toLocalFile()); 0794 0795 //create the directory structure first 0796 QFileInfo fileInfo(file); 0797 QDir dir = fileInfo.absolutePath(); 0798 dir.mkpath("."); 0799 0800 //create an empty file 0801 file.open(QIODevice::ReadWrite); 0802 file.close(); 0803 0804 fileOpen(url, QString()); 0805 } 0806 0807 void Manager::fileOpen() 0808 { 0809 //determine the starting dir for the file dialog 0810 QString compileName = m_ki->getCompileName(); 0811 QString currentDir; 0812 if(QFileInfo::exists(compileName)) { 0813 currentDir = QFileInfo(compileName).absolutePath(); 0814 } 0815 else { 0816 currentDir = m_ki->fileSelector()->currentUrl().toLocalFile(); 0817 } 0818 0819 // use a filter for fileOpen dialog 0820 Extensions *extensions = m_ki->extensions(); 0821 QString filter = extensions->fileFilterKDEStyle(true, {KileDocument::Extensions::TEX, 0822 KileDocument::Extensions::PACKAGES, 0823 KileDocument::Extensions::BIB, 0824 KileDocument::Extensions::METAPOST 0825 }); 0826 0827 // try to get the current encoding, this is kind of ugly ... 0828 QString encoding = m_ki->toolManager()->config()->group("Kate Document Defaults").readEntry("Encoding",""); 0829 0830 //get the URLs 0831 KEncodingFileDialog::Result result = KEncodingFileDialog::getOpenUrlsAndEncoding(encoding, QUrl::fromLocalFile(currentDir), filter, m_ki->mainWindow(), i18n("Open Files")); 0832 0833 //open them 0834 const QList<QUrl>& urls = result.URLs; 0835 for (QList<QUrl>::ConstIterator i = urls.begin(); i != urls.end(); ++i) { 0836 const QUrl& url = *i; 0837 if(m_ki->extensions()->isProjectFile(url)) { // this can happen... (bug 317432) 0838 KILE_DEBUG_MAIN << "file is a project file:" << url; 0839 projectOpen(url); 0840 continue; 0841 } 0842 0843 fileOpen(url, result.encoding); 0844 } 0845 } 0846 0847 void Manager::fileSelected(const KFileItem& file) 0848 { 0849 fileSelected(file.url()); 0850 } 0851 0852 void Manager::fileSelected(const KileProjectItem * item) 0853 { 0854 fileOpen(item->url(), item->encoding()); 0855 } 0856 0857 void Manager::fileSelected(const QUrl &url) 0858 { 0859 fileOpen(url, QString()); 0860 } 0861 0862 void Manager::saveURL(const QUrl &url) 0863 { 0864 KTextEditor::Document *doc = docFor(url); 0865 if(doc) { 0866 doc->save(); 0867 } 0868 } 0869 0870 void Manager::newDocumentStatus(KTextEditor::Document *doc) 0871 { 0872 KILE_DEBUG_MAIN << "void Manager::newDocumentStatus(Kate::Document)" << Qt::endl; 0873 if(!doc) { 0874 return; 0875 } 0876 0877 // sync terminal 0878 m_ki->texKonsole()->sync(); 0879 0880 emit(documentModificationStatusChanged(doc, doc->isModified(), KTextEditor::ModificationInterface::OnDiskUnmodified)); 0881 } 0882 0883 bool Manager::fileSaveAll(bool disUntitled) 0884 { 0885 // this can occur when autosaving should take place when we 0886 // are still busy with it (KIO::NetAccess keeps the event loop running) 0887 if(m_currentlySavingAll) { 0888 return true; 0889 } 0890 m_currentlySavingAll = true; 0891 KTextEditor::View *view = Q_NULLPTR; 0892 QFileInfo fi; 0893 bool oneSaveFailed = false; 0894 QUrl url, backupUrl; 0895 0896 KILE_DEBUG_MAIN << "===Kile::fileSaveAll(disUntitled = " << disUntitled <<")"; 0897 0898 for(int i = 0; i < m_ki->viewManager()->textViewCount(); ++i) { 0899 view = m_ki->viewManager()->textView(i); 0900 0901 if(view && view->document()->isModified()) { 0902 url = view->document()->url(); 0903 fi.setFile(url.toLocalFile()); 0904 0905 if(!disUntitled || !(disUntitled && url.isEmpty())) { // either we don't disregard untitled docs, or the doc has a title 0906 KILE_DEBUG_MAIN << "trying to save: " << url.toLocalFile(); 0907 bool saveResult = view->document()->documentSave(); 0908 fi.refresh(); 0909 0910 if(!saveResult) { 0911 oneSaveFailed = true; 0912 m_ki->errorHandler()->printMessage(KileTool::Error, 0913 i18n("Kile encountered problems while saving the file %1. Do you have enough free disk space left?", url.toDisplayString()), 0914 i18n("Saving")); 0915 } 0916 } 0917 } 0918 } 0919 0920 /* 0921 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 0922 And if we only have one masterdoc or none nothing goes wrong. 0923 */ 0924 emit(updateStructure(false, Q_NULLPTR)); 0925 m_currentlySavingAll = false; 0926 return !oneSaveFailed; 0927 } 0928 0929 void Manager::fileSaveCompiledDocument() 0930 { 0931 const QString compiledDocumentFileName = m_ki->livePreviewManager()->getPreviewFile(); 0932 0933 QFileInfo fileInfo(compiledDocumentFileName); 0934 if(!fileInfo.exists() || !fileInfo.isReadable()) { 0935 KILE_WARNING_MAIN << "file doesn't exist or cannot be read:" << compiledDocumentFileName; 0936 return; 0937 } 0938 QMimeDatabase db; 0939 0940 QStringList nameFilters; 0941 { 0942 QMimeType detectedMimeType = db.mimeTypeForFile(fileInfo); 0943 if(!detectedMimeType.isDefault()) { // All files (*) 0944 nameFilters << detectedMimeType.filterString(); 0945 } 0946 } 0947 nameFilters << i18n("Any files (*)"); 0948 0949 QFileDialog *dlg = new QFileDialog(m_ki->mainWindow(), i18n("Save Compiled Document As...")); 0950 dlg->setModal(true); 0951 dlg->setNameFilters(nameFilters); 0952 dlg->selectFile(fileInfo.fileName()); 0953 dlg->setAcceptMode(QFileDialog::AcceptSave); 0954 0955 connect(dlg, &QFileDialog::urlSelected, 0956 this, [compiledDocumentFileName](const QUrl& url) { 0957 if(!url.isValid()) { 0958 return; 0959 } 0960 // the QFileDialog will take care of asking for overwrite permission (if the chosen file exists already) 0961 KIO::CopyJob *copyJob = KIO::copy(QUrl::fromLocalFile(compiledDocumentFileName), url, KIO::Overwrite); 0962 QObject::connect(copyJob, &KIO::CopyJob::finished, copyJob, &QObject::deleteLater); 0963 }); 0964 dlg->exec(); 0965 } 0966 0967 TextInfo* Manager::fileOpen(const QUrl &url, const QString& encoding, int index) 0968 { 0969 m_currentlyOpeningFile = true; 0970 KILE_DEBUG_MAIN << "==Kile::fileOpen=========================="; 0971 0972 if(url.isLocalFile() && QFileInfo(url.toLocalFile()).isDir()) { 0973 KILE_DEBUG_MAIN << "tried to open directory" << url; 0974 KMessageBox::error(m_ki->mainWindow(), i18n("The URL \"%1\" cannot be opened\nas it is a directory.", url.toDisplayString()), 0975 i18n("Cannot open directory")); 0976 m_currentlyOpeningFile = false; // has to be before the 'switchToTextView' call as 0977 // it emits signals that are handled by the live preview manager 0978 return Q_NULLPTR; 0979 } 0980 0981 KILE_DEBUG_MAIN << "url is " << url.url(); 0982 const QUrl realurl = KileUtilities::canonicalUrl(url); 0983 KILE_DEBUG_MAIN << "canonical url is " << realurl.url(); 0984 0985 if(m_ki->isOpen(realurl)) { 0986 m_currentlyOpeningFile = false; // has to be before the 'switchToTextView' call as 0987 // it emits signals that are handled by the live preview manager 0988 m_ki->viewManager()->switchToTextView(realurl); 0989 return textInfoFor(realurl); 0990 } 0991 0992 KTextEditor::View *view = loadText(m_ki->extensions()->determineDocumentType(realurl), realurl, encoding, true, QString(), QString(), QString(), index); 0993 if(!view) { 0994 m_currentlyOpeningFile = false; // has to be before the 'switchToTextView' call as 0995 // it emits signals that are handled by the live preview manager 0996 return Q_NULLPTR; 0997 } 0998 QList<KileProjectItem*> itemList = itemsFor(realurl); 0999 TextInfo *textInfo = textInfoFor(realurl); 1000 1001 for(QList<KileProjectItem*>::iterator it = itemList.begin(); it != itemList.end(); ++it) { 1002 (*it)->setInfo(textInfo); 1003 } 1004 1005 if(itemList.isEmpty()) { 1006 emit addToProjectView(realurl); 1007 loadDocumentAndViewSettings(textInfo); 1008 } 1009 else if(view) { 1010 KileProjectItem *item = itemList.first(); 1011 item->loadDocumentAndViewSettings(); 1012 } 1013 1014 emit(updateStructure(false, Q_NULLPTR)); 1015 emit(updateModeStatus()); 1016 // update undefined references in this file 1017 emit(updateReferences(textInfoFor(realurl))); 1018 m_currentlyOpeningFile = false; 1019 emit documentOpened(textInfo); 1020 return textInfo; 1021 } 1022 1023 bool Manager::fileSave(KTextEditor::View *view) 1024 { 1025 // the 'data' property can be set by the view manager 1026 QAction *action = dynamic_cast<QAction*>(QObject::sender()); 1027 if(action) { 1028 QVariant var = action->data(); 1029 if(!view && var.isValid()) { 1030 view = var.value<KTextEditor::View*>(); 1031 // the 'data' property for the relevant actions is cleared 1032 // inside the view manager 1033 } 1034 } 1035 if(!view) { 1036 view = m_ki->viewManager()->currentTextView(); 1037 } 1038 if(!view) { 1039 return false; 1040 } 1041 QUrl url = view->document()->url(); 1042 if(url.isEmpty()) { // newly created document 1043 return fileSaveAs(view); 1044 } 1045 else { 1046 bool ret = view->document()->documentSave(); 1047 emit(updateStructure(false, textInfoFor(view->document()))); 1048 return ret; 1049 } 1050 } 1051 1052 bool Manager::fileSaveAs(KTextEditor::View* view) 1053 { 1054 // the 'data' property can be set by the view manager 1055 QAction *action = dynamic_cast<QAction*>(QObject::sender()); 1056 if(action) { 1057 QVariant var = action->data(); 1058 if(!view && var.isValid()) { 1059 view = var.value<KTextEditor::View*>(); 1060 // the 'data' property for the relevant actions is cleared 1061 // inside the view manager 1062 } 1063 } 1064 if(!view) { 1065 view = m_ki->viewManager()->currentTextView(); 1066 } 1067 if(!view) { 1068 return false; 1069 } 1070 1071 KTextEditor::Document* doc = view->document(); 1072 Q_ASSERT(doc); 1073 KileDocument::TextInfo* info = textInfoFor(doc); 1074 Q_ASSERT(info); 1075 QUrl startUrl = info->url(); 1076 QUrl oldURL = info->url(); 1077 if(startUrl.isEmpty()) { 1078 QUrl baseDirectory = info->getBaseDirectory(); 1079 if(baseDirectory.isEmpty()) { 1080 startUrl = QUrl("kfiledialog:///KILE_LATEX_SAVE_DIR"); 1081 } 1082 else { 1083 startUrl = baseDirectory; 1084 } 1085 } 1086 1087 KILE_DEBUG_MAIN << "startUrl is " << startUrl; 1088 KEncodingFileDialog::Result result; 1089 QUrl saveAsUrl; 1090 while(true) { 1091 QString filter = m_ki->extensions()->fileFilterKDEStyle(true, info->getFileFilter()); 1092 1093 result = KEncodingFileDialog::getSaveUrlAndEncoding(doc->encoding(), startUrl, filter, m_ki->mainWindow(), i18n("Save File")); 1094 if(result.URLs.isEmpty() || result.URLs.first().isEmpty()) { 1095 return false; 1096 } 1097 saveAsUrl = result.URLs.first(); 1098 if(info->getType() == KileDocument::LaTeX) { 1099 saveAsUrl = Info::makeValidTeXURL(saveAsUrl, m_ki->mainWindow(), 1100 m_ki->extensions()->isTexFile(saveAsUrl), false); // don't check for file existence 1101 } 1102 1103 if(!checkForFileOverwritePermission(saveAsUrl)) { 1104 continue; 1105 } 1106 break; 1107 } 1108 doc->setEncoding(result.encoding); 1109 if(!doc->saveAs(saveAsUrl)) { 1110 return false; 1111 } 1112 if(oldURL != saveAsUrl) { 1113 if(info->isDocumentTypePromotionAllowed()) { 1114 recreateTextDocumentInfo(info); 1115 info = textInfoFor(doc); 1116 } 1117 m_ki->structureWidget()->updateUrl(info); 1118 emit addToRecentFiles(saveAsUrl); 1119 emit addToProjectView(doc->url()); 1120 } 1121 emit(documentSavedAs(view, info)); 1122 return true; 1123 } 1124 1125 bool Manager::checkForFileOverwritePermission(const QUrl& url) 1126 { 1127 auto statJob = KIO::statDetails(url, KIO::StatJob::SourceSide, KIO::StatNoDetails); 1128 KJobWidgets::setWindow(statJob, m_ki->mainWindow()); 1129 if (statJob->exec()) { // check for writing possibility 1130 int r = KMessageBox::warningContinueCancel(m_ki->mainWindow(), i18n("A file with the name \"%1\" exists already. Do you want to overwrite it?", 1131 url.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); 1132 if(r != KMessageBox::Continue) { 1133 return false; 1134 } 1135 } 1136 return true; 1137 } 1138 1139 bool Manager::fileCloseAllOthers(KTextEditor::View *currentView) 1140 { 1141 // the 'data' property can be set by the view manager 1142 QAction *action = dynamic_cast<QAction*>(QObject::sender()); 1143 if(action) { 1144 QVariant var = action->data(); 1145 if(!currentView && var.isValid()) { 1146 // the 'data' property for the relevant actions is cleared 1147 // inside the view manager 1148 currentView = var.value<KTextEditor::View*>(); 1149 } 1150 } 1151 if(!currentView) { 1152 currentView = m_ki->viewManager()->currentTextView(); 1153 } 1154 if(!currentView) { 1155 return false; 1156 } 1157 1158 QList<KTextEditor::View*> viewList; 1159 for(int i = 0; i < m_ki->viewManager()->textViewCount(); ++i) { 1160 KTextEditor::View *view = m_ki->viewManager()->textView(i); 1161 if(currentView == view) { 1162 continue; 1163 } 1164 viewList.push_back(view); 1165 1166 } 1167 for(QList<KTextEditor::View*>::iterator it = viewList.begin(); 1168 it != viewList.end(); ++it) { 1169 if (!fileClose(*it)) { 1170 return false; 1171 } 1172 } 1173 return true; 1174 } 1175 1176 bool Manager::fileCloseAll() 1177 { 1178 KTextEditor::View * view = m_ki->viewManager()->currentTextView(); 1179 1180 //assumes one view per doc here 1181 while(m_ki->viewManager()->textViewCount() > 0) { 1182 view = m_ki->viewManager()->textView(0); 1183 if (!fileClose(view->document())) { 1184 return false; 1185 } 1186 } 1187 return true; 1188 } 1189 1190 bool Manager::fileClose(const QUrl &url) 1191 { 1192 KTextEditor::Document *doc = docFor(url); 1193 if(!doc) { 1194 return true; 1195 } 1196 else { 1197 return fileClose(doc); 1198 } 1199 } 1200 1201 bool Manager::fileClose(KTextEditor::View *view) 1202 { 1203 // the 'data' property can be set by the view manager 1204 QAction *action = dynamic_cast<QAction*>(QObject::sender()); 1205 if(action) { 1206 QVariant var = action->data(); 1207 if(!view && var.isValid()) { 1208 view = var.value<KTextEditor::View*>(); 1209 // the 'data' property for the relevant actions is cleared 1210 // inside the view manager 1211 } 1212 } 1213 if(!view) { 1214 view = m_ki->viewManager()->currentTextView(); 1215 } 1216 if(!view) { 1217 return false; 1218 } 1219 return fileClose(view->document()); 1220 } 1221 1222 bool Manager::fileClose(KTextEditor::Document *doc /* = 0L*/, bool closingproject /*= false*/) 1223 { 1224 KILE_DEBUG_MAIN << "==Kile::fileClose=========================="; 1225 1226 if(!doc) { 1227 doc = m_ki->activeTextDocument(); 1228 } 1229 1230 if(!doc) { 1231 return true; 1232 } 1233 1234 //FIXME: remove from docinfo map, remove from dirwatch 1235 KILE_DEBUG_MAIN << "doc->url().toLocalFile()=" << doc->url().toLocalFile(); 1236 1237 const QUrl url = doc->url(); 1238 1239 TextInfo *docinfo= textInfoFor(doc); 1240 if(!docinfo) { 1241 qWarning() << "no DOCINFO for " << url.url(); 1242 return true; 1243 } 1244 bool inProject = false; 1245 QList<KileProjectItem*> items = itemsFor(docinfo); 1246 for(QList<KileProjectItem*>::iterator it = items.begin(); it != items.end(); ++it) { 1247 KileProjectItem *item = *it; 1248 1249 //FIXME: refactor here 1250 if(item && doc) { 1251 storeProjectItem(item, doc); 1252 inProject = true; 1253 } 1254 } 1255 1256 if(!inProject) { 1257 KILE_DEBUG_MAIN << "not in project"; 1258 saveDocumentAndViewSettings(docinfo); 1259 } 1260 1261 if(doc->closeUrl()) { 1262 // docinfo may have been recreated from 'Untitled' doc to a named doc 1263 if(url.isEmpty()) { 1264 docinfo= textInfoFor(doc); 1265 } 1266 1267 if(KileConfig::cleanUpAfterClose()) { 1268 cleanUpTempFiles(url, true); // yes we pass here url and not docinfo->url() 1269 } 1270 1271 //FIXME: use signal/slot 1272 if( doc->views().count() > 0) { 1273 m_ki->viewManager()->removeView(doc->views().first()); 1274 } 1275 //remove the decorations 1276 1277 trashDoc(docinfo, doc); 1278 m_ki->structureWidget()->clean(docinfo); 1279 removeTextDocumentInfo(docinfo, closingproject); 1280 1281 emit removeFromProjectView(url); 1282 emit updateModeStatus(); 1283 } 1284 else { 1285 return false; 1286 } 1287 1288 return true; 1289 } 1290 1291 void Manager::buildProjectTree(const QUrl &url) 1292 { 1293 KileProject * project = projectFor(url); 1294 1295 if (project) { 1296 buildProjectTree(project); 1297 } 1298 } 1299 1300 void Manager::buildProjectTree(KileProject *project) 1301 { 1302 if(!project) { 1303 project = activeProject(); 1304 } 1305 1306 if(!project) { 1307 project = selectProject(i18n("Refresh Project Tree")); 1308 } 1309 1310 if (project) { 1311 //TODO: update structure for all docs 1312 project->buildProjectTree(); 1313 } 1314 else if (m_projects.count() == 0) { 1315 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")); 1316 } 1317 } 1318 1319 void Manager::projectNew() 1320 { 1321 KileNewProjectDialog *dlg = new KileNewProjectDialog(m_ki->templateManager(), m_ki->extensions(), m_ki->mainWindow()); 1322 1323 if (dlg->exec()) 1324 { 1325 TextInfo *newTextInfo = Q_NULLPTR; 1326 1327 KileProject *project = dlg->project(); 1328 1329 //add the project file to the project 1330 KileProjectItem *item = new KileProjectItem(project, project->url()); 1331 createTextInfoForProjectItem(item); 1332 item->setOpenState(false); 1333 projectOpenItem(item); 1334 1335 if(dlg->createNewFile()) { 1336 m_currentlyOpeningFile = true; // don't let live preview interfere 1337 1338 QString filename = dlg->file(); 1339 1340 //create the new document and fill it with the template 1341 KTextEditor::View *view = loadTemplate(dlg->getSelection()); 1342 1343 if(view) { 1344 //derive the URL from the base url of the project 1345 QUrl url = project->baseURL(); 1346 url = url.adjusted(QUrl::StripTrailingSlash); 1347 url.setPath(url.path() + '/' + filename); 1348 1349 newTextInfo = textInfoFor(view->document()); 1350 1351 //save the new file 1352 //FIXME: this needs proper error handling 1353 view->document()->saveAs(url); 1354 emit(documentModificationStatusChanged(view->document(), 1355 false, KTextEditor::ModificationInterface::OnDiskUnmodified)); 1356 1357 //add this file to the project 1358 item = new KileProjectItem(project, url); 1359 item->setInfo(newTextInfo); 1360 1361 //docinfo->updateStruct(m_kwStructure->level()); 1362 emit(updateStructure(false, newTextInfo)); 1363 } 1364 1365 m_currentlyOpeningFile = false; 1366 } 1367 1368 project->buildProjectTree(); 1369 project->save(); 1370 addProject(project); 1371 1372 emit(updateModeStatus()); 1373 emit(addToRecentProjects(project->url())); 1374 1375 if(newTextInfo) { 1376 emit documentOpened(newTextInfo); 1377 } 1378 } 1379 } 1380 1381 void Manager::addProject(KileProject *project) 1382 { 1383 KILE_DEBUG_MAIN << "==void Manager::addProject(const KileProject *project)=========="; 1384 m_projects.append(project); 1385 KILE_DEBUG_MAIN << "\tnow " << m_projects.count() << " projects"; 1386 emit addToProjectView(project); 1387 connect(project, SIGNAL(projectTreeChanged(const KileProject*)), this, SIGNAL(projectTreeChanged(const KileProject*))); 1388 } 1389 1390 KileProject* Manager::selectProject(const QString& caption) 1391 { 1392 QStringList list; 1393 for(QList<KileProject*>::iterator it = m_projects.begin(); it != m_projects.end(); ++it) { 1394 list.append((*it)->name()); 1395 } 1396 1397 KileProject *project = Q_NULLPTR; 1398 QString name; 1399 if (list.count() > 1) { 1400 KileListSelector *dlg = new KileListSelector(list, caption, i18n("Select Project"), true, m_ki->mainWindow()); 1401 if (dlg->exec()) { 1402 if(!dlg->hasSelection()) { 1403 return Q_NULLPTR; 1404 } 1405 name = dlg->selectedItems().first(); 1406 } 1407 delete dlg; 1408 } 1409 else if (list.count() == 0) { 1410 return Q_NULLPTR; 1411 } 1412 else { 1413 name = m_projects.first()->name(); 1414 } 1415 1416 project = projectFor(name); 1417 1418 return project; 1419 } 1420 1421 void Manager::addToProject(const QUrl &url) 1422 { 1423 KILE_DEBUG_MAIN << "===Kile::addToProject(const QUrl &url =" << url.url() << ")"; 1424 1425 KileProject *project = selectProject(i18n("Add to Project")); 1426 1427 if (project) { 1428 addToProject(project, url); 1429 } 1430 } 1431 1432 void Manager::addToProject(KileProject* project, const QUrl &url) 1433 { 1434 const QUrl realurl = KileUtilities::canonicalUrl(url); 1435 QFileInfo fi(realurl.toLocalFile()); 1436 1437 if (project->contains(realurl)) { 1438 m_ki->errorHandler()->printMessage(KileTool::Info, 1439 i18n("The file %1 is already member of the project %2", realurl.fileName(), project->name()), 1440 i18n("Add to Project")); 1441 return; 1442 } 1443 else if(!fi.exists() || !fi.isReadable()) 1444 { 1445 m_ki->errorHandler()->printMessage(KileTool::Info, 1446 i18n("The file %1 can not be added because it does not exist or is not readable", realurl.fileName()), 1447 i18n("Add to Project")); 1448 return; 1449 } 1450 1451 KileProjectItem *item = new KileProjectItem(project, realurl); 1452 createTextInfoForProjectItem(item); 1453 item->setOpenState(m_ki->isOpen(realurl)); 1454 projectOpenItem(item); 1455 emit addToProjectView(item); 1456 buildProjectTree(project); 1457 } 1458 1459 void Manager::removeFromProject(KileProjectItem *item) 1460 { 1461 if (item && item->project()) { 1462 KILE_DEBUG_MAIN << "\tprojecturl = " << item->project()->url().toLocalFile() << ", url = " << item->url().toLocalFile(); 1463 1464 if (item->project()->url() == item->url()) { 1465 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")); 1466 return; 1467 } 1468 1469 emit removeItemFromProjectView(item, m_ki->isOpen(item->url())); 1470 1471 KileProject *project = item->project(); 1472 project->remove(item); 1473 1474 // update undefined references in all project files 1475 updateProjectReferences(project); 1476 project->buildProjectTree(); 1477 } 1478 } 1479 1480 // WARNING: 'item' must have been set up with a TextInfo* object already 1481 void Manager::projectOpenItem(KileProjectItem *item, bool openProjectItemViews) 1482 { 1483 KILE_DEBUG_MAIN << "==Kile::projectOpenItem=========================="; 1484 KILE_DEBUG_MAIN << "\titem:" << item->url().toLocalFile(); 1485 1486 if (m_ki->isOpen(item->url())) { //remove item from projectview (this file was opened before as a normal file) 1487 emit removeFromProjectView(item->url()); 1488 } 1489 1490 KileDocument::TextInfo* itemInfo = item->getInfo(); 1491 Q_ASSERT(itemInfo); 1492 1493 if(item->isOpen()) { 1494 KTextEditor::View *view = loadItem(m_ki->extensions()->determineDocumentType(item->url()), item, QString(), openProjectItemViews); 1495 if (view) { 1496 item->loadDocumentAndViewSettings(); 1497 } 1498 // make sure that the item has been parsed, even if it isn't shown; 1499 // this is necessary to identify the correct LaTeX root document (bug 233667); 1500 m_ki->structureWidget()->update(itemInfo, true); 1501 } 1502 else if(item->type() == KileProjectItem::Source || item->type() == KileProjectItem::Package || item->type() == KileProjectItem::Bibliography) { 1503 // 'item' is not shown (and it is either a LaTeX source file or package), i.e. its 1504 // contents won't be loaded into a KTextEditor::Document; so, we have to do it: 1505 // we are loading the contents of the project item into the docinfo 1506 // for a moment 1507 itemInfo->setDocumentContents(loadTextURLContents(item->url(), item->encoding())); 1508 // in order to pass the contents to the parser 1509 m_ki->structureWidget()->update(itemInfo, true); 1510 // now we don't need the contents anymore 1511 itemInfo->setDocumentContents(QStringList()); 1512 } 1513 } 1514 1515 void Manager::createTextInfoForProjectItem(KileProjectItem *item) 1516 { 1517 item->setInfo(createTextDocumentInfo(m_ki->extensions()->determineDocumentType(item->url()), 1518 item->url(), item->project()->baseURL())); 1519 } 1520 1521 void Manager::projectOpen(const QUrl &url, int step, int max, bool openProjectItemViews) 1522 { 1523 KILE_DEBUG_MAIN << "==Kile::projectOpen=========================="; 1524 KILE_DEBUG_MAIN << "\tfilename: " << url.fileName(); 1525 1526 const QUrl realurl = KileUtilities::canonicalUrl(url); 1527 1528 if(m_ki->projectIsOpen(realurl)) { 1529 if(m_progressDialog) { 1530 m_progressDialog->hide(); 1531 } 1532 1533 KMessageBox::information(m_ki->mainWindow(), i18n("<p>The project \"%1\" is already open.</p>" 1534 "<p>If you wanted to reload the project, close the project before you re-open it.</p>", url.fileName()), 1535 i18n("Project Already Open")); 1536 return; 1537 } 1538 1539 QFileInfo fi(realurl.toLocalFile()); 1540 if(!fi.isReadable()) { 1541 if(m_progressDialog) { 1542 m_progressDialog->hide(); 1543 } 1544 1545 if (KMessageBox::warningTwoActions(m_ki->mainWindow(), 1546 i18n("<p>The project file for the project \"%1\" does not exist or it is not readable.</p>" 1547 "<p>Do you want to remove this project from the recent projects list?</p>", 1548 url.fileName()), 1549 i18n("Could Not Open Project"), 1550 KStandardGuiItem::remove(), KStandardGuiItem::cancel()) == KMessageBox::PrimaryAction) { 1551 emit(removeFromRecentProjects(realurl)); 1552 } 1553 return; 1554 } 1555 1556 if(!m_progressDialog) { 1557 createProgressDialog(); 1558 } 1559 1560 KileProject *kp = new KileProject(realurl, m_ki->extensions()); 1561 1562 if(!kp->appearsToBeValidProjectFile()) { 1563 if(m_progressDialog) { 1564 m_progressDialog->hide(); 1565 } 1566 1567 KMessageBox::error(m_ki->mainWindow(), i18n("<p>The file \"%1\" cannot be opened as it does not appear to be a project file.</p>", 1568 url.fileName()), 1569 i18n("Impossible to Open Project File")); 1570 delete kp; 1571 return; 1572 } 1573 1574 if(kp->getProjectFileVersion() > KILE_PROJECTFILE_VERSION) { 1575 if(m_progressDialog) { 1576 m_progressDialog->hide(); 1577 } 1578 1579 KMessageBox::error(m_ki->mainWindow(), i18n("<p>The project \"%1\" cannot be opened as it was created <br/>by a newer version of Kile.</p>", 1580 url.fileName()), 1581 i18n("Impossible to Open Project")); 1582 delete kp; 1583 return; 1584 } 1585 1586 if(!kp->isOfCurrentVersion()) { 1587 if(m_progressDialog) { 1588 m_progressDialog->hide(); 1589 } 1590 1591 if(KMessageBox::questionTwoActions(m_ki->mainWindow(), i18n("<p>The project file \"%1\" was created by a previous version of Kile.<br/>" 1592 "It needs to be updated before it can be opened.</p>" 1593 "<p>Do you want to update it?</p>", url.fileName()), 1594 i18n("Project File Needs to be Updated"), 1595 KStandardGuiItem::ok(), KStandardGuiItem::cancel()) == KMessageBox::SecondaryAction) { 1596 delete kp; 1597 return; 1598 } 1599 1600 if(!kp->migrateProjectFileToCurrentVersion()) { 1601 if (KMessageBox::warningTwoActions(m_ki->mainWindow(), i18n("<p>The project file \"%1\" could be not updated.</p>" 1602 "<p>Do you want to remove this project from the recent projects list?</p>", url.fileName()), 1603 i18n("Could Not Update Project File"), 1604 KStandardGuiItem::remove(), KStandardGuiItem::cancel()) == KMessageBox::PrimaryAction) { 1605 emit(removeFromRecentProjects(realurl)); 1606 } 1607 delete kp; 1608 return; 1609 } 1610 } 1611 1612 m_progressDialog->show(); 1613 1614 kp->load(); 1615 1616 if(kp->isInvalid()) { 1617 if(m_progressDialog) { 1618 m_progressDialog->hide(); 1619 } 1620 delete kp; 1621 return; 1622 } 1623 1624 emit(addToRecentProjects(realurl)); 1625 1626 QList<KileProjectItem*> list = kp->items(); 1627 int project_steps = list.count(); 1628 m_progressDialog->setMaximum(project_steps * max); 1629 project_steps *= step; 1630 m_progressDialog->setValue(project_steps); 1631 1632 // open the project files in the correct order 1633 QVector<KileProjectItem*> givenPositionVector(list.count(), Q_NULLPTR); 1634 QList<KileProjectItem*> notCorrectlyOrderedList; 1635 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 1636 KileProjectItem *item = *it; 1637 int order = item->order(); 1638 1639 if(order >= 0 && order >= list.count()) { 1640 order = -1; 1641 } 1642 if(!item->isOpen() || order < 0 || givenPositionVector[order] != Q_NULLPTR) { 1643 notCorrectlyOrderedList.push_back(item); 1644 } 1645 else { 1646 givenPositionVector[order] = item; 1647 } 1648 } 1649 1650 QList<KileProjectItem*> orderedList; 1651 for(int i = 0; i < givenPositionVector.size(); ++i) { 1652 KileProjectItem *item = givenPositionVector[i]; 1653 if(item) { 1654 orderedList.push_back(item); 1655 } 1656 } 1657 for(QList<KileProjectItem*>::iterator i = notCorrectlyOrderedList.begin(); i != notCorrectlyOrderedList.end(); ++i) { 1658 orderedList.push_back(*i); 1659 } 1660 1661 addProject(kp); 1662 1663 // for the parsing to work correctly, all ProjectItems need to have TextInfo* objects, but 1664 // the URL of 'item' might already be associated with a TextInfo* object; for example, through 1665 // a stand-alone document currently being open already, or through a project item that belongs to 1666 // a different project 1667 // => 'createTextDocumentInfo' will take care of that situation as well 1668 for (QList<KileProjectItem*>::iterator i = orderedList.begin(); i != orderedList.end(); ++i) { 1669 createTextInfoForProjectItem(*i); 1670 } 1671 1672 unsigned int counter = 1; 1673 for (QList<KileProjectItem*>::iterator i = orderedList.begin(); i != orderedList.end(); ++i) { 1674 projectOpenItem(*i, openProjectItemViews); 1675 m_progressDialog->setValue(counter + project_steps); 1676 qApp->processEvents(); 1677 ++counter; 1678 } 1679 1680 kp->buildProjectTree(); 1681 1682 emit(updateStructure(false, Q_NULLPTR)); 1683 emit(updateModeStatus()); 1684 1685 // update undefined references in all project files 1686 updateProjectReferences(kp); 1687 1688 m_ki->viewManager()->switchToTextView(kp->lastDocument()); 1689 1690 emit(projectOpened(kp)); 1691 } 1692 1693 // as all labels are gathered in the project, we can check for unsolved references 1694 void Manager::updateProjectReferences(KileProject *project) 1695 { 1696 QList<KileProjectItem*> list = project->items(); 1697 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 1698 emit(updateReferences((*it)->getInfo())); 1699 } 1700 } 1701 1702 void Manager::projectOpen() 1703 { 1704 KILE_DEBUG_MAIN << "==Kile::projectOpen=========================="; 1705 QUrl url = QFileDialog::getOpenFileUrl(m_ki->mainWindow(), i18n("Open Project"), 1706 QUrl::fromLocalFile(KileConfig::defaultProjectLocation()), 1707 m_ki->extensions()->fileFilterQtStyle(false, {KileDocument::Extensions::KILE_PROJECT})); 1708 1709 if(!url.isEmpty()) { 1710 projectOpen(url); 1711 } 1712 } 1713 1714 1715 void Manager::projectSave(KileProject *project /* = 0 */) 1716 { 1717 KILE_DEBUG_MAIN << "==Kile::projectSave=========================="; 1718 if (!project) { 1719 //find the project that corresponds to the active doc 1720 project= activeProject(); 1721 } 1722 1723 if(!project) { 1724 project = selectProject(i18n("Save Project")); 1725 } 1726 1727 if(project) { 1728 QList<KileProjectItem*> list = project->items(); 1729 KTextEditor::Document *doc = Q_NULLPTR; 1730 KileProjectItem *item = Q_NULLPTR; 1731 TextInfo *docinfo = Q_NULLPTR; 1732 1733 // determine the order in which the project items are opened 1734 QVector<KileProjectItem*> viewPositionVector(m_ki->viewManager()->getTabCount(), Q_NULLPTR); 1735 for(QList<KileProjectItem*>::iterator i = list.begin(); i != list.end(); ++i) { 1736 docinfo = (*i)->getInfo(); 1737 if(docinfo) { 1738 KTextEditor::View *view = m_ki->viewManager()->textView(docinfo); 1739 if(view) { 1740 int position = m_ki->viewManager()->tabIndexOf(view); 1741 if(position >= 0 && position < viewPositionVector.size()) { 1742 viewPositionVector[position] = *i; 1743 } 1744 } 1745 } 1746 } 1747 int position = 0; 1748 for(int i = 0; i < viewPositionVector.size(); ++i) { 1749 if(viewPositionVector[i] != Q_NULLPTR) { 1750 viewPositionVector[i]->setOrder(position); 1751 ++position; 1752 } 1753 } 1754 1755 //update the open-state of the items 1756 for (QList<KileProjectItem*>::iterator i = list.begin(); i != list.end(); ++i) { 1757 item = *i; 1758 KILE_DEBUG_MAIN << "\tsetOpenState(" << (*i)->url().toLocalFile() << ") to " << m_ki->isOpen(item->url()); 1759 item->setOpenState(m_ki->isOpen(item->url())); 1760 docinfo = item->getInfo(); 1761 1762 if(docinfo) { 1763 doc = docinfo->getDoc(); 1764 } 1765 if(doc) { 1766 storeProjectItem(item, doc); 1767 } 1768 1769 doc = Q_NULLPTR; 1770 docinfo = Q_NULLPTR; 1771 } 1772 1773 project->save(); 1774 } 1775 else { 1776 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")); 1777 } 1778 } 1779 1780 void Manager::projectAddFiles(const QUrl &url) 1781 { 1782 KileProject *project = projectFor(url); 1783 1784 if (project) { 1785 projectAddFiles(project,url); 1786 } 1787 } 1788 1789 void Manager::projectAddFiles(KileProject *project,const QUrl &fileUrl) 1790 { 1791 KILE_DEBUG_MAIN << "==Kile::projectAddFiles()=========================="; 1792 if(project == 0) { 1793 project = activeProject(); 1794 } 1795 1796 if(project == 0) { 1797 project = selectProject(i18n("Add Files to Project")); 1798 } 1799 1800 if (project) { 1801 QString currentDir; 1802 if(fileUrl.isEmpty()) { 1803 currentDir = QFileInfo(project->url().path()).dir().dirName(); 1804 } 1805 else { 1806 currentDir = fileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); 1807 } 1808 1809 KILE_DEBUG_MAIN << "currentDir is " << currentDir; 1810 QFileDialog *dlg = new QFileDialog(m_ki->mainWindow(), i18n("Add Files"), currentDir, m_ki->extensions()->fileFilterQtStyle(true, {})); 1811 dlg->setModal(true); 1812 dlg->setFileMode(QFileDialog::ExistingFiles); 1813 dlg->setLabelText(QFileDialog::Accept, i18n("Add")); 1814 1815 if(dlg->exec()) { 1816 QList<QUrl> urls = dlg->selectedUrls(); 1817 for(int i=0; i < urls.count(); ++i) { 1818 addToProject(project, urls[i]); 1819 } 1820 // update undefined references in all project files 1821 updateProjectReferences(project); 1822 } 1823 delete dlg; 1824 1825 //open them 1826 } 1827 else if (m_projects.count() == 0) { 1828 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")); 1829 } 1830 } 1831 1832 void Manager::toggleArchive(KileProjectItem *item) 1833 { 1834 item->setArchive(!item->archive()); 1835 } 1836 1837 void Manager::projectOptions(const QUrl &url) 1838 { 1839 KileProject *project = projectFor(url); 1840 1841 if (project) { 1842 projectOptions(project); 1843 } 1844 } 1845 1846 void Manager::projectOptions(KileProject *project /* = 0*/) 1847 { 1848 KILE_DEBUG_MAIN << "==Kile::projectOptions=========================="; 1849 if(!project) { 1850 project = activeProject(); 1851 } 1852 1853 if(!project) { 1854 project = selectProject(i18n("Project Options For")); 1855 } 1856 1857 if (project) { 1858 KILE_DEBUG_MAIN << "\t" << project->name(); 1859 KileProjectOptionsDialog *dlg = new KileProjectOptionsDialog(project, m_ki->extensions(), m_ki->mainWindow()); 1860 dlg->exec(); 1861 } 1862 else if (m_projects.count() == 0) { 1863 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")); 1864 } 1865 } 1866 1867 bool Manager::projectCloseAll() 1868 { 1869 KILE_DEBUG_MAIN << "==Kile::projectCloseAll=========================="; 1870 1871 while(m_projects.size() > 0) { 1872 if(!projectClose(m_projects.first()->url())) { 1873 return false; 1874 } 1875 } 1876 1877 return true; 1878 } 1879 1880 bool Manager::projectClose(const QUrl &url) 1881 { 1882 KILE_DEBUG_MAIN << "==Kile::projectClose=========================="; 1883 KileProject *project = 0; 1884 1885 if (url.isEmpty()) { 1886 project = activeProject(); 1887 1888 if (!project) { 1889 project = selectProject(i18n("Close Project")); 1890 } 1891 } 1892 else { 1893 project = projectFor(url); 1894 } 1895 1896 if(project) { 1897 KILE_DEBUG_MAIN << "\tclosing:" << project->name(); 1898 project->setLastDocument(QUrl::fromLocalFile(m_ki->getName())); 1899 1900 projectSave(project); 1901 1902 QList<KileProjectItem*> list = project->items(); 1903 1904 bool close = true; 1905 KTextEditor::Document *doc = Q_NULLPTR; 1906 TextInfo *docinfo = Q_NULLPTR; 1907 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 1908 KileProjectItem *item = *it; 1909 1910 doc = Q_NULLPTR; 1911 docinfo = item->getInfo(); 1912 if (docinfo) { 1913 doc = docinfo->getDoc(); 1914 } 1915 else { 1916 continue; 1917 } 1918 if (doc) { 1919 KILE_DEBUG_MAIN << "\t\tclosing item " << doc->url().toLocalFile(); 1920 bool r = fileClose(doc, true); 1921 close = close && r; 1922 if (!close) { 1923 break; 1924 } 1925 } 1926 else { 1927 // we still need to delete the TextInfo object 1928 removeTextDocumentInfo(docinfo, true); 1929 } 1930 } 1931 1932 if (close) { 1933 m_projects.removeAll(project); 1934 emit removeFromProjectView(project); 1935 delete project; 1936 emit(updateModeStatus()); 1937 return true; 1938 } 1939 else 1940 return false; 1941 } 1942 else if (m_projects.count() == 0) 1943 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")); 1944 1945 return true; 1946 } 1947 1948 void Manager::storeProjectItem(KileProjectItem *item, KTextEditor::Document *doc) 1949 { 1950 KILE_DEBUG_MAIN << "===Kile::storeProjectItem=============="; 1951 KILE_DEBUG_MAIN << "\titem = " << item << ", doc = " << doc; 1952 item->setEncoding(doc->encoding()); 1953 item->setMode(doc->mode()); 1954 item->setHighlight(doc->highlightingMode()); 1955 item->saveDocumentAndViewSettings(); 1956 } 1957 1958 void Manager::cleanUpTempFiles(const QUrl &url, bool silent) 1959 { 1960 KILE_DEBUG_MAIN << "===void Manager::cleanUpTempFiles(const QUrl " << url.toLocalFile() << ", bool " << silent << ")==="; 1961 1962 if( url.isEmpty() ) 1963 return; 1964 1965 QStringList extlist; 1966 QFileInfo fi(url.toLocalFile()); 1967 const QStringList templist = KileConfig::cleanUpFileExtensions().split(' '); 1968 const QString fileName = fi.fileName(); 1969 const QString dirPath = fi.absolutePath(); 1970 const QString baseName = fi.completeBaseName(); 1971 1972 for (int i = 0; i < templist.count(); ++i) { 1973 fi.setFile( dirPath + '/' + baseName + templist[i] ); 1974 if(fi.exists()) { 1975 extlist.append(templist[i]); 1976 } 1977 } 1978 1979 if(!silent && fileName.isEmpty()) { 1980 return; 1981 } 1982 1983 if (!silent && extlist.count() > 0) { 1984 KILE_DEBUG_MAIN << "not silent"; 1985 KileDialog::Clean *dialog = new KileDialog::Clean(m_ki->mainWindow(), fileName, extlist); 1986 if (dialog->exec() == QDialog::Accepted) { 1987 extlist = dialog->cleanList(); 1988 } 1989 else { 1990 delete dialog; 1991 return; 1992 } 1993 1994 delete dialog; 1995 } 1996 1997 if(extlist.count() == 0) { 1998 m_ki->errorHandler()->printMessage(KileTool::Warning, i18n("Nothing to clean for %1", fileName), 1999 i18n("Clean")); 2000 } 2001 else { 2002 for(int i = 0; i < extlist.count(); ++i) { 2003 QFile file(dirPath + '/' + baseName + extlist[i]); 2004 KILE_DEBUG_MAIN << "About to remove file = " << file.fileName(); 2005 file.remove(); 2006 } 2007 m_ki->errorHandler()->printMessage(KileTool::Info, 2008 i18n("Cleaning %1: %2", fileName, extlist.join(" ")), 2009 i18n("Clean")); 2010 } 2011 } 2012 2013 void Manager::openDroppedURLs(QDropEvent *e) { 2014 QList<QUrl> urls = e->mimeData()->urls(); 2015 Extensions *extensions = m_ki->extensions(); 2016 2017 for(QList<QUrl>::iterator i = urls.begin(); i != urls.end(); ++i) { 2018 QUrl url = *i; 2019 if(extensions->isProjectFile(url)) { 2020 projectOpen(url); 2021 } 2022 else { 2023 fileOpen(url); 2024 } 2025 } 2026 } 2027 2028 void Manager::reloadXMLOnAllDocumentsAndViews() 2029 { 2030 for(QList<TextInfo*>::iterator it = m_textInfoList.begin(); it != m_textInfoList.end(); ++it) { 2031 KTextEditor::Document *doc = (*it)->getDoc(); 2032 // FIXME: 'doc' can be null, for example if it belongs to a project item 2033 // which has been closed, but this should be improved in the sense 2034 // that 'm_textInfoList' should only contain 'TextInfo' objects which 2035 // contain valid pointers to 'KTextEditor::Document' objects. 2036 if(!doc) { 2037 continue; 2038 } 2039 doc->reloadXML(); 2040 QList<KTextEditor::View*> views = doc->views(); 2041 for(QList<KTextEditor::View*>::iterator viewIt = views.begin(); viewIt != views.end(); ++viewIt) { 2042 (*viewIt)->reloadXML(); 2043 } 2044 } 2045 } 2046 2047 2048 void Manager::handleParsingComplete(const QUrl &url, KileParser::ParserOutput* output) 2049 { 2050 KILE_DEBUG_MAIN << url << output; 2051 if(!output) { 2052 KILE_DEBUG_MAIN << "NULL output given"; 2053 return; 2054 } 2055 KileDocument::TextInfo *textInfo = textInfoFor(url); 2056 if(!textInfo) { 2057 KileProjectItem* item = itemFor(url); 2058 if(item) { 2059 textInfo = item->getInfo(); 2060 } 2061 if(!textInfo) { 2062 // this can happen for instance when the document is closed 2063 // while the parser is still running 2064 KILE_DEBUG_MAIN << "no TextInfo object found for" << url << "found"; 2065 return; 2066 } 2067 } 2068 textInfo->installParserOutput(output); 2069 m_ki->structureWidget()->updateAfterParsing(textInfo, output->structureViewItems); 2070 delete(output); 2071 } 2072 2073 // Show all opened projects and switch to another one, if you want 2074 2075 void Manager::projectShow() 2076 { 2077 if(m_projects.count() <= 1) { 2078 return; 2079 } 2080 2081 // select the new project 2082 KileProject *project = selectProject(i18n("Switch Project")); 2083 if(!project || project==activeProject()) { 2084 return; 2085 } 2086 2087 // get last opened document 2088 const QUrl lastdoc = project->lastDocument(); 2089 KileProjectItem *docitem = (!lastdoc.isEmpty()) ? itemFor(lastdoc, project) : Q_NULLPTR; 2090 2091 // if not, we search for the first opened tex file of this project 2092 // if no file is opened, we take the first tex file mentioned in the list 2093 KileProjectItem *first_texitem = Q_NULLPTR; 2094 if(!docitem) { 2095 QList<KileProjectItem*> list = project->items(); 2096 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 2097 KileProjectItem *item = *it; 2098 2099 QString itempath = item->path(); 2100 2101 // called from QAction 'Show projects...': find the first opened 2102 // LaTeX document or, if that fails, any other opened file 2103 QStringList extlist = (m_ki->extensions()->latexDocuments() + ' ' + m_ki->extensions()->latexPackages()).split(' '); 2104 for(QStringList::Iterator extIt = extlist.begin(); extIt != extlist.end(); ++extIt) { 2105 if(itempath.indexOf( (*extIt), -(*extIt).length() ) >= 0) { 2106 if (m_ki->isOpen(item->url())) { 2107 docitem = item; 2108 break; 2109 } 2110 else if(!first_texitem) { 2111 first_texitem = item; 2112 } 2113 } 2114 } 2115 if(docitem) { 2116 break; 2117 } 2118 } 2119 } 2120 2121 // did we find one opened file or must we take the first entry 2122 if(!docitem) { 2123 if(!first_texitem) { 2124 return; 2125 } 2126 docitem = first_texitem; 2127 } 2128 2129 // ok, we can switch to another project now 2130 if (m_ki->isOpen(docitem->url())) { 2131 m_ki->viewManager()->switchToTextView(docitem->url()); 2132 } 2133 else { 2134 fileOpen(docitem->url(), docitem->encoding()); 2135 } 2136 } 2137 2138 void Manager::projectRemoveFiles() 2139 { 2140 QList<KileProjectItem*> itemsList = selectProjectFileItems(i18n("Select Files to Remove")); 2141 if(itemsList.count() > 0) { 2142 for(QList<KileProjectItem*>::iterator it = itemsList.begin(); it != itemsList.end(); ++it) { 2143 removeFromProject(*it); 2144 } 2145 } 2146 } 2147 2148 void Manager::projectShowFiles() 2149 { 2150 KileProjectItem *item = selectProjectFileItem( i18n("Select File") ); 2151 if(item) { 2152 if (item->type() == KileProjectItem::ProjectFile) { 2153 dontOpenWarning(item, i18n("Show Project Files"), i18n("project configuration file")); 2154 } 2155 else if(item->type() == KileProjectItem::Image) { 2156 dontOpenWarning(item, i18n("Show Project Files"), i18n("graphics file")); 2157 } 2158 else { // ok, we can switch to another file 2159 if (m_ki->isOpen(item->url())) { 2160 m_ki->viewManager()->switchToTextView(item->url()); 2161 } 2162 else { 2163 fileOpen(item->url(), item->encoding() ); 2164 } 2165 } 2166 } 2167 } 2168 2169 void Manager::projectOpenAllFiles() 2170 { 2171 KileProject *project = selectProject(i18n("Select Project")); 2172 if(project) { 2173 projectOpenAllFiles(project->url()); 2174 } 2175 } 2176 2177 void Manager::projectOpenAllFiles(const QUrl &url) 2178 { 2179 KileProject* project; 2180 KTextEditor::Document* doc = Q_NULLPTR; 2181 2182 if(!url.isValid()) { 2183 return; 2184 } 2185 project = projectFor(url); 2186 2187 if(!project) 2188 return; 2189 2190 2191 if(m_ki->viewManager()->currentTextView()) { 2192 doc = m_ki->viewManager()->currentTextView()->document(); 2193 } 2194 // we remember the actual view, so the user gets the same view back after opening 2195 2196 QList<KileProjectItem*> list = project->items(); 2197 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 2198 KileProjectItem *item = *it; 2199 2200 if (item->type()==KileProjectItem::ProjectFile) { 2201 dontOpenWarning( item, i18n("Open All Project Files"), i18n("project configuration file") ); 2202 } 2203 else if(item->type()==KileProjectItem::Image) { 2204 dontOpenWarning( item, i18n("Open All Project Files"), i18n("graphics file") ); 2205 } 2206 else if(!m_ki->isOpen(item->url())) { 2207 fileOpen(item->url(), item->encoding()); 2208 } 2209 } 2210 2211 if(doc) { // we have a doc so switch back to original view 2212 m_ki->viewManager()->switchToTextView(doc->url()); 2213 } 2214 } 2215 2216 QStringList Manager::getProjectFiles() 2217 { 2218 QStringList filelist; 2219 2220 KileProject *project = activeProject(); 2221 if ( project ) 2222 { 2223 QList<KileProjectItem*> list = project->items(); 2224 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 2225 KileProjectItem *item = *it; 2226 2227 if(item->type() != KileProjectItem::ProjectFile && item->type() != KileProjectItem::Image) { 2228 filelist << item->url().toLocalFile(); 2229 } 2230 } 2231 } 2232 return filelist; 2233 } 2234 2235 void Manager::dontOpenWarning(KileProjectItem *item, const QString &action, const QString &filetype) 2236 { 2237 m_ki->errorHandler()->printMessage(KileTool::Info, 2238 i18n("not opened: %1 (%2)", item->url().toLocalFile(), filetype), 2239 action); 2240 } 2241 2242 KileProjectItem* Manager::selectProjectFileItem(const QString &label) 2243 { 2244 // select a project 2245 KileProject *project = selectProject(i18n("Select Project")); 2246 if(!project) { 2247 return Q_NULLPTR; 2248 } 2249 2250 // get a list of files 2251 QStringList filelist; 2252 QMap<QString, KileProjectItem*> map; 2253 QList<KileProjectItem*> list = project->items(); 2254 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 2255 KileProjectItem *item = *it; 2256 2257 filelist << item->path(); 2258 map[item->path()] = item; 2259 } 2260 2261 // select one of these files 2262 KileProjectItem *item = Q_NULLPTR; 2263 KileListSelector *dlg = new KileListSelector(filelist, i18n("Project Files"), label, true, m_ki->mainWindow()); 2264 if(dlg->exec()) { 2265 if(dlg->hasSelection()) { 2266 QString name = dlg->selectedItems().first(); 2267 if(map.contains(name)) { 2268 item = map[name]; 2269 } 2270 else { 2271 KMessageBox::error(m_ki->mainWindow(), i18n("Could not determine the selected file."),i18n( "Project Error")); 2272 } 2273 } 2274 } 2275 delete dlg; 2276 2277 return item; 2278 } 2279 2280 QList<KileProjectItem*> Manager::selectProjectFileItems(const QString &label) 2281 { 2282 KileProject *project = selectProject(i18n("Select Project")); 2283 if(!project) { 2284 return QList<KileProjectItem*>(); 2285 } 2286 2287 QStringList filelist; 2288 QMap<QString,KileProjectItem *> map; 2289 2290 QList<KileProjectItem*> list = project->items(); 2291 for(QList<KileProjectItem*>::iterator it = list.begin(); it != list.end(); ++it) { 2292 KileProjectItem *item = *it; 2293 2294 filelist << item->path(); 2295 map[item->path()] = item; 2296 } 2297 2298 QList<KileProjectItem*> itemsList; 2299 2300 KileListSelector *dlg = new KileListSelector(filelist, i18n("Project Files"), label, true, m_ki->mainWindow()); 2301 dlg->setSelectionMode(QAbstractItemView::ExtendedSelection); 2302 if(dlg->exec()) { 2303 if(dlg->hasSelection()) { 2304 QStringList selectedfiles = dlg->selectedItems(); 2305 for(QStringList::Iterator it = selectedfiles.begin(); it != selectedfiles.end(); ++it ) { 2306 if(map.contains(*it)) { 2307 itemsList.append(map[(*it)]); 2308 } 2309 else { 2310 KMessageBox::error(m_ki->mainWindow(), i18n("Could not determine the selected file."), i18n( "Project Error")); 2311 } 2312 } 2313 } 2314 } 2315 delete dlg; 2316 2317 return itemsList; 2318 } 2319 2320 // add a new file to the project 2321 // - only when there is an active project 2322 // - if the file doesn't already belong to it (checked by addToProject) 2323 2324 void Manager::projectAddFile(QString filename, bool graphics) 2325 { 2326 KILE_DEBUG_MAIN << "===Kile::projectAddFile=============="; 2327 KileProject *project = activeProject(); 2328 if(!project) { 2329 return; 2330 } 2331 2332 QFileInfo fi(filename); 2333 if(!fi.exists()) { 2334 if(graphics) { 2335 return; 2336 } 2337 2338 // called from InputDialog after a \input- or \include command: 2339 // - if the chosen file has an extension: accept 2340 // - if not we add the default TeX extension: accept if it exists else reject 2341 QString ext = fi.completeSuffix(); 2342 if ( ! ext.isEmpty() ) { 2343 return; 2344 } 2345 2346 filename += m_ki->extensions()->latexDocumentDefault(); 2347 if (QFileInfo::exists(filename)) { 2348 return; 2349 } 2350 } 2351 2352 //ok, we have a project and an existing file 2353 KILE_DEBUG_MAIN << "\tadd file: " << filename; 2354 m_ki->viewManager()->updateStructure(false); 2355 2356 QUrl url; 2357 url.setPath(filename); 2358 addToProject(project, url); 2359 } 2360 2361 void Manager::cleanupDocumentInfoForProjectItems(KileDocument::Info *info) 2362 { 2363 QList<KileProjectItem*> itemsList = itemsFor(info); 2364 for(QList<KileProjectItem*>::iterator it = itemsList.begin(); it != itemsList.end(); ++it) { 2365 (*it)->setInfo(Q_NULLPTR); 2366 } 2367 } 2368 2369 void Manager::createProgressDialog() 2370 { 2371 //TODO this is a dangerous dialog and should be removed in the long-term: 2372 // the dialog disables all close events unless all files are loaded, 2373 // thus if there is a loading error, the only way to abort loading gracefully is to 2374 // terminate the application 2375 m_progressDialog = new KileWidget::ProgressDialog(m_ki->mainWindow()); 2376 QLabel *label = new QLabel(m_progressDialog); 2377 label->setText(i18n("Opening Project...")); 2378 m_progressDialog->setLabel(label); 2379 m_progressDialog->setModal(true); 2380 m_progressDialog->setLabelText(i18n("Scanning project files...")); 2381 m_progressDialog->setAutoClose(true); 2382 m_progressDialog->setMinimumDuration(2000); 2383 m_progressDialog->hide(); 2384 } 2385 2386 void Manager::loadDocumentAndViewSettings(KileDocument::TextInfo *textInfo) 2387 { 2388 KTextEditor::Document *document = textInfo->getDoc(); 2389 if(!document) { 2390 return; 2391 } 2392 2393 KConfigGroup configGroup = configGroupForDocumentSettings(document); 2394 if(!configGroup.exists()) { 2395 return; 2396 } 2397 2398 document->readSessionConfig(configGroup, QSet<QString>() << "SkipEncoding" << "SkipUrl"); 2399 { 2400 LaTeXInfo *latexInfo = dynamic_cast<LaTeXInfo*>(textInfo); 2401 if(latexInfo) { 2402 KileTool::LivePreviewManager::readLivePreviewStatusSettings(configGroup, latexInfo); 2403 } 2404 } 2405 2406 { 2407 LaTeXOutputHandler *h = dynamic_cast<LaTeXOutputHandler*>(textInfo); 2408 if(h) { 2409 h->readBibliographyBackendSettings(configGroup); 2410 } 2411 } 2412 2413 QList<KTextEditor::View*> viewList = document->views(); 2414 int i = 0; 2415 for(QList<KTextEditor::View*>::iterator it = viewList.begin(); it != viewList.end(); ++it) { 2416 KTextEditor::View *view = *it; 2417 configGroup = configGroupForViewSettings(document, i); 2418 view->readSessionConfig(configGroup); 2419 ++i; 2420 } 2421 2422 } 2423 2424 void Manager::saveDocumentAndViewSettings(KileDocument::TextInfo *textInfo) 2425 { 2426 KTextEditor::Document *document = textInfo->getDoc(); 2427 if(!document) { 2428 return; 2429 } 2430 2431 KConfigGroup configGroup = configGroupForDocumentSettings(document); 2432 2433 QUrl url = document->url(); 2434 url.setPassword(""); // we don't want the password to appear in the configuration file 2435 deleteDocumentAndViewSettingsGroups(url); 2436 2437 document->writeSessionConfig(configGroup, QSet<QString>() << "SkipEncoding" << "SkipUrl"); 2438 { 2439 LaTeXInfo *latexInfo = dynamic_cast<LaTeXInfo*>(textInfo); 2440 if(latexInfo) { 2441 KileTool::LivePreviewManager::writeLivePreviewStatusSettings(configGroup, latexInfo); 2442 } 2443 } 2444 2445 { 2446 LaTeXOutputHandler *h = dynamic_cast<LaTeXOutputHandler*>(textInfo); 2447 if(h) { 2448 h->writeBibliographyBackendSettings(configGroup); 2449 } 2450 } 2451 2452 QList<KTextEditor::View*> viewList = document->views(); 2453 int i = 0; 2454 for(QList<KTextEditor::View*>::iterator it = viewList.begin(); it != viewList.end(); ++it) { 2455 configGroup = configGroupForViewSettings(document, i); 2456 (*it)->writeSessionConfig(configGroup); 2457 ++i; 2458 } 2459 // finally remove the config groups for the oldest documents that exceed MAX_NUMBER_OF_STORED_SETTINGS 2460 configGroup = KSharedConfig::openConfig()->group("Session Settings"); 2461 QList<QUrl> urlList = QUrl::fromStringList(configGroup.readEntry("Saved Documents", QStringList())); 2462 urlList.removeAll(url); 2463 urlList.push_front(url); 2464 // remove excess elements 2465 if(urlList.length() > MAX_NUMBER_OF_STORED_SETTINGS) { 2466 int excessNumber = urlList.length() - MAX_NUMBER_OF_STORED_SETTINGS; 2467 for(; excessNumber > 0; --excessNumber) { 2468 QUrl removeUrl = urlList.takeLast(); 2469 deleteDocumentAndViewSettingsGroups(removeUrl); 2470 } 2471 } 2472 configGroup.writeEntry("Documents", url); 2473 configGroup.writeEntry("Saved Documents", QUrl::toStringList(urlList)); 2474 } 2475 2476 KConfigGroup Manager::configGroupForDocumentSettings(KTextEditor::Document *doc) const 2477 { 2478 return KSharedConfig::openConfig()->group(configGroupNameForDocumentSettings(doc->url())); 2479 } 2480 2481 QString Manager::configGroupNameForDocumentSettings(const QUrl &url) const 2482 { 2483 QUrl url2 = url; 2484 url2.setPassword(""); 2485 return "Document-Settings,URL=" + url2.url(); 2486 } 2487 2488 KConfigGroup Manager::configGroupForViewSettings(KTextEditor::Document *doc, int viewIndex) const 2489 { 2490 return KSharedConfig::openConfig()->group(configGroupNameForViewSettings(doc->url(), viewIndex)); 2491 } 2492 2493 QString Manager::configGroupNameForViewSettings(const QUrl &url, int viewIndex) const 2494 { 2495 QUrl url2 = url; 2496 url2.setPassword(""); 2497 return "View-Settings,View=" + QString::number(viewIndex) + ",URL=" + url2.url(); 2498 } 2499 2500 void Manager::deleteDocumentAndViewSettingsGroups(const QUrl &url) 2501 { 2502 QString urlString = url.url(); 2503 const QStringList groupList = KSharedConfig::openConfig()->groupList(); 2504 for (const auto& groupName : groupList) { 2505 if(!KSharedConfig::openConfig()->hasGroup(groupName)) { // 'groupName' might have been deleted 2506 continue; // work around bug 384039 2507 } 2508 if(groupName.startsWith(QLatin1String("Document-Settings")) 2509 || groupName.startsWith(QLatin1String("View-Settings"))) { 2510 int urlIndex = groupName.indexOf("URL="); 2511 if(urlIndex >= 0 && groupName.mid(urlIndex + 4) == urlString) { 2512 KSharedConfig::openConfig()->deleteGroup(groupName); 2513 } 2514 } 2515 } 2516 } 2517 2518 QStringList Manager::loadTextURLContents(const QUrl &url, const QString& encoding) 2519 { 2520 QTemporaryFile *temporaryFile = Q_NULLPTR; 2521 QString localFileName; 2522 if(url.isLocalFile()) { 2523 localFileName = url.path(); 2524 } 2525 else { // only use KIO when we have to 2526 temporaryFile = new QTemporaryFile(); 2527 if(!temporaryFile->open()) { 2528 KILE_DEBUG_MAIN << "Cannot create temporary file for" << url; 2529 delete temporaryFile; 2530 return QStringList(); 2531 } 2532 localFileName = temporaryFile->fileName(); 2533 auto downloadJob = KIO::file_copy(url, QUrl::fromLocalFile(localFileName), 0600, KIO::Overwrite); 2534 KJobWidgets::setWindow(downloadJob, m_ki->mainWindow()); 2535 // FIXME: 'exec' should not be used! 2536 if (!downloadJob->exec()) { 2537 KILE_DEBUG_MAIN << "Cannot download resource: " << url; 2538 KILE_DEBUG_MAIN << downloadJob->errorString(); 2539 delete temporaryFile; 2540 return QStringList(); 2541 } 2542 } 2543 2544 QFile localFile(localFileName); 2545 2546 if (!localFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 2547 KILE_DEBUG_MAIN << "Cannot open source file: " << localFileName; 2548 delete temporaryFile; 2549 return QStringList(); 2550 } 2551 2552 QStringList res; 2553 QTextStream stream(&localFile); 2554 if(!encoding.isEmpty()) { 2555 stream.setCodec(encoding.toLatin1()); 2556 } 2557 while(!stream.atEnd()) { 2558 res.append(stream.readLine()); 2559 } 2560 delete temporaryFile; 2561 return res; 2562 } 2563 2564 }