diff --git a/language/duchain/navigation/useswidget.cpp b/language/duchain/navigation/useswidget.cpp index dd998aeaf..adc14f976 100644 --- a/language/duchain/navigation/useswidget.cpp +++ b/language/duchain/navigation/useswidget.cpp @@ -1,699 +1,699 @@ /* Copyright 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 "useswidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString backgroundColor(bool isHighlighted) { if (isHighlighted) { return QColor(251, 150, 242).name(); } else { return QColor(251, 250, 150).name(); } } } const int tooltipContextSize = 2; //How many lines around the use are shown in the tooltip ///The returned text is fully escaped ///@param cutOff The total count of characters that should be cut of, all in all on both sides together. ///@param range The range that is highlighted, and that will be preserved during cutting, given that there is enough room beside it. QString highlightAndEscapeUseText(QString line, int cutOff, KTextEditor::Range range) { int leftCutRoom = range.start().column(); int rightCutRoom = line.length() - range.end().column(); if(range.start().column() < 0 || range.end().column() > line.length() || cutOff > leftCutRoom + rightCutRoom) return QString(); //Not enough room for cutting off on sides int leftCut = 0; int rightCut = 0; if(leftCutRoom < rightCutRoom) { if(leftCutRoom * 2 >= cutOff) { //Enough room on both sides. Just cut. leftCut = cutOff / 2; rightCut = cutOff - leftCut; }else{ //Not enough room in left side, but enough room all together leftCut = leftCutRoom; rightCut = cutOff - leftCut; } }else{ if(rightCutRoom * 2 >= cutOff) { //Enough room on both sides. Just cut. rightCut = cutOff / 2; leftCut = cutOff - rightCut; }else{ //Not enough room in right side, but enough room all together rightCut = rightCutRoom; leftCut = cutOff - rightCut; } } Q_ASSERT(leftCut + rightCut <= cutOff); line = line.left(line.length() - rightCut); line = line.mid(leftCut); range += KTextEditor::Range(0, -leftCut, 0, -leftCut); Q_ASSERT(range.start().column() >= 0 && range.end().column() <= line.length()); //TODO: share code with context browser // mixing (255, 255, 0, 100) with white yields this: const QColor foreground(0, 0, 0); - return "" + line.left(range.start().column()).toHtmlEscaped() + return "" + line.left(range.start().column()).toHtmlEscaped() + "" + line.mid(range.start().column(), range.end().column() - range.start().column()).toHtmlEscaped() - + "" + line.mid(range.end().column(), line.length() - range.end().column()).toHtmlEscaped() + ""; + + "" + line.mid(range.end().column(), line.length() - range.end().column()).toHtmlEscaped() + ""; } /** * Note: the links in the HTML here are only used for styling * the navigation is implemented in the mouse press event handler */ OneUseWidget::OneUseWidget(IndexedDeclaration declaration, IndexedString document, KTextEditor::Range range, const CodeRepresentation& code) : m_range(new PersistentMovingRange(range, document)), m_declaration(declaration), m_document(document) { //Make the sizing of this widget independent of the content, because we will adapt the content to the size setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); m_sourceLine = code.line(m_range->range().start().line()); m_layout = new QHBoxLayout(this); m_layout->setContentsMargins(0, 0, 0, 0); setLayout(m_layout); setCursor(Qt::PointingHandCursor); m_label = new QLabel(this); m_icon = new QLabel(this); m_icon->setPixmap(QIcon::fromTheme(QStringLiteral("code-function")).pixmap(16)); DUChainReadLocker lock(DUChain::lock()); QString text = "" + i18nc("refers to a line in source code", "Line %1:", range.start().line()) + QStringLiteral(""); if(!m_sourceLine.isEmpty() && m_sourceLine.length() > m_range->range().end().column()) { text += "  " + highlightAndEscapeUseText(m_sourceLine, 0, m_range->range()); //Useful tooltip: int start = m_range->range().start().line() - tooltipContextSize; int end = m_range->range().end().line() + tooltipContextSize + 1; QString toolTipText; for(int a = start; a < end; ++a) { QString lineText = code.line(a).toHtmlEscaped(); if (m_range->range().start().line() <= a && m_range->range().end().line() >= a) { lineText = QStringLiteral("") + lineText + QStringLiteral(""); } if(!lineText.trimmed().isEmpty()) { toolTipText += lineText + "
"; } } if ( toolTipText.endsWith(QLatin1String("
")) ) { toolTipText.remove(toolTipText.length() - 4, 4); } setToolTip(QStringLiteral("
") + toolTipText + QStringLiteral("
")); } m_label->setText(text); m_layout->addWidget(m_icon); m_layout->addWidget(m_label); m_layout->setAlignment(Qt::AlignLeft); } void OneUseWidget::setHighlighted(bool highlight) { if (highlight == m_isHighlighted) { return; } if (highlight) { m_label->setText(m_label->text().replace("background-color:" + backgroundColor(false), "background-color:" + backgroundColor(true))); m_isHighlighted = true; } else { m_label->setText(m_label->text().replace("background-color:" + backgroundColor(true), "background-color:" + backgroundColor(false))); m_isHighlighted = false; } } bool KDevelop::OneUseWidget::isHighlighted() const { return m_isHighlighted; } void OneUseWidget::activateLink() { ICore::self()->documentController()->openDocument(m_document.toUrl(), m_range->range().start()); } void OneUseWidget::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton && !event->modifiers()) { activateLink(); event->accept(); } } OneUseWidget::~OneUseWidget() { } void OneUseWidget::resizeEvent ( QResizeEvent * event ) { ///Adapt the content QSize size = event->size(); KTextEditor::Range range = m_range->range(); int cutOff = 0; int maxCutOff = m_sourceLine.length() - (range.end().column() - range.start().column()); //Reset so we also get more context while up-sizing m_label->setText(QStringLiteral("") + i18nc("Refers to a line in source code", "Line %1:", range.start().line()+1) + QStringLiteral(" ") + highlightAndEscapeUseText(m_sourceLine, cutOff, range)); /// FIXME: this is incredibly ugly and slow... we could simply paint the text ourselves and elide it properly while(sizeHint().width() > size.width() && cutOff < maxCutOff) { //We've got to save space m_label->setText(QStringLiteral("") + i18nc("Refers to a line in source code", "Line %1:", range.start().line()+1) + QStringLiteral(" ") + highlightAndEscapeUseText(m_sourceLine, cutOff, range)); cutOff += 5; } event->accept(); QWidget::resizeEvent(event); } void NavigatableWidgetList::setShowHeader(bool show) { if(show && !m_headerLayout->parent()) m_layout->insertLayout(0, m_headerLayout); else m_headerLayout->setParent(nullptr); } NavigatableWidgetList::~NavigatableWidgetList() { delete m_headerLayout; } NavigatableWidgetList::NavigatableWidgetList(bool allowScrolling, uint maxHeight, bool vertical) : m_allowScrolling(allowScrolling) { m_layout = new QVBoxLayout; m_layout->setMargin(0); m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); m_layout->setSpacing(0); setBackgroundRole(QPalette::Base); m_useArrows = false; if(vertical) m_itemLayout = new QVBoxLayout; else m_itemLayout = new QHBoxLayout; m_itemLayout->setContentsMargins(0, 0, 0, 0); m_itemLayout->setMargin(0); m_itemLayout->setSpacing(0); // m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); // setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); setWidgetResizable(true); m_headerLayout = new QHBoxLayout; m_headerLayout->setMargin(0); m_headerLayout->setSpacing(0); if(m_useArrows) { auto previousButton = new QToolButton(); previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); auto nextButton = new QToolButton(); nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_headerLayout->addWidget(previousButton); m_headerLayout->addWidget(nextButton); } //hide these buttons for now, they're senseless m_layout->addLayout(m_headerLayout); QHBoxLayout* spaceLayout = new QHBoxLayout; spaceLayout->addSpacing(10); spaceLayout->addLayout(m_itemLayout); m_layout->addLayout(spaceLayout); if(maxHeight) setMaximumHeight(maxHeight); if(m_allowScrolling) { QWidget* contentsWidget = new QWidget; contentsWidget->setLayout(m_layout); setWidget(contentsWidget); }else{ setLayout(m_layout); } } void NavigatableWidgetList::deleteItems() { foreach(QWidget* item, items()) delete item; } void NavigatableWidgetList::addItem(QWidget* widget, int pos) { if(pos == -1) m_itemLayout->addWidget(widget); else m_itemLayout->insertWidget(pos, widget); } QList NavigatableWidgetList::items() const { QList ret; for(int a = 0; a < m_itemLayout->count(); ++a) { QWidgetItem* widgetItem = dynamic_cast(m_itemLayout->itemAt(a)); if(widgetItem) { ret << widgetItem->widget(); } } return ret; } bool NavigatableWidgetList::hasItems() const { return (bool)m_itemLayout->count(); } void NavigatableWidgetList::addHeaderItem(QWidget* widget, Qt::Alignment alignment) { if(m_useArrows) { Q_ASSERT(m_headerLayout->count() >= 2); //At least the 2 back/next buttons m_headerLayout->insertWidget(m_headerLayout->count()-1, widget, alignment); }else{ //We need to do this so the header doesn't get stretched widget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_headerLayout->insertWidget(m_headerLayout->count(), widget, alignment); // widget->setMaximumHeight(20); } } ///Returns whether the uses in the child should be a new uses-group bool isNewGroup(DUContext* parent, DUContext* child) { if(parent->type() == DUContext::Other && child->type() == DUContext::Other) return false; else return true; } uint countUses(int usedDeclarationIndex, DUContext* context) { uint ret = 0; for(int useIndex = 0; useIndex < context->usesCount(); ++useIndex) if(context->uses()[useIndex].m_declarationIndex == usedDeclarationIndex) ++ret; foreach(DUContext* child, context->childContexts()) if(!isNewGroup(context, child)) ret += countUses(usedDeclarationIndex, child); return ret; } QList createUseWidgets(const CodeRepresentation& code, int usedDeclarationIndex, IndexedDeclaration decl, DUContext* context) { QList ret; VERIFY_FOREGROUND_LOCKED for(int useIndex = 0; useIndex < context->usesCount(); ++useIndex) if(context->uses()[useIndex].m_declarationIndex == usedDeclarationIndex) ret << new OneUseWidget(decl, context->url(), context->transformFromLocalRevision(context->uses()[useIndex].m_range), code); foreach(DUContext* child, context->childContexts()) if(!isNewGroup(context, child)) ret += createUseWidgets(code, usedDeclarationIndex, decl, child); return ret; } ContextUsesWidget::ContextUsesWidget(const CodeRepresentation& code, QList usedDeclarations, IndexedDUContext context) : m_context(context) { setFrameShape(NoFrame); DUChainReadLocker lock(DUChain::lock()); QString headerText = i18n("Unknown context"); setUpdatesEnabled(false); if(context.data()) { DUContext* ctx = context.data(); if(ctx->scopeIdentifier(true).isEmpty()) headerText = i18n("Global"); else { headerText = ctx->scopeIdentifier(true).toString(); if(ctx->type() == DUContext::Function || (ctx->owner() && ctx->owner()->isFunctionDeclaration())) headerText += QLatin1String("(...)"); } QSet hadIndices; foreach(const IndexedDeclaration usedDeclaration, usedDeclarations) { int usedDeclarationIndex = ctx->topContext()->indexForUsedDeclaration(usedDeclaration.data(), false); if(hadIndices.contains(usedDeclarationIndex)) continue; hadIndices.insert(usedDeclarationIndex); if(usedDeclarationIndex != std::numeric_limits::max()) { foreach(OneUseWidget* widget, createUseWidgets(code, usedDeclarationIndex, usedDeclaration, ctx)) addItem(widget); } } } QLabel* headerLabel = new QLabel(i18nc("%1: source file", "In %1", "" + headerText.toHtmlEscaped() + ": ")); addHeaderItem(headerLabel); setUpdatesEnabled(true); connect(headerLabel, &QLabel::linkActivated, this, &ContextUsesWidget::linkWasActivated); } void ContextUsesWidget::linkWasActivated(QString link) { if ( link == QLatin1String("navigateToFunction") ) { DUChainReadLocker lock(DUChain::lock()); DUContext* context = m_context.context(); if(context) { CursorInRevision contextStart = context->range().start; KTextEditor::Cursor cursor(contextStart.line, contextStart.column); QUrl url = context->url().toUrl(); lock.unlock(); ForegroundLock fgLock; ICore::self()->documentController()->openDocument(url, cursor); } } } DeclarationWidget::DeclarationWidget(const CodeRepresentation& code, const IndexedDeclaration& decl) { setFrameShape(NoFrame); DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); if (Declaration* dec = decl.data()) { QLabel* headerLabel = new QLabel(dec->isDefinition() ? i18n("Definition") : i18n("Declaration")); addHeaderItem(headerLabel); addItem(new OneUseWidget(decl, dec->url(), dec->rangeInCurrentRevision(), code)); } setUpdatesEnabled(true); } TopContextUsesWidget::TopContextUsesWidget(IndexedDeclaration declaration, QList allDeclarations, IndexedTopDUContext topContext) : m_topContext(topContext) , m_declaration(declaration) , m_allDeclarations(allDeclarations) , m_usesCount(0) { m_itemLayout->setContentsMargins(10, 0, 0, 5); setFrameShape(NoFrame); setUpdatesEnabled(false); DUChainReadLocker lock(DUChain::lock()); QHBoxLayout * labelLayout = new QHBoxLayout; labelLayout->setContentsMargins(0, -1, 0, 0); // let's keep the spacing *above* the line QWidget* headerWidget = new QWidget; headerWidget->setLayout(labelLayout); headerWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); QLabel* label = new QLabel(this); m_icon = new QLabel(this); m_toggleButton = new QLabel(this); m_icon->setPixmap(QIcon::fromTheme(QStringLiteral("code-class")).pixmap(16)); labelLayout->addWidget(m_icon); labelLayout->addWidget(label); labelLayout->addWidget(m_toggleButton); labelLayout->setAlignment(Qt::AlignLeft); if(topContext.isLoaded()) m_usesCount = DUChainUtils::contextCountUses(topContext.data(), declaration.data()); QString labelText = i18ncp("%1: number of uses, %2: filename with uses", "%2: 1 use", "%2: %1 uses", m_usesCount, ICore::self()->projectController()->prettyFileName(topContext.url().toUrl())); label->setText(labelText); m_toggleButton->setText("   [" + i18nc("Refers to closing a UI element", "Collapse") + "]"); connect(m_toggleButton, &QLabel::linkActivated, this, &TopContextUsesWidget::labelClicked); addHeaderItem(headerWidget); setUpdatesEnabled(true); } int TopContextUsesWidget::usesCount() const { return m_usesCount; } QList buildContextUses(const CodeRepresentation& code, QList declarations, DUContext* context) { QList ret; if(!context->parentContext() || isNewGroup(context->parentContext(), context)) { ContextUsesWidget* created = new ContextUsesWidget(code, declarations, context); if(created->hasItems()) ret << created; else delete created; } foreach(DUContext* child, context->childContexts()) ret += buildContextUses(code, declarations, child); return ret; } void TopContextUsesWidget::setExpanded(bool expanded) { if(!expanded) { m_toggleButton->setText("   [" + i18nc("Refers to opening a UI element", "Expand") + "]"); deleteItems(); }else{ m_toggleButton->setText("   [" + i18nc("Refers to closing a UI element", "Collapse") + "]"); if(hasItems()) return; DUChainReadLocker lock(DUChain::lock()); TopDUContext* topContext = m_topContext.data(); if(topContext && m_declaration.data()) { CodeRepresentation::Ptr code = createCodeRepresentation(topContext->url()); setUpdatesEnabled(false); IndexedTopDUContext localTopContext(topContext); foreach(const IndexedDeclaration &decl, m_allDeclarations) { if(decl.indexedTopContext() == localTopContext) { addItem(new DeclarationWidget(*code, decl)); } } foreach(ContextUsesWidget* usesWidget, buildContextUses(*code, m_allDeclarations, topContext)) { addItem(usesWidget); } setUpdatesEnabled(true); } } } void TopContextUsesWidget::labelClicked() { if(hasItems()) { setExpanded(false); }else{ setExpanded(true); } } UsesWidget::~UsesWidget() { if (m_collector) { m_collector->setWidget(nullptr); } } UsesWidget::UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector) : NavigatableWidgetList(true) { DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); m_headerLine = new QLabel; redrawHeaderLine(); connect(m_headerLine, &QLabel::linkActivated, this, &UsesWidget::headerLinkActivated); m_layout->insertWidget(0, m_headerLine, 0, Qt::AlignTop); m_layout->setAlignment(Qt::AlignTop); m_itemLayout->setAlignment(Qt::AlignTop); m_progressBar = new QProgressBar; addHeaderItem(m_progressBar); if (!customCollector) { m_collector = QSharedPointer(new UsesWidget::UsesWidgetCollector(declaration)); } else { m_collector = customCollector; } m_collector->setProcessDeclarations(true); m_collector->setWidget(this); m_collector->startCollecting(); setUpdatesEnabled(true); } void UsesWidget::redrawHeaderLine() { m_headerLine->setText(headerLineText()); } const QString UsesWidget::headerLineText() const { return i18np("1 use found", "%1 uses found", countAllUses()) + " • " "[" + i18n("Expand all") + "] • " "[" + i18n("Collapse all") + "]"; } unsigned int UsesWidget::countAllUses() const { unsigned int totalUses = 0; foreach ( QWidget* w, items() ) { if ( TopContextUsesWidget* useWidget = dynamic_cast(w) ) { totalUses += useWidget->usesCount(); } } return totalUses; } void UsesWidget::setAllExpanded(bool expanded) { foreach ( QWidget* w, items() ) { if ( TopContextUsesWidget* useWidget = dynamic_cast(w) ) { useWidget->setExpanded(expanded); } } } void UsesWidget::headerLinkActivated(QString linkName) { if(linkName == QLatin1String("expandAll")) { setAllExpanded(true); } else if(linkName == QLatin1String("collapseAll")) { setAllExpanded(false); } } UsesWidget::UsesWidgetCollector::UsesWidgetCollector(IndexedDeclaration decl) : UsesCollector(decl), m_widget(nullptr) { } void UsesWidget::UsesWidgetCollector::setWidget(UsesWidget* widget ) { m_widget = widget; } void UsesWidget::UsesWidgetCollector::maximumProgress(uint max) { if (!m_widget) { return; } if(m_widget->m_progressBar) { m_widget->m_progressBar->setMaximum(max); m_widget->m_progressBar->setMinimum(0); m_widget->m_progressBar->setValue(0); }else{ qCWarning(LANGUAGE) << "maximumProgress called twice"; } } void UsesWidget::UsesWidgetCollector::progress(uint processed, uint total) { if (!m_widget) { return; } m_widget->redrawHeaderLine(); if(m_widget->m_progressBar) { m_widget->m_progressBar->setValue(processed); if(processed == total) { m_widget->setUpdatesEnabled(false); delete m_widget->m_progressBar; m_widget->m_progressBar = nullptr; m_widget->setShowHeader(false); m_widget->setUpdatesEnabled(true); } }else{ qCWarning(LANGUAGE) << "progress() called too often"; } } void UsesWidget::UsesWidgetCollector::processUses( KDevelop::ReferencedTopDUContext topContext ) { if (!m_widget) { return; } DUChainReadLocker lock; qCDebug(LANGUAGE) << "processing" << topContext->url().str(); TopContextUsesWidget* widget = new TopContextUsesWidget(declaration(), declarations(), topContext.data()); // move to back if it's just the declaration/definition bool toBack = widget->usesCount() == 0; // move to front the item belonging to the current open document IDocument* doc = ICore::self()->documentController()->activeDocument(); bool toFront = doc && (doc->url() == topContext->url().toUrl()); widget->setExpanded(true); m_widget->addItem(widget, toFront ? 0 : toBack ? widget->items().size() : -1); m_widget->redrawHeaderLine(); } QSize KDevelop::UsesWidget::sizeHint() const { QSize ret = QWidget::sizeHint(); if(ret.height() < 300) ret.setHeight(300); return ret; } diff --git a/plugins/filetemplates/templatepreviewtoolview.cpp b/plugins/filetemplates/templatepreviewtoolview.cpp index aea6909b7..ff948afb3 100644 --- a/plugins/filetemplates/templatepreviewtoolview.cpp +++ b/plugins/filetemplates/templatepreviewtoolview.cpp @@ -1,178 +1,180 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * 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 "templatepreviewtoolview.h" #include "ui_templatepreviewtoolview.h" #include "filetemplatesplugin.h" #include "templatepreview.h" #include #include #include #include #include using namespace KDevelop; TemplatePreviewToolView::TemplatePreviewToolView(FileTemplatesPlugin* plugin, QWidget* parent) : QWidget(parent) , ui(new Ui::TemplatePreviewToolView) , m_original(nullptr) , m_plugin(plugin) { ui->setupUi(this); + setWindowIcon(QIcon::fromTheme(QStringLiteral("document-preview"), windowIcon())); + ui->messageWidget->hide(); ui->emptyLinesPolicyComboBox->setCurrentIndex(1); IDocumentController* dc = ICore::self()->documentController(); if (dc->activeDocument()) { m_original = dc->activeDocument()->textDocument(); } if (m_original) { documentActivated(dc->activeDocument()); } connect(ui->projectRadioButton, &QRadioButton::toggled, this, &TemplatePreviewToolView::selectedRendererChanged); connect(ui->emptyLinesPolicyComboBox, static_cast(&QComboBox::currentIndexChanged), this, &TemplatePreviewToolView::selectedRendererChanged); selectedRendererChanged(); connect(dc, &IDocumentController::documentActivated, this, &TemplatePreviewToolView::documentActivated); connect(dc, &IDocumentController::documentClosed, this, &TemplatePreviewToolView::documentClosed); } TemplatePreviewToolView::~TemplatePreviewToolView() { delete ui; } void TemplatePreviewToolView::documentActivated(KDevelop::IDocument* document) { documentChanged(document->textDocument()); } void TemplatePreviewToolView::documentChanged(KTextEditor::Document* document) { if (!isVisible()) { return; } if (m_original) { disconnect(m_original, &KTextEditor::Document::textChanged, this, &TemplatePreviewToolView::documentChanged); } m_original = document; FileTemplatesPlugin::TemplateType type = FileTemplatesPlugin::NoTemplate; if (m_original) { connect(m_original, &KTextEditor::Document::textChanged, this, &TemplatePreviewToolView::documentChanged); type = m_plugin->determineTemplateType(document->url()); } switch (type) { case FileTemplatesPlugin::NoTemplate: ui->messageWidget->setMessageType(KMessageWidget::Information); if (m_original) { ui->messageWidget->setText(xi18n("The active text document is not a KDevelop template")); } else { ui->messageWidget->setText(i18n("No active text document.")); } ui->messageWidget->animatedShow(); ui->preview->setText(QString()); break; case FileTemplatesPlugin::FileTemplate: ui->classRadioButton->setChecked(true); sourceTextChanged(m_original->text()); break; case FileTemplatesPlugin::ProjectTemplate: ui->projectRadioButton->setChecked(true); sourceTextChanged(m_original->text()); break; } } void TemplatePreviewToolView::showEvent(QShowEvent*) { IDocument* doc = ICore::self()->documentController()->activeDocument(); documentChanged(doc ? doc->textDocument() : nullptr); } void TemplatePreviewToolView::documentClosed(IDocument* document) { m_original = nullptr; if (document && document->textDocument() == m_original) { documentChanged(nullptr); } } void TemplatePreviewToolView::sourceTextChanged(const QString& text) { QString errorString = ui->preview->setText(text, ui->projectRadioButton->isChecked(), m_policy); if (!errorString.isEmpty()) { ui->messageWidget->setMessageType(KMessageWidget::Error); ui->messageWidget->setText(errorString); ui->messageWidget->animatedShow(); } else { ui->messageWidget->animatedHide(); } if (m_original) { ui->preview->document()->setMode(m_original->mode()); } } void TemplatePreviewToolView::selectedRendererChanged() { if (ui->classRadioButton->isChecked()) { TemplateRenderer::EmptyLinesPolicy policy = TemplateRenderer::KeepEmptyLines; switch (ui->emptyLinesPolicyComboBox->currentIndex()) { case 0: policy = TemplateRenderer::KeepEmptyLines; break; case 1: policy = TemplateRenderer::TrimEmptyLines; break; case 2: policy = TemplateRenderer::RemoveEmptyLines; break; } m_policy = policy; } documentChanged(m_original); } diff --git a/plugins/welcomepage/qml/Develop.qml b/plugins/welcomepage/qml/Develop.qml index d5bd25935..00ccab60a 100644 --- a/plugins/welcomepage/qml/Develop.qml +++ b/plugins/welcomepage/qml/Develop.qml @@ -1,140 +1,140 @@ /* KDevelop * * Copyright 2011 Aleix Pol * Copyright 2016 Kevin Funk * * 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) 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. */ import QtQuick 2.0 import QtQuick.Layouts 1.2 import QtQuick.Controls 1.3 import org.kdevelop.welcomepage 4.3 StandardPage { id: root ColumnLayout { anchors.fill: parent anchors.margins: 20 spacing: 20 RowLayout { id: toolBar width: parent.width Button { iconName: "project-development-new-template" text: i18n("New Project") onClicked: kdev.retrieveMenuAction("project/project_new").trigger() } Button { text: i18n("Open Project") iconName: "project-development-open" onClicked: ICore.projectController.openProject() } Button { text: i18n("Fetch Project") - iconName: "download" + iconName: "edit-download" onClicked: kdev.retrieveMenuAction("project/project_fetch").trigger() } Button { iconName: "document-open-recent" text: i18n("Recent Projects") onClicked: kdev.showMenu("project/project_open_recent") } Item { Layout.fillWidth: true } } Label { id: greetingLabel visible: !sessionsView.visible Layout.fillWidth: true Layout.fillHeight: true text: i18n("

Welcome to KDevelop!

\n" + "

You can start working on a project by opening an existing or creating a new one via the above buttons.

\n" + "

If you need help, please check out the User Manual.

") + (Qt.platform.os === "windows" ? i18n("
\n" + "

Note for Windows users

\n" + "

Note that KDevelop does NOT ship a C/C++ compiler on Windows!

\n" + "

You need to install either GCC via MinGW or install a recent version of the Microsoft Visual Studio IDE and make sure the environment is setup correctly before starting KDevelop.

\n" + "

If you need further assistance, please check out the KDevelop under Windows instructions.

") : "") wrapMode: Text.WordWrap onLinkActivated: Qt.openUrlExternally(link) MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor } } ScrollView { Layout.fillHeight: true Layout.fillWidth: true visible: sessionsView.count > 1 // we always have at least one active session ListView { id: sessionsView anchors.fill: parent delegate: MouseArea { width: sessionsView.width height: visible ? 30 : 0 visible: projects.length > 0 hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: sessionsModel.loadSession(uuid) Label { readonly property string projectNamesString: projectNames.join(", ").replace(/.kdev4/g, "") width: parent.width text: display == "" ? projectNamesString : i18n("%1: %2", display, projectNamesString) elide: Text.ElideRight opacity: parent.containsMouse ? 0.8 : 1 } } model: SessionsModel { id: sessionsModel } header: Heading { text: i18n("Sessions") } } } } } diff --git a/project/projectchangesmodel.cpp b/project/projectchangesmodel.cpp index 952ae922b..7292615e2 100644 --- a/project/projectchangesmodel.cpp +++ b/project/projectchangesmodel.cpp @@ -1,280 +1,286 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Gonzalez 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 "projectchangesmodel.h" +#include "debug.h" + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KDevelop::IProject*) using namespace KDevelop; ProjectChangesModel::ProjectChangesModel(QObject* parent) : VcsFileChangesModel(parent) { foreach(IProject* p, ICore::self()->projectController()->projects()) addProject(p); connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &ProjectChangesModel::addProject); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectChangesModel::removeProject); connect(ICore::self()->documentController(), &IDocumentController::documentSaved, this, &ProjectChangesModel::documentSaved); connect(ICore::self()->projectController()->projectModel(), &ProjectModel::rowsInserted, this, &ProjectChangesModel::itemsAdded); connect(ICore::self()->runController(), &IRunController::jobUnregistered, this, &ProjectChangesModel::jobUnregistered); } ProjectChangesModel::~ProjectChangesModel() {} void ProjectChangesModel::addProject(IProject* p) { QStandardItem* it = new QStandardItem(p->name()); it->setData(p->name(), ProjectChangesModel::ProjectNameRole); IPlugin* plugin = p->versionControlPlugin(); if(plugin) { IBasicVersionControl* vcs = plugin->extension(); auto info = ICore::self()->pluginController()->pluginInfo(plugin); it->setIcon(QIcon::fromTheme(info.iconName())); it->setToolTip(vcs->name()); IBranchingVersionControl* branchingExtension = plugin->extension(); if(branchingExtension) { const auto pathUrl = p->path().toUrl(); branchingExtension->registerRepositoryForCurrentBranchChanges(pathUrl); // can't use new signal slot syntax here, IBranchingVersionControl is not a QObject connect(plugin, SIGNAL(repositoryBranchChanged(QUrl)), this, SLOT(repositoryBranchChanged(QUrl))); repositoryBranchChanged(pathUrl); } else reload(QList() << p); } else { it->setEnabled(false); } appendRow(it); } void ProjectChangesModel::removeProject(IProject* p) { QStandardItem* it=projectItem(p); removeRow(it->row()); } QStandardItem* findItemChild(QStandardItem* parent, const QVariant& value, int role = Qt::DisplayRole) { for(int i=0; irowCount(); i++) { QStandardItem* curr=parent->child(i); if(curr->data(role) == value) return curr; } return nullptr; } QStandardItem* ProjectChangesModel::projectItem(IProject* p) const { return findItemChild(invisibleRootItem(), p->name(), ProjectChangesModel::ProjectNameRole); } void ProjectChangesModel::updateState(IProject* p, const KDevelop::VcsStatusInfo& status) { QStandardItem* pItem = projectItem(p); Q_ASSERT(pItem); VcsFileChangesModel::updateState(pItem, status); } void ProjectChangesModel::changes(IProject* project, const QList& urls, IBasicVersionControl::RecursionMode mode) { IPlugin* vcsplugin=project->versionControlPlugin(); IBasicVersionControl* vcs = vcsplugin ? vcsplugin->extension() : nullptr; if(vcs && vcs->isVersionControlled(urls.first())) { //TODO: filter? VcsJob* job=vcs->status(urls, mode); job->setProperty("urls", qVariantFromValue>(urls)); job->setProperty("mode", qVariantFromValue(mode)); job->setProperty("project", qVariantFromValue(project)); connect(job, &VcsJob::finished, this, &ProjectChangesModel::statusReady); ICore::self()->runController()->registerJob(job); } } void ProjectChangesModel::statusReady(KJob* job) { VcsJob* status=static_cast(job); QList states = status->fetchResults().toList(); IProject* project = job->property("project").value(); if(!project) return; QSet foundUrls; foundUrls.reserve(states.size()); foreach(const QVariant& state, states) { const VcsStatusInfo st = state.value(); foundUrls += st.url(); updateState(project, st); } QStandardItem* itProject = projectItem(project); + if (!itProject) { + qCDebug(PROJECT) << "Project no longer listed in model:" << project->name() << "- skipping update"; + return; + } IBasicVersionControl::RecursionMode mode = IBasicVersionControl::RecursionMode(job->property("mode").toInt()); QSet uncertainUrls = urls(itProject).toSet().subtract(foundUrls); QList sourceUrls = job->property("urls").value>(); foreach(const QUrl& url, sourceUrls) { if(url.isLocalFile() && QDir(url.toLocalFile()).exists()) { foreach(const QUrl& currentUrl, uncertainUrls) { if((mode == IBasicVersionControl::NonRecursive && currentUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash) == url.adjusted(QUrl::StripTrailingSlash)) || (mode == IBasicVersionControl::Recursive && url.isParentOf(currentUrl)) ) { removeUrl(currentUrl); } } } } } void ProjectChangesModel::documentSaved(KDevelop::IDocument* document) { reload({document->url()}); } void ProjectChangesModel::itemsAdded(const QModelIndex& parent, int start, int end) { ProjectModel* model=ICore::self()->projectController()->projectModel(); ProjectBaseItem* item=model->itemFromIndex(parent); if(!item) return; IProject* project=item->project(); if(!project) return; QList urls; for(int i=start; iindex(i, 0, parent); item=model->itemFromIndex(idx); if(item->type()==ProjectBaseItem::File || item->type()==ProjectBaseItem::Folder || item->type()==ProjectBaseItem::BuildFolder) urls += item->path().toUrl(); } if(!urls.isEmpty()) changes(project, urls, KDevelop::IBasicVersionControl::NonRecursive); } void ProjectChangesModel::reload(const QList& projects) { foreach(IProject* project, projects) changes(project, {project->path().toUrl()}, KDevelop::IBasicVersionControl::Recursive); } void ProjectChangesModel::reload(const QList& urls) { foreach(const QUrl& url, urls) { IProject* project=ICore::self()->projectController()->findProjectForUrl(url); if (project) { // FIXME: merge multiple urls of the same project changes(project, {url}, KDevelop::IBasicVersionControl::NonRecursive); } } } void ProjectChangesModel::reloadAll() { QList< IProject* > projects = ICore::self()->projectController()->projects(); reload(projects); } void ProjectChangesModel::jobUnregistered(KJob* job) { static QList readOnly = QList() << KDevelop::VcsJob::Add << KDevelop::VcsJob::Remove << KDevelop::VcsJob::Pull << KDevelop::VcsJob::Commit << KDevelop::VcsJob::Move << KDevelop::VcsJob::Copy << KDevelop::VcsJob::Revert ; VcsJob* vcsjob=dynamic_cast(job); if(vcsjob && readOnly.contains(vcsjob->type())) { reloadAll(); } } void ProjectChangesModel::repositoryBranchChanged(const QUrl& url) { IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(project) { IPlugin* v = project->versionControlPlugin(); Q_ASSERT(v); IBranchingVersionControl* branching = v->extension(); Q_ASSERT(branching); VcsJob* job = branching->currentBranch(url); connect(job, &VcsJob::resultsReady, this, &ProjectChangesModel::branchNameReady); job->setProperty("project", QVariant::fromValue(project)); ICore::self()->runController()->registerJob(job); } } void ProjectChangesModel::branchNameReady(VcsJob* job) { IProject* project = qobject_cast(job->property("project").value()); if(job->status()==VcsJob::JobSucceeded) { QString name = job->fetchResults().toString(); QString branchName = name.isEmpty() ? i18n("no branch") : name; projectItem(project)->setText(i18nc("project name (branch name)", "%1 (%2)", project->name(), branchName)); } else { projectItem(project)->setText(project->name()); } reload(QList() << project); } diff --git a/shell/sourceformattercontroller.cpp b/shell/sourceformattercontroller.cpp index 012c69db1..520c3832c 100644 --- a/shell/sourceformattercontroller.cpp +++ b/shell/sourceformattercontroller.cpp @@ -1,629 +1,629 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur 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 "sourceformattercontroller.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 #include #include #include #include #include "core.h" #include "debug.h" #include "plugincontroller.h" #include "sourceformatterjob.h" namespace { namespace Strings { QString SourceFormatter() { return QStringLiteral("SourceFormatter"); } } } namespace KDevelop { QString SourceFormatterController::kateModeLineConfigKey() { return QStringLiteral("ModelinesEnabled"); } QString SourceFormatterController::kateOverrideIndentationConfigKey() { return QStringLiteral("OverrideKateIndentation"); } QString SourceFormatterController::styleCaptionKey() { return QStringLiteral("Caption"); } QString SourceFormatterController::styleContentKey() { return QStringLiteral("Content"); } QString SourceFormatterController::styleMimeTypesKey() { return QStringLiteral("MimeTypes"); } QString SourceFormatterController::styleSampleKey() { return QStringLiteral("StyleSample"); } SourceFormatterController::SourceFormatterController(QObject *parent) : ISourceFormatterController(parent), m_enabled(true) { setObjectName(QStringLiteral("SourceFormatterController")); setComponentName(QStringLiteral("kdevsourceformatter"), i18n("Source Formatter")); setXMLFile(QStringLiteral("kdevsourceformatter.rc")); if (Core::self()->setupFlags() & Core::NoUi) return; m_formatTextAction = actionCollection()->addAction(QStringLiteral("edit_reformat_source")); m_formatTextAction->setText(i18n("&Reformat Source")); m_formatTextAction->setToolTip(i18n("Reformat source using AStyle")); m_formatTextAction->setWhatsThis(i18n("Source reformatting functionality using astyle library.")); connect(m_formatTextAction, &QAction::triggered, this, &SourceFormatterController::beautifySource); m_formatLine = actionCollection()->addAction(QStringLiteral("edit_reformat_line")); m_formatLine->setText(i18n("Reformat Line")); m_formatLine->setToolTip(i18n("Reformat current line using AStyle")); m_formatLine->setWhatsThis(i18n("Source reformatting of line under cursor using astyle library.")); connect(m_formatLine, &QAction::triggered, this, &SourceFormatterController::beautifyLine); m_formatFilesAction = actionCollection()->addAction(QStringLiteral("tools_astyle")); m_formatFilesAction->setText(i18n("Reformat Files...")); m_formatFilesAction->setToolTip(i18n("Format file(s) using the current theme")); m_formatFilesAction->setWhatsThis(i18n("Formatting functionality using astyle library.")); connect(m_formatFilesAction, &QAction::triggered, this, static_cast(&SourceFormatterController::formatFiles)); // connect to both documentActivated & documentClosed, // otherwise we miss when the last document was closed connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &SourceFormatterController::updateFormatTextAction); connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &SourceFormatterController::updateFormatTextAction); // Use a queued connection, because otherwise the view is not yet fully set up connect(Core::self()->documentController(), &IDocumentController::documentLoaded, this, &SourceFormatterController::documentLoaded, Qt::QueuedConnection); updateFormatTextAction(); } void SourceFormatterController::documentLoaded( IDocument* doc ) { // NOTE: explicitly check this here to prevent crashes on shutdown // when this slot gets called (note: delayed connection) // but the text document was already destroyed // there have been unit tests that failed due to that... if (!doc->textDocument()) { return; } QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); adaptEditorIndentationMode( doc->textDocument(), formatterForMimeType(mime) ); } void SourceFormatterController::initialize() { } SourceFormatterController::~SourceFormatterController() { } ISourceFormatter* SourceFormatterController::formatterForUrl(const QUrl &url) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); return formatterForMimeType(mime); } KConfigGroup SourceFormatterController::sessionConfig() const { return KDevelop::Core::self()->activeSession()->config()->group( Strings::SourceFormatter() ); } KConfigGroup SourceFormatterController::globalConfig() const { return KSharedConfig::openConfig()->group( Strings::SourceFormatter() ); } ISourceFormatter* SourceFormatterController::findFirstFormatterForMimeType(const QMimeType& mime ) const { static QHash knownFormatters; if (knownFormatters.contains(mime.name())) return knownFormatters[mime.name()]; QList plugins = Core::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") ); foreach( IPlugin* p, plugins) { ISourceFormatter *iformatter = p->extension(); QSharedPointer formatter(createFormatterForPlugin(iformatter)); if( formatter->supportedMimeTypes().contains(mime.name()) ) { knownFormatters[mime.name()] = iformatter; return iformatter; } } knownFormatters[mime.name()] = nullptr; return nullptr; } static void populateStyleFromConfigGroup(SourceFormatterStyle* s, const KConfigGroup& stylegrp) { s->setCaption( stylegrp.readEntry( SourceFormatterController::styleCaptionKey(), QString() ) ); s->setContent( stylegrp.readEntry( SourceFormatterController::styleContentKey(), QString() ) ); s->setMimeTypes( stylegrp.readEntry( SourceFormatterController::styleMimeTypesKey(), QStringList() ) ); s->setOverrideSample( stylegrp.readEntry( SourceFormatterController::styleSampleKey(), QString() ) ); } SourceFormatter* SourceFormatterController::createFormatterForPlugin(ISourceFormatter *ifmt) const { SourceFormatter* formatter = new SourceFormatter(); formatter->formatter = ifmt; // Inserted a new formatter. Now fill it with styles foreach( const KDevelop::SourceFormatterStyle& style, ifmt->predefinedStyles() ) { formatter->styles[ style.name() ] = new SourceFormatterStyle(style); } KConfigGroup grp = globalConfig(); if( grp.hasGroup( ifmt->name() ) ) { KConfigGroup fmtgrp = grp.group( ifmt->name() ); foreach( const QString& subgroup, fmtgrp.groupList() ) { SourceFormatterStyle* s = new SourceFormatterStyle( subgroup ); KConfigGroup stylegrp = fmtgrp.group( subgroup ); populateStyleFromConfigGroup(s, stylegrp); formatter->styles[ s->name() ] = s; } } return formatter; } ISourceFormatter* SourceFormatterController::formatterForMimeType(const QMimeType& mime) { if( !m_enabled || !isMimeTypeSupported( mime ) ) { return nullptr; } QString formatter = sessionConfig().readEntry( mime.name(), QString() ); if( formatter.isEmpty() ) { return findFirstFormatterForMimeType( mime ); } QStringList formatterinfo = formatter.split( QStringLiteral("||"), QString::SkipEmptyParts ); if( formatterinfo.size() != 2 ) { qCDebug(SHELL) << "Broken formatting entry for mime:" << mime.name() << "current value:" << formatter; return nullptr; } return Core::self()->pluginControllerInternal()->extensionForPlugin( QStringLiteral("org.kdevelop.ISourceFormatter"), formatterinfo.at(0) ); } bool SourceFormatterController::isMimeTypeSupported(const QMimeType& mime) { if( findFirstFormatterForMimeType( mime ) ) { return true; } return false; } QString SourceFormatterController::indentationMode(const QMimeType& mime) { if (mime.inherits(QStringLiteral("text/x-c++src")) || mime.inherits(QStringLiteral("text/x-chdr")) || mime.inherits(QStringLiteral("text/x-c++hdr")) || mime.inherits(QStringLiteral("text/x-csrc")) || mime.inherits(QStringLiteral("text/x-java")) || mime.inherits(QStringLiteral("text/x-csharp"))) { return QStringLiteral("cstyle"); } return QStringLiteral("none"); } QString SourceFormatterController::addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType& mime) { if( !isMimeTypeSupported(mime) ) return input; QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$"); // If there already is a modeline in the document, adapt it while formatting, even // if "add modeline" is disabled. if( !sessionConfig().readEntry( SourceFormatterController::kateModeLineConfigKey(), false ) && kateModelineWithNewline.indexIn( input ) == -1 ) return input; ISourceFormatter* fmt = formatterForMimeType( mime ); ISourceFormatter::Indentation indentation = fmt->indentation(url); if( !indentation.isValid() ) return input; QString output; QTextStream os(&output, QIODevice::WriteOnly); QTextStream is(&input, QIODevice::ReadOnly); Q_ASSERT(fmt); QString modeline(QStringLiteral("// kate: ") + QStringLiteral("indent-mode ") + indentationMode(mime) + QStringLiteral("; ")); if(indentation.indentWidth) // We know something about indentation-width modeline.append(QStringLiteral("indent-width %1; ").arg(indentation.indentWidth)); if(indentation.indentationTabWidth != 0) // We know something about tab-usage { modeline.append(QStringLiteral("replace-tabs %1; ").arg(QLatin1String((indentation.indentationTabWidth == -1) ? "on" : "off"))); if(indentation.indentationTabWidth > 0) modeline.append(QStringLiteral("tab-width %1; ").arg(indentation.indentationTabWidth)); } qCDebug(SHELL) << "created modeline: " << modeline << endl; QRegExp kateModeline("^\\s*//\\s*kate:(.*)$"); bool modelinefound = false; QRegExp knownOptions("\\s*(indent-width|space-indent|tab-width|indent-mode|replace-tabs)"); while (!is.atEnd()) { QString line = is.readLine(); // replace only the options we care about if (kateModeline.indexIn(line) >= 0) { // match qCDebug(SHELL) << "Found a kate modeline: " << line << endl; modelinefound = true; QString options = kateModeline.cap(1); QStringList optionList = options.split(';', QString::SkipEmptyParts); os << modeline; foreach(QString s, optionList) { if (knownOptions.indexIn(s) < 0) { // unknown option, add it if(s.startsWith(' ')) s=s.mid(1); os << s << ";"; qCDebug(SHELL) << "Found unknown option: " << s << endl; } } os << endl; } else os << line << endl; } if (!modelinefound) os << modeline << endl; return output; } void SourceFormatterController::cleanup() { } void SourceFormatterController::updateFormatTextAction() { bool enabled = false; IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument(); if (doc) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); if (isMimeTypeSupported(mime)) enabled = true; } m_formatLine->setEnabled(enabled); m_formatTextAction->setEnabled(enabled); } void SourceFormatterController::beautifySource() { IDocument* idoc = KDevelop::ICore::self()->documentController()->activeDocument(); if (!idoc) return; KTextEditor::View* view = idoc->activeTextView(); if (!view) return; KTextEditor::Document* doc = view->document(); // load the appropriate formatter QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } // Ignore the modeline, as the modeline will be changed anyway adaptEditorIndentationMode( doc, formatter, true ); bool has_selection = view->selection(); if (has_selection) { QString original = view->selectionText(); QString output = formatter->formatSource(view->selectionText(), doc->url(), mime, doc->text(KTextEditor::Range(KTextEditor::Cursor(0,0),view->selectionRange().start())), doc->text(KTextEditor::Range(view->selectionRange().end(), doc->documentRange().end()))); //remove the final newline character, unless it should be there if (!original.endsWith('\n') && output.endsWith('\n')) output.resize(output.length() - 1); //there was a selection, so only change the part of the text related to it // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code( dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( view->selectionRange(), original, output ); } else { formatDocument(idoc, formatter, mime); } } void SourceFormatterController::beautifyLine() { KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); KDevelop::IDocument *doc = docController->activeDocument(); if (!doc || !doc->isTextDocument()) return; KTextEditor::Document *tDoc = doc->textDocument(); KTextEditor::View* view = doc->activeTextView(); if (!view) return; // load the appropriate formatter QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } const KTextEditor::Cursor cursor = view->cursorPosition(); const QString line = tDoc->line(cursor.line()); const QString prev = tDoc->text(KTextEditor::Range(0, 0, cursor.line(), 0)); const QString post = '\n' + tDoc->text(KTextEditor::Range(KTextEditor::Cursor(cursor.line() + 1, 0), tDoc->documentEnd())); const QString formatted = formatter->formatSource(line, doc->url(), mime, prev, post); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code(dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( KTextEditor::Range(cursor.line(), 0, cursor.line(), line.length()), line, formatted ); // advance cursor one line view->setCursorPosition(KTextEditor::Cursor(cursor.line() + 1, 0)); } void SourceFormatterController::formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime) { Q_ASSERT(doc); Q_ASSERT(formatter); qCDebug(SHELL) << "Running" << formatter->name() << "on" << doc->url(); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop CodeRepresentation::Ptr code = KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ); KTextEditor::Cursor cursor = doc->cursorPosition(); QString text = formatter->formatSource(code->text(), doc->url(), mime); text = addModelineForCurrentLang(text, doc->url(), mime); code->setText(text); doc->setCursorPosition(cursor); } void SourceFormatterController::settingsChanged() { if( sessionConfig().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), false ) ) foreach( KDevelop::IDocument* doc, ICore::self()->documentController()->openDocuments() ) adaptEditorIndentationMode( doc->textDocument(), formatterForUrl(doc->url()) ); } /** * Kate commands: * Use spaces for indentation: * "set-replace-tabs 1" * Use tabs for indentation (eventually mixed): * "set-replace-tabs 0" * Indent width: * "set-indent-width X" * Tab width: * "set-tab-width X" * */ void SourceFormatterController::adaptEditorIndentationMode(KTextEditor::Document *doc, ISourceFormatter *formatter, bool ignoreModeline ) { if( !formatter || !sessionConfig().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), false ) || !doc ) return; qCDebug(SHELL) << "adapting mode for" << doc->url(); QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$"); // modelines should always take precedence if( !ignoreModeline && kateModelineWithNewline.indexIn( doc->text() ) != -1 ) { qCDebug(SHELL) << "ignoring because a kate modeline was found"; return; } ISourceFormatter::Indentation indentation = formatter->indentation(doc->url()); if(indentation.isValid()) { struct CommandCaller { explicit CommandCaller(KTextEditor::Document* _doc) : doc(_doc), editor(KTextEditor::Editor::instance()) { Q_ASSERT(editor); } void operator()(QString cmd) { KTextEditor::Command* command = editor->queryCommand( cmd ); Q_ASSERT(command); QString msg; qCDebug(SHELL) << "calling" << cmd; foreach(KTextEditor::View* view, doc->views()) if( !command->exec( view, cmd, msg ) ) qCWarning(SHELL) << "setting indentation width failed: " << msg; } KTextEditor::Document* doc; KTextEditor::Editor* editor; } call(doc); if( indentation.indentWidth ) // We know something about indentation-width call( QStringLiteral("set-indent-width %1").arg(indentation.indentWidth ) ); if( indentation.indentationTabWidth != 0 ) // We know something about tab-usage { call( QStringLiteral("set-replace-tabs %1").arg( (indentation.indentationTabWidth == -1) ? 1 : 0 ) ); if( indentation.indentationTabWidth > 0 ) call( QStringLiteral("set-tab-width %1").arg(indentation.indentationTabWidth ) ); } }else{ qCDebug(SHELL) << "found no valid indentation"; } } void SourceFormatterController::formatFiles() { - if (m_prjItems.isEmpty()) + if (m_prjItems.isEmpty() && m_urls.isEmpty()) return; //get a list of all files in this folder recursively QList folders; foreach(KDevelop::ProjectBaseItem *item, m_prjItems) { if (!item) continue; if (item->folder()) folders.append(item->folder()); else if (item->file()) m_urls.append(item->file()->path().toUrl()); else if (item->target()) { foreach(KDevelop::ProjectFileItem *f, item->fileList()) m_urls.append(f->path().toUrl()); } } while (!folders.isEmpty()) { KDevelop::ProjectFolderItem *item = folders.takeFirst(); foreach(KDevelop::ProjectFolderItem *f, item->folderList()) folders.append(f); foreach(KDevelop::ProjectTargetItem *f, item->targetList()) { foreach(KDevelop::ProjectFileItem *child, f->fileList()) m_urls.append(child->path().toUrl()); } foreach(KDevelop::ProjectFileItem *f, item->fileList()) m_urls.append(f->path().toUrl()); } auto win = ICore::self()->uiController()->activeMainWindow()->window(); QMessageBox msgBox(QMessageBox::Question, i18n("Reformat files?"), i18n("Reformat all files in the selected folder?"), QMessageBox::Ok|QMessageBox::Cancel, win); msgBox.setDefaultButton(QMessageBox::Cancel); auto okButton = msgBox.button(QMessageBox::Ok); okButton->setText(i18n("Reformat")); msgBox.exec(); if (msgBox.clickedButton() == okButton) { auto formatterJob = new SourceFormatterJob(this); formatterJob->setFiles(m_urls); ICore::self()->runController()->registerJob(formatterJob); } } KDevelop::ContextMenuExtension SourceFormatterController::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { Q_UNUSED(parent); KDevelop::ContextMenuExtension ext; m_urls.clear(); m_prjItems.clear(); if (context->hasType(KDevelop::Context::EditorContext)) { if(m_formatTextAction->isEnabled()) ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatTextAction); } else if (context->hasType(KDevelop::Context::FileContext)) { KDevelop::FileContext* filectx = static_cast(context); m_urls = filectx->urls(); ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatFilesAction); } else if (context->hasType(KDevelop::Context::CodeContext)) { } else if (context->hasType(KDevelop::Context::ProjectItemContext)) { KDevelop::ProjectItemContext* prjctx = static_cast(context); m_prjItems = prjctx->items(); if ( !m_prjItems.isEmpty() ) { ext.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_formatFilesAction); } } return ext; } SourceFormatterStyle SourceFormatterController::styleForMimeType(const QMimeType& mime) { QStringList formatter = sessionConfig().readEntry( mime.name(), QString() ).split( QStringLiteral("||"), QString::SkipEmptyParts ); if( formatter.count() == 2 ) { SourceFormatterStyle s( formatter.at( 1 ) ); KConfigGroup fmtgrp = globalConfig().group( formatter.at(0) ); if( fmtgrp.hasGroup( formatter.at(1) ) ) { KConfigGroup stylegrp = fmtgrp.group( formatter.at(1) ); populateStyleFromConfigGroup(&s, stylegrp); } return s; } return SourceFormatterStyle(); } void SourceFormatterController::disableSourceFormatting(bool disable) { m_enabled = !disable; } bool SourceFormatterController::sourceFormattingEnabled() { return m_enabled; } } diff --git a/vcs/models/vcsfilechangesmodel.cpp b/vcs/models/vcsfilechangesmodel.cpp index 2157279fc..ac9384398 100644 --- a/vcs/models/vcsfilechangesmodel.cpp +++ b/vcs/models/vcsfilechangesmodel.cpp @@ -1,270 +1,295 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Split into separate class Copyright 2011 Andrey Batyiev 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 "vcsfilechangesmodel.h" +#include "debug.h" + #include #include #include #include #include #include namespace KDevelop { static QString stateToString(KDevelop::VcsStatusInfo::State state) { switch(state) { case KDevelop::VcsStatusInfo::ItemAdded: return i18nc("file was added to versioncontrolsystem", "Added"); case KDevelop::VcsStatusInfo::ItemDeleted: return i18nc("file was deleted from versioncontrolsystem", "Deleted"); case KDevelop::VcsStatusInfo::ItemHasConflicts: return i18nc("file is confilicting (versioncontrolsystem)", "Has Conflicts"); case KDevelop::VcsStatusInfo::ItemModified: return i18nc("version controlled file was modified", "Modified"); case KDevelop::VcsStatusInfo::ItemUpToDate: return i18nc("file is up to date in versioncontrolsystem", "Up To Date"); case KDevelop::VcsStatusInfo::ItemUnknown: case KDevelop::VcsStatusInfo::ItemUserState: return i18nc("file is not known to versioncontrolsystem", "Unknown"); } return i18nc("Unknown VCS file status, probably a backend error", "?"); } static QIcon stateToIcon(KDevelop::VcsStatusInfo::State state) { switch(state) { case KDevelop::VcsStatusInfo::ItemAdded: return QIcon::fromTheme(QStringLiteral("vcs-added")); case KDevelop::VcsStatusInfo::ItemDeleted: return QIcon::fromTheme(QStringLiteral("vcs-removed")); case KDevelop::VcsStatusInfo::ItemHasConflicts: return QIcon::fromTheme(QStringLiteral("vcs-conflicting")); case KDevelop::VcsStatusInfo::ItemModified: return QIcon::fromTheme(QStringLiteral("vcs-locally-modified")); case KDevelop::VcsStatusInfo::ItemUpToDate: return QIcon::fromTheme(QStringLiteral("vcs-normal")); case KDevelop::VcsStatusInfo::ItemUnknown: case KDevelop::VcsStatusInfo::ItemUserState: return QIcon::fromTheme(QStringLiteral("unknown")); } return QIcon::fromTheme(QStringLiteral("dialog-error")); } VcsFileChangesSortProxyModel::VcsFileChangesSortProxyModel(QObject* parent) : QSortFilterProxyModel(parent) { } bool VcsFileChangesSortProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { const auto leftStatus = source_left.data(VcsFileChangesModel::StateRole).value(); const auto rightStatus = source_right.data(VcsFileChangesModel::StateRole).value(); if (leftStatus != rightStatus) { return leftStatus < rightStatus; } const QString leftPath = source_left.data(VcsFileChangesModel::UrlRole).toString(); const QString rightPath = source_right.data(VcsFileChangesModel::UrlRole).toString(); return QString::localeAwareCompare(leftPath, rightPath) < 0; } class VcsStatusInfoItem : public QStandardItem { public: explicit VcsStatusInfoItem(const VcsStatusInfo& info) : QStandardItem() , m_info(info) {} void setStatus(const VcsStatusInfo& info) { m_info = info; emitDataChanged(); } QVariant data(int role) const override { switch(role) { case Qt::DisplayRole: return stateToString(m_info.state()); case Qt::DecorationRole: return stateToIcon(m_info.state()); case VcsFileChangesModel::VcsStatusInfoRole: return QVariant::fromValue(m_info); case VcsFileChangesModel::UrlRole: return m_info.url(); case VcsFileChangesModel::StateRole: return QVariant::fromValue(m_info.state()); } return {}; } private: VcsStatusInfo m_info; }; class VcsFileChangesModelPrivate { public: bool allowSelection; }; VcsFileChangesModel::VcsFileChangesModel(QObject *parent, bool allowSelection) : QStandardItemModel(parent), d(new VcsFileChangesModelPrivate {allowSelection} ) { setColumnCount(2); setHeaderData(0, Qt::Horizontal, i18n("Filename")); setHeaderData(1, Qt::Horizontal, i18n("Status")); } VcsFileChangesModel::~VcsFileChangesModel() { } int VcsFileChangesModel::updateState(QStandardItem *parent, const KDevelop::VcsStatusInfo &status) { if(status.state()==VcsStatusInfo::ItemUnknown || status.state()==VcsStatusInfo::ItemUpToDate) { removeUrl(status.url()); return -1; } else { QStandardItem* item = fileItemForUrl(parent, status.url()); if(!item) { QString path = ICore::self()->projectController()->prettyFileName(status.url(), KDevelop::IProjectController::FormatPlain); QMimeType mime = status.url().isLocalFile() ? QMimeDatabase().mimeTypeForFile(status.url().toLocalFile(), QMimeDatabase::MatchExtension) : QMimeDatabase().mimeTypeForUrl(status.url()); QIcon icon = QIcon::fromTheme(mime.iconName()); item = new QStandardItem(icon, path); auto itStatus = new VcsStatusInfoItem(status); if(d->allowSelection) { item->setCheckable(true); item->setCheckState(status.state() == VcsStatusInfo::ItemUnknown ? Qt::Unchecked : Qt::Checked); } parent->appendRow({ item, itStatus }); } else { QStandardItem *parent = item->parent(); if(parent == nullptr) parent = invisibleRootItem(); auto statusInfoItem = static_cast(parent->child(item->row(), 1)); statusInfoItem->setStatus(status); } return item->row(); } } QVariant VcsFileChangesModel::data(const QModelIndex &index, int role) const { if (role >= VcsStatusInfoRole && index.column()==0) { return QStandardItemModel::data(index.sibling(index.row(), 1), role); } return QStandardItemModel::data(index, role); } QStandardItem* VcsFileChangesModel::fileItemForUrl(QStandardItem* parent, const QUrl& url) const { + Q_ASSERT(parent); + if (!parent) { + qCWarning(VCS) << "null QStandardItem passed to" << Q_FUNC_INFO; + return nullptr; + } + for(int i=0, c=parent->rowCount(); ichild(i); if(indexFromItem(item).data(UrlRole).toUrl() == url) { return parent->child(i); } } return nullptr; } void VcsFileChangesModel::setAllChecked(bool checked) { if(!d->allowSelection) return; QStandardItem* parent = invisibleRootItem(); for(int i = 0, c = parent->rowCount(); i < c; i++) { QStandardItem* item = parent->child(i); item->setCheckState(checked ? Qt::Checked : Qt::Unchecked); } } QList VcsFileChangesModel::checkedUrls(QStandardItem *parent) const { + Q_ASSERT(parent); + if (!parent) { + qCWarning(VCS) << "null QStandardItem passed to" << Q_FUNC_INFO; + return {}; + } + QList ret; for(int i = 0, c = parent->rowCount(); i < c; i++) { QStandardItem* item = parent->child(i); if(!d->allowSelection || item->checkState() == Qt::Checked) { ret << indexFromItem(item).data(UrlRole).toUrl(); } } return ret; } QList VcsFileChangesModel::urls(QStandardItem *parent) const { + Q_ASSERT(parent); + if (!parent) { + qCWarning(VCS) << "null QStandardItem passed to" << Q_FUNC_INFO; + return {}; + } + QList ret; for(int i = 0, c = parent->rowCount(); i < c; i++) { QStandardItem* item = parent->child(i); ret << indexFromItem(item).data(UrlRole).toUrl(); } return ret; } void VcsFileChangesModel::checkUrls(QStandardItem *parent, const QList& urls) const { - QSet urlSet(urls.toSet()); + Q_ASSERT(parent); + if (!parent) { + qCWarning(VCS) << "null QStandardItem passed to" << Q_FUNC_INFO; + return; + } if(!d->allowSelection) return; + QSet urlSet(urls.toSet()); for(int i = 0, c = parent->rowCount(); i < c; i++) { QStandardItem* item = parent->child(i); item->setCheckState(urlSet.contains(indexFromItem(item).data(UrlRole).toUrl()) ? Qt::Checked : Qt::Unchecked); } } void VcsFileChangesModel::setIsCheckbable(bool checkable) { d->allowSelection = checkable; } bool VcsFileChangesModel::isCheckable() const { return d->allowSelection; } bool VcsFileChangesModel::removeUrl(const QUrl& url) { const auto matches = match(index(0, 0), UrlRole, url, 1, Qt::MatchExactly); if (matches.isEmpty()) return false; const auto& idx = matches.first(); return removeRow(idx.row(), idx.parent()); } }