diff --git a/language/codegen/basicrefactoring.cpp b/language/codegen/basicrefactoring.cpp index d5e8c67a4f..4ce24cb84c 100644 --- a/language/codegen/basicrefactoring.cpp +++ b/language/codegen/basicrefactoring.cpp @@ -1,264 +1,288 @@ /* This file is part of KDevelop * * Copyright 2014 Miquel Sabaté * * 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. */ // Qt #include // KDE / KDevelop #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_basicrefactoring.h" namespace KDevelop { //BEGIN: BasicRefactoringCollector BasicRefactoringCollector::BasicRefactoringCollector(const IndexedDeclaration &decl) : UsesWidgetCollector(decl) { setCollectConstructors(true); setCollectDefinitions(true); setCollectOverloads(true); } QVector BasicRefactoringCollector::allUsingContexts() const { return m_allUsingContexts; } void BasicRefactoringCollector::processUses(KDevelop::ReferencedTopDUContext topContext) { m_allUsingContexts << IndexedTopDUContext(topContext.data()); UsesWidgetCollector::processUses(topContext); } //END: BasicRefactoringCollector //BEGIN: BasicRefactoring BasicRefactoring::BasicRefactoring(QObject *parent) : QObject(parent) { /* There's nothing to do here. */ } void BasicRefactoring::fillContextMenu(ContextMenuExtension &extension, Context *context) { DeclarationContext *declContext = dynamic_cast(context); if (!declContext) return; DUChainReadLocker lock; Declaration *declaration = declContext->declaration().data(); if (declaration && acceptForContextMenu(declaration)) { QFileInfo finfo(declaration->topContext()->url().str()); if (finfo.isWritable()) { QAction *action = new QAction(i18n("Rename \"%1\"...", declaration->qualifiedIdentifier().toString()), 0); action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); action->setIcon(KIcon("edit-rename")); connect(action, SIGNAL(triggered(bool)), this, SLOT(executeRenameAction())); extension.addAction(ContextMenuExtension::RefactorGroup, action); } } } DocumentChangeSet::ChangeResult BasicRefactoring::applyChanges(const QString &oldName, const QString &newName, DocumentChangeSet &changes, DUContext *context, int usedDeclarationIndex) { if (usedDeclarationIndex == std::numeric_limits::max()) return DocumentChangeSet::ChangeResult(true); for (int a = 0; a < context->usesCount(); ++a) { const Use &use(context->uses()[a]); if (use.m_declarationIndex != usedDeclarationIndex) continue; if (use.m_range.isEmpty()) { kDebug() << "found empty use"; continue; } DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(context->url(), context->transformFromLocalRevision(use.m_range), oldName, newName)); if (!result) return result; } foreach (DUContext *child, context->childContexts()) { DocumentChangeSet::ChangeResult result = applyChanges(oldName, newName, changes, child, usedDeclarationIndex); if (!result) return result; } return DocumentChangeSet::ChangeResult(true); } DocumentChangeSet::ChangeResult BasicRefactoring::applyChangesToDeclarations(const QString &oldName, const QString &newName, DocumentChangeSet &changes, const QList &declarations) { foreach (const IndexedDeclaration &decl, declarations) { Declaration *declaration = decl.data(); if (!declaration) continue; if (declaration->range().isEmpty()) kDebug() << "found empty declaration"; TopDUContext *top = declaration->topContext(); DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(top->url(), declaration->rangeInCurrentRevision(), oldName, newName)); if (!result) return result; } return DocumentChangeSet::ChangeResult(true); } KDevelop::IndexedDeclaration BasicRefactoring::declarationUnderCursor(bool allowUse) { KDevelop::IDocument *doc = ICore::self()->documentController()->activeDocument(); if (doc && doc->textDocument() && doc->textDocument()->activeView()) { DUChainReadLocker lock; if (allowUse) return DUChainUtils::itemUnderCursor(doc->url(), SimpleCursor(doc->textDocument()->activeView()->cursorPosition())); else return DUChainUtils::declarationInLine(SimpleCursor(doc->textDocument()->activeView()->cursorPosition()), DUChainUtils::standardContextForUrl(doc->url())); } return KDevelop::IndexedDeclaration(); } void BasicRefactoring::startInteractiveRename(const KDevelop::IndexedDeclaration &decl) { DUChainReadLocker lock(DUChain::lock()); Declaration *declaration = decl.data(); if (!declaration) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("No declaration under cursor")); return; } QFileInfo info(declaration->topContext()->url().str()); if (!info.isWritable()) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Declaration is located in non-writeable file %1.", declaration->topContext()->url().str())); return; } QString originalName = declaration->identifier().identifier().str(); - QString replacementName; + lock.unlock(); + + NameAndCollector nc = newNameForDeclaration(DeclarationPointer(declaration)); + + if (nc.newName == originalName || nc.newName.isEmpty()) + return; + + renameCollectedDeclarations(nc.collector.data(), nc.newName, originalName); +} + +bool BasicRefactoring::acceptForContextMenu(const Declaration *decl) +{ + // Default implementation. Some language plugins might override it to + // handle some cases. + Q_UNUSED(decl); + return true; +} + +void BasicRefactoring::executeRenameAction() +{ + QAction *action = qobject_cast(sender()); + if (action) { + IndexedDeclaration decl = action->data().value(); + if(!decl.isValid()) + decl = declarationUnderCursor(); + startInteractiveRename(decl); + } +} + +BasicRefactoring::NameAndCollector BasicRefactoring::newNameForDeclaration(const KDevelop::DeclarationPointer& declaration) +{ + DUChainReadLocker lock; + if (!declaration) { + return {}; + } - BasicRefactoringCollector *collector = new BasicRefactoringCollector(decl); + QSharedPointer collector(new BasicRefactoringCollector(declaration.data())); Ui::RenameDialog renameDialog; QDialog dialog; renameDialog.setupUi(&dialog); - UsesWidget uses(declaration, collector); + UsesWidget uses(declaration.data(), collector); //So the context-links work - QWidget *navigationWidget = declaration->context()->createNavigationWidget(declaration); + QWidget *navigationWidget = declaration->context()->createNavigationWidget(declaration.data()); AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget); if (abstractNavigationWidget) connect(&uses, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), abstractNavigationWidget, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); dialog.setWindowTitle(i18n("Rename %1", declaration->toString())); + renameDialog.edit->setText(declaration->identifier().identifier().str()); renameDialog.edit->selectAll(); renameDialog.tabWidget->addTab(&uses, i18n("Uses")); if (navigationWidget) renameDialog.tabWidget->addTab(navigationWidget, i18n("Declaration Info")); - lock.unlock(); if (dialog.exec() != QDialog::Accepted) - return; + return {}; - replacementName = renameDialog.edit->text(); - if (replacementName == originalName || replacementName.isEmpty()) - return; + //TODO: input validation + return {renameDialog.edit->text(),collector}; +} +DocumentChangeSet BasicRefactoring::renameCollectedDeclarations(KDevelop::BasicRefactoringCollector* collector, const QString& replacementName, const QString& originalName, bool apply) +{ DocumentChangeSet changes; - lock.lock(); - foreach (const KDevelop::IndexedTopDUContext &collected, collector->allUsingContexts()) { + DUChainReadLocker lock; + + foreach (const KDevelop::IndexedTopDUContext& collected, collector->allUsingContexts()) { QSet hadIndices; - foreach (const IndexedDeclaration &decl, collector->declarations()) { + foreach (const IndexedDeclaration& decl, collector->declarations()) { uint usedDeclarationIndex = collected.data()->indexForUsedDeclaration(decl.data(), false); if (hadIndices.contains(usedDeclarationIndex)) continue; hadIndices.insert(usedDeclarationIndex); DocumentChangeSet::ChangeResult result = applyChanges(originalName, replacementName, changes, collected.data(), usedDeclarationIndex); if (!result) { KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); - return; + return {}; } } } DocumentChangeSet::ChangeResult result = applyChangesToDeclarations(originalName, replacementName, changes, collector->declarations()); - if(!result) { + if (!result) { KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); - return; + return {}; } ///We have to ignore failed changes for now, since uses of a constructor or of operator() may be created on "(" parens changes.setReplacementPolicy(DocumentChangeSet::IgnoreFailedChange); + + if (!apply) { + return changes; + } + result = changes.applyAllChanges(); - if(!result) { + if (!result) { KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); - return; } -} - -bool BasicRefactoring::acceptForContextMenu(const Declaration *decl) -{ - // Default implementation. Some language plugins might override it to - // handle some cases. - Q_UNUSED(decl); - return true; -} -void BasicRefactoring::executeRenameAction() -{ - QAction *action = qobject_cast(sender()); - if (action) { - IndexedDeclaration decl = action->data().value(); - if(!decl.isValid()) - decl = declarationUnderCursor(); - startInteractiveRename(decl); - } + return {}; } //END: BasicRefactoring } // End of namespace KDevelop #include "basicrefactoring.moc" diff --git a/language/codegen/basicrefactoring.h b/language/codegen/basicrefactoring.h index 06606a5b87..8c7f782bdc 100644 --- a/language/codegen/basicrefactoring.h +++ b/language/codegen/basicrefactoring.h @@ -1,112 +1,134 @@ /* This file is part of KDevelop * * Copyright 2014 Miquel Sabaté * * 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. */ #ifndef BASICREFACTORING_H_ #define BASICREFACTORING_H_ #include -#include +#include #include #include #include namespace KDevelop { class ContextMenuExtension; class IndexedDeclaration; class Context; class Declaration; class DUContext; - /** * A widget that show the uses that it has collected for * the given declaration. */ class KDEVPLATFORMLANGUAGE_EXPORT BasicRefactoringCollector : public UsesWidget::UsesWidgetCollector { public: BasicRefactoringCollector(const IndexedDeclaration &decl); QVector allUsingContexts() const; protected: /// Process the uses for the given TopDUContext. virtual void processUses(KDevelop::ReferencedTopDUContext topContext) override; private: QVector m_allUsingContexts; }; + /// The base class for Refactoring classes from Language plugins. class KDEVPLATFORMLANGUAGE_EXPORT BasicRefactoring : public QObject { Q_OBJECT public: explicit BasicRefactoring(QObject *parent = NULL); /// Update the context menu with the "Rename" action. virtual void fillContextMenu(KDevelop::ContextMenuExtension &extension, KDevelop::Context *context); + struct NameAndCollector { + QString newName; + QSharedPointer collector; + }; + protected: /** * Apply the changes to the uses that can be found inside the given * context and its children. * NOTE: the DUChain must be locked. */ virtual DocumentChangeSet::ChangeResult applyChanges(const QString &oldName, const QString &newName, DocumentChangeSet &changes, DUContext *context, int usedDeclarationIndex); /** * Apply the changes to the given declarations. * NOTE: the DUChain must be locked. */ virtual DocumentChangeSet::ChangeResult applyChangesToDeclarations(const QString &oldName, const QString &newName, DocumentChangeSet &changes, const QList &declarations); /** * Get the declaration under the current position of the cursor. * * @param allowUse Set to false if the declarations to be returned * cannot come from uses. */ virtual IndexedDeclaration declarationUnderCursor(bool allowUse = true); - /// Start the renaming of a declaration. + /** + * Start the renaming of a declaration. + * This function retrieves the new name for declaration @p decl and if approved renames all instances of it. + */ virtual void startInteractiveRename(const KDevelop::IndexedDeclaration &decl); + /** + * Asks user to input a new name for @p declaration + * @return new name or empty string if user changed his mind or new name contains inappropriate symbols (e.g. spaces, points, braces e.t.c) and the collector used for collecting information about @p declaration. + * NOTE: unlock the DUChain before calling this. + */ + virtual BasicRefactoring::NameAndCollector newNameForDeclaration(const KDevelop::DeclarationPointer& declaration); + + /** + * Renames all declarations collected by @p collector from @p oldName to @p newName + * @param apply - if changes should be applied immediately + * @return all changes if @p apply is false and empty set otherwise. + */ + DocumentChangeSet renameCollectedDeclarations(KDevelop::BasicRefactoringCollector* collector, const QString& replacementName, const QString& originalName, bool apply = true); + /** * @returns true if we can show the interactive rename widget for the * given declaration. The default implementation just returns true. */ virtual bool acceptForContextMenu(const Declaration *decl); private slots: void executeRenameAction(); }; } // End of namespace KDevelop #endif /* BASICREFACTORING_H_ */ diff --git a/language/duchain/navigation/useswidget.cpp b/language/duchain/navigation/useswidget.cpp index 2fd0bd9abc..aec0e4495f 100644 --- a/language/duchain/navigation/useswidget.cpp +++ b/language/duchain/navigation/useswidget.cpp @@ -1,633 +1,651 @@ /* 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; 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, uint cutOff, SimpleRange range) { uint leftCutRoom = range.start.column; uint 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 uint leftCut = 0; uint 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.start.column -= leftCut; range.end.column -= 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 background(251, 250, 150); const QColor foreground(0, 0, 0); return "" + Qt::escape(line.left(range.start.column)) + "" + Qt::escape(line.mid(range.start.column, range.end.column - range.start.column)) + "" + Qt::escape(line.mid(range.end.column, line.length() - range.end.column)) + ""; } OneUseWidget::OneUseWidget(IndexedDeclaration declaration, IndexedString document, SimpleRange 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); setLayout(m_layout); m_label = new QLabel(this); m_icon = new QLabel(this); m_icon->setPixmap(KIcon("code-function").pixmap(16)); connect(m_label, SIGNAL(linkActivated(QString)), this, SLOT(jumpTo())); DUChainReadLocker lock(DUChain::lock()); QString text = "" + i18nc("refers to a line in source code", "Line %1:", range.start.line) + QString(""); 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 = Qt::escape(code.line(a)); if (m_range->range().start.line <= a && m_range->range().end.line >= a) { lineText = QString("") + lineText + QString(""); } if(!lineText.trimmed().isEmpty()) { toolTipText += lineText + "
"; } } if ( toolTipText.endsWith("
") ) { toolTipText.remove(toolTipText.length() - 4, 4); } setToolTip(QString("
") + toolTipText + QString("
")); } m_label->setText(text); m_layout->addWidget(m_icon); m_layout->addWidget(m_label); m_layout->setAlignment(Qt::AlignLeft); } void OneUseWidget::jumpTo() { //This is used to execute the slot delayed in the event-loop, so crashes are avoided ICore::self()->documentController()->openDocument(m_document.toUrl(), m_range->range().start.textCursor()); } OneUseWidget::~OneUseWidget() { } void OneUseWidget::resizeEvent ( QResizeEvent * event ) { ///Adapt the content QSize size = event->size(); SimpleRange 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(QString("") + i18nc("Refers to a line in source code", "Line %1", range.start.line+1) + QString(" %2").arg(highlightAndEscapeUseText(m_sourceLine, cutOff, range))); while(sizeHint().width() > size.width() && cutOff < maxCutOff) { //We've got to save space m_label->setText(QString("") + i18nc("Refers to a line in source code", "Line %1", range.start.line+1) + QString(" %2").arg(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(0); } 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->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) { m_previousButton = new QToolButton(); m_previousButton->setIcon(KIcon("go-previous")); m_nextButton = new QToolButton(); m_nextButton->setIcon(KIcon("go-next")); m_headerLayout->addWidget(m_previousButton); m_headerLayout->addWidget(m_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 += "(...)"; } 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", "" + Qt::escape(headerText) + ": ")); addHeaderItem(headerLabel); setUpdatesEnabled(true); connect(headerLabel, SIGNAL(linkActivated(QString)), this, SLOT(linkWasActivated(QString))); } void ContextUsesWidget::linkWasActivated(QString link) { if ( link == "navigateToFunction" ) { DUChainReadLocker lock(DUChain::lock()); DUContext* context = m_context.context(); if(context) { CursorInRevision contextStart = context->range().start; KTextEditor::Cursor cursor(contextStart.line, contextStart.column); KUrl 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; 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(KIcon("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, SIGNAL(linkActivated(QString)), this, SLOT(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() { - delete m_collector; +UsesWidget::~UsesWidget() +{ + if (m_collector) { + m_collector->setWidget(0); + } } -UsesWidget::UsesWidget(IndexedDeclaration declaration, UsesWidgetCollector* customCollector) : NavigatableWidgetList(true) { +UsesWidget::UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector) + : NavigatableWidgetList(true) +{ DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); m_headerLine = new QLabel; redrawHeaderLine(); connect(m_headerLine, SIGNAL(linkActivated(QString)), this, SLOT(headerLinkActivated(QString))); 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 = new UsesWidgetCollector(declaration); - }else{ - m_collector = customCollector; + if (!customCollector) { + m_collector = QSharedPointer(new 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 == "expandAll") { setAllExpanded(true); } else if(linkName == "collapseAll") { setAllExpanded(false); } } UsesWidget::UsesWidgetCollector::UsesWidgetCollector(IndexedDeclaration decl) : UsesCollector(decl), m_widget(0) { } 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{ kWarning() << "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 = 0; m_widget->setShowHeader(false); m_widget->setUpdatesEnabled(true); } }else{ kWarning() << "progress() called too often"; } } void UsesWidget::UsesWidgetCollector::processUses( KDevelop::ReferencedTopDUContext topContext ) { - DUChainReadLocker lock(DUChain::lock()); + if (!m_widget) { + return; + } + + DUChainReadLocker lock; + kDebug() << "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().equals(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; } #include "useswidget.moc" diff --git a/language/duchain/navigation/useswidget.h b/language/duchain/navigation/useswidget.h index f52d8688a8..a2bf006000 100644 --- a/language/duchain/navigation/useswidget.h +++ b/language/duchain/navigation/useswidget.h @@ -1,163 +1,162 @@ /* 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. */ #ifndef KDEVPLATFORM_USESWIDGET_H #define KDEVPLATFORM_USESWIDGET_H #include #include +#include #include #include #include "../../languageexport.h" #include "usescollector.h" #include class QLabel; class QToolButton; class QVBoxLayout; class QHBoxLayout; class QBoxLayout; class QPushButton; class QProgressBar; namespace KDevelop { class CodeRepresentation; class IndexedDeclaration; ///A widget representing one use of a Declaration in a speicific context class KDEVPLATFORMLANGUAGE_EXPORT OneUseWidget : public QWidget { Q_OBJECT public: OneUseWidget(IndexedDeclaration declaration, IndexedString document, SimpleRange range, const CodeRepresentation& code); ~OneUseWidget(); private slots: void jumpTo(); private: virtual void resizeEvent ( QResizeEvent * event ); PersistentMovingRange::Ptr m_range; IndexedDeclaration m_declaration; IndexedString m_document; QString m_sourceLine; QLabel* m_label; QLabel* m_icon; QHBoxLayout* m_layout; }; class KDEVPLATFORMLANGUAGE_EXPORT NavigatableWidgetList : public QScrollArea { Q_OBJECT public: NavigatableWidgetList(bool allowScrolling = false, uint maxHeight = 0, bool vertical = true); ~NavigatableWidgetList(); void addItem(QWidget* widget, int pos = -1); void addHeaderItem(QWidget* widget, Qt::Alignment alignment = 0); ///Whether items were added to this list using addItem(..) bool hasItems() const; ///Deletes all items that were added using addItem void deleteItems(); QList items() const; void setShowHeader(bool show); protected: QBoxLayout* m_itemLayout; QVBoxLayout* m_layout; private: QHBoxLayout* m_headerLayout; QToolButton *m_previousButton, *m_nextButton; bool m_allowScrolling, m_useArrows; }; class KDEVPLATFORMLANGUAGE_EXPORT ContextUsesWidget : public NavigatableWidgetList { Q_OBJECT public: ContextUsesWidget(const CodeRepresentation& code, QList usedDeclaration, IndexedDUContext context); Q_SIGNALS: void navigateDeclaration(KDevelop::IndexedDeclaration); private Q_SLOTS: void linkWasActivated(QString); private: IndexedDUContext m_context; }; class KDEVPLATFORMLANGUAGE_EXPORT DeclarationWidget : public NavigatableWidgetList { Q_OBJECT public: DeclarationWidget(const KDevelop::CodeRepresentation& code, const KDevelop::IndexedDeclaration& declaration); }; /** * Represents the uses of a declaration within one top-context */ class KDEVPLATFORMLANGUAGE_EXPORT TopContextUsesWidget : public NavigatableWidgetList { Q_OBJECT public: TopContextUsesWidget(IndexedDeclaration declaration, QList localDeclarations, IndexedTopDUContext topContext); void setExpanded(bool); int usesCount() const; private slots: void labelClicked(); private: IndexedTopDUContext m_topContext; IndexedDeclaration m_declaration; QLabel* m_icon; QLabel* m_toggleButton; QList m_allDeclarations; int m_usesCount; }; /** * A widget that allows browsing through all the uses of a declaration, and also through all declarations of it. */ class KDEVPLATFORMLANGUAGE_EXPORT UsesWidget : public NavigatableWidgetList { Q_OBJECT public: ///This class can be overridden to do additional processing while the uses-widget shows the uses. struct KDEVPLATFORMLANGUAGE_EXPORT UsesWidgetCollector : public UsesCollector { public: void setWidget(UsesWidget* widget ); UsesWidgetCollector(IndexedDeclaration decl); virtual void processUses(KDevelop::ReferencedTopDUContext topContext); virtual void maximumProgress(uint max); virtual void progress(uint processed, uint total); UsesWidget* m_widget; }; virtual QSize sizeHint () const; - ///@param customCollector allows specifying an own subclass of UsesWidgetCollector. The object will be owned - ///@param showDeclarations whether all declarations used for the search should be shown as well in the list - ///by this widget, and will be deleted on destruction. - UsesWidget(IndexedDeclaration declaration, UsesWidgetCollector* customCollector = 0); + ///@param customCollector allows specifying an own subclass of UsesWidgetCollector. + UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector = {}); ~UsesWidget(); void setAllExpanded(bool expanded); unsigned int countAllUses() const; Q_SIGNALS: void navigateDeclaration(KDevelop::IndexedDeclaration); private: const QString headerLineText() const; QLabel* m_headerLine; - UsesWidgetCollector* m_collector; + QSharedPointer m_collector; QProgressBar* m_progressBar; public slots: void headerLinkActivated(QString linkName); void redrawHeaderLine(); }; } #endif