diff --git a/language/backgroundparser/backgroundparser.cpp b/language/backgroundparser/backgroundparser.cpp index 736e376b22..2345f1e16f 100644 --- a/language/backgroundparser/backgroundparser.cpp +++ b/language/backgroundparser/backgroundparser.cpp @@ -1,842 +1,840 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "backgroundparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util/debug.h" #include "parsejob.h" #include const bool separateThreadForHighPriority = true; /** * Elides string in @p path, e.g. "VEEERY/LONG/PATH" -> ".../LONG/PATH" * - probably much faster than QFontMetrics::elidedText() * - we dont need a widget context * - takes path separators into account * * @p width Maximum number of characters * * TODO: Move to kdevutil? */ static QString elidedPathLeft(const QString& path, int width) { static const QChar separator = QDir::separator(); static const QString placeholder = QStringLiteral("..."); if (path.size() <= width) { return path; } int start = (path.size() - width) + placeholder.size(); int pos = path.indexOf(separator, start); if (pos == -1) { pos = start; // no separator => just cut off the path at the beginning } Q_ASSERT(path.size() - pos >= 0 && path.size() - pos <= width); QStringRef elidedText = path.rightRef(path.size() - pos); QString result = placeholder; result.append(elidedText); return result; } namespace { /** * @return true if @p url is non-empty, valid and has a clean path, false otherwise. */ inline bool isValidURL(const KDevelop::IndexedString& url) { if (url.isEmpty()) { return false; } QUrl original = url.toUrl(); if (!original.isValid() || original.isRelative() || original.fileName().isEmpty()) { qCWarning(LANGUAGE) << "INVALID URL ENCOUNTERED:" << url << original; return false; } QUrl cleaned = original.adjusted(QUrl::NormalizePathSegments); return original == cleaned; } } namespace KDevelop { class BackgroundParserPrivate { public: BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController) :m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive) { parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings() m_timer.setSingleShot(true); m_delay = 500; m_threads = 1; m_doneParseJobs = 0; m_maxParseJobs = 0; m_neededPriority = BackgroundParser::WorstPriority; ThreadWeaver::setDebugLevel(true, 1); QObject::connect(&m_timer, &QTimer::timeout, m_parser, &BackgroundParser::parseDocuments); } void startTimerThreadSafe() { QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection); } ~BackgroundParserPrivate() { } // Non-mutex guarded functions, only call with m_mutex acquired. void parseDocumentsInternal() { if(m_shuttingDown) return; // Create delayed jobs, that is, jobs for documents which have been changed // by the user. QList jobs; // Before starting a new job, first wait for all higher-priority ones to finish. // That way, parse job priorities can be used for dependency handling. int bestRunningPriority = BackgroundParser::WorstPriority; foreach (const ThreadWeaver::QObjectDecorator* decorator, m_parseJobs) { const ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); if (parseJob->respectsSequentialProcessing() && parseJob->parsePriority() < bestRunningPriority) { bestRunningPriority = parseJob->parsePriority(); } } bool done = false; for (QMap >::Iterator it1 = m_documentsForPriority.begin(); it1 != m_documentsForPriority.end(); ++it1 ) { if(it1.key() > m_neededPriority) break; //The priority is not good enough to be processed right now for(QSet::Iterator it = it1.value().begin(); it != it1.value().end();) { //Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily if(m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority) ) break; if(m_parseJobs.count() >= m_threads && it1.key() > BackgroundParser::NormalPriority && !specialParseJob) break; //The additional parsing thread is reserved for higher priority parsing // When a document is scheduled for parsing while it is being parsed, it will be parsed // again once the job finished, but not now. if (m_parseJobs.contains(*it) ) { ++it; continue; } Q_ASSERT(m_documents.contains(*it)); const DocumentParsePlan& parsePlan = m_documents[*it]; // If the current job requires sequential processing, but not all jobs with a better priority have been // completed yet, it will not be created now. if ( parsePlan.sequentialProcessingFlags() & ParseJob::RequiresSequentialProcessing && parsePlan.priority() > bestRunningPriority ) { ++it; continue; } qCDebug(LANGUAGE) << "creating parse-job" << *it << "new count of active parse-jobs:" << m_parseJobs.count() + 1; const QString elidedPathString = elidedPathLeft(it->str(), 70); emit m_parser->showMessage(m_parser, i18n("Parsing: %1", elidedPathString)); ThreadWeaver::QObjectDecorator* decorator = createParseJob(*it, parsePlan.features(), parsePlan.notifyWhenReady(), parsePlan.priority()); if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = decorator; //This parse-job is allocated into the reserved thread if (decorator) { ParseJob* parseJob = dynamic_cast(decorator->job()); parseJob->setSequentialProcessingFlags(parsePlan.sequentialProcessingFlags()); jobs.append(ThreadWeaver::JobPointer(decorator)); // update the currently best processed priority, if the created job respects sequential processing if ( parsePlan.sequentialProcessingFlags() & ParseJob::RespectsSequentialProcessing && parsePlan.priority() < bestRunningPriority) { bestRunningPriority = parsePlan.priority(); } } // Remove all mentions of this document. foreach(const DocumentParseTarget& target, parsePlan.targets) { if (target.priority != it1.key()) { m_documentsForPriority[target.priority].remove(*it); } } m_documents.remove(*it); it = it1.value().erase(it); --m_maxParseJobs; //We have added one when putting the document into m_documents if(!m_documents.isEmpty()) { // Only try creating one parse-job at a time, else we might iterate through thousands of files // without finding a language-support, and block the UI for a long time. // If there are more documents to parse, instantly re-try. QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection); done = true; break; } } if ( done ) break; } // Ok, enqueueing is fine because m_parseJobs contains all of the jobs now foreach (const ThreadWeaver::JobPointer& job, jobs) m_weaver.enqueue(job); m_parser->updateProgressBar(); //We don't hide the progress-bar in updateProgressBar, so it doesn't permanently flash when a document is reparsed again and again. if(m_doneParseJobs == m_maxParseJobs || (m_neededPriority == BackgroundParser::BestPriority && m_weaver.queueLength() == 0)) { emit m_parser->hideProgress(m_parser); } } ThreadWeaver::QObjectDecorator* createParseJob(const IndexedString& url, TopDUContext::Features features, const QList >& notifyWhenReady, int priority = 0) { ///FIXME: use IndexedString in the other APIs as well! Esp. for createParseJob! QUrl qUrl = url.toUrl(); auto languages = m_languageController->languagesForUrl(qUrl); foreach (const auto language, languages) { if(!language) { qCWarning(LANGUAGE) << "got zero language for" << qUrl; continue; } ParseJob* job = language->createParseJob(url); if (!job) { continue; // Language part did not produce a valid ParseJob. } job->setParsePriority(priority); job->setMinimumFeatures(features); job->setNotifyWhenReady(notifyWhenReady); ThreadWeaver::QObjectDecorator* decorator = new ThreadWeaver::QObjectDecorator(job); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::done, m_parser, &BackgroundParser::parseComplete); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::failed, m_parser, &BackgroundParser::parseComplete); QObject::connect(job, &ParseJob::progress, m_parser, &BackgroundParser::parseProgress, Qt::QueuedConnection); m_parseJobs.insert(url, decorator); ++m_maxParseJobs; // TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse) return decorator; } if(languages.isEmpty()) qCDebug(LANGUAGE) << "found no languages for url" << qUrl; else qCDebug(LANGUAGE) << "could not create parse-job for url" << qUrl; //Notify that we failed typedef QPointer Notify; foreach(const Notify& n, notifyWhenReady) if(n) QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext())); return nullptr; } void loadSettings() { ///@todo re-load settings when they have been changed! Q_ASSERT(ICore::self()->activeSession()); KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser"); // stay backwards compatible KConfigGroup oldConfig(KSharedConfig::openConfig(), "Background Parser"); #define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \ config.readEntry(entry, oldConfig.readEntry(entry, default)) m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500); m_timer.setInterval(m_delay); m_threads = 0; - bool maxThreadsOverrideOk; - const int maxThreadsOverride = qgetenv("KDEV_BACKGROUNDPARSER_MAXTHREADS").toInt(&maxThreadsOverrideOk); - if (maxThreadsOverrideOk) { - m_parser->setThreadCount(maxThreadsOverride); + if (qEnvironmentVariableIsSet("KDEV_BACKGROUNDPARSER_MAXTHREADS")) { + m_parser->setThreadCount(qEnvironmentVariableIntValue("KDEV_BACKGROUNDPARSER_MAXTHREADS")); } else { m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", QThread::idealThreadCount())); } resume(); if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) { m_parser->enableProcessing(); } else { m_parser->disableProcessing(); } } void suspend() { qCDebug(LANGUAGE) << "Suspending background parser"; bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (s) { // Already suspending qCWarning(LANGUAGE) << "Already suspended or suspending"; return; } m_timer.stop(); m_weaver.suspend(); } void resume() { bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (m_timer.isActive() && !s) { // Not suspending return; } m_timer.start(m_delay); m_weaver.resume(); } BackgroundParser *m_parser; ILanguageController* m_languageController; //Current parse-job that is executed in the additional thread QPointer specialParseJob; QTimer m_timer; int m_delay; int m_threads; bool m_shuttingDown; struct DocumentParseTarget { QPointer notifyWhenReady; int priority; TopDUContext::Features features; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; struct DocumentParsePlan { QSet targets; ParseJob::SequentialProcessingFlags sequentialProcessingFlags() const { //Pick the strictest possible flags ParseJob::SequentialProcessingFlags ret = ParseJob::IgnoresSequentialProcessing; foreach(const DocumentParseTarget &target, targets) { ret |= target.sequentialProcessingFlags; } return ret; } int priority() const { //Pick the best priority int ret = BackgroundParser::WorstPriority; foreach(const DocumentParseTarget &target, targets) { if(target.priority < ret) { ret = target.priority; } } return ret; } TopDUContext::Features features() const { //Pick the best features TopDUContext::Features ret = (TopDUContext::Features)0; foreach(const DocumentParseTarget &target, targets) { ret = (TopDUContext::Features) (ret | target.features); } return ret; } QList > notifyWhenReady() const { QList > ret; foreach(const DocumentParseTarget &target, targets) if(target.notifyWhenReady) ret << target.notifyWhenReady; return ret; } }; // A list of documents that are planned to be parsed, and their priority QHash m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // A change tracker for each managed document QHash m_managed; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; // Projects currently in progress of loading QSet m_loadingProjects; ThreadWeaver::Queue m_weaver; QMutex m_mutex; int m_maxParseJobs; int m_doneParseJobs; QHash m_jobProgress; int m_neededPriority; //The minimum priority needed for processed jobs }; inline uint qHash(const BackgroundParserPrivate::DocumentParseTarget& target) { return target.features * 7 + target.priority * 13 + target.sequentialProcessingFlags * 17 + reinterpret_cast(target.notifyWhenReady.data()); }; BackgroundParser::BackgroundParser(ILanguageController *languageController) : QObject(languageController), d(new BackgroundParserPrivate(this, languageController)) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), &IDocumentController::documentLoaded, this, &BackgroundParser::documentLoaded); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &BackgroundParser::documentUrlChanged); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &BackgroundParser::documentClosed); connect(ICore::self(), &ICore::aboutToShutdown, this, &BackgroundParser::aboutToQuit); bool connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectAboutToBeOpened, this, &BackgroundParser::projectAboutToBeOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &BackgroundParser::projectOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpeningAborted, this, &BackgroundParser::projectOpeningAborted); Q_ASSERT(connected); Q_UNUSED(connected); } void BackgroundParser::aboutToQuit() { d->m_shuttingDown = true; } BackgroundParser::~BackgroundParser() { delete d; } QString BackgroundParser::statusName() const { return i18n("Background Parser"); } void BackgroundParser::loadSettings() { d->loadSettings(); } void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text) { Q_UNUSED(text) d->m_jobProgress[job] = value; updateProgressBar(); } void BackgroundParser::revertAllRequests(QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); for(QHash::iterator it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); foreach ( const BackgroundParserPrivate::DocumentParseTarget& target, (*it).targets ) { if ( notifyWhenReady && target.notifyWhenReady.data() == notifyWhenReady ) { (*it).targets.remove(target); } } if((*it).targets.isEmpty()) { it = d->m_documents.erase(it); --d->m_maxParseJobs; continue; } d->m_documentsForPriority[it.value().priority()].insert(it.key()); ++it; } } void BackgroundParser::addDocument(const IndexedString& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady, ParseJob::SequentialProcessingFlags flags) { // qCDebug(LANGUAGE) << "BackgroundParser::addDocument" << url.toUrl(); Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); { BackgroundParserPrivate::DocumentParseTarget target; target.priority = priority; target.features = features; target.sequentialProcessingFlags = flags; target.notifyWhenReady = QPointer(notifyWhenReady); QHash::iterator it = d->m_documents.find(url); if (it != d->m_documents.end()) { //Update the stored plan d->m_documentsForPriority[it.value().priority()].remove(url); it.value().targets << target; d->m_documentsForPriority[it.value().priority()].insert(url); }else{ // qCDebug(LANGUAGE) << "BackgroundParser::addDocument: queuing" << cleanedUrl; d->m_documents[url].targets << target; d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); ++d->m_maxParseJobs; //So the progress-bar waits for this document } d->startTimerThreadSafe(); } } void BackgroundParser::removeDocument(const IndexedString& url, QObject* notifyWhenReady) { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(const BackgroundParserPrivate::DocumentParseTarget& target, d->m_documents[url].targets) { if(target.notifyWhenReady.data() == notifyWhenReady) { d->m_documents[url].targets.remove(target); } } if(d->m_documents[url].targets.isEmpty()) { d->m_documents.remove(url); --d->m_maxParseJobs; }else{ //Insert with an eventually different priority d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); } } } void BackgroundParser::parseDocuments() { if (!d->m_loadingProjects.empty()) { startTimer(); return; } QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(const ThreadWeaver::JobPointer& job) { auto decorator = dynamic_cast(job.data()); Q_ASSERT(decorator); ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); emit parseJobFinished(parseJob); { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document()); d->m_jobProgress.remove(parseJob); ++d->m_doneParseJobs; updateProgressBar(); } //Continue creating more parse-jobs QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection); } void BackgroundParser::disableProcessing() { setNeededPriority(BestPriority); } void BackgroundParser::enableProcessing() { setNeededPriority(WorstPriority); } int BackgroundParser::priorityForDocument(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents[url].priority(); } bool BackgroundParser::isQueued(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents.contains(url); } int BackgroundParser::queuedCount() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.count(); } bool BackgroundParser::isIdle() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.isEmpty() && d->m_weaver.isIdle(); } void BackgroundParser::setNeededPriority(int priority) { QMutexLocker lock(&d->m_mutex); d->m_neededPriority = priority; d->startTimerThreadSafe(); } void BackgroundParser::abortAllJobs() { qCDebug(LANGUAGE) << "Aborting all parse jobs"; d->m_weaver.requestAbort(); } void BackgroundParser::suspend() { d->suspend(); emit hideProgress(this); } void BackgroundParser::resume() { d->resume(); updateProgressBar(); } void BackgroundParser::updateProgressBar() { if (d->m_doneParseJobs >= d->m_maxParseJobs) { if(d->m_doneParseJobs > d->m_maxParseJobs) { qCDebug(LANGUAGE) << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs; } d->m_doneParseJobs = 0; d->m_maxParseJobs = 0; } else { float additionalProgress = 0; for(QHash::const_iterator it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it) additionalProgress += *it; emit showProgress(this, 0, d->m_maxParseJobs*1000, (additionalProgress + d->m_doneParseJobs)*1000); } } ParseJob* BackgroundParser::parseJobForDocument(const IndexedString& document) const { Q_ASSERT(isValidURL(document)); QMutexLocker lock(&d->m_mutex); auto decorator = d->m_parseJobs.value(document); return decorator ? dynamic_cast(decorator->job()) : nullptr; } void BackgroundParser::setThreadCount(int threadCount) { if (d->m_threads != threadCount) { d->m_threads = threadCount; d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing } } int BackgroundParser::threadCount() const { return d->m_threads; } void BackgroundParser::setDelay(int miliseconds) { if (d->m_delay != miliseconds) { d->m_delay = miliseconds; d->m_timer.setInterval(d->m_delay); } } QList< IndexedString > BackgroundParser::managedDocuments() { QMutexLocker l(&d->m_mutex); return d->m_managed.keys(); } DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const { if (url.isEmpty()) { // this happens e.g. when setting the final location of a problem that is not // yet associated with a top ctx. return 0; } Q_ASSERT(isValidURL(url)); QMutexLocker l(&d->m_mutex); return d->m_managed.value(url, 0); } void BackgroundParser::documentClosed(IDocument* document) { QMutexLocker l(&d->m_mutex); if(document->textDocument()) { KTextEditor::Document* textDocument = document->textDocument(); if(!d->m_managedTextDocumentUrls.contains(textDocument)) return; // Probably the document had an invalid url, and thus it wasn't added to the background parser Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument)); IndexedString url(d->m_managedTextDocumentUrls[textDocument]); Q_ASSERT(d->m_managed.contains(url)); qCDebug(LANGUAGE) << "removing" << url.str() << "from background parser"; delete d->m_managed[url]; d->m_managedTextDocumentUrls.remove(textDocument); d->m_managed.remove(url); } } void BackgroundParser::documentLoaded( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument() && document->textDocument()->url().isValid()) { KTextEditor::Document* textDocument = document->textDocument(); IndexedString url(document->url()); // Some debugging because we had issues with this if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument) { qCDebug(LANGUAGE) << "Got redundant documentLoaded from" << document->url() << textDocument; return; } qCDebug(LANGUAGE) << "Creating change tracker for " << document->url(); Q_ASSERT(!d->m_managed.contains(url)); Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument)); d->m_managedTextDocumentUrls[textDocument] = url; d->m_managed.insert(url, new DocumentChangeTracker(textDocument)); }else{ qCDebug(LANGUAGE) << "NOT creating change tracker for" << document->url(); } } void BackgroundParser::documentUrlChanged(IDocument* document) { documentClosed(document); // Only call documentLoaded if the file wasn't renamed to a filename that is already tracked. if(document->textDocument() && !d->m_managed.contains(IndexedString(document->textDocument()->url()))) documentLoaded(document); } void BackgroundParser::startTimer() { d->m_timer.start(d->m_delay); } void BackgroundParser::projectAboutToBeOpened(IProject* project) { d->m_loadingProjects.insert(project); } void BackgroundParser::projectOpened(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::projectOpeningAborted(IProject* project) { d->m_loadingProjects.remove(project); } } Q_DECLARE_TYPEINFO(KDevelop::BackgroundParserPrivate::DocumentParseTarget, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::BackgroundParserPrivate::DocumentParsePlan, Q_MOVABLE_TYPE); diff --git a/language/codegen/utilities.cpp b/language/codegen/utilities.cpp index 3789e0222f..f3a71e23b5 100644 --- a/language/codegen/utilities.cpp +++ b/language/codegen/utilities.cpp @@ -1,131 +1,131 @@ /* Copyright 2009 Ramón Zarazúa This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "utilities.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include namespace KDevelop { namespace CodeGenUtils { IdentifierValidator::IdentifierValidator( DUContext * context) : QValidator(0), m_context(context) { } IdentifierValidator::~IdentifierValidator() { } QValidator::State IdentifierValidator::validate (QString & input, int &) const { //I can't figure out why it wouln't compile when I tried to use Identifier identifier(); Identifier identifier = Identifier(IndexedString(input)); if(identifier.isUnique()) return Acceptable; DUChainReadLocker lock(DUChain::lock(), 10); return m_context->findLocalDeclarations(identifier, CursorInRevision::invalid(), 0, AbstractType::Ptr(), DUContext::NoFiltering).empty() ? Acceptable : Invalid; } IndexedString fetchImplementationFileForClass(const Declaration & targetClass) { DUChainReadLocker lock(DUChain::lock()); qCDebug(LANGUAGE) << "Looking for implementation file for class:" << targetClass.identifier().toString(); DUContext * context = targetClass.internalContext(); //If this declaration is not a user defined type, then ignore and return empty file if(targetClass.kind() != Declaration::Type) return IndexedString(); //If this is a forward declaration attempt to resolve it. const Declaration * realClass = &targetClass; if(const ForwardDeclaration * forward = dynamic_cast(realClass)) { if(!(realClass = forward->resolve(context->topContext()))) return IndexedString(); context = realClass->internalContext(); } QVector declarations = context->localDeclarations(); QMap implementationsInFile; foreach(Declaration * decl, declarations) { ///@todo check for static variable instantiation as well if(ClassFunctionDeclaration * classFun = dynamic_cast(decl)) if(FunctionDefinition * def = FunctionDefinition::definition(classFun)) { qCDebug(LANGUAGE) << "Definition For declaration in:" << def->url().toUrl(); ++implementationsInFile[def->url()]; } } QMultiMap sorter; foreach(const IndexedString& file, implementationsInFile.keys()) sorter.insert(implementationsInFile[file], file); QList sortedFiles = sorter.values(); //If there are no methods, then just return the file the declaration is in if(sortedFiles.empty()) return context->url(); if(sortedFiles.size() == 1) return sortedFiles[0]; - if(sorter.values(sorter.end().key()).size() > 1) + const QList tiedFiles = sorter.values(sorter.end().key()); + if(tiedFiles.size() > 1) { //Return the file that has the most uses - QList tiedFiles = sorter.values(sorter.end().key()); QMap > uses = realClass->uses(); IndexedString mostUsesFile; unsigned int mostUses = 0; foreach(const IndexedString& currentFile, tiedFiles) if(static_cast(uses[currentFile].size()) > mostUses) { mostUses = uses[currentFile].size(); mostUsesFile = currentFile; } return mostUsesFile; } else return sortedFiles.back(); } } } diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index d3698843f3..3967337116 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1746 +1,1749 @@ /* This is part of KDevelop Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain.h" #include "duchainlock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "../interfaces/icodehighlighting.h" #include "../backgroundparser/backgroundparser.h" #include "util/debug.h" #include "language-features.h" #include "topducontext.h" #include "topducontextdata.h" #include "topducontextdynamicdata.h" #include "parsingenvironment.h" #include "declaration.h" #include "definitions.h" #include "duchainutils.h" #include "use.h" #include "uses.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "persistentsymboltable.h" #include "serialization/itemrepository.h" #include "waitforupdate.h" #include "importers.h" #if HAVE_MALLOC_TRIM #include "malloc.h" #endif namespace { //Additional "soft" cleanup steps that are done before the actual cleanup. //During "soft" cleanup, the consistency is not guaranteed. The repository is //marked to be updating during soft cleanup, so if kdevelop crashes, it will be cleared. //The big advantage of the soft cleanup steps is, that the duchain is always only locked for //short times, which leads to no lockup in the UI. const int SOFT_CLEANUP_STEPS = 1; const uint cleanupEverySeconds = 200; ///Approximate maximum count of top-contexts that are checked during final cleanup const uint maxFinalCleanupCheckContexts = 2000; const uint minimumFinalCleanupCheckContextsPercentage = 10; //Check at least n% of all top-contexts during cleanup //Set to true as soon as the duchain is deleted } namespace KDevelop { bool DUChain::m_deleted = false; ///Must be locked through KDevelop::SpinLock before using chainsByIndex ///This lock should be locked only for very short times QMutex DUChain::chainsByIndexLock; std::vector DUChain::chainsByIndex; //This thing is not actually used, but it's needed for compiling DEFINE_LIST_MEMBER_HASH(EnvironmentInformationListItem, items, uint) //An entry for the item-repository that holds some meta-data. Behind this entry, the actual ParsingEnvironmentFileData is stored. class EnvironmentInformationItem { public: EnvironmentInformationItem(uint topContext, uint size) : m_topContext(topContext), m_size(size) { } ~EnvironmentInformationItem() { } unsigned int hash() const { return m_topContext; } unsigned int itemSize() const { return sizeof(*this) + m_size; } uint m_topContext; uint m_size;//Size of the data behind, that holds the actual item }; struct ItemRepositoryIndexHash { uint operator()(unsigned int __x) const { return 173*(__x>>2) + 11 * (__x >> 16); } }; class EnvironmentInformationRequest { public: ///This constructor should only be used for lookup EnvironmentInformationRequest(uint topContextIndex) : m_file(0), m_index(topContextIndex) { } EnvironmentInformationRequest(const ParsingEnvironmentFile* file) : m_file(file), m_index(file->indexedTopContext().index()) { } enum { AverageSize = 32 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_index; } uint itemSize() const { return sizeof(EnvironmentInformationItem) + DUChainItemSystem::self().dynamicSize(*m_file->d_func()); } void createItem(EnvironmentInformationItem* item) const { new (item) EnvironmentInformationItem(m_index, DUChainItemSystem::self().dynamicSize(*m_file->d_func())); Q_ASSERT(m_file->d_func()->m_dynamic); DUChainBaseData* data = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); DUChainItemSystem::self().copy(*m_file->d_func(), *data, true); Q_ASSERT(data->m_range == m_file->d_func()->m_range); Q_ASSERT(data->classId == m_file->d_func()->classId); Q_ASSERT(data->m_dynamic == false); } static void destroy(EnvironmentInformationItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationItem(); //We don't need to call the destructor, because that's done in DUChainBase::makeDynamic() //We just need to make sure that every environment-file is dynamic when it's deleted // DUChainItemSystem::self().callDestructor((DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))); } static bool persistent(const EnvironmentInformationItem* ) { //Cleanup done separately return true; } bool equals(const EnvironmentInformationItem* item) const { return m_index == item->m_topContext; } const ParsingEnvironmentFile* m_file; uint m_index; }; ///A list of environment-information/top-contexts mapped to a file-name class EnvironmentInformationListItem { public: EnvironmentInformationListItem() { initializeAppendedLists(true); } EnvironmentInformationListItem(const EnvironmentInformationListItem& rhs, bool dynamic = true) { initializeAppendedLists(dynamic); m_file = rhs.m_file; copyListsFrom(rhs); } ~EnvironmentInformationListItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return m_file.hash(); } unsigned short int itemSize() const { return dynamicSize(); } IndexedString m_file; uint classSize() const { return sizeof(*this); } START_APPENDED_LISTS(EnvironmentInformationListItem); ///Contains the index of each contained environment-item APPENDED_LIST_FIRST(EnvironmentInformationListItem, uint, items); END_APPENDED_LISTS(EnvironmentInformationListItem, items); }; class EnvironmentInformationListRequest { public: ///This constructor should only be used for lookup EnvironmentInformationListRequest(const IndexedString& file) : m_file(file), m_item(0) { } ///This is used to actually construct the information in the repository EnvironmentInformationListRequest(const IndexedString& file, const EnvironmentInformationListItem& item) : m_file(file), m_item(&item) { } enum { AverageSize = 160 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_file.hash(); } uint itemSize() const { return m_item->itemSize(); } void createItem(EnvironmentInformationListItem* item) const { Q_ASSERT(m_item->m_file == m_file); new (item) EnvironmentInformationListItem(*m_item, false); } static void destroy(EnvironmentInformationListItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationListItem(); } static bool persistent(const EnvironmentInformationListItem*) { //Cleanup is done separately return true; } bool equals(const EnvironmentInformationListItem* item) const { return m_file == item->m_file; } IndexedString m_file; const EnvironmentInformationListItem* m_item; }; class DUChainPrivate; static DUChainPrivate* duChainPrivateSelf = 0; class DUChainPrivate { class CleanupThread : public QThread { public: CleanupThread(DUChainPrivate* data) : m_stopRunning(false), m_data(data) { } void stopThread() { { QMutexLocker lock(&m_waitMutex); m_stopRunning = true; m_wait.wakeAll(); //Wakes the thread up, so it notices it should exit } wait(); } private: void run() override { while(1) { for(uint s = 0; s < cleanupEverySeconds; ++s) { if(m_stopRunning) break; QMutexLocker lock(&m_waitMutex); m_wait.wait(&m_waitMutex, 1000); } if(m_stopRunning) break; //Just to make sure the cache is cleared periodically ModificationRevisionSet::clearCache(); m_data->doMoreCleanup(SOFT_CLEANUP_STEPS, TryLock); if(m_stopRunning) break; } } bool m_stopRunning; QWaitCondition m_wait; QMutex m_waitMutex; DUChainPrivate* m_data; }; public: DUChainPrivate() : m_chainsMutex(QMutex::Recursive), m_cleanupMutex(QMutex::Recursive), instance(0), m_cleanupDisabled(false), m_destroyed(false), m_environmentListInfo(QStringLiteral("Environment Lists")), m_environmentInfo(QStringLiteral("Environment Information")) { #if defined(TEST_NO_CLEANUP) m_cleanupDisabled = true; #endif duChainPrivateSelf = this; qRegisterMetaType("KDevelop::DUChainBasePointer"); qRegisterMetaType("KDevelop::DUContextPointer"); qRegisterMetaType("KDevelop::TopDUContextPointer"); qRegisterMetaType("KDevelop::DeclarationPointer"); qRegisterMetaType("KDevelop::FunctionDeclarationPointer"); qRegisterMetaType("KDevelop::IndexedString"); qRegisterMetaType("KDevelop::IndexedTopDUContext"); qRegisterMetaType("KDevelop::ReferencedTopDUContext"); instance = new DUChain(); m_cleanup = new CleanupThread(this); m_cleanup->start(); DUChain::m_deleted = false; ///Loading of some static data: { ///@todo Solve this more duchain-like QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::ReadOnly); ///FIXME: ugh, so ugly ParsingEnvironmentFile::m_staticData = reinterpret_cast( new char[sizeof(StaticParsingEnvironmentData)]); if(opened) { qCDebug(LANGUAGE) << "reading parsing-environment static data"; //Read f.read((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); }else{ qCDebug(LANGUAGE) << "creating new parsing-environment static data"; //Initialize new (ParsingEnvironmentFile::m_staticData) StaticParsingEnvironmentData(); } } ///Read in the list of available top-context indices { QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::ReadOnly); if(opened) { Q_ASSERT( (f.size() % sizeof(uint)) == 0); m_availableTopContextIndices.resize(f.size()/(int)sizeof(uint)); f.read((char*)m_availableTopContextIndices.data(), f.size()); } } } ~DUChainPrivate() { qCDebug(LANGUAGE) << "Destroying"; DUChain::m_deleted = true; m_cleanup->stopThread(); delete m_cleanup; delete instance; } void clear() { if(!m_cleanupDisabled) doMoreCleanup(); DUChainWriteLocker writeLock(DUChain::lock()); QMutexLocker l(&m_chainsMutex); - foreach(TopDUContext* top, m_chainsByUrl.values()) + foreach(TopDUContext* top, m_chainsByUrl) removeDocumentChainFromMemory(top); m_indexEnvironmentInformations.clear(); m_fileEnvironmentInformations.clear(); Q_ASSERT(m_fileEnvironmentInformations.isEmpty()); Q_ASSERT(m_chainsByUrl.isEmpty()); } ///DUChain must be write-locked ///Also removes from the environment-manager if the top-context is not on disk void removeDocumentChainFromMemory(TopDUContext* context) { QMutexLocker l(&m_chainsMutex); { QMutexLocker l(&m_referenceCountsMutex); if(m_referenceCounts.contains(context)) { //This happens during shutdown, since everything is unloaded qCDebug(LANGUAGE) << "removed a top-context that was reference-counted:" << context->url().str() << context->ownIndex(); m_referenceCounts.remove(context); } } uint index = context->ownIndex(); // qCDebug(LANGUAGE) << "duchain: removing document" << context->url().str(); Q_ASSERT(hasChainForIndex(index)); Q_ASSERT(m_chainsByUrl.contains(context->url(), context)); m_chainsByUrl.remove(context->url(), context); if(!context->isOnDisk()) instance->removeFromEnvironmentManager(context); l.unlock(); //DUChain is write-locked, so we can do whatever we want on the top-context, including deleting it context->deleteSelf(); l.relock(); Q_ASSERT(hasChainForIndex(index)); QMutexLocker lock(&DUChain::chainsByIndexLock); DUChain::chainsByIndex[index] = 0; } ///Must be locked before accessing content of this class. ///Should be released during expensive disk-operations and such. QMutex m_chainsMutex; QMutex m_cleanupMutex; CleanupThread* m_cleanup; DUChain* instance; DUChainLock lock; QMultiMap m_chainsByUrl; //Must be locked before accessing m_referenceCounts QMutex m_referenceCountsMutex; QHash m_referenceCounts; Definitions m_definitions; Uses m_uses; QSet m_loading; bool m_cleanupDisabled; //List of available top-context indices, protected by m_chainsMutex QVector m_availableTopContextIndices; ///Used to keep alive the top-context that belong to documents loaded in the editor QSet m_openDocumentContexts; bool m_destroyed; ///The item must not be stored yet ///m_chainsMutex should not be locked, since this can trigger I/O void addEnvironmentInformation(ParsingEnvironmentFilePointer info) { Q_ASSERT(!findInformation(info->indexedTopContext().index())); Q_ASSERT(m_environmentInfo.findIndex(info->indexedTopContext().index()) == 0); QMutexLocker lock(&m_chainsMutex); m_fileEnvironmentInformations.insert(info->url(), info); m_indexEnvironmentInformations.insert(info->indexedTopContext().index(), info); Q_ASSERT(info->d_func()->classId); } ///The item must be managed currently ///m_chainsMutex does not need to be locked void removeEnvironmentInformation(ParsingEnvironmentFilePointer info) { info->makeDynamic(); //By doing this, we make sure the data is actually being destroyed in the destructor bool removed = false; bool removed2 = false; { QMutexLocker lock(&m_chainsMutex); removed = m_fileEnvironmentInformations.remove(info->url(), info); removed2 = m_indexEnvironmentInformations.remove(info->indexedTopContext().index()); } { //Remove it from the environment information lists if it was there QMutexLocker lock(m_environmentListInfo.mutex()); uint index = m_environmentListInfo.findIndex(info->url()); if(index) { EnvironmentInformationListItem item(*m_environmentListInfo.itemFromIndex(index)); if(item.itemsList().removeOne(info->indexedTopContext().index())) { m_environmentListInfo.deleteItem(index); if(!item.itemsList().isEmpty()) m_environmentListInfo.index(EnvironmentInformationListRequest(info->url(), item)); } } } QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(info->indexedTopContext().index()); if(index) { m_environmentInfo.deleteItem(index); } Q_UNUSED(removed); Q_UNUSED(removed2); Q_ASSERT(index || (removed && removed2)); Q_ASSERT(!findInformation(info->indexedTopContext().index())); } ///m_chainsMutex should _not_ be locked, because this may trigger I/O QList getEnvironmentInformation(IndexedString url) { QList ret; uint listIndex = m_environmentListInfo.findIndex(url); if(listIndex) { KDevVarLengthArray topContextIndices; { //First store all the possible intices into the KDevVarLengthArray, so we can unlock the mutex before processing them. QMutexLocker lock(m_environmentListInfo.mutex()); //Lock the mutex to make sure the item isn't changed while it's being iterated const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(listIndex); FOREACH_FUNCTION(uint topContextIndex, item->items) topContextIndices << topContextIndex; } //Process the indices in a separate step after copying them from the array, so we don't need m_environmentListInfo.mutex locked, //and can call loadInformation(..) safely, which else might lead to a deadlock. foreach (uint topContextIndex, topContextIndices) { QExplicitlySharedDataPointer< ParsingEnvironmentFile > p = ParsingEnvironmentFilePointer(loadInformation(topContextIndex)); if(p) { ret << p; }else{ qCDebug(LANGUAGE) << "Failed to load enviromment-information for" << TopDUContextDynamicData::loadUrl(topContextIndex).str(); } } } QMutexLocker l(&m_chainsMutex); //Add those information that have not been added to the stored lists yet foreach(const ParsingEnvironmentFilePointer& file, m_fileEnvironmentInformations.values(url)) if(!ret.contains(file)) ret << file; return ret; } ///Must be called _without_ the chainsByIndex spin-lock locked static inline bool hasChainForIndex(uint index) { QMutexLocker lock(&DUChain::chainsByIndexLock); return (DUChain::chainsByIndex.size() > index) && DUChain::chainsByIndex[index]; } ///Must be called _without_ the chainsByIndex spin-lock locked. Returns the top-context if it is loaded. static inline TopDUContext* readChainForIndex(uint index) { QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() > index) return DUChain::chainsByIndex[index]; else return 0; } ///Makes sure that the chain with the given index is loaded ///@warning m_chainsMutex must NOT be locked when this is called void loadChain(uint index, QSet& loaded) { QMutexLocker l(&m_chainsMutex); if(!hasChainForIndex(index)) { if(m_loading.contains(index)) { //It's probably being loaded by another thread. So wait until the load is ready while(m_loading.contains(index)) { l.unlock(); qCDebug(LANGUAGE) << "waiting for another thread to load index" << index; QThread::usleep(50000); l.relock(); } loaded.insert(index); return; } m_loading.insert(index); loaded.insert(index); l.unlock(); qCDebug(LANGUAGE) << "loading top-context" << index; TopDUContext* chain = TopDUContextDynamicData::load(index); if(chain) { chain->setParsingEnvironmentFile(loadInformation(chain->ownIndex())); if(!chain->usingImportsCache()) { //Eventually also load all the imported chains, so the import-structure is built foreach(const DUContext::Import &import, chain->DUContext::importedParentContexts()) { if(!loaded.contains(import.topContextIndex())) { loadChain(import.topContextIndex(), loaded); } } } chain->rebuildDynamicImportStructure(); chain->setInDuChain(true); instance->addDocumentChain(chain); } l.relock(); m_loading.remove(index); } } ///Stores all environment-information ///Also makes sure that all information that stays is referenced, so it stays alive. ///@param atomic If this is false, the write-lock will be released time by time void storeAllInformation(bool atomic, DUChainWriteLocker& locker) { uint cnt = 0; QList urls; { QMutexLocker lock(&m_chainsMutex); urls += m_fileEnvironmentInformations.keys(); } foreach(const IndexedString &url, urls) { QList check; { QMutexLocker lock(&m_chainsMutex); check = m_fileEnvironmentInformations.values(url); } foreach(ParsingEnvironmentFilePointer file, check) { EnvironmentInformationRequest req(file.data()); QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(req); if(file->d_func()->isDynamic()) { //This item has been changed, or isn't in the repository yet //Eventually remove an old entry if(index) m_environmentInfo.deleteItem(index); //Add the new entry to the item repository index = m_environmentInfo.index(req); Q_ASSERT(index); EnvironmentInformationItem* item = const_cast(m_environmentInfo.itemFromIndex(index)); DUChainBaseData* theData = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); Q_ASSERT(theData->m_range == file->d_func()->m_range); Q_ASSERT(theData->m_dynamic == false); Q_ASSERT(theData->classId == file->d_func()->classId); file->setData( theData ); ++cnt; }else{ m_environmentInfo.itemFromIndex(index); //Prevent unloading of the data, by accessing the item } } ///We must not release the lock while holding a reference to a ParsingEnvironmentFilePointer, else we may miss the deletion of an ///information, and will get crashes. if(!atomic && (cnt % 100 == 0)) { //Release the lock on a regular basis locker.unlock(); locker.lock(); } storeInformationList(url); //Access the data in the repository, so the bucket isn't unloaded uint index = m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)); if(index) { m_environmentListInfo.itemFromIndex(index); }else{ QMutexLocker lock(&m_chainsMutex); qCDebug(LANGUAGE) << "Did not find stored item for" << url.str() << "count:" << m_fileEnvironmentInformations.values(url); } if(!atomic) { locker.unlock(); locker.lock(); } } } QMutex& cleanupMutex() { return m_cleanupMutex; } /// defines how we interact with the ongoing language parse jobs enum LockFlag { /// no locking required, only used when we locked previously NoLock = 0, /// lock all parse jobs and block until we succeeded. required at shutdown BlockingLock = 1, /// only try to lock and abort on failure, good for the intermittent cleanups TryLock = 2, }; ///@param retries When this is nonzero, then doMoreCleanup will do the specified amount of cycles ///doing the cleanup without permanently locking the du-chain. During these steps the consistency ///of the disk-storage is not guaranteed, but only few changes will be done during these steps, ///so the final step where the duchain is permanently locked is much faster. void doMoreCleanup(int retries = 0, LockFlag lockFlag = BlockingLock) { if(m_cleanupDisabled) return; //This mutex makes sure that there's never 2 threads at he same time trying to clean up QMutexLocker lockCleanupMutex(&cleanupMutex()); if(m_destroyed || m_cleanupDisabled) return; Q_ASSERT(!instance->lock()->currentThreadHasReadLock() && !instance->lock()->currentThreadHasWriteLock()); DUChainWriteLocker writeLock(instance->lock()); //This is used to stop all parsing before starting to do the cleanup. This way less happens during the //soft cleanups, and we have a good chance that during the "hard" cleanup only few data has to be written. QList locked; if (lockFlag != NoLock) { QList languages; if (ICore* core = ICore::self()) if (ILanguageController* lc = core->languageController()) languages = lc->loadedLanguages(); writeLock.unlock(); //Here we wait for all parsing-threads to stop their processing foreach(const auto language, languages) { if (lockFlag == TryLock) { if (!language->parseLock()->tryLockForWrite()) { qCDebug(LANGUAGE) << "Aborting cleanup because language plugin is still parsing:" << language->name(); // some language is still parsing, don't interfere with the cleanup foreach(auto* lock, locked) { lock->unlock(); } return; } } else { language->parseLock()->lockForWrite(); } locked << language->parseLock(); } writeLock.lock(); globalItemRepositoryRegistry().lockForWriting(); qCDebug(LANGUAGE) << "starting cleanup"; } QTime startTime = QTime::currentTime(); PersistentSymbolTable::self().clearCache(); storeAllInformation(!retries, writeLock); //Puts environment-information into a repository //We don't need to increase the reference-count, since the cleanup-mutex is locked QSet workOnContexts; { QMutexLocker l(&m_chainsMutex); - foreach(TopDUContext* top, m_chainsByUrl.values()) { + workOnContexts.reserve(m_chainsByUrl.size()); + foreach(TopDUContext* top, m_chainsByUrl) { workOnContexts << top; Q_ASSERT(hasChainForIndex(top->ownIndex())); } } foreach(TopDUContext* context, workOnContexts) { context->m_dynamicData->store(); if(retries) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between QThread::usleep(500); writeLock.lock(); } } //Unload all top-contexts that don't have a reference-count and that are not imported by a referenced one QSet unloadedNames; bool unloadedOne = true; bool unloadAllUnreferenced = !retries; //Now unload contexts, but only ones that are not imported by any other currently loaded context //The complication: Since during the lock-break new references may be added, we must never keep //the du-chain in an invalid state. Thus we can only unload contexts that are not imported by any //currently loaded contexts. In case of loops, we have to unload everything at once. while(unloadedOne) { unloadedOne = false; int hadUnloadable = 0; unloadContexts: foreach(TopDUContext* unload, workOnContexts) { bool hasReference = false; { QMutexLocker l(&m_referenceCountsMutex); //Test if the context is imported by a referenced one foreach(TopDUContext* context, m_referenceCounts.keys()) { if(context == unload || context->imports(unload, CursorInRevision())) { workOnContexts.remove(unload); hasReference = true; } } } if(!hasReference) ++hadUnloadable; //We have found a context that is not referenced else continue; //This context is referenced bool isImportedByLoaded = !unload->loadedImporters().isEmpty(); //If we unload a context that is imported by other contexts, we create a bad loaded state if(isImportedByLoaded && !unloadAllUnreferenced) continue; unloadedNames.insert(unload->url()); //Since we've released the write-lock in between, we've got to call store() again to be sure that none of the data is dynamic //If nothing has changed, it is only a low-cost call. unload->m_dynamicData->store(); Q_ASSERT(!unload->d_func()->m_dynamic); removeDocumentChainFromMemory(unload); workOnContexts.remove(unload); unloadedOne = true; if(!unloadAllUnreferenced) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between QThread::usleep(500); writeLock.lock(); } } if(hadUnloadable && !unloadedOne) { Q_ASSERT(!unloadAllUnreferenced); //This can happen in case of loops. We have o unload everything at one time. qCDebug(LANGUAGE) << "found" << hadUnloadable << "unloadable contexts, but could not unload separately. Unloading atomically."; unloadAllUnreferenced = true; hadUnloadable = 0; //Reset to 0, so we cannot loop forever goto unloadContexts; } } if(retries == 0) { QMutexLocker lock(&m_chainsMutex); //Do this atomically, since we must be sure that _everything_ is already saved for(QMultiMap::iterator it = m_fileEnvironmentInformations.begin(); it != m_fileEnvironmentInformations.end(); ) { ParsingEnvironmentFile* f = it->data(); Q_ASSERT(f->d_func()->classId); if(f->ref.load() == 1) { Q_ASSERT(!f->d_func()->isDynamic()); //It cannot be dynamic, since we have stored before //The ParsingEnvironmentFilePointer is only referenced once. This means that it does not belong to any //loaded top-context, so just remove it to save some memory and processing time. ///@todo use some kind of timeout before removing it = m_fileEnvironmentInformations.erase(it); }else{ ++it; } } } if(retries) writeLock.unlock(); //This must be the last step, due to the on-disk reference counting globalItemRepositoryRegistry().store(); //Stores all repositories { //Store the static parsing-environment file data ///@todo Solve this more elegantly, using a general mechanism to store static duchain-like data Q_ASSERT(ParsingEnvironmentFile::m_staticData); QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); } ///Write out the list of available top-context indices { QMutexLocker lock(&m_chainsMutex); QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)m_availableTopContextIndices.data(), m_availableTopContextIndices.size() * sizeof(uint)); } if(retries) { doMoreCleanup(retries-1, NoLock); writeLock.lock(); } if(lockFlag != NoLock) { globalItemRepositoryRegistry().unlockForWriting(); int elapsedSeconds = startTime.secsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "seconds spent doing cleanup: " << elapsedSeconds << "top-contexts still open:" << m_chainsByUrl.size(); } if(!retries) { int elapesedMilliSeconds = startTime.msecsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "milliseconds spent doing cleanup with locked duchain: " << elapesedMilliSeconds; } foreach(QReadWriteLock* lock, locked) lock->unlock(); #if HAVE_MALLOC_TRIM // trim unused memory but keep a pad buffer of about 50 MB // this can greatly decrease the perceived memory consumption of kdevelop // see: https://sourceware.org/bugzilla/show_bug.cgi?id=14827 malloc_trim(50 * 1024 * 1024); #endif } ///Checks whether the information is already loaded. ParsingEnvironmentFile* findInformation(uint topContextIndex) { QMutexLocker lock(&m_chainsMutex); QHash::iterator it = m_indexEnvironmentInformations.find(topContextIndex); if(it != m_indexEnvironmentInformations.end()) return (*it).data(); return 0; } ///Loads/gets the environment-information for the given top-context index, or returns zero if none exists ///@warning m_chainsMutex should NOT be locked when this is called, because it triggers I/O ///@warning no other mutexes should be locked, as that may lead to a dedalock ParsingEnvironmentFile* loadInformation(uint topContextIndex) { ParsingEnvironmentFile* alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; //Step two: Check if it is on disk, and if is, load it uint dataIndex = m_environmentInfo.findIndex(EnvironmentInformationRequest(topContextIndex)); if(!dataIndex) { //No environment-information stored for this top-context return 0; } const EnvironmentInformationItem& item(*m_environmentInfo.itemFromIndex(dataIndex)); QMutexLocker lock(&m_chainsMutex); //Due to multi-threading, we must do this check after locking the mutex, so we can be sure we don't create the same item twice at the same time alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; ///FIXME: ugly, and remove const_cast ParsingEnvironmentFile* ret = dynamic_cast(DUChainItemSystem::self().create( const_cast(reinterpret_cast(reinterpret_cast(&item) + sizeof(EnvironmentInformationItem))) )); if(ret) { Q_ASSERT(ret->d_func()->classId); Q_ASSERT(ret->indexedTopContext().index() == topContextIndex); ParsingEnvironmentFilePointer retPtr(ret); m_fileEnvironmentInformations.insert(ret->url(), retPtr); Q_ASSERT(!m_indexEnvironmentInformations.contains(ret->indexedTopContext().index())); m_indexEnvironmentInformations.insert(ret->indexedTopContext().index(), retPtr); } return ret; } struct CleanupListVisitor { QList checkContexts; bool operator()(const EnvironmentInformationItem* item) { checkContexts << item->m_topContext; return true; } }; ///Will check a selection of all top-contexts for up-to-date ness, and remove them if out of date void cleanupTopContexts() { DUChainWriteLocker lock( DUChain::lock() ); qCDebug(LANGUAGE) << "cleaning top-contexts"; CleanupListVisitor visitor; uint startPos = 0; m_environmentInfo.visitAllItems(visitor); int checkContextsCount = maxFinalCleanupCheckContexts; int percentageOfContexts = (visitor.checkContexts.size() * 100) / minimumFinalCleanupCheckContextsPercentage; if(checkContextsCount < percentageOfContexts) checkContextsCount = percentageOfContexts; if(visitor.checkContexts.size() > (int)checkContextsCount) startPos = qrand() % (visitor.checkContexts.size() - checkContextsCount); int endPos = startPos + maxFinalCleanupCheckContexts; if(endPos > visitor.checkContexts.size()) endPos = visitor.checkContexts.size(); QSet< uint > check; for(int a = startPos; a < endPos && check.size() < checkContextsCount; ++a) if(check.size() < checkContextsCount) addContextsForRemoval(check, IndexedTopDUContext(visitor.checkContexts[a])); foreach(uint topIndex, check) { IndexedTopDUContext top(topIndex); if(top.data()) { qCDebug(LANGUAGE) << "removing top-context for" << top.data()->url().str() << "because it is out of date"; instance->removeDocumentChain(top.data()); } } qCDebug(LANGUAGE) << "check ready"; } private: void addContextsForRemoval(QSet& topContexts, IndexedTopDUContext top) { if(topContexts.contains(top.index())) return; QExplicitlySharedDataPointer info( instance->environmentFileForDocument(top) ); ///@todo Also check if the context is "useful"(Not a duplicate context, imported by a useful one, ...) if(info && info->needsUpdate()) { //This context will be removed }else{ return; } topContexts.insert(top.index()); if(info) { //Check whether importers need to be removed as well QList< QExplicitlySharedDataPointer > importers = info->importers(); QSet< QExplicitlySharedDataPointer > checkNext; //Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached for(QList< QExplicitlySharedDataPointer >::iterator it = importers.begin(); it != importers.end(); ++it) { IndexedTopDUContext c = (*it)->indexedTopContext(); if(!topContexts.contains(c.index())) { topContexts.insert(c.index()); //Prevent useless recursion checkNext.insert(*it); } } for(QSet< QExplicitlySharedDataPointer >::const_iterator it = checkNext.begin(); it != checkNext.end(); ++it) { topContexts.remove((*it)->indexedTopContext().index()); //Enable full check again addContextsForRemoval(topContexts, (*it)->indexedTopContext()); } } } ///Stores the environment-information for the given url void storeInformationList(IndexedString url) { QMutexLocker lock(m_environmentListInfo.mutex()); EnvironmentInformationListItem newItem; newItem.m_file = url; QSet newItems; { QMutexLocker lock(&m_chainsMutex); QMultiMap::iterator start = m_fileEnvironmentInformations.lowerBound(url); QMultiMap::iterator end = m_fileEnvironmentInformations.upperBound(url); for(QMultiMap::iterator it = start; it != end; ++it) { uint topContextIndex = (*it)->indexedTopContext().index(); newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } uint index = m_environmentListInfo.findIndex(url); if(index) { //We only handle adding items here, since we can never be sure whether everything is loaded //Removal is handled directly in removeEnvironmentInformation const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(index); QSet oldItems; FOREACH_FUNCTION(uint topContextIndex, item->items) { oldItems.insert(topContextIndex); if(!newItems.contains(topContextIndex)) { newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } if(oldItems == newItems) return; ///Update/insert a new list m_environmentListInfo.deleteItem(index); //Remove the previous item } Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)) == 0); //Insert the new item m_environmentListInfo.index(EnvironmentInformationListRequest(url, newItem)); Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url))); } //Loaded environment-informations. Protected by m_chainsMutex QMultiMap m_fileEnvironmentInformations; QHash m_indexEnvironmentInformations; ///The following repositories are thread-safe, and m_chainsMutex should not be locked when using them, because ///they may trigger I/O. Still it may be required to lock their local mutexes. ///Maps filenames to a list of top-contexts/environment-information. ItemRepository m_environmentListInfo; ///Maps top-context-indices to environment-information item. ItemRepository m_environmentInfo; }; Q_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate) DUChain::DUChain() { Q_ASSERT(ICore::self()); connect(ICore::self()->documentController(), &IDocumentController::documentLoadedPrepare, this, &DUChain::documentLoadedPrepare); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &DUChain::documentRenamed); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &DUChain::documentActivated); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &DUChain::documentClosed); } DUChain::~DUChain() { DUChain::m_deleted = true; } DUChain* DUChain::self() { return sdDUChainPrivate->instance; } extern void initModificationRevisionSetRepository(); extern void initDeclarationRepositories(); extern void initIdentifierRepository(); extern void initTypeRepository(); extern void initInstantiationInformationRepository(); void DUChain::initialize() { // Initialize the global item repository as first thing after loading the session Q_ASSERT(ICore::self()); Q_ASSERT(ICore::self()->activeSession()); ItemRepositoryRegistry::initialize(ICore::self()->activeSessionLock()); initReferenceCounting(); // This needs to be initialized here too as the function is not threadsafe, but can // sometimes be called from different threads. This results in the underlying QFile // being 0 and hence crashes at some point later when accessing the contents via // read. See https://bugs.kde.org/show_bug.cgi?id=250779 RecursiveImportRepository::repository(); RecursiveImportCacheRepository::repository(); // similar to above, see https://bugs.kde.org/show_bug.cgi?id=255323 initDeclarationRepositories(); initModificationRevisionSetRepository(); initIdentifierRepository(); initTypeRepository(); initInstantiationInformationRepository(); Importers::self(); globalImportIdentifier(); globalIndexedImportIdentifier(); globalAliasIdentifier(); globalIndexedAliasIdentifier(); } DUChainLock* DUChain::lock() { return &sdDUChainPrivate->lock; } QList DUChain::allChains() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); return sdDUChainPrivate->m_chainsByUrl.values(); } void DUChain::updateContextEnvironment( TopDUContext* context, ParsingEnvironmentFile* file ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); removeFromEnvironmentManager( context ); context->setParsingEnvironmentFile( file ); addToEnvironmentManager( context ); } void DUChain::removeDocumentChain( TopDUContext* context ) { ENSURE_CHAIN_WRITE_LOCKED; IndexedTopDUContext indexed(context->indexed()); Q_ASSERT(indexed.data() == context); ///This assertion fails if you call removeDocumentChain(..) on a document that has not been added to the du-chain context->m_dynamicData->deleteOnDisk(); Q_ASSERT(indexed.data() == context); sdDUChainPrivate->removeDocumentChainFromMemory(context); Q_ASSERT(!indexed.data()); Q_ASSERT(!environmentFileForDocument(indexed)); QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); sdDUChainPrivate->m_availableTopContextIndices.push_back(indexed.index()); } void DUChain::addDocumentChain( TopDUContext * chain ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // qCDebug(LANGUAGE) << "duchain: adding document" << chain->url().str() << " " << chain; Q_ASSERT(chain); Q_ASSERT(!sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); { QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() <= chain->ownIndex()) DUChain::chainsByIndex.resize(chain->ownIndex() + 100, 0); DUChain::chainsByIndex[chain->ownIndex()] = chain; } { Q_ASSERT(DUChain::chainsByIndex[chain->ownIndex()]); } Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); sdDUChainPrivate->m_chainsByUrl.insert(chain->url(), chain); Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); chain->setInDuChain(true); l.unlock(); addToEnvironmentManager(chain); // This function might be called during shutdown by stale parse jobs // Make sure we don't access null-pointers here if (ICore::self() && ICore::self()->languageController() && ICore::self()->languageController()->backgroundParser()->trackerForUrl(chain->url())) { //Make sure the context stays alive at least as long as the context is open ReferencedTopDUContext ctx(chain); sdDUChainPrivate->m_openDocumentContexts.insert(ctx); } } void DUChain::addToEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage Q_ASSERT(file->indexedTopContext().index() == chain->ownIndex()); if(ParsingEnvironmentFile* alreadyHave = sdDUChainPrivate->findInformation(file->indexedTopContext().index())) { ///If this triggers, there has already been another environment-information registered for this top-context. ///removeFromEnvironmentManager should have been called before to remove the old environment-information. Q_ASSERT(alreadyHave == file.data()); Q_UNUSED(alreadyHave); return; } sdDUChainPrivate->addEnvironmentInformation(file); } void DUChain::removeFromEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage sdDUChainPrivate->removeEnvironmentInformation(file); } TopDUContext* DUChain::chainForDocument(const QUrl& document, bool proxyContext) const { return chainForDocument(IndexedString(document), proxyContext); } bool DUChain::isInMemory(uint topContextIndex) const { return DUChainPrivate::hasChainForIndex(topContextIndex); } IndexedString DUChain::urlForIndex(uint index) const { { TopDUContext* chain = DUChainPrivate::readChainForIndex(index); if(chain) return chain->url(); } return TopDUContextDynamicData::loadUrl(index); } TopDUContext* DUChain::loadChain(uint index) { QSet loaded; sdDUChainPrivate->loadChain(index, loaded); { QMutexLocker lock(&chainsByIndexLock); if(chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if(top) return top; } } return 0; } TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return 0; QList list = sdDUChainPrivate->getEnvironmentInformation(document); foreach(const ParsingEnvironmentFilePointer &file, list) if(isInMemory(file->indexedTopContext().index()) && file->isProxyContext() == proxyContext) { return file->topContext(); } foreach(const ParsingEnvironmentFilePointer &file, list) if(proxyContext == file->isProxyContext()) { return file->topContext(); } //Allow selecting a top-context even if there is no ParsingEnvironmentFile QList< TopDUContext* > ret = chainsForDocument(document); foreach(TopDUContext* ctx, ret) { if(!ctx->parsingEnvironmentFile() || (ctx->parsingEnvironmentFile()->isProxyContext() == proxyContext)) return ctx; } return 0; } QList DUChain::chainsForDocument(const QUrl& document) const { return chainsForDocument(IndexedString(document)); } QList DUChain::chainsForDocument(const IndexedString& document) const { QList chains; if(sdDUChainPrivate->m_destroyed) return chains; QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // Match all parsed versions of this document for (auto it = sdDUChainPrivate->m_chainsByUrl.lowerBound(document); it != sdDUChainPrivate->m_chainsByUrl.end(); ++it) { if (it.key() == document) chains << it.value(); else break; } return chains; } TopDUContext* DUChain::chainForDocument( const QUrl& document, const KDevelop::ParsingEnvironment* environment, bool proxyContext ) const { return chainForDocument( IndexedString(document), environment, proxyContext ); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return ParsingEnvironmentFilePointer(); QList< ParsingEnvironmentFilePointer> list = sdDUChainPrivate->getEnvironmentInformation(document); // qCDebug(LANGUAGE) << document.str() << ": matching" << list.size() << (onlyProxyContexts ? "proxy-contexts" : (noProxyContexts ? "content-contexts" : "contexts")); auto it = list.constBegin(); while(it != list.constEnd()) { if(*it && ((*it)->isProxyContext() == proxyContext) && (*it)->matchEnvironment(environment) && // Verify that the environment-file and its top-context are "good": The top-context must exist, // and there must be a content-context associated to the proxy-context. (*it)->topContext() && (!proxyContext || DUChainUtils::contentContextFromProxyContext((*it)->topContext())) ) { return *it; } ++it; } return ParsingEnvironmentFilePointer(); } QList DUChain::allEnvironmentFiles(const IndexedString& document) { return sdDUChainPrivate->getEnvironmentInformation(document); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument(IndexedTopDUContext topContext) const { if(topContext.index() == 0) return ParsingEnvironmentFilePointer(); return ParsingEnvironmentFilePointer(sdDUChainPrivate->loadInformation(topContext.index())); } TopDUContext* DUChain::chainForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { if(sdDUChainPrivate->m_destroyed) return 0; ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext); if(envFile) { return envFile->topContext(); }else{ return 0; } } QList DUChain::documents() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; - foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) { + ret.reserve(sdDUChainPrivate->m_chainsByUrl.count()); + foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl) { ret << top->url().toUrl(); } return ret; } QList DUChain::indexedDocuments() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; - foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) { + ret.reserve(sdDUChainPrivate->m_chainsByUrl.count()); + foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl) { ret << top->url(); } return ret; } void DUChain::documentActivated(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; DUChainReadLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); auto backgroundParser = ICore::self()->languageController()->backgroundParser(); auto addWithHighPriority = [backgroundParser, doc]() { backgroundParser->addDocument(IndexedString(doc->url()), TopDUContext::VisibleDeclarationsAndContexts, BackgroundParser::BestPriority); }; TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url(), true); //Check whether the document has an attached environment-manager, and whether that one thinks the document needs to be updated. //If yes, update it. if (ctx && ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->needsUpdate()) { qCDebug(LANGUAGE) << "Document needs update, using best priority since it just got activated:" << doc->url(); addWithHighPriority(); } else if (backgroundParser->managedDocuments().contains(IndexedString(doc->url()))) { // increase priority if there's already parse job of this document in the queue qCDebug(LANGUAGE) << "Prioritizing activated document:" << doc->url(); addWithHighPriority(); } } void DUChain::documentClosed(IDocument* document) { if(sdDUChainPrivate->m_destroyed) return; IndexedString url(document->url()); foreach(const ReferencedTopDUContext &top, sdDUChainPrivate->m_openDocumentContexts) if(top->url() == url) sdDUChainPrivate->m_openDocumentContexts.remove(top); } void DUChain::documentLoadedPrepare(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; const IndexedString url(doc->url()); DUChainWriteLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(doc->url()); QList chains = chainsForDocument(url); auto languages = ICore::self()->languageController()->languagesForUrl(doc->url()); if(standardContext) { Q_ASSERT(chains.contains(standardContext)); //We have just loaded it Q_ASSERT((standardContext->url() == url)); sdDUChainPrivate->m_openDocumentContexts.insert(standardContext); bool needsUpdate = standardContext->parsingEnvironmentFile() && standardContext->parsingEnvironmentFile()->needsUpdate(); if(!needsUpdate) { //Only apply the highlighting if we don't need to update, else we might highlight total crap //Do instant highlighting only if all imports are loaded, to make sure that we don't block the user-interface too long //Else the highlighting will be done in the background-thread //This is not exactly right, as the direct imports don't necessarily equal the real imports used by uses //but it approximates the correct behavior. bool allImportsLoaded = true; foreach(const DUContext::Import& import, standardContext->importedParentContexts()) if(!import.indexedContext().indexedTopContext().isLoaded()) allImportsLoaded = false; if(allImportsLoaded) { l.unlock(); lock.unlock(); foreach(const auto language, languages) { if(language->codeHighlighting()) { language->codeHighlighting()->highlightDUChain(standardContext); } } qCDebug(LANGUAGE) << "highlighted" << doc->url() << "in foreground"; return; } }else{ qCDebug(LANGUAGE) << "not highlighting the duchain because the documents needs an update"; } if(needsUpdate || !(standardContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); return; } } //Add for highlighting etc. ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), TopDUContext::AllDeclarationsContextsAndUses); } void DUChain::documentRenamed(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; if(!doc->url().isValid()) { ///Maybe this happens when a file was deleted? qCWarning(LANGUAGE) << "Strange, url of renamed document is invalid!"; }else{ ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); } } Uses* DUChain::uses() { return &sdDUChainPrivate->m_uses; } Definitions* DUChain::definitions() { return &sdDUChainPrivate->m_definitions; } static void finalCleanup() { DUChainWriteLocker writeLock(DUChain::lock()); qCDebug(LANGUAGE) << "doing final cleanup"; int cleaned = 0; while((cleaned = globalItemRepositoryRegistry().finalCleanup())) { qCDebug(LANGUAGE) << "cleaned" << cleaned << "B"; if(cleaned < 1000) { qCDebug(LANGUAGE) << "cleaned enough"; break; } } qCDebug(LANGUAGE) << "final cleanup ready"; } void DUChain::shutdown() { // if core is not shutting down, we can end up in deadlocks or crashes // since language plugins might still try to access static duchain stuff Q_ASSERT(!ICore::self() || ICore::self()->shuttingDown()); qCDebug(LANGUAGE) << "Cleaning up and shutting down DUChain"; QMutexLocker lock(&sdDUChainPrivate->cleanupMutex()); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); sdDUChainPrivate->cleanupTopContexts(); globalItemRepositoryRegistry().unlockForWriting(); } sdDUChainPrivate->doMoreCleanup(); //Must be done _before_ finalCleanup, else we may be deleting yet needed data sdDUChainPrivate->m_openDocumentContexts.clear(); sdDUChainPrivate->m_destroyed = true; sdDUChainPrivate->clear(); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); finalCleanup(); globalItemRepositoryRegistry().unlockForWriting(); } globalItemRepositoryRegistry().shutdown(); } uint DUChain::newTopContextIndex() { { QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); if(!sdDUChainPrivate->m_availableTopContextIndices.isEmpty()) { uint ret = sdDUChainPrivate->m_availableTopContextIndices.back(); sdDUChainPrivate->m_availableTopContextIndices.pop_back(); if(TopDUContextDynamicData::fileExists(ret)) { qCWarning(LANGUAGE) << "Problem in the management of availalbe top-context indices"; return newTopContextIndex(); } return ret; } } static QAtomicInt& currentId( globalItemRepositoryRegistry().getCustomCounter(QStringLiteral("Top-Context Counter"), 1) ); return currentId.fetchAndAddRelaxed(1); } void DUChain::refCountUp(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) sdDUChainPrivate->m_referenceCounts.insert(top, 1); else ++sdDUChainPrivate->m_referenceCounts[top]; } bool DUChain::deleted() { return m_deleted; } void DUChain::refCountDown(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) { //qCWarning(LANGUAGE) << "tried to decrease reference-count for" << top->url().str() << "but this top-context is not referenced"; return; } --sdDUChainPrivate->m_referenceCounts[top]; if(!sdDUChainPrivate->m_referenceCounts[top]) sdDUChainPrivate->m_referenceCounts.remove(top); } void DUChain::emitDeclarationSelected(const DeclarationPointer& decl) { if(sdDUChainPrivate->m_destroyed) return; emit declarationSelected(decl); } void DUChain::emitUpdateReady(const IndexedString& url, const ReferencedTopDUContext& topContext) { if(sdDUChainPrivate->m_destroyed) return; emit updateReady(url, topContext); } KDevelop::ReferencedTopDUContext DUChain::waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext) { Q_ASSERT(!lock()->currentThreadHasReadLock() && !lock()->currentThreadHasWriteLock()); WaitForUpdate waiter; updateContextForUrl(document, minFeatures, &waiter); // waiter.m_waitMutex.lock(); // waiter.m_dataMutex.unlock(); while(!waiter.m_ready) { // we might have been shut down in the meanwhile if (!ICore::self()) { return 0; } QMetaObject::invokeMethod(ICore::self()->languageController()->backgroundParser(), "parseDocuments"); QApplication::processEvents(); QThread::usleep(1000); } if(!proxyContext) { DUChainReadLocker readLock(DUChain::lock()); return DUChainUtils::contentContextFromProxyContext(waiter.m_topContext); } return waiter.m_topContext; } void DUChain::updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures, QObject* notifyReady, int priority) const { DUChainReadLocker lock( DUChain::lock() ); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(document.toUrl()); if(standardContext && standardContext->parsingEnvironmentFile() && !standardContext->parsingEnvironmentFile()->needsUpdate() && standardContext->parsingEnvironmentFile()->featuresSatisfied(minFeatures)) { lock.unlock(); if(notifyReady) QMetaObject::invokeMethod(notifyReady, "updateReady", Qt::DirectConnection, Q_ARG(KDevelop::IndexedString, document), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext(standardContext))); }else{ ///Start a parse-job for the given document ICore::self()->languageController()->backgroundParser()->addDocument(document, minFeatures, priority, notifyReady); } } void DUChain::disablePersistentStorage(bool disable) { sdDUChainPrivate->m_cleanupDisabled = disable; } void DUChain::storeToDisk() { bool wasDisabled = sdDUChainPrivate->m_cleanupDisabled; sdDUChainPrivate->m_cleanupDisabled = false; sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_cleanupDisabled = wasDisabled; } bool DUChain::compareToDisk() { DUChainWriteLocker writeLock(DUChain::lock()); ///Step 1: Compare the repositories return true; } } diff --git a/plugins/bazaar/diffjob.cpp b/plugins/bazaar/diffjob.cpp index 549dc0f6f6..0788a4c810 100644 --- a/plugins/bazaar/diffjob.cpp +++ b/plugins/bazaar/diffjob.cpp @@ -1,96 +1,96 @@ /*************************************************************************** * Copyright 2013-2014 Maciej Poleski * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "diffjob.h" #include #include #include #include "bazaarplugin.h" using namespace KDevelop; DiffJob::DiffJob(const QDir& workingDir, const QString& revisionSpecRange, const QUrl& fileOrDirectory, BazaarPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity), m_plugin(parent), m_status(KDevelop::VcsJob::JobNotStarted) { setType(KDevelop::VcsJob::Diff); setCapabilities(Killable); m_job = new KDevelop::DVcsJob(workingDir, parent, verbosity); m_job->setType(VcsJob::Diff); *m_job << "bzr" << "diff" << "-p1" << revisionSpecRange << fileOrDirectory; } KDevelop::IPlugin* DiffJob::vcsPlugin() const { return m_plugin; } KDevelop::VcsJob::JobStatus DiffJob::status() const { return m_status; } QVariant DiffJob::fetchResults() { return m_result; } void DiffJob::start() { if (m_status != KDevelop::VcsJob::JobNotStarted) return; if (m_job) { connect(m_job.data(), &DVcsJob::finished, this, &DiffJob::prepareResult); m_status = KDevelop::VcsJob::JobRunning; m_job->start(); } } bool DiffJob::doKill() { m_status = KDevelop::VcsJob::JobCanceled; if (m_job) return m_job->kill(KJob::Quietly); else return true; } void DiffJob::prepareResult(KJob*) { - if (m_job->process()->exitStatus() <= 2) { + if (m_job->process()->exitStatus() == QProcess::NormalExit) { KDevelop::VcsDiff diff; diff.setDiff(m_job->output()); diff.setBaseDiff(QUrl::fromLocalFile(m_job->directory().absolutePath())); m_result.setValue(diff); m_status = KDevelop::VcsJob::JobSucceeded; } else { setError(m_job->process()->exitStatus()); m_status = KDevelop::VcsJob::JobFailed; } emitResult(); emit resultsReady(this); } diff --git a/plugins/contextbrowser/browsemanager.cpp b/plugins/contextbrowser/browsemanager.cpp index 0d8f6cb494..1d626ffb48 100644 --- a/plugins/contextbrowser/browsemanager.cpp +++ b/plugins/contextbrowser/browsemanager.cpp @@ -1,322 +1,322 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "browsemanager.h" #include #include #include #include #include #include #include #include #include #include "contextbrowserview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "contextbrowser.h" #include "debug.h" using namespace KDevelop; using namespace KTextEditor; EditorViewWatcher::EditorViewWatcher(QObject* parent) : QObject(parent) { connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &EditorViewWatcher::documentCreated); foreach(KDevelop::IDocument* document, ICore::self()->documentController()->openDocuments()) documentCreated(document); } void EditorViewWatcher::documentCreated( KDevelop::IDocument* document ) { KTextEditor::Document* textDocument = document->textDocument(); if(textDocument) { connect(textDocument, &Document::viewCreated, this, &EditorViewWatcher::viewCreated); foreach(KTextEditor::View* view, textDocument->views()) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } } } void EditorViewWatcher::addViewInternal(KTextEditor::View* view) { m_views << view; viewAdded(view); connect(view, &View::destroyed, this, &EditorViewWatcher::viewDestroyed); } void EditorViewWatcher::viewAdded(KTextEditor::View*) { } void EditorViewWatcher::viewDestroyed(QObject* view) { m_views.removeAll(static_cast(view)); } void EditorViewWatcher::viewCreated(KTextEditor::Document* /*doc*/, KTextEditor::View* view) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } QList EditorViewWatcher::allViews() { return m_views; } void BrowseManager::eventuallyStartDelayedBrowsing() { avoidMenuAltFocus(); if(m_browsingByKey && m_browsingStartedInView) emit startDelayedBrowsing(m_browsingStartedInView); } BrowseManager::BrowseManager(ContextBrowserPlugin* controller) : QObject(controller) , m_plugin(controller) , m_browsingByKey(0) , m_watcher(this) { m_delayedBrowsingTimer = new QTimer(this); m_delayedBrowsingTimer->setSingleShot(true); connect(m_delayedBrowsingTimer, &QTimer::timeout, this, &BrowseManager::eventuallyStartDelayedBrowsing); foreach(KTextEditor::View* view, m_watcher.allViews()) viewAdded(view); } KTextEditor::View* viewFromWidget(QWidget* widget) { if(!widget) return 0; KTextEditor::View* view = qobject_cast(widget); if(view) return view; else return viewFromWidget(widget->parentWidget()); } BrowseManager::JumpLocation BrowseManager::determineJumpLoc(KTextEditor::Cursor textCursor, const QUrl& viewUrl) const { // @todo find out why this is needed, fix the code in kate if (textCursor.column() > 0) { textCursor.setColumn(textCursor.column() - 1); } // Step 1: Look for a special language object(Macro, included header, etc.) - for (const auto& language: ICore::self()->languageController()->languagesForUrl(viewUrl)) { + foreach (const auto& language, ICore::self()->languageController()->languagesForUrl(viewUrl)) { auto jumpTo = language->specialLanguageObjectJumpCursor(viewUrl, textCursor); if (jumpTo.first.isValid() && jumpTo.second.isValid()) { return {jumpTo}; } } // Step 2: Look for a declaration/use DUChainReadLocker lock; // Jump to definition by default, unless a definition itself was selected, // in which case jump to declaration. if (auto selectedDeclaration = DUChainUtils::itemUnderCursor(viewUrl, textCursor)) { auto jumpDestination = selectedDeclaration; if (selectedDeclaration->isDefinition()) { // A definition was clicked directly - jump to declaration instead. if (auto declaration = DUChainUtils::declarationForDefinition(selectedDeclaration)) { jumpDestination = declaration; } } else if (selectedDeclaration == DUChainUtils::declarationForDefinition(selectedDeclaration)) { // Clicked the declaration - jump to definition if (auto definition = FunctionDefinition::definition(selectedDeclaration)) { jumpDestination = definition; } } return {{jumpDestination->url().toUrl(), jumpDestination->rangeInCurrentRevision().start()}}; } return {}; } bool BrowseManager::eventFilter(QObject * watched, QEvent * event) { QWidget* widget = qobject_cast(watched); Q_ASSERT(widget); QKeyEvent* keyEvent = dynamic_cast(event); const int browseKey = Qt::Key_Control; const int magicModifier = Qt::Key_Alt; KTextEditor::View* view = viewFromWidget(widget); //Eventually start key-browsing if(keyEvent && (keyEvent->key() == browseKey || keyEvent->key() == magicModifier) && !m_browsingByKey && keyEvent->type() == QEvent::KeyPress) { m_delayedBrowsingTimer->start(300); // always start the timer, to get consistent behavior regarding the ALT key and the menu activation m_browsingByKey = keyEvent->key(); if(!view) { return false; } if(keyEvent->key() == magicModifier) { if(dynamic_cast(view) && dynamic_cast(view)->isCompletionActive()) { //Completion is active. avoidMenuAltFocus(); }else{ m_browsingStartedInView = view; } } } if(!view) { return false; } QFocusEvent* focusEvent = dynamic_cast(event); //Eventually stop key-browsing if((keyEvent && m_browsingByKey && keyEvent->key() == m_browsingByKey && keyEvent->type() == QEvent::KeyRelease) || (focusEvent && focusEvent->lostFocus())) { m_browsingByKey = 0; emit stopDelayedBrowsing(); } QMouseEvent* mouseEvent = dynamic_cast(event); if(mouseEvent) { if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton1) { m_plugin->historyPrevious(); return true; } if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton2) { m_plugin->historyNext(); return true; } } if(!m_browsingByKey) { resetChangedCursor(); return false; } if(mouseEvent) { KTextEditor::View* iface = dynamic_cast(view); if(!iface) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Update kdelibs for the browsing-mode to work"; return false; } QPoint coordinatesInView = widget->mapTo(view, mouseEvent->pos()); KTextEditor::Cursor textCursor = iface->coordinatesToCursor(coordinatesInView); if (textCursor.isValid()) { JumpLocation jumpTo = determineJumpLoc(textCursor, view->document()->url()); if (jumpTo.isValid()) { if(mouseEvent->button() == Qt::LeftButton) { if(mouseEvent->type() == QEvent::MouseButtonPress) { m_buttonPressPosition = textCursor; // view->setCursorPosition(textCursor); // return false; }else if(mouseEvent->type() == QEvent::MouseButtonRelease && textCursor == m_buttonPressPosition) { ICore::self()->documentController()->openDocument(jumpTo.url, jumpTo.cursor); // event->accept(); // return true; } }else if(mouseEvent->type() == QEvent::MouseMove) { //Make the cursor a "hand" setHandCursor(widget); return false; } } } resetChangedCursor(); } return false; } void BrowseManager::resetChangedCursor() { QMap, QCursor> cursors = m_oldCursors; m_oldCursors.clear(); for(QMap, QCursor>::iterator it = cursors.begin(); it != cursors.end(); ++it) if(it.key()) it.key()->setCursor(QCursor(Qt::IBeamCursor)); } void BrowseManager::setHandCursor(QWidget* widget) { if(m_oldCursors.contains(widget)) return; //Nothing to do m_oldCursors[widget] = widget->cursor(); widget->setCursor(QCursor(Qt::PointingHandCursor)); } void BrowseManager::avoidMenuAltFocus() { // send an invalid key event to the main menu bar. The menu bar will // stop listening when observing another key than ALT between the press // and the release. QKeyEvent event1(QEvent::KeyPress, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event1); QKeyEvent event2(QEvent::KeyRelease, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event2); } void BrowseManager::applyEventFilter(QWidget* object, bool install) { if(install) object->installEventFilter(this); else object->removeEventFilter(this); foreach(QObject* child, object->children()) if(qobject_cast(child)) applyEventFilter(qobject_cast(child), install); } void BrowseManager::viewAdded(KTextEditor::View* view) { applyEventFilter(view, true); //We need to listen for cursorPositionChanged, to clear the shift-detector. The problem: Kate listens for the arrow-keys using shortcuts, //so those keys are not passed to the event-filter // can't use new signal/slot syntax here, these signals are only defined in KateView // TODO: should we really depend on kate internals here? connect(view, SIGNAL(navigateLeft()), m_plugin, SLOT(navigateLeft())); connect(view, SIGNAL(navigateRight()), m_plugin, SLOT(navigateRight())); connect(view, SIGNAL(navigateUp()), m_plugin, SLOT(navigateUp())); connect(view, SIGNAL(navigateDown()), m_plugin, SLOT(navigateDown())); connect(view, SIGNAL(navigateAccept()), m_plugin, SLOT(navigateAccept())); connect(view, SIGNAL(navigateBack()), m_plugin, SLOT(navigateBack())); } void Watcher::viewAdded(KTextEditor::View* view) { m_manager->viewAdded(view); } Watcher::Watcher(BrowseManager* manager) : EditorViewWatcher(manager), m_manager(manager) { foreach(KTextEditor::View* view, allViews()) m_manager->applyEventFilter(view, true); } diff --git a/plugins/problemreporter/problemhighlighter.cpp b/plugins/problemreporter/problemhighlighter.cpp index 24492618e3..cfa134876e 100644 --- a/plugins/problemreporter/problemhighlighter.cpp +++ b/plugins/problemreporter/problemhighlighter.cpp @@ -1,260 +1,260 @@ /* * KDevelop Problem Reporter * * Copyright 2008 Hamish Rodda * Copyright 2008-2009 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemhighlighter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KTextEditor; using namespace KDevelop; namespace { QColor colorForSeverity(IProblem::Severity severity) { KColorScheme scheme(QPalette::Active); switch (severity) { case IProblem::Error: return scheme.foreground(KColorScheme::NegativeText).color(); case IProblem::Warning: return scheme.foreground(KColorScheme::NeutralText).color(); case IProblem::Hint: default: return scheme.foreground(KColorScheme::PositiveText).color(); } } } ProblemHighlighter::ProblemHighlighter(KTextEditor::Document* document) : m_document(document) , m_textHintProvider(this) { Q_ASSERT(m_document); foreach (KTextEditor::View* view, m_document->views()) viewCreated(document, view); connect(m_document.data(), &Document::viewCreated, this, &ProblemHighlighter::viewCreated); connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemHighlighter::settingsChanged); connect(m_document.data(), &Document::aboutToReload, this, &ProblemHighlighter::clearProblems); if (qobject_cast(m_document)) { // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearProblems())); } connect(m_document, SIGNAL(aboutToRemoveText(KTextEditor::Range)), this, SLOT(aboutToRemoveText(KTextEditor::Range))); } void ProblemHighlighter::settingsChanged() { // Re-highlight setProblems(m_problems); } void ProblemHighlighter::viewCreated(Document*, View* view) { KTextEditor::TextHintInterface* iface = dynamic_cast(view); if (!iface) return; iface->registerTextHintProvider(&m_textHintProvider); } ProblemTextHintProvider::ProblemTextHintProvider(ProblemHighlighter* highlighter) : m_highlighter(highlighter) { } QString ProblemTextHintProvider::textHint(View* view, const Cursor& pos) { KTextEditor::MovingInterface* moving = dynamic_cast(view->document()); if (moving) { ///@todo Sort the ranges when writing them, and do binary search instead of linear foreach (MovingRange* range, m_highlighter->m_topHLRanges) { if (m_highlighter->m_problemsForRanges.contains(range) && range->contains(pos)) { // There is a problem which's range contains the cursor IProblem::Ptr problem = m_highlighter->m_problemsForRanges[range]; if (problem->source() == IProblem::ToDo) { continue; } if (m_currentHintRange == range->toRange()) { continue; } m_currentHintRange = range->toRange(); KDevelop::AbstractNavigationWidget* widget = new KDevelop::AbstractNavigationWidget; widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problem))); KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, QCursor::pos() + QPoint(20, 40), widget); tooltip->resize(widget->sizeHint() + QSize(10, 10)); tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, m_currentHintRange)); tooltip->connect(tooltip, &ActiveToolTip::destroyed, [&] () { m_currentHintRange = {}; }); ActiveToolTip::showToolTip(tooltip, 99, QStringLiteral("problem-tooltip")); return QString(); } } } return QString(); } ProblemHighlighter::~ProblemHighlighter() { if (m_topHLRanges.isEmpty() || !m_document) return; qDeleteAll(m_topHLRanges); } void ProblemHighlighter::setProblems(const QVector& problems) { if (!m_document) return; const bool hadProblems = !m_problems.isEmpty(); m_problems = problems; qDeleteAll(m_topHLRanges); m_topHLRanges.clear(); m_problemsForRanges.clear(); IndexedString url(m_document->url()); /// TODO: create a better MarkInterface that makes it possible to add the marks to the scrollbar /// but having no background. /// also make it nicer together with other plugins, this would currently fail with /// this method... const uint errorMarkType = KTextEditor::MarkInterface::Error; const uint warningMarkType = KTextEditor::MarkInterface::Warning; KTextEditor::MarkInterface* markIface = dynamic_cast(m_document.data()); if (markIface && hadProblems) { // clear previously added marks - foreach (KTextEditor::Mark* mark, markIface->marks().values()) { + foreach (KTextEditor::Mark* mark, markIface->marks()) { if (mark->type == errorMarkType || mark->type == warningMarkType) { markIface->removeMark(mark->line, mark->type); } } } if (problems.isEmpty()) { return; } DUChainReadLocker lock; TopDUContext* top = DUChainUtils::standardContextForUrl(m_document->url()); KTextEditor::MovingInterface* iface = dynamic_cast(m_document.data()); Q_ASSERT(iface); foreach (const IProblem::Ptr& problem, problems) { if (problem->finalLocation().document != url || !problem->finalLocation().isValid()) continue; KTextEditor::Range range; if (top) range = top->transformFromLocalRevision(RangeInRevision::castFromSimpleRange(problem->finalLocation())); else range = problem->finalLocation(); if (range.end().line() >= m_document->lines()) range.end() = KTextEditor::Cursor(m_document->endOfLine(m_document->lines() - 1)); if (range.isEmpty()) { range.setEnd(range.end() + KTextEditor::Cursor(0, 1)); } KTextEditor::MovingRange* problemRange = iface->newMovingRange(range); m_problemsForRanges.insert(problemRange, problem); m_topHLRanges.append(problemRange); if (problem->source() != IProblem::ToDo && (problem->severity() != IProblem::Hint || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems())) { KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); attribute->setUnderlineStyle(QTextCharFormat::WaveUnderline); attribute->setUnderlineColor(colorForSeverity(problem->severity())); problemRange->setAttribute(attribute); } if (markIface && ICore::self()->languageController()->completionSettings()->highlightProblematicLines()) { uint mark; if (problem->severity() == IProblem::Error) { mark = errorMarkType; } else if (problem->severity() == IProblem::Warning) { mark = warningMarkType; } else { continue; } markIface->addMark(problem->finalLocation().start().line(), mark); } } } void ProblemHighlighter::aboutToRemoveText(const KTextEditor::Range& range) { if (range.onSingleLine()) { // no need to optimize this return; } QList::iterator it = m_topHLRanges.begin(); while (it != m_topHLRanges.end()) { if (range.contains((*it)->toRange())) { m_problemsForRanges.remove(*it); delete (*it); it = m_topHLRanges.erase(it); } else { ++it; } } } void ProblemHighlighter::clearProblems() { setProblems({}); } diff --git a/project/tests/abstractfilemanagerpluginimporttest.cpp b/project/tests/abstractfilemanagerpluginimporttest.cpp index fdda8a974b..684b876057 100644 --- a/project/tests/abstractfilemanagerpluginimporttest.cpp +++ b/project/tests/abstractfilemanagerpluginimporttest.cpp @@ -1,58 +1,58 @@ /* This file is part of KDevelop Copyright 2016 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include using namespace KDevelop; int main(int argc, char** argv) { if (argc != 2) { qWarning() << "Missing argument for directory path to list."; return 1; } QCoreApplication app(argc, argv); AutoTestShell::init(); auto core = TestCore::initialize(Core::NoUi); auto plugin = new AbstractFileManagerPlugin({}, core); auto project = new TestProject(Path(QString::fromUtf8(argv[1]))); auto root = plugin->import(project); auto import = plugin->createImportJob(root); QObject::connect(import, &KJob::finished, - &app, [&] { + &app, [project] { qDebug() << "loaded project with" << project->fileSet().size() << "files"; - app.quit(); + QCoreApplication::instance()->quit(); }); import->start(); return app.exec(); } diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index 752c435b81..19a956014a 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -1,177 +1,175 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") add_subdirectory(tests) set(KDevPlatformShell_LIB_SRCS workingsetcontroller.cpp workingsets/workingset.cpp workingsets/workingsetfilelabel.cpp workingsets/workingsettoolbutton.cpp workingsets/workingsettooltipwidget.cpp workingsets/workingsetwidget.cpp workingsets/closedworkingsetswidget.cpp workingsets/workingsethelpers.cpp assistantpopup.cpp mainwindow.cpp mainwindow_p.cpp plugincontroller.cpp ktexteditorpluginintegration.cpp shellextension.cpp core.cpp uicontroller.cpp projectcontroller.cpp project.cpp partcontroller.cpp #document.cpp partdocument.cpp textdocument.cpp documentcontroller.cpp languagecontroller.cpp statusbar.cpp runcontroller.cpp unitylauncher.cpp sessioncontroller.cpp session.cpp sessionlock.cpp sessionchooserdialog.cpp savedialog.cpp - sessiondialog.cpp sourceformattercontroller.cpp completionsettings.cpp openprojectpage.cpp openprojectdialog.cpp projectinfopage.cpp selectioncontroller.cpp documentationcontroller.cpp debugcontroller.cpp launchconfiguration.cpp launchconfigurationdialog.cpp loadedpluginsdialog.cpp testcontroller.cpp projectsourcepage.cpp debug.cpp configdialog.cpp editorconfigpage.cpp environmentconfigurebutton.cpp checkerstatus.cpp problem.cpp problemmodelset.cpp problemmodel.cpp problemstore.cpp watcheddocumentset.cpp filteredproblemstore.cpp progresswidget/progressmanager.cpp progresswidget/statusbarprogresswidget.cpp progresswidget/overlaywidget.cpp progresswidget/progressdialog.cpp areadisplay.cpp settings/uipreferences.cpp settings/pluginpreferences.cpp settings/sourceformattersettings.cpp settings/editstyledialog.cpp settings/projectpreferences.cpp settings/environmentwidget.cpp settings/environmentgroupmodel.cpp settings/environmentpreferences.cpp settings/languagepreferences.cpp settings/bgpreferences.cpp settings/templateconfig.cpp settings/templatepage.cpp ) kconfig_add_kcfg_files(KDevPlatformShell_LIB_SRCS settings/uiconfig.kcfgc settings/projectconfig.kcfgc settings/languageconfig.kcfgc settings/bgconfig.kcfgc ) ki18n_wrap_ui(KDevPlatformShell_LIB_SRCS - sessiondialog.ui projectinfopage.ui launchconfigurationdialog.ui projectsourcepage.ui settings/uiconfig.ui settings/editstyledialog.ui settings/sourceformattersettings.ui settings/projectpreferences.ui settings/environmentwidget.ui settings/languagepreferences.ui settings/bgpreferences.ui settings/templateconfig.ui settings/templatepage.ui ) qt5_add_resources(KDevPlatformShell_LIB_SRCS kdevplatformshell.qrc) kdevplatform_add_library(KDevPlatformShell SOURCES ${KDevPlatformShell_LIB_SRCS}) target_link_libraries(KDevPlatformShell LINK_PUBLIC KF5::XmlGui KDev::Sublime KDev::OutputView KDev::Interfaces LINK_PRIVATE Qt5::Quick Qt5::QuickWidgets KF5::GuiAddons KF5::IconThemes KF5::KIOFileWidgets KF5::KIOWidgets KF5::Parts KF5::Notifications KF5::NotifyConfig KF5::TextEditor KF5::ThreadWeaver KF5::JobWidgets KF5::ItemViews KF5::WindowSystem KF5::KCMUtils #for KPluginSelector, not sure why it is in kcmutils KF5::NewStuff # template config page KF5::Archive # template config page KDev::Debugger KDev::Project KDev::Vcs KDev::Language KDev::Util KDev::Documentation ) install(FILES mainwindow.h plugincontroller.h shellextension.h core.h uicontroller.h projectcontroller.h project.h partcontroller.h partdocument.h textdocument.h documentcontroller.h languagecontroller.h session.h sessioncontroller.h sessionlock.h sourceformattercontroller.h selectioncontroller.h runcontroller.h launchconfiguration.h environmentconfigurebutton.h checkerstatus.h problem.h problemmodel.h problemmodelset.h problemconstants.h filteredproblemstore.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/shell COMPONENT Devel ) install( FILES AssistantButton.qml assistantpopup.qml DESTINATION ${KDE_INSTALL_DATADIR}/kdevelop ) diff --git a/shell/mainwindow_actions.cpp b/shell/mainwindow_actions.cpp index 005104c9a0..c4315f77ff 100644 --- a/shell/mainwindow_actions.cpp +++ b/shell/mainwindow_actions.cpp @@ -1,247 +1,246 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "core.h" #include "documentcontroller.h" #include "mainwindow_p.h" -#include "sessiondialog.h" #include "uicontroller.h" #include "mainwindow.h" #include "loadedpluginsdialog.h" #include #include namespace KDevelop { // merge the gotoNext and gotoPrev code, to prevent copy/paste errors static void gotoPrevNextWindow(bool next) { UiController* ui = Core::self()->uiControllerInternal(); if( !ui->activeSublimeWindow() ) return; Sublime::Area* activeArea = ui->activeArea(); if (!activeArea) return; Sublime::View* activeView = ui->activeSublimeWindow()->activeView(); Sublime::AreaIndex* index = activeArea->indexOf(activeView); if (!index) return; int viewIndex = index->views().indexOf(activeView); viewIndex = next ? viewIndex + 1 : viewIndex -1; if (viewIndex < 0) viewIndex = index->views().count() - 1; else if (viewIndex >= index->views().count()) viewIndex = 0; if (viewIndex >= 0 && viewIndex < index->views().count()) ui->activeSublimeWindow()->activateView(index->views().at(viewIndex)); } void MainWindowPrivate::gotoNextWindow() { gotoPrevNextWindow(true); } void MainWindowPrivate::gotoPreviousWindow() { gotoPrevNextWindow(false); } void MainWindowPrivate::selectPrevItem() { auto actionListener = qobject_cast( Core::self()->uiControllerInternal()->activeToolViewActionListener()); if (actionListener) { actionListener->selectPreviousItem(); } } void MainWindowPrivate::selectNextItem() { auto actionListener = qobject_cast( Core::self()->uiControllerInternal()->activeToolViewActionListener()); if (actionListener) { actionListener->selectNextItem(); } } void MainWindowPrivate::newToolbarConfig() { m_mainWindow->applyMainWindowSettings( KConfigGroup(KSharedConfig::openConfig(), "MainWindow") ); } void MainWindowPrivate::settingsDialog() { Core::self()->uiControllerInternal()->showSettingsDialog(); } void MainWindowPrivate::newWindow() { Core::self()->uiController()->switchToArea(m_mainWindow->area()->objectName(), UiController::NewWindow); } void MainWindowPrivate::splitHorizontal() { split(Qt::Vertical); } void MainWindowPrivate::splitVertical() { split(Qt::Horizontal); } void MainWindowPrivate::split(Qt::Orientation orientation) { if (!m_mainWindow->area()) return; Sublime::View *view = m_mainWindow->activeView(); if (!view) return; Sublime::View *newView = view->document()->createView(); m_mainWindow->area()->addView(newView, view, orientation); m_mainWindow->activateView(newView); } static void gotoPrevNextSplit(bool next) { UiController* ui = Core::self()->uiControllerInternal(); if( !ui->activeSublimeWindow() ) return; Sublime::Area* area = ui->activeSublimeWindow()->area(); if (!area) return; QList topViews = ui->activeSublimeWindow()->getTopViews(); Sublime::View *activeView = ui->activeSublimeWindow()->activeView(); if (!activeView) return; int viewIndex = topViews.indexOf(activeView); viewIndex = next ? viewIndex + 1 : viewIndex -1; if (viewIndex < 0) viewIndex = topViews.count() - 1; else if (viewIndex >= topViews.count()) viewIndex = 0; if (viewIndex >= 0 && viewIndex < topViews.count()) ui->activeSublimeWindow()->activateView(topViews.at(viewIndex)); } void MainWindowPrivate::gotoNextSplit() { gotoPrevNextSplit(true); } void MainWindowPrivate::gotoPreviousSplit() { gotoPrevNextSplit(false); } void MainWindowPrivate::toggleFullScreen(bool fullScreen) { KToggleFullScreenAction::setFullScreen( m_mainWindow, fullScreen ); } void MainWindowPrivate::fileNew() { Core::self()->documentControllerInternal()->openDocument(DocumentController::nextEmptyDocumentUrl()); } void MainWindowPrivate::viewAddNewToolView() { Core::self()->uiControllerInternal()->selectNewToolViewToAdd(m_mainWindow); } void MainWindowPrivate::quitAll() { QApplication::closeAllWindows(); } void MainWindowPrivate::configureNotifications() { KNotifyConfigWidget::configure(m_mainWindow); } void MainWindowPrivate::showAboutPlatform() { KAboutApplicationDialog dlg(Core::self()->aboutData(), m_mainWindow ); dlg.exec(); } void MainWindowPrivate::showLoadedPlugins() { LoadedPluginsDialog dlg(m_mainWindow); dlg.exec(); } void MainWindowPrivate::contextMenuFileNew() { m_mainWindow->activateView(m_tabView); fileNew(); } void MainWindowPrivate::contextMenuSplitHorizontal() { m_mainWindow->activateView(m_tabView); splitHorizontal(); } void MainWindowPrivate::contextMenuSplitVertical() { m_mainWindow->activateView(m_tabView); splitVertical(); } void MainWindowPrivate::reloadAll() { foreach ( IDocument* doc, Core::self()->documentController()->openDocuments() ) { doc->reload(); } } } diff --git a/shell/openprojectdialog.cpp b/shell/openprojectdialog.cpp index 9ccca43fa9..ad50e4aca8 100644 --- a/shell/openprojectdialog.cpp +++ b/shell/openprojectdialog.cpp @@ -1,322 +1,322 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "shellextension.h" #include "projectsourcepage.h" #include namespace { struct URLInfo { bool isValid; bool isDir; QString extension; }; URLInfo getUrlInfo(const QUrl& url) { URLInfo ret; ret.isValid = false; if (url.isLocalFile()) { QFileInfo info(url.toLocalFile()); ret.isValid = info.exists(); if (ret.isValid) { ret.isDir = info.isDir(); ret.extension = info.suffix(); } } else if (url.isValid()) { KIO::StatJob* statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, KDevelop::Core::self()->uiControllerInternal()->defaultMainWindow()); ret.isValid = statJob->exec(); // TODO: do this asynchronously so that the user isn't blocked while typing every letter of the hostname in sftp://hostname if (ret.isValid) { KIO::UDSEntry entry = statJob->statResult(); ret.isDir = entry.isDir(); ret.extension = QFileInfo(entry.stringValue(KIO::UDSEntry::UDS_NAME)).suffix(); } } return ret; } } namespace KDevelop { OpenProjectDialog::OpenProjectDialog( bool fetch, const QUrl& startUrl, QWidget* parent ) : KAssistantDialog( parent ) , sourcePage(nullptr) , openPage(nullptr) , projectInfoPage(nullptr) { resize(QSize(700, 500)); const bool useKdeFileDialog = qEnvironmentVariableIsSet("KDE_FULL_SESSION"); QStringList filters, allEntry; QString filterFormat = useKdeFileDialog ? QStringLiteral("%1|%2 (%1)") : QStringLiteral("%2 (%1)"); allEntry << "*." + ShellExtension::getInstance()->projectFileExtension(); filters << filterFormat.arg("*." + ShellExtension::getInstance()->projectFileExtension(), ShellExtension::getInstance()->projectFileDescription()); QVector plugins = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); foreach(const KPluginMetaData& info, plugins) { QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); QString desc = info.value(QStringLiteral("X-KDevelop-ProjectFilesFilterDescription")); if (!filter.isEmpty() && !desc.isEmpty()) { m_projectFilters.insert(info.name(), filter); allEntry += filter; filters << filterFormat.arg(filter.join(QStringLiteral(" ")), desc); } } if (useKdeFileDialog) filters.prepend(i18n("%1|All Project Files (%1)", allEntry.join(QStringLiteral(" ")))); else filters.prepend(i18n("All Project Files (%1)", allEntry.join(QStringLiteral(" ")))); QUrl start = startUrl.isValid() ? startUrl : Core::self()->projectController()->projectsBaseDirectory(); start = start.adjusted(QUrl::NormalizePathSegments); KPageWidgetItem* currentPage = 0; if( fetch ) { sourcePageWidget = new ProjectSourcePage( start, this ); connect( sourcePageWidget, &ProjectSourcePage::isCorrect, this, &OpenProjectDialog::validateSourcePage ); sourcePage = addPage( sourcePageWidget, i18n("Select Source") ); currentPage = sourcePage; } if (useKdeFileDialog) { openPageWidget = new OpenProjectPage( start, filters, this ); connect( openPageWidget, &OpenProjectPage::urlSelected, this, &OpenProjectDialog::validateOpenUrl ); connect( openPageWidget, &OpenProjectPage::accepted, this, &OpenProjectDialog::openPageAccepted ); openPage = addPage( openPageWidget, i18n("Select a build system setup file, existing KDevelop project, " "or any folder to open as a project") ); if (!currentPage) { currentPage = openPage; } } else { nativeDialog = new QFileDialog(this, i18n("Open Project")); nativeDialog->setDirectoryUrl(start); nativeDialog->setFileMode(QFileDialog::ExistingFile); nativeDialog->setNameFilters(filters); } ProjectInfoPage* page = new ProjectInfoPage( this ); connect( page, &ProjectInfoPage::projectNameChanged, this, &OpenProjectDialog::validateProjectName ); connect( page, &ProjectInfoPage::projectManagerChanged, this, &OpenProjectDialog::validateProjectManager ); projectInfoPage = addPage( page, i18n("Project Information") ); if (!currentPage) { currentPage = projectInfoPage; } setValid( sourcePage, false ); setValid( openPage, false ); setValid( projectInfoPage, false); setAppropriate( projectInfoPage, false ); setCurrentPage( currentPage ); setWindowTitle(i18n("Open Project")); } bool OpenProjectDialog::execNativeDialog() { while (true) { if (nativeDialog->exec()) { - QUrl selectedUrl = nativeDialog->selectedUrls()[0]; + QUrl selectedUrl = nativeDialog->selectedUrls().at(0); if (getUrlInfo(selectedUrl).isValid) { // validate directory first to populate m_projectName and m_projectManager validateOpenUrl(selectedUrl.adjusted(QUrl::RemoveFilename)); validateOpenUrl(selectedUrl); return true; } } else { return false; } } } int OpenProjectDialog::exec() { if (nativeDialog && !execNativeDialog()) { reject(); return QDialog::Rejected; } return KAssistantDialog::exec(); } void OpenProjectDialog::validateSourcePage(bool valid) { setValid(sourcePage, valid); openPageWidget->setUrl(sourcePageWidget->workingDir()); } void OpenProjectDialog::validateOpenUrl( const QUrl& url_ ) { URLInfo urlInfo = getUrlInfo(url_); const QUrl url = url_.adjusted(QUrl::StripTrailingSlash); // openPage is used only in KDE if (openPage) { if ( urlInfo.isValid ) { // reset header openPage->setHeader(i18n("Open \"%1\" as project", url.fileName())); } else { // report error KColorScheme scheme(palette().currentColorGroup()); const QString errorMsg = i18n("Selected URL is invalid"); openPage->setHeader(QStringLiteral("%2") .arg(scheme.foreground(KColorScheme::NegativeText).color().name(), errorMsg) ); setAppropriate( projectInfoPage, false ); setAppropriate( openPage, true ); setValid( openPage, false ); return; } } m_selected = url; if( urlInfo.isDir || urlInfo.extension != ShellExtension::getInstance()->projectFileExtension() ) { setAppropriate( projectInfoPage, true ); m_url = url; if( !urlInfo.isDir ) { m_url = m_url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename); } ProjectInfoPage* page = qobject_cast( projectInfoPage->widget() ); if( page ) { page->setProjectName( m_url.fileName() ); // Default manager page->setProjectManager( QStringLiteral("Generic Project Manager") ); // clear the filelist m_fileList.clear(); if( urlInfo.isDir ) { // If a dir was selected fetch all files in it KIO::ListJob* job = KIO::listDir( m_url ); connect( job, &KIO::ListJob::entries, this, &OpenProjectDialog::storeFileList); KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); job->exec(); } else { // Else we'lll just take the given file m_fileList << url.fileName(); } // Now find a manager for the file(s) in our filelist. bool managerFound = false; foreach( const QString& manager, m_projectFilters.keys() ) { foreach( const QString& filterexp, m_projectFilters.value(manager) ) { if( !m_fileList.filter( QRegExp( filterexp, Qt::CaseSensitive, QRegExp::Wildcard ) ).isEmpty() ) { managerFound = true; break; } } if( managerFound ) { page->setProjectManager( manager ); break; } } } m_url.setPath( m_url.path() + '/' + m_url.fileName() + '.' + ShellExtension::getInstance()->projectFileExtension() ); } else { setAppropriate( projectInfoPage, false ); m_url = url; } validateProjectInfo(); setValid( openPage, true ); } void OpenProjectDialog::openPageAccepted() { if ( isValid( openPage ) ) { next(); } } void OpenProjectDialog::validateProjectName( const QString& name ) { m_projectName = name; validateProjectInfo(); } void OpenProjectDialog::validateProjectInfo() { setValid( projectInfoPage, (!projectName().isEmpty() && !projectManager().isEmpty()) ); } void OpenProjectDialog::validateProjectManager( const QString& manager ) { m_projectManager = manager; validateProjectInfo(); } QUrl OpenProjectDialog::projectFileUrl() const { return m_url; } QUrl OpenProjectDialog::selectedUrl() const { return m_selected; } QString OpenProjectDialog::projectName() const { return m_projectName; } QString OpenProjectDialog::projectManager() const { return m_projectManager; } void OpenProjectDialog::storeFileList(KIO::Job*, const KIO::UDSEntryList& list) { foreach( const KIO::UDSEntry& entry, list ) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if( name != QLatin1String(".") && name != QLatin1String("..") && !entry.isDir() ) { m_fileList << name; } } } } diff --git a/shell/problemstore.cpp b/shell/problemstore.cpp index 6c18190cbb..ee9ee5c3da 100644 --- a/shell/problemstore.cpp +++ b/shell/problemstore.cpp @@ -1,230 +1,230 @@ /* * Copyright 2015 Laszlo Kis-Adam * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemstore.h" #include #include #include "problemstorenode.h" struct ProblemStorePrivate { ProblemStorePrivate() : m_documents(nullptr) , m_severities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint) , m_rootNode(new KDevelop::ProblemStoreNode()) { } /// Watched document set. Only problems that are in files in this set are stored. KDevelop::WatchedDocumentSet *m_documents; /// The severity filter setting KDevelop::IProblem::Severities m_severities; /// The problems list KDevelop::ProblemStoreNode *m_rootNode; /// Path of the currently open document KDevelop::IndexedString m_currentDocument; }; namespace KDevelop { ProblemStore::ProblemStore(QObject *parent) : QObject(parent), d(new ProblemStorePrivate) { } ProblemStore::~ProblemStore() { clear(); delete d->m_rootNode; } void ProblemStore::addProblem(const IProblem::Ptr &problem) { ProblemNode *node = new ProblemNode(d->m_rootNode); node->setProblem(problem); d->m_rootNode->addChild(node); } void ProblemStore::setProblems(const QVector &problems) { clear(); - foreach (IProblem::Ptr problem, problems) { + foreach (const IProblem::Ptr &problem, problems) { d->m_rootNode->addChild(new ProblemNode(d->m_rootNode, problem)); } rebuild(); } const ProblemStoreNode* ProblemStore::findNode(int row, ProblemStoreNode *parent) const { Q_UNUSED(parent); return d->m_rootNode->child(row); } int ProblemStore::count(ProblemStoreNode *parent) const { if(parent) return parent->count(); else return d->m_rootNode->count(); } void ProblemStore::clear() { d->m_rootNode->clear(); } void ProblemStore::rebuild() { } void ProblemStore::setSeverity(int severity) { switch (severity) { case KDevelop::IProblem::Error: setSeverities(KDevelop::IProblem::Error); break; case KDevelop::IProblem::Warning: setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning); break; case KDevelop::IProblem::Hint: setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint); break; } } void ProblemStore::setSeverities(KDevelop::IProblem::Severities severities) { if(severities != d->m_severities) { d->m_severities = severities; rebuild(); emit changed(); } } int ProblemStore::severity() const { if (d->m_severities.testFlag(KDevelop::IProblem::Hint)) return KDevelop::IProblem::Hint; if (d->m_severities.testFlag(KDevelop::IProblem::Warning)) return KDevelop::IProblem::Warning; if (d->m_severities.testFlag(KDevelop::IProblem::Error)) return KDevelop::IProblem::Error; return 0; } KDevelop::IProblem::Severities ProblemStore::severities() const { return d->m_severities; } WatchedDocumentSet* ProblemStore::documents() const { return d->m_documents; } void ProblemStore::setScope(int scope) { ProblemScope cast_scope = static_cast(scope); if (cast_scope == BypassScopeFilter) { return; } if (d->m_documents) { if(cast_scope == d->m_documents->getScope()) return; delete d->m_documents; } switch (cast_scope) { case CurrentDocument: d->m_documents = new CurrentDocumentSet(d->m_currentDocument, this); break; case OpenDocuments: d->m_documents = new OpenDocumentSet(this); break; case CurrentProject: d->m_documents = new CurrentProjectSet(d->m_currentDocument, this); break; case AllProjects: d->m_documents = new AllProjectSet(this); break; case BypassScopeFilter: // handled above break; } rebuild(); connect(d->m_documents, &WatchedDocumentSet::changed, this, &ProblemStore::onDocumentSetChanged); emit changed(); } int ProblemStore::scope() const { Q_ASSERT(d->m_documents != nullptr); return d->m_documents->getScope(); } void ProblemStore::setGrouping(int grouping) { Q_UNUSED(grouping); } void ProblemStore::setCurrentDocument(const IndexedString &doc) { d->m_currentDocument = doc; d->m_documents->setCurrentDocument(doc); } const KDevelop::IndexedString& ProblemStore::currentDocument() const { return d->m_currentDocument; } void ProblemStore::onDocumentSetChanged() { rebuild(); emit changed(); } ProblemStoreNode* ProblemStore::rootNode() { return d->m_rootNode; } } diff --git a/shell/runcontroller.cpp b/shell/runcontroller.cpp index bdb2667dee..93b6d20432 100644 --- a/shell/runcontroller.cpp +++ b/shell/runcontroller.cpp @@ -1,1057 +1,1057 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "unitylauncher.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { namespace Strings { QString LaunchConfigurationsGroup() { return QStringLiteral("Launch"); } QString LaunchConfigurationsListEntry() { return QStringLiteral("Launch Configurations"); } QString CurrentLaunchConfigProjectEntry() { return QStringLiteral("Current Launch Config Project"); } QString CurrentLaunchConfigNameEntry() { return QStringLiteral("Current Launch Config GroupName"); } QString ConfiguredFromProjectItemEntry() { return QStringLiteral("Configured from ProjectItem"); } } } typedef QPair Target; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("tools-report-bug")); } QString id() const override { return QStringLiteral("debug"); } QString name() const override { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); } QString id() const override { return QStringLiteral("profile"); } QString name() const override { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); } QString id() const override { return QStringLiteral("execute"); } QString name() const override { return i18n("Execute"); } }; class RunController::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QSignalMapper* launchChangeMapper; QSignalMapper* launchAsMapper; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; UnityLauncher* unityLauncher; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QLatin1String("") ); grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() ); grp.sync(); } } void configureLaunches() { LaunchConfigurationDialog dlg; dlg.exec(); } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { ILauncher* launcher = 0; foreach (ILauncher *l, type->launchers()) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } if (launcher) { QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); ILaunchConfiguration* ilaunch = 0; foreach (LaunchConfiguration *l, launchConfigurations) { QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; ilaunch = l; break; } } if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); LaunchConfiguration* launch = dynamic_cast( ilaunch ); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" ); QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" ); LaunchConfiguration* l = 0; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { foreach( QAction* a, currentTargetAction->actions() ) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().at(0)->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(qVariantFromValue(l)); } void readLaunchConfigs( KSharedConfigPtr cfg, IProject* prj ) { KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup()); QStringList configs = group.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); foreach( const QString& cfg, configs ) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qWarning() << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return 0; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d(new RunControllerPrivate) { setObjectName(QStringLiteral("RunController")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"), this, QDBusConnection::ExportScriptableSlots); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = 0; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->launchChangeMapper = new QSignalMapper( this ); d->launchAsMapper = 0; d->contextItem = 0; d->executeMode = 0; d->debugMode = 0; d->profileMode = 0; d->unityLauncher = new UnityLauncher(this); // desktopFileName() reports itself as "org.kdevelop.kdevelop" which is rubbish d->unityLauncher->setLauncherId(KAboutData::applicationData().componentName() + QLatin1String(".desktop")); if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() { delete d; } void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { delete d->executeMode; d->executeMode = 0; delete d->profileMode; d->profileMode = 0; delete d->debugMode; d->debugMode = 0; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), 0 ); foreach (IProject* project, Core::self()->projectController()->projects()) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; return 0; } LaunchConfiguration *run = dynamic_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { KMessageBox::error( qApp->activeWindow(), i18n("The current launch configuration does not support the '%1' mode.", runMode), QLatin1String("")); return 0; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); action = new QAction(i18n("Configure Launches..."), this); ac->addAction(QStringLiteral("configure_launches"), action); action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, [&] { d->configureLaunches(); }); d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction(QStringLiteral("run_execute"), d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18n("Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction(QStringLiteral("run_debug"), d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape"))); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_all"), action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action); action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_menu"), action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { if (!d->currentTargetAction) return; foreach (QAction* action, d->currentTargetAction->actions()) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().at(0)->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( QStringLiteral("debug") ); } void RunController::slotProfile() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( QStringLiteral("profile") ); } void RunController::slotExecute() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( QStringLiteral("execute") ); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return 0; } void KDevelop::RunController::registerJob(KJob * job) { if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qWarning() << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { QAction* stopJobAction = 0; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", job->staticMetaObject.className()) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); // FIXME percent is a private signal and thus we cannot use new connext syntax connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(jobPercentChanged())); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { bool running = false; int jobCount = 0; int totalProgress = 0; for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) { KJob *job = it.key(); if (!job->isSuspended()) { running = true; ++jobCount; totalProgress += job->percent(); } } d->unityLauncher->setProgressVisible(running); if (jobCount > 0) { d->unityLauncher->setProgress((totalProgress + 1) / jobCount); } else { d->unityLauncher->setProgress(0); } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 // foreach already iterates over a copy foreach (KJob* job, d->jobs.keys()) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qWarning() << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { QAction* action = dynamic_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { ///WARNING: do *not* use a nested event loop here, it might cause /// random crashes later on, see e.g.: /// https://bugs.kde.org/show_bug.cgi?id=309811 auto dialog = new QDialog(qApp->activeWindow()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Process Error")); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, dialog); KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Warning, job->errorString(), QStringList(), QString(), 0, KMessageBox::NoExec); dialog->show(); } } } void RunController::jobDestroyed(QObject* job) { KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qWarning() << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void RunController::jobPercentChanged() { checkState(); } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; foreach (LaunchConfiguration *config, launchConfigurationsInternal()) configs << config; return configs; } QList RunController::launchConfigurationsInternal() const { return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { foreach( LaunchConfiguration* l, d->launchConfigurations ) { if( l->type() == type ) { removeLaunchConfigurationInternal( l ); } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { QMap::iterator it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return 0; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launcherGroup.sync(); removeLaunchConfigurationInternal( l ); } void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { auto dl = defaultLaunch(); if( !dl ) { qWarning() << "no default launch!"; return; } execute( runMode, dl ); } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { foreach(ILaunchConfiguration* config, Core::self()->runControllerInternal()->launchConfigurations()) { if(config->name()==name) return true; } return false; } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); uint num = 0; QString baseName = QStringLiteral("Launch Configuration"); while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launchGroup.sync(); LaunchConfiguration* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { return d->delegate; } ContextMenuExtension RunController::contextMenuExtension ( Context* ctx ) { delete d->launchAsMapper; d->launchAsMapper = new QSignalMapper( this ); connect( d->launchAsMapper, static_cast(&QSignalMapper::mapped), this, [&] (int id) { d->launchAs(id); } ); d->launchAsInfo.clear(); d->contextItem = 0; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { KDevelop::ProjectItemContext* prjctx = dynamic_cast( ctx ); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; - foreach( ILaunchMode* mode, d->launchModes.values() ) + foreach( ILaunchMode* mode, d->launchModes ) { KActionMenu* menu = new KActionMenu( i18n("%1 As...", mode->name() ), this ); foreach( LaunchConfigurationType* type, launchConfigurationTypes() ) { bool hasLauncher = false; foreach( ILauncher* launcher, type->launchers() ) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); QAction* act = new QAction( d->launchAsMapper ); act->setText( type->name() ); qCDebug(SHELL) << "Setting up mapping for:" << i << "for action" << act->text() << "in mode" << mode->name(); d->launchAsMapper->setMapping( act, i ); connect( act, &QAction::triggered, d->launchAsMapper, static_cast(&QSignalMapper::map) ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/shell/sessioncontroller.cpp b/shell/sessioncontroller.cpp index adf8314d5d..2c5d6eac5d 100644 --- a/shell/sessioncontroller.cpp +++ b/shell/sessioncontroller.cpp @@ -1,678 +1,664 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessioncontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" -#include "sessiondialog.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.h" #include "debug.h" #include #include #include #include #include namespace KDevelop { namespace { int argc = 0; char** argv = 0; }; void SessionController::setArguments(int _argc, char** _argv) { argc = _argc; argv = _argv; } static QStringList standardArguments() { QStringList ret; for(int a = 0; a < argc; ++a) { QString arg = QString::fromLocal8Bit(argv[a]); if(arg.startsWith(QLatin1String("-graphicssystem")) || arg.startsWith(QLatin1String("-style"))) { ret << '-' + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(0) , grp(0) { } ~SessionControllerPrivate() override { } Session* findSessionForName( const QString& name ) const { foreach( Session* s, sessionActions.keys() ) { if( s->name() == name ) return s; } return 0; } Session* findSessionForId(QString idString) { QUuid id(idString); foreach( Session* s, sessionActions.keys() ) { if( s->id() == id) return s; } return 0; } void newSession() { qsrand(QDateTime::currentDateTimeUtc().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << QStringLiteral("-s") << session->id().toString() << standardArguments()); delete session; #if 0 //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); #endif } - void configureSessions() - { - SessionDialog dlg(ICore::self()->uiController()-> activeMainWindow()); - dlg.exec(); - } - void deleteCurrentSession() { int choice = KMessageBox::warningContinueCancel(Core::self()->uiController()->activeMainWindow(), i18n("The current session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?")); if(choice == KMessageBox::Continue) { q->deleteSessionFromDisk(sessionLock); q->emitQuitSession(); } } void renameSession() { bool ok; auto newSessionName = QInputDialog::getText(Core::self()->uiController()->activeMainWindow(), i18n("Rename Session"), i18n("New Session Name:"), QLineEdit::Normal, q->activeSession()->name(), &ok); if (ok) { static_cast(q->activeSession())->setName(newSessionName); } q->updateXmlGuiActionList(); // resort } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << QStringLiteral("-s") << s->id().toString() << standardArguments()); return true; } TryLockSessionResult activateSession( Session* s ) { Q_ASSERT( s ); activeSession = s; TryLockSessionResult result = SessionController::tryLockSession( s->id().toString()); if( !result.lock ) { activeSession = 0; return result; } Q_ASSERT(s->id().toString() == result.lock->id()); sessionLock = result.lock; KConfigGroup grp = KSharedConfig::openConfig()->group( SessionController::cfgSessionGroup() ); grp.writeEntry( SessionController::cfgActiveSessionEntry(), s->id().toString() ); grp.sync(); if (Core::self()->setupFlags() & Core::NoUi) return result; QHash::iterator it = sessionActions.find(s); Q_ASSERT( it != sessionActions.end() ); (*it)->setCheckable(true); (*it)->setChecked(true); for(it = sessionActions.begin(); it != sessionActions.end(); ++it) { if(it.key() != s) (*it)->setCheckable(false); } return result; } void loadSessionFromAction(QAction* action) { auto session = action->data().value(); loadSessionExternally(session); } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { sessionActions[s] = 0; return; } QAction* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData(QVariant::fromValue(s)); sessionActions[s] = a; q->actionCollection()->addAction( "session_"+s->id().toString(), a ); connect( s, &Session::sessionUpdated, this, &SessionControllerPrivate::sessionUpdated ); sessionUpdated( s ); } SessionController* q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ qApp->applicationName() + "/sessions/"; } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } private slots: void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } }; SessionController::SessionController( QObject *parent ) : QObject( parent ), d(new SessionControllerPrivate(this)) { setObjectName(QStringLiteral("SessionController")); setComponentName(QStringLiteral("kdevsession"), QStringLiteral("KDevSession")); setXMLFile(QStringLiteral("kdevsessionui.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/SessionController"), this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction( QStringLiteral("new_session"), this, SLOT(newSession()) ); action->setText( i18nc("@action:inmenu", "Start New Session") ); action->setToolTip( i18nc("@info:tooltip", "Start a new KDevelop instance with an empty session") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); action = actionCollection()->addAction( QStringLiteral("rename_session"), this, SLOT(renameSession()) ); action->setText( i18n("Rename Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); action = actionCollection()->addAction( QStringLiteral("delete_session"), this, SLOT(deleteCurrentSession()) ); action->setText( i18n("Delete Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action = actionCollection()->addAction( QStringLiteral("quit"), this, SIGNAL(quitSession()) ); action->setText( i18n("Quit") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'Quit KDevelop...' actionCollection()->setDefaultShortcut( action, Qt::CTRL | Qt::Key_Q ); action->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); - #if 0 - action = actionCollection()->addAction( "configure_sessions", this, SLOT(configureSessions()) ); - action->setText( i18n("Configure Sessions...") ); - action->setToolTip( i18n("Create/Delete/Activate Sessions") ); - action->setWhatsThis( i18n( "Shows a dialog to Create/Delete Sessions and set a new active session." ) ); - #endif - d->grp = new QActionGroup( this ); connect( d->grp, &QActionGroup::triggered, this, [&] (QAction* a) { d->loadSessionFromAction(a); } ); } SessionController::~SessionController() { delete d; } void SessionController::startNewSession() { d->newSession(); } void SessionController::cleanup() { if (d->activeSession) { Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); if (d->activeSession->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } d->activeSession = 0; } d->sessionLock.clear(); qDeleteAll(d->sessionActions); d->sessionActions.clear(); } void SessionController::initialize( const QString& session ) { QDir sessiondir( SessionControllerPrivate::sessionBaseDirectory() ); foreach( const QString& s, sessiondir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot ) ) { QUuid id( s ); if( id.isNull() ) continue; // Only create sessions for directories that represent proper uuid's Session* ses = new Session( id.toString(), this ); //Delete sessions that have no name and are empty if( ses->containedProjects().isEmpty() && ses->name().isEmpty() && (session.isEmpty() || (ses->id().toString() != session && ses->name() != session)) ) { TryLockSessionResult result = tryLockSession(s); if (result.lock) { deleteSessionFromDisk(result.lock); } delete ses; } else { d->addSession( ses ); } } loadDefaultSession( session ); updateXmlGuiActionList(); } ISession* SessionController::activeSession() const { return d->activeSession; } ISessionLock::Ptr SessionController::activeSessionLock() const { return d->sessionLock; } void SessionController::loadSession( const QString& nameOrId ) { d->loadSessionExternally( session( nameOrId ) ); } QList SessionController::sessionNames() const { QStringList l; foreach( const Session* s, d->sessionActions.keys() ) { l << s->name(); } return l; } QList< const KDevelop::Session* > SessionController::sessions() const { QList< const KDevelop::Session* > ret; foreach( const Session* s, d->sessionActions.keys() ) { ret << s; } return ret; } Session* SessionController::createSession( const QString& name ) { Session* s; if(name.startsWith('{')) { s = new Session( QUuid(name).toString() ); }else{ qsrand(QDateTime::currentDateTimeUtc().toTime_t()); s = new Session( QUuid::createUuid().toString() ); s->setName( name ); } d->addSession( s ); updateXmlGuiActionList(); return s; } void SessionController::deleteSession( const ISessionLock::Ptr& lock ) { Session* s = session(lock->id()); QHash::iterator it = d->sessionActions.find(s); Q_ASSERT( it != d->sessionActions.end() ); unplugActionList( QStringLiteral("available_sessions") ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( QStringLiteral("available_sessions"), d->grp->actions() ); } if (s == d->activeSession) { d->activeSession = nullptr; } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); delete s; } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { qCDebug(SHELL) << "Deleting session:" << lock->id(); static_cast(lock.data())->removeFromDisk(); ItemRepositoryRegistry::deleteRepositoryFromDisk( lock ); } void SessionController::loadDefaultSession( const QString& session ) { QString load = session; if (load.isEmpty()) { KConfigGroup grp = KSharedConfig::openConfig()->group( cfgSessionGroup() ); load = grp.readEntry( cfgActiveSessionEntry(), "default" ); } // Iteratively try to load the session, asking user what to do in case of failure // If showForceOpenDialog() returns empty string, stop trying Session* s = 0; do { s = this->session( load ); if( !s ) { s = createSession( load ); } TryLockSessionResult result = d->activateSession( s ); if( result.lock ) { Q_ASSERT(d->activeSession == s); Q_ASSERT(d->sessionLock = result.lock); break; } load = handleLockedSession( s->name(), s->id().toString(), result.runInfo ); } while( !load.isEmpty() ); } Session* SessionController::session( const QString& nameOrId ) const { Session* ret = d->findSessionForName( nameOrId ); if(ret) return ret; return d->findSessionForId( nameOrId ); } QString SessionController::cloneSession( const QString& nameOrid ) { Session* origSession = session( nameOrid ); qsrand(QDateTime::currentDateTimeUtc().toTime_t()); QUuid id = QUuid::createUuid(); auto copyJob = KIO::copy(QUrl::fromLocalFile(sessionDirectory(origSession->id().toString())), QUrl::fromLocalFile(sessionDirectory( id.toString()))); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); copyJob->exec(); Session* newSession = new Session( id.toString() ); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); updateXmlGuiActionList(); return newSession->name(); } void SessionController::updateXmlGuiActionList() { unplugActionList( QStringLiteral("available_sessions") ); if (d->grp) { auto actions = d->grp->actions(); std::sort(actions.begin(), actions.end(), [](const QAction* lhs, const QAction* rhs) { auto s1 = lhs->data().value(); auto s2 = rhs->data().value(); return QString::localeAwareCompare(s1->description(), s2->description()) < 0; }); plugActionList(QStringLiteral("available_sessions"), actions); } } QString SessionController::cfgSessionGroup() { return QStringLiteral("Sessions"); } QString SessionController::cfgActiveSessionEntry() { return QStringLiteral("Active Session ID"); } QList SessionController::availableSessionInfo() { return availableSessionInfos().toList(); } SessionInfos SessionController::availableSessionInfos() { SessionInfos sessionInfos; foreach( const QString& sessionId, QDir( SessionControllerPrivate::sessionBaseDirectory() ).entryList( QDir::AllDirs ) ) { if( !QUuid( sessionId ).isNull() ) { sessionInfos << Session::parse( sessionId ); } } return sessionInfos; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id) { return SessionLock::tryLockSession(id, true); } bool SessionController::isSessionRunning(const QString& id) { return sessionRunInfo(id).isRunning; } SessionRunInfo SessionController::sessionRunInfo(const QString& id) { return SessionLock::tryLockSession(id, false).runInfo; } QString SessionController::showSessionChooserDialog(QString headerText, bool onlyRunning) { ///FIXME: move this code into sessiondialog.cpp QListView* view = new QListView; QLineEdit* filter = new QLineEdit; filter->setClearButtonEnabled( true ); filter->setPlaceholderText(i18n("Search")); QStandardItemModel* model = new QStandardItemModel(view); QSortFilterProxyModel *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, &QLineEdit::textChanged, proxy, &QSortFilterProxyModel::setFilterFixedString); SessionChooserDialog dialog(view, proxy, filter); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QVBoxLayout layout(dialog.mainWidget()); if(!headerText.isEmpty()) { QLabel* heading = new QLabel(headerText); QFont font = heading->font(); font.setBold(true); heading->setFont(font); layout.addWidget(heading); } model->setColumnCount(3); model->setHeaderData(0, Qt::Horizontal,i18n("Identity")); model->setHeaderData(1, Qt::Horizontal, i18n("Contents")); model->setHeaderData(2, Qt::Horizontal,i18n("State")); view->setModel(proxy); view->setModelColumn(1); QHBoxLayout* filterLayout = new QHBoxLayout(); filterLayout->addWidget(new QLabel(i18n("Filter:"))); filterLayout->addWidget(filter); layout.addLayout(filterLayout); layout.addWidget(view); filter->setFocus(); int row = 0; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); ++row; } model->sort(1); if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); const QModelIndex defaultSessionIndex = model->match(model->index(0, 0), Qt::DisplayRole, defaultSession, 1, Qt::MatchExactly).value(0); view->selectionModel()->setCurrentIndex(proxy->mapFromSource(defaultSessionIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ///@todo We need a way to get a proper size-hint from the view, but unfortunately, that only seems possible after the view was shown. dialog.resize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); if (!selected.isValid()) return QString(); const QString selectedSessionId = selected.sibling(selected.row(), 0).data().toString(); if (selectedSessionId.isEmpty()) { // "Create New Session" item selected, return a fresh UUID qsrand(QDateTime::currentDateTimeUtc().toTime_t()); return QUuid::createUuid().toString(); } return selectedSessionId; } QString SessionController::handleLockedSession( const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo ) { return SessionLock::handleLockedSession(sessionName, sessionId, runInfo); } QString SessionController::sessionDir() { if( !activeSession() ) return QString(); return d->ownSessionDirectory(); } QString SessionController::sessionName() { if(!activeSession()) return QString(); return activeSession()->description(); } } #include "sessioncontroller.moc" #include "moc_sessioncontroller.cpp" diff --git a/shell/sessioncontroller.h b/shell/sessioncontroller.h index 28521b21f5..f358540fa5 100644 --- a/shell/sessioncontroller.h +++ b/shell/sessioncontroller.h @@ -1,181 +1,180 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2013 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_SESSIONCONTROLLER_H #define KDEVPLATFORM_SESSIONCONTROLLER_H #include "shellexport.h" #include "session.h" #include #include #include namespace KDevelop { struct SessionRunInfo { SessionRunInfo() : isRunning(false) , holderPid(-1) {} bool operator==(const SessionRunInfo& o) const { return isRunning == o.isRunning && holderPid == o.holderPid && holderApp == o.holderApp && holderHostname == o.holderHostname; } bool operator!=(const SessionRunInfo& o) const { return !(operator==(o)); } // if this is true, this session is currently running in an external process bool isRunning; // if the session is running, this contains the PID of its process qint64 holderPid; // if the session is running, this contains the name of its process QString holderApp; // if the session is running, this contains the host name where the process runs QString holderHostname; }; struct TryLockSessionResult { TryLockSessionResult(const ISessionLock::Ptr& _lock) : lock(_lock) {} TryLockSessionResult(const SessionRunInfo& _runInfo) : runInfo(_runInfo) {} // if this is non-null then the session was locked ISessionLock::Ptr lock; // otherwise this contains information about who is locking the session SessionRunInfo runInfo; }; class KDEVPLATFORMSHELL_EXPORT SessionController : public QObject, public KXMLGUIClient { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kdevelop.SessionController") public: explicit SessionController( QObject *parent = nullptr ); ~SessionController() override; void initialize( const QString& session ); void cleanup(); /// Returns whether the given session can be locked (i. e., is not locked currently). /// @param doLocking whether to really lock the session or just "dry-run" the locking process static TryLockSessionResult tryLockSession(const QString& id); /** * @return true when the given session is currently running, false otherwise */ static bool isSessionRunning(const QString& id); /** * @return information about whether the session @p id is running */ static SessionRunInfo sessionRunInfo(const QString& id); /// The application should call this on startup to tell the /// session-controller about the received arguments. /// Some of them may need to be passed to newly opened sessions. static void setArguments(int argc, char** argv); ///Finds a session by its name or by its UUID Session* session( const QString& nameOrId ) const; virtual ISession* activeSession() const; ISessionLock::Ptr activeSessionLock() const; QList sessionNames() const; Session* createSession( const QString& name ); QList sessions() const; void loadDefaultSession( const QString& session ); void startNewSession(); void loadSession( const QString& nameOrId ); void deleteSession( const ISessionLock::Ptr& lock ); static void deleteSessionFromDisk( const ISessionLock::Ptr& lock ); QString cloneSession( const QString& nameOrid ); /** * Path to session directory for the session with the given @p sessionId. */ static QString sessionDirectory( const QString& sessionId ); static QString cfgSessionGroup(); static QString cfgActiveSessionEntry(); static QT_DEPRECATED QList availableSessionInfo(); // use availableSessionInfos() static SessionInfos availableSessionInfos(); /** * Shows a dialog where the user can choose the session * @param headerText an additional text that will be shown at the top in a label * @param onlyRunning whether only currently running sessions should be shown * @return UUID on success, empty string in any other case */ static QString showSessionChooserDialog(QString headerText = QString(), bool onlyRunning = false); /// Should be called if session to be opened is locked. /// It attempts to bring existing instance's window up via a DBus call; if that succeeds, empty string is returned. /// Otherwise (if the app did not respond) it shows a dialog where the user may choose /// 1) to force-remove the lockfile and continue, /// 2) to select another session via \ref showSessionChooserDialog, /// 3) to quit the current (starting-up) instance. /// @param sessionName session name (for the message) /// @param sessionId current session GUID (to return if user chooses force-removal) /// @param runInfo the run information about the session /// @return new session GUID to try or an empty string if application startup shall be aborted static QString handleLockedSession( const QString& sessionName, const QString& currentSessionId, const SessionRunInfo& runInfo ); void updateXmlGuiActionList(); void emitQuitSession() { emit quitSession(); } public Q_SLOTS: // Returns the pretty name of the currently active session (used in the shell integration) virtual Q_SCRIPTABLE QString sessionName(); // Returns the directory associated to the active session (used in the shell integration) virtual Q_SCRIPTABLE QString sessionDir(); Q_SIGNALS: void sessionLoaded( ISession* ); void sessionDeleted( const QString& id); void quitSession(); private: Q_PRIVATE_SLOT( d, void newSession() ) - Q_PRIVATE_SLOT( d, void configureSessions() ) Q_PRIVATE_SLOT( d, void deleteCurrentSession() ) Q_PRIVATE_SLOT( d, void renameSession() ) Q_PRIVATE_SLOT( d, void loadSessionFromAction( QAction* ) ) class SessionControllerPrivate* const d; }; } #endif diff --git a/shell/sessiondialog.cpp b/shell/sessiondialog.cpp deleted file mode 100644 index 95b867b202..0000000000 --- a/shell/sessiondialog.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/* This file is part of KDevelop -Copyright 2008 Andreas Pakulat - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#include "sessiondialog.h" -#include "ui_sessiondialog.h" - -#include -#include - -#include -#include - -#include "core.h" -#include "sessioncontroller.h" -#include "session.h" - -namespace KDevelop -{ - -QString newSessionName() { return QStringLiteral("New Session"); } - -SessionModel::SessionModel( QObject* parent ) - : QAbstractListModel( parent ) -{ -} - -int SessionModel::rowCount( const QModelIndex& parent ) const -{ - if( parent.isValid() ) - return 0; - return Core::self()->sessionController()->sessionNames().count(); -} - -QVariant SessionModel::headerData( int, Qt::Orientation, int ) const -{ - return QVariant(); -} - -QVariant SessionModel::data( const QModelIndex& idx, int role ) const -{ - if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() - || ( role != Qt::DisplayRole && role != Qt::BackgroundRole && role != Qt::EditRole && role != Qt::FontRole ) ) - { - return QVariant(); - } - const Session* s = Core::self()->sessionController()->sessions().at( idx.row() ); - if( role == Qt::DisplayRole ) - { - return s->description(); - } else if( role == Qt::EditRole ) - { - return s->name(); - } else if( role == Qt::FontRole ) - { - QFont f = QFontDatabase::systemFont(QFontDatabase::GeneralFont); - if( Core::self()->activeSession()->name() == s->name() ) - { - f.setBold( true ); - } - return QVariant::fromValue( f ); - } else - { - if( Core::self()->activeSession()->name() == s->name() ) - { - return KColorScheme( QPalette::Active ).background( KColorScheme::ActiveBackground ); - } else - { - return KColorScheme( QPalette::Active ).background( KColorScheme::NormalBackground ); - } - } -} - -Qt::ItemFlags SessionModel::flags( const QModelIndex& idx ) const -{ - if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() ) - { - return 0; - } - return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; -} - -bool SessionModel::setData( const QModelIndex& idx, const QVariant& value, int role ) -{ - if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() || role != Qt::EditRole || !value.isValid() || !value.canConvert( QVariant::String ) ) - { - return false; - } - QString sname = Core::self()->sessionController()->sessionNames().at( idx.row() ); - Session* s = Core::self()->sessionController()->session( sname ); - s->setName( value.toString() ); - emit dataChanged( idx, idx ); - return true; -} - -void SessionModel::addSession() -{ - beginInsertRows( QModelIndex(), rowCount(), rowCount() ); - Core::self()->sessionController()->createSession( (newSessionName()) ); - endInsertRows(); -} - -void SessionModel::deleteSessions( const QList& indexes ) -{ - if( indexes.isEmpty() ) - { - return; - } - QVector deleteSessions; - int startRow = rowCount(), endRow = -1; - foreach( const QModelIndex& idx, indexes ) - { - if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() ) - { - continue; - } - QString sname = Core::self()->sessionController()->sessionNames().at( idx.row() ); - TryLockSessionResult locked = SessionController::tryLockSession( sname ); - if (!locked.lock) { - continue; - } - if( idx.row() < startRow ) - startRow = idx.row(); - if( idx.row() > endRow ) - endRow = idx.row(); - deleteSessions << locked.lock; - } - beginRemoveRows( QModelIndex(), startRow, endRow ); - foreach( const ISessionLock::Ptr& session, deleteSessions ) - { - Core::self()->sessionController()->deleteSessionFromDisk( session ); - } - endRemoveRows(); -} - -void SessionModel::activateSession( const QModelIndex& idx ) -{ - if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() ) - { - return; - } - QStringList sessionList = Core::self()->sessionController()->sessionNames(); - QString sname = sessionList.at( idx.row() ); - QString aname = Core::self()->activeSession()->name(); - if( sname == aname ) - { - return; - } - int activerow = sessionList.indexOf( aname ); - Core::self()->sessionController()->loadSession( sname ); - emit dataChanged( index( activerow, 0, QModelIndex() ), index( activerow, 0, QModelIndex() ) ); - emit dataChanged( idx, idx ); -} - -void SessionModel::cloneSession( const QModelIndex& idx ) -{ - if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() ) - { - return; - } - - beginInsertRows( QModelIndex(), rowCount(), rowCount() ); - - Core::self()->sessionController()->cloneSession( - Core::self()->sessionController()->sessions().at( idx.row() )->id().toString() - ); - endInsertRows(); -} - -SessionDialog::SessionDialog( QWidget* parent ) - : QDialog( parent ), m_ui( new Ui::SessionDialog ), m_model( new SessionModel( this ) ) -{ - setWindowTitle( i18n( "Configure Sessions" ) ); - - m_ui->setupUi(this); - m_ui->sessionList->setModel( m_model ); - connect( m_ui->newButton, &QPushButton::clicked, this, &SessionDialog::createSession ); - connect( m_ui->deleteButton, &QPushButton::clicked, this, &SessionDialog::deleteSession ); - connect( m_ui->activateButton, &QPushButton::clicked, this, &SessionDialog::activateSession ); - connect( m_ui->cloneButton, &QPushButton::clicked, this, &SessionDialog::cloneSession ); - connect( m_ui->sessionList->selectionModel(), &QItemSelectionModel::selectionChanged, - this, static_cast(&SessionDialog::enableButtons) ); - connect( m_ui->sessionList->selectionModel(), &QItemSelectionModel::currentChanged, - this, static_cast(&SessionDialog::enableButtons) ); - connect( m_model, &SessionModel::rowsRemoved, - this, static_cast(&SessionDialog::enableButtons) ); - enableButtons( m_ui->sessionList->selectionModel()->selection(), QItemSelection() ); - enableButtons(); -} - -SessionDialog::~SessionDialog() -{ - delete m_ui; -} - -void SessionDialog::enableButtons() -{ - m_ui->activateButton->setEnabled( m_model->rowCount() > 1 ); - m_ui->deleteButton->setEnabled( m_model->rowCount() > 1 ); -} - -void SessionDialog::enableButtons( const QModelIndex& current, const QModelIndex& previous ) -{ - Q_UNUSED( previous ); - if( m_model->data( current ).toString() == Core::self()->activeSession()->name() ) - { - m_ui->activateButton->setEnabled( false ); - m_ui->deleteButton->setEnabled( false ); - } else - { - m_ui->activateButton->setEnabled( true ); - m_ui->deleteButton->setEnabled( true ); - } -} - -void SessionDialog::enableButtons( const QItemSelection& selected, const QItemSelection& ) -{ - m_ui->deleteButton->setEnabled( !selected.isEmpty() ); - m_ui->activateButton->setEnabled( !selected.isEmpty() ); - m_ui->cloneButton->setEnabled( !selected.isEmpty() ); - QString activeName = Core::self()->activeSession()->name(); - foreach( const QModelIndex& idx, m_ui->sessionList->selectionModel()->selectedRows() ) - { - if( m_model->data( idx ).toString() == activeName ) - { - m_ui->deleteButton->setEnabled( false ); - m_ui->activateButton->setEnabled( false ); - break; - } - } -} - -void SessionDialog::createSession() -{ - m_model->addSession(); - m_ui->sessionList->edit( m_model->index( m_model->rowCount() - 1, 0, QModelIndex() ) ); - m_ui->deleteButton->setEnabled( true ); - m_ui->activateButton->setEnabled( true ); -} - -void SessionDialog::deleteSession() -{ - m_model->deleteSessions( m_ui->sessionList->selectionModel()->selectedRows() ); -} - -void SessionDialog::activateSession() -{ - m_model->activateSession( m_ui->sessionList->selectionModel()->selectedRows().at( 0 ) ); -} - -void SessionDialog::cloneSession() -{ - m_model->cloneSession( m_ui->sessionList->selectionModel()->selectedRows().at( 0 ) ); - m_ui->sessionList->edit( m_model->index( m_model->rowCount() - 1, 0, QModelIndex() ) ); -} - -} - diff --git a/shell/sessiondialog.h b/shell/sessiondialog.h deleted file mode 100644 index 478b1ff3ac..0000000000 --- a/shell/sessiondialog.h +++ /dev/null @@ -1,76 +0,0 @@ -/* This file is part of KDevelop -Copyright 2008 Andreas Pakulat - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#ifndef KDEVPLATFORM_SESSIONDIALOG_H -#define KDEVPLATFORM_SESSIONDIALOG_H - -#include -#include - -namespace Ui -{ -class SessionDialog; -} - -class QModelIndex; -class QItemSelection; -class QVariant; - -namespace KDevelop -{ -class Session; - -class SessionModel : public QAbstractListModel -{ - Q_OBJECT -public: - explicit SessionModel( QObject* parent = nullptr ); - int rowCount( const QModelIndex& = QModelIndex() ) const override; - QVariant data( const QModelIndex&, int = Qt::DisplayRole ) const override; - QVariant headerData( int, Qt::Orientation, int = Qt::DisplayRole ) const override; - bool setData( const QModelIndex&, const QVariant&, int = Qt::DisplayRole ) override; - Qt::ItemFlags flags( const QModelIndex& ) const override; - void deleteSessions( const QList& ); - void activateSession( const QModelIndex& ); - void addSession(); - void cloneSession( const QModelIndex& ); -}; - -class SessionDialog : public QDialog -{ - Q_OBJECT -public: - explicit SessionDialog( QWidget* = nullptr ); - ~SessionDialog() override; -private Q_SLOTS: - void createSession(); - void deleteSession(); - void activateSession(); - void cloneSession(); - void enableButtons( const QModelIndex&, const QModelIndex& ); - void enableButtons( const QItemSelection&, const QItemSelection& ); - void enableButtons(); -private: - Ui::SessionDialog* m_ui; - SessionModel* m_model; -}; - -} - -#endif diff --git a/shell/sessiondialog.ui b/shell/sessiondialog.ui deleted file mode 100644 index 967ba89a35..0000000000 --- a/shell/sessiondialog.ui +++ /dev/null @@ -1,105 +0,0 @@ - - - SessionDialog - - - - 0 - 0 - 395 - 189 - - - - - - - true - - - - - - - 20 - - - - - 3 - - - - - New - - - false - - - - - - - Clone - - - false - - - - - - - - - false - - - Make Active - - - false - - - - - - - false - - - Delete - - - false - - - - - - - - - Qt::Vertical - - - - 20 - 23 - - - - - - - - QDialogButtonBox::Close - - - - - - - - diff --git a/sublime/idealdockwidget.cpp b/sublime/idealdockwidget.cpp index 27ce343f48..dae0ea2cf4 100644 --- a/sublime/idealdockwidget.cpp +++ b/sublime/idealdockwidget.cpp @@ -1,205 +1,205 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2011 Alexander Dymo Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "idealdockwidget.h" #include "mainwindow.h" #include "area.h" #include "document.h" #include "view.h" #include #include #include #include #include #include #include #include #include using namespace Sublime; IdealDockWidget::IdealDockWidget(IdealController *controller, Sublime::MainWindow *parent) : QDockWidget(parent), m_area(0), m_view(0), m_docking_area(Qt::NoDockWidgetArea), m_controller(controller) { setAutoFillBackground(true); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &IdealDockWidget::customContextMenuRequested, this, &IdealDockWidget::contextMenuRequested); QAbstractButton *closeButton = findChild(QStringLiteral("qt_dockwidget_closebutton")); if (closeButton) { disconnect(closeButton, &QAbstractButton::clicked, 0, 0); connect(closeButton, &QAbstractButton::clicked, this, &IdealDockWidget::closeRequested); } setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); // do not allow to move docks to the top dock area (no buttonbar there in our current UI) setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea); } IdealDockWidget::~IdealDockWidget() { } Area *IdealDockWidget::area() const { return m_area; } void IdealDockWidget::setArea(Area *area) { m_area = area; } View *IdealDockWidget::view() const { return m_view; } void IdealDockWidget::setView(View *view) { m_view = view; } Qt::DockWidgetArea IdealDockWidget::dockWidgetArea() const { return m_docking_area; } void IdealDockWidget::setDockWidgetArea(Qt::DockWidgetArea dockingArea) { m_docking_area = dockingArea; } void IdealDockWidget::slotRemove() { m_area->removeToolView(m_view); } void IdealDockWidget::contextMenuRequested(const QPoint &point) { QWidget* senderWidget = qobject_cast(sender()); Q_ASSERT(senderWidget); QMenu menu; - menu.addSection(windowIcon(), windowTitle()); + menu.addSection(windowIcon(), m_view->document()->title()); QList< QAction* > viewActions = m_view->contextMenuActions(); if(!viewActions.isEmpty()) { menu.addActions(viewActions); menu.addSeparator(); } ///TODO: can this be cleaned up? if(QToolBar* toolBar = widget()->findChild()) { menu.addAction(toolBar->toggleViewAction()); menu.addSeparator(); } /// start position menu QMenu* positionMenu = menu.addMenu(i18n("Toolview Position")); QActionGroup *g = new QActionGroup(this); QAction *left = new QAction(i18nc("toolview position", "Left"), g); QAction *bottom = new QAction(i18nc("toolview position", "Bottom"), g); QAction *right = new QAction(i18nc("toolview position", "Right"), g); QAction *detach = new QAction(i18nc("toolview position", "Detached"), g); for (auto action : {left, bottom, right, detach}) { positionMenu->addAction(action); action->setCheckable(true); } if (isFloating()) { detach->setChecked(true); } else if (m_docking_area == Qt::BottomDockWidgetArea) bottom->setChecked(true); else if (m_docking_area == Qt::LeftDockWidgetArea) left->setChecked(true); else if (m_docking_area == Qt::RightDockWidgetArea) right->setChecked(true); /// end position menu menu.addSeparator(); QAction *setShortcut = menu.addAction(QIcon::fromTheme(QStringLiteral("configure-shortcuts")), i18n("Assign Shortcut...")); setShortcut->setToolTip(i18n("Use this shortcut to trigger visibility of the toolview.")); menu.addSeparator(); QAction* remove = menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-close")), i18n("Remove Toolview")); QAction* triggered = menu.exec(senderWidget->mapToGlobal(point)); if (triggered) { if ( triggered == remove ) { slotRemove(); return; } else if ( triggered == setShortcut ) { QDialog* dialog(new QDialog(this)); dialog->setWindowTitle(i18n("Assign Shortcut For '%1' Tool View", m_view->document()->title())); KShortcutWidget *w = new KShortcutWidget(dialog); w->setShortcut(m_controller->actionForView(m_view)->shortcuts()); QVBoxLayout* dialogLayout = new QVBoxLayout(dialog); dialogLayout->addWidget(w); QDialogButtonBox* buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel ); dialogLayout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); if (dialog->exec() == QDialog::Accepted) { m_controller->actionForView(m_view)->setShortcuts(w->shortcut()); //save shortcut config KConfigGroup config = KSharedConfig::openConfig()->group("UI"); QStringList shortcuts; shortcuts << w->shortcut().value(0).toString(); shortcuts << w->shortcut().value(1).toString(); config.writeEntry(QStringLiteral("Shortcut for %1").arg(m_view->document()->title()), shortcuts); config.sync(); } delete dialog; return; } else if ( triggered == detach ) { setFloating(true); m_area->raiseToolView(m_view); return; } if (isFloating()) { setFloating(false); } Sublime::Position pos; if (triggered == left) pos = Sublime::Left; else if (triggered == bottom) pos = Sublime::Bottom; else if (triggered == right) pos = Sublime::Right; else return; Area *area = m_area; View *view = m_view; /* This call will delete *this, so we no longer can access member variables. */ m_area->moveToolView(m_view, pos); area->raiseToolView(view); } } diff --git a/sublime/mainwindow_p.cpp b/sublime/mainwindow_p.cpp index 3ab490cdb7..74ef494fbe 100644 --- a/sublime/mainwindow_p.cpp +++ b/sublime/mainwindow_p.cpp @@ -1,823 +1,819 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow_p.h" #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "areaindex.h" #include "document.h" #include "container.h" #include "controller.h" #include "mainwindow.h" #include "idealcontroller.h" #include "holdupdates.h" #include "idealbuttonbarwidget.h" #include "sublimedebug.h" class IdealToolBar : public QToolBar { Q_OBJECT public: explicit IdealToolBar(const QString& title, bool hideWhenEmpty, Sublime::IdealButtonBarWidget* buttons, QMainWindow* parent) : QToolBar(title, parent) , m_timer(nullptr) , m_buttons(buttons) , m_hideWhenEmpty(hideWhenEmpty) , m_requestedVisibility(true) { setMovable(false); setFloatable(false); setObjectName(title); layout()->setMargin(0); addWidget(m_buttons); if (m_hideWhenEmpty) { m_timer = new QTimer(this); m_timer->setInterval(100); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &IdealToolBar::refresh); connect(this, &IdealToolBar::visibilityChanged, m_timer, static_cast(&QTimer::start)); connect(m_buttons, &Sublime::IdealButtonBarWidget::emptyChanged, m_timer, static_cast(&QTimer::start)); } } void setVisible(bool visible) override { QToolBar::setVisible(visible); m_requestedVisibility = visible; if (m_hideWhenEmpty && visible) { m_timer->start(); } } private slots: void refresh() { setVisible(m_requestedVisibility && !m_buttons->isEmpty()); } private: QTimer* m_timer; Sublime::IdealButtonBarWidget* m_buttons; const bool m_hideWhenEmpty; bool m_requestedVisibility; }; namespace Sublime { MainWindowPrivate::MainWindowPrivate(MainWindow *w, Controller* controller) :controller(controller), area(0), activeView(0), activeToolView(0), bgCentralWidget(0), ignoreDockShown(false), autoAreaSettingsSave(false), m_mainWindow(w) { KActionCollection *ac = m_mainWindow->actionCollection(); m_concentrationModeAction = new QAction(i18n("Concentration Mode"), this); m_concentrationModeAction->setIcon(QIcon::fromTheme(QStringLiteral("page-zoom"))); m_concentrationModeAction->setToolTip(i18n("Removes most of the controls so you can focus on what matters.")); m_concentrationModeAction->setCheckable(true); m_concentrationModeAction->setChecked(false); ac->setDefaultShortcut(m_concentrationModeAction, Qt::META | Qt::Key_C); connect(m_concentrationModeAction, &QAction::toggled, this, &MainWindowPrivate::restoreConcentrationMode); ac->addAction(QStringLiteral("toggle_concentration_mode"), m_concentrationModeAction); QAction* action = new QAction(i18n("Show Left Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Left); connect(action, &QAction::toggled, this, &MainWindowPrivate::showLeftDock); ac->addAction(QStringLiteral("show_left_dock"), action); action = new QAction(i18n("Show Right Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Right); connect(action, &QAction::toggled, this, &MainWindowPrivate::showRightDock); ac->addAction(QStringLiteral("show_right_dock"), action); action = new QAction(i18n("Show Bottom Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Down); connect(action, &QAction::toggled, this, &MainWindowPrivate::showBottomDock); ac->addAction(QStringLiteral("show_bottom_dock"), action); action = new QAction(i18nc("@action", "Focus Editor"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_E); connect(action, &QAction::triggered, this, &MainWindowPrivate::focusEditor); ac->addAction(QStringLiteral("focus_editor"), action); action = new QAction(i18n("Hide/Restore Docks"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Up); connect(action, &QAction::triggered, this, &MainWindowPrivate::toggleDocksShown); ac->addAction(QStringLiteral("hide_all_docks"), action); action = new QAction(i18n("Next Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_N); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextDock); ac->addAction(QStringLiteral("select_next_dock"), action); action = new QAction(i18n("Previous Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_P); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPreviousDock); ac->addAction(QStringLiteral("select_previous_dock"), action); action = new KActionMenu(i18n("Tool Views"), this); ac->addAction(QStringLiteral("docks_submenu"), action); idealController = new IdealController(m_mainWindow); m_leftToolBar = new IdealToolBar(i18n("Left Button Bar"), true, idealController->leftBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::LeftToolBarArea, m_leftToolBar); m_rightToolBar = new IdealToolBar(i18n("Right Button Bar"), true, idealController->rightBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::RightToolBarArea, m_rightToolBar); m_bottomToolBar = new IdealToolBar(i18n("Bottom Button Bar"), false, idealController->bottomBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::BottomToolBarArea, m_bottomToolBar); // adymo: intentionally do not add a toolbar for top buttonbar // this doesn't work well with toolbars added via xmlgui centralWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(centralWidget); centralWidget->setLayout(layout); layout->setMargin(0); splitterCentralWidget = new QSplitter(centralWidget); layout->addWidget(splitterCentralWidget); m_mainWindow->setCentralWidget(centralWidget); connect(idealController, &IdealController::dockShown, this, &MainWindowPrivate::slotDockShown); connect(idealController, &IdealController::widgetResized, this, &MainWindowPrivate::widgetResized); connect(idealController, &IdealController::dockBarContextMenuRequested, m_mainWindow, &MainWindow::dockBarContextMenuRequested); } MainWindowPrivate::~MainWindowPrivate() { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } void MainWindowPrivate::disableConcentrationMode() { m_concentrationModeAction->setChecked(false); restoreConcentrationMode(); } void MainWindowPrivate::restoreConcentrationMode() { const bool concentrationModeOn = m_concentrationModeAction->isChecked(); QWidget* cornerWidget = nullptr; if (m_concentrateToolBar) { QLayout* l = m_concentrateToolBar->layout(); QLayoutItem* li = l->takeAt(1); //ensure the cornerWidget isn't destroyed with the toolbar if (li) { cornerWidget = li->widget(); delete li; } m_concentrateToolBar->deleteLater(); } m_mainWindow->menuBar()->setVisible(!concentrationModeOn); m_bottomToolBar->setVisible(!concentrationModeOn); m_leftToolBar->setVisible(!concentrationModeOn); m_rightToolBar->setVisible(!concentrationModeOn); - const bool hideToolBar = concentrationModeOn || !Container::configTabBarVisible(); - foreach(Container* c, viewContainers) { - c->setTabBarHidden(hideToolBar); - } if (concentrationModeOn) { m_concentrateToolBar = new QToolBar(m_mainWindow); m_concentrateToolBar->setObjectName(QStringLiteral("concentrateToolBar")); m_concentrateToolBar->addAction(m_concentrationModeAction); QWidgetAction *action = new QWidgetAction(this); action->setDefaultWidget(m_mainWindow->menuBar()->cornerWidget(Qt::TopRightCorner)); m_concentrateToolBar->addAction(action); m_concentrateToolBar->setMovable(false); m_mainWindow->addToolBar(Qt::TopToolBarArea, m_concentrateToolBar); m_mainWindow->menuBar()->setCornerWidget(0, Qt::TopRightCorner); } else if (cornerWidget) { m_mainWindow->menuBar()->setCornerWidget(cornerWidget, Qt::TopRightCorner); cornerWidget->show(); } if (concentrationModeOn) { m_mainWindow->installEventFilter(this); } else { m_mainWindow->removeEventFilter(this); } } bool MainWindowPrivate::eventFilter(QObject* obj, QEvent* event) { Q_ASSERT(m_mainWindow == obj); if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { Qt::KeyboardModifiers modifiers = static_cast(event)->modifiers(); m_mainWindow->menuBar()->setVisible(modifiers == Qt::AltModifier && event->type() == QEvent::KeyPress); } return false; } void MainWindowPrivate::showLeftDock(bool b) { idealController->showLeftDock(b); } void MainWindowPrivate::showBottomDock(bool b) { idealController->showBottomDock(b); } void MainWindowPrivate::showRightDock(bool b) { idealController->showRightDock(b); } void MainWindowPrivate::setBackgroundCentralWidget(QWidget* w) { delete bgCentralWidget; QLayout* l=m_mainWindow->centralWidget()->layout(); l->addWidget(w); bgCentralWidget=w; setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::setBackgroundVisible(bool v) { if(!bgCentralWidget) return; bgCentralWidget->setVisible(v); splitterCentralWidget->setVisible(!v); } void MainWindowPrivate::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } void MainWindowPrivate::toggleDocksShown() { idealController->toggleDocksShown(); } void MainWindowPrivate::selectNextDock() { idealController->goPrevNextDock(IdealController::NextDock); } void MainWindowPrivate::selectPreviousDock() { idealController->goPrevNextDock(IdealController::PrevDock); } Area::WalkerMode MainWindowPrivate::IdealToolViewCreator::operator() (View *view, Sublime::Position position) { if (!d->docks.contains(view)) { d->docks << view; //add view d->idealController->addView(d->positionToDockArea(position), view); } return Area::ContinueWalker; } Area::WalkerMode MainWindowPrivate::ViewCreator::operator() (AreaIndex *index) { QSplitter *splitter = d->m_indexSplitters.value(index); if (!splitter) { //no splitter - we shall create it and populate with views if (!index->parent()) { qCDebug(SUBLIME) << "reconstructing root area"; //this is root area splitter = d->splitterCentralWidget; d->m_indexSplitters[index] = splitter; d->centralWidget->layout()->addWidget(splitter); } else { if (!d->m_indexSplitters.value(index->parent())) { // can happen in working set code, as that adds a view to a child index first // hence, recursively reconstruct the parent indizes first operator()(index->parent()); } QSplitter *parent = d->m_indexSplitters.value(index->parent()); splitter = new QSplitter(parent); d->m_indexSplitters[index] = splitter; if(index == index->parent()->first()) parent->insertWidget(0, splitter); else parent->addWidget(splitter); } Q_ASSERT(splitter); } if (index->isSplit()) //this is a visible splitter splitter->setOrientation(index->orientation()); else { Container *container = 0; while(splitter->count() && qobject_cast(splitter->widget(0))) { // After unsplitting, we might have to remove old splitters QWidget* widget = splitter->widget(0); qCDebug(SUBLIME) << "deleting" << widget; widget->setParent(0); delete widget; } if (!splitter->widget(0)) { //we need to create view container container = new Container(splitter); connect(container, &Container::activateView, d->m_mainWindow, &MainWindow::activateViewAndFocus); connect(container, &Container::tabDoubleClicked, d->m_mainWindow, &MainWindow::tabDoubleClicked); connect(container, &Container::tabContextMenuRequested, d->m_mainWindow, &MainWindow::tabContextMenuRequested); connect(container, &Container::tabToolTipRequested, d->m_mainWindow, &MainWindow::tabToolTipRequested); connect(container, static_cast(&Container::requestClose), d, &MainWindowPrivate::widgetCloseRequest, Qt::QueuedConnection); connect(container, &Container::newTabRequested, d->m_mainWindow, &MainWindow::newTabRequested); splitter->addWidget(container); } else container = qobject_cast(splitter->widget(0)); container->show(); int position = 0; bool hadActiveView = false; Sublime::View* activeView = d->activeView; foreach (View *view, index->views()) { QWidget *widget = view->widget(container); if (widget) { if(!container->hasWidget(widget)) { container->addWidget(view, position); d->viewContainers[view] = container; d->widgetToView[widget] = view; } if(activeView == view) { hadActiveView = true; container->setCurrentWidget(widget); }else if(topViews.contains(view) && !hadActiveView) container->setCurrentWidget(widget); } position++; } } return Area::ContinueWalker; } void MainWindowPrivate::reconstructViews(QList topViews) { ViewCreator viewCreator(this, topViews); area->walkViews(viewCreator, area->rootIndex()); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::reconstruct() { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(0); } idealController->setWidthForArea(Qt::LeftDockWidgetArea, area->thickness(Sublime::Left)); idealController->setWidthForArea(Qt::BottomDockWidgetArea, area->thickness(Sublime::Bottom)); idealController->setWidthForArea(Qt::RightDockWidgetArea, area->thickness(Sublime::Right)); IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, Sublime::AllPositions); reconstructViews(); m_mainWindow->blockSignals(true); qCDebug(SUBLIME) << "RECONSTRUCT" << area << area->shownToolViews(Sublime::Left); foreach (View *view, area->toolViews()) { QString id = view->document()->documentSpecifier(); if (!id.isEmpty()) { Sublime::Position pos = area->toolViewPosition(view); if (area->shownToolViews(pos).contains(id)) idealController->raiseView(view, IdealController::GroupWithOtherViews); } } m_mainWindow->blockSignals(false); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::clearArea() { if(m_leftTabbarCornerWidget) m_leftTabbarCornerWidget->setParent(0); //reparent toolview widgets to 0 to prevent their deletion together with dockwidgets foreach (View *view, area->toolViews()) { // FIXME should we really delete here?? bool nonDestructive = true; idealController->removeView(view, nonDestructive); if (view->hasWidget()) view->widget()->setParent(0); } docks.clear(); //reparent all view widgets to 0 to prevent their deletion together with central //widget. this reparenting is necessary when switching areas inside the same mainwindow foreach (View *view, area->views()) { if (view->hasWidget()) view->widget()->setParent(0); } cleanCentralWidget(); m_mainWindow->setActiveView(0); m_indexSplitters.clear(); area = 0; viewContainers.clear(); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::cleanCentralWidget() { while(splitterCentralWidget->count()) delete splitterCentralWidget->widget(0); setBackgroundVisible(true); } struct ShownToolViewFinder { ShownToolViewFinder() {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && v->widget()->isVisible()) views << v; return Area::ContinueWalker; } QList views; }; void MainWindowPrivate::slotDockShown(Sublime::View* /*view*/, Sublime::Position pos, bool /*shown*/) { if (ignoreDockShown) return; ShownToolViewFinder finder; m_mainWindow->area()->walkToolViews(finder, pos); QStringList ids; foreach (View *v, finder.views) { ids << v->document()->documentSpecifier(); } area->setShownToolViews(pos, ids); } void MainWindowPrivate::viewRemovedInternal(AreaIndex* index, View* view) { Q_UNUSED(index); Q_UNUSED(view); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::viewAdded(Sublime::AreaIndex *index, Sublime::View *view) { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(0); } // Remove container objects in the hierarchy from the parents, // because they are not needed anymore, and might lead to broken splitter hierarchy and crashes. for(Sublime::AreaIndex* current = index; current; current = current->parent()) { QSplitter *splitter = m_indexSplitters[current]; if (current->isSplit() && splitter) { // Also update the orientation splitter->setOrientation(current->orientation()); for(int w = 0; w < splitter->count(); ++w) { Container *container = qobject_cast(splitter->widget(w)); //we need to remove extra container before reconstruction //first reparent widgets in container so that they are not deleted if(container) { while (container->count()) { container->widget(0)->setParent(0); } //and then delete the container delete container; } } } } ViewCreator viewCreator(this); area->walkViews(viewCreator, index); emit m_mainWindow->viewAdded( view ); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); setBackgroundVisible(false); } void Sublime::MainWindowPrivate::raiseToolView(Sublime::View * view) { idealController->raiseView(view); } void MainWindowPrivate::aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view) { QSplitter *splitter = m_indexSplitters[index]; if (!splitter) return; qCDebug(SUBLIME) << "index " << index << " root " << area->rootIndex(); qCDebug(SUBLIME) << "splitter " << splitter << " container " << splitter->widget(0); qCDebug(SUBLIME) << "structure: " << index->print() << " whole structure: " << area->rootIndex()->print(); //find the container for the view and remove the widget Container *container = qobject_cast(splitter->widget(0)); if (!container) { qWarning() << "Splitter does not have a left widget!"; return; } emit m_mainWindow->aboutToRemoveView( view ); if (view->widget()) widgetToView.remove(view->widget()); viewContainers.remove(view); const bool wasActive = m_mainWindow->activeView() == view; if (container->count() > 1) { //container is not empty or this is a root index //just remove a widget if( view->widget() ) { container->removeWidget(view->widget()); view->widget()->setParent(0); //activate what is visible currently in the container if the removed view was active if (wasActive) return m_mainWindow->setActiveView(container->viewForWidget(container->currentWidget())); } } else { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(0); } // We've about to remove the last view of this container. It will // be empty, so have to delete it, as well. // If we have a container, then it should be the only child of // the splitter. Q_ASSERT(splitter->count() == 1); container->removeWidget(view->widget()); if (view->widget()) view->widget()->setParent(0); else qWarning() << "View does not have a widget!"; Q_ASSERT(container->count() == 0); // We can be called from signal handler of container // (which is tab widget), so defer deleting it. container->deleteLater(); container->setParent(0); /* If we're not at the top level, we get to collapse split views. */ if (index->parent()) { /* The splitter used to have container as the only child, now it's time to get rid of it. Make sure deleting splitter does not delete container -- per above comment, we'll delete it later. */ container->setParent(0); m_indexSplitters.remove(index); delete splitter; AreaIndex *parent = index->parent(); QSplitter *parentSplitter = m_indexSplitters[parent]; AreaIndex *sibling = parent->first() == index ? parent->second() : parent->first(); QSplitter *siblingSplitter = m_indexSplitters[sibling]; if(siblingSplitter) { HoldUpdates du(parentSplitter); //save sizes and orientation of the sibling splitter parentSplitter->setOrientation(siblingSplitter->orientation()); QList sizes = siblingSplitter->sizes(); /* Parent has two children -- 'index' that we've deleted and 'sibling'. We move all children of 'sibling' into parent, and delete 'sibling'. sibling either contains a single Container instance, or a bunch of further QSplitters. */ while (siblingSplitter->count() > 0) { //reparent contents into parent splitter QWidget *siblingWidget = siblingSplitter->widget(0); siblingWidget->setParent(parentSplitter); parentSplitter->addWidget(siblingWidget); } m_indexSplitters.remove(sibling); delete siblingSplitter; parentSplitter->setSizes(sizes); } qCDebug(SUBLIME) << "after deleation " << parent << " has " << parentSplitter->count() << " elements"; //find the container somewhere to activate Container *containerToActivate = parentSplitter->findChild(); //activate the current view there if (containerToActivate) { m_mainWindow->setActiveView(containerToActivate->viewForWidget(containerToActivate->currentWidget())); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); return; } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); if ( wasActive ) { m_mainWindow->setActiveView(0L); } } void MainWindowPrivate::toolViewAdded(Sublime::View* /*toolView*/, Sublime::Position position) { IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, position); } void MainWindowPrivate::aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position /*position*/) { if (!docks.contains(toolView)) return; idealController->removeView(toolView); // TODO are Views unique? docks.removeAll(toolView); } void MainWindowPrivate::toolViewMoved( Sublime::View *toolView, Sublime::Position position) { if (!docks.contains(toolView)) return; idealController->moveView(toolView, positionToDockArea(position)); } Qt::DockWidgetArea MainWindowPrivate::positionToDockArea(Position position) { switch (position) { case Sublime::Left: return Qt::LeftDockWidgetArea; case Sublime::Right: return Qt::RightDockWidgetArea; case Sublime::Bottom: return Qt::BottomDockWidgetArea; case Sublime::Top: return Qt::TopDockWidgetArea; default: return Qt::LeftDockWidgetArea; } } void MainWindowPrivate::switchToArea(QAction *action) { qCDebug(SUBLIME) << "for" << action; controller->showArea(m_actionAreas.value(action), m_mainWindow); } void MainWindowPrivate::updateAreaSwitcher(Sublime::Area *area) { QAction* action = m_areaActions.value(area); if (action) action->setChecked(true); } void MainWindowPrivate::activateFirstVisibleView() { QList views = area->views(); if (views.count() > 0) m_mainWindow->activateView(views.first()); } void MainWindowPrivate::widgetResized(Qt::DockWidgetArea /*dockArea*/, int /*thickness*/) { //TODO: adymo: remove all thickness business } void MainWindowPrivate::widgetCloseRequest(QWidget* widget) { if (View *view = widgetToView.value(widget)) { area->closeView(view); } } void MainWindowPrivate::setTabBarLeftCornerWidget(QWidget* widget) { if(widget != m_leftTabbarCornerWidget.data()) { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } m_leftTabbarCornerWidget = widget; if(!widget || !area || viewContainers.isEmpty()) return; AreaIndex* putToIndex = area->rootIndex(); QSplitter* splitter = m_indexSplitters[putToIndex]; while(putToIndex->isSplit()) { putToIndex = putToIndex->first(); splitter = m_indexSplitters[putToIndex]; } // Q_ASSERT(splitter || putToIndex == area->rootIndex()); Container* c = 0; if(splitter) { c = qobject_cast(splitter->widget(0)); }else{ - c = viewContainers.values()[0]; + c = *viewContainers.constBegin(); } Q_ASSERT(c); c->setLeftCornerWidget(widget); } } #include "mainwindow_p.moc" #include "moc_mainwindow_p.cpp" diff --git a/util/commandexecutor.cpp b/util/commandexecutor.cpp index 4c5f7c4a26..578a6bf48f 100644 --- a/util/commandexecutor.cpp +++ b/util/commandexecutor.cpp @@ -1,167 +1,167 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "commandexecutor.h" #include "processlinemaker.h" #include #include #include #include #include namespace KDevelop { class CommandExecutorPrivate { public: CommandExecutorPrivate( CommandExecutor* cmd ) : m_exec(cmd), m_useShell(false) { } CommandExecutor* m_exec; KProcess* m_process; ProcessLineMaker* m_lineMaker; QString m_command; QStringList m_args; QString m_workDir; QMap m_env; bool m_useShell; void procError( QProcess::ProcessError error ) { Q_UNUSED(error) m_lineMaker->flushBuffers(); emit m_exec->failed( error ); } void procFinished( int code, QProcess::ExitStatus status ) { m_lineMaker->flushBuffers(); if( status == QProcess::NormalExit ) emit m_exec->completed(code); } }; CommandExecutor::CommandExecutor( const QString& command, QObject* parent ) : QObject(parent), d(new CommandExecutorPrivate(this)) { d->m_process = new KProcess(this); d->m_process->setOutputChannelMode( KProcess::SeparateChannels ); d->m_lineMaker = new ProcessLineMaker( d->m_process ); d->m_command = command; connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &CommandExecutor::receivedStandardOutput ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &CommandExecutor::receivedStandardError ); connect( d->m_process, static_cast(&KProcess::error), this, [&] (QProcess::ProcessError error) { d->procError(error); } ); connect( d->m_process, static_cast(&KProcess::finished), this, [&] (int code, QProcess::ExitStatus status) { d->procFinished(code, status); } ); } CommandExecutor::~CommandExecutor() { delete d->m_process; delete d->m_lineMaker; delete d; } void CommandExecutor::setEnvironment( const QMap& env ) { d->m_env = env; } void CommandExecutor::setEnvironment( const QStringList& env ) { QMap envmap; foreach( const QString& var, env ) { int sep = var.indexOf( '=' ); envmap.insert( var.left( sep ), var.mid( sep + 1 ) ); } d->m_env = envmap; } void CommandExecutor::setArguments( const QStringList& args ) { d->m_args = args; } void CommandExecutor::setWorkingDirectory( const QString& dir ) { d->m_workDir = dir; } bool CommandExecutor::useShell() const { return d->m_useShell; } void CommandExecutor::setUseShell( bool shell ) { d->m_useShell = shell; } void CommandExecutor::start() { - Q_FOREACH( const QString &s, d->m_env.keys() ) + for(auto it = d->m_env.constBegin(), itEnd = d->m_env.constEnd(); it!=itEnd; ++it) { - d->m_process->setEnv( s, d->m_env[s] ); + d->m_process->setEnv( it.key(), it.value() ); } d->m_process->setWorkingDirectory( d->m_workDir ); if( !d->m_useShell ) { d->m_process->setProgram( d->m_command, d->m_args ); } else { QStringList arguments; Q_FOREACH( const QString &a, d->m_args ) arguments << KShell::quoteArg( a ); d->m_process->setShellCommand(d->m_command + ' ' + arguments.join(QLatin1Char(' '))); } d->m_process->start(); } void CommandExecutor::setCommand( const QString& command ) { d->m_command = command; } void CommandExecutor::kill() { d->m_process->kill(); } QString CommandExecutor::command() const { return d->m_command; } QStringList CommandExecutor::arguments() const { return d->m_args; } QString CommandExecutor::workingDirectory() const { return d->m_workDir; } } #include "moc_commandexecutor.cpp" diff --git a/util/environmentgrouplist.cpp b/util/environmentgrouplist.cpp index 1e850709b7..c762296517 100644 --- a/util/environmentgrouplist.cpp +++ b/util/environmentgrouplist.cpp @@ -1,223 +1,225 @@ /* This file is part of KDevelop Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentgrouplist.h" #include #include #include #include #include #include namespace KDevelop { class EnvironmentGroupListPrivate { public: QMap > m_groups; QString m_defaultGroup; }; } using namespace KDevelop; namespace { namespace Strings { inline QString defaultEnvGroupKey() { return QStringLiteral("Default Environment Group"); } inline QString envGroup() { return QStringLiteral("Environment Settings"); } inline QString groupListKey() { return QStringLiteral("Group List"); } inline QString defaultGroup() { return QStringLiteral("default"); } } void decode( KConfig* config, EnvironmentGroupListPrivate* d ) { KConfigGroup cfg( config, Strings::envGroup() ); d->m_defaultGroup = cfg.readEntry( Strings::defaultEnvGroupKey(), Strings::defaultGroup() ); QStringList grouplist = cfg.readEntry( Strings::groupListKey(), QStringList{Strings::defaultGroup()} ); foreach( const QString &envgrpname, grouplist ) { KConfigGroup envgrp( &cfg, envgrpname ); QMap variables; foreach( const QString &varname, envgrp.keyList() ) { variables[varname] = envgrp.readEntry( varname, QString() ); } d->m_groups.insert( envgrpname, variables ); } } void encode( KConfig* config, EnvironmentGroupListPrivate* d ) { KConfigGroup cfg( config, Strings::envGroup() ); cfg.writeEntry( Strings::defaultEnvGroupKey(), d->m_defaultGroup ); cfg.writeEntry( Strings::groupListKey(), d->m_groups.keys() ); foreach( const QString &group, cfg.groupList() ) { - if( !d->m_groups.keys().contains( group ) ) + if( !d->m_groups.contains( group ) ) { cfg.deleteGroup( group ); } } - foreach( const QString &group, d->m_groups.keys() ) + for(auto it = d->m_groups.cbegin(), itEnd = d->m_groups.cend(); it!=itEnd; ++it) { - KConfigGroup envgrp( &cfg, group ); + KConfigGroup envgrp( &cfg, it.key() ); envgrp.deleteGroup(); - foreach( const QString &var, d->m_groups[group].keys() ) + + const auto val = it.value(); + for(auto it2 = val.cbegin(), it2End = val.cend(); it2!=it2End; ++it2) { - envgrp.writeEntry( var, d->m_groups[group][var] ); + envgrp.writeEntry( it2.key(), *it2 ); } } cfg.sync(); } } EnvironmentGroupList::EnvironmentGroupList( const EnvironmentGroupList& rhs ) : d( new EnvironmentGroupListPrivate( *rhs.d ) ) { } EnvironmentGroupList& EnvironmentGroupList::operator=( const EnvironmentGroupList& rhs ) { *d = *rhs.d; return *this; } EnvironmentGroupList::EnvironmentGroupList( KSharedConfigPtr config ) : d( new EnvironmentGroupListPrivate ) { decode( config.data(), d ); } EnvironmentGroupList::EnvironmentGroupList( KConfig* config ) : d( new EnvironmentGroupListPrivate ) { decode( config, d ); } EnvironmentGroupList::~EnvironmentGroupList() { delete d; } const QMap EnvironmentGroupList::variables( const QString& group ) const { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QMap& EnvironmentGroupList::variables( const QString& group ) { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QString EnvironmentGroupList::defaultGroup() const { return d->m_defaultGroup; } void EnvironmentGroupList::setDefaultGroup( const QString& group ) { if( group.isEmpty() ) { return; } if( d->m_groups.contains( group ) ) { d->m_defaultGroup = group; } } void EnvironmentGroupList::saveSettings( KConfig* config ) const { encode( config, d ); config->sync(); } void EnvironmentGroupList::loadSettings( KConfig* config ) { d->m_groups.clear(); decode( config, d ); } QStringList EnvironmentGroupList::groups() const { return d->m_groups.keys(); } void EnvironmentGroupList::removeGroup( const QString& group ) { d->m_groups.remove( group ); } EnvironmentGroupList::EnvironmentGroupList() : d( new EnvironmentGroupListPrivate ) { } QStringList EnvironmentGroupList::createEnvironment( const QString & group, const QStringList & defaultEnvironment ) const { QMap retMap; foreach( const QString &line, defaultEnvironment ) { QString varName = line.section( '=', 0, 0 ); QString varValue = line.section( '=', 1 ); retMap.insert( varName, varValue ); } if( !group.isEmpty() ) { QMap userMap = variables(group); for( QMap::const_iterator it = userMap.constBegin(); it != userMap.constEnd(); ++it ) { retMap.insert( it.key(), it.value() ); } } QStringList env; for( QMap::const_iterator it = retMap.constBegin(); it != retMap.constEnd(); ++it ) { env << it.key() + '=' + it.value(); } return env; } void KDevelop::expandVariables(QMap& variables, const QProcessEnvironment& environment) { QRegularExpression rVar(QStringLiteral("(? This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "multilevellistview.h" #include #include #include #include #include /** * Interface to set the label of a model. */ class LabeledProxy { public: virtual ~LabeledProxy() { } void setLabel(const QString& label) { m_label = label; } QVariant header(QAbstractItemModel* model, int section, Qt::Orientation orientation, int role) const { if (model && section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { return m_label; } else { return QVariant(); } } protected: QString m_label; }; /** * The left-most view's model which only contains the root nodes of the source model. */ class RootProxyModel : public QSortFilterProxyModel, public LabeledProxy { Q_OBJECT public: RootProxyModel( QObject* parent = 0 ) : QSortFilterProxyModel( parent ) { } bool filterAcceptsRow( int /*source_row*/, const QModelIndex& source_parent ) const override { return !source_parent.isValid(); } QVariant headerData( int section, Qt::Orientation orientation, int role ) const override { return header(sourceModel(), section, orientation, role); } }; /** * A class that automatically updates its contents based on the selection in another view. */ class SubTreeProxyModel : public KSelectionProxyModel, public LabeledProxy { Q_OBJECT public: explicit SubTreeProxyModel( QItemSelectionModel* selectionModel, QObject* parent = 0 ) : KSelectionProxyModel( selectionModel, parent ) {} QVariant headerData( int section, Qt::Orientation orientation, int role ) const override { return header(sourceModel(), section, orientation, role); } Qt::ItemFlags flags(const QModelIndex& index) const override { Qt::ItemFlags ret = KSelectionProxyModel::flags(index); if (filterBehavior() == KSelectionProxyModel::SubTreesWithoutRoots && hasChildren(index)) { // we want to select child items ret &= ~Qt::ItemIsSelectable; } return ret; } }; using namespace KDevelop; class KDevelop::MultiLevelListViewPrivate { public: MultiLevelListViewPrivate(MultiLevelListView* view); ~MultiLevelListViewPrivate(); void viewSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void lastViewsContentsChanged(); void ensureViewSelected(QTreeView* view); /** * @param index index in any of our proxy models * @return an index in the source model */ QModelIndex mapToSource(QModelIndex index); /** * @param index an index in the source model * @return an index in the view's model at level @p level */ QModelIndex mapFromSource(QModelIndex index, int level); MultiLevelListView* view; int levels; QList views; QList proxies; QList layouts; QAbstractItemModel* model; }; MultiLevelListViewPrivate::MultiLevelListViewPrivate(MultiLevelListView* view_) : view(view_) , levels(0) , model(0) { } MultiLevelListViewPrivate::~MultiLevelListViewPrivate() { } void MultiLevelListViewPrivate::viewSelectionChanged(const QModelIndex& current, const QModelIndex& previous) { if (!current.isValid()) { // ignore, as we should always have some kind of selection return; } // figure out which proxy this signal belongs to QAbstractProxyModel* proxy = qobject_cast( const_cast(current.model())); Q_ASSERT(proxy); // what level is this proxy in int level = -1; for(int i = 0; i < levels; ++i) { if (views.at(i)->model() == proxy) { level = i; break; } } Q_ASSERT(level >= 0 && level < levels); if (level + 1 == levels) { // right-most view if (current.child(0, 0).isValid()) { // select the first leaf node for this view QModelIndex idx = current; QModelIndex child = idx.child(0, 0); while(child.isValid()) { idx = child; child = idx.child(0, 0); } views.last()->setCurrentIndex(idx); return; } // signal that our actual selection has changed emit view->currentIndexChanged(mapToSource(current), mapToSource(previous)); } else { // some leftish view // ensure the next view's first item is selected QTreeView* treeView = views.at(level + 1); // we need to delay the call, because at this point the child view // will still have its old data which is going to be invalidated // right after this method exits // be we must not set the index to 0,0 here directly, since e.g. // MultiLevelListView::setCurrentIndex might have been used, which // sets a proper index already. QMetaObject::invokeMethod(view, "ensureViewSelected", Qt::QueuedConnection, Q_ARG(QTreeView*, treeView)); } } void MultiLevelListViewPrivate::lastViewsContentsChanged() { views.last()->expandAll(); } void MultiLevelListViewPrivate::ensureViewSelected(QTreeView* view) { if (!view->currentIndex().isValid()) { view->setCurrentIndex(view->model()->index(0, 0)); } } QModelIndex MultiLevelListViewPrivate::mapToSource(QModelIndex index) { if (!index.isValid()) { return index; } while(index.model() != model) { QAbstractProxyModel* proxy = qobject_cast( const_cast(index.model())); Q_ASSERT(proxy); index = proxy->mapToSource(index); Q_ASSERT(index.isValid()); } return index; } QModelIndex MultiLevelListViewPrivate::mapFromSource(QModelIndex index, int level) { if (!index.isValid()) { return index; } Q_ASSERT(index.model() == model); QAbstractProxyModel* proxy = qobject_cast(views.at(level)->model()); Q_ASSERT(proxy); // find all proxies between the source and our view QVector proxies; proxies << proxy; forever { QAbstractProxyModel* child = qobject_cast(proxy->sourceModel()); if (child) { proxy = child; proxies << proxy; } else { Q_ASSERT(proxy->sourceModel() == model); break; } } // iterate in reverse order to find the view's index for(int i = proxies.size() - 1; i >= 0; --i) { proxy = proxies.at(i); index = proxy->mapFromSource(index); Q_ASSERT(index.isValid()); } return index; } MultiLevelListView::MultiLevelListView(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new MultiLevelListViewPrivate(this)) { delete layout(); setLayout(new QHBoxLayout()); layout()->setContentsMargins(0, 0, 0, 0); qRegisterMetaType("QTreeView*"); } MultiLevelListView::~MultiLevelListView() { delete d; } int MultiLevelListView::levels() const { return d->levels; } void MultiLevelListView::setLevels(int levels) { qDeleteAll(d->views); qDeleteAll(d->proxies); qDeleteAll(d->layouts); d->views.clear(); d->proxies.clear(); d->layouts.clear(); d->levels = levels; QTreeView* previousView = 0; for (int i = 0; i < d->levels; ++i) { QVBoxLayout* levelLayout = new QVBoxLayout(); QTreeView* view = new QTreeView(this); view->setContentsMargins(0, 0, 0, 0); // only the right-most view is decorated view->setRootIsDecorated(i + 1 == d->levels); view->setHeaderHidden(false); view->setSelectionMode(QAbstractItemView::SingleSelection); if (!previousView) { // the root, i.e. left-most view RootProxyModel* root = new RootProxyModel(this); root->setDynamicSortFilter(true); d->proxies << root; root->setSourceModel(d->model); view->setModel(root); } else { SubTreeProxyModel* subTreeProxy = new SubTreeProxyModel(previousView->selectionModel(), this); if (i + 1 < d->levels) { // middel views only shows children of selection subTreeProxy->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); } else { // right-most view shows the rest subTreeProxy->setFilterBehavior(KSelectionProxyModel::SubTreesWithoutRoots); } d->proxies << subTreeProxy; subTreeProxy->setSourceModel(d->model); // sorting requires another proxy in-between QSortFilterProxyModel* sortProxy = new QSortFilterProxyModel(subTreeProxy); sortProxy->setSourceModel(subTreeProxy); sortProxy->setDynamicSortFilter(true); view->setModel(sortProxy); } // view->setModel creates the selection model connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, [&] (const QModelIndex& current, const QModelIndex& previous) { d->viewSelectionChanged(current, previous); }); if (i + 1 == d->levels) { connect(view->model(), &QAbstractItemModel::rowsInserted, this, [&] { d->lastViewsContentsChanged(); }); } view->setSortingEnabled(true); view->sortByColumn(0, Qt::AscendingOrder); levelLayout->addWidget(view); layout()->addItem(levelLayout); d->layouts << levelLayout; d->views << view; previousView = view; } setModel(d->model); } QAbstractItemModel* MultiLevelListView::model() const { return d->model; } void MultiLevelListView::setModel(QAbstractItemModel* model) { d->model = model; foreach (LabeledProxy* proxy, d->proxies) { dynamic_cast(proxy)->setSourceModel(model); } if (model && !d->views.isEmpty()) { d->views.first()->setCurrentIndex(d->views.first()->model()->index(0, 0)); } } QTreeView* MultiLevelListView::viewForLevel( int level ) const { return d->views[level]; } void MultiLevelListView::addWidget(int level, QWidget* widget) { Q_ASSERT(level < d->levels); d->layouts[level]->addWidget(widget); } QModelIndex MultiLevelListView::currentIndex() const { return d->mapToSource(d->views.last()->currentIndex()); } void MultiLevelListView::setCurrentIndex(const QModelIndex& index) { // incoming index is for the original model Q_ASSERT(!index.isValid() || index.model() == d->model); const QModelIndex previous = currentIndex(); QModelIndex idx(index); - QModelIndexList indexes; + QVector indexes; while (idx.isValid()) { indexes.prepend(idx); idx = idx.parent(); } for (int i = 0; i < d->levels; ++i) { QTreeView* view = d->views.at(i); if (indexes.size() <= i) { // select first item by default view->setCurrentIndex(view->model()->index(0, 0)); continue; } QModelIndex index; if (i + 1 == d->levels) { // select the very last index in the list (i.e. might be deep down in the actual tree) index = indexes.last(); } else { // select the first index for that level index = indexes.at(i); } view->setCurrentIndex(d->mapFromSource(index, i)); } emit currentIndexChanged(index, previous); } void MultiLevelListView::setRootIndex(const QModelIndex& index) { Q_ASSERT(!index.isValid() || index.model() == d->model); d->views.first()->setRootIndex(index); } void MultiLevelListView::setHeaderLabels(const QStringList& labels) { int n = qMin(d->levels, labels.size()); for (int i = 0; i < n; ++i) { d->proxies.at(i)->setLabel(labels[i]); } } void MultiLevelListView::setLastModelsFilterBehavior(KSelectionProxyModel::FilterBehavior filter) { if (d->proxies.isEmpty()) { return; } dynamic_cast(d->proxies.last())->setFilterBehavior(filter); } #include "multilevellistview.moc" #include "moc_multilevellistview.cpp" diff --git a/vcs/widgets/vcsdiffwidget.cpp b/vcs/widgets/vcsdiffwidget.cpp index 9c0481d2c9..d873321d1d 100644 --- a/vcs/widgets/vcsdiffwidget.cpp +++ b/vcs/widgets/vcsdiffwidget.cpp @@ -1,108 +1,104 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "vcsdiffwidget.h" #include #include #include #include "../vcsjob.h" #include "../vcsrevision.h" #include "../vcsdiff.h" #include "../debug.h" #include "ui_vcsdiffwidget.h" #include "vcsdiffpatchsources.h" namespace KDevelop { class VcsDiffWidgetPrivate { public: Ui::VcsDiffWidget* m_ui; VcsJob* m_job; VcsDiffWidget* q; VcsDiffWidgetPrivate(VcsDiffWidget* _q) : q(_q) { } void diffReady( KDevelop::VcsJob* job ) { if( job != m_job ) return; KDevelop::VcsDiff diff = m_job->fetchResults().value(); // Try using the patch-review plugin if possible VCSDiffPatchSource* patch = new VCSDiffPatchSource(diff); if(showVcsDiff(patch)) { q->deleteLater(); return; }else{ delete patch; } qCDebug(VCS) << "diff:" << diff.leftTexts().count(); - foreach( const KDevelop::VcsLocation &l, diff.leftTexts().keys() ) - { - qCDebug(VCS) << "diff:" << l.localUrl() << l.repositoryServer(); - } qCDebug(VCS) << "diff:" << diff.diff(); qCDebug(VCS) << "diff:" << diff.type(); qCDebug(VCS) << "diff:" << diff.contentType(); m_ui->diffDisplay->setPlainText( diff.diff() ); m_ui->diffDisplay->setReadOnly( true ); } }; VcsDiffWidget::VcsDiffWidget( KDevelop::VcsJob* job, QWidget* parent ) : QWidget( parent ), d(new VcsDiffWidgetPrivate(this)) { d->m_job = job; d->m_ui = new Ui::VcsDiffWidget(); d->m_ui->setupUi( this ); connect( d->m_job, &VcsJob::resultsReady, this, [&] (VcsJob* job) { d->diffReady(job); } ); ICore::self()->runController()->registerJob( d->m_job ); } VcsDiffWidget::~VcsDiffWidget() { delete d->m_ui; delete d; } void VcsDiffWidget::setRevisions( const KDevelop::VcsRevision& first, const KDevelop::VcsRevision& second ) { d->m_ui->revLabel->setText( i18n("Difference between revision %1 and %2:", first.prettyValue(), second.prettyValue() ) ); } } #include "moc_vcsdiffwidget.cpp"