diff --git a/debugger/breakpoint/breakpoint.cpp b/debugger/breakpoint/breakpoint.cpp index 9171baa4df..cfd6eacc32 100644 --- a/debugger/breakpoint/breakpoint.cpp +++ b/debugger/breakpoint/breakpoint.cpp @@ -1,356 +1,356 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda 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, see . */ #include "breakpoint.h" #include #include #include "breakpointmodel.h" #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../interfaces/idebugsession.h" #include "../interfaces/ibreakpointcontroller.h" using namespace KDevelop; static const char* const BREAKPOINT_KINDS[Breakpoint::LastBreakpointKind] = { "Code", "Write", "Read", "Access" }; static Breakpoint::BreakpointKind stringToKind(const QString& kindString) { for (int i = 0; i < Breakpoint::LastBreakpointKind; ++i) { if (BREAKPOINT_KINDS[i] == kindString) { return (Breakpoint::BreakpointKind)i; } } return Breakpoint::CodeBreakpoint; } Breakpoint::Breakpoint(BreakpointModel *model, BreakpointKind kind) : m_model(model), m_enabled(true) , m_deleted(false) , m_state(NotStartedState) , m_kind(kind) , m_line(-1) - , m_movingCursor(0) + , m_movingCursor(nullptr) , m_hitCount(0) , m_ignoreHits(0) { if (model) { model->registerBreakpoint(this); } } Breakpoint::Breakpoint(BreakpointModel *model, const KConfigGroup& config) : m_model(model), m_enabled(true) , m_deleted(false) , m_state(NotStartedState) , m_line(-1) - , m_movingCursor(0) + , m_movingCursor(nullptr) , m_hitCount(0) , m_ignoreHits(0) { if (model) { model->registerBreakpoint(this); } m_kind = stringToKind(config.readEntry("kind", "")); m_enabled = config.readEntry("enabled", false); m_url = config.readEntry("url", QUrl()); m_line = config.readEntry("line", -1); m_expression = config.readEntry("expression", QString()); setCondition(config.readEntry("condition", "")); setIgnoreHits(config.readEntry("ignoreHits", 0)); } BreakpointModel *Breakpoint::breakpointModel() { return m_model; } bool Breakpoint::setData(int index, const QVariant& value) { if (index == EnableColumn) { m_enabled = static_cast(value.toInt()) == Qt::Checked; } if (index == LocationColumn || index == ConditionColumn) { QString s = value.toString(); if (index == LocationColumn) { QRegExp rx("^(.+):([0-9]+)$"); int idx = rx.indexIn(s); if (m_kind == CodeBreakpoint && idx != -1) { m_url = QUrl::fromLocalFile(rx.cap(1)); m_line = rx.cap(2).toInt() - 1; m_expression.clear(); } else { m_expression = s; m_url.clear(); m_line = -1; } } else { m_condition = s; } } reportChange(static_cast(index)); return true; } QVariant Breakpoint::data(int column, int role) const { if (column == EnableColumn) { if (role == Qt::CheckStateRole) return m_enabled ? Qt::Checked : Qt::Unchecked; else if (role == Qt::DisplayRole) return QVariant(); else return QVariant(); } if (column == StateColumn) { if (role == Qt::DecorationRole) { if (!errorText().isEmpty()) { return QIcon::fromTheme(QStringLiteral("dialog-warning")); } switch (state()) { case NotStartedState: return QVariant(); case DirtyState: return QIcon::fromTheme(QStringLiteral("system-switch-user")); case PendingState: return QIcon::fromTheme(QStringLiteral("help-contents")); case CleanState: return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); } } else if (role == Qt::ToolTipRole) { if (!errorText().isEmpty()) { return i18nc("@info:tooltip", "Error"); } switch (state()) { case NotStartedState: return QString(); case DirtyState: return i18nc("@info:tooltip", "Dirty"); case PendingState: return i18nc("@info:tooltip", "Pending"); case CleanState: return i18nc("@info:tooltip", "Clean"); } } else if (role == Qt::DisplayRole) { return QVariant(); } return QVariant(); } if (column == TypeColumn && role == Qt::DisplayRole) { return BREAKPOINT_KINDS[m_kind]; } if (column == ConditionColumn && (role == Qt::DisplayRole || role == Qt::EditRole)) { return m_condition; } if (column == LocationColumn) { if (role == LocationRole || role == Qt::EditRole || role == Qt::ToolTipRole || role == Qt::DisplayRole) { QString ret; if (m_kind == CodeBreakpoint && m_line != -1) { if (role == Qt::DisplayRole) { ret = m_url.fileName(); } else { ret = m_url.toDisplayString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); } ret += ':' + QString::number(m_line+1); } else { ret = m_expression; } //FIXME: there should be proper columns for function name and address. if (!m_address.isEmpty() && role == Qt::DisplayRole) { ret = i18nc("location (address)", "%1 (%2)", ret, m_address); } return ret; } } return QVariant(); } void Breakpoint::setDeleted() { m_deleted = true; BreakpointModel* m = breakpointModel(); if (!m) return; // already removed if (m->breakpointIndex(this, 0).isValid()) { m->removeRow(m->breakpointIndex(this, 0).row()); } - m_model = 0; // invalidate + m_model = nullptr; // invalidate } int Breakpoint::line() const { return m_line; } void Breakpoint::setLine(int line) { Q_ASSERT(m_kind == CodeBreakpoint); m_line = line; reportChange(LocationColumn); } void Breakpoint::setUrl(const QUrl& url) { Q_ASSERT(m_kind == CodeBreakpoint); Q_ASSERT(url.isEmpty() || (!url.isRelative() && !url.fileName().isEmpty())); m_url = url; reportChange(LocationColumn); } QUrl Breakpoint::url() const { return m_url; } void Breakpoint::setLocation(const QUrl& url, int line) { Q_ASSERT(m_kind == CodeBreakpoint); Q_ASSERT(url.isEmpty() || (!url.isRelative() && !url.fileName().isEmpty())); m_url = url; m_line = line; reportChange(LocationColumn); } QString KDevelop::Breakpoint::location() { return data(LocationColumn, LocationRole).toString(); } void Breakpoint::save(KConfigGroup& config) { config.writeEntry("kind", BREAKPOINT_KINDS[m_kind]); config.writeEntry("enabled", m_enabled); config.writeEntry("url", m_url); config.writeEntry("line", m_line); config.writeEntry("expression", m_expression); config.writeEntry("condition", m_condition); config.writeEntry("ignoreHits", m_ignoreHits); } Breakpoint::BreakpointKind Breakpoint::kind() const { return m_kind; } void Breakpoint::setAddress(const QString& address) { m_address = address; //reportChange(); } QString Breakpoint::address() const { return m_address; } int Breakpoint::hitCount() const { return m_hitCount; } bool Breakpoint::deleted() const { return m_deleted; } bool Breakpoint::enabled() const { return data(EnableColumn, Qt::CheckStateRole).toBool(); } void KDevelop::Breakpoint::setMovingCursor(KTextEditor::MovingCursor* cursor) { m_movingCursor = cursor; } KTextEditor::MovingCursor* KDevelop::Breakpoint::movingCursor() const { return m_movingCursor; } void Breakpoint::setIgnoreHits(int c) { if (m_ignoreHits != c) { m_ignoreHits = c; reportChange(IgnoreHitsColumn); } } int Breakpoint::ignoreHits() const { return m_ignoreHits; } void Breakpoint::setCondition(const QString& c) { if (c != m_condition) { m_condition = c; reportChange(ConditionColumn); } } QString Breakpoint::condition() const { return m_condition; } void Breakpoint::setExpression(const QString& e) { if (e != m_expression) { m_expression = e; reportChange(LocationColumn); } } QString Breakpoint::expression() const { return m_expression; } Breakpoint::BreakpointState Breakpoint::state() const { return m_state; } QString Breakpoint::errorText() const { return m_errorText; } void KDevelop::Breakpoint::reportChange(Column c) { if (!breakpointModel()) return; breakpointModel()->reportChange(this, c); } diff --git a/debugger/breakpoint/breakpointdetails.cpp b/debugger/breakpoint/breakpointdetails.cpp index d58ef551b0..37c3688df9 100644 --- a/debugger/breakpoint/breakpointdetails.cpp +++ b/debugger/breakpoint/breakpointdetails.cpp @@ -1,160 +1,160 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * * 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. */ #include "breakpointdetails.h" #include #include #include #include #include #include #include "../breakpoint/breakpoint.h" #include "../interfaces/idebugsession.h" #include "../../interfaces/icore.h" #include "../interfaces/ibreakpointcontroller.h" #include "../../interfaces/idebugcontroller.h" using namespace KDevelop; BreakpointDetails::BreakpointDetails(QWidget *parent) - : QWidget(parent), m_currentBreakpoint(0) + : QWidget(parent), m_currentBreakpoint(nullptr) { QVBoxLayout* layout = new QVBoxLayout(this); m_status = new QLabel(this); connect(m_status, &QLabel::linkActivated, this, &BreakpointDetails::showExplanation); layout->addWidget(m_status); QGridLayout* hitsLayout = new QGridLayout(); layout->addLayout(hitsLayout); hitsLayout->setContentsMargins(0, 0, 0, 0); m_hits = new QLabel(i18n("Not hit yet"), this); m_hits->setWordWrap(true); hitsLayout->addWidget(m_hits, 0, 0, 1, 3); QLabel *l2 = new QLabel(i18n("Ignore"), this); hitsLayout->addWidget(l2, 2, 0); m_ignore = new QSpinBox(this); hitsLayout->addWidget(m_ignore, 2, 1); m_ignore->setRange(0, 99999); connect(m_ignore, static_cast(&QSpinBox::valueChanged), this, &BreakpointDetails::setIgnoreHits); QLabel *l3 = new QLabel(i18n("next hits"), this); hitsLayout->addWidget(l3, 2, 2); layout->addStretch(); - setItem(0); //initialize with no breakpoint active + setItem(nullptr); //initialize with no breakpoint active } void KDevelop::BreakpointDetails::setIgnoreHits(int ignoreHits) { if (!m_currentBreakpoint) return; m_currentBreakpoint->setIgnoreHits(ignoreHits); } void BreakpointDetails::setItem(Breakpoint *breakpoint) { m_currentBreakpoint = breakpoint; if (!breakpoint) { m_status->hide(); m_hits->hide(); m_ignore->setEnabled(false); return; } m_ignore->setValue(breakpoint->ignoreHits()); if (breakpoint->state() == Breakpoint::NotStartedState) { m_status->hide(); m_hits->hide(); m_ignore->setEnabled(true); return; } m_status->show(); m_hits->show(); m_ignore->setEnabled(true); if (breakpoint->errorText().isEmpty()) { switch (breakpoint->state()) { case Breakpoint::NotStartedState: Q_ASSERT(0); break; case Breakpoint::PendingState: m_status->setText(i18n("Breakpoint is pending")); break; case Breakpoint::DirtyState: m_status->setText(i18n("Breakpoint is dirty")); break; case Breakpoint::CleanState: m_status->setText(i18n("Breakpoint is active")); break; } if (breakpoint->hitCount() == -1) m_hits->clear(); else if (breakpoint->hitCount()) m_hits->setText(i18np("Hit %1 time", "Hit %1 times", breakpoint->hitCount())); else m_hits->setText(i18n("Not hit yet")); } else { m_status->setText(i18n("Breakpoint has errors")); m_hits->setText(breakpoint->errorText()); } } void BreakpointDetails::showExplanation(const QString& link) { QPoint pos = m_status->mapToGlobal(m_status->geometry().topLeft()); if (link == QLatin1String("pending")) { QWhatsThis::showText(pos, i18n("Breakpoint is pending" "

Pending breakpoints are those that have " "been passed to GDB, but which are not yet " "installed in the target, because GDB cannot " "find the function or file to which the breakpoint " "refers. The most common case is a breakpoint " "in a shared library: GDB will insert this " "breakpoint only when the library is loaded.

"), m_status); } else if (link == QLatin1String("dirty")) { QWhatsThis::showText(pos, i18n("Breakpoint is dirty" "

The breakpoint has not yet been passed " "to the debugger.

"), m_status); } } diff --git a/debugger/breakpoint/breakpointmodel.cpp b/debugger/breakpoint/breakpointmodel.cpp index e08930fddd..dde3bd6f27 100644 --- a/debugger/breakpoint/breakpointmodel.cpp +++ b/debugger/breakpoint/breakpointmodel.cpp @@ -1,640 +1,640 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams 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, see . */ #include "breakpointmodel.h" #include #include #include #include #include #include #include "../interfaces/icore.h" #include "../interfaces/idebugcontroller.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/idocument.h" #include "../interfaces/ipartcontroller.h" #include #include #include #include "util/debug.h" #include "breakpoint.h" #include #include #include #include #include #define IF_DEBUG(x) using namespace KDevelop; using namespace KTextEditor; namespace { IBreakpointController* breakpointController() { KDevelop::ICore* core = KDevelop::ICore::self(); if (!core) { return nullptr; } IDebugController* controller = core->debugController(); if (!controller) { return nullptr; } IDebugSession* session = controller->currentSession(); return session ? session->breakpointController() : nullptr; } } // anonymous namespace BreakpointModel::BreakpointModel(QObject* parent) : QAbstractTableModel(parent), m_dontUpdateMarks(false) { connect(this, &BreakpointModel::dataChanged, this, &BreakpointModel::updateMarks); if (KDevelop::ICore::self()->partController()) { //TODO remove if foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts()) slotPartAdded(p); connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &BreakpointModel::slotPartAdded); } connect (KDevelop::ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &BreakpointModel::textDocumentCreated); connect (KDevelop::ICore::self()->documentController(), &IDocumentController::documentSaved, this, &BreakpointModel::documentSaved); } BreakpointModel::~BreakpointModel() { qDeleteAll(m_breakpoints); } void BreakpointModel::slotPartAdded(KParts::Part* part) { if (auto doc = qobject_cast(part)) { MarkInterface *iface = dynamic_cast(doc); if( !iface ) return; iface->setMarkDescription((MarkInterface::MarkTypes)BreakpointMark, i18n("Breakpoint")); iface->setMarkPixmap((MarkInterface::MarkTypes)BreakpointMark, *breakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)PendingBreakpointMark, *pendingBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)ReachedBreakpointMark, *reachedBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)DisabledBreakpointMark, *disabledBreakpointPixmap()); iface->setEditableMarks( MarkInterface::Bookmark | BreakpointMark ); updateMarks(); } } void BreakpointModel::textDocumentCreated(KDevelop::IDocument* doc) { KTextEditor::MarkInterface *iface = qobject_cast(doc->textDocument()); if (iface) { // can't use new signal slot syntax here, MarkInterface is not a QObject connect(doc->textDocument(), SIGNAL( markChanged(KTextEditor::Document*, KTextEditor::Mark, KTextEditor::MarkInterface::MarkChangeAction)), this, SLOT(markChanged(KTextEditor::Document*, KTextEditor::Mark, KTextEditor::MarkInterface::MarkChangeAction))); connect(doc->textDocument(), SIGNAL(markContextMenuRequested(KTextEditor::Document*, KTextEditor::Mark, QPoint, bool&)), SLOT(markContextMenuRequested(KTextEditor::Document*, KTextEditor::Mark, QPoint, bool&))); } } void BreakpointModel::markContextMenuRequested(Document* document, Mark mark, const QPoint &pos, bool& handled) { int type = mark.type; qCDebug(DEBUGGER) << type; /* Is this a breakpoint mark, to begin with? */ if (!(type & AllBreakpointMarks)) return; Breakpoint *b = breakpoint(document->url(), mark.line); if (!b) { QMessageBox::critical(nullptr, i18n("Breakpoint not found"), i18n("Couldn't find breakpoint at %1:%2", document->url().toString(), mark.line)); return; } QMenu menu; - QAction deleteAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Delete Breakpoint"), 0); - QAction disableAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Disable Breakpoint"), 0); - QAction enableAction(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("&Enable Breakpoint"), 0); + QAction deleteAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Delete Breakpoint"), nullptr); + QAction disableAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Disable Breakpoint"), nullptr); + QAction enableAction(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("&Enable Breakpoint"), nullptr); menu.addAction(&deleteAction); if (b->enabled()) { menu.addAction(&disableAction); } else { menu.addAction(&enableAction); } QAction *a = menu.exec(pos); if (a == &deleteAction) { b->setDeleted(); } else if (a == &disableAction) { b->setData(Breakpoint::EnableColumn, Qt::Unchecked); } else if (a == &enableAction) { b->setData(Breakpoint::EnableColumn, Qt::Checked); } handled = true; } QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) return QVariant(); if (role == Qt::DecorationRole ) { if (section == 0) return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); else if (section == 1) return QIcon::fromTheme(QStringLiteral("system-switch-user")); } if (role == Qt::DisplayRole) { if (section == 0 || section == 1) return ""; if (section == 2) return i18n("Type"); if (section == 3) return i18n("Location"); if (section == 4) return i18n("Condition"); } if (role == Qt::ToolTipRole) { if (section == 0) return i18n("Active status"); if (section == 1) return i18n("State"); return headerData(section, orientation, Qt::DisplayRole); } return QVariant(); } Qt::ItemFlags BreakpointModel::flags(const QModelIndex &index) const { /* FIXME: all this logic must be in item */ if (!index.isValid()) - return 0; + return nullptr; if (index.column() == 0) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); if (index.column() == Breakpoint::LocationColumn || index.column() == Breakpoint::ConditionColumn) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); return static_cast(Qt::ItemIsEnabled | Qt::ItemIsSelectable); } QModelIndex BreakpointModel::breakpointIndex(KDevelop::Breakpoint* b, int column) { int row = m_breakpoints.indexOf(b); if (row == -1) return QModelIndex(); return index(row, column); } bool KDevelop::BreakpointModel::removeRows(int row, int count, const QModelIndex& parent) { if (count < 1 || (row < 0) || (row + count) > rowCount(parent)) return false; IBreakpointController* controller = breakpointController(); beginRemoveRows(parent, row, row+count-1); for (int i=0; i < count; ++i) { Breakpoint* b = m_breakpoints.at(row); b->m_deleted = true; if (controller) controller->breakpointAboutToBeDeleted(row); m_breakpoints.removeAt(row); - b->m_model = 0; + b->m_model = nullptr; // To be changed: the controller is currently still responsible for deleting the breakpoint // object } endRemoveRows(); updateMarks(); scheduleSave(); return true; } int KDevelop::BreakpointModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return m_breakpoints.count(); } return 0; } int KDevelop::BreakpointModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 5; } QVariant BreakpointModel::data(const QModelIndex& index, int role) const { if (!index.parent().isValid() && index.row() < m_breakpoints.count()) { return m_breakpoints.at(index.row())->data(index.column(), role); } return QVariant(); } bool KDevelop::BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.parent().isValid() && index.row() < m_breakpoints.count() && (role == Qt::EditRole || role == Qt::CheckStateRole)) { return m_breakpoints.at(index.row())->setData(index.column(), value); } return false; } void BreakpointModel::updateState(int row, Breakpoint::BreakpointState state) { Breakpoint* breakpoint = m_breakpoints.at(row); if (state != breakpoint->m_state) { breakpoint->m_state = state; reportChange(breakpoint, Breakpoint::StateColumn); } } void BreakpointModel::updateHitCount(int row, int hitCount) { Breakpoint* breakpoint = m_breakpoints.at(row); if (hitCount != breakpoint->m_hitCount) { breakpoint->m_hitCount = hitCount; reportChange(breakpoint, Breakpoint::HitCountColumn); } } void BreakpointModel::updateErrorText(int row, const QString& errorText) { Breakpoint* breakpoint = m_breakpoints.at(row); if (breakpoint->m_errorText != errorText) { breakpoint->m_errorText = errorText; reportChange(breakpoint, Breakpoint::StateColumn); } if (!errorText.isEmpty()) { emit error(row, errorText); } } void BreakpointModel::notifyHit(int row) { emit hit(row); } void BreakpointModel::markChanged( KTextEditor::Document *document, KTextEditor::Mark mark, KTextEditor::MarkInterface::MarkChangeAction action) { int type = mark.type; /* Is this a breakpoint mark, to begin with? */ if (!(type & AllBreakpointMarks)) return; if (action == KTextEditor::MarkInterface::MarkAdded) { Breakpoint *b = breakpoint(document->url(), mark.line); if (b) { //there was already a breakpoint, so delete instead of adding b->setDeleted(); return; } Breakpoint *breakpoint = addCodeBreakpoint(document->url(), mark.line); KTextEditor::MovingInterface *moving = qobject_cast(document); if (moving) { KTextEditor::MovingCursor* cursor = moving->newMovingCursor(KTextEditor::Cursor(mark.line, 0)); // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(document, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), Qt::UniqueConnection); breakpoint->setMovingCursor(cursor); } } else { // Find this breakpoint and delete it Breakpoint *b = breakpoint(document->url(), mark.line); if (b) { b->setDeleted(); } } #if 0 if ( KDevelop::ICore::self()->documentController()->activeDocument() && KDevelop::ICore::self()->documentController()->activeDocument()->textDocument() == document ) { //bring focus back to the editor // TODO probably want a different command here KDevelop::ICore::self()->documentController()->activateDocument(KDevelop::ICore::self()->documentController()->activeDocument()); } #endif } const QPixmap* BreakpointModel::breakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Active, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::pendingBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Normal, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::reachedBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Selected, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::disabledBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Disabled, QIcon::Off); return &pixmap; } void BreakpointModel::toggleBreakpoint(const QUrl& url, const KTextEditor::Cursor& cursor) { Breakpoint *b = breakpoint(url, cursor.line()); if (b) { b->setDeleted(); } else { addCodeBreakpoint(url, cursor.line()); } } void BreakpointModel::reportChange(Breakpoint* breakpoint, Breakpoint::Column column) { // note: just a portion of Breakpoint::Column is displayed in this model! if (column >= 0 && column < columnCount()) { QModelIndex idx = breakpointIndex(breakpoint, column); Q_ASSERT(idx.isValid()); // make sure we don't pass invalid indices to dataChanged() emit dataChanged(idx, idx); } if (IBreakpointController* controller = breakpointController()) { int row = m_breakpoints.indexOf(breakpoint); Q_ASSERT(row != -1); controller->breakpointModelChanged(row, ColumnFlags(1 << column)); } scheduleSave(); } uint BreakpointModel::breakpointType(Breakpoint *breakpoint) { uint type = BreakpointMark; if (!breakpoint->enabled()) { type = DisabledBreakpointMark; } else if (breakpoint->hitCount() > 0) { type = ReachedBreakpointMark; } else if (breakpoint->state() == Breakpoint::PendingState) { type = PendingBreakpointMark; } return type; } void KDevelop::BreakpointModel::updateMarks() { if (m_dontUpdateMarks) return; //add marks foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (breakpoint->line() == -1) continue; IDocument *doc = ICore::self()->documentController()->documentForUrl(breakpoint->url()); if (!doc) continue; KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; uint type = breakpointType(breakpoint); IF_DEBUG( qCDebug(DEBUGGER) << type << breakpoint->url() << mark->mark(breakpoint->line()); ) doc->textDocument()->blockSignals(true); if (mark->mark(breakpoint->line()) & AllBreakpointMarks) { if (!(mark->mark(breakpoint->line()) & type)) { mark->removeMark(breakpoint->line(), AllBreakpointMarks); mark->addMark(breakpoint->line(), type); } } else { mark->addMark(breakpoint->line(), type); } doc->textDocument()->blockSignals(false); } //remove marks foreach (IDocument *doc, ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; doc->textDocument()->blockSignals(true); foreach (KTextEditor::Mark *m, mark->marks()) { if (!(m->type & AllBreakpointMarks)) continue; IF_DEBUG( qCDebug(DEBUGGER) << m->line << m->type; ) foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (doc->url() == breakpoint->url() && m->line == breakpoint->line()) { goto continueNextMark; } } mark->removeMark(m->line, AllBreakpointMarks); continueNextMark:; } doc->textDocument()->blockSignals(false); } } void BreakpointModel::documentSaved(KDevelop::IDocument* doc) { IF_DEBUG( qCDebug(DEBUGGER); ) foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->movingCursor()) { if (breakpoint->movingCursor()->document() != doc->textDocument()) continue; if (breakpoint->movingCursor()->line() == breakpoint->line()) continue; m_dontUpdateMarks = true; breakpoint->setLine(breakpoint->movingCursor()->line()); m_dontUpdateMarks = false; } } } void BreakpointModel::aboutToDeleteMovingInterfaceContent(KTextEditor::Document* document) { foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->movingCursor() && breakpoint->movingCursor()->document() == document) { - breakpoint->setMovingCursor(0); + breakpoint->setMovingCursor(nullptr); } } } void BreakpointModel::load() { KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); int count = breakpoints.readEntry("number", 0); if (count == 0) return; beginInsertRows(QModelIndex(), 0, count - 1); for (int i = 0; i < count; ++i) { if (!breakpoints.group(QString::number(i)).readEntry("kind", "").isEmpty()) { new Breakpoint(this, breakpoints.group(QString::number(i))); } } endInsertRows(); } void BreakpointModel::save() { m_dirty = false; KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); breakpoints.writeEntry("number", m_breakpoints.count()); int i = 0; foreach (Breakpoint *b, m_breakpoints) { KConfigGroup g = breakpoints.group(QString::number(i)); b->save(g); ++i; } breakpoints.sync(); } void BreakpointModel::scheduleSave() { if (m_dirty) return; m_dirty = true; QTimer::singleShot(0, this, SLOT(save())); } QList KDevelop::BreakpointModel::breakpoints() const { return m_breakpoints; } Breakpoint* BreakpointModel::breakpoint(int row) { - if (row >= m_breakpoints.count()) return 0; + if (row >= m_breakpoints.count()) return nullptr; return m_breakpoints.at(row); } Breakpoint* BreakpointModel::addCodeBreakpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::CodeBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QUrl& url, int line) { Breakpoint* n = addCodeBreakpoint(); n->setLocation(url, line); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QString& expression) { Breakpoint* n = addCodeBreakpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addWatchpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::WriteBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addWatchpoint(const QString& expression) { Breakpoint* n = addWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addReadWatchpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::ReadBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addReadWatchpoint(const QString& expression) { Breakpoint* n = addReadWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::AccessBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint(const QString& expression) { Breakpoint* n = addAccessWatchpoint(); n->setExpression(expression); return n; } void BreakpointModel::registerBreakpoint(Breakpoint* breakpoint) { Q_ASSERT(!m_breakpoints.contains(breakpoint)); int row = m_breakpoints.size(); m_breakpoints << breakpoint; if (IBreakpointController* controller = breakpointController()) { controller->breakpointAdded(row); } scheduleSave(); } Breakpoint* BreakpointModel::breakpoint(const QUrl& url, int line) { foreach (Breakpoint *b, m_breakpoints) { if (b->url() == url && b->line() == line) { return b; } } - return 0; + return nullptr; } diff --git a/debugger/breakpoint/breakpointwidget.cpp b/debugger/breakpoint/breakpointwidget.cpp index 30c3d9611d..32e47e8549 100644 --- a/debugger/breakpoint/breakpointwidget.cpp +++ b/debugger/breakpoint/breakpointwidget.cpp @@ -1,315 +1,315 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * Copyright 2013 Vlas Puhov * * 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. */ #include "breakpointwidget.h" #include #include #include #include #include #include #include #include #include #include #include "breakpointdetails.h" #include "../breakpoint/breakpoint.h" #include "../breakpoint/breakpointmodel.h" #include "util/debug.h" #include #include #define IF_DEBUG(x) #include #include #include using namespace KDevelop; BreakpointWidget::BreakpointWidget(IDebugController *controller, QWidget *parent) : AutoOrientedSplitter(parent), m_firstShow(true), m_debugController(controller), - m_breakpointDisableAllAction(0), m_breakpointEnableAllAction(0), m_breakpointRemoveAll(0) + m_breakpointDisableAllAction(nullptr), m_breakpointEnableAllAction(nullptr), m_breakpointRemoveAll(nullptr) { setWindowTitle(i18nc("@title:window", "Debugger Breakpoints")); setWhatsThis(i18nc("@info:whatsthis", "Displays a list of breakpoints with " "their current status. Clicking on a " "breakpoint item allows you to change " "the breakpoint and will take you " "to the source in the editor window.")); setWindowIcon( QIcon::fromTheme( QStringLiteral( "media-playback-pause"), windowIcon() ) ); m_breakpointsView = new QTreeView(this); m_breakpointsView->setSelectionBehavior(QAbstractItemView::SelectRows); m_breakpointsView->setSelectionMode(QAbstractItemView::SingleSelection); m_breakpointsView->setRootIsDecorated(false); auto detailsContainer = new QGroupBox(i18n("Breakpoint Details"), this); auto detailsLayout = new QVBoxLayout(detailsContainer); m_details = new BreakpointDetails(detailsContainer); detailsLayout->addWidget(m_details); setStretchFactor(0, 2); PlaceholderItemProxyModel* proxyModel = new PlaceholderItemProxyModel(this); proxyModel->setSourceModel(m_debugController->breakpointModel()); proxyModel->setColumnHint(Breakpoint::LocationColumn, i18n("New code breakpoint ...")); proxyModel->setColumnHint(Breakpoint::ConditionColumn, i18n("Enter condition ...")); m_breakpointsView->setModel(proxyModel); connect(proxyModel, &PlaceholderItemProxyModel::dataInserted, this, &BreakpointWidget::slotDataInserted); m_proxyModel = proxyModel; connect(m_breakpointsView, &QTreeView::activated, this, &BreakpointWidget::slotOpenFile); connect(m_breakpointsView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::rowsInserted, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::rowsRemoved, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::modelReset, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::dataChanged, this, &BreakpointWidget::slotUpdateBreakpointDetail); connect(m_debugController->breakpointModel(), &BreakpointModel::hit, this, &BreakpointWidget::breakpointHit); connect(m_debugController->breakpointModel(), &BreakpointModel::error, this, &BreakpointWidget::breakpointError); setupPopupMenu(); } void BreakpointWidget::setupPopupMenu() { m_popup = new QMenu(this); QMenu* newBreakpoint = m_popup->addMenu( i18nc("New breakpoint", "&New") ); newBreakpoint->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); QAction* action = newBreakpoint->addAction( i18nc("Code breakpoint", "&Code"), this, SLOT(slotAddBlankBreakpoint()) ); // Use this action also to provide a local shortcut action->setShortcut(QKeySequence(Qt::Key_B + Qt::CTRL, Qt::Key_C)); addAction(action); newBreakpoint->addAction( i18nc("Data breakpoint", "Data &Write"), this, SLOT(slotAddBlankWatchpoint())); newBreakpoint->addAction( i18nc("Data read breakpoint", "Data &Read"), this, SLOT(slotAddBlankReadWatchpoint())); newBreakpoint->addAction( i18nc("Data access breakpoint", "Data &Access"), this, SLOT(slotAddBlankAccessWatchpoint())); QAction* breakpointDelete = m_popup->addAction( QIcon::fromTheme(QStringLiteral("edit-delete")), i18n( "&Delete" ), this, SLOT(slotRemoveBreakpoint())); breakpointDelete->setShortcut(Qt::Key_Delete); breakpointDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(breakpointDelete); m_popup->addSeparator(); m_breakpointDisableAllAction = m_popup->addAction(i18n("Disable &All"), this, SLOT(slotDisableAllBreakpoints())); m_breakpointEnableAllAction = m_popup->addAction(i18n("&Enable All"), this, SLOT(slotEnableAllBreakpoints())); m_breakpointRemoveAll = m_popup->addAction(i18n("&Remove All"), this, SLOT(slotRemoveAllBreakpoints())); connect(m_popup,&QMenu::aboutToShow, this, &BreakpointWidget::slotPopupMenuAboutToShow); } void BreakpointWidget::contextMenuEvent(QContextMenuEvent* event) { m_popup->popup(event->globalPos()); } void BreakpointWidget::slotPopupMenuAboutToShow() { if (m_debugController->breakpointModel()->rowCount() < 1) { m_breakpointDisableAllAction->setDisabled(true); m_breakpointEnableAllAction->setDisabled(true); m_breakpointRemoveAll->setDisabled(true); } else { m_breakpointRemoveAll->setEnabled(true); bool allDisabled = true; bool allEnabled = true; for (int i = 0; i < m_debugController->breakpointModel()->rowCount(); i++) { Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(i); if (bp->enabled()) allDisabled = false; else allEnabled = false; } m_breakpointDisableAllAction->setDisabled(allDisabled); m_breakpointEnableAllAction->setDisabled(allEnabled); } } void BreakpointWidget::showEvent(QShowEvent *) { if (m_firstShow && m_debugController->breakpointModel()->rowCount() > 0) { for (int i = 0; i < m_breakpointsView->model()->columnCount(); ++i) { if(i == Breakpoint::LocationColumn){ continue; } m_breakpointsView->resizeColumnToContents(i); } //for some reasons sometimes width can be very small about 200... But it doesn't matter as we use tooltip anyway. int width = m_breakpointsView->size().width(); QHeaderView* header = m_breakpointsView->header(); header->resizeSection(Breakpoint::LocationColumn, width > 400 ? width/2 : header->sectionSize(Breakpoint::LocationColumn)*2 ); m_firstShow = false; } } void BreakpointWidget::edit(KDevelop::Breakpoint *n) { QModelIndex index = m_proxyModel->mapFromSource(m_debugController->breakpointModel()->breakpointIndex(n, Breakpoint::LocationColumn)); m_breakpointsView->setCurrentIndex(index); m_breakpointsView->edit(index); } void BreakpointWidget::slotDataInserted(int column, const QVariant& value) { Breakpoint* breakpoint = m_debugController->breakpointModel()->addCodeBreakpoint(); breakpoint->setData(column, value); } void BreakpointWidget::slotAddBlankBreakpoint() { edit(m_debugController->breakpointModel()->addCodeBreakpoint()); } void BreakpointWidget::slotAddBlankWatchpoint() { edit(m_debugController->breakpointModel()->addWatchpoint()); } void BreakpointWidget::slotAddBlankReadWatchpoint() { edit(m_debugController->breakpointModel()->addReadWatchpoint()); } void KDevelop::BreakpointWidget::slotAddBlankAccessWatchpoint() { edit(m_debugController->breakpointModel()->addAccessWatchpoint()); } void BreakpointWidget::slotRemoveBreakpoint() { QItemSelectionModel* sel = m_breakpointsView->selectionModel(); QModelIndexList selected = sel->selectedIndexes(); IF_DEBUG( qCDebug(DEBUGGER) << selected; ) if (!selected.isEmpty()) { m_debugController->breakpointModel()->removeRow(selected.first().row()); } } void BreakpointWidget::slotRemoveAllBreakpoints() { m_debugController->breakpointModel()->removeRows(0, m_debugController->breakpointModel()->rowCount()); } void BreakpointWidget::slotUpdateBreakpointDetail() { showEvent(nullptr); QModelIndexList selected = m_breakpointsView->selectionModel()->selectedIndexes(); IF_DEBUG( qCDebug(DEBUGGER) << selected; ) if (selected.isEmpty()) { - m_details->setItem(0); + m_details->setItem(nullptr); } else { m_details->setItem(m_debugController->breakpointModel()->breakpoint(selected.first().row())); } } void BreakpointWidget::breakpointHit(int row) { const QModelIndex index = m_proxyModel->mapFromSource(m_debugController->breakpointModel()->index(row, 0)); m_breakpointsView->selectionModel()->select( index, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); } void BreakpointWidget::breakpointError(int row, const QString& msg) { // FIXME: we probably should prevent this error notification during // initial setting of breakpoint, to avoid a cloud of popups. if (!m_breakpointsView->isVisible()) return; const QModelIndex index = m_proxyModel->mapFromSource( m_debugController->breakpointModel()->index(row, BreakpointModel::LocationColumn)); QPoint p = m_breakpointsView->visualRect(index).topLeft(); p = m_breakpointsView->mapToGlobal(p); KPassivePopup *pop = new KPassivePopup(m_breakpointsView); pop->setPopupStyle(KPassivePopup::Boxed); pop->setAutoDelete(true); // FIXME: the icon, too. pop->setView(QString(), msg); pop->setTimeout(-1); pop->show(p); } void BreakpointWidget::slotOpenFile(const QModelIndex& breakpointIdx) { if (breakpointIdx.column() != Breakpoint::LocationColumn){ return; } Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(breakpointIdx.row()); if (!bp || bp->line() == -1 || bp->url().isEmpty() ){ return; } ICore::self()->documentController()->openDocument(bp->url(), KTextEditor::Cursor(bp->line(), 0), IDocumentController::DoNotFocus); } void BreakpointWidget::slotDisableAllBreakpoints() { for (int i = 0; i < m_debugController->breakpointModel()->rowCount(); i++) { Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(i); bp->setData(Breakpoint::EnableColumn, Qt::Unchecked); } } void BreakpointWidget::slotEnableAllBreakpoints() { for (int i = 0; i < m_debugController->breakpointModel()->rowCount(); i++) { Breakpoint *bp = m_debugController->breakpointModel()->breakpoint(i); bp->setData(Breakpoint::EnableColumn, Qt::Checked); } } diff --git a/debugger/framestack/framestackwidget.cpp b/debugger/framestack/framestackwidget.cpp index 0326ff4f5e..200609daae 100644 --- a/debugger/framestack/framestackwidget.cpp +++ b/debugger/framestack/framestackwidget.cpp @@ -1,263 +1,263 @@ /* * This file is part of KDevelop * * Copyright 1999 John Birch * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * Copyright 2009 Aleix Pol * * 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. */ #include "framestackwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util/debug.h" #include "framestackmodel.h" namespace KDevelop { class FrameStackItemDelegate : public QItemDelegate { Q_OBJECT public: using QItemDelegate::QItemDelegate; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; void FrameStackItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem newOption(option); newOption.textElideMode = index.column() == 2 ? Qt::ElideMiddle : Qt::ElideRight; QItemDelegate::paint(painter, newOption, index); } FramestackWidget::FramestackWidget(IDebugController* controller, QWidget* parent) - : AutoOrientedSplitter(Qt::Horizontal, parent), m_session(0) + : AutoOrientedSplitter(Qt::Horizontal, parent), m_session(nullptr) { connect(controller, &IDebugController::currentSessionChanged, this, &FramestackWidget::currentSessionChanged); //TODO: shouldn't this signal be in IDebugController? Otherwise we are effectively depending on it being a DebugController here connect(controller, SIGNAL(raiseFramestackViews()), SIGNAL(requestRaise())); setWhatsThis(i18n("Frame stack" "Often referred to as the \"call stack\", " "this is a list showing which function is " "currently active, and what called each " "function to get to this point in your " "program. By clicking on an item you " "can see the values in any of the " "previous calling functions.")); setWindowIcon(QIcon::fromTheme(QStringLiteral("view-list-text"), windowIcon())); m_threadsWidget = new QWidget(this); m_threadsListView = new QListView(m_threadsWidget); m_framesTreeView = new QTreeView(this); m_framesTreeView->setRootIsDecorated(false); m_framesTreeView->setItemDelegate(new FrameStackItemDelegate(this)); m_framesTreeView->setSelectionMode(QAbstractItemView::ContiguousSelection); m_framesTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_framesTreeView->setAllColumnsShowFocus(true); m_framesTreeView->setContextMenuPolicy(Qt::CustomContextMenu); m_framesContextMenu = new QMenu(m_framesTreeView); QAction* selectAllAction = KStandardAction::selectAll(m_framesTreeView); selectAllAction->setShortcut(QKeySequence()); //FIXME: why does CTRL-A conflict with Katepart (while CTRL-Cbelow doesn't) ? selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(selectAllAction, &QAction::triggered, this, &FramestackWidget::selectAll); m_framesContextMenu->addAction(selectAllAction); QAction* copyAction = KStandardAction::copy(m_framesTreeView); copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(copyAction, &QAction::triggered, this, &FramestackWidget::copySelection); m_framesContextMenu->addAction(copyAction); addAction(copyAction); connect(m_framesTreeView, &QTreeView::customContextMenuRequested, this, &FramestackWidget::frameContextMenuRequested); m_threadsWidget->setLayout(new QVBoxLayout()); m_threadsWidget->layout()->addWidget(new QLabel(i18n("Threads:"))); m_threadsWidget->layout()->addWidget(m_threadsListView); addWidget(m_threadsWidget); addWidget(m_framesTreeView); setStretchFactor(1, 3); connect(m_framesTreeView->verticalScrollBar(), &QScrollBar::valueChanged, this, &FramestackWidget::checkFetchMoreFrames); // Show the selected frame when clicked, even if it has previously been selected connect(m_framesTreeView, &QTreeView::clicked, this, &FramestackWidget::frameSelectionChanged); currentSessionChanged(controller->currentSession()); } FramestackWidget::~FramestackWidget() { } void FramestackWidget::currentSessionChanged(KDevelop::IDebugSession* session) { m_session = session; - m_threadsListView->setModel(session ? session->frameStackModel() : 0); - m_framesTreeView->setModel(session ? session->frameStackModel() : 0); + m_threadsListView->setModel(session ? session->frameStackModel() : nullptr); + m_framesTreeView->setModel(session ? session->frameStackModel() : nullptr); if (session) { connect(session->frameStackModel(), &IFrameStackModel::dataChanged, this, &FramestackWidget::checkFetchMoreFrames); connect(session->frameStackModel(), &IFrameStackModel::currentThreadChanged, this, &FramestackWidget::currentThreadChanged); currentThreadChanged(session->frameStackModel()->currentThread()); connect(session->frameStackModel(), &IFrameStackModel::currentFrameChanged, this, &FramestackWidget::currentFrameChanged); currentFrameChanged(session->frameStackModel()->currentFrame()); connect(session, &IDebugSession::stateChanged, this, &FramestackWidget::sessionStateChanged); connect(m_threadsListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FramestackWidget::setThreadShown); // Show the selected frame, independent of the means by which it has been selected connect(m_framesTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FramestackWidget::frameSelectionChanged); sessionStateChanged(session->state()); } } void FramestackWidget::setThreadShown(const QModelIndex& current) { if (!current.isValid()) return; m_session->frameStackModel()->setCurrentThread(current); } void FramestackWidget::checkFetchMoreFrames() { int val = m_framesTreeView->verticalScrollBar()->value(); int max = m_framesTreeView->verticalScrollBar()->maximum(); const int offset = 20; if (val + offset > max && m_session) { m_session->frameStackModel()->fetchMoreFrames(); } } void FramestackWidget::currentThreadChanged(int thread) { if (thread != -1) { IFrameStackModel* model = m_session->frameStackModel(); QModelIndex idx = model->currentThreadIndex(); m_threadsListView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); m_threadsWidget->setVisible(model->rowCount() > 1); m_framesTreeView->setRootIndex(idx); m_framesTreeView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); } else { m_threadsWidget->hide(); m_threadsListView->selectionModel()->clear(); m_framesTreeView->setRootIndex(QModelIndex()); } } void FramestackWidget::currentFrameChanged(int frame) { if (frame != -1) { IFrameStackModel* model = m_session->frameStackModel(); QModelIndex idx = model->currentFrameIndex(); m_framesTreeView->selectionModel()->select( idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } else { m_framesTreeView->selectionModel()->clear(); } } void FramestackWidget::frameSelectionChanged(const QModelIndex& current /* previous */) { if (!current.isValid()) return; IFrameStackModel::FrameItem f = m_session->frameStackModel()->frame(current); /* If line is -1, then it's not a source file at all. */ if (f.line != -1) { QPair file = m_session->convertToLocalUrl(qMakePair(f.file, f.line)); ICore::self()->documentController()->openDocument(file.first, KTextEditor::Cursor(file.second, 0), IDocumentController::DoNotFocus); } m_session->frameStackModel()->setCurrentFrame(f.nr); } void FramestackWidget::frameContextMenuRequested(const QPoint& pos) { m_framesContextMenu->popup( m_framesTreeView->mapToGlobal(pos) + QPoint(0, m_framesTreeView->header()->height()) ); } void FramestackWidget::copySelection() { QClipboard *cb = QApplication::clipboard(); QModelIndexList indexes = m_framesTreeView->selectionModel()->selectedRows(); QString content; Q_FOREACH(const QModelIndex& index, indexes) { IFrameStackModel::FrameItem frame = m_session->frameStackModel()->frame(index); if (frame.line == -1) { content += i18nc("#frame function() at file", "#%1 %2() at %3\n", frame.nr, frame.name, frame.file.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); } else { content += i18nc("#frame function() at file:line", "#%1 %2() at %3:%4\n", frame.nr, frame.name, frame.file.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash), frame.line+1); } } cb->setText(content); } void FramestackWidget::selectAll() { m_framesTreeView->selectAll(); } void FramestackWidget::sessionStateChanged(KDevelop::IDebugSession::DebuggerState state) { bool enable = state == IDebugSession::PausedState || state == IDebugSession::StoppedState; m_framesTreeView->setEnabled(enable); m_threadsListView->setEnabled(enable); } } #include "framestackwidget.moc" diff --git a/debugger/interfaces/ibreakpointcontroller.cpp b/debugger/interfaces/ibreakpointcontroller.cpp index 02f81964f2..2bb88f5c3d 100644 --- a/debugger/interfaces/ibreakpointcontroller.cpp +++ b/debugger/interfaces/ibreakpointcontroller.cpp @@ -1,227 +1,227 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams 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 "ibreakpointcontroller.h" #include #include #include #include "idebugsession.h" #include "../../interfaces/icore.h" #include "../breakpoint/breakpointmodel.h" #include "../../interfaces/idebugcontroller.h" #include "../breakpoint/breakpoint.h" #include "../../interfaces/iuicontroller.h" #include "util/debug.h" namespace KDevelop { IBreakpointController::IBreakpointController(KDevelop::IDebugSession* parent) : QObject(parent), m_dontSendChanges(0) { connect(parent, &IDebugSession::stateChanged, this, &IBreakpointController::debuggerStateChanged); } IDebugSession* IBreakpointController::debugSession() const { return static_cast(const_cast(QObject::parent())); } BreakpointModel* IBreakpointController::breakpointModel() const { - if (!ICore::self()) return 0; + if (!ICore::self()) return nullptr; return ICore::self()->debugController()->breakpointModel(); } void IBreakpointController::updateState(int row, Breakpoint::BreakpointState state) { breakpointModel()->updateState(row, state); } void IBreakpointController::updateHitCount(int row, int hitCount) { breakpointModel()->updateHitCount(row, hitCount); } void IBreakpointController::updateErrorText(int row, const QString& errorText) { breakpointModel()->updateErrorText(row, errorText); } void IBreakpointController::notifyHit(int row, const QString& msg) { BreakpointModel* model = breakpointModel(); model->notifyHit(row); // This is a slightly odd place to issue this notification, // but then again it's not clear which place would be more natural Breakpoint* breakpoint = model->breakpoint(row); - KNotification* ev = 0; + KNotification* ev = nullptr; switch(breakpoint->kind()) { case Breakpoint::CodeBreakpoint: ev = new KNotification(QStringLiteral("BreakpointHit"), ICore::self()->uiController()->activeMainWindow()); ev->setText(i18n("Breakpoint hit: %1", breakpoint->location()) + msg); break; case Breakpoint::WriteBreakpoint: case Breakpoint::ReadBreakpoint: case Breakpoint::AccessBreakpoint: ev = new KNotification(QStringLiteral("WatchpointHit"), ICore::self()->uiController()->activeMainWindow()); ev->setText(i18n("Watchpoint hit: %1", breakpoint->location()) + msg); break; default: Q_ASSERT(0); break; } if (ev) { ev->setPixmap(QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(22,22))); // TODO: Port //ev->setComponentName(ICore::self()->aboutData().componentName()); ev->sendEvent(); } } // Temporary: empty default implementation void IBreakpointController::breakpointAdded(int row) { Q_UNUSED(row); } // Temporary: implement old-style behavior to ease transition through API changes void IBreakpointController::breakpointModelChanged(int row, BreakpointModel::ColumnFlags columns) { if (m_dontSendChanges) return; if ((columns & ~BreakpointModel::StateColumnFlag) != 0) { Breakpoint * breakpoint = breakpointModel()->breakpoint(row); for (int column = 0; column < BreakpointModel::NumColumns; ++column) { if (columns & (1 << column)) { m_dirty[breakpoint].insert(Breakpoint::Column(column)); if (m_errors.contains(breakpoint)) { m_errors[breakpoint].remove(Breakpoint::Column(column)); } } } breakpointStateChanged(breakpoint); if (debugSession()->isRunning()) { sendMaybe(breakpoint); } } } void IBreakpointController::debuggerStateChanged(IDebugSession::DebuggerState state) { BreakpointModel* model = breakpointModel(); if (!model) return; //breakpoint state changes when session started or stopped foreach (Breakpoint* breakpoint, model->breakpoints()) { if (state == IDebugSession::StartingState) { auto& dirty = m_dirty[breakpoint]; //when starting everything is dirty dirty.insert(Breakpoint::LocationColumn); if (!breakpoint->condition().isEmpty()) { dirty.insert(Breakpoint::ConditionColumn); } if (!breakpoint->enabled()) { dirty.insert(KDevelop::Breakpoint::EnableColumn); } } breakpointStateChanged(breakpoint); } } void IBreakpointController::sendMaybeAll() { BreakpointModel* model = breakpointModel(); if (!model) return; foreach (Breakpoint *breakpoint, model->breakpoints()) { sendMaybe(breakpoint); } } // Temporary implementation to ease the API transition void IBreakpointController::breakpointAboutToBeDeleted(int row) { Breakpoint* breakpoint = breakpointModel()->breakpoint(row); qCDebug(DEBUGGER) << "breakpointAboutToBeDeleted(" << row << "): " << breakpoint; sendMaybe(breakpoint); } void IBreakpointController::breakpointStateChanged(Breakpoint* breakpoint) { if (breakpoint->deleted()) return; Breakpoint::BreakpointState newState = Breakpoint::NotStartedState; if (debugSession()->state() != IDebugSession::EndedState && debugSession()->state() != IDebugSession::NotStartedState) { if (m_dirty.value(breakpoint).isEmpty()) { if (m_pending.contains(breakpoint)) { newState = Breakpoint::PendingState; } else { newState = Breakpoint::CleanState; } } else { newState = Breakpoint::DirtyState; } } m_dontSendChanges++; updateState(breakpointModel()->breakpointIndex(breakpoint, 0).row(), newState); m_dontSendChanges--; } void IBreakpointController::setHitCount(Breakpoint* breakpoint, int count) { m_dontSendChanges++; updateHitCount(breakpointModel()->breakpointIndex(breakpoint, 0).row(), count); m_dontSendChanges--; } void IBreakpointController::error(Breakpoint* breakpoint, const QString &msg, Breakpoint::Column column) { BreakpointModel* model = breakpointModel(); int row = model->breakpointIndex(breakpoint, 0).row(); m_dontSendChanges++; m_errors[breakpoint].insert(column); updateErrorText(row, msg); m_dontSendChanges--; } void IBreakpointController::hit(KDevelop::Breakpoint* breakpoint, const QString &msg) { int row = breakpointModel()->breakpointIndex(breakpoint, 0).row(); notifyHit(row, msg); } } diff --git a/debugger/interfaces/ivariablecontroller.cpp b/debugger/interfaces/ivariablecontroller.cpp index 55d9d091c6..9d36d01e44 100644 --- a/debugger/interfaces/ivariablecontroller.cpp +++ b/debugger/interfaces/ivariablecontroller.cpp @@ -1,132 +1,132 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ivariablecontroller.h" #include "idebugsession.h" #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../variable/variablecollection.h" #include "util/debug.h" #include "iframestackmodel.h" namespace KDevelop { IVariableController::IVariableController(IDebugSession* parent) : QObject(parent), m_activeThread(-1), m_activeFrame(-1) { connect(parent, &IDebugSession::stateChanged, this, &IVariableController::stateChanged); } VariableCollection* IVariableController::variableCollection() { - if (!ICore::self()) return 0; + if (!ICore::self()) return nullptr; return ICore::self()->debugController()->variableCollection(); } IDebugSession* IVariableController::session() const { return static_cast(parent()); } void IVariableController::stateChanged(IDebugSession::DebuggerState state) { if (!ICore::self() || ICore::self()->shuttingDown()) { return; } if (state == IDebugSession::ActiveState) { //variables are now outdated, update them m_activeThread = -1; m_activeFrame = -1; } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { // Remove all locals. foreach (Locals *l, variableCollection()->allLocals()) { l->deleteChildren(); l->setHasMore(false); } for (int i=0; i < variableCollection()->watches()->childCount(); ++i) { Variable *var = qobject_cast(variableCollection()->watches()->child(i)); if (var) { var->setInScope(false); } } } } void IVariableController::updateIfFrameOrThreadChanged() { IFrameStackModel *sm = session()->frameStackModel(); if (sm->currentThread() != m_activeThread || sm->currentFrame() != m_activeFrame) { variableCollection()->root()->resetChanged(); update(); } } void IVariableController::handleEvent(IDebugSession::event_t event) { if (!variableCollection()) return; switch (event) { case IDebugSession::thread_or_frame_changed: qCDebug(DEBUGGER) << m_autoUpdate; if (!(m_autoUpdate & UpdateLocals)) { foreach (Locals *l, variableCollection()->allLocals()) { if (!l->isExpanded() && !l->childCount()) { l->setHasMore(true); } } } if (m_autoUpdate != UpdateNone) { updateIfFrameOrThreadChanged(); } // update our cache of active thread/frame regardless of m_autoUpdate // to keep them synced when user currently hides the variable list m_activeThread = session()->frameStackModel()->currentThread(); m_activeFrame = session()->frameStackModel()->currentFrame(); break; default: break; } } void IVariableController::setAutoUpdate(QFlags autoUpdate) { IDebugSession::DebuggerState state = session()->state(); m_autoUpdate = autoUpdate; qCDebug(DEBUGGER) << m_autoUpdate; if (m_autoUpdate != UpdateNone && state == IDebugSession::PausedState) { update(); } } QFlags IVariableController::autoUpdate() { return m_autoUpdate; } } diff --git a/debugger/util/treeitem.cpp b/debugger/util/treeitem.cpp index 9ea619779d..1b884e693f 100644 --- a/debugger/util/treeitem.cpp +++ b/debugger/util/treeitem.cpp @@ -1,265 +1,265 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * * 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. */ #include "treeitem.h" #include #include #include "debug.h" #include "treemodel.h" using namespace KDevelop; TreeItem::TreeItem(TreeModel* model, TreeItem *parent) -: model_(model), more_(false), ellipsis_(0), expanded_(false) +: model_(model), more_(false), ellipsis_(nullptr), expanded_(false) { parentItem = parent; } TreeItem::~TreeItem() { foreach (TreeItem *it, childItems) delete it; delete ellipsis_; } void TreeItem::setData(const QVector &data) { itemData=data; } void TreeItem::appendChild(TreeItem *item, bool initial) { QModelIndex index = model_->indexForItem(this, 0); // Note that we need emit beginRemoveRows, even if we're replacing // ellipsis item with the real one. The number of rows does not change // with the result, but the last item is different. The address of the // item is stored inside QModelIndex, so just replacing the item, and // deleting the old one, will lead to crash. if (more_) { if (!initial) model_->beginRemoveRows(index, childItems.size(), childItems.size()); more_ = false; delete ellipsis_; - ellipsis_ = 0; + ellipsis_ = nullptr; if (!initial) model_->endRemoveRows(); } if (!initial) model_->beginInsertRows(index, childItems.size(), childItems.size()); childItems.append(item); if (!initial) model_->endInsertRows(); } void TreeItem::insertChild(int position, TreeItem *child, bool initial) { QModelIndex index = model_->indexForItem(this, 0); if (!initial) model_->beginInsertRows(index, position, position); childItems.insert(position, child); if (!initial) model_->endInsertRows(); } void TreeItem::reportChange() { QModelIndex index = model_->indexForItem(this, 0); QModelIndex index2 = model_->indexForItem(this, itemData.size()-1); emit model_->dataChanged(index, index2); } void KDevelop::TreeItem::reportChange(int column) { QModelIndex index = model_->indexForItem(this, column); emit model_->dataChanged(index, index); } void TreeItem::removeChild(int index) { QModelIndex modelIndex = model_->indexForItem(this, 0); model_->beginRemoveRows(modelIndex, index, index); childItems.erase(childItems.begin() + index); model_->endRemoveRows(); } void TreeItem::removeSelf() { QModelIndex modelIndex = model_->indexForItem(this, 0); parentItem->removeChild(modelIndex.row()); } void TreeItem::deleteChildren() { QVector copy = childItems; clear(); // Only delete the children after removing them // from model. Otherwise, the model will touch // deleted things, with undefined results. for (int i = 0; i < childItems.size(); ++i) { TreeItem* v = child(i); delete v; } } void TreeItem::clear() { if (!childItems.isEmpty() || more_) { QModelIndex index = model_->indexForItem(this, 0); model_->beginRemoveRows(index, 0, childItems.size()-1+more_); childItems.clear(); more_ = false; delete ellipsis_; - ellipsis_ = 0; + ellipsis_ = nullptr; model_->endRemoveRows(); } } TreeItem *TreeItem::child(int row) { if (row < childItems.size()) return childItems.value(row); else if (row == childItems.size() && more_) return ellipsis_; else - return NULL; + return nullptr; } int TreeItem::childCount() const { return childItems.count() + more_; } int TreeItem::columnCount() const { return itemData.count(); } QVariant TreeItem::data(int column, int role) const { if (role == Qt::DecorationRole) return icon(column); else if (role==Qt::DisplayRole || role == Qt::EditRole) return itemData.value(column); return QVariant(); } TreeItem *TreeItem::parent() { return parentItem; } int TreeItem::row() const { if (parentItem) return parentItem->childItems.indexOf(const_cast(this)); return 0; } class EllipsisItem : public TreeItem { Q_OBJECT public: EllipsisItem(TreeModel *model, TreeItem *parent) : TreeItem(model, parent) { QVector data; data.push_back("..."); for (int i = 1; i < model->columnCount(QModelIndex()); ++i) data.push_back(""); setData(data); } void clicked() override { qCDebug(DEBUGGER) << "Ellipsis item clicked"; /* FIXME: restore Q_ASSERT (parentItem->hasMore()); */ parentItem->fetchMoreChildren(); } void fetchMoreChildren() override {} }; void TreeItem::setHasMore(bool more) { /* FIXME: this will crash if used in ctor of root item, where the model is not associated with item or something. */ QModelIndex index = model_->indexForItem(this, 0); if (more && !more_) { model_->beginInsertRows(index, childItems.size(), childItems.size()); ellipsis_ = new EllipsisItem (model(), this); more_ = more; model_->endInsertRows(); } else if (!more && more_) { model_->beginRemoveRows(index, childItems.size(), childItems.size()); delete ellipsis_; - ellipsis_ = 0; + ellipsis_ = nullptr; more_ = more; model_->endRemoveRows(); } } void TreeItem::emitAllChildrenFetched() { emit allChildrenFetched(); } void TreeItem::setHasMoreInitial(bool more) { more_ = more; if (more) { ellipsis_ = new EllipsisItem (model(), this); } } QVariant KDevelop::TreeItem::icon(int column) const { Q_UNUSED(column); return QVariant(); } void KDevelop::TreeItem::setExpanded(bool b) { if (expanded_ != b) { expanded_ = b; if (expanded_) emit expanded(); else emit collapsed(); } } #include "treeitem.moc" diff --git a/debugger/util/treemodel.cpp b/debugger/util/treemodel.cpp index e1ad859756..4c50186ee5 100644 --- a/debugger/util/treemodel.cpp +++ b/debugger/util/treemodel.cpp @@ -1,206 +1,206 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * * 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. */ #include "treemodel.h" //#include #include #include "treeitem.h" using namespace KDevelop; TreeModel::TreeModel(const QVector& headers, QObject *parent) - : QAbstractItemModel(parent), headers_(headers), root_(NULL) + : QAbstractItemModel(parent), headers_(headers), root_(nullptr) { } void TreeModel::setRootItem(TreeItem *item) { root_ = item; root_->fetchMoreChildren(); } TreeModel::~TreeModel() { delete root_; } int TreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return headers_.size(); } QVariant TreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); TreeItem *item = static_cast(index.internalPointer()); if (role == ItemRole) return QVariant::fromValue(item); return item->data(index.column(), role); } Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) - return 0; + return nullptr; return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return headers_[section]; return QVariant(); } QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); TreeItem *parentItem; if (!parent.isValid()) parentItem = root_; else parentItem = static_cast(parent.internalPointer()); TreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return createIndex(row, column, nullptr); } QModelIndex TreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); TreeItem *childItem = static_cast(index.internalPointer()); TreeItem *parentItem = childItem->parent(); if (parentItem == root_) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } int TreeModel::rowCount(const QModelIndex &parent) const { TreeItem *parentItem; if (parent.column() > 0) return 0; if (!parent.isValid()) parentItem = root_; else parentItem = static_cast(parent.internalPointer()); if(parentItem) return parentItem->childCount(); else return 0; } TreeItem* TreeModel::itemForIndex(const QModelIndex& index) const { if (!index.isValid()) return root_; else return static_cast(index.internalPointer()); } QModelIndex TreeModel::indexForItem(TreeItem *item, int column) const { - if (item->parent() == 0) + if (item->parent() == nullptr) return QModelIndex(); if (TreeItem* parent = item->parent()) { /* FIXME: we might store row directly in item. */ int row = parent->childItems.indexOf(item); Q_ASSERT(row != -1); return createIndex(row, column, item); } else { return QModelIndex(); } } void TreeModel::expanded(const QModelIndex &index) { TreeItem* item = itemForIndex(index); QObject::connect(item, &TreeItem::allChildrenFetched, this, &TreeModel::itemChildrenReady); if (item->hasMore() && item->childCount() == 1) item->fetchMoreChildren(); else emit itemChildrenReady(); item->setExpanded(true); } void TreeModel::collapsed(const QModelIndex &index) { TreeItem* item = itemForIndex(index); item->setExpanded(false); } void TreeModel::clicked(const QModelIndex &index) { TreeItem* item = itemForIndex(index); item->clicked(); } bool TreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { /* FIXME: CheckStateRole is dirty. Should we pass the role to the item? */ if (index.isValid() && (role == Qt::EditRole || role == Qt::CheckStateRole)) { TreeItem *item = static_cast(index.internalPointer()); item->setColumn(index.column(), value); return true; } return false; } KDevelop::TreeItem* KDevelop::TreeModel::root() const { return root_; } diff --git a/debugger/util/treeview.cpp b/debugger/util/treeview.cpp index a5df136adb..d2bd861e75 100644 --- a/debugger/util/treeview.cpp +++ b/debugger/util/treeview.cpp @@ -1,87 +1,87 @@ /* * This file is part of KDevelop * * Copyright 2008 Vladimir Prus * * 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. */ #include #include #include #include "treeview.h" #include "treemodel.h" using namespace KDevelop; -AsyncTreeView::AsyncTreeView(TreeModel* model, QSortFilterProxyModel *proxy, QWidget *parent = 0) +AsyncTreeView::AsyncTreeView(TreeModel* model, QSortFilterProxyModel *proxy, QWidget *parent = nullptr) : QTreeView(parent) , m_proxy(proxy) { connect (this, &AsyncTreeView::expanded, this, &AsyncTreeView::slotExpanded); connect (this, &AsyncTreeView::collapsed, this, &AsyncTreeView::slotCollapsed); connect (this, &AsyncTreeView::clicked, this, &AsyncTreeView::slotClicked); connect (model, &TreeModel::itemChildrenReady, this, &AsyncTreeView::slotExpandedDataReady); } void AsyncTreeView::slotExpanded(const QModelIndex &index) { static_cast(model())->expanded(m_proxy->mapToSource(index)); } void AsyncTreeView::slotCollapsed(const QModelIndex &index) { static_cast(model())->collapsed(m_proxy->mapToSource(index)); resizeColumns(); } void AsyncTreeView::slotClicked(const QModelIndex &index) { static_cast(model())->clicked(m_proxy->mapToSource(index)); resizeColumns(); } QSize AsyncTreeView::sizeHint() const { //Assuming that columns are awlays resized to fit their contents, return a size that will fit all without a scrollbar QMargins margins = contentsMargins(); int horizontalSize = margins.left() + margins.right(); for (int i = 0; i < model()->columnCount(); ++i) { horizontalSize += columnWidth(i); } horizontalSize = qMin(horizontalSize, QApplication::desktop()->screenGeometry().width()*3/4); return QSize(horizontalSize, margins.top() + margins.bottom() + sizeHintForRow(0)); } void AsyncTreeView::resizeColumns() { for (int i = 0; i < model()->columnCount(); ++i) { this->resizeColumnToContents(i); } this->updateGeometry(); } void AsyncTreeView::slotExpandedDataReady() { resizeColumns(); } diff --git a/debugger/variable/variablecollection.cpp b/debugger/variable/variablecollection.cpp index c1eba9dcae..5222394077 100644 --- a/debugger/variable/variablecollection.cpp +++ b/debugger/variable/variablecollection.cpp @@ -1,544 +1,544 @@ /* * KDevelop Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * 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. */ #include "variablecollection.h" #include #include #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/idocumentcontroller.h" #include "../../interfaces/iuicontroller.h" #include "../../sublime/controller.h" #include "../../sublime/view.h" #include "../../interfaces/idebugcontroller.h" #include "../interfaces/idebugsession.h" #include "../interfaces/ivariablecontroller.h" #include "util/debug.h" #include "util/texteditorhelpers.h" #include "variabletooltip.h" #include namespace KDevelop { IDebugSession* currentSession() { return ICore::self()->debugController()->currentSession(); } IDebugSession::DebuggerState currentSessionState() { if (!currentSession()) return IDebugSession::NotStartedState; return currentSession()->state(); } bool hasStartedSession() { IDebugSession::DebuggerState s = currentSessionState(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; } Variable::Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : TreeItem(model, parent), m_inScope(true), m_topLevel(true), m_changed(false), m_showError(false), m_format(Natural) { m_expression = expression; // FIXME: should not duplicate the data, instead overload 'data' // and return expression_ directly. if (display.isEmpty()) setData(QVector() << expression << QString() << QString()); else setData(QVector() << display << QString() << QString()); } QString Variable::expression() const { return m_expression; } bool Variable::inScope() const { return m_inScope; } void Variable::setValue(const QString& v) { itemData[VariableCollection::ValueColumn] = v; reportChange(); } QString Variable::value() const { return itemData[VariableCollection::ValueColumn].toString(); } void Variable::setType(const QString& type) { itemData[VariableCollection::TypeColumn] = type; reportChange(); } QString Variable::type() const { return itemData[VariableCollection::TypeColumn].toString(); } void Variable::setTopLevel(bool v) { m_topLevel = v; } void Variable::setInScope(bool v) { m_inScope = v; for (int i=0; i < childCount(); ++i) { if (Variable *var = qobject_cast(child(i))) { var->setInScope(v); } } reportChange(); } void Variable::setShowError (bool v) { m_showError = v; reportChange(); } bool Variable::showError() { return m_showError; } Variable::~Variable() { } void Variable::die() { removeSelf(); deleteLater(); } void Variable::setChanged(bool c) { m_changed=c; reportChange(); } void Variable::resetChanged() { setChanged(false); for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } Variable::format_t Variable::str2format(const QString& str) { if(str==QLatin1String("Binary") || str==QLatin1String("binary")) return Binary; if(str==QLatin1String("Octal") || str==QLatin1String("octal")) return Octal; if(str==QLatin1String("Decimal") || str==QLatin1String("decimal")) return Decimal; if(str==QLatin1String("Hexadecimal") || str==QLatin1String("hexadecimal"))return Hexadecimal; return Natural; // maybe most reasonable default } QString Variable::format2str(format_t format) { switch(format) { case Natural: return QStringLiteral("natural"); case Binary: return QStringLiteral("binary"); case Octal: return QStringLiteral("octal"); case Decimal: return QStringLiteral("decimal"); case Hexadecimal: return QStringLiteral("hexadecimal"); default: return QString(); } } void Variable::setFormat(Variable::format_t format) { if (m_format != format) { m_format = format; formatChanged(); } } void Variable::formatChanged() { } bool Variable::isPotentialProblematicValue() const { const auto value = data(VariableCollection::ValueColumn, Qt::DisplayRole).toString(); return value == QLatin1String("0x0"); } QVariant Variable::data(int column, int role) const { if (m_showError) { if (role == Qt::FontRole) { QVariant ret = TreeItem::data(column, role); QFont font = ret.value(); font.setStyle(QFont::StyleItalic); return font; } else if (column == 1 && role == Qt::DisplayRole) { return i18n("Error"); } } if (column == 1 && role == Qt::TextColorRole) { KColorScheme scheme(QPalette::Active); if (!m_inScope) { return scheme.foreground(KColorScheme::InactiveText).color(); } else if (isPotentialProblematicValue()) { return scheme.foreground(KColorScheme::NegativeText).color(); } else if (m_changed) { return scheme.foreground(KColorScheme::NeutralText).color(); } } if (role == Qt::ToolTipRole) { return TreeItem::data(column, Qt::DisplayRole); } return TreeItem::data(column, role); } Watches::Watches(TreeModel* model, TreeItem* parent) -: TreeItem(model, parent), finishResult_(0) +: TreeItem(model, parent), finishResult_(nullptr) { setData(QVector() << i18n("Auto") << QString()); } Variable* Watches::add(const QString& expression) { - if (!hasStartedSession()) return 0; + if (!hasStartedSession()) return nullptr; Variable* v = currentSession()->variableController()->createVariable( model(), this, expression); appendChild(v); v->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return v; } Variable *Watches::addFinishResult(const QString& convenienceVarible) { if( finishResult_ ) { removeFinishResult(); } finishResult_ = currentSession()->variableController()->createVariable( model(), this, convenienceVarible, QStringLiteral("$ret")); appendChild(finishResult_); finishResult_->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return finishResult_; } void Watches::removeFinishResult() { if (finishResult_) { finishResult_->die(); - finishResult_ = 0; + finishResult_ = nullptr; } } void Watches::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } QVariant Watches::data(int column, int role) const { #if 0 if (column == 0 && role == Qt::FontRole) { /* FIXME: is creating font again and agian efficient? */ QFont f = font(); f.setBold(true); return f; } #endif return TreeItem::data(column, role); } void Watches::reinstall() { for (int i = 0; i < childItems.size(); ++i) { Variable* v = static_cast(child(i)); v->attachMaybe(); } } Locals::Locals(TreeModel* model, TreeItem* parent, const QString &name) : TreeItem(model, parent) { setData(QVector() << name << QString()); } QList Locals::updateLocals(QStringList locals) { QSet existing, current; for (int i = 0; i < childItems.size(); i++) { Q_ASSERT(qobject_cast(child(i))); Variable* var= static_cast(child(i)); existing << var->expression(); } foreach (const QString& var, locals) { current << var; // If we currently don't display this local var, add it. if( !existing.contains( var ) ) { // FIXME: passing variableCollection this way is awkward. // In future, variableCollection probably should get a // method to create variable. Variable* v = currentSession()->variableController()->createVariable( ICore::self()->debugController()->variableCollection(), this, var ); appendChild( v, false ); } } for (int i = 0; i < childItems.size(); ++i) { KDevelop::Variable* v = static_cast(child(i)); if (!current.contains(v->expression())) { removeChild(i); --i; // FIXME: check that -var-delete is sent. delete v; } } if (hasMore()) { setHasMore(false); } // Variables which changed just value are updated by a call to -var-update. // Variables that changed type -- likewise. QList ret; foreach (TreeItem *i, childItems) { Q_ASSERT(qobject_cast(i)); ret << static_cast(i); } return ret; } void Locals::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } VariablesRoot::VariablesRoot(TreeModel* model) : TreeItem(model) , m_watches(new Watches(model, this)) { appendChild(m_watches, true); } Locals* VariablesRoot::locals(const QString& name) { if (!m_locals.contains(name)) { m_locals[name] = new Locals(model(), this, name); appendChild(m_locals[name]); } return m_locals[name]; } QHash VariablesRoot::allLocals() const { return m_locals; } void VariablesRoot::resetChanged() { m_watches->resetChanged(); foreach (Locals *l, m_locals) { l->resetChanged(); } } VariableCollection::VariableCollection(IDebugController* controller) : TreeModel({i18n("Name"), i18n("Value"), i18n("Type")}, controller) , m_widgetVisible(false) , m_textHintProvider(this) { m_universe = new VariablesRoot(this); setRootItem(m_universe); //new ModelTest(this); connect (ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &VariableCollection::textDocumentCreated ); connect(controller, &IDebugController::currentSessionChanged, this, &VariableCollection::updateAutoUpdate); // Qt5 signal slot syntax does not support default arguments auto callUpdateAutoUpdate = [&] { updateAutoUpdate(); }; connect(locals(), &Locals::expanded, this, callUpdateAutoUpdate); connect(locals(), &Locals::collapsed, this, callUpdateAutoUpdate); connect(watches(), &Watches::expanded, this, callUpdateAutoUpdate); connect(watches(), &Watches::collapsed, this, callUpdateAutoUpdate); } void VariableCollection::variableWidgetHidden() { m_widgetVisible = false; updateAutoUpdate(); } void VariableCollection::variableWidgetShown() { m_widgetVisible = true; updateAutoUpdate(); } void VariableCollection::updateAutoUpdate(IDebugSession* session) { if (!session) session = currentSession(); qCDebug(DEBUGGER) << session; if (!session) return; if (!m_widgetVisible) { session->variableController()->setAutoUpdate(IVariableController::UpdateNone); } else { QFlags t = IVariableController::UpdateNone; if (locals()->isExpanded()) t |= IVariableController::UpdateLocals; if (watches()->isExpanded()) t |= IVariableController::UpdateWatches; session->variableController()->setAutoUpdate(t); } } VariableCollection::~ VariableCollection() { } void VariableCollection::textDocumentCreated(IDocument* doc) { connect( doc->textDocument(), &KTextEditor::Document::viewCreated, this, &VariableCollection::viewCreated ); foreach( KTextEditor::View* view, doc->textDocument()->views() ) viewCreated( doc->textDocument(), view ); } void VariableCollection::viewCreated(KTextEditor::Document* doc, KTextEditor::View* view) { Q_UNUSED(doc); using namespace KTextEditor; TextHintInterface *iface = dynamic_cast(view); if( !iface ) return; iface->registerTextHintProvider(&m_textHintProvider); } VariableProvider::VariableProvider(VariableCollection* collection) : KTextEditor::TextHintProvider() , m_collection(collection) { } QString VariableProvider::textHint(KTextEditor::View* view, const KTextEditor::Cursor& cursor) { if (!hasStartedSession()) return QString(); if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("debug")) return QString(); //TODO: These keyboardModifiers should also hide already opened tooltip, and show another one for code area. if (QApplication::keyboardModifiers() == Qt::ControlModifier || QApplication::keyboardModifiers() == Qt::AltModifier){ return QString(); } KTextEditor::Document* doc = view->document(); KTextEditor::Range expressionRange = currentSession()->variableController()->expressionRangeUnderCursor(doc, cursor); if (!expressionRange.isValid()) return QString(); QString expression = doc->text(expressionRange).trimmed(); // Don't do anything if there's already an open tooltip with matching range if (m_collection->m_activeTooltip && m_collection->m_activeTooltip->variable()->expression() == expression) return QString(); if (expression.isEmpty()) return QString(); QPoint local = view->cursorToCoordinate(cursor); QPoint global = view->mapToGlobal(local); QWidget* w = view->childAt(local); if (!w) w = view; m_collection->m_activeTooltip = new VariableToolTip(w, global+QPoint(30,30), expression); m_collection->m_activeTooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, expressionRange)); return QString(); } } diff --git a/debugger/variable/variablewidget.cpp b/debugger/variable/variablewidget.cpp index fb81082fc4..014851a0a3 100644 --- a/debugger/variable/variablewidget.cpp +++ b/debugger/variable/variablewidget.cpp @@ -1,512 +1,512 @@ // ************************************************************************** // begin : Sun Aug 8 1999 // copyright : (C) 1999 by John Birch // email : jbb@kdevelop.org // ************************************************************************** // * Copyright 2006 Vladimir Prus // ************************************************************************** // * * // * 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. * // * * // ************************************************************************** #include "variablewidget.h" #include #include #include #include #include #include #include #include #include #include "../util/treemodel.h" #include "../../interfaces/icore.h" #include #include "../interfaces/ivariablecontroller.h" #include "variablecollection.h" #include "variablesortmodel.h" #include "util/debug.h" /** The variables widget is passive, and is invoked by the rest of the code via two main Q_SLOTS: - slotDbgStatus - slotCurrentFrame The first is received the program status changes and the second is received after current frame in the debugger can possibly changes. The widget has a list item for each frame/thread combination, with variables as children. However, at each moment only one item is shown. When handling the slotCurrentFrame, we check if variables for the current frame are available. If yes, we simply show the corresponding item. Otherwise, we fetch the new data from debugger. Fetching the data is done by emitting the produceVariablesInfo signal. In response, we get slotParametersReady and slotLocalsReady signal, in that order. The data is parsed and changed variables are highlighted. After that, we 'trim' variable items that were not reported by gdb -- that is, gone out of scope. */ // ************************************************************************** // ************************************************************************** // ************************************************************************** namespace KDevelop { VariableCollection *variableCollection() { return ICore::self()->debugController()->variableCollection(); } VariableWidget::VariableWidget(IDebugController* controller, QWidget *parent) : QWidget(parent), m_variablesRoot(controller->variableCollection()->root()) { //setWindowIcon(QIcon::fromTheme("math_brace")); setWindowIcon(QIcon::fromTheme(QStringLiteral("debugger"), windowIcon())); setWindowTitle(i18n("Debugger Variables")); m_proxy = new VariableSortProxyModel(this); m_varTree = new VariableTree(controller, this, m_proxy); setFocusProxy(m_varTree); m_watchVarEditor = new KHistoryComboBox( this ); QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(m_varTree, 10); topLayout->addWidget(m_watchVarEditor); topLayout->setMargin(0); connect(m_watchVarEditor, static_cast(&KHistoryComboBox::returnPressed), this, &VariableWidget::slotAddWatch); //TODO //connect(plugin, SIGNAL(raiseVariableViews()), this, SIGNAL(requestRaise())); // Setup help items. setWhatsThis( i18n( "Variable tree" "The variable tree allows you to see the values of local " "variables and arbitrary expressions.
" "Local variables are displayed automatically and are updated " "as you step through your program. " "For each expression you enter, you can either evaluate it once, " "or \"watch\" it (make it auto-updated). Expressions that are not " "auto-updated can be updated manually from the context menu. " "Expressions can be renamed to more descriptive names by clicking " "on the name column.
" "To change the value of a variable or an expression, " "click on the value.
")); m_watchVarEditor->setWhatsThis( i18n("Expression entry" "Type in expression to watch.")); } void VariableWidget::slotAddWatch(const QString &expression) { if (!expression.isEmpty()) { m_watchVarEditor->addToHistory(expression); qCDebug(DEBUGGER) << "Trying to add watch"; Variable* v = m_variablesRoot->watches()->add(expression); if (v) { /* For watches on structures, we really do want them to be shown expanded. Except maybe for structure with custom pretty printing, but will handle that later. FIXME: it does not actually works now. */ //QModelIndex index = variableCollection()->indexForItem(v, 0); //varTree_->setExpanded(index, true); } m_watchVarEditor->clearEditText(); } } void VariableWidget::hideEvent(QHideEvent* e) { QWidget::hideEvent(e); variableCollection()->variableWidgetHidden(); } void VariableWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); variableCollection()->variableWidgetShown(); } // ************************************************************************** // ************************************************************************** // ************************************************************************** VariableTree::VariableTree(IDebugController *controller, VariableWidget *parent, QSortFilterProxyModel *proxy) : AsyncTreeView(controller->variableCollection(), proxy, parent) , m_proxy(proxy) #if 0 , activePopup_(0), toggleWatch_(0) #endif { setRootIsDecorated(true); setAllColumnsShowFocus(true); // setting proxy model m_model = static_cast(controller->variableCollection()); m_proxy->setSourceModel(m_model); setModel(m_proxy); setSortingEnabled(true); sortByColumn(VariableCollection::NameColumn, Qt::AscendingOrder); QModelIndex index = controller->variableCollection()->indexForItem( controller->variableCollection()->watches(), 0); setExpanded(index, true); m_signalMapper = new QSignalMapper(this); setupActions(); } VariableCollection* VariableTree::collection() const { Q_ASSERT(qobject_cast(static_cast(model())->sourceModel())); return static_cast(model()); } VariableTree::~VariableTree() { } void VariableTree::setupActions() { // TODO decorate this properly to make nice menu title m_contextMenuTitle = new QAction(this); m_contextMenuTitle->setEnabled(false); // make Format menu action group m_formatMenu = new QMenu(i18n("&Format"), this); QActionGroup *ag= new QActionGroup(m_formatMenu); QAction* act; act = new QAction(i18n("&Natural"), ag); act->setData(Variable::Natural); act->setShortcut(Qt::Key_N); m_formatMenu->addAction(act); act = new QAction(i18n("&Binary"), ag); act->setData(Variable::Binary); act->setShortcut(Qt::Key_B); m_formatMenu->addAction(act); act = new QAction(i18n("&Octal"), ag); act->setData(Variable::Octal); act->setShortcut(Qt::Key_O); m_formatMenu->addAction(act); act = new QAction(i18n("&Decimal"), ag); act->setData(Variable::Decimal); act->setShortcut(Qt::Key_D); m_formatMenu->addAction(act); act = new QAction(i18n("&Hexadecimal"), ag); act->setData(Variable::Hexadecimal); act->setShortcut(Qt::Key_H); m_formatMenu->addAction(act); foreach(QAction* act, m_formatMenu->actions()) { act->setCheckable(true); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_signalMapper->setMapping(act, act->data().toInt()); connect(act, &QAction::triggered, m_signalMapper, static_cast(&QSignalMapper::map)); addAction(act); } connect(m_signalMapper, static_cast(&QSignalMapper::mapped), this, &VariableTree::changeVariableFormat); m_watchDelete = new QAction( QIcon::fromTheme(QStringLiteral("edit-delete")), i18n( "Remove Watch Variable" ), this); m_watchDelete->setShortcut(Qt::Key_Delete); m_watchDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(m_watchDelete); connect(m_watchDelete, &QAction::triggered, this, &VariableTree::watchDelete); m_copyVariableValue = new QAction(i18n("&Copy Value"), this); m_copyVariableValue->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_copyVariableValue->setShortcut(QKeySequence::Copy); connect(m_copyVariableValue, &QAction::triggered, this, &VariableTree::copyVariableValue); m_stopOnChange = new QAction(i18n("&Stop on Change"), this); connect(m_stopOnChange, &QAction::triggered, this, &VariableTree::stopOnChange); } Variable* VariableTree::selectedVariable() const { - if (selectionModel()->selectedRows().isEmpty()) return 0; + if (selectionModel()->selectedRows().isEmpty()) return nullptr; auto item = selectionModel()->currentIndex().data(TreeModel::ItemRole).value(); - if (!item) return 0; + if (!item) return nullptr; return qobject_cast(item); } void VariableTree::contextMenuEvent(QContextMenuEvent* event) { if (!selectedVariable()) return; // set up menu QMenu contextMenu(this->parentWidget()); m_contextMenuTitle->setText(selectedVariable()->expression()); contextMenu.addAction(m_contextMenuTitle); if(selectedVariable()->canSetFormat()) contextMenu.addMenu(m_formatMenu); foreach(QAction* act, m_formatMenu->actions()) { if(act->data().toInt()==selectedVariable()->format()) act->setChecked(true); } if (qobject_cast(selectedVariable()->parent())) { contextMenu.addAction(m_watchDelete); } contextMenu.addSeparator(); contextMenu.addAction(m_copyVariableValue); contextMenu.addAction(m_stopOnChange); contextMenu.exec(event->globalPos()); } void VariableTree::changeVariableFormat(int format) { if (!selectedVariable()) return; selectedVariable()->setFormat(static_cast(format)); } void VariableTree::watchDelete() { if (!selectedVariable()) return; if (!qobject_cast(selectedVariable()->parent())) return; selectedVariable()->die(); } void VariableTree::copyVariableValue() { if (!selectedVariable()) return; QApplication::clipboard()->setText(selectedVariable()->value()); } void VariableTree::stopOnChange() { if (!selectedVariable()) return; IDebugSession *session = ICore::self()->debugController()->currentSession(); if (session && session->state() != IDebugSession::NotStartedState && session->state() != IDebugSession::EndedState) { session->variableController()->addWatchpoint(selectedVariable()); } } #if 0 void VariableTree::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (!index.isValid()) return; AbstractVariableItem* item = collection()->itemForIndex(index); if (RecentItem* recent = qobject_cast(item)) { QMenu popup(this); popup.addTitle(i18n("Recent Expressions")); QAction* remove = popup.addAction(QIcon::fromTheme("editdelete"), i18n("Remove All")); QAction* reevaluate = popup.addAction(QIcon::fromTheme("reload"), i18n("Re-evaluate All")); if (controller()->stateIsOn(s_dbgNotStarted)) reevaluate->setEnabled(false); QAction* res = popup.exec(QCursor::pos()); if (res == remove) { collection()->deleteItem(recent); } else if (res == reevaluate) { foreach (AbstractVariableItem* item, recent->children()) { if (VariableItem* variable = qobject_cast(item)) variable->updateValue(); } } } else { activePopup_ = new QMenu(this); QMenu format(this); QAction* remember = 0; QAction* remove = 0; QAction* reevaluate = 0; QAction* watch = 0; QAction* natural = 0; QAction* hex = 0; QAction* decimal = 0; QAction* character = 0; QAction* binary = 0; #define MAYBE_DISABLE(action) if (!var->isAlive()) action->setEnabled(false) VariableItem* var = qobject_cast(item); AbstractVariableItem* root = item->abstractRoot(); RecentItem* recentRoot = qobject_cast(root); if (!recentRoot) { remember = activePopup_->addAction(QIcon::fromTheme("draw-freehand"), i18n("Remember Value")); MAYBE_DISABLE(remember); } if (!recentRoot) { watch = activePopup_->addAction(i18n("Watch Variable")); MAYBE_DISABLE(watch); } if (recentRoot) { reevaluate = activePopup_->addAction(QIcon::fromTheme("reload"), i18n("Reevaluate Expression")); MAYBE_DISABLE(reevaluate); remove = activePopup_->addAction(QIcon::fromTheme("editdelete"), i18n("Remove Expression")); remove->setShortcut(Qt::Key_Delete); } if (var) { toggleWatch_ = activePopup_->addAction( i18n("Data write breakpoint") ); toggleWatch_->setCheckable(true); toggleWatch_->setEnabled(false); } /* This code can be executed when debugger is stopped, and we invoke popup menu on a var under "recent expressions" just to delete it. */ if (var && var->isAlive() && !controller()->stateIsOn(s_dbgNotStarted)) { GDBCommand* cmd = new GDBCommand(DataEvaluateExpression, QStringLiteral("&%1") .arg(var->gdbExpression())); cmd->setHandler(this, &VariableTree::handleAddressComputed, true /*handles error*/); cmd->setThread(var->thread()); cmd->setFrame(var->frame()); controller_->addCommand(cmd); } QAction* res = activePopup_->exec(event->globalPos()); delete activePopup_; activePopup_ = 0; if (res == remember) { if (var) { ((VariableWidget*)parent())-> slotEvaluateExpression(var->gdbExpression()); } } else if (res == watch) { if (var) { ((VariableWidget*)parent())-> slotAddWatchVariable(var->gdbExpression()); } } else if (res == remove) { delete item; } else if (res == toggleWatch_) { if (var) emit toggleWatchpoint(var->gdbExpression()); } else if (res == reevaluate) { if (var) { var->updateValue(); } } event->accept(); } } void VariableTree::updateCurrentFrame() { } // ************************************************************************** void VariableTree::handleAddressComputed(const GDBMI::ResultRecord& r) { if (r.reason == "error") { // Not lvalue, leave item disabled. return; } if (activePopup_) { toggleWatch_->setEnabled(true); //quint64 address = r["value"].literal().toULongLong(0, 16); /*if (breakpointWidget_->hasWatchpointForAddress(address)) { toggleWatch_->setChecked(true); }*/ } } VariableCollection * VariableTree::collection() const { return controller_->variables(); } GDBController * VariableTree::controller() const { return controller_; } void VariableTree::showEvent(QShowEvent * event) { Q_UNUSED(event) for (int i = 0; i < model()->columnCount(); ++i) resizeColumnToContents(i); } #endif // ************************************************************************** // ************************************************************************** // ************************************************************************** } diff --git a/interfaces/context.cpp b/interfaces/context.cpp index 5a4954a6a1..4602a69bbe 100644 --- a/interfaces/context.cpp +++ b/interfaces/context.cpp @@ -1,149 +1,149 @@ /* This file is part of KDevelop Copyright 2001-2002 Matthias Hoelzer-Kluepfel Copyright 2001-2002 Bernd Gehrmann Copyright 2001 Sandy Meier Copyright 2002 Daniel Engelschalt Copyright 2002 Simon Hausmann Copyright 2002-2003 Roberto Raggi Copyright 2003 Mario Scalas Copyright 2003 Harald Fernengel Copyright 2003,2006 Hamish Rodda Copyright 2004 Alexander Dymo Copyright 2006 Adam Treat 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 "context.h" #include #include #include namespace KDevelop { Context::Context() - : d(0) + : d(nullptr) {} Context::~Context() {} bool Context::hasType( int aType ) const { return aType == this->type(); } class FileContextPrivate { public: FileContextPrivate( const QList &urls ) : m_urls( urls ) {} QList m_urls; }; FileContext::FileContext( const QList &urls ) : Context(), d( new FileContextPrivate( urls ) ) {} FileContext::~FileContext() { delete d; } int FileContext::type() const { return Context::FileContext; } QList FileContext::urls() const { return d->m_urls; } class ProjectItemContextPrivate { public: ProjectItemContextPrivate( const QList &items ) : m_items( items ) {} QList m_items; }; ProjectItemContext::ProjectItemContext( const QList &items ) : Context(), d( new ProjectItemContextPrivate( items ) ) {} ProjectItemContext::~ProjectItemContext() { delete d; } int ProjectItemContext::type() const { return Context::ProjectItemContext; } QList ProjectItemContext::items() const { return d->m_items; } class OpenWithContextPrivate { public: OpenWithContextPrivate(const QList& urls, const QMimeType& mimeType) : m_urls( urls ) , m_mimeType( mimeType ) {} QList m_urls; QMimeType m_mimeType; }; OpenWithContext::OpenWithContext(const QList& urls, const QMimeType& mimeType) : Context() , d(new OpenWithContextPrivate(urls, mimeType)) { } OpenWithContext::~OpenWithContext() { delete d; } int OpenWithContext::type() const { return Context::OpenWithContext; } QList OpenWithContext::urls() const { return d->m_urls; } QMimeType OpenWithContext::mimeType() const { return d->m_mimeType; } } diff --git a/interfaces/ibuddydocumentfinder.cpp b/interfaces/ibuddydocumentfinder.cpp index 60e169c8ac..1afe69d908 100644 --- a/interfaces/ibuddydocumentfinder.cpp +++ b/interfaces/ibuddydocumentfinder.cpp @@ -1,53 +1,53 @@ /*************************************************************************** * Copyright 2011 Yannick Motta * * Martin Heide * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ibuddydocumentfinder.h" #include namespace KDevelop { //Our private data is entirely static, so don't need to create an //instance of the private data class struct IBuddyDocumentFinder::Private { static QMap& finders() { static QMap finders; return finders; } }; // ---------------- "Registry" interface -------------------------------------------- void IBuddyDocumentFinder::addFinder(const QString& mimeType, IBuddyDocumentFinder *finder) { Private::finders()[mimeType] = finder; } void IBuddyDocumentFinder::removeFinder(const QString& mimeType) { Private::finders().remove(mimeType); } IBuddyDocumentFinder* IBuddyDocumentFinder::finderForMimeType(const QString& mimeType) { - return Private::finders().value(mimeType, 0); + return Private::finders().value(mimeType, nullptr); } } diff --git a/interfaces/icore.cpp b/interfaces/icore.cpp index 2eb5809ba3..73ee9e3e4e 100644 --- a/interfaces/icore.cpp +++ b/interfaces/icore.cpp @@ -1,45 +1,45 @@ /* This file is part of KDevelop Copyright 2007 Alexander Dymo Copyright 2007 Kris Wong 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 "icore.h" namespace KDevelop { -ICore *ICore::m_self = 0; +ICore *ICore::m_self = nullptr; ICore::ICore(QObject *parent) : QObject(parent) { - Q_ASSERT(m_self == 0); + Q_ASSERT(m_self == nullptr); m_self = this; } ICore::~ICore() { - m_self = 0; + m_self = nullptr; } ICore *ICore::self() { return m_self; } } diff --git a/interfaces/idocument.cpp b/interfaces/idocument.cpp index a09703def0..21ddab6289 100644 --- a/interfaces/idocument.cpp +++ b/interfaces/idocument.cpp @@ -1,146 +1,146 @@ /*************************************************************************** * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "idocument.h" #include "icore.h" #include "idocumentcontroller.h" namespace KDevelop { class IDocumentPrivate { public: inline IDocumentPrivate(KDevelop::ICore *core) - : m_core(core), scriptWrapper(0) + : m_core(core), scriptWrapper(nullptr) {} KDevelop::ICore* m_core; QObject *scriptWrapper; QString m_prettyName; /* Internal access to the wrapper script object */ static inline QObject *&getWrapper(IDocument *doc) { return doc->d->scriptWrapper; } }; /* This allows the scripting backend to register the scripting wrapper. Not beautiful, but makes sure it doesn't expand to much code. */ QObject *&getWrapper(IDocument *doc) { return IDocumentPrivate::getWrapper(doc); } IDocument::IDocument( KDevelop::ICore* core ) : d(new IDocumentPrivate(core)) { } IDocument::~IDocument() { delete d->scriptWrapper; delete d; } KDevelop::ICore* IDocument::core() { return d->m_core; } void IDocument::notifySaved() { emit core()->documentController()->documentSaved(this); } void IDocument::notifyStateChanged() { emit core()->documentController()->documentStateChanged(this); } void IDocument::notifyActivated() { emit core()->documentController()->documentActivated(this); } void IDocument::notifyContentChanged() { emit core()->documentController()->documentContentChanged(this); } bool IDocument::isTextDocument() const { return false; } void IDocument::notifyTextDocumentCreated() { emit core()->documentController()->textDocumentCreated(this); } KTextEditor::Range IDocument::textSelection() const { return KTextEditor::Range::invalid(); } QString IDocument::textLine() const { return QString(); } QString IDocument::textWord() const { return QString(); } QString IDocument::prettyName() const { return d->m_prettyName; } void IDocument::setPrettyName(QString name) { d->m_prettyName = name; } void IDocument::notifyUrlChanged() { emit core()->documentController()->documentUrlChanged(this); } void IDocument::notifyLoaded() { emit core()->documentController()->documentLoadedPrepare(this); emit core()->documentController()->documentLoaded(this); } KTextEditor::View* IDocument::activeTextView() const { - return 0; + return nullptr; } QString KDevelop::IDocument::text(const KTextEditor::Range& range) const { Q_UNUSED(range); return {}; } } diff --git a/interfaces/ipartcontroller.cpp b/interfaces/ipartcontroller.cpp index 80a2002bfd..7edc59e897 100644 --- a/interfaces/ipartcontroller.cpp +++ b/interfaces/ipartcontroller.cpp @@ -1,98 +1,98 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ipartcontroller.h" #include #include #include #include #include namespace KDevelop { IPartController::IPartController( QWidget* toplevel ) - : KParts::PartManager( toplevel, 0 ) + : KParts::PartManager( toplevel, nullptr ) { } KPluginFactory* IPartController::findPartFactory ( const QString& mimetype, const QString& parttype, const QString& preferredName ) { // parttype may be a interface type not derived from KParts/ReadOnlyPart const KService::List offers = KMimeTypeTrader::self()->query( mimetype, QStringLiteral( "KParts/ReadOnlyPart" ), QStringLiteral( "'%1' in ServiceTypes" ).arg( parttype ) ); if ( ! offers.isEmpty() ) { KService::Ptr ptr; // if there is a preferred plugin we'll take it if ( !preferredName.isEmpty() ) { KService::List::ConstIterator it; for ( it = offers.constBegin(); it != offers.constEnd(); ++it ) { if ( ( *it ) ->desktopEntryName() == preferredName ) { ptr = ( *it ); break; } } } // else we just take the first in the list if ( !ptr ) { ptr = offers.first(); } KPluginLoader loader( ptr->library() ); return loader.factory(); } - return 0; + return nullptr; } KParts::Part* IPartController::createPart ( const QString& mimetype, const QString& prefName ) { const uint length = 1; static const char* const services[length] = { // Disable read/write parts until we can support them /*"KParts/ReadWritePart",*/ "KParts/ReadOnlyPart" }; - KParts::Part* part = 0; + KParts::Part* part = nullptr; for ( uint i = 0; i < length; ++i ) { KPluginFactory* editorFactory = findPartFactory( mimetype, QString::fromLatin1(services[ i ]), prefName ); if ( editorFactory ) { - part = editorFactory->create( 0, this ); + part = editorFactory->create( nullptr, this ); break; } } return part; } } diff --git a/interfaces/iplugin.cpp b/interfaces/iplugin.cpp index 860d9d8d90..d8546b5930 100644 --- a/interfaces/iplugin.cpp +++ b/interfaces/iplugin.cpp @@ -1,221 +1,221 @@ /* This file is part of the KDE project Copyright 2002 Simon Hausmann Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Harald Fernengel Copyright 2002 Falk Brettschneider Copyright 2003 Julian Rockey Copyright 2003 Roberto Raggi Copyright 2003 Jens Dagerbo Copyright 2003 Mario Scalas Copyright 2003-2004,2007 Alexander Dymo Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "iplugin.h" #include #include #include #include #include "icore.h" #include "iplugincontroller.h" #include "iprojectcontroller.h" #include "iproject.h" #include "contextmenuextension.h" namespace KDevelop { class IPluginPrivate { public: IPluginPrivate(IPlugin *q) : q(q) {} ~IPluginPrivate() { } void guiClientAdded(KXMLGUIClient *client) { if (client != q) return; q->initializeGuiState(); updateState(); } void updateState() { const int projectCount = ICore::self()->projectController()->projectCount(); IPlugin::ReverseStateChange reverse = IPlugin::StateNoReverse; if (! projectCount) reverse = IPlugin::StateReverse; q->stateChanged(QStringLiteral("has_project"), reverse); } IPlugin *q; ICore *core; QVector m_extensions; QString m_errorDescription; }; IPlugin::IPlugin( const QString &componentName, QObject *parent ) : QObject(parent) , KXMLGUIClient() , d(new IPluginPrivate(this)) { // The following cast is safe, there's no component in KDevPlatform that // creates plugins except the plugincontroller. The controller passes // Core::self() as parent to KServiceTypeTrader::createInstanceFromQuery // so we know the parent is always a Core* pointer. // This is the only way to pass the Core pointer to the plugin during its // creation so plugins have access to ICore during their creation. Q_ASSERT(qobject_cast(parent)); d->core = static_cast(parent); setComponentName(componentName, componentName); auto clientAdded = [=] (KXMLGUIClient* client) { d->guiClientAdded(client); }; foreach (KMainWindow* mw, KMainWindow::memberList()) { KXmlGuiWindow* guiWindow = qobject_cast(mw); if (! guiWindow) continue; connect(guiWindow->guiFactory(), &KXMLGUIFactory::clientAdded, this, clientAdded); } auto updateState = [=] { d->updateState(); }; connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, updateState); connect(ICore::self()->projectController(), &IProjectController::projectClosed, this, updateState); } IPlugin::~IPlugin() { delete d; } void IPlugin::unload() { } ICore *IPlugin::core() const { return d->core; } } QVector KDevelop::IPlugin::extensions( ) const { return d->m_extensions; } void KDevelop::IPlugin::addExtension( const QByteArray& ext ) { d->m_extensions << ext; } KDevelop::ContextMenuExtension KDevelop::IPlugin::contextMenuExtension( KDevelop::Context* ) { return KDevelop::ContextMenuExtension(); } void KDevelop::IPlugin::initializeGuiState() { } class CustomXmlGUIClient : public KXMLGUIClient { public: CustomXmlGUIClient(const QString& componentName) { // TODO KF5: Get rid off this setComponentName(componentName, componentName); } void setXmlFile(QString file) { KXMLGUIClient::setXMLFile(file); } }; KXMLGUIClient* KDevelop::IPlugin::createGUIForMainWindow(Sublime::MainWindow* window) { CustomXmlGUIClient* ret = new CustomXmlGUIClient(componentName()); QString file; createActionsForMainWindow(window, file, *ret->actionCollection()); if(!ret->actionCollection()->isEmpty()) { Q_ASSERT(!file.isEmpty()); //A file must have been set ret->setXmlFile(file); } else { delete ret; - ret = 0; + ret = nullptr; } return ret; } void KDevelop::IPlugin::createActionsForMainWindow( Sublime::MainWindow* /*window*/, QString& /*xmlFile*/, KActionCollection& /*actions*/ ) { } bool KDevelop::IPlugin::hasError() const { return !d->m_errorDescription.isEmpty(); } void KDevelop::IPlugin::setErrorDescription(const QString& description) { d->m_errorDescription = description; } QString KDevelop::IPlugin::errorDescription() const { return d->m_errorDescription; } int KDevelop::IPlugin::configPages() const { return 0; } KDevelop::ConfigPage* KDevelop::IPlugin::configPage (int, QWidget*) { return nullptr; } int KDevelop::IPlugin::perProjectConfigPages() const { return 0; } KDevelop::ConfigPage* KDevelop::IPlugin::perProjectConfigPage(int, const ProjectConfigOptions&, QWidget*) { return nullptr; } #include "moc_iplugin.cpp" diff --git a/interfaces/launchconfigurationtype.cpp b/interfaces/launchconfigurationtype.cpp index 98b8dcf236..eaa7cf5b89 100644 --- a/interfaces/launchconfigurationtype.cpp +++ b/interfaces/launchconfigurationtype.cpp @@ -1,73 +1,73 @@ /* This file is part of KDevelop Copyright 2009 Andrea s Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "launchconfigurationtype.h" #include "ilauncher.h" namespace KDevelop { class LaunchConfigurationTypePrivate { public: QList starters; }; LaunchConfigurationType::LaunchConfigurationType() : d( new LaunchConfigurationTypePrivate ) { } LaunchConfigurationType::~LaunchConfigurationType() { qDeleteAll(d->starters); delete d; } void LaunchConfigurationType::addLauncher( ILauncher* starter ) { if( !d->starters.contains( starter ) ) { d->starters.append( starter ); } } void LaunchConfigurationType::removeLauncher( ILauncher* starter ) { d->starters.removeAll( starter ); } QList LaunchConfigurationType::launchers() const { return d->starters; } ILauncher* LaunchConfigurationType::launcherForId( const QString& id ) { foreach( ILauncher* l, d->starters ) { if( l->id() == id ) { return l; } } - return 0; + return nullptr; } } diff --git a/language/assistant/renameaction.cpp b/language/assistant/renameaction.cpp index 994ec46dc3..01a702c0fb 100644 --- a/language/assistant/renameaction.cpp +++ b/language/assistant/renameaction.cpp @@ -1,114 +1,114 @@ /* Copyright 2012 Olivier de Gaalon Copyright 2014 Kevin Funk 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 "renameaction.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; QVector RevisionedFileRanges::convert(const QMap >& uses) { QVector ret(uses.size()); auto insertIt = ret.begin(); for (auto it = uses.constBegin(); it != uses.constEnd(); ++it, ++insertIt) { insertIt->file = it.key(); insertIt->ranges = it.value(); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(it.key()); if (tracker) { insertIt->revision = tracker->revisionAtLastReset(); } } return ret; } struct RenameAction::Private { Private() {} Identifier m_oldDeclarationName; QString m_newDeclarationName; QVector m_oldDeclarationUses; }; RenameAction::RenameAction(const Identifier& oldDeclarationName, const QString& newDeclarationName, const QVector& oldDeclarationUses) : d(new Private) { d->m_oldDeclarationName = oldDeclarationName; d->m_newDeclarationName = newDeclarationName; d->m_oldDeclarationUses = oldDeclarationUses; } RenameAction::~RenameAction() { } QString RenameAction::description() const { return i18n("Rename \"%1\" to \"%2\"", d->m_oldDeclarationName.toString(), d->m_newDeclarationName); } QString RenameAction::newDeclarationName() const { return d->m_newDeclarationName; } QString RenameAction::oldDeclarationName() const { return d->m_oldDeclarationName.toString(); } void RenameAction::execute() { DocumentChangeSet changes; foreach(const RevisionedFileRanges& ranges, d->m_oldDeclarationUses) { foreach (const RangeInRevision range, ranges.ranges) { KTextEditor::Range currentRange; if (ranges.revision && ranges.revision->valid()) { currentRange = ranges.revision->transformToCurrentRevision(range); } else { currentRange = range.castToSimpleRange(); } DocumentChange useRename(ranges.file, currentRange, d->m_oldDeclarationName.toString(), d->m_newDeclarationName); changes.addChange( useRename ); changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); } } DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); if (!result) { - KMessageBox::error(0, i18n("Failed to apply changes: %1", result.m_failureReason)); + KMessageBox::error(nullptr, i18n("Failed to apply changes: %1", result.m_failureReason)); } emit executed(this); } diff --git a/language/assistant/renameassistant.cpp b/language/assistant/renameassistant.cpp index a9db12c772..ed5d2799ba 100644 --- a/language/assistant/renameassistant.cpp +++ b/language/assistant/renameassistant.cpp @@ -1,238 +1,238 @@ /* Copyright 2010 Olivier de Gaalon Copyright 2014 Kevin Funk 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 "renameassistant.h" #include "renameaction.h" #include "renamefileaction.h" #include "util/debug.h" #include "../codegen/basicrefactoring.h" #include "../codegen/documentchangeset.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainutils.h" #include "../duchain/declaration.h" #include "../duchain/functiondefinition.h" #include "../duchain/classfunctiondeclaration.h" #include #include #include #include #include using namespace KDevelop; namespace { bool rangesConnect(const KTextEditor::Range& firstRange, const KTextEditor::Range& secondRange) { return !firstRange.intersect(secondRange + KTextEditor::Range(0, -1, 0, +1)).isEmpty(); } Declaration* getDeclarationForChangedRange(KTextEditor::Document* doc, const KTextEditor::Range& changed) { const KTextEditor::Cursor cursor(changed.start()); Declaration* declaration = DUChainUtils::itemUnderCursor(doc->url(), cursor).declaration; //If it's null we could be appending, but there's a case where appending gives a wrong decl //and not a null declaration ... "type var(init)", so check for that too if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { declaration = DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(cursor.line(), cursor.column()-1)).declaration; } //In this case, we may either not have a decl at the cursor, or we got a decl, but are editing its use. //In either of those cases, give up and return 0 if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { - return 0; + return nullptr; } return declaration; } } struct RenameAssistant::Private { Private(RenameAssistant* qq) : q(qq) , m_isUseful(false) , m_renameFile(false) { } void reset() { q->doHide(); q->clearActions(); m_oldDeclarationName = Identifier(); m_newDeclarationRange.reset(); m_oldDeclarationUses.clear(); m_isUseful = false; m_renameFile = false; } RenameAssistant* q; KDevelop::Identifier m_oldDeclarationName; QString m_newDeclarationName; KDevelop::PersistentMovingRange::Ptr m_newDeclarationRange; QVector m_oldDeclarationUses; bool m_isUseful; bool m_renameFile; KTextEditor::Cursor m_lastChangedLocation; QPointer m_lastChangedDocument = nullptr; }; RenameAssistant::RenameAssistant(ILanguageSupport* supportedLanguage) : StaticAssistant(supportedLanguage) , d(new Private(this)) { } RenameAssistant::~RenameAssistant() { } QString RenameAssistant::title() const { return tr("Rename"); } bool RenameAssistant::isUseful() const { return d->m_isUseful; } void RenameAssistant::textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, const QString& removedText) { clearActions(); d->m_lastChangedLocation = invocationRange.end(); d->m_lastChangedDocument = doc; if (!supportedLanguage()->refactoring()) { qCWarning(LANGUAGE) << "Refactoring not supported. Aborting."; return; } if (!doc) return; //If the inserted text isn't valid for a variable name, consider the editing ended QRegExp validDeclName("^[0-9a-zA-Z_]*$"); if (removedText.isEmpty() && !validDeclName.exactMatch(doc->text(invocationRange))) { d->reset(); return; } const QUrl url = doc->url(); const IndexedString indexedUrl(url); DUChainReadLocker lock; //If we've stopped editing m_newDeclarationRange or switched the view, // reset and see if there's another declaration being edited if (!d->m_newDeclarationRange.data() || !rangesConnect(d->m_newDeclarationRange->range(), invocationRange) || d->m_newDeclarationRange->document() != indexedUrl) { d->reset(); Declaration* declAtCursor = getDeclarationForChangedRange(doc, invocationRange); if (!declAtCursor) { // not editing a declaration return; } if (supportedLanguage()->refactoring()->shouldRenameUses(declAtCursor)) { QMap< IndexedString, QList > declUses = declAtCursor->uses(); if (declUses.isEmpty()) { // new declaration has no uses return; } for(QMap< IndexedString, QList< RangeInRevision > >::const_iterator it = declUses.constBegin(); it != declUses.constEnd(); ++it) { foreach(const RangeInRevision range, it.value()) { KTextEditor::Range currentRange = declAtCursor->transformFromLocalRevision(range); if(currentRange.isEmpty() || doc->text(currentRange) != declAtCursor->identifier().identifier().str()) { return; // One of the uses is invalid. Maybe the replacement has already been performed. } } } d->m_oldDeclarationUses = RevisionedFileRanges::convert(declUses); } else if (supportedLanguage()->refactoring()->shouldRenameFile(declAtCursor)) { d->m_renameFile = true; } else { // not a valid declaration return; } d->m_oldDeclarationName = declAtCursor->identifier(); KTextEditor::Range newRange = declAtCursor->rangeInCurrentRevision(); if (removedText.isEmpty() && newRange.intersect(invocationRange).isEmpty()) { newRange = newRange.encompass(invocationRange); //if text was added to the ends, encompass it } d->m_newDeclarationRange = new PersistentMovingRange(newRange, indexedUrl, true); } //Unfortunately this happens when you make a selection including one end of the decl's range and replace it if (removedText.isEmpty() && d->m_newDeclarationRange->range().intersect(invocationRange).isEmpty()) { d->m_newDeclarationRange = new PersistentMovingRange( d->m_newDeclarationRange->range().encompass(invocationRange), indexedUrl, true); } d->m_newDeclarationName = doc->text(d->m_newDeclarationRange->range()); if (d->m_newDeclarationName == d->m_oldDeclarationName.toString()) { d->reset(); return; } if (d->m_renameFile && supportedLanguage()->refactoring()->newFileName(url, d->m_newDeclarationName) == url.fileName()) { // no change, don't do anything return; } d->m_isUseful = true; IAssistantAction::Ptr action; if (d->m_renameFile) { action = new RenameFileAction(supportedLanguage()->refactoring(), url, d->m_newDeclarationName); } else { action = new RenameAction(d->m_oldDeclarationName, d->m_newDeclarationName, d->m_oldDeclarationUses); } connect(action.data(), &IAssistantAction::executed, this, [&] { d->reset(); }); addAction(action); emit actionsChanged(); } KTextEditor::Range KDevelop::RenameAssistant::displayRange() const { if ( !d->m_lastChangedDocument ) { return {}; } auto range = d->m_lastChangedDocument->wordRangeAt(d->m_lastChangedLocation); qDebug() << "range:" << range; return range; } #include "moc_renameassistant.cpp" diff --git a/language/assistant/renamefileaction.cpp b/language/assistant/renamefileaction.cpp index db3d01109d..bc070453c4 100644 --- a/language/assistant/renamefileaction.cpp +++ b/language/assistant/renamefileaction.cpp @@ -1,83 +1,83 @@ /* Copyright 2012 Milian Wolff Copyright 2014 Kevin Funk 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 "renamefileaction.h" #include "util/debug.h" #include #include #include #include #include #include using namespace KDevelop; struct RenameFileAction::Private { KDevelop::BasicRefactoring* m_refactoring; QUrl m_file; QString m_newName; }; RenameFileAction::RenameFileAction(BasicRefactoring* refactoring, const QUrl& file, const QString& newName) : d(new Private) { d->m_refactoring = refactoring; d->m_file = file; d->m_newName = newName; } RenameFileAction::~RenameFileAction() { } QString RenameFileAction::description() const { return i18n("Rename file from \"%1\" to \"%2\".", d->m_file.fileName(), d->m_refactoring->newFileName(d->m_file, d->m_newName)); } void RenameFileAction::execute() { // save document to prevent unwanted dialogs IDocument* doc = ICore::self()->documentController()->documentForUrl(d->m_file); if (!doc) { qCWarning(LANGUAGE) << "could find no document for url:" << d->m_file; return; } if (!ICore::self()->documentController()->saveSomeDocuments(QList() << doc, IDocument::Silent)) { return; } // rename document DocumentChangeSet changes; DocumentChangeSet::ChangeResult result = d->m_refactoring->addRenameFileChanges(d->m_file, d->m_newName, &changes); if (result) { result = changes.applyAllChanges(); } if(!result) { - KMessageBox::error(0, i18n("Failed to apply changes: %1", result.m_failureReason)); + KMessageBox::error(nullptr, i18n("Failed to apply changes: %1", result.m_failureReason)); } emit executed(this); } diff --git a/language/backgroundparser/backgroundparser.cpp b/language/backgroundparser/backgroundparser.cpp index 602ea192d3..3491149e32 100644 --- a/language/backgroundparser/backgroundparser.cpp +++ b/language/backgroundparser/backgroundparser.cpp @@ -1,901 +1,901 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "backgroundparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util/debug.h" #include "parsejob.h" #include using namespace KDevelop; namespace { const bool separateThreadForHighPriority = true; /** * Elides string in @p path, e.g. "VEEERY/LONG/PATH" -> ".../LONG/PATH" * - probably much faster than QFontMetrics::elidedText() * - we dont need a widget context * - takes path separators into account * * @p width Maximum number of characters * * TODO: Move to kdevutil? */ QString elidedPathLeft(const QString& path, int width) { static const QChar separator = QDir::separator(); static const QString placeholder = QStringLiteral("..."); if (path.size() <= width) { return path; } int start = (path.size() - width) + placeholder.size(); int pos = path.indexOf(separator, start); if (pos == -1) { pos = start; // no separator => just cut off the path at the beginning } Q_ASSERT(path.size() - pos >= 0 && path.size() - pos <= width); QStringRef elidedText = path.rightRef(path.size() - pos); QString result = placeholder; result.append(elidedText); return result; } /** * @return true if @p url is non-empty, valid and has a clean path, false otherwise. */ inline bool isValidURL(const IndexedString& url) { if (url.isEmpty()) { return false; } QUrl original = url.toUrl(); if (!original.isValid() || original.isRelative() || original.fileName().isEmpty()) { qCWarning(LANGUAGE) << "INVALID URL ENCOUNTERED:" << url << original; return false; } QUrl cleaned = original.adjusted(QUrl::NormalizePathSegments); return original == cleaned; } } struct DocumentParseTarget { QPointer notifyWhenReady; int priority; TopDUContext::Features features; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; inline uint qHash(const DocumentParseTarget& target) { return target.features * 7 + target.priority * 13 + target.sequentialProcessingFlags * 17 + reinterpret_cast(target.notifyWhenReady.data()); }; struct DocumentParsePlan { QSet targets; ParseJob::SequentialProcessingFlags sequentialProcessingFlags() const { //Pick the strictest possible flags ParseJob::SequentialProcessingFlags ret = ParseJob::IgnoresSequentialProcessing; foreach(const DocumentParseTarget &target, targets) { ret |= target.sequentialProcessingFlags; } return ret; } int priority() const { //Pick the best priority int ret = BackgroundParser::WorstPriority; foreach(const DocumentParseTarget &target, targets) { if(target.priority < ret) { ret = target.priority; } } return ret; } TopDUContext::Features features() const { //Pick the best features TopDUContext::Features ret = (TopDUContext::Features)0; foreach(const DocumentParseTarget &target, targets) { ret = (TopDUContext::Features) (ret | target.features); } return ret; } QList > notifyWhenReady() const { QList > ret; foreach(const DocumentParseTarget &target, targets) { if(target.notifyWhenReady) ret << target.notifyWhenReady; } return ret; } }; Q_DECLARE_TYPEINFO(DocumentParseTarget, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(DocumentParsePlan, Q_MOVABLE_TYPE); class KDevelop::BackgroundParserPrivate { public: BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController) :m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive) { parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings() m_timer.setSingleShot(true); m_delay = 500; m_threads = 1; m_doneParseJobs = 0; m_maxParseJobs = 0; m_neededPriority = BackgroundParser::WorstPriority; ThreadWeaver::setDebugLevel(true, 1); QObject::connect(&m_timer, &QTimer::timeout, m_parser, &BackgroundParser::parseDocuments); } void startTimerThreadSafe(int delay) { QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection, Q_ARG(int, delay)); } ~BackgroundParserPrivate() { m_weaver.resume(); m_weaver.finish(); } // Non-mutex guarded functions, only call with m_mutex acquired. int currentBestRunningPriority() const { int bestRunningPriority = BackgroundParser::WorstPriority; for (const auto* decorator : m_parseJobs) { const ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); if (parseJob->respectsSequentialProcessing() && parseJob->parsePriority() < bestRunningPriority) { bestRunningPriority = parseJob->parsePriority(); } } return bestRunningPriority; } IndexedString nextDocumentToParse() const { // Before starting a new job, first wait for all higher-priority ones to finish. // That way, parse job priorities can be used for dependency handling. const int bestRunningPriority = currentBestRunningPriority(); for (auto it1 = m_documentsForPriority.begin(); it1 != m_documentsForPriority.end(); ++it1 ) { const auto priority = it1.key(); if(priority > m_neededPriority) break; //The priority is not good enough to be processed right now if (m_parseJobs.count() >= m_threads && priority > BackgroundParser::NormalPriority && !specialParseJob) { break; //The additional parsing thread is reserved for higher priority parsing } for (const auto& url : it1.value()) { // When a document is scheduled for parsing while it is being parsed, it will be parsed // again once the job finished, but not now. if (m_parseJobs.contains(url)) { continue; } Q_ASSERT(m_documents.contains(url)); const auto& parsePlan = m_documents[url]; // If the current job requires sequential processing, but not all jobs with a better priority have been // completed yet, it will not be created now. if ( parsePlan.sequentialProcessingFlags() & ParseJob::RequiresSequentialProcessing && parsePlan.priority() > bestRunningPriority ) { continue; } return url; } } return {}; } /** * Create a single delayed parse job * * E.g. jobs for documents which have been changed by the user, but also to * handle initial startup where we parse all project files. */ void parseDocumentsInternal() { if(m_shuttingDown) return; //Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily if (m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority)) { return; } const auto& url = nextDocumentToParse(); if (!url.isEmpty()) { qCDebug(LANGUAGE) << "creating parse-job" << url << "new count of active parse-jobs:" << m_parseJobs.count() + 1; const QString elidedPathString = elidedPathLeft(url.str(), 70); emit m_parser->showMessage(m_parser, i18n("Parsing: %1", elidedPathString)); ThreadWeaver::QObjectDecorator* decorator = nullptr; { // copy shared data before unlocking the mutex const auto parsePlanConstIt = m_documents.constFind(url); const DocumentParsePlan parsePlan = *parsePlanConstIt; // we must not lock the mutex while creating a parse job // this could in turn lock e.g. the DUChain and then // we have a classic lock order inversion (since, usually, // we lock first the duchain and then our background parser // mutex) // see also: https://bugs.kde.org/show_bug.cgi?id=355100 m_mutex.unlock(); decorator = createParseJob(url, parsePlan); m_mutex.lock(); } // iterator might get invalid during the time we didn't have the lock // search again const auto parsePlanIt = m_documents.find(url); if (parsePlanIt != m_documents.end()) { // Remove all mentions of this document. for (const auto& target : parsePlanIt->targets) { m_documentsForPriority[target.priority].remove(url); } m_documents.erase(parsePlanIt); } else { qWarning(LANGUAGE) << "Document got removed during parse job creation:" << url; } if (decorator) { if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = decorator; //This parse-job is allocated into the reserved thread m_parseJobs.insert(url, decorator); m_weaver.enqueue(ThreadWeaver::JobPointer(decorator)); } else { --m_maxParseJobs; } if (!m_documents.isEmpty()) { // Only try creating one parse-job at a time, else we might iterate through thousands of files // without finding a language-support, and block the UI for a long time. QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection); } else { // make sure we cleaned up properly // TODO: also empty m_documentsForPriority when m_documents is empty? or do we want to keep capacity? Q_ASSERT(std::none_of(m_documentsForPriority.constBegin(), m_documentsForPriority.constEnd(), [] (const QSet& docs) { return !docs.isEmpty(); })); } } m_parser->updateProgressBar(); //We don't hide the progress-bar in updateProgressBar, so it doesn't permanently flash when a document is reparsed again and again. if(m_doneParseJobs == m_maxParseJobs || (m_neededPriority == BackgroundParser::BestPriority && m_weaver.queueLength() == 0)) { emit m_parser->hideProgress(m_parser); } } // NOTE: you must not access any of the data structures that are protected by any of the // background parser internal mutexes in this method // see also: https://bugs.kde.org/show_bug.cgi?id=355100 ThreadWeaver::QObjectDecorator* createParseJob(const IndexedString& url, const DocumentParsePlan& parsePlan) { ///FIXME: use IndexedString in the other APIs as well! Esp. for createParseJob! QUrl qUrl = url.toUrl(); const auto languages = m_languageController->languagesForUrl(qUrl); const auto& notifyWhenReady = parsePlan.notifyWhenReady(); for (const auto language : languages) { if (!language) { qCWarning(LANGUAGE) << "got zero language for" << qUrl; continue; } ParseJob* job = language->createParseJob(url); if (!job) { continue; // Language part did not produce a valid ParseJob. } job->setParsePriority(parsePlan.priority()); job->setMinimumFeatures(parsePlan.features()); job->setNotifyWhenReady(notifyWhenReady); job->setSequentialProcessingFlags(parsePlan.sequentialProcessingFlags()); ThreadWeaver::QObjectDecorator* decorator = new ThreadWeaver::QObjectDecorator(job); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::done, m_parser, &BackgroundParser::parseComplete); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::failed, m_parser, &BackgroundParser::parseComplete); QObject::connect(job, &ParseJob::progress, m_parser, &BackgroundParser::parseProgress, Qt::QueuedConnection); // TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse) return decorator; } if (languages.isEmpty()) qCDebug(LANGUAGE) << "found no languages for url" << qUrl; else qCDebug(LANGUAGE) << "could not create parse-job for url" << qUrl; //Notify that we failed for (const auto& n : notifyWhenReady) { if (!n) { continue; } QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext())); } return nullptr; } void loadSettings() { ///@todo re-load settings when they have been changed! Q_ASSERT(ICore::self()->activeSession()); KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser"); // stay backwards compatible KConfigGroup oldConfig(KSharedConfig::openConfig(), "Background Parser"); #define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \ config.readEntry(entry, oldConfig.readEntry(entry, default)) m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500); m_timer.setInterval(m_delay); m_threads = 0; if (qEnvironmentVariableIsSet("KDEV_BACKGROUNDPARSER_MAXTHREADS")) { m_parser->setThreadCount(qgetenv("KDEV_BACKGROUNDPARSER_MAXTHREADS").toInt()); } else { m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", QThread::idealThreadCount())); } resume(); if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) { m_parser->enableProcessing(); } else { m_parser->disableProcessing(); } } void suspend() { qCDebug(LANGUAGE) << "Suspending background parser"; bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (s) { // Already suspending qCWarning(LANGUAGE) << "Already suspended or suspending"; return; } m_timer.stop(); m_weaver.suspend(); } void resume() { bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (m_timer.isActive() && !s) { // Not suspending return; } m_timer.start(m_delay); m_weaver.resume(); } BackgroundParser *m_parser; ILanguageController* m_languageController; //Current parse-job that is executed in the additional thread QPointer specialParseJob; QTimer m_timer; int m_delay; int m_threads; bool m_shuttingDown; // A list of documents that are planned to be parsed, and their priority QHash m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; // Projects currently in progress of loading QSet m_loadingProjects; ThreadWeaver::Queue m_weaver; // generic high-level mutex QMutex m_mutex; // local mutex only protecting m_managed QMutex m_managedMutex; // A change tracker for each managed document QHash m_managed; int m_maxParseJobs; int m_doneParseJobs; QHash m_jobProgress; int m_neededPriority; //The minimum priority needed for processed jobs }; BackgroundParser::BackgroundParser(ILanguageController *languageController) : QObject(languageController), d(new BackgroundParserPrivate(this, languageController)) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), &IDocumentController::documentLoaded, this, &BackgroundParser::documentLoaded); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &BackgroundParser::documentUrlChanged); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &BackgroundParser::documentClosed); connect(ICore::self(), &ICore::aboutToShutdown, this, &BackgroundParser::aboutToQuit); bool connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectAboutToBeOpened, this, &BackgroundParser::projectAboutToBeOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &BackgroundParser::projectOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpeningAborted, this, &BackgroundParser::projectOpeningAborted); Q_ASSERT(connected); Q_UNUSED(connected); } void BackgroundParser::aboutToQuit() { d->m_shuttingDown = true; } BackgroundParser::~BackgroundParser() { delete d; } QString BackgroundParser::statusName() const { return i18n("Background Parser"); } void BackgroundParser::loadSettings() { d->loadSettings(); } void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text) { Q_UNUSED(text) d->m_jobProgress[job] = value; updateProgressBar(); } void BackgroundParser::revertAllRequests(QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); for (auto it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); foreach ( const DocumentParseTarget& target, (*it).targets ) { if ( notifyWhenReady && target.notifyWhenReady.data() == notifyWhenReady ) { (*it).targets.remove(target); } } if((*it).targets.isEmpty()) { it = d->m_documents.erase(it); --d->m_maxParseJobs; continue; } d->m_documentsForPriority[it.value().priority()].insert(it.key()); ++it; } } void BackgroundParser::addDocument(const IndexedString& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady, ParseJob::SequentialProcessingFlags flags, int delay) { // qCDebug(LANGUAGE) << "BackgroundParser::addDocument" << url.toUrl(); Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); { DocumentParseTarget target; target.priority = priority; target.features = features; target.sequentialProcessingFlags = flags; target.notifyWhenReady = QPointer(notifyWhenReady); auto it = d->m_documents.find(url); if (it != d->m_documents.end()) { //Update the stored plan d->m_documentsForPriority[it.value().priority()].remove(url); it.value().targets << target; d->m_documentsForPriority[it.value().priority()].insert(url); }else{ // qCDebug(LANGUAGE) << "BackgroundParser::addDocument: queuing" << cleanedUrl; d->m_documents[url].targets << target; d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); ++d->m_maxParseJobs; //So the progress-bar waits for this document } if ( delay == ILanguageSupport::DefaultDelay ) { delay = d->m_delay; } d->startTimerThreadSafe(delay); } } void BackgroundParser::removeDocument(const IndexedString& url, QObject* notifyWhenReady) { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(const DocumentParseTarget& target, d->m_documents[url].targets) { if(target.notifyWhenReady.data() == notifyWhenReady) { d->m_documents[url].targets.remove(target); } } if(d->m_documents[url].targets.isEmpty()) { d->m_documents.remove(url); --d->m_maxParseJobs; }else{ //Insert with an eventually different priority d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); } } } void BackgroundParser::parseDocuments() { if (!d->m_loadingProjects.empty()) { startTimer(d->m_delay); return; } QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(const ThreadWeaver::JobPointer& job) { auto decorator = dynamic_cast(job.data()); Q_ASSERT(decorator); ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); emit parseJobFinished(parseJob); { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document()); d->m_jobProgress.remove(parseJob); ++d->m_doneParseJobs; updateProgressBar(); } //Continue creating more parse-jobs QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection); } void BackgroundParser::disableProcessing() { setNeededPriority(BestPriority); } void BackgroundParser::enableProcessing() { setNeededPriority(WorstPriority); } int BackgroundParser::priorityForDocument(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents[url].priority(); } bool BackgroundParser::isQueued(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents.contains(url); } int BackgroundParser::queuedCount() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.count(); } bool BackgroundParser::isIdle() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.isEmpty() && d->m_weaver.isIdle(); } void BackgroundParser::setNeededPriority(int priority) { QMutexLocker lock(&d->m_mutex); d->m_neededPriority = priority; d->startTimerThreadSafe(d->m_delay); } void BackgroundParser::abortAllJobs() { qCDebug(LANGUAGE) << "Aborting all parse jobs"; d->m_weaver.requestAbort(); } void BackgroundParser::suspend() { d->suspend(); emit hideProgress(this); } void BackgroundParser::resume() { d->resume(); updateProgressBar(); } void BackgroundParser::updateProgressBar() { if (d->m_doneParseJobs >= d->m_maxParseJobs) { if(d->m_doneParseJobs > d->m_maxParseJobs) { qCDebug(LANGUAGE) << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs; } d->m_doneParseJobs = 0; d->m_maxParseJobs = 0; } else { float additionalProgress = 0; for (auto it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it) { additionalProgress += *it; } emit showProgress(this, 0, d->m_maxParseJobs*1000, (additionalProgress + d->m_doneParseJobs)*1000); } } ParseJob* BackgroundParser::parseJobForDocument(const IndexedString& document) const { Q_ASSERT(isValidURL(document)); QMutexLocker lock(&d->m_mutex); auto decorator = d->m_parseJobs.value(document); return decorator ? dynamic_cast(decorator->job()) : nullptr; } void BackgroundParser::setThreadCount(int threadCount) { if (d->m_threads != threadCount) { d->m_threads = threadCount; d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing } } int BackgroundParser::threadCount() const { return d->m_threads; } void BackgroundParser::setDelay(int milliseconds) { if (d->m_delay != milliseconds) { d->m_delay = milliseconds; d->m_timer.setInterval(d->m_delay); } } QList< IndexedString > BackgroundParser::managedDocuments() { QMutexLocker l(&d->m_managedMutex); return d->m_managed.keys(); } DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const { if (url.isEmpty()) { // this happens e.g. when setting the final location of a problem that is not // yet associated with a top ctx. - return 0; + return nullptr; } if ( !isValidURL(url) ) { qWarning() << "Tracker requested for invalild URL:" << url.toUrl(); } Q_ASSERT(isValidURL(url)); QMutexLocker l(&d->m_managedMutex); - return d->m_managed.value(url, 0); + return d->m_managed.value(url, nullptr); } void BackgroundParser::documentClosed(IDocument* document) { QMutexLocker l(&d->m_mutex); if(document->textDocument()) { KTextEditor::Document* textDocument = document->textDocument(); if(!d->m_managedTextDocumentUrls.contains(textDocument)) return; // Probably the document had an invalid url, and thus it wasn't added to the background parser Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument)); IndexedString url(d->m_managedTextDocumentUrls[textDocument]); QMutexLocker l2(&d->m_managedMutex); Q_ASSERT(d->m_managed.contains(url)); qCDebug(LANGUAGE) << "removing" << url.str() << "from background parser"; delete d->m_managed[url]; d->m_managedTextDocumentUrls.remove(textDocument); d->m_managed.remove(url); } } void BackgroundParser::documentLoaded( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument() && document->textDocument()->url().isValid()) { KTextEditor::Document* textDocument = document->textDocument(); IndexedString url(document->url()); // Some debugging because we had issues with this QMutexLocker l2(&d->m_managedMutex); if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument) { qCDebug(LANGUAGE) << "Got redundant documentLoaded from" << document->url() << textDocument; return; } qCDebug(LANGUAGE) << "Creating change tracker for " << document->url(); Q_ASSERT(!d->m_managed.contains(url)); Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument)); d->m_managedTextDocumentUrls[textDocument] = url; d->m_managed.insert(url, new DocumentChangeTracker(textDocument)); }else{ qCDebug(LANGUAGE) << "NOT creating change tracker for" << document->url(); } } void BackgroundParser::documentUrlChanged(IDocument* document) { documentClosed(document); // Only call documentLoaded if the file wasn't renamed to a filename that is already tracked. if(document->textDocument() && !trackerForUrl(IndexedString(document->textDocument()->url()))) documentLoaded(document); } void BackgroundParser::startTimer(int delay) { d->m_timer.start(delay); } void BackgroundParser::projectAboutToBeOpened(IProject* project) { d->m_loadingProjects.insert(project); } void BackgroundParser::projectOpened(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::projectOpeningAborted(IProject* project) { d->m_loadingProjects.remove(project); } diff --git a/language/backgroundparser/documentchangetracker.cpp b/language/backgroundparser/documentchangetracker.cpp index 294bc14038..ef63c3b243 100644 --- a/language/backgroundparser/documentchangetracker.cpp +++ b/language/backgroundparser/documentchangetracker.cpp @@ -1,469 +1,469 @@ /* * This file is part of KDevelop * * Copyright 2010 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentchangetracker.h" #include #include #include #include #include #include #include #include #include #include "backgroundparser.h" #include "util/debug.h" #include // Can be used to disable the 'clever' updating logic that ignores whitespace-only changes and such. // #define ALWAYS_UPDATE using namespace KTextEditor; /** * @todo Track the exact changes to the document, and then: * Dont reparse if: * - Comment added/changed * - Newlines added/changed (ready) * Complete the document for validation: * - Incomplete for-loops * - ... * Only reparse after a statement was completed (either with statement-completion or manually), or after the cursor was switched away * Incremental parsing: * - All changes within a local function (or function parameter context): Update only the context (and all its importers) * * @todo: Prevent recursive updates after insignificant changes * (whitespace changes, or changes that don't affect publically visible stuff, eg. local incremental changes) * -> Maybe alter the file-modification caches directly * */ namespace KDevelop { DocumentChangeTracker::DocumentChangeTracker( KTextEditor::Document* document ) - : m_needUpdate(false), m_document(document), m_moving(0) + : m_needUpdate(false), m_document(document), m_moving(nullptr) { m_url = IndexedString(document->url()); Q_ASSERT(document); Q_ASSERT(document->url().isValid()); connect(document, &Document::textInserted, this, &DocumentChangeTracker::textInserted); connect(document, &Document::lineWrapped, this, &DocumentChangeTracker::lineWrapped); connect(document, &Document::lineUnwrapped, this, &DocumentChangeTracker::lineUnwrapped); connect(document, &Document::textRemoved, this, &DocumentChangeTracker::textRemoved); connect(document, &Document::destroyed, this, &DocumentChangeTracker::documentDestroyed); connect(document, &Document::documentSavedOrUploaded, this, &DocumentChangeTracker::documentSavedOrUploaded); m_moving = dynamic_cast(document); Q_ASSERT(m_moving); // can't use new connect syntax here, MovingInterface is not a QObject connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); reset(); } QList< QPair< KTextEditor::Range, QString > > DocumentChangeTracker::completions() const { VERIFY_FOREGROUND_LOCKED QList< QPair< KTextEditor::Range , QString > > ret; return ret; } void DocumentChangeTracker::reset() { VERIFY_FOREGROUND_LOCKED // We don't reset the insertion here, as it may continue m_needUpdate = false; m_revisionAtLastReset = acquireRevision(m_moving->revision()); Q_ASSERT(m_revisionAtLastReset); } RevisionReference DocumentChangeTracker::currentRevision() { VERIFY_FOREGROUND_LOCKED return acquireRevision(m_moving->revision()); } RevisionReference DocumentChangeTracker::revisionAtLastReset() const { VERIFY_FOREGROUND_LOCKED return m_revisionAtLastReset; } bool DocumentChangeTracker::needUpdate() const { VERIFY_FOREGROUND_LOCKED return m_needUpdate; } void DocumentChangeTracker::updateChangedRange(int delay) { // Q_ASSERT(m_moving->revision() != m_revisionAtLastReset->revision()); // May happen after reload // When reloading, textRemoved is called with an invalid m_document->url(). For that reason, we use m_url instead. ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); if(needUpdate()) { ICore::self()->languageController()->backgroundParser()->addDocument(m_url, TopDUContext::AllDeclarationsContextsAndUses, 0, nullptr, ParseJob::IgnoresSequentialProcessing, delay); } } static Cursor cursorAdd(Cursor c, const QString& text) { c.setLine(c.line() + text.count('\n')); c.setColumn(c.column() + (text.length() - qMin(0, text.lastIndexOf('\n')))); return c; } int DocumentChangeTracker::recommendedDelay(KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& text, bool removal) { auto languages = ICore::self()->languageController()->languagesForUrl(doc->url()); int delay = ILanguageSupport::NoUpdateRequired; Q_FOREACH (const auto& lang, languages) { // take the largest value, because NoUpdateRequired is -2 and we want to make sure // that if one language requires an update it actually happens delay = qMax(lang->suggestedReparseDelayForChange(doc, range, text, removal), delay); } return delay; } void DocumentChangeTracker::lineWrapped(KTextEditor::Document* document, const KTextEditor::Cursor& position) { textInserted(document, position, QStringLiteral("\n")); } void DocumentChangeTracker::lineUnwrapped(KTextEditor::Document* document, int line) { textRemoved(document, {{document->lineLength(line), line}, {0, line+1}}, QStringLiteral("\n")); } void DocumentChangeTracker::textInserted(Document* document, const Cursor& cursor, const QString& text) { /// TODO: get this data from KTextEditor directly, make its signal public KTextEditor::Range range(cursor, cursorAdd(cursor, text)); if(!m_lastInsertionPosition.isValid() || m_lastInsertionPosition == cursor) { m_currentCleanedInsertion.append(text); m_lastInsertionPosition = range.end(); } auto delay = recommendedDelay(document, range, text, false); m_needUpdate = delay != ILanguageSupport::NoUpdateRequired; updateChangedRange(delay); } void DocumentChangeTracker::textRemoved( Document* document, const KTextEditor::Range& oldRange, const QString& oldText ) { m_currentCleanedInsertion.clear(); m_lastInsertionPosition = KTextEditor::Cursor::invalid(); auto delay = recommendedDelay(document, oldRange, oldText, true); m_needUpdate = delay != ILanguageSupport::NoUpdateRequired; updateChangedRange(delay); } void DocumentChangeTracker::documentSavedOrUploaded(KTextEditor::Document* doc,bool) { ModificationRevision::clearModificationCache(IndexedString(doc->url())); } void DocumentChangeTracker::documentDestroyed( QObject* ) { - m_document = 0; - m_moving = 0; + m_document = nullptr; + m_moving = nullptr; } DocumentChangeTracker::~DocumentChangeTracker() { Q_ASSERT(m_document); ModificationRevision::clearEditorRevisionForFile(KDevelop::IndexedString(m_document->url())); } Document* DocumentChangeTracker::document() const { return m_document; } MovingInterface* DocumentChangeTracker::documentMovingInterface() const { return m_moving; } void DocumentChangeTracker::aboutToInvalidateMovingInterfaceContent ( Document* ) { // Release all revisions! They must not be used any more. qCDebug(LANGUAGE) << "clearing all revisions"; m_revisionLocks.clear(); m_revisionAtLastReset = RevisionReference(); ModificationRevision::setEditorRevisionForFile(m_url, 0); } KDevelop::RangeInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::RangeInRevision range, qint64 fromRevision, qint64 toRevision) const { VERIFY_FOREGROUND_LOCKED if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { m_moving->transformCursor(range.start.line, range.start.column, KTextEditor::MovingCursor::MoveOnInsert, fromRevision, toRevision); m_moving->transformCursor(range.end.line, range.end.column, KTextEditor::MovingCursor::StayOnInsert, fromRevision, toRevision); } return range; } KDevelop::CursorInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::CursorInRevision cursor, qint64 fromRevision, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { m_moving->transformCursor(cursor.line, cursor.column, behavior, fromRevision, toRevision); } return cursor; } RangeInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Range range, qint64 toRevision) const { return transformBetweenRevisions(RangeInRevision::castFromSimpleRange(range), -1, toRevision); } CursorInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Cursor cursor, qint64 toRevision, MovingCursor::InsertBehavior behavior) const { return transformBetweenRevisions(CursorInRevision::castFromSimpleCursor(cursor), -1, toRevision, behavior); } KTextEditor::Range DocumentChangeTracker::transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const { return transformBetweenRevisions(range, fromRevision, -1).castToSimpleRange(); } KTextEditor::Cursor DocumentChangeTracker::transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision, MovingCursor::InsertBehavior behavior) const { return transformBetweenRevisions(cursor, fromRevision, -1, behavior).castToSimpleCursor(); } RevisionLockerAndClearerPrivate::RevisionLockerAndClearerPrivate(DocumentChangeTracker* tracker, qint64 revision) : m_tracker(tracker), m_revision(revision) { VERIFY_FOREGROUND_LOCKED moveToThread(QApplication::instance()->thread()); // Lock the revision m_tracker->lockRevision(revision); } RevisionLockerAndClearerPrivate::~RevisionLockerAndClearerPrivate() { if (m_tracker) m_tracker->unlockRevision(m_revision); } RevisionLockerAndClearer::~RevisionLockerAndClearer() { m_p->deleteLater(); // Will be deleted in the foreground thread, as the object was re-owned to the foreground } RevisionReference DocumentChangeTracker::acquireRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED if(!holdingRevision(revision) && revision != m_moving->revision()) return RevisionReference(); RevisionReference ret(new RevisionLockerAndClearer); ret->m_p = new RevisionLockerAndClearerPrivate(this, revision); return ret; } bool DocumentChangeTracker::holdingRevision(qint64 revision) const { VERIFY_FOREGROUND_LOCKED return m_revisionLocks.contains(revision); } void DocumentChangeTracker::lockRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED QMap< qint64, int >::iterator it = m_revisionLocks.find(revision); if(it != m_revisionLocks.end()) ++(*it); else { m_revisionLocks.insert(revision, 1); m_moving->lockRevision(revision); } } void DocumentChangeTracker::unlockRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED QMap< qint64, int >::iterator it = m_revisionLocks.find(revision); if(it == m_revisionLocks.end()) { qCDebug(LANGUAGE) << "cannot unlock revision" << revision << ", probably the revisions have been cleared"; return; } --(*it); if(*it == 0) { m_moving->unlockRevision(revision); m_revisionLocks.erase(it); } } qint64 RevisionLockerAndClearer::revision() const { return m_p->revision(); } RangeInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& to) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid() || (to && !to->valid())) return range; qint64 fromRevision = revision(); qint64 toRevision = -1; if(to) toRevision = to->revision(); return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); } CursorInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& to, MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid() || (to && !to->valid())) return cursor; qint64 fromRevision = revision(); qint64 toRevision = -1; if(to) toRevision = to->revision(); return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); } RangeInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& from) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid()) return range; qint64 toRevision = revision(); qint64 fromRevision = -1; if(from) fromRevision = from->revision(); return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); } CursorInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& from, MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker) return cursor; qint64 toRevision = revision(); qint64 fromRevision = -1; if(from) fromRevision = from->revision(); return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); } KTextEditor::Range RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::RangeInRevision& range) const { return transformToRevision(range, KDevelop::RevisionLockerAndClearer::Ptr()).castToSimpleRange(); } KTextEditor::Cursor RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::CursorInRevision& cursor, MovingCursor::InsertBehavior behavior) const { return transformToRevision(cursor, KDevelop::RevisionLockerAndClearer::Ptr(), behavior).castToSimpleCursor(); } RangeInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Range& range) const { return transformFromRevision(RangeInRevision::castFromSimpleRange(range), RevisionReference()); } CursorInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Cursor& cursor, MovingCursor::InsertBehavior behavior) const { return transformFromRevision(CursorInRevision::castFromSimpleCursor(cursor), RevisionReference(), behavior); } bool RevisionLockerAndClearer::valid() const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker) return false; if(revision() == -1) return true; // The 'current' revision is always valid return m_p->m_tracker->holdingRevision(revision()); } RevisionReference DocumentChangeTracker::diskRevision() const { ///@todo Track which revision was last saved to disk return RevisionReference(); } } diff --git a/language/backgroundparser/tests/test_backgroundparser.cpp b/language/backgroundparser/tests/test_backgroundparser.cpp index d85cbd7113..174640afd1 100644 --- a/language/backgroundparser/tests/test_backgroundparser.cpp +++ b/language/backgroundparser/tests/test_backgroundparser.cpp @@ -1,386 +1,386 @@ /* * This file is part of KDevelop * * Copyright 2012 by Sven Brauch * Copyright 2012 by Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_backgroundparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "testlanguagesupport.h" #include "testparsejob.h" QTEST_MAIN(TestBackgroundparser) #define QVERIFY_RETURN(statement, retval) \ do { if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) return retval; } while (0) using namespace KDevelop; JobPlan::JobPlan() { } void JobPlan::addJob(const JobPrototype& job) { m_jobs << job; } void JobPlan::clear() { m_jobs.clear(); m_finishedJobs.clear(); m_createdJobs.clear(); } void JobPlan::parseJobCreated(ParseJob* job) { // e.g. for the benchmark if (m_jobs.isEmpty()) { return; } TestParseJob* testJob = dynamic_cast(job); Q_ASSERT(testJob); qDebug() << "assigning propierties for created job" << testJob->document().toUrl(); testJob->duration_ms = jobForUrl(testJob->document()).m_duration; m_createdJobs.append(testJob->document()); } bool JobPlan::runJobs(int timeoutMS) { // add parse jobs foreach(const JobPrototype& job, m_jobs) { ICore::self()->languageController()->backgroundParser()->addDocument( job.m_url, TopDUContext::Empty, job.m_priority, this, job.m_flags ); } ICore::self()->languageController()->backgroundParser()->parseDocuments(); QElapsedTimer t; t.start(); while ( !t.hasExpired(timeoutMS) && m_jobs.size() != m_finishedJobs.size() ) { QTest::qWait(50); } QVERIFY_RETURN(m_jobs.size() == m_createdJobs.size(), false); QVERIFY_RETURN(m_finishedJobs.size() == m_jobs.size(), false); // verify they're started in the right order int currentBestPriority = BackgroundParser::BestPriority; foreach ( const IndexedString& url, m_createdJobs ) { const JobPrototype p = jobForUrl(url); QVERIFY_RETURN(p.m_priority >= currentBestPriority, false); currentBestPriority = p.m_priority; } return true; } JobPrototype JobPlan::jobForUrl(const IndexedString& url) { foreach(const JobPrototype& job, m_jobs) { if (job.m_url == url) { return job; } } return JobPrototype(); } void JobPlan::updateReady(const IndexedString& url, const ReferencedTopDUContext& /*context*/) { qDebug() << "update ready on " << url.toUrl(); const JobPrototype job = jobForUrl(url); QVERIFY(job.m_url.toUrl().isValid()); if (job.m_flags & ParseJob::RequiresSequentialProcessing) { // ensure that all jobs that respect sequential processing // with lower priority have been run foreach(const JobPrototype& otherJob, m_jobs) { if (otherJob.m_url == job.m_url) { continue; } if (otherJob.m_flags & ParseJob::RespectsSequentialProcessing && otherJob.m_priority < job.m_priority) { QVERIFY(m_finishedJobs.contains(otherJob.m_url)); } } } QVERIFY(!m_finishedJobs.contains(job.m_url)); m_finishedJobs << job.m_url; } void TestBackgroundparser::initTestCase() { AutoTestShell::init(); TestCore* core = TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); TestLanguageController* langController = new TestLanguageController(core); core->setLanguageController(langController); langController->backgroundParser()->setThreadCount(4); langController->backgroundParser()->abortAllJobs(); m_langSupport = new TestLanguageSupport(this); connect(m_langSupport, &TestLanguageSupport::parseJobCreated, &m_jobPlan, &JobPlan::parseJobCreated); langController->addTestLanguage(m_langSupport, QStringList() << QStringLiteral("text/plain")); const auto languages = langController->languagesForUrl(QUrl::fromLocalFile(QStringLiteral("/foo.txt"))); QCOMPARE(languages.size(), 1); QCOMPARE(languages.first(), m_langSupport); } void TestBackgroundparser::cleanupTestCase() { TestCore::shutdown(); m_langSupport = nullptr; } void TestBackgroundparser::init() { m_jobPlan.clear(); } void TestBackgroundparser::testShutdownWithRunningJobs() { m_jobPlan.clear(); // prove that background parsing happens with sequential flags although there is a high-priority // foreground thread (active document being edited, ...) running all the time. // the long-running high-prio job m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile(QStringLiteral("/test_fgt_hp.txt")), -500, ParseJob::IgnoresSequentialProcessing, 1000)); // add parse jobs foreach(const JobPrototype& job, m_jobPlan.m_jobs) { ICore::self()->languageController()->backgroundParser()->addDocument( job.m_url, TopDUContext::Empty, job.m_priority, this, job.m_flags ); } ICore::self()->languageController()->backgroundParser()->parseDocuments(); QTest::qWait(50); // shut down with running jobs, make sure we don't crash cleanupTestCase(); // restart again to restore invariant (core always running in test functions) initTestCase(); } void TestBackgroundparser::testParseOrdering_foregroundThread() { m_jobPlan.clear(); // prove that background parsing happens with sequential flags although there is a high-priority // foreground thread (active document being edited, ...) running all the time. // the long-running high-prio job m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile(QStringLiteral("/test_fgt_hp.txt")), -500, ParseJob::IgnoresSequentialProcessing, 630)); // several small background jobs for ( int i = 0; i < 10; i++ ) { m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test_fgt_lp__" + QString::number(i) + ".txt"), i, ParseJob::FullSequentialProcessing, 40)); } // not enough time if the small jobs run after the large one QVERIFY(m_jobPlan.runJobs(700)); } void TestBackgroundparser::testParseOrdering_noSequentialProcessing() { m_jobPlan.clear(); for ( int i = 0; i < 20; i++ ) { // create jobs with no sequential processing, and different priorities m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test_nsp1__" + QString::number(i) + ".txt"), i, ParseJob::IgnoresSequentialProcessing, i)); } for ( int i = 0; i < 8; i++ ) { // create a few more jobs with the same priority m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test_nsp2__" + QString::number(i) + ".txt"), 10, ParseJob::IgnoresSequentialProcessing, i)); } QVERIFY(m_jobPlan.runJobs(1000)); } void TestBackgroundparser::testParseOrdering_lockup() { m_jobPlan.clear(); for ( int i = 3; i > 0; i-- ) { // add 3 jobs which do not care about sequential processing, at 4 threads it should take no more than 1s to process them m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test" + QString::number(i) + ".txt"), i, ParseJob::IgnoresSequentialProcessing, 200)); } // add one job which requires sequential processing with high priority m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile(QStringLiteral("/test_hp.txt")), -200, ParseJob::FullSequentialProcessing, 200)); // verify that the low-priority nonsequential jobs are run simultaneously with the other one. QVERIFY(m_jobPlan.runJobs(700)); } void TestBackgroundparser::testParseOrdering_simple() { m_jobPlan.clear(); for ( int i = 20; i > 0; i-- ) { // the job with priority i should be at place i in the finished list // (lower priority value -> should be parsed first) ParseJob::SequentialProcessingFlags flags = ParseJob::FullSequentialProcessing; m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test" + QString::number(i) + ".txt"), i, flags)); } // also add a few jobs which ignore the processing for ( int i = 0; i < 5; ++i ) { m_jobPlan.addJob(JobPrototype(QUrl::fromLocalFile("/test2-" + QString::number(i) + ".txt"), BackgroundParser::NormalPriority, ParseJob::IgnoresSequentialProcessing)); } QVERIFY(m_jobPlan.runJobs(1000)); } void TestBackgroundparser::benchmark() { const int jobs = 10000; QVector jobUrls; jobUrls.reserve(jobs); for ( int i = 0; i < jobs; ++i ) { jobUrls << IndexedString("/test" + QString::number(i) + ".txt"); } QBENCHMARK { foreach ( const IndexedString& url, jobUrls ) { ICore::self()->languageController()->backgroundParser()->addDocument(url); } ICore::self()->languageController()->backgroundParser()->parseDocuments(); while ( ICore::self()->languageController()->backgroundParser()->queuedCount() ) { QTest::qWait(50); } } } void TestBackgroundparser::benchmarkDocumentChanges() { KTextEditor::Editor* editor = KTextEditor::Editor::instance(); QVERIFY(editor); KTextEditor::Document* doc = editor->createDocument(this); QVERIFY(doc); QTemporaryFile file; QVERIFY(file.open()); doc->saveAs(QUrl::fromLocalFile(file.fileName())); DocumentChangeTracker tracker(doc); doc->setText(QStringLiteral("hello world")); // required for proper benchmark results - doc->createView(0); + doc->createView(nullptr); QBENCHMARK { for ( int i = 0; i < 5000; i++ ) { { KTextEditor::Document::EditingTransaction t(doc); doc->insertText(KTextEditor::Cursor(0, 0), QStringLiteral("This is a test line.\n")); } QApplication::processEvents(); } } doc->clear(); doc->save(); } // see also: http://bugs.kde.org/355100 void TestBackgroundparser::testNoDeadlockInJobCreation() { m_jobPlan.clear(); // we need to run the background thread first (best priority) const auto runUrl = QUrl::fromLocalFile(QStringLiteral("/lockInRun.txt")); const auto run = IndexedString(runUrl); m_jobPlan.addJob(JobPrototype(runUrl, BackgroundParser::BestPriority, ParseJob::IgnoresSequentialProcessing, 0)); // before handling the foreground code (worst priority) const auto ctorUrl = QUrl::fromLocalFile(QStringLiteral("/lockInCtor.txt")); const auto ctor = IndexedString(ctorUrl); m_jobPlan.addJob(JobPrototype(ctorUrl, BackgroundParser::WorstPriority, ParseJob::IgnoresSequentialProcessing, 0)); // make sure that the background thread has the duchain locked for write QSemaphore semaphoreA; // make sure the foreground thread is inside the parse job ctor QSemaphore semaphoreB; // actually distribute the complicate code across threads to trigger the // deadlock reliably QObject::connect(m_langSupport, &TestLanguageSupport::aboutToCreateParseJob, m_langSupport, [&] (const IndexedString& url, ParseJob** job) { if (url == run) { auto testJob = new TestParseJob(url, m_langSupport); testJob->run_callback = [&] (const IndexedString& url) { // this is run in the background parse thread DUChainWriteLocker lock; semaphoreA.release(); // sync with the foreground parse job ctor semaphoreB.acquire(); // this is acquiring the background parse lock // we want to support this order - i.e. DUChain -> Background Parser ICore::self()->languageController()->backgroundParser()->isQueued(url); }; *job = testJob; } else if (url == ctor) { // this is run in the foreground, essentially the same // as code run within the parse job ctor semaphoreA.acquire(); semaphoreB.release(); // Note how currently, the background parser is locked while creating a parse job // thus locking the duchain here used to trigger a lock order inversion DUChainReadLocker lock; *job = new TestParseJob(url, m_langSupport); } }, Qt::DirectConnection); // should be able to run quickly, if no deadlock occurs QVERIFY(m_jobPlan.runJobs(500)); } diff --git a/language/backgroundparser/tests/testparsejob.cpp b/language/backgroundparser/tests/testparsejob.cpp index 66c921ea56..ba202cfd72 100644 --- a/language/backgroundparser/tests/testparsejob.cpp +++ b/language/backgroundparser/tests/testparsejob.cpp @@ -1,55 +1,55 @@ /* * This file is part of KDevelop * * Copyright 2012 by Sven Brauch * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testparsejob.h" #include TestParseJob::TestParseJob(const IndexedString& url, ILanguageSupport* languageSupport) : ParseJob(url, languageSupport) , duration_ms(0) { } void TestParseJob::run(ThreadWeaver::JobPointer, ThreadWeaver::Thread*) { qDebug() << "Running parse job for" << document(); if (run_callback) { run_callback(document()); } if (duration_ms) { qDebug() << "waiting" << duration_ms << "ms"; QTest::qWait(duration_ms); } } ControlFlowGraph* TestParseJob::controlFlowGraph() { - return 0; + return nullptr; } DataAccessRepository* TestParseJob::dataAccessInformation() { - return 0; + return nullptr; } diff --git a/language/checks/controlflownode.cpp b/language/checks/controlflownode.cpp index 78a69dbb02..f65d3b76cb 100644 --- a/language/checks/controlflownode.cpp +++ b/language/checks/controlflownode.cpp @@ -1,80 +1,80 @@ /* 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 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 "controlflownode.h" using namespace KDevelop; ControlFlowNode::ControlFlowNode() - : m_conditionRange(RangeInRevision::invalid()), m_next(0), m_alternative(0) + : m_conditionRange(RangeInRevision::invalid()), m_next(nullptr), m_alternative(nullptr) {} ControlFlowNode::Type ControlFlowNode::type() const { Q_ASSERT(!m_alternative || m_next); //If we have alternative, we have next. if(m_next && m_alternative) return Conditional; else if(m_next) return Sequential; else return Exit; } void ControlFlowNode::setConditionRange(const RangeInRevision& range) { Q_ASSERT(!range.isValid() || range.end>=range.start); m_conditionRange = range; } void ControlFlowNode::setStartCursor(const CursorInRevision& cursor) { m_nodeRange.start = cursor; } void ControlFlowNode::setEndCursor(const CursorInRevision& cursor) { m_nodeRange.end = cursor; } void ControlFlowNode::setNext(ControlFlowNode* next) { m_next = next; } void ControlFlowNode::setAlternative(ControlFlowNode* alt) { m_alternative=alt; } ControlFlowNode* ControlFlowNode::next() const { return m_next; } ControlFlowNode* ControlFlowNode::alternative() const { return m_alternative; } RangeInRevision ControlFlowNode::nodeRange() const { return m_nodeRange; } RangeInRevision ControlFlowNode::conditionRange() const { return m_conditionRange; } diff --git a/language/checks/dataaccessrepository.cpp b/language/checks/dataaccessrepository.cpp index c7011e2ee5..04b592018b 100644 --- a/language/checks/dataaccessrepository.cpp +++ b/language/checks/dataaccessrepository.cpp @@ -1,74 +1,74 @@ /* 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 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 "dataaccessrepository.h" #include namespace KDevelop { class DataAccessRepository::Private { public: QList m_modifications; }; DataAccessRepository::DataAccessRepository() : d(new Private) {} DataAccessRepository::~DataAccessRepository() { clear(); delete d; } void DataAccessRepository::addModification(const CursorInRevision& cursor, DataAccess::DataAccessFlags flags, const KDevelop::RangeInRevision& range) { Q_ASSERT(!range.isValid() || flags == DataAccess::Write); d->m_modifications.append(new DataAccess(cursor, flags, range)); } void DataAccessRepository::clear() { qDeleteAll(d->m_modifications); d->m_modifications.clear(); } QList< DataAccess* > DataAccessRepository::modifications() const { return d->m_modifications; } DataAccess* DataAccessRepository::accessAt(const CursorInRevision& cursor) const { foreach(DataAccess* a, d->m_modifications) { if(a->pos() == cursor) return a; } - return 0; + return nullptr; } QList DataAccessRepository::accessesInRange(const RangeInRevision& range) const { QList ret; foreach(DataAccess* a, d->m_modifications) { if(range.contains(a->pos())) ret+=a; } return ret; } } diff --git a/language/classmodel/classmodel.cpp b/language/classmodel/classmodel.cpp index 67b35ff7fe..cf2603111c 100644 --- a/language/classmodel/classmodel.cpp +++ b/language/classmodel/classmodel.cpp @@ -1,278 +1,278 @@ /* * KDevelop Class Browser * * Copyright 2007-2008 Hamish Rodda * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classmodel.h" #include "classmodelnode.h" #include "allclassesfolder.h" #include "projectfolder.h" #include "../duchain/declaration.h" #include #include "../../interfaces/icore.h" #include "../../interfaces/iproject.h" #include "../../interfaces/iprojectcontroller.h" using namespace KDevelop; using namespace ClassModelNodes; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// NodesModelInterface::~NodesModelInterface() { } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ClassModel::ClassModel() : m_features(NodesModelInterface::AllProjectsClasses | NodesModelInterface::BaseAndDerivedClasses | NodesModelInterface::ClassInternals) { m_topNode = new FolderNode(QStringLiteral("Top Node"), this); if ( features().testFlag(NodesModelInterface::AllProjectsClasses) ) { m_allClassesNode = new FilteredAllClassesFolder(this); m_topNode->addNode( m_allClassesNode ); } connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &ClassModel::removeProjectNode); connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &ClassModel::addProjectNode); foreach ( IProject* project, ICore::self()->projectController()->projects() ) { addProjectNode(project); } } ClassModel::~ClassModel() { delete m_topNode; } void ClassModel::updateFilterString(QString a_newFilterString) { m_allClassesNode->updateFilterString(a_newFilterString); foreach ( ClassModelNodes::FilteredProjectFolder* folder, m_projectNodes ) { folder->updateFilterString(a_newFilterString); } } void ClassModel::collapsed(const QModelIndex& index) { Node* node = static_cast(index.internalPointer()); node->collapse(); } void ClassModel::expanded(const QModelIndex& index) { Node* node = static_cast(index.internalPointer()); node->expand(); } QFlags< Qt::ItemFlag > ClassModel::flags(const QModelIndex&) const { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } int ClassModel::rowCount(const QModelIndex& parent) const { Node* node = m_topNode; if ( parent.isValid() ) node = static_cast(parent.internalPointer()); return node->getChildren().size(); } QVariant ClassModel::data(const QModelIndex& index, int role) const { if ( !index.isValid() ) return QVariant(); Node* node = static_cast(index.internalPointer()); if ( role == Qt::DisplayRole ) return node->displayName(); if ( role == Qt::DecorationRole ) { QIcon icon = node->getCachedIcon(); return icon.isNull() ? QVariant() : icon; } return QVariant(); } QVariant ClassModel::headerData(int, Qt::Orientation, int role) const { if ( role == Qt::DisplayRole ) return "Class"; return QVariant(); } int ClassModel::columnCount(const QModelIndex&) const { return 1; } bool ClassModel::hasChildren(const QModelIndex& parent) const { if ( !parent.isValid() ) return true; Node* node = static_cast(parent.internalPointer()); return node->hasChildren(); } QModelIndex ClassModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || column != 0) return QModelIndex(); Node* node = m_topNode; if ( parent.isValid() ) node = static_cast(parent.internalPointer()); if ( row >= node->getChildren().size() ) return QModelIndex(); return index(node->getChildren()[row]); } QModelIndex ClassModel::parent(const QModelIndex& childIndex) const { if ( !childIndex.isValid() ) return QModelIndex(); Node* childNode = static_cast(childIndex.internalPointer()); if ( childNode->getParent() == m_topNode ) return QModelIndex(); return index( childNode->getParent() ); } QModelIndex ClassModel::index(ClassModelNodes::Node* a_node) const { if (!a_node) { return QModelIndex(); } // If no parent exists, we have an invalid index (root node or not part of a model). - if ( a_node->getParent() == 0 ) + if ( a_node->getParent() == nullptr ) return QModelIndex(); return createIndex(a_node->row(), 0, a_node); } KDevelop::DUChainBase* ClassModel::duObjectForIndex(const QModelIndex& a_index) { if ( !a_index.isValid() ) - return 0; + return nullptr; Node* node = static_cast(a_index.internalPointer()); if ( IdentifierNode* identifierNode = dynamic_cast(node) ) return identifierNode->getDeclaration(); // Non was found. - return 0; + return nullptr; } QModelIndex ClassModel::getIndexForIdentifier(const KDevelop::IndexedQualifiedIdentifier& a_id) { ClassNode* node = m_allClassesNode->findClassNode(a_id); - if ( node == 0 ) + if ( node == nullptr ) return QModelIndex(); return index(node); } void ClassModel::nodesLayoutAboutToBeChanged(ClassModelNodes::Node*) { emit layoutAboutToBeChanged(); } void ClassModel::nodesLayoutChanged(ClassModelNodes::Node*) { QModelIndexList oldIndexList = persistentIndexList(); QModelIndexList newIndexList; foreach(const QModelIndex& oldIndex, oldIndexList) { Node* node = static_cast(oldIndex.internalPointer()); if ( node ) { // Re-map the index. newIndexList << createIndex(node->row(), 0, node); } else newIndexList << oldIndex; } changePersistentIndexList(oldIndexList, newIndexList); emit layoutChanged(); } void ClassModel::nodesRemoved(ClassModelNodes::Node* a_parent, int a_first, int a_last) { beginRemoveRows(index(a_parent), a_first, a_last); endRemoveRows(); } void ClassModel::nodesAboutToBeAdded(ClassModelNodes::Node* a_parent, int a_pos, int a_size) { beginInsertRows(index(a_parent), a_pos, a_pos + a_size - 1); } void ClassModel::nodesAdded(ClassModelNodes::Node*) { endInsertRows(); } void ClassModel::addProjectNode( IProject* project ) { m_projectNodes[project] = new ClassModelNodes::FilteredProjectFolder(this, project); nodesLayoutAboutToBeChanged(m_projectNodes[project]); m_topNode->addNode(m_projectNodes[project]); nodesLayoutChanged(m_projectNodes[project]); } void ClassModel::removeProjectNode( IProject* project ) { m_topNode->removeNode(m_projectNodes[project]); m_projectNodes.remove(project); } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/classmodel/classmodelnode.cpp b/language/classmodel/classmodelnode.cpp index 6ac0bf6eec..aff09c1ca5 100644 --- a/language/classmodel/classmodelnode.cpp +++ b/language/classmodel/classmodelnode.cpp @@ -1,610 +1,610 @@ /* * KDevelop Class Browser * * Copyright 2007-2009 Hamish Rodda * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classmodelnode.h" #include "debug.h" #include #include #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/persistentsymboltable.h" #include "../duchain/duchainutils.h" #include "../duchain/classdeclaration.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumerationtype.h" #include "util/debug.h" using namespace KDevelop; using namespace ClassModelNodes; IdentifierNode::IdentifierNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model, const QString& a_displayName) : DynamicNode(a_displayName.isEmpty() ? a_decl->identifier().toString() : a_displayName, a_model) , m_identifier(a_decl->qualifiedIdentifier()) , m_indexedDeclaration(a_decl) , m_cachedDeclaration(a_decl) { } Declaration* IdentifierNode::getDeclaration() { if ( !m_cachedDeclaration ) m_cachedDeclaration = m_indexedDeclaration.declaration(); return m_cachedDeclaration.data(); } bool IdentifierNode::getIcon(QIcon& a_resultIcon) { DUChainReadLocker readLock(DUChain::lock()); Declaration* decl = getDeclaration(); if ( decl ) a_resultIcon = DUChainUtils::iconForDeclaration(decl); return !a_resultIcon.isNull(); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// EnumNode::EnumNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { // Set display name for anonymous enums if ( m_displayName.isEmpty() ) m_displayName = QStringLiteral("*Anonymous*"); } bool EnumNode::getIcon(QIcon& a_resultIcon) { DUChainReadLocker readLock(DUChain::lock()); ClassMemberDeclaration* decl = dynamic_cast(getDeclaration()); - if ( decl == 0 ) + if ( decl == nullptr ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("enum")); a_resultIcon = Icon; } else { if ( decl->accessPolicy() == Declaration::Protected ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("protected_enum")); a_resultIcon = Icon; } else if ( decl->accessPolicy() == Declaration::Private ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("private_enum")); a_resultIcon = Icon; } else { static QIcon Icon = QIcon::fromTheme(QStringLiteral("enum")); a_resultIcon = Icon; } } return true; } void EnumNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); Declaration* decl = getDeclaration(); if ( decl->internalContext() ) foreach( Declaration* enumDecl, decl->internalContext()->localDeclarations() ) addNode( new EnumNode(enumDecl, m_model) ); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ClassNode::ClassNode(Declaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { } ClassNode::~ClassNode() { if ( !m_cachedUrl.isEmpty() ) { ClassModelNodesController::self().unregisterForChanges(m_cachedUrl, this); m_cachedUrl = IndexedString(); } } void ClassNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); if ( m_model->features().testFlag(NodesModelInterface::ClassInternals) ) { if ( updateClassDeclarations() ) { m_cachedUrl = getDeclaration()->url(); ClassModelNodesController::self().registerForChanges(m_cachedUrl, this); } } // Add special folders if (m_model->features().testFlag(NodesModelInterface::BaseAndDerivedClasses)) addBaseAndDerived(); } template <> inline bool qMapLessThanKey(const IndexedIdentifier &key1, const IndexedIdentifier &key2) { return key1.getIndex() < key2.getIndex(); } bool ClassNode::updateClassDeclarations() { bool hadChanges = false; SubIdentifiersMap existingIdentifiers = m_subIdentifiers; ClassDeclaration* klass = dynamic_cast(getDeclaration()); if ( klass ) { foreach(Declaration* decl, klass->internalContext()->localDeclarations()) { // Ignore forward declarations. if ( decl->isForwardDeclaration() ) continue; // Don't add existing declarations. if ( existingIdentifiers.contains( decl->ownIndex() ) ) { existingIdentifiers.remove(decl->ownIndex()); continue; } - Node* newNode = 0; + Node* newNode = nullptr; if ( EnumerationType::Ptr enumType = decl->type() ) newNode = new EnumNode( decl, m_model ); else if ( decl->isFunctionDeclaration() ) newNode = new FunctionNode( decl, m_model ); else if ( ClassDeclaration* classDecl = dynamic_cast(decl) ) newNode = new ClassNode(classDecl, m_model); else if ( ClassMemberDeclaration* memDecl = dynamic_cast(decl) ) newNode = new ClassMemberNode( memDecl, m_model ); else { // Debug - for reference. qCDebug(LANGUAGE) << "class: " << klass->toString() << "name: " << decl->toString() << " - unknown declaration type: " << typeid(*decl).name(); } if ( newNode ) { addNode(newNode); // Also remember the identifier. m_subIdentifiers.insert(decl->ownIndex(), newNode); hadChanges = true; } } } // Remove old existing identifiers for ( SubIdentifiersMap::iterator iter = existingIdentifiers.begin(); iter != existingIdentifiers.end(); ++iter ) { iter.value()->removeSelf(); m_subIdentifiers.remove(iter.key()); hadChanges = true; } return hadChanges; } bool ClassNode::addBaseAndDerived() { bool added = false; BaseClassesFolderNode *baseClassesNode = new BaseClassesFolderNode( m_model ); addNode( baseClassesNode ); if ( !baseClassesNode->hasChildren() ) removeNode( baseClassesNode ); else added = true; DerivedClassesFolderNode *derivedClassesNode = new DerivedClassesFolderNode( m_model ); addNode( derivedClassesNode ); if ( !derivedClassesNode->hasChildren() ) removeNode( derivedClassesNode ); else added = true; return added; } void ClassNode::nodeCleared() { if ( !m_cachedUrl.isEmpty() ) { ClassModelNodesController::self().unregisterForChanges(m_cachedUrl, this); m_cachedUrl = IndexedString(); } m_subIdentifiers.clear(); } void ClassModelNodes::ClassNode::documentChanged(const KDevelop::IndexedString&) { DUChainReadLocker readLock(DUChain::lock()); if ( updateClassDeclarations() ) recursiveSort(); } ClassNode* ClassNode::findSubClass(const KDevelop::IndexedQualifiedIdentifier& a_id) { // Make sure we have sub nodes. performPopulateNode(); /// @todo This is slow - we go over all the sub identifiers but the assumption is that /// this function call is rare and the list is not that long. foreach(Node* item, m_subIdentifiers) { ClassNode* classNode = dynamic_cast(item); - if ( classNode == 0 ) + if ( classNode == nullptr ) continue; if ( classNode->getIdentifier() == a_id ) return classNode; } - return 0; + return nullptr; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// FunctionNode::FunctionNode(Declaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { // Append the argument signature to the identifier's name (which is what the displayName is. if (FunctionType::Ptr type = a_decl->type()) m_displayName += type->partToString(FunctionType::SignatureArguments); // Add special values for ctor / dtor to sort first ClassFunctionDeclaration* classmember = dynamic_cast(a_decl); if ( classmember ) { if ( classmember->isConstructor() || classmember->isDestructor() ) m_sortableString = '0' + m_displayName; else m_sortableString = '1' + m_displayName; } else { m_sortableString = m_displayName; } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ClassMemberNode::ClassMemberNode(KDevelop::ClassMemberDeclaration* a_decl, NodesModelInterface* a_model) : IdentifierNode(a_decl, a_model) { } bool ClassMemberNode::getIcon(QIcon& a_resultIcon) { DUChainReadLocker readLock(DUChain::lock()); ClassMemberDeclaration* decl = dynamic_cast(getDeclaration()); - if ( decl == 0 ) + if ( decl == nullptr ) return false; if ( decl->isTypeAlias() ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("typedef")); a_resultIcon = Icon; } else if ( decl->accessPolicy() == Declaration::Protected ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("protected_field")); a_resultIcon = Icon; } else if ( decl->accessPolicy() == Declaration::Private ) { static QIcon Icon = QIcon::fromTheme(QStringLiteral("private_field")); a_resultIcon = Icon; } else { static QIcon Icon = QIcon::fromTheme(QStringLiteral("field")); a_resultIcon = Icon; } return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DynamicFolderNode::DynamicFolderNode(const QString& a_displayName, NodesModelInterface* a_model) : DynamicNode(a_displayName, a_model) { } bool DynamicFolderNode::getIcon(QIcon& a_resultIcon) { static QIcon folderIcon = QIcon::fromTheme(QStringLiteral("folder")); a_resultIcon = folderIcon; return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// FolderNode::FolderNode(const QString& a_displayName, NodesModelInterface* a_model) : Node(a_displayName, a_model) { } bool FolderNode::getIcon(QIcon& a_resultIcon) { static QIcon folderIcon = QIcon::fromTheme(QStringLiteral("folder")); a_resultIcon = folderIcon; return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// BaseClassesFolderNode::BaseClassesFolderNode(NodesModelInterface* a_model) : DynamicFolderNode(i18n("Base classes"), a_model) { } void BaseClassesFolderNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); ClassDeclaration* klass = dynamic_cast( static_cast(getParent())->getDeclaration() ); if ( klass ) { // I use the imports instead of the baseClasses in the ClassDeclaration because I need // to get to the base class identifier which is not directly accessible through the // baseClasses function. foreach( const DUContext::Import& import, klass->internalContext()->importedParentContexts() ) { DUContext* baseContext = import.context( klass->topContext() ); if ( baseContext && baseContext->type() == DUContext::Class ) { Declaration* baseClassDeclaration = baseContext->owner(); if ( baseClassDeclaration ) { // Add the base class. addNode( new ClassNode(baseClassDeclaration, m_model) ); } } } } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DerivedClassesFolderNode::DerivedClassesFolderNode(NodesModelInterface* a_model) : DynamicFolderNode(i18n("Derived classes"), a_model) { } void DerivedClassesFolderNode::populateNode() { DUChainReadLocker readLock(DUChain::lock()); ClassDeclaration* klass = dynamic_cast( static_cast(getParent())->getDeclaration() ); if ( klass ) { uint steps = 10000; QList< Declaration* > inheriters = DUChainUtils::getInheriters(klass, steps, true); foreach( Declaration* decl, inheriters ) { addNode( new ClassNode(decl, m_model) ); } } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// Node::Node(const QString& a_displayName, NodesModelInterface* a_model) - : m_parentNode(0) + : m_parentNode(nullptr) , m_displayName(a_displayName) , m_model(a_model) { } Node::~Node() { // Notify the model about the removal of this nodes' children. if ( !m_children.empty() && m_model ) m_model->nodesRemoved(this, 0, m_children.size()-1); clear(); } void Node::clear() { qDeleteAll(m_children); m_children.clear(); } void Node::addNode(Node* a_child) { /// @note This is disabled for performance reasons - we add them to the bottom and a /// sort usually follows which causes a layout change to be fired. // m_model->nodesAboutToBeAdded(this, m_children.size(), 1); a_child->m_parentNode = this; m_children.push_back(a_child); // m_model->nodesAdded(this); } void Node::removeNode(Node* a_child) { int row = a_child->row(); m_children.removeAt(row); m_model->nodesRemoved(this, row, row ); delete a_child; } // Sort algorithm for the nodes. struct SortNodesFunctor { bool operator() (Node* a_lhs, Node* a_rhs) { if ( a_lhs->getScore() == a_rhs->getScore() ) { return a_lhs->getSortableString() < a_rhs->getSortableString(); } else return a_lhs->getScore() < a_rhs->getScore(); } }; void Node::recursiveSortInternal() { // Sort my nodes. std::sort(m_children.begin(), m_children.end(), SortNodesFunctor()); // Tell each node to sort it self. foreach (Node* node, m_children) node->recursiveSortInternal(); } void Node::recursiveSort() { m_model->nodesLayoutAboutToBeChanged(this); recursiveSortInternal(); m_model->nodesLayoutChanged(this); } int Node::row() { - if ( m_parentNode == 0 ) + if ( m_parentNode == nullptr ) return -1; return m_parentNode->m_children.indexOf(this); } QIcon ClassModelNodes::Node::getCachedIcon() { // Load the cached icon if it's null. if ( m_cachedIcon.isNull() ) { if ( !getIcon(m_cachedIcon) ) m_cachedIcon = QIcon(); } return m_cachedIcon; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DynamicNode::DynamicNode(const QString& a_displayName, NodesModelInterface* a_model) : Node(a_displayName, a_model) , m_populated(false) { } void DynamicNode::collapse() { performNodeCleanup(); } void DynamicNode::expand() { performPopulateNode(); } void DynamicNode::performNodeCleanup() { if ( !m_populated ) return; if ( !m_children.empty() ) { // Notify model for this node. m_model->nodesRemoved(this, 0, m_children.size()-1); } // Clear sub-nodes. clear(); // This shouldn't be called from clear since clear is called also from the d-tor // and the function is virtual. nodeCleared(); // Mark the fact that we've been collapsed m_populated = false; } void DynamicNode::performPopulateNode(bool a_forceRepopulate) { if ( m_populated ) { if ( a_forceRepopulate ) performNodeCleanup(); else return; } populateNode(); // We're populated. m_populated = true; // Sort the list. recursiveSort(); } bool DynamicNode::hasChildren() const { // To get a true status, we'll need to populate the node. const_cast(this)->performPopulateNode(); return !m_children.empty(); } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/classmodel/documentclassesfolder.cpp b/language/classmodel/documentclassesfolder.cpp index 56a8a611ec..7fee4272c8 100644 --- a/language/classmodel/documentclassesfolder.cpp +++ b/language/classmodel/documentclassesfolder.cpp @@ -1,451 +1,451 @@ /* * KDevelop Class Browser * * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentclassesfolder.h" #include "../duchain/declaration.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/persistentsymboltable.h" #include "../duchain/codemodel.h" #include #include #include using namespace KDevelop; using namespace ClassModelNodes; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Contains a static list of classes within the namespace. class ClassModelNodes::StaticNamespaceFolderNode : public Node { public: StaticNamespaceFolderNode(const KDevelop::QualifiedIdentifier& a_identifier, NodesModelInterface* a_model); /// Returns the qualified identifier for this node const KDevelop::QualifiedIdentifier& qualifiedIdentifier() const { return m_identifier; } public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; int getScore() const override { return 101; } private: /// The namespace identifier. KDevelop::QualifiedIdentifier m_identifier; }; StaticNamespaceFolderNode::StaticNamespaceFolderNode(const KDevelop::QualifiedIdentifier& a_identifier, NodesModelInterface* a_model) : Node(a_identifier.last().toString(), a_model) , m_identifier(a_identifier) { } bool StaticNamespaceFolderNode::getIcon(QIcon& a_resultIcon) { static QIcon folderIcon = QIcon::fromTheme(QStringLiteral("namespace")); a_resultIcon = folderIcon; return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DocumentClassesFolder::OpenedFileClassItem::OpenedFileClassItem(const KDevelop::IndexedString& a_file, const KDevelop::IndexedQualifiedIdentifier& a_classIdentifier, ClassModelNodes::ClassNode* a_nodeItem) : file(a_file) , classIdentifier(a_classIdentifier) , nodeItem(a_nodeItem) { } DocumentClassesFolder::DocumentClassesFolder(const QString& a_displayName, NodesModelInterface* a_model) : DynamicFolderNode(a_displayName, a_model) , m_updateTimer( new QTimer(this) ) { connect( m_updateTimer, &QTimer::timeout, this, &DocumentClassesFolder::updateChangedFiles); } void DocumentClassesFolder::updateChangedFiles() { bool hadChanges = false; // re-parse changed documents. foreach( const IndexedString& file, m_updatedFiles ) { // Make sure it's one of the monitored files. if ( m_openFiles.contains(file) ) hadChanges |= updateDocument(file); } // Processed all files. m_updatedFiles.clear(); // Sort if had changes. if ( hadChanges ) recursiveSort(); } void DocumentClassesFolder::nodeCleared() { // Clear cached namespaces list (node was cleared). m_namespaces.clear(); // Clear open files and classes list m_openFiles.clear(); m_openFilesClasses.clear(); // Stop the update timer. m_updateTimer->stop(); } void DocumentClassesFolder::populateNode() { // Start updates timer - this is the required delay. m_updateTimer->start(2000); } QSet< KDevelop::IndexedString > DocumentClassesFolder::getAllOpenDocuments() { return m_openFiles; } ClassNode* DocumentClassesFolder::findClassNode(const IndexedQualifiedIdentifier& a_id) { // Make sure that the classes node is populated, otherwise // the lookup will not work. performPopulateNode(); ClassIdentifierIterator iter = m_openFilesClasses.get().find(a_id); if ( iter == m_openFilesClasses.get().end() ) - return 0; + return nullptr; // If the node is invisible - make it visible by going over the identifiers list. - if ( iter->nodeItem == 0 ) + if ( iter->nodeItem == nullptr ) { QualifiedIdentifier qualifiedIdentifier = a_id.identifier(); // Ignore zero length identifiers. if ( qualifiedIdentifier.count() == 0 ) - return 0; + return nullptr; - ClassNode* closestNode = 0; + ClassNode* closestNode = nullptr; int closestNodeIdLen = qualifiedIdentifier.count(); // First find the closest visible class node by reverse iteration over the id list. - while ( (closestNodeIdLen > 0) && (closestNode == 0) ) + while ( (closestNodeIdLen > 0) && (closestNode == nullptr) ) { // Omit one from the end. --closestNodeIdLen; // Find the closest class. closestNode = findClassNode(qualifiedIdentifier.mid(0, closestNodeIdLen)); } - if ( closestNode != 0 ) + if ( closestNode != nullptr ) { // Start iterating forward from this node by exposing each class. // By the end of this loop, closestNode should hold the actual node. while ( closestNode && (closestNodeIdLen < qualifiedIdentifier.count()) ) { // Try the next Id. ++closestNodeIdLen; closestNode = closestNode->findSubClass(qualifiedIdentifier.mid(0, closestNodeIdLen)); } } return closestNode; } return iter->nodeItem; } void DocumentClassesFolder::closeDocument(const IndexedString& a_file) { // Get list of nodes associated with this file and remove them. std::pair< FileIterator, FileIterator > range = m_openFilesClasses.get().equal_range( a_file ); if ( range.first != m_openFilesClasses.get().end() ) { BOOST_FOREACH( const OpenedFileClassItem& item, range ) { if ( item.nodeItem ) removeClassNode(item.nodeItem); } // Clear the lists m_openFilesClasses.get().erase(range.first, range.second); } // Clear the file from the list of monitored documents. m_openFiles.remove(a_file); } bool DocumentClassesFolder::updateDocument(const KDevelop::IndexedString& a_file) { uint codeModelItemCount = 0; const CodeModelItem* codeModelItems; CodeModel::self().items(a_file, codeModelItemCount, codeModelItems); // List of declared namespaces in this file. QSet< QualifiedIdentifier > declaredNamespaces; // List of removed classes - it initially contains all the known classes, we'll eliminate them // one by one later on when we encounter them in the document. QMap< IndexedQualifiedIdentifier, FileIterator > removedClasses; { std::pair< FileIterator, FileIterator > range = m_openFilesClasses.get().equal_range( a_file ); for ( FileIterator iter = range.first; iter != range.second; ++iter ) { removedClasses.insert(iter->classIdentifier, iter); } } bool documentChanged = false; for(uint codeModelItemIndex = 0; codeModelItemIndex < codeModelItemCount; ++codeModelItemIndex) { const CodeModelItem& item = codeModelItems[codeModelItemIndex]; // Don't insert unknown or forward declarations into the class browser if ( (item.kind & CodeModelItem::Unknown) || (item.kind & CodeModelItem::ForwardDeclaration) ) continue; KDevelop::QualifiedIdentifier id = item.id.identifier(); // Don't add empty identifiers. if ( id.count() == 0 ) continue; // If it's a namespace, create it in the list. if ( item.kind & CodeModelItem::Namespace ) { // This should create the namespace folder and add it to the cache. getNamespaceFolder(id); // Add to the locally created namespaces. declaredNamespaces.insert(id); } else if ( item.kind & CodeModelItem::Class ) { // Ignore empty unnamed classes. if ( id.last().toString().isEmpty() ) continue; // See if it matches our filter? if ( isClassFiltered(id) ) continue; // Is this a new class or an existing class? if ( removedClasses.contains(id) ) { // It already exist - remove it from the known classes and continue. removedClasses.remove(id); continue; } // Where should we put this class? - Node* parentNode = 0; + Node* parentNode = nullptr; // Check if it's namespaced and add it to the proper namespace. if ( id.count() > 1 ) { QualifiedIdentifier parentIdentifier(id.left(-1)); // Look up the namespace in the cache. // If we fail to find it we assume that the parent context is a class // and in that case, when the parent class gets expanded, it will show it. NamespacesMap::iterator iter = m_namespaces.find(parentIdentifier); if ( iter != m_namespaces.end() ) { // Add to the namespace node. parentNode = iter.value(); } else { // Reaching here means we didn't encounter any namespace declaration in the document // But a class might still be declared under a namespace. // So we'll perform a more through search to see if it's under a namespace. DUChainReadLocker readLock(DUChain::lock()); uint declsCount = 0; const IndexedDeclaration* decls; PersistentSymbolTable::self().declarations(parentIdentifier, declsCount, decls); for ( uint i = 0; i < declsCount; ++i ) { // Look for the first valid declaration. if ( decls->declaration() ) { // See if it should be namespaced. if ( decls->declaration()->kind() == Declaration::Namespace ) { // This should create the namespace folder and add it to the cache. parentNode = getNamespaceFolder(parentIdentifier); // Add to the locally created namespaces. declaredNamespaces.insert(parentIdentifier); } break; } } } } else { // Add to the main root. parentNode = this; } - ClassNode* newNode = 0; - if ( parentNode != 0 ) + ClassNode* newNode = nullptr; + if ( parentNode != nullptr ) { // Create the new node and add it. IndexedDeclaration decl; uint count = 0; const IndexedDeclaration* declarations; DUChainReadLocker lock; PersistentSymbolTable::self().declarations(item.id, count, declarations); for ( uint i = 0; i < count; ++i ) { if (declarations[i].indexedTopContext().url() == a_file) { decl = declarations[i]; break; } } if (decl.isValid()) { newNode = new ClassNode(decl.declaration(), m_model); parentNode->addNode( newNode ); } } // Insert it to the map - newNode can be 0 - meaning the class is hidden. m_openFilesClasses.insert( OpenedFileClassItem( a_file, id, newNode ) ); documentChanged = true; } } // Remove empty namespaces from the list. // We need this because when a file gets unloaded, we unload the declared classes in it // and if a namespace has no class in it, it'll forever exist and no one will remove it // from the children list. foreach( const QualifiedIdentifier& id, declaredNamespaces ) removeEmptyNamespace(id); // Clear erased classes. foreach( const FileIterator item, removedClasses ) { if ( item->nodeItem ) removeClassNode(item->nodeItem); m_openFilesClasses.get().erase(item); documentChanged = true; } return documentChanged; } void DocumentClassesFolder::parseDocument(const IndexedString& a_file) { // Add the document to the list of open files - this means we monitor it. if ( !m_openFiles.contains(a_file) ) m_openFiles.insert(a_file); updateDocument(a_file); } void DocumentClassesFolder::removeClassNode(ClassModelNodes::ClassNode* a_node) { // Get the parent namespace identifier. QualifiedIdentifier parentNamespaceIdentifier; if ( auto namespaceParent = dynamic_cast(a_node->getParent()) ) { parentNamespaceIdentifier = namespaceParent->qualifiedIdentifier(); } // Remove the node. a_node->removeSelf(); // Remove empty namespace removeEmptyNamespace(parentNamespaceIdentifier); } void DocumentClassesFolder::removeEmptyNamespace(const QualifiedIdentifier& a_identifier) { // Stop condition. if ( a_identifier.count() == 0 ) return; // Look it up in the cache. NamespacesMap::iterator iter = m_namespaces.find(a_identifier); if ( iter != m_namespaces.end() ) { if ( !(*iter)->hasChildren() ) { // Remove this node and try to remove the parent node. QualifiedIdentifier parentIdentifier = (*iter)->qualifiedIdentifier().left(-1); (*iter)->removeSelf(); m_namespaces.remove(a_identifier); removeEmptyNamespace(parentIdentifier); } } } StaticNamespaceFolderNode* DocumentClassesFolder::getNamespaceFolder(const KDevelop::QualifiedIdentifier& a_identifier) { // Stop condition. if ( a_identifier.count() == 0 ) - return 0; + return nullptr; // Look it up in the cache. NamespacesMap::iterator iter = m_namespaces.find(a_identifier); if ( iter == m_namespaces.end() ) { // It's not in the cache - create folders up to it. Node* parentNode = getNamespaceFolder(a_identifier.left(-1)); - if ( parentNode == 0 ) + if ( parentNode == nullptr ) parentNode = this; // Create the new node. StaticNamespaceFolderNode* newNode = new StaticNamespaceFolderNode(a_identifier, m_model); parentNode->addNode( newNode ); // Add it to the cache. m_namespaces.insert( a_identifier, newNode ); // Return the result. return newNode; } else return *iter; } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/classmodel/projectfolder.cpp b/language/classmodel/projectfolder.cpp index 34c6657e80..12af350616 100644 --- a/language/classmodel/projectfolder.cpp +++ b/language/classmodel/projectfolder.cpp @@ -1,97 +1,97 @@ /* 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 "projectfolder.h" #include "../../interfaces/iproject.h" #include "../../serialization/indexedstring.h" #include using namespace KDevelop; using namespace ClassModelNodes; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ProjectFolder::ProjectFolder( NodesModelInterface* a_model, IProject* project ) : DocumentClassesFolder( i18n("Classes in project %1", project->name()), a_model ) , m_project(project) { } ProjectFolder::ProjectFolder( NodesModelInterface* a_model ) : DocumentClassesFolder( QLatin1String(""), a_model ) - , m_project( 0 ) + , m_project( nullptr ) { } void ProjectFolder::populateNode() { foreach( const IndexedString &file, m_project->fileSet() ) { parseDocument(file); } recursiveSort(); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// FilteredProjectFolder::FilteredProjectFolder(NodesModelInterface* a_model, IProject* project) : ProjectFolder(a_model, project) { } void FilteredProjectFolder::updateFilterString(QString a_newFilterString) { m_filterString = a_newFilterString; if ( isPopulated() ) { #if 1 // Choose speed over correctness. // Close the node and re-open it should be quicker than reload each document // and remove indevidual nodes (at the cost of loosing the current selection). performPopulateNode(true); #else bool hadChanges = false; // Reload the documents. foreach( const IndexedString& file, getAllOpenDocuments() ) hadChanges |= updateDocument(file); // Sort if we've updated documents. if ( hadChanges ) recursiveSort(); else { // If nothing changed, the title changed so mark the node as updated. m_model->nodesLayoutAboutToBeChanged(this); m_model->nodesLayoutChanged(this); } #endif } else { // Displayed name changed only... m_model->nodesLayoutAboutToBeChanged(this); m_model->nodesLayoutChanged(this); } } bool FilteredProjectFolder::isClassFiltered(const KDevelop::QualifiedIdentifier& a_id) { return !a_id.last().toString().contains(m_filterString, Qt::CaseInsensitive); } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/codecompletion/codecompletioncontext.cpp b/language/codecompletion/codecompletioncontext.cpp index 3e898833d6..c4b2bed00a 100644 --- a/language/codecompletion/codecompletioncontext.cpp +++ b/language/codecompletion/codecompletioncontext.cpp @@ -1,94 +1,94 @@ /* Copyright 2007 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 "codecompletioncontext.h" #include "codecompletionitem.h" #include "util/debug.h" #include #include #include using namespace KDevelop; typedef PushValue IntPusher; ///Extracts the last line from the given string QString CodeCompletionContext::extractLastLine(const QString& str) { int prevLineEnd = str.lastIndexOf('\n'); if(prevLineEnd != -1) return str.mid(prevLineEnd+1); else return str; } int completionRecursionDepth = 0; CodeCompletionContext::CodeCompletionContext(DUContextPointer context, const QString& text, const KDevelop::CursorInRevision& position, int depth) - : m_text(text), m_depth(depth), m_valid(true), m_position(position), m_duContext(context), m_parentContext(0) + : m_text(text), m_depth(depth), m_valid(true), m_position(position), m_duContext(context), m_parentContext(nullptr) { IntPusher( completionRecursionDepth, completionRecursionDepth+1 ); if( depth > 10 ) { qCWarning(LANGUAGE) << "too much recursion"; m_valid = false; return; } if( completionRecursionDepth > 10 ) { qCWarning(LANGUAGE) << "too much recursion"; m_valid = false; return; } } CodeCompletionContext::~CodeCompletionContext() { } int CodeCompletionContext::depth() const { return m_depth; } bool CodeCompletionContext::isValid() const { return m_valid; } void KDevelop::CodeCompletionContext::setParentContext(QExplicitlySharedDataPointer< KDevelop::CodeCompletionContext > newParent) { m_parentContext = newParent; int newDepth = m_depth+1; while(newParent) { newParent->m_depth = newDepth; ++newDepth; newParent = newParent->m_parentContext; } } CodeCompletionContext* CodeCompletionContext::parentContext() { return m_parentContext.data(); } QList< QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > > KDevelop::CodeCompletionContext::ungroupedElements() { return QList< QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > >(); } KDevelop::DUContext* KDevelop::CodeCompletionContext::duContext() const { return m_duContext.data(); } diff --git a/language/codecompletion/codecompletionitem.cpp b/language/codecompletion/codecompletionitem.cpp index b17f47b020..dc5e024bad 100644 --- a/language/codecompletion/codecompletionitem.cpp +++ b/language/codecompletion/codecompletionitem.cpp @@ -1,164 +1,164 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codecompletionitem.h" #include #include #include #include #include #include #include "util/debug.h" #include "../duchain/declaration.h" #include "../duchain/duchainutils.h" using namespace KTextEditor; namespace KDevelop { ///Intermediate nodes struct CompletionTreeNode; ///Leaf items class CompletionTreeItem; -CompletionTreeElement::CompletionTreeElement() : m_parent(0), m_rowInParent(0) { +CompletionTreeElement::CompletionTreeElement() : m_parent(nullptr), m_rowInParent(0) { } CompletionTreeElement::~CompletionTreeElement() { } CompletionTreeElement* CompletionTreeElement::parent() const { return m_parent; } void CompletionTreeElement::setParent(CompletionTreeElement* parent) { - Q_ASSERT(m_parent == 0); + Q_ASSERT(m_parent == nullptr); m_parent = parent; auto node = parent ? parent->asNode() : nullptr; if (node) { m_rowInParent = node->children.count(); } } void CompletionTreeNode::appendChildren(QList< QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > > children) { foreach (const auto& child, children) { appendChild(child); } } void CompletionTreeNode::appendChildren(QList< QExplicitlySharedDataPointer< KDevelop::CompletionTreeItem > > children) { foreach (const auto& child, children) { appendChild(CompletionTreeElementPointer(child.data())); } } void CompletionTreeNode::appendChild(QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > child) { child->setParent(this); children << child; } int CompletionTreeElement::columnInParent() const { return 0; } CompletionTreeNode::CompletionTreeNode() : CompletionTreeElement(), role((KTextEditor::CodeCompletionModel::ExtraItemDataRoles)Qt::DisplayRole) {} CompletionTreeNode::~CompletionTreeNode() { } CompletionTreeNode* CompletionTreeElement::asNode() { return dynamic_cast(this); } CompletionTreeItem* CompletionTreeElement::asItem() { return dynamic_cast(this); } const CompletionTreeNode* CompletionTreeElement::asNode() const { return dynamic_cast(this); } const CompletionTreeItem* CompletionTreeElement::asItem() const { return dynamic_cast(this); } int CompletionTreeElement::rowInParent() const { return m_rowInParent; } void CompletionTreeItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { Q_UNUSED(view) Q_UNUSED(word) qCWarning(LANGUAGE) << "doing nothing"; } QVariant CompletionTreeItem::data(const QModelIndex& index, int role, const CodeCompletionModel* model) const { Q_UNUSED(index) Q_UNUSED(model) if(role == Qt::DisplayRole) return i18n("not implemented"); return QVariant(); } int CompletionTreeItem::inheritanceDepth() const { return 0; } int CompletionTreeItem::argumentHintDepth() const { return 0; } KTextEditor::CodeCompletionModel::CompletionProperties CompletionTreeItem::completionProperties() const { Declaration* dec = declaration().data(); if(!dec) { return {}; } return DUChainUtils::completionProperties(dec); } DeclarationPointer CompletionTreeItem::declaration() const { return DeclarationPointer(); } QList CompletionTreeItem::typeForArgumentMatching() const { return QList(); } CompletionCustomGroupNode::CompletionCustomGroupNode(QString groupName, int _inheritanceDepth) { role = (KTextEditor::CodeCompletionModel::ExtraItemDataRoles)Qt::DisplayRole; roleValue = groupName; inheritanceDepth = _inheritanceDepth; } bool CompletionTreeItem::dataChangedWithInput() const { return false; } } diff --git a/language/codecompletion/codecompletionmodel.cpp b/language/codecompletion/codecompletionmodel.cpp index e89cb806f3..c2e54c092b 100644 --- a/language/codecompletion/codecompletionmodel.cpp +++ b/language/codecompletion/codecompletionmodel.cpp @@ -1,454 +1,454 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codecompletionmodel.h" #include #include #include #include #include #include #include #include #include #include "../duchain/declaration.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/ducontext.h" #include "../duchain/duchain.h" #include "../duchain/namespacealiasdeclaration.h" #include "../duchain/parsingenvironment.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainbase.h" #include "../duchain/topducontext.h" #include "../duchain/duchainutils.h" #include "../interfaces/quickopendataprovider.h" #include "../interfaces/icore.h" #include "../interfaces/ilanguagecontroller.h" #include "../interfaces/icompletionsettings.h" #include "util/debug.h" #include "codecompletionworker.h" #include "codecompletioncontext.h" #include #if KTEXTEDITOR_VERSION < QT_VERSION_CHECK(5, 10, 0) Q_DECLARE_METATYPE(KTextEditor::Cursor) #endif using namespace KTextEditor; //Multi-threaded completion creates some multi-threading related crashes, and sometimes shows the completions in the wrong position if the cursor was moved // #define SINGLE_THREADED_COMPLETION namespace KDevelop { class CompletionWorkerThread : public QThread { Q_OBJECT public: CompletionWorkerThread(CodeCompletionModel* model) : QThread(model), m_model(model), m_worker(m_model->createCompletionWorker()) { - Q_ASSERT(m_worker->parent() == 0); // Must be null, else we cannot change the thread affinity! + Q_ASSERT(m_worker->parent() == nullptr); // Must be null, else we cannot change the thread affinity! m_worker->moveToThread(this); Q_ASSERT(m_worker->thread() == this); } ~CompletionWorkerThread() override { delete m_worker; } void run () override { //We connect directly, so we can do the pre-grouping within the background thread connect(m_worker, &CodeCompletionWorker::foundDeclarationsReal, m_model, &CodeCompletionModel::foundDeclarations, Qt::QueuedConnection); connect(m_model, &CodeCompletionModel::completionsNeeded, m_worker, static_cast,const Cursor&,View*)>(&CodeCompletionWorker::computeCompletions), Qt::QueuedConnection); connect(m_model, &CodeCompletionModel::doSpecialProcessingInBackground, m_worker, &CodeCompletionWorker::doSpecialProcessing); exec(); } CodeCompletionModel* m_model; CodeCompletionWorker* m_worker; }; bool CodeCompletionModel::forceWaitForModel() { return m_forceWaitForModel; } void CodeCompletionModel::setForceWaitForModel(bool wait) { m_forceWaitForModel = wait; } CodeCompletionModel::CodeCompletionModel( QObject * parent ) : KTextEditor::CodeCompletionModel(parent) , m_forceWaitForModel(false) , m_fullCompletion(true) , m_mutex(new QMutex) - , m_thread(0) + , m_thread(nullptr) { qRegisterMetaType(); } void CodeCompletionModel::initialize() { if(!m_thread) { m_thread = new CompletionWorkerThread(this); #ifdef SINGLE_THREADED_COMPLETION m_thread->m_worker = createCompletionWorker(); #endif m_thread->start(); } } CodeCompletionModel::~CodeCompletionModel() { if(m_thread->m_worker) m_thread->m_worker->abortCurrentCompletion(); m_thread->quit(); m_thread->wait(); delete m_thread; delete m_mutex; } void CodeCompletionModel::addNavigationWidget(const CompletionTreeElement* element, QWidget* widget) const { Q_ASSERT(dynamic_cast(widget)); m_navigationWidgets[element] = widget; } bool CodeCompletionModel::fullCompletion() const { return m_fullCompletion; } KDevelop::CodeCompletionWorker* CodeCompletionModel::worker() const { return m_thread->m_worker; } void CodeCompletionModel::clear() { beginResetModel(); m_completionItems.clear(); m_navigationWidgets.clear(); m_completionContext.reset(); endResetModel(); } void CodeCompletionModel::completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType, const QUrl& url) { Q_ASSERT(m_thread == worker()->thread()); Q_UNUSED(invocationType) DUChainReadLocker lock(DUChain::lock(), 400); if( !lock.locked() ) { qCDebug(LANGUAGE) << "could not lock du-chain in time"; return; } TopDUContext* top = DUChainUtils::standardContextForUrl( url ); if(!top) { return; } setCurrentTopContext(TopDUContextPointer(top)); RangeInRevision rangeInRevision = top->transformToLocalRevision(KTextEditor::Range(range)); if (top) { qCDebug(LANGUAGE) << "completion invoked for context" << (DUContext*)top; if( top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->modificationRevision() != ModificationRevision::revisionForFile(IndexedString(url.toString())) ) { qCDebug(LANGUAGE) << "Found context is not current. Its revision is " /*<< top->parsingEnvironmentFile()->modificationRevision() << " while the document-revision is " << ModificationRevision::revisionForFile(IndexedString(url.toString()))*/; } DUContextPointer thisContext; { qCDebug(LANGUAGE) << "apply specialization:" << range.start(); thisContext = SpecializationStore::self().applySpecialization(top->findContextAt(rangeInRevision.start), top); if ( thisContext ) { qCDebug(LANGUAGE) << "after specialization:" << thisContext->localScopeIdentifier().toString() << thisContext->rangeInCurrentRevision(); } if(!thisContext) thisContext = top; qCDebug(LANGUAGE) << "context is set to" << thisContext.data(); if( !thisContext ) { qCDebug(LANGUAGE) << "================== NO CONTEXT FOUND ======================="; beginResetModel(); m_completionItems.clear(); m_navigationWidgets.clear(); endResetModel(); return; } } lock.unlock(); if(m_forceWaitForModel) emit waitForReset(); emit completionsNeeded(thisContext, range.start(), view); } else { qCDebug(LANGUAGE) << "Completion invoked for unknown context. Document:" << url << ", Known documents:" << DUChain::self()->documents(); } } void CodeCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType) { //If this triggers, initialize() has not been called after creation. Q_ASSERT(m_thread); KDevelop::ICompletionSettings::CompletionLevel level = KDevelop::ICore::self()->languageController()->completionSettings()->completionLevel(); if(level == KDevelop::ICompletionSettings::AlwaysFull || (invocationType != AutomaticInvocation && level == KDevelop::ICompletionSettings::MinimalWhenAutomatic)) m_fullCompletion = true; else m_fullCompletion = false; //Only use grouping in full completion mode setHasGroups(m_fullCompletion); Q_UNUSED(invocationType) if (!worker()) { qCWarning(LANGUAGE) << "Completion invoked on a completion model which has no code completion worker assigned!"; } beginResetModel(); m_navigationWidgets.clear(); m_completionItems.clear(); endResetModel(); worker()->abortCurrentCompletion(); worker()->setFullCompletion(m_fullCompletion); QUrl url = view->document()->url(); completionInvokedInternal(view, range, invocationType, url); } void CodeCompletionModel::foundDeclarations(const QList>& items, const QExplicitlySharedDataPointer& completionContext) { m_completionContext = completionContext; if(m_completionItems.isEmpty() && items.isEmpty()) { if(m_forceWaitForModel) { // TODO KF5: Check if this actually works beginResetModel(); endResetModel(); //If we need to reset the model, reset it } return; //We don't need to reset, which is bad for target model } beginResetModel(); m_completionItems = items; endResetModel(); if(m_completionContext) { qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements"; } } KTextEditor::CodeCompletionModelControllerInterface::MatchReaction CodeCompletionModel::matchingItem(const QModelIndex& /*matched*/) { return None; } void CodeCompletionModel::setCompletionContext(QExplicitlySharedDataPointer completionContext) { QMutexLocker lock(m_mutex); m_completionContext = completionContext; if(m_completionContext) { qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements"; } } QExplicitlySharedDataPointer CodeCompletionModel::completionContext() const { QMutexLocker lock(m_mutex); return m_completionContext; } void CodeCompletionModel::executeCompletionItem(View* view, const KTextEditor::Range& word, const QModelIndex& index) const { //We must not lock the duchain at this place, because the items might rely on that CompletionTreeElement* element = static_cast(index.internalPointer()); if( !element || !element->asItem() ) return; element->asItem()->execute(view, word); } QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > CodeCompletionModel::itemForIndex(QModelIndex index) const { CompletionTreeElement* element = static_cast(index.internalPointer()); return QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement >(element); } QVariant CodeCompletionModel::data(const QModelIndex& index, int role) const { if ( role == Qt::TextAlignmentRole && index.column() == 0 ) { return Qt::AlignRight; } auto element = static_cast(index.internalPointer()); if( !element ) return QVariant(); if( role == CodeCompletionModel::GroupRole ) { if( element->asNode() ) { return QVariant(element->asNode()->role); }else { qCDebug(LANGUAGE) << "Requested group-role from leaf tree element"; return QVariant(); } }else{ if( element->asNode() ) { if( role == CodeCompletionModel::InheritanceDepth ) { auto customGroupNode = dynamic_cast(element); if(customGroupNode) return QVariant(customGroupNode->inheritanceDepth); } if( role == element->asNode()->role ) { return element->asNode()->roleValue; } else { return QVariant(); } } } if(!element->asItem()) { qCWarning(LANGUAGE) << "Error in completion model"; return QVariant(); } //Navigation widget interaction is done here, the other stuff is done within the tree-elements switch (role) { case CodeCompletionModel::InheritanceDepth: return element->asItem()->inheritanceDepth(); case CodeCompletionModel::ArgumentHintDepth: return element->asItem()->argumentHintDepth(); case CodeCompletionModel::ItemSelected: { DeclarationPointer decl = element->asItem()->declaration(); if(decl) { DUChain::self()->emitDeclarationSelected(decl); } break; } } //In minimal completion mode, hide all columns except the "name" one if(!m_fullCompletion && role == Qt::DisplayRole && index.column() != Name && (element->asItem()->argumentHintDepth() == 0 || index.column() == Prefix)) return QVariant(); //In reduced completion mode, don't show information text with the selected items if(role == ItemSelected && (!m_fullCompletion || !ICore::self()->languageController()->completionSettings()->showMultiLineSelectionInformation())) return QVariant(); return element->asItem()->data(index, role, this); } KDevelop::TopDUContextPointer CodeCompletionModel::currentTopContext() const { return m_currentTopContext; } void CodeCompletionModel::setCurrentTopContext(KDevelop::TopDUContextPointer topContext) { m_currentTopContext = topContext; } QModelIndex CodeCompletionModel::index(int row, int column, const QModelIndex& parent) const { if( parent.isValid() ) { CompletionTreeElement* element = static_cast(parent.internalPointer()); CompletionTreeNode* node = element->asNode(); if( !node ) { qCDebug(LANGUAGE) << "Requested sub-index of leaf node"; return QModelIndex(); } if (row < 0 || row >= node->children.count() || column < 0 || column >= ColumnCount) return QModelIndex(); return createIndex(row, column, node->children[row].data()); } else { if (row < 0 || row >= m_completionItems.count() || column < 0 || column >= ColumnCount) return QModelIndex(); return createIndex(row, column, const_cast(m_completionItems[row].data())); } } QModelIndex CodeCompletionModel::parent ( const QModelIndex & index ) const { if(rowCount() == 0) return QModelIndex(); if( index.isValid() ) { CompletionTreeElement* element = static_cast(index.internalPointer()); if( element->parent() ) return createIndex( element->rowInParent(), element->columnInParent(), element->parent() ); } return QModelIndex(); } int CodeCompletionModel::rowCount ( const QModelIndex & parent ) const { if( parent.isValid() ) { CompletionTreeElement* element = static_cast(parent.internalPointer()); CompletionTreeNode* node = element->asNode(); if( !node ) return 0; return node->children.count(); }else{ return m_completionItems.count(); } } QString CodeCompletionModel::filterString(KTextEditor::View* view, const KTextEditor::Range& range, const KTextEditor::Cursor& position) { m_filterString = KTextEditor::CodeCompletionModelControllerInterface::filterString(view, range, position); return m_filterString; } } #include "moc_codecompletionmodel.cpp" #include "codecompletionmodel.moc" diff --git a/language/codecompletion/codecompletionworker.cpp b/language/codecompletion/codecompletionworker.cpp index 6f1efb9dd3..bb2ead1fde 100644 --- a/language/codecompletion/codecompletionworker.cpp +++ b/language/codecompletion/codecompletionworker.cpp @@ -1,224 +1,224 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codecompletionworker.h" #include #include #include #include "../duchain/ducontext.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "util/debug.h" #include "codecompletion.h" #include "codecompletionitem.h" #include "codecompletionmodel.h" #include "codecompletionitemgrouper.h" #include using namespace KTextEditor; using namespace KDevelop; CodeCompletionWorker::CodeCompletionWorker(KDevelop::CodeCompletionModel* model) : m_hasFoundDeclarations(false) , m_mutex(new QMutex()) , m_abort(false) , m_fullCompletion(true) , m_model(model) { } CodeCompletionWorker::~CodeCompletionWorker() { delete m_mutex; } void CodeCompletionWorker::setFullCompletion(bool full) { m_fullCompletion = full; } bool CodeCompletionWorker::fullCompletion() const { return m_fullCompletion; } void CodeCompletionWorker::failed() { foundDeclarations({}, {}); } void CodeCompletionWorker::foundDeclarations(const QList& items, const CodeCompletionContext::Ptr& completionContext) { m_hasFoundDeclarations = true; emit foundDeclarationsReal(items, completionContext); } void CodeCompletionWorker::computeCompletions(KDevelop::DUContextPointer context, const KTextEditor::Cursor& position, KTextEditor::View* view) { { QMutexLocker lock(m_mutex); m_abort = false; } ///@todo It's not entirely safe to pass KTextEditor::View* through a queued connection // We access the view/document which is not thread-safe, so we need the foreground lock ForegroundLock foreground; //Compute the text we should complete on KTextEditor::Document* doc = view->document(); if( !doc ) { qCDebug(LANGUAGE) << "No document for completion"; failed(); return; } KTextEditor::Range range; QString text; { QMutexLocker lock(m_mutex); DUChainReadLocker lockDUChain; if(context) { qCDebug(LANGUAGE) << context->localScopeIdentifier().toString(); range = KTextEditor::Range(context->rangeInCurrentRevision().start(), position); } else range = KTextEditor::Range(KTextEditor::Cursor(position.line(), 0), position); updateContextRange(range, view, context); text = doc->text(range); } if( position.column() == 0 ) //Seems like when the cursor is a the beginning of a line, kate does not give the \n text += '\n'; if (aborting()) { failed(); return; } m_hasFoundDeclarations = false; KTextEditor::Cursor cursorPosition = view->cursorPosition(); QString followingText; //followingText may contain additional text that stands for the current item. For example in the case "QString|", QString is in addedText. if(position < cursorPosition) followingText = view->document()->text( KTextEditor::Range( position, cursorPosition ) ); foreground.unlock(); computeCompletions(context, position, followingText, range, text); if(!m_hasFoundDeclarations) failed(); } void KDevelop::CodeCompletionWorker::doSpecialProcessing(uint) { } CodeCompletionContext* CodeCompletionWorker::createCompletionContext(KDevelop::DUContextPointer context, const QString& contextText, const QString& followingText, const KDevelop::CursorInRevision& position) const { Q_UNUSED(context); Q_UNUSED(contextText); Q_UNUSED(followingText); Q_UNUSED(position); - return 0; + return nullptr; } void CodeCompletionWorker::computeCompletions(KDevelop::DUContextPointer context, const KTextEditor::Cursor& position, QString followingText, const KTextEditor::Range& contextRange, const QString& contextText) { Q_UNUSED(contextRange); qCDebug(LANGUAGE) << "added text:" << followingText; CodeCompletionContext::Ptr completionContext( createCompletionContext( context, contextText, followingText, CursorInRevision::castFromSimpleCursor(KTextEditor::Cursor(position)) ) ); if (KDevelop::CodeCompletionModel* m = model()) m->setCompletionContext(completionContext); if( completionContext && completionContext->isValid() ) { { DUChainReadLocker lock(DUChain::lock()); if (!context) { failed(); qCDebug(LANGUAGE) << "Completion context disappeared before completions could be calculated"; return; } } QList items = completionContext->completionItems(aborting(), fullCompletion()); if (aborting()) { failed(); return; } QList > tree = computeGroups( items, completionContext ); if(aborting()) { failed(); return; } tree += completionContext->ungroupedElements(); foundDeclarations( tree, completionContext ); } else { qCDebug(LANGUAGE) << "setContext: Invalid code-completion context"; } } QList > CodeCompletionWorker::computeGroups(QList items, QExplicitlySharedDataPointer completionContext) { Q_UNUSED(completionContext); QList > tree; /** * 1. Group by argument-hint depth * 2. Group by inheritance depth * 3. Group by simplified attributes * */ - CodeCompletionItemGrouper > > argumentHintDepthGrouper(tree, 0, items); + CodeCompletionItemGrouper > > argumentHintDepthGrouper(tree, nullptr, items); return tree; } void CodeCompletionWorker::abortCurrentCompletion() { QMutexLocker lock(m_mutex); m_abort = true; } bool& CodeCompletionWorker::aborting() { return m_abort; } KDevelop::CodeCompletionModel* CodeCompletionWorker::model() const { return m_model; } void CodeCompletionWorker::updateContextRange(Range& contextRange, View* view, DUContextPointer context) const { Q_UNUSED(contextRange); Q_UNUSED(view); Q_UNUSED(context); } diff --git a/language/codecompletion/normaldeclarationcompletionitem.cpp b/language/codecompletion/normaldeclarationcompletionitem.cpp index 268854352d..9e30fea1ef 100644 --- a/language/codecompletion/normaldeclarationcompletionitem.cpp +++ b/language/codecompletion/normaldeclarationcompletionitem.cpp @@ -1,230 +1,230 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "normaldeclarationcompletionitem.h" #include "codecompletionmodel.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumeratortype.h" #include "../duchain/duchainutils.h" #include "../duchain/navigation/abstractdeclarationnavigationcontext.h" #include "util/debug.h" #include #include namespace KDevelop { const int NormalDeclarationCompletionItem::normalBestMatchesCount = 5; //If this is true, the return-values of argument-hints will be just written as "..." if they are too long const bool NormalDeclarationCompletionItem::shortenArgumentHintReturnValues = true; const int NormalDeclarationCompletionItem::maximumArgumentHintReturnValueLength = 30; const int NormalDeclarationCompletionItem::desiredTypeLength = 20; NormalDeclarationCompletionItem::NormalDeclarationCompletionItem(KDevelop::DeclarationPointer decl, QExplicitlySharedDataPointer context, int inheritanceDepth) : m_completionContext(context), m_declaration(decl), m_inheritanceDepth(inheritanceDepth) { } KDevelop::DeclarationPointer NormalDeclarationCompletionItem::declaration() const { return m_declaration; } QExplicitlySharedDataPointer< KDevelop::CodeCompletionContext > NormalDeclarationCompletionItem::completionContext() const { return m_completionContext; } int NormalDeclarationCompletionItem::inheritanceDepth() const { return m_inheritanceDepth; } int NormalDeclarationCompletionItem::argumentHintDepth() const { if( m_completionContext ) return m_completionContext->depth(); else return 0; } QString NormalDeclarationCompletionItem::declarationName() const { if (!m_declaration) { return QStringLiteral(""); } QString ret = m_declaration->identifier().toString(); if (ret.isEmpty()) return QStringLiteral(""); else return ret; } void NormalDeclarationCompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { if( m_completionContext && m_completionContext->depth() != 0 ) return; //Do not replace any text when it is an argument-hint KTextEditor::Document* document = view->document(); QString newText; { KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); if(m_declaration) { newText = declarationName(); } else { qCDebug(LANGUAGE) << "Declaration disappeared"; return; } } document->replaceText(word, newText); KTextEditor::Range newRange = word; newRange.setEnd(KTextEditor::Cursor(newRange.end().line(), newRange.start().column() + newText.length())); executed(view, newRange); } QWidget* NormalDeclarationCompletionItem::createExpandingWidget(const KDevelop::CodeCompletionModel* model) const { Q_UNUSED(model); - return 0; + return nullptr; } bool NormalDeclarationCompletionItem::createsExpandingWidget() const { return false; } QString NormalDeclarationCompletionItem::shortenedTypeString(KDevelop::DeclarationPointer decl, int desiredTypeLength) const { Q_UNUSED(desiredTypeLength); return decl->abstractType()->toString(); } void NormalDeclarationCompletionItem::executed(KTextEditor::View* view, const KTextEditor::Range& word) { Q_UNUSED(view); Q_UNUSED(word); } QVariant NormalDeclarationCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock(DUChain::lock(), 500); if(!lock.locked()) { qCDebug(LANGUAGE) << "Failed to lock the du-chain in time"; return QVariant(); } if(!m_declaration) return QVariant(); switch (role) { case Qt::DisplayRole: if (index.column() == CodeCompletionModel::Name) { return declarationName(); } else if(index.column() == CodeCompletionModel::Postfix) { if (FunctionType::Ptr functionType = m_declaration->type()) { // Retrieve const/volatile string return functionType->AbstractType::toString(); } } else if(index.column() == CodeCompletionModel::Prefix) { if(m_declaration->kind() == Declaration::Namespace) return QStringLiteral("namespace"); if (m_declaration->abstractType()) { if(EnumeratorType::Ptr enumerator = m_declaration->type()) { if(m_declaration->context()->owner() && m_declaration->context()->owner()->abstractType()) { if(!m_declaration->context()->owner()->identifier().isEmpty()) return shortenedTypeString(DeclarationPointer(m_declaration->context()->owner()), desiredTypeLength); else return "enum"; } } if (FunctionType::Ptr functionType = m_declaration->type()) { ClassFunctionDeclaration* funDecl = dynamic_cast(m_declaration.data()); if (functionType->returnType()) { QString ret = shortenedTypeString(m_declaration, desiredTypeLength); if(shortenArgumentHintReturnValues && argumentHintDepth() && ret.length() > maximumArgumentHintReturnValueLength) return QStringLiteral("..."); else return ret; }else if(argumentHintDepth()) { return QString();//Don't show useless prefixes in the argument-hints }else if(funDecl && funDecl->isConstructor() ) return ""; else if(funDecl && funDecl->isDestructor() ) return ""; else return ""; } else { return shortenedTypeString(m_declaration, desiredTypeLength); } } else { return ""; } } else if (index.column() == CodeCompletionModel::Arguments) { if (m_declaration->isFunctionDeclaration()) { auto functionType = declaration()->type(); return functionType ? functionType->partToString(FunctionType::SignatureArguments) : QVariant(); } } break; case CodeCompletionModel::BestMatchesCount: return QVariant(normalBestMatchesCount); break; case CodeCompletionModel::IsExpandable: return QVariant(createsExpandingWidget()); case CodeCompletionModel::ExpandingWidget: { QWidget* nav = createExpandingWidget(model); Q_ASSERT(nav); model->addNavigationWidget(this, nav); QVariant v; v.setValue(nav); return v; } case CodeCompletionModel::ScopeIndex: return static_cast(reinterpret_cast(m_declaration->context())); case CodeCompletionModel::CompletionRole: return (int)completionProperties(); case CodeCompletionModel::ItemSelected: { NavigationContextPointer ctx(new AbstractDeclarationNavigationContext(DeclarationPointer(m_declaration), TopDUContextPointer())); return ctx->html(true); } case Qt::DecorationRole: { if( index.column() == CodeCompletionModel::Icon ) { CodeCompletionModel::CompletionProperties p = completionProperties(); lock.unlock(); return DUChainUtils::iconForProperties(p); } break; } } return QVariant(); } } diff --git a/language/codegen/basicrefactoring.cpp b/language/codegen/basicrefactoring.cpp index 38cad2b098..c971ee4126 100644 --- a/language/codegen/basicrefactoring.cpp +++ b/language/codegen/basicrefactoring.cpp @@ -1,361 +1,361 @@ /* 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 #include #include #include "progressdialogs/refactoringdialog.h" #include "util/debug.h" #include "ui_basicrefactoring.h" namespace { QPair splitFileAtExtension(const QString& fileName) { int idx = fileName.indexOf('.'); if (idx == -1) { return qMakePair(fileName, QString()); } return qMakePair(fileName.left(idx), fileName.mid(idx)); } } using 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); + QAction *action = new QAction(i18n("Rename \"%1\"...", declaration->qualifiedIdentifier().toString()), nullptr); action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); connect(action, &QAction::triggered, this, &BasicRefactoring::executeRenameAction); extension.addAction(ContextMenuExtension::RefactorGroup, action); } } } bool BasicRefactoring::shouldRenameUses(KDevelop::Declaration* declaration) const { // Now we know we're editing a declaration, but some declarations we don't offer a rename for // basically that's any declaration that wouldn't be fully renamed just by renaming its uses(). if (declaration->internalContext() || declaration->isForwardDeclaration()) { //make an exception for non-class functions if (!declaration->isFunctionDeclaration() || dynamic_cast(declaration)) return false; } return true; } QString BasicRefactoring::newFileName(const QUrl& current, const QString& newName) { QPair nameExtensionPair = splitFileAtExtension(current.fileName()); // if current file is lowercased, keep that if (nameExtensionPair.first == nameExtensionPair.first.toLower()) { return newName.toLower() + nameExtensionPair.second; } else { return newName + nameExtensionPair.second; } } DocumentChangeSet::ChangeResult BasicRefactoring::addRenameFileChanges(const QUrl& current, const QString& newName, DocumentChangeSet* changes) { return changes->addDocumentRenameChange( IndexedString(current), IndexedString(newFileName(current, newName))); } bool BasicRefactoring::shouldRenameFile(Declaration* declaration) { // only try to rename files when we renamed a class/struct if (!dynamic_cast(declaration)) { return false; } const QUrl currUrl = declaration->topContext()->url().toUrl(); const QString fileName = currUrl.fileName(); const QPair nameExtensionPair = splitFileAtExtension(fileName); // check whether we renamed something that is called like the document it lives in return nameExtensionPair.first.compare(declaration->identifier().toString(), Qt::CaseInsensitive) == 0; } 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()) { qCDebug(LANGUAGE) << "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()) qCDebug(LANGUAGE) << "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) { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) return KDevelop::IndexedDeclaration(); KTextEditor::Document* doc = view->document(); DUChainReadLocker lock; if (allowUse) return DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(view->cursorPosition())).declaration; else return DUChainUtils::declarationInLine(KTextEditor::Cursor(view->cursorPosition()), DUChainUtils::standardContextForUrl(doc->url())); } 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(); 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(); if(!decl.isValid()) return; startInteractiveRename(decl); } } BasicRefactoring::NameAndCollector BasicRefactoring::newNameForDeclaration(const KDevelop::DeclarationPointer& declaration) { DUChainReadLocker lock; if (!declaration) { return {}; } QSharedPointer collector(new BasicRefactoringCollector(declaration.data())); Ui::RenameDialog renameDialog; QDialog dialog; renameDialog.setupUi(&dialog); UsesWidget uses(declaration.data(), collector); //So the context-links work QWidget *navigationWidget = declaration->context()->createNavigationWidget(declaration.data()); AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget); if (abstractNavigationWidget) connect(&uses, &UsesWidget::navigateDeclaration, abstractNavigationWidget, &AbstractNavigationWidget::navigateDeclaration); QString declarationName = declaration->toString(); dialog.setWindowTitle(i18nc("Renaming some declaration", "Rename \"%1\"", declarationName)); 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 {}; RefactoringProgressDialog refactoringProgress(i18n("Renaming \"%1\" to \"%2\"", declarationName, renameDialog.edit->text()), collector.data()); if (!collector->isReady()) { refactoringProgress.exec(); if (refactoringProgress.result() != QDialog::Accepted) { 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; DUChainReadLocker lock; foreach (const KDevelop::IndexedTopDUContext collected, collector->allUsingContexts()) { QSet hadIndices; 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)); + KMessageBox::error(nullptr, i18n("Applying changes failed: %1", result.m_failureReason)); return {}; } } } DocumentChangeSet::ChangeResult result = applyChangesToDeclarations(originalName, replacementName, changes, collector->declarations()); if (!result) { - KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); + KMessageBox::error(nullptr, i18n("Applying changes failed: %1", result.m_failureReason)); 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) { - KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); + KMessageBox::error(nullptr, i18n("Applying changes failed: %1", result.m_failureReason)); } return {}; } //END: BasicRefactoring diff --git a/language/codegen/documentchangeset.cpp b/language/codegen/documentchangeset.cpp index e66769a5db..1358c978d4 100644 --- a/language/codegen/documentchangeset.cpp +++ b/language/codegen/documentchangeset.cpp @@ -1,593 +1,593 @@ /* 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 "documentchangeset.h" #include "coderepresentation.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KDevelop { typedef QList ChangesList; typedef QHash ChangesHash; struct DocumentChangeSetPrivate { DocumentChangeSet::ReplacementPolicy replacePolicy; DocumentChangeSet::FormatPolicy formatPolicy; DocumentChangeSet::DUChainUpdateHandling updatePolicy; DocumentChangeSet::ActivationPolicy activationPolicy; ChangesHash changes; QHash documentsRename; DocumentChangeSet::ChangeResult addChange(const DocumentChangePointer& change); DocumentChangeSet::ChangeResult replaceOldText(CodeRepresentation* repr, const QString& newText, const ChangesList& sortedChangesList); DocumentChangeSet::ChangeResult generateNewText(const IndexedString& file, ChangesList& sortedChanges, const CodeRepresentation* repr, QString& output); DocumentChangeSet::ChangeResult removeDuplicates(const IndexedString& file, ChangesList& filteredChanges); void formatChanges(); void updateFiles(); }; // Simple helpers to clear up code clutter namespace { inline bool changeIsValid(const DocumentChange& change, const QStringList& textLines) { return change.m_range.start() <= change.m_range.end() && change.m_range.end().line() < textLines.size() && change.m_range.start().line() >= 0 && change.m_range.start().column() >= 0 && change.m_range.start().column() <= textLines[change.m_range.start().line()].length() && change.m_range.end().column() >= 0 && change.m_range.end().column() <= textLines[change.m_range.end().line()].length(); } inline bool duplicateChanges(const DocumentChangePointer& previous, const DocumentChangePointer& current) { // Given the option of considering a duplicate two changes in the same range // but with different old texts to be ignored return previous->m_range == current->m_range && previous->m_newText == current->m_newText && (previous->m_oldText == current->m_oldText || (previous->m_ignoreOldText && current->m_ignoreOldText)); } inline QString rangeText(const KTextEditor::Range& range, const QStringList& textLines) { QStringList ret; ret.reserve(range.end().line() - range.start().line() + 1); for(int line = range.start().line(); line <= range.end().line(); ++line) { const QString lineText = textLines.at(line); int startColumn = 0; int endColumn = lineText.length(); if (line == range.start().line()) { startColumn = range.start().column(); } if (line == range.end().line()) { endColumn = range.end().column(); } ret << lineText.mid(startColumn, endColumn - startColumn); } return ret.join(QStringLiteral("\n")); } // need to have it as otherwise the arguments can exceed the maximum of 10 static QString printRange(const KTextEditor::Range& r) { return i18nc("text range line:column->line:column", "%1:%2->%3:%4", r.start().line(), r.start().column(), r.end().line(), r.end().column()); } } DocumentChangeSet::DocumentChangeSet() : d(new DocumentChangeSetPrivate) { d->replacePolicy = StopOnFailedChange; d->formatPolicy = AutoFormatChanges; d->updatePolicy = SimpleUpdate; d->activationPolicy = DoNotActivate; } DocumentChangeSet::DocumentChangeSet(const DocumentChangeSet& rhs) : d(new DocumentChangeSetPrivate(*rhs.d)) { } DocumentChangeSet& DocumentChangeSet::operator=(const DocumentChangeSet& rhs) { *d = *rhs.d; return *this; } DocumentChangeSet::~DocumentChangeSet() { delete d; } DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChange& change) { return d->addChange(DocumentChangePointer(new DocumentChange(change))); } DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChangePointer& change) { return d->addChange(change); } DocumentChangeSet::ChangeResult DocumentChangeSet::addDocumentRenameChange(const IndexedString& oldFile, const IndexedString& newname) { d->documentsRename.insert(oldFile, newname); return true; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::addChange(const DocumentChangePointer& change) { changes[change->m_document].append(change); return true; } void DocumentChangeSet::setReplacementPolicy(DocumentChangeSet::ReplacementPolicy policy) { d->replacePolicy = policy; } void DocumentChangeSet::setFormatPolicy(DocumentChangeSet::FormatPolicy policy) { d->formatPolicy = policy; } void DocumentChangeSet::setUpdateHandling(DocumentChangeSet::DUChainUpdateHandling policy) { d->updatePolicy = policy; } void DocumentChangeSet::setActivationPolicy(DocumentChangeSet::ActivationPolicy policy) { d->activationPolicy = policy; } DocumentChangeSet::ChangeResult DocumentChangeSet::applyAllChanges() { QUrl oldActiveDoc; if (IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) { oldActiveDoc = activeDoc->url(); } QList allFiles; foreach (IndexedString file, d->documentsRename.keys().toSet() + d->changes.keys().toSet()) { allFiles << file.toUrl(); } if (!KDevelop::ensureWritable(allFiles)) { return ChangeResult(QStringLiteral("some affected files are not writable")); } // rename files QHash::const_iterator it = d->documentsRename.constBegin(); for(; it != d->documentsRename.constEnd(); ++it) { QUrl url = it.key().toUrl(); IProject* p = ICore::self()->projectController()->findProjectForUrl(url); if(p) { QList files = p->filesForPath(it.key()); if(!files.isEmpty()) { ProjectBaseItem::RenameStatus renamed = files.first()->rename(it.value().str()); if(renamed == ProjectBaseItem::RenameOk) { const QUrl newUrl = Path(Path(url).parent(), it.value().str()).toUrl(); if (url == oldActiveDoc) { oldActiveDoc = newUrl; } IndexedString idxNewDoc(newUrl); // ensure changes operate on new file name ChangesHash::iterator iter = d->changes.find(it.key()); if (iter != d->changes.end()) { // copy changes ChangesList value = iter.value(); // remove old entry d->changes.erase(iter); // adapt to new url ChangesList::iterator itChange = value.begin(); ChangesList::iterator itEnd = value.end(); for(; itChange != itEnd; ++itChange) { (*itChange)->m_document = idxNewDoc; } d->changes[idxNewDoc] = value; } } else { ///FIXME: share code with project manager for the error code string representation return ChangeResult(i18n("Could not rename '%1' to '%2'", url.toDisplayString(QUrl::PreferLocalFile), it.value().str())); } } else { //TODO: do it outside the project management? qCWarning(LANGUAGE) << "tried to rename file not tracked by project - not implemented"; } } else { qCWarning(LANGUAGE) << "tried to rename a file outside of a project - not implemented"; } } QMap codeRepresentations; QMap newTexts; ChangesHash filteredSortedChanges; ChangeResult result(true); QList files(d->changes.keys()); foreach(const IndexedString &file, files) { CodeRepresentation::Ptr repr = createCodeRepresentation(file); if(!repr) { return ChangeResult(QStringLiteral("Could not create a Representation for %1").arg(file.str())); } codeRepresentations[file] = repr; QList& sortedChangesList(filteredSortedChanges[file]); { result = d->removeDuplicates(file, sortedChangesList); if(!result) return result; } { result = d->generateNewText(file, sortedChangesList, repr.data(), newTexts[file]); if(!result) return result; } } QMap oldTexts; //Apply the changes to the files foreach(const IndexedString &file, files) { oldTexts[file] = codeRepresentations[file]->text(); result = d->replaceOldText(codeRepresentations[file].data(), newTexts[file], filteredSortedChanges[file]); if(!result && d->replacePolicy == StopOnFailedChange) { //Revert all files foreach(const IndexedString &revertFile, oldTexts.keys()) { codeRepresentations[revertFile]->setText(oldTexts[revertFile]); } return result; } } d->updateFiles(); if(d->activationPolicy == Activate) { foreach(const IndexedString& file, files) { ICore::self()->documentController()->openDocument(file.toUrl()); } } // ensure the old document is still activated if (oldActiveDoc.isValid()) { ICore::self()->documentController()->openDocument(oldActiveDoc); } return result; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::replaceOldText(CodeRepresentation* repr, const QString& newText, const ChangesList& sortedChangesList) { DynamicCodeRepresentation* dynamic = dynamic_cast(repr); if(dynamic) { auto transaction = dynamic->makeEditTransaction(); //Replay the changes one by one for(int pos = sortedChangesList.size()-1; pos >= 0; --pos) { const DocumentChange& change(*sortedChangesList[pos]); if(!dynamic->replace(change.m_range, change.m_oldText, change.m_newText, change.m_ignoreOldText)) { QString warningString = i18nc("Inconsistent change in between , found (encountered ) -> ", "Inconsistent change in %1 between %2, found %3 (encountered \"%4\") -> \"%5\"" , change.m_document.str() , printRange(change.m_range) , change.m_oldText , dynamic->rangeText(change.m_range) , change.m_newText); if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } else if(replacePolicy == DocumentChangeSet::StopOnFailedChange) { return DocumentChangeSet::ChangeResult(warningString); } //If set to ignore failed changes just continue with the others } } return true; } //For files on disk if (!repr->setText(newText)) { QString warningString = i18n("Could not replace text in the document: %1", sortedChangesList.begin()->data()->m_document.str()); if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } return DocumentChangeSet::ChangeResult(warningString); } return true; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::generateNewText(const IndexedString & file, ChangesList& sortedChanges, const CodeRepresentation * repr, QString & output) { - ISourceFormatter* formatter = 0; + ISourceFormatter* formatter = nullptr; if(ICore::self()) { formatter = ICore::self()->sourceFormatterController()->formatterForUrl(file.toUrl()); } //Create the actual new modified file QStringList textLines = repr->text().split('\n'); QUrl url = file.toUrl(); QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); QVector removedLines; for(int pos = sortedChanges.size()-1; pos >= 0; --pos) { DocumentChange& change(*sortedChanges[pos]); QString encountered; if(changeIsValid(change, textLines) && //We demand this, although it should be fixed ((encountered = rangeText(change.m_range, textLines)) == change.m_oldText || change.m_ignoreOldText)) { ///Problem: This does not work if the other changes significantly alter the context @todo Use the changed context QString leftContext = QStringList(textLines.mid(0, change.m_range.start().line()+1)).join(QStringLiteral("\n")); leftContext.chop(textLines[change.m_range.start().line()].length() - change.m_range.start().column()); QString rightContext = QStringList(textLines.mid(change.m_range.end().line())).join(QStringLiteral("\n")).mid(change.m_range.end().column()); if(formatter && (formatPolicy == DocumentChangeSet::AutoFormatChanges || formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation)) { QString oldNewText = change.m_newText; change.m_newText = formatter->formatSource(change.m_newText, url, mime, leftContext, rightContext); if(formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation) { // Reproduce the previous indentation QStringList oldLines = oldNewText.split('\n'); QStringList newLines = change.m_newText.split('\n'); if(oldLines.size() == newLines.size()) { for(int line = 0; line < newLines.size(); ++line) { // Keep the previous indentation QString oldIndentation; for (int a = 0; a < oldLines[line].size(); ++a) { if (oldLines[line][a].isSpace()) { oldIndentation.append(oldLines[line][a]); } else { break; } } int newIndentationLength = 0; for(int a = 0; a < newLines[line].size(); ++a) { if(newLines[line][a].isSpace()) { newIndentationLength = a; } else { break; } } newLines[line].replace(0, newIndentationLength, oldIndentation); } change.m_newText = newLines.join(QStringLiteral("\n")); } else { qCDebug(LANGUAGE) << "Cannot keep the indentation because the line count has changed" << oldNewText; } } } QString& line = textLines[change.m_range.start().line()]; if (change.m_range.start().line() == change.m_range.end().line()) { // simply replace existing line content line.replace(change.m_range.start().column(), change.m_range.end().column()-change.m_range.start().column(), change.m_newText); } else { // replace first line contents line.replace(change.m_range.start().column(), line.length() - change.m_range.start().column(), change.m_newText); // null other lines and remember for deletion for(int i = change.m_range.start().line() + 1; i <= change.m_range.end().line(); ++i) { textLines[i].clear(); removedLines << i; } } }else{ QString warningString = i18nc("Inconsistent change in at " " = (encountered ) -> ", "Inconsistent change in %1 at %2" " = \"%3\"(encountered \"%4\") -> \"%5\"" , file.str() , printRange(change.m_range) , change.m_oldText , encountered , change.m_newText); if(replacePolicy == DocumentChangeSet::IgnoreFailedChange) { //Just don't do the replacement } else if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } else { return DocumentChangeSet::ChangeResult(warningString, sortedChanges[pos]); } } } if (!removedLines.isEmpty()) { int offset = 0; std::sort(removedLines.begin(), removedLines.end()); foreach(int l, removedLines) { textLines.removeAt(l - offset); ++offset; } } output = textLines.join(QStringLiteral("\n")); return true; } //Removes all duplicate changes for a single file, and then returns (via filteredChanges) the filtered duplicates DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::removeDuplicates(const IndexedString& file, ChangesList& filteredChanges) { typedef QMultiMap ChangesMap; ChangesMap sortedChanges; foreach(const DocumentChangePointer &change, changes[file]) { sortedChanges.insert(change->m_range.end(), change); } //Remove duplicates ChangesMap::iterator previous = sortedChanges.begin(); for(ChangesMap::iterator it = ++sortedChanges.begin(); it != sortedChanges.end(); ) { if(( *previous ) && ( *previous )->m_range.end() > (*it)->m_range.start()) { //intersection if(duplicateChanges(( *previous ), *it)) { //duplicate, remove one it = sortedChanges.erase(it); continue; } //When two changes contain each other, and the container change is set to ignore old text, then it should be safe to //just ignore the contained change, and apply the bigger change else if((*it)->m_range.contains(( *previous )->m_range) && (*it)->m_ignoreOldText ) { qCDebug(LANGUAGE) << "Removing change: " << ( *previous )->m_oldText << "->" << ( *previous )->m_newText << ", because it is contained by change: " << (*it)->m_oldText << "->" << (*it)->m_newText; sortedChanges.erase(previous); } //This case is for when both have the same end, either of them could be the containing range else if((*previous)->m_range.contains((*it)->m_range) && (*previous)->m_ignoreOldText ) { qCDebug(LANGUAGE) << "Removing change: " << (*it)->m_oldText << "->" << (*it)->m_newText << ", because it is contained by change: " << ( *previous )->m_oldText << "->" << ( *previous )->m_newText; it = sortedChanges.erase(it); continue; } else { return DocumentChangeSet::ChangeResult( i18nc("Inconsistent change-request at :" "intersecting changes: " " -> @ & " " -> @", "Inconsistent change-request at %1; " "intersecting changes: " "\"%2\"->\"%3\"@%4 & \"%5\"->\"%6\"@%7 " , file.str() , ( *previous )->m_oldText , ( *previous )->m_newText , printRange(( *previous )->m_range) , (*it)->m_oldText , (*it)->m_newText , printRange((*it)->m_range))); } } previous = it; ++it; } filteredChanges = sortedChanges.values(); return true; } void DocumentChangeSetPrivate::updateFiles() { ModificationRevisionSet::clearCache(); foreach(const IndexedString& file, changes.keys()) { ModificationRevision::clearModificationCache(file); } if(updatePolicy != DocumentChangeSet::NoUpdate && ICore::self()) { // The active document should be updated first, so that the user sees the results instantly if(IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(activeDoc->url())); } // If there are currently open documents that now need an update, update them too foreach(const IndexedString& doc, ICore::self()->languageController()->backgroundParser()->managedDocuments()) { DUChainReadLocker lock(DUChain::lock()); TopDUContext* top = DUChainUtils::standardContextForUrl(doc.toUrl(), true); if((top && top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->needsUpdate()) || !top) { lock.unlock(); ICore::self()->languageController()->backgroundParser()->addDocument(doc); } } // Eventually update _all_ affected files foreach(const IndexedString &file, changes.keys()) { if(!file.toUrl().isValid()) { qCWarning(LANGUAGE) << "Trying to apply changes to an invalid document"; continue; } ICore::self()->languageController()->backgroundParser()->addDocument(file); } } } } diff --git a/language/codegen/sourcefiletemplate.cpp b/language/codegen/sourcefiletemplate.cpp index 46f5ae385e..f599429645 100644 --- a/language/codegen/sourcefiletemplate.cpp +++ b/language/codegen/sourcefiletemplate.cpp @@ -1,317 +1,317 @@ /* * 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 "sourcefiletemplate.h" #include "templaterenderer.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; typedef SourceFileTemplate::ConfigOption ConfigOption; class KDevelop::SourceFileTemplatePrivate { public: KArchive* archive; QString descriptionFileName; QStringList searchLocations; ConfigOption readEntry(const QDomElement& element, TemplateRenderer* renderer); }; ConfigOption SourceFileTemplatePrivate::readEntry(const QDomElement& element, TemplateRenderer* renderer) { ConfigOption entry; entry.name = element.attribute(QStringLiteral("name")); entry.type = element.attribute(QStringLiteral("type"), QStringLiteral("String")); for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { QString tag = e.tagName(); if (tag == QLatin1String("label")) { entry.label = e.text(); } else if (tag == QLatin1String("tooltip")) { entry.label = e.text(); } else if (tag == QLatin1String("whatsthis")) { entry.label = e.text(); } else if ( tag == QLatin1String("min") ) { entry.minValue = e.text(); } else if ( tag == QLatin1String("max") ) { entry.maxValue = e.text(); } else if ( tag == QLatin1String("default") ) { entry.value = renderer->render(e.text(), entry.name); } } qCDebug(LANGUAGE) << "Read entry" << entry.name << "with default value" << entry.value; return entry; } SourceFileTemplate::SourceFileTemplate (const QString& templateDescription) : d(new KDevelop::SourceFileTemplatePrivate) { - d->archive = 0; + d->archive = nullptr; setTemplateDescription(templateDescription); } SourceFileTemplate::SourceFileTemplate() : d(new KDevelop::SourceFileTemplatePrivate) { - d->archive = 0; + d->archive = nullptr; } SourceFileTemplate::SourceFileTemplate (const SourceFileTemplate& other) : d(new KDevelop::SourceFileTemplatePrivate) { - d->archive = 0; + d->archive = nullptr; *this = other; } SourceFileTemplate::~SourceFileTemplate() { delete d->archive; delete d; } SourceFileTemplate& SourceFileTemplate::operator=(const SourceFileTemplate& other) { if (other.d == d) { return *this; } delete d->archive; if (other.d->archive) { if (other.d->archive->fileName().endsWith(QLatin1String(".zip"))) { d->archive = new KZip(other.d->archive->fileName()); } else { d->archive = new KTar(other.d->archive->fileName()); } d->archive->open(QIODevice::ReadOnly); } else { - d->archive = 0; + d->archive = nullptr; } d->descriptionFileName = other.d->descriptionFileName; return *this; } void SourceFileTemplate::setTemplateDescription(const QString& templateDescription) { delete d->archive; d->descriptionFileName = templateDescription; QString archiveFileName; const QString templateBaseName = QFileInfo(templateDescription).baseName(); d->searchLocations.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("/kdevfiletemplates/templates/"), QStandardPaths::LocateDirectory)); foreach(const QString& dir, d->searchLocations) { foreach(const auto& entry, QDir(dir).entryInfoList(QDir::Files)) { if (entry.baseName() == templateBaseName) { archiveFileName = entry.absoluteFilePath(); qCDebug(LANGUAGE) << "Found template archive" << archiveFileName; break; } } } if (archiveFileName.isEmpty() || !QFileInfo::exists(archiveFileName)) { qCWarning(LANGUAGE) << "Could not find a template archive for description" << templateDescription << ", archive file" << archiveFileName; - d->archive = 0; + d->archive = nullptr; } else { QFileInfo info(archiveFileName); if (info.suffix() == QLatin1String("zip")) { d->archive = new KZip(archiveFileName); } else { d->archive = new KTar(archiveFileName); } d->archive->open(QIODevice::ReadOnly); } } bool SourceFileTemplate::isValid() const { return d->archive; } QString SourceFileTemplate::name() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Name"); } QString SourceFileTemplate::type() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Type", QString()); } QString SourceFileTemplate::languageName() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Language", QString()); } QStringList SourceFileTemplate::category() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Category", QStringList()); } QStringList SourceFileTemplate::defaultBaseClasses() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("BaseClasses", QStringList()); } const KArchiveDirectory* SourceFileTemplate::directory() const { Q_ASSERT(isValid()); return d->archive->directory(); } QList< SourceFileTemplate::OutputFile > SourceFileTemplate::outputFiles() const { QList outputFiles; KConfig templateConfig(d->descriptionFileName); KConfigGroup group(&templateConfig, "General"); QStringList files = group.readEntry("Files", QStringList()); qCDebug(LANGUAGE) << "Files in template" << files; foreach (const QString& fileGroup, files) { KConfigGroup cg(&templateConfig, fileGroup); OutputFile f; f.identifier = cg.name(); f.label = cg.readEntry("Name"); f.fileName = cg.readEntry("File"); f.outputName = cg.readEntry("OutputFile"); outputFiles << f; } return outputFiles; } bool SourceFileTemplate::hasCustomOptions() const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); bool hasOptions = d->archive->directory()->entries().contains(cg.readEntry("OptionsFile", "options.kcfg")); qCDebug(LANGUAGE) << cg.readEntry("OptionsFile", "options.kcfg") << hasOptions; return hasOptions; } QHash< QString, QList > SourceFileTemplate::customOptions(TemplateRenderer* renderer) const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); const KArchiveEntry* entry = d->archive->directory()->entry(cg.readEntry("OptionsFile", "options.kcfg")); QHash > options; if (!entry->isFile()) { return options; } const KArchiveFile* file = static_cast(entry); /* * Copied from kconfig_compiler.kcfg */ QDomDocument doc; QString errorMsg; int errorRow; int errorCol; if ( !doc.setContent( file->data(), &errorMsg, &errorRow, &errorCol ) ) { qCDebug(LANGUAGE) << "Unable to load document."; qCDebug(LANGUAGE) << "Parse error in line " << errorRow << ", col " << errorCol << ": " << errorMsg; return options; } QDomElement cfgElement = doc.documentElement(); if ( cfgElement.isNull() ) { qCDebug(LANGUAGE) << "No document in kcfg file"; return options; } QDomNodeList groups = cfgElement.elementsByTagName(QStringLiteral("group")); for (int i = 0; i < groups.size(); ++i) { QDomElement group = groups.at(i).toElement(); QList optionGroup; QString groupName = group.attribute(QStringLiteral("name")); QDomNodeList entries = group.elementsByTagName(QStringLiteral("entry")); for (int j = 0; j < entries.size(); ++j) { QDomElement entry = entries.at(j).toElement(); optionGroup << d->readEntry(entry, renderer); } options.insert(groupName, optionGroup); } return options; } void SourceFileTemplate::addAdditionalSearchLocation(const QString& location) { if(!d->searchLocations.contains(location)) d->searchLocations.append(location); } diff --git a/language/codegen/utilities.cpp b/language/codegen/utilities.cpp index f3a71e23b5..13cc35bc89 100644 --- a/language/codegen/utilities.cpp +++ b/language/codegen/utilities.cpp @@ -1,131 +1,131 @@ /* Copyright 2009 Ramón Zarazúa This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "utilities.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include namespace KDevelop { namespace CodeGenUtils { -IdentifierValidator::IdentifierValidator( DUContext * context) : QValidator(0), m_context(context) +IdentifierValidator::IdentifierValidator( DUContext * context) : QValidator(nullptr), m_context(context) { } IdentifierValidator::~IdentifierValidator() { } QValidator::State IdentifierValidator::validate (QString & input, int &) const { //I can't figure out why it wouln't compile when I tried to use Identifier identifier(); Identifier identifier = Identifier(IndexedString(input)); if(identifier.isUnique()) return Acceptable; DUChainReadLocker lock(DUChain::lock(), 10); - return m_context->findLocalDeclarations(identifier, CursorInRevision::invalid(), 0, AbstractType::Ptr(), DUContext::NoFiltering).empty() ? Acceptable : Invalid; + return m_context->findLocalDeclarations(identifier, CursorInRevision::invalid(), nullptr, AbstractType::Ptr(), DUContext::NoFiltering).empty() ? Acceptable : Invalid; } IndexedString fetchImplementationFileForClass(const Declaration & targetClass) { DUChainReadLocker lock(DUChain::lock()); qCDebug(LANGUAGE) << "Looking for implementation file for class:" << targetClass.identifier().toString(); DUContext * context = targetClass.internalContext(); //If this declaration is not a user defined type, then ignore and return empty file if(targetClass.kind() != Declaration::Type) return IndexedString(); //If this is a forward declaration attempt to resolve it. const Declaration * realClass = &targetClass; if(const ForwardDeclaration * forward = dynamic_cast(realClass)) { if(!(realClass = forward->resolve(context->topContext()))) return IndexedString(); context = realClass->internalContext(); } QVector declarations = context->localDeclarations(); QMap implementationsInFile; foreach(Declaration * decl, declarations) { ///@todo check for static variable instantiation as well if(ClassFunctionDeclaration * classFun = dynamic_cast(decl)) if(FunctionDefinition * def = FunctionDefinition::definition(classFun)) { qCDebug(LANGUAGE) << "Definition For declaration in:" << def->url().toUrl(); ++implementationsInFile[def->url()]; } } QMultiMap sorter; foreach(const IndexedString& file, implementationsInFile.keys()) sorter.insert(implementationsInFile[file], file); QList sortedFiles = sorter.values(); //If there are no methods, then just return the file the declaration is in if(sortedFiles.empty()) return context->url(); if(sortedFiles.size() == 1) return sortedFiles[0]; const QList tiedFiles = sorter.values(sorter.end().key()); if(tiedFiles.size() > 1) { //Return the file that has the most uses QMap > uses = realClass->uses(); IndexedString mostUsesFile; unsigned int mostUses = 0; foreach(const IndexedString& currentFile, tiedFiles) if(static_cast(uses[currentFile].size()) > mostUses) { mostUses = uses[currentFile].size(); mostUsesFile = currentFile; } return mostUsesFile; } else return sortedFiles.back(); } } } diff --git a/language/duchain/classdeclaration.cpp b/language/duchain/classdeclaration.cpp index fca7ef335f..881f1931ee 100644 --- a/language/duchain/classdeclaration.cpp +++ b/language/duchain/classdeclaration.cpp @@ -1,198 +1,198 @@ /* This file is part of KDevelop Copyright 2007 David Nolden Copyright 2009 Lior Mualem 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 "classdeclaration.h" #include "identifier.h" #include #include #include #include "types/structuretype.h" #include "util/debug.h" namespace KDevelop { DEFINE_LIST_MEMBER_HASH(ClassDeclarationData, baseClasses, BaseClassInstance) ClassDeclaration::ClassDeclaration(const KDevelop::RangeInRevision& range, DUContext* context) : ClassMemberDeclaration(*new ClassDeclarationData, range) { d_func_dynamic()->setClassId(this); setContext(context); } ClassDeclaration::ClassDeclaration( ClassDeclarationData& data, const KDevelop::RangeInRevision& range, DUContext* context ) : ClassMemberDeclaration( data, range ) { setContext(context); } ClassDeclaration::ClassDeclaration(ClassDeclarationData& data) : ClassMemberDeclaration(data) { } REGISTER_DUCHAIN_ITEM(ClassDeclaration); void ClassDeclaration::clearBaseClasses() { d_func_dynamic()->baseClassesList().clear(); } uint ClassDeclaration::baseClassesSize() const { return d_func()->baseClassesSize(); } const BaseClassInstance* ClassDeclaration::baseClasses() const { return d_func()->baseClasses(); } void ClassDeclaration::addBaseClass(const BaseClassInstance& klass) { d_func_dynamic()->baseClassesList().append(klass); } void ClassDeclaration::replaceBaseClass(uint n, const BaseClassInstance& klass) { Q_ASSERT(n <= d_func()->baseClassesSize()); d_func_dynamic()->baseClassesList()[n] = klass; } ClassDeclaration::~ClassDeclaration() { } ClassDeclaration::ClassDeclaration(const ClassDeclaration& rhs) : ClassMemberDeclaration(*new ClassDeclarationData(*rhs.d_func())) { d_func_dynamic()->setClassId(this); } Declaration* ClassDeclaration::clonePrivate() const { return new ClassDeclaration(*this); } namespace { bool isPublicBaseClassInternal( const ClassDeclaration* self, ClassDeclaration* base, const KDevelop::TopDUContext* topContext, int* baseConversionLevels, int depth, QSet* checked ) { if(checked) { if(checked->contains(self)) return false; checked->insert(self); } else if(depth > 3) { //Too much depth, to prevent endless recursion, we control the recursion using the 'checked' set QSet checkedSet; return isPublicBaseClassInternal(self, base, topContext, baseConversionLevels, depth, &checkedSet); } if( baseConversionLevels ) *baseConversionLevels = 0; if( self->indexedType() == base->indexedType() ) return true; FOREACH_FUNCTION(const BaseClassInstance& b, self->baseClasses) { if( baseConversionLevels ) ++ (*baseConversionLevels); //qCDebug(LANGUAGE) << "public base of" << c->toString() << "is" << b.baseClass->toString(); if( b.access != KDevelop::Declaration::Private ) { int nextBaseConversion = 0; if( StructureType::Ptr c = b.baseClass.type() ) { ClassDeclaration* decl = dynamic_cast(c->declaration(topContext)); if( decl && isPublicBaseClassInternal( decl, base, topContext, &nextBaseConversion, depth+1, checked ) ) { if ( baseConversionLevels ) *baseConversionLevels += nextBaseConversion; return true; } } } if( baseConversionLevels ) -- (*baseConversionLevels); } return false; } } bool ClassDeclaration::isPublicBaseClass( ClassDeclaration* base, const KDevelop::TopDUContext* topContext, int* baseConversionLevels ) const { - return isPublicBaseClassInternal( this, base, topContext, baseConversionLevels, 0, 0 ); + return isPublicBaseClassInternal( this, base, topContext, baseConversionLevels, 0, nullptr ); } QString ClassDeclaration::toString() const { QString ret; switch ( classModifier() ) { case ClassDeclarationData::None: //nothing break; case ClassDeclarationData::Abstract: ret += QLatin1String("abstract "); break; case ClassDeclarationData::Final: ret += QLatin1String("final "); break; } switch ( classType() ) { case ClassDeclarationData::Class: ret += QLatin1String("class "); break; case ClassDeclarationData::Interface: ret += QLatin1String("interface "); break; case ClassDeclarationData::Trait: ret += QLatin1String("trait "); break; case ClassDeclarationData::Union: ret += QLatin1String("union "); break; case ClassDeclarationData::Struct: ret += QLatin1String("struct "); break; } return ret + identifier().toString(); } ClassDeclarationData::ClassType ClassDeclaration::classType() const { return d_func()->m_classType; } void ClassDeclaration::setClassType(ClassDeclarationData::ClassType type) { d_func_dynamic()->m_classType = type; } ClassDeclarationData::ClassModifier ClassDeclaration::classModifier() const { return d_func()->m_classModifier; } void ClassDeclaration::setClassModifier(ClassDeclarationData::ClassModifier modifier) { d_func_dynamic()->m_classModifier = modifier; } } diff --git a/language/duchain/codemodel.cpp b/language/duchain/codemodel.cpp index 3e1e7551e6..ca8000cf81 100644 --- a/language/duchain/codemodel.cpp +++ b/language/duchain/codemodel.cpp @@ -1,361 +1,361 @@ /* This file is part of KDevelop 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 "codemodel.h" #include #include #include "appendedlist.h" #include "util/debug.h" #include #include "identifier.h" #include #include #include #define ifDebug(x) namespace KDevelop { class CodeModelItemHandler { public: static int leftChild(const CodeModelItem& m_data) { return (int)m_data.referenceCount; } static void setLeftChild(CodeModelItem& m_data, int child) { m_data.referenceCount = (uint)child; } static int rightChild(const CodeModelItem& m_data) { return (int)m_data.uKind; } static void setRightChild(CodeModelItem& m_data, int child) { m_data.uKind = (uint)child; } //Copies this item into the given one static void copyTo(const CodeModelItem& m_data, CodeModelItem& data) { data = m_data; } static void createFreeItem(CodeModelItem& data) { data = CodeModelItem(); data.referenceCount = (uint)-1; data.uKind = (uint)-1; } static bool isFree(const CodeModelItem& m_data) { return !m_data.id.isValid(); } static const CodeModelItem& data(const CodeModelItem& m_data) { return m_data; } static bool equals(const CodeModelItem& m_data, const CodeModelItem& rhs) { return m_data.id == rhs.id; } }; DEFINE_LIST_MEMBER_HASH(CodeModelRepositoryItem, items, CodeModelItem) class CodeModelRepositoryItem { public: CodeModelRepositoryItem() : centralFreeItem(-1) { initializeAppendedLists(); } CodeModelRepositoryItem(const CodeModelRepositoryItem& rhs, bool dynamic = true) : file(rhs.file), centralFreeItem(rhs.centralFreeItem) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~CodeModelRepositoryItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return file.index(); } uint itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(CodeModelRepositoryItem); } IndexedString file; int centralFreeItem; START_APPENDED_LISTS(CodeModelRepositoryItem); APPENDED_LIST_FIRST(CodeModelRepositoryItem, CodeModelItem, items); END_APPENDED_LISTS(CodeModelRepositoryItem, items); }; class CodeModelRequestItem { public: CodeModelRequestItem(const CodeModelRepositoryItem& item) : m_item(item) { } enum { AverageSize = 30 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.itemSize(); } void createItem(CodeModelRepositoryItem* item) const { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); new (item) CodeModelRepositoryItem(m_item, false); } static void destroy(CodeModelRepositoryItem* item, KDevelop::AbstractItemRepository&) { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); // Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); item->~CodeModelRepositoryItem(); } static bool persistent(const CodeModelRepositoryItem* item) { Q_UNUSED(item); return true; } bool equals(const CodeModelRepositoryItem* item) const { return m_item.file == item->file; } const CodeModelRepositoryItem& m_item; }; class CodeModelPrivate { public: CodeModelPrivate() : m_repository(QStringLiteral("Code Model")) { } //Maps declaration-ids to items ItemRepository m_repository; }; CodeModel::CodeModel() : d(new CodeModelPrivate()) { } CodeModel::~CodeModel() { delete d; } void CodeModel::addItem(const IndexedString& file, const IndexedQualifiedIdentifier& id, CodeModelItem::Kind kind) { ifDebug( qCDebug(LANGUAGE) << "addItem" << file.str() << id.identifier().toString() << id.index; ) if(!id.isValid()) return; CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); CodeModelItem newItem; newItem.id = id; newItem.kind = kind; newItem.referenceCount = 1; if(index) { const CodeModelRepositoryItem* oldItem = d->m_repository.itemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(newItem); QMutexLocker lock(d->m_repository.mutex()); DynamicItem editableItem = d->m_repository.dynamicItemFromIndex(index); CodeModelItem* items = const_cast(editableItem->items()); if(listIndex != -1) { //Only update the reference-count ++items[listIndex].referenceCount; items[listIndex].kind = kind; return; }else{ //Add the item to the list EmbeddedTreeAddItem add(items, editableItem->itemsSize(), editableItem->centralFreeItem, newItem); if(add.newItemCount() != editableItem->itemsSize()) { //The data needs to be transferred into a bigger list. That list is within "item". item.itemsList().resize(add.newItemCount()); add.transferData(item.itemsList().data(), item.itemsList().size(), &item.centralFreeItem); d->m_repository.deleteItem(index); }else{ //We're fine: The item fits into the existing list. return; } } }else{ //We're creating a new index item.itemsList().append(newItem); } Q_ASSERT(!d->m_repository.findIndex(request)); //This inserts the changed item volatile uint newIndex = d->m_repository.index(request); Q_UNUSED(newIndex); ifDebug( qCDebug(LANGUAGE) << "new index" << newIndex; ) Q_ASSERT(d->m_repository.findIndex(request)); } void CodeModel::updateItem(const IndexedString& file, const IndexedQualifiedIdentifier& id, CodeModelItem::Kind kind) { ifDebug( qCDebug(LANGUAGE) << file.str() << id.identifier().toString() << kind; ) if(!id.isValid()) return; CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); CodeModelItem newItem; newItem.id = id; newItem.kind = kind; newItem.referenceCount = 1; uint index = d->m_repository.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item QMutexLocker lock(d->m_repository.mutex()); DynamicItem oldItem = d->m_repository.dynamicItemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(newItem); Q_ASSERT(listIndex != -1); CodeModelItem* items = const_cast(oldItem->items()); Q_ASSERT(items[listIndex].id == id); items[listIndex].kind = kind; return; } Q_ASSERT(0); //The updated item as not in the symbol table! } void CodeModel::removeItem(const IndexedString& file, const IndexedQualifiedIdentifier& id) //void CodeModel::removeDeclaration(const QualifiedIdentifier& id, const IndexedDeclaration& declaration) { if(!id.isValid()) return; ifDebug( qCDebug(LANGUAGE) << "removeItem" << file.str() << id.identifier().toString(); ) CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); if(index) { CodeModelItem searchItem; searchItem.id = id; QMutexLocker lock(d->m_repository.mutex()); DynamicItem oldItem = d->m_repository.dynamicItemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(searchItem); if(listIndex == -1) return; CodeModelItem* items = const_cast(oldItem->items()); --items[listIndex].referenceCount; if(oldItem->items()[listIndex].referenceCount) return; //Nothing to remove, there's still a reference-count left //We have reduced the reference-count to zero, so remove the item from the list EmbeddedTreeRemoveItem remove(items, oldItem->itemsSize(), oldItem->centralFreeItem, searchItem); uint newItemCount = remove.newItemCount(); if(newItemCount != oldItem->itemsSize()) { if(newItemCount == 0) { //Has become empty, delete the item d->m_repository.deleteItem(index); return; }else{ //Make smaller item.itemsList().resize(newItemCount); remove.transferData(item.itemsList().data(), item.itemsSize(), &item.centralFreeItem); //Delete the old list d->m_repository.deleteItem(index); //Add the new list d->m_repository.index(request); return; } } } } void CodeModel::items(const IndexedString& file, uint& count, const CodeModelItem*& items) const { ifDebug( qCDebug(LANGUAGE) << "items" << file.str(); ) CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); if(index) { const CodeModelRepositoryItem* repositoryItem = d->m_repository.itemFromIndex(index); ifDebug( qCDebug(LANGUAGE) << "found index" << index << repositoryItem->itemsSize(); ) count = repositoryItem->itemsSize(); items = repositoryItem->items(); }else{ ifDebug( qCDebug(LANGUAGE) << "found no index"; ) count = 0; - items = 0; + items = nullptr; } } CodeModel& CodeModel::self() { static CodeModel ret; return ret; } } diff --git a/language/duchain/declaration.cpp b/language/duchain/declaration.cpp index 4a03f26ad6..fc937a3041 100644 --- a/language/duchain/declaration.cpp +++ b/language/duchain/declaration.cpp @@ -1,798 +1,798 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "declaration.h" #include "declarationdata.h" #include #include #include "topducontext.h" #include "topducontextdynamicdata.h" #include "use.h" #include "forwarddeclaration.h" #include "duchain.h" #include "duchainlock.h" #include "ducontextdata.h" #include "declarationid.h" #include "uses.h" #include #include "duchainregister.h" #include "persistentsymboltable.h" #include "types/identifiedtype.h" #include "types/structuretype.h" #include "functiondefinition.h" #include "codemodel.h" #include "specializationstore.h" #include "types/typeutils.h" #include "types/typealiastype.h" #include "classdeclaration.h" #include "serialization/stringrepository.h" #include "ducontextdynamicdata.h" namespace KDevelop { REGISTER_DUCHAIN_ITEM(Declaration); DeclarationData::DeclarationData() : m_comment(0) , m_isDefinition(false) , m_inSymbolTable(false) , m_isTypeAlias(false) , m_anonymousInContext(false) , m_isDeprecated(false) , m_alwaysForceDirect(false) , m_isAutoDeclaration(false) , m_isExplicitlyDeleted(false) { m_kind = Declaration::Instance; } DeclarationData::DeclarationData( const DeclarationData& rhs ) : DUChainBaseData(rhs), m_internalContext(rhs.m_internalContext), m_type(rhs.m_type), m_identifier(rhs.m_identifier), m_declaration(rhs.m_declaration), m_comment(rhs.m_comment), m_kind(rhs.m_kind), m_isDefinition(rhs.m_isDefinition), m_inSymbolTable(rhs.m_inSymbolTable), m_isTypeAlias(rhs.m_isTypeAlias), m_anonymousInContext(rhs.m_anonymousInContext), m_isDeprecated(rhs.m_isDeprecated), m_alwaysForceDirect(rhs.m_alwaysForceDirect), m_isAutoDeclaration(rhs.m_isAutoDeclaration), m_isExplicitlyDeleted(rhs.m_isExplicitlyDeleted) { } ///@todo Use reference counting static Repositories::StringRepository& commentRepository() { static Repositories::StringRepository commentRepositoryObject(QStringLiteral("Comment Repository")); return commentRepositoryObject; } void initDeclarationRepositories() { commentRepository(); } Declaration::Kind Declaration::kind() const { DUCHAIN_D(Declaration); return d->m_kind; } void Declaration::setKind(Kind kind) { DUCHAIN_D_DYNAMIC(Declaration); d->m_kind = kind; updateCodeModel(); } bool Declaration::inDUChain() const { DUCHAIN_D(Declaration); if( d->m_anonymousInContext ) return false; if( !context() ) return false; TopDUContext* top = topContext(); return top && top->inDUChain(); } Declaration::Declaration( const RangeInRevision& range, DUContext* context ) : DUChainBase(*new DeclarationData, range) { d_func_dynamic()->setClassId(this); - m_topContext = 0; - m_context = 0; + m_topContext = nullptr; + m_context = nullptr; m_indexInTopContext = 0; if(context) setContext(context); } uint Declaration::ownIndex() const { ENSURE_CAN_READ return m_indexInTopContext; } Declaration::Declaration(const Declaration& rhs) : DUChainBase(*new DeclarationData( *rhs.d_func() )) { - m_topContext = 0; - m_context = 0; + m_topContext = nullptr; + m_context = nullptr; m_indexInTopContext = 0; } Declaration::Declaration( DeclarationData & dd ) : DUChainBase(dd) { - m_topContext = 0; - m_context = 0; + m_topContext = nullptr; + m_context = nullptr; m_indexInTopContext = 0; } Declaration::Declaration( DeclarationData & dd, const RangeInRevision& range ) : DUChainBase(dd, range) { - m_topContext = 0; - m_context = 0; + m_topContext = nullptr; + m_context = nullptr; m_indexInTopContext = 0; } bool Declaration::persistentlyDestroying() const { TopDUContext* topContext = this->topContext(); return !topContext->deleting() || !topContext->isOnDisk(); } Declaration::~Declaration() { uint oldOwnIndex = m_indexInTopContext; TopDUContext* topContext = this->topContext(); //Only perform the actions when the top-context isn't being deleted, or when it hasn't been stored to disk if(persistentlyDestroying()) { DUCHAIN_D_DYNAMIC(Declaration); // Inserted by the builder after construction has finished. if( d->m_internalContext.context() ) - d->m_internalContext.context()->setOwner(0); + d->m_internalContext.context()->setOwner(nullptr); setInSymbolTable(false); } // If the parent-context already has dynamic data, like for example any temporary context, // always delete the declaration, to not create crashes within more complex code like C++ template stuff. if (context() && !d_func()->m_anonymousInContext) { if(!topContext->deleting() || !topContext->isOnDisk() || context()->d_func()->isDynamic()) context()->m_dynamicData->removeDeclaration(this); } clearOwnIndex(); if(!topContext->deleting() || !topContext->isOnDisk()) { - setContext(0); + setContext(nullptr); setAbstractType(AbstractType::Ptr()); } Q_ASSERT(d_func()->isDynamic() == (!topContext->deleting() || !topContext->isOnDisk() || topContext->m_dynamicData->isTemporaryDeclarationIndex(oldOwnIndex))); Q_UNUSED(oldOwnIndex); } QByteArray Declaration::comment() const { DUCHAIN_D(Declaration); if(!d->m_comment) - return 0; + return nullptr; else return Repositories::arrayFromItem(commentRepository().itemFromIndex(d->m_comment)); } void Declaration::setComment(const QByteArray& str) { DUCHAIN_D_DYNAMIC(Declaration); if(str.isEmpty()) d->m_comment = 0; else d->m_comment = commentRepository().index(Repositories::StringRepositoryItemRequest(str, IndexedString::hashString(str, str.length()), str.length())); } void Declaration::setComment(const QString& str) { setComment(str.toUtf8()); } Identifier Declaration::identifier( ) const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_identifier.identifier(); } const IndexedIdentifier& Declaration::indexedIdentifier( ) const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_identifier; } void Declaration::rebuildDynamicData(DUContext* parent, uint ownIndex) { DUChainBase::rebuildDynamicData(parent, ownIndex); m_context = parent; m_topContext = parent->topContext(); m_indexInTopContext = ownIndex; } void Declaration::setIdentifier(const Identifier& identifier) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(Declaration); bool wasInSymbolTable = d->m_inSymbolTable; setInSymbolTable(false); d->m_identifier = identifier; setInSymbolTable(wasInSymbolTable); } IndexedType Declaration::indexedType() const { return d_func()->m_type; } AbstractType::Ptr Declaration::abstractType( ) const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_type.abstractType(); } void Declaration::setAbstractType(AbstractType::Ptr type) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(Declaration); d->m_type = type ? type->indexed() : IndexedType(); updateCodeModel(); } Declaration* Declaration::specialize(const IndexedInstantiationInformation& /*specialization*/, const TopDUContext* topContext, int /*upDistance*/) { if(!topContext) - return 0; + return nullptr; return this; } QualifiedIdentifier Declaration::qualifiedIdentifier() const { ENSURE_CAN_READ QualifiedIdentifier ret; DUContext* ctx = m_context; if(ctx) ret = ctx->scopeIdentifier(true); ret.push(d_func()->m_identifier); return ret; } DUContext * Declaration::context() const { //ENSURE_CAN_READ Commented out for performance reasons return m_context; } bool Declaration::isAnonymous() const { return d_func()->m_anonymousInContext; } void Declaration::setContext(DUContext* context, bool anonymous) { Q_ASSERT(!context || context->topContext()); DUCHAIN_D_DYNAMIC(Declaration); if (context == m_context && anonymous == d->m_anonymousInContext) { // skip costly operations below when the same context is set // this happens often when updating a TopDUContext from the cache return; } setInSymbolTable(false); //We don't need to clear, because it's not allowed to move from one top-context into another // clearOwnIndex(); if (m_context && context) { Q_ASSERT(m_context->topContext() == context->topContext()); } if (m_context) { if( !d->m_anonymousInContext ) { m_context->m_dynamicData->removeDeclaration(this); } } if(context) m_topContext = context->topContext(); else - m_topContext = 0; + m_topContext = nullptr; d->m_anonymousInContext = anonymous; m_context = context; if (context) { if(!m_indexInTopContext) allocateOwnIndex(); if(!d->m_anonymousInContext) { context->m_dynamicData->addDeclaration(this); } if(context->inSymbolTable() && !anonymous) setInSymbolTable(true); } } void Declaration::clearOwnIndex() { if(!m_indexInTopContext) return; if(!context() || (!d_func()->m_anonymousInContext && !context()->isAnonymous())) { ENSURE_CAN_WRITE } if(m_indexInTopContext) { Q_ASSERT(m_topContext); m_topContext->m_dynamicData->clearDeclarationIndex(this); } m_indexInTopContext = 0; } void Declaration::allocateOwnIndex() { ///@todo Fix multithreading stuff with template instantiation, preferably using some internal mutexes // if(context() && (!context()->isAnonymous() && !d_func()->m_anonymousInContext)) { // ENSURE_CAN_WRITE // } Q_ASSERT(m_topContext); m_indexInTopContext = m_topContext->m_dynamicData->allocateDeclarationIndex(this, d_func()->m_anonymousInContext || !context() || context()->isAnonymous()); Q_ASSERT(m_indexInTopContext); if(!m_topContext->m_dynamicData->getDeclarationForIndex(m_indexInTopContext)) qFatal("Could not re-retrieve declaration\nindex: %d", m_indexInTopContext); } const Declaration* Declaration::logicalDeclaration(const TopDUContext* topContext) const { ENSURE_CAN_READ if(isForwardDeclaration()) { const auto dec = static_cast(this); Declaration* ret = dec->resolve(topContext); if(ret) return ret; } return this; } Declaration* Declaration::logicalDeclaration(const TopDUContext* topContext) { ENSURE_CAN_READ if(isForwardDeclaration()) { const auto dec = static_cast(this); Declaration* ret = dec->resolve(topContext); if(ret) return ret; } return this; } DUContext * Declaration::logicalInternalContext(const TopDUContext* topContext) const { ENSURE_CAN_READ if(!isDefinition()) { Declaration* def = FunctionDefinition::definition(this); if( def ) return def->internalContext(); } if( d_func()->m_isTypeAlias ) { ///If this is a type-alias, return the internal context of the actual type. TypeAliasType::Ptr t = type(); if(t) { AbstractType::Ptr target = t->type(); IdentifiedType* idType = dynamic_cast(target.data()); if( idType ) { Declaration* decl = idType->declaration(topContext); if(decl && decl != this) { return decl->logicalInternalContext( topContext ); } } } } return internalContext(); } DUContext * Declaration::internalContext() const { // ENSURE_CAN_READ return d_func()->m_internalContext.context(); } void Declaration::setInternalContext(DUContext* context) { if(this->context()) { ENSURE_CAN_WRITE } DUCHAIN_D_DYNAMIC(Declaration); if( context == d->m_internalContext.context() ) return; if(!m_topContext) { //Take the top-context from the other side. We need to allocate an index, so we can safely call setOwner(..) m_topContext = context->topContext(); allocateOwnIndex(); } DUContext* oldInternalContext = d->m_internalContext.context(); d->m_internalContext = context; //Q_ASSERT( !oldInternalContext || oldInternalContext->owner() == this ); if( oldInternalContext && oldInternalContext->owner() == this ) - oldInternalContext->setOwner(0); + oldInternalContext->setOwner(nullptr); if( context ) context->setOwner(this); } bool Declaration::operator ==(const Declaration & other) const { ENSURE_CAN_READ return this == &other; } QString Declaration::toString() const { return QStringLiteral("%3 %4").arg(abstractType() ? abstractType()->toString() : QStringLiteral(""), identifier().toString()); } bool Declaration::isDefinition() const { ENSURE_CAN_READ DUCHAIN_D(Declaration); return d->m_isDefinition; } void Declaration::setDeclarationIsDefinition(bool dd) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(Declaration); d->m_isDefinition = dd; // if (d->m_isDefinition && definition()) { // setDefinition(0); // } } bool Declaration::isAutoDeclaration() const { return d_func()->m_isAutoDeclaration; } void Declaration::setAutoDeclaration(bool _auto) { d_func_dynamic()->m_isAutoDeclaration = _auto; } bool Declaration::isDeprecated() const { return d_func()->m_isDeprecated; } void Declaration::setDeprecated(bool deprecated) { d_func_dynamic()->m_isDeprecated = deprecated; } bool Declaration::alwaysForceDirect() const { return d_func()->m_alwaysForceDirect; } void Declaration::setAlwaysForceDirect(bool direct) { d_func_dynamic()->m_alwaysForceDirect = direct; } bool Declaration::isExplicitlyDeleted() const { return d_func()->m_isExplicitlyDeleted; } void Declaration::setExplicitlyDeleted(bool deleted) { d_func_dynamic()->m_isExplicitlyDeleted = deleted; } ///@todo see whether it would be useful to create an own TypeAliasDeclaration sub-class for this bool Declaration::isTypeAlias() const { DUCHAIN_D(Declaration); return d->m_isTypeAlias; } void Declaration::setIsTypeAlias(bool isTypeAlias) { DUCHAIN_D_DYNAMIC(Declaration); d->m_isTypeAlias = isTypeAlias; } IndexedInstantiationInformation Declaration::specialization() const { return IndexedInstantiationInformation(); } void Declaration::activateSpecialization() { if(specialization().index()) { DeclarationId baseId(id()); baseId.setSpecialization(IndexedInstantiationInformation()); SpecializationStore::self().set(baseId, specialization()); } } DeclarationId Declaration::id(bool forceDirect) const { ENSURE_CAN_READ if(inSymbolTable() && !forceDirect && !alwaysForceDirect()) return DeclarationId(qualifiedIdentifier(), additionalIdentity(), specialization()); else return DeclarationId(IndexedDeclaration(const_cast(this)), specialization()); } bool Declaration::inSymbolTable() const { DUCHAIN_D(Declaration); return d->m_inSymbolTable; } CodeModelItem::Kind kindForDeclaration(Declaration* decl) { CodeModelItem::Kind kind = CodeModelItem::Unknown; if(decl->kind() == Declaration::Namespace) return CodeModelItem::Namespace; if(decl->isFunctionDeclaration()) { kind = CodeModelItem::Function; } if(decl->kind() == Declaration::Type && (decl->type() || dynamic_cast(decl))) kind = CodeModelItem::Class; if(kind == CodeModelItem::Unknown && decl->kind() == Declaration::Instance) kind = CodeModelItem::Variable; if(decl->isForwardDeclaration()) kind = (CodeModelItem::Kind)(kind | CodeModelItem::ForwardDeclaration); if ( decl->context() && decl->context()->type() == DUContext::Class ) kind = (CodeModelItem::Kind)(kind | CodeModelItem::ClassMember); return kind; } void Declaration::updateCodeModel() { DUCHAIN_D(Declaration); if(!d->m_identifier.isEmpty() && d->m_inSymbolTable) { QualifiedIdentifier id(qualifiedIdentifier()); CodeModel::self().updateItem(url(), id, kindForDeclaration(this)); } } void Declaration::setInSymbolTable(bool inSymbolTable) { DUCHAIN_D_DYNAMIC(Declaration); if(!d->m_identifier.isEmpty()) { if(!d->m_inSymbolTable && inSymbolTable) { QualifiedIdentifier id(qualifiedIdentifier()); PersistentSymbolTable::self().addDeclaration(id, this); CodeModel::self().addItem(url(), id, kindForDeclaration(this)); } else if(d->m_inSymbolTable && !inSymbolTable) { QualifiedIdentifier id(qualifiedIdentifier()); PersistentSymbolTable::self().removeDeclaration(id, this); CodeModel::self().removeItem(url(), id); } } d->m_inSymbolTable = inSymbolTable; } TopDUContext * Declaration::topContext() const { return m_topContext; } Declaration* Declaration::clonePrivate() const { return new Declaration(*this); } Declaration* Declaration::clone() const { Declaration* ret = clonePrivate(); ret->d_func_dynamic()->m_inSymbolTable = false; return ret; } bool Declaration::isForwardDeclaration() const { return false; } bool Declaration::isFunctionDeclaration() const { return false; } uint Declaration::additionalIdentity() const { return 0; } bool Declaration::equalQualifiedIdentifier(const Declaration* rhs) const { ENSURE_CAN_READ DUCHAIN_D(Declaration); if(d->m_identifier != rhs->d_func()->m_identifier) return false; return m_context->equalScopeIdentifier(m_context); } QMap > Declaration::uses() const { ENSURE_CAN_READ QMap > tempUses; //First, search for uses within the own context { QMap& ranges(tempUses[topContext()->url()]); foreach(const RangeInRevision range, allUses(topContext(), const_cast(this))) ranges[range] = true; } DeclarationId _id = id(); KDevVarLengthArray useContexts = DUChain::uses()->uses(_id); if (!_id.isDirect()) { // also check uses based on direct IDs KDevVarLengthArray directUseContexts = DUChain::uses()->uses(id(true)); useContexts.append(directUseContexts.data(), directUseContexts.size()); } foreach (const IndexedTopDUContext indexedContext, useContexts) { TopDUContext* context = indexedContext.data(); if(context) { QMap& ranges(tempUses[context->url()]); foreach(const RangeInRevision range, allUses(context, const_cast(this))) ranges[range] = true; } } QMap > ret; for(QMap >::const_iterator it = tempUses.constBegin(); it != tempUses.constEnd(); ++it) { if(!(*it).isEmpty()) { QList& list(ret[it.key()]); for(QMap::const_iterator it2 = (*it).constBegin(); it2 != (*it).constEnd(); ++it2) list << it2.key(); } } return ret; } bool hasDeclarationUse(DUContext* context, int declIdx) { bool ret=false; int usescount=context->usesCount(); const Use* uses=context->uses(); for(int i=0; !ret && ichildContexts()) { ret = ret || hasDeclarationUse(child, declIdx); if(ret) break; } return ret; } bool Declaration::hasUses() const { ENSURE_CAN_READ int idx = topContext()->indexForUsedDeclaration(const_cast(this), false); bool ret = idx != std::numeric_limits::max() && (idx>=0 || hasDeclarationUse(topContext(), idx)); //hasLocalUses DeclarationId myId = id(); if (!ret && DUChain::uses()->hasUses(myId)) { ret = true; } if (!ret && !myId.isDirect() && DUChain::uses()->hasUses(id(true))) { ret = true; } return ret; } QMap > Declaration::usesCurrentRevision() const { ENSURE_CAN_READ QMap > tempUses; //First, search for uses within the own context { QMap& ranges(tempUses[topContext()->url()]); foreach(const RangeInRevision range, allUses(topContext(), const_cast(this))) { ranges[topContext()->transformFromLocalRevision(range)] = true; } } DeclarationId _id = id(); KDevVarLengthArray useContexts = DUChain::uses()->uses(_id); if (!_id.isDirect()) { // also check uses based on direct IDs KDevVarLengthArray directUseContexts = DUChain::uses()->uses(id(true)); useContexts.append(directUseContexts.data(), directUseContexts.size()); } foreach (const IndexedTopDUContext indexedContext, useContexts) { TopDUContext* context = indexedContext.data(); if(context) { QMap& ranges(tempUses[context->url()]); foreach(const RangeInRevision range, allUses(context, const_cast(this))) ranges[context->transformFromLocalRevision(range)] = true; } } QMap > ret; for(QMap >::const_iterator it = tempUses.constBegin(); it != tempUses.constEnd(); ++it) { if(!(*it).isEmpty()) { QList& list(ret[it.key()]); for(QMap::const_iterator it2 = (*it).constBegin(); it2 != (*it).constEnd(); ++it2) list << it2.key(); } } return ret; } } diff --git a/language/duchain/declarationid.cpp b/language/duchain/declarationid.cpp index ffd4c9f23d..e767b3bbc6 100644 --- a/language/duchain/declarationid.cpp +++ b/language/duchain/declarationid.cpp @@ -1,245 +1,245 @@ /* This file is part of KDevelop 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 "declarationid.h" #include "ducontext.h" #include "topducontext.h" #include "duchain.h" #include "declaration.h" #include "persistentsymboltable.h" #include "instantiationinformation.h" #include "../editor/cursorinrevision.h" #include namespace KDevelop { DeclarationId::DeclarationId(const IndexedQualifiedIdentifier& id, uint additionalId, const IndexedInstantiationInformation& specialization) : m_indirectData{id, additionalId} , m_isDirect(false) , m_specialization(specialization) { } DeclarationId::DeclarationId(const IndexedDeclaration& decl, const IndexedInstantiationInformation& specialization) : m_directData(decl) , m_isDirect(true) , m_specialization(specialization) { } DeclarationId::DeclarationId(const DeclarationId& rhs) : m_isDirect(rhs.m_isDirect) , m_specialization(rhs.m_specialization) { if (!m_isDirect) { // IndexedQualifiedIdentifier doesn't like zero-initialization... new (&m_indirectData.identifier) IndexedQualifiedIdentifier(rhs.m_indirectData.identifier); m_indirectData.additionalIdentity = rhs.m_indirectData.additionalIdentity; } else { m_directData = rhs.m_directData; } } DeclarationId::~DeclarationId() { if (!m_isDirect) { m_indirectData.~Indirect(); } } DeclarationId& DeclarationId::operator=(const DeclarationId& rhs) { if (&rhs == this) return *this; m_isDirect = rhs.m_isDirect; m_specialization = rhs.m_specialization; if (!m_isDirect) { m_indirectData = rhs.m_indirectData; } else { m_directData = rhs.m_directData; } return *this; } bool DeclarationId::isDirect() const { return m_isDirect; } void DeclarationId::setSpecialization(const IndexedInstantiationInformation& spec) { m_specialization = spec; } IndexedInstantiationInformation DeclarationId::specialization() const { return m_specialization; } KDevVarLengthArray DeclarationId::getDeclarations(const TopDUContext* top) const { KDevVarLengthArray ret; if(m_isDirect == false) { //Find the declaration by its qualified identifier and additionalIdentity QualifiedIdentifier id(m_indirectData.identifier); if(top) { //Do filtering PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(id, top->recursiveImportIndices()); for(; filter; ++filter) { Declaration* decl = filter->data(); if(decl && m_indirectData.additionalIdentity == decl->additionalIdentity()) { //Hit ret.append(decl); } } }else{ //Just accept anything PersistentSymbolTable::Declarations decls = PersistentSymbolTable::self().getDeclarations(id); PersistentSymbolTable::Declarations::Iterator decl = decls.iterator(); for(; decl; ++decl) { const IndexedDeclaration& iDecl(*decl); ///@todo think this over once we don't pull in all imported top-context any more //Don't trigger loading of top-contexts from here, it will create a lot of problems if((!DUChain::self()->isInMemory(iDecl.topContextIndex()))) continue; if(!top) { Declaration* decl = iDecl.data(); if(decl && m_indirectData.additionalIdentity == decl->additionalIdentity()) { //Hit ret.append(decl); } } } } }else{ Declaration* decl = m_directData.declaration(); if(decl) ret.append(decl); } if(!ret.isEmpty() && m_specialization.index()) { KDevVarLengthArray newRet; foreach (Declaration* decl, ret) { Declaration* specialized = decl->specialize(m_specialization, top ? top : decl->topContext()); if(specialized) newRet.append(specialized); } return newRet; } return ret; } Declaration* DeclarationId::getDeclaration(const TopDUContext* top, bool instantiateIfRequired) const { - Declaration* ret = 0; + Declaration* ret = nullptr; if(m_isDirect == false) { //Find the declaration by its qualified identifier and additionalIdentity QualifiedIdentifier id(m_indirectData.identifier); if(top) { //Do filtering PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(id, top->recursiveImportIndices()); for(; filter; ++filter) { Declaration* decl = filter->data(); if(decl && m_indirectData.additionalIdentity == decl->additionalIdentity()) { //Hit ret = decl; if(!ret->isForwardDeclaration()) break; } } }else{ //Just accept anything PersistentSymbolTable::Declarations decls = PersistentSymbolTable::self().getDeclarations(id); PersistentSymbolTable::Declarations::Iterator decl = decls.iterator(); for(; decl; ++decl) { const IndexedDeclaration& iDecl(*decl); ///@todo think this over once we don't pull in all imported top-context any more //Don't trigger loading of top-contexts from here, it will create a lot of problems if((!DUChain::self()->isInMemory(iDecl.topContextIndex()))) continue; if(!top) { Declaration* decl = iDecl.data(); if(decl && m_indirectData.additionalIdentity == decl->additionalIdentity()) { //Hit ret = decl; if(!ret->isForwardDeclaration()) break; } } } } }else{ //Find the declaration by m_topContext and m_declaration ret = m_directData.declaration(); } if(ret) { if(m_specialization.isValid()) { const TopDUContext* topContextForSpecialization = top; if(!instantiateIfRequired) - topContextForSpecialization = 0; //If we don't want to instantiate new declarations, set the top-context to zero, so specialize(..) will only look-up + topContextForSpecialization = nullptr; //If we don't want to instantiate new declarations, set the top-context to zero, so specialize(..) will only look-up else if(!topContextForSpecialization) topContextForSpecialization = ret->topContext(); return ret->specialize(m_specialization, topContextForSpecialization); }else{ return ret; } }else - return 0; + return nullptr; } QualifiedIdentifier DeclarationId::qualifiedIdentifier() const { if(!m_isDirect) { QualifiedIdentifier baseIdentifier = m_indirectData.identifier.identifier(); if(!m_specialization.index()) return baseIdentifier; return m_specialization.information().applyToIdentifier(baseIdentifier); } else { - Declaration* decl = getDeclaration(0); + Declaration* decl = getDeclaration(nullptr); if(decl) return decl->qualifiedIdentifier(); return QualifiedIdentifier(i18n("(unknown direct declaration)")); } return QualifiedIdentifier(i18n("(missing)")) + m_indirectData.identifier.identifier(); } } diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index b825371ac7..7856360677 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1751 +1,1751 @@ /* This is part of KDevelop Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain.h" #include "duchainlock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "../interfaces/icodehighlighting.h" #include "../backgroundparser/backgroundparser.h" #include "util/debug.h" #include "language-features.h" #include "topducontext.h" #include "topducontextdata.h" #include "topducontextdynamicdata.h" #include "parsingenvironment.h" #include "declaration.h" #include "definitions.h" #include "duchainutils.h" #include "use.h" #include "uses.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "persistentsymboltable.h" #include "serialization/itemrepository.h" #include "waitforupdate.h" #include "importers.h" #if HAVE_MALLOC_TRIM #include "malloc.h" #endif namespace { //Additional "soft" cleanup steps that are done before the actual cleanup. //During "soft" cleanup, the consistency is not guaranteed. The repository is //marked to be updating during soft cleanup, so if kdevelop crashes, it will be cleared. //The big advantage of the soft cleanup steps is, that the duchain is always only locked for //short times, which leads to no lockup in the UI. const int SOFT_CLEANUP_STEPS = 1; const uint cleanupEverySeconds = 200; ///Approximate maximum count of top-contexts that are checked during final cleanup const uint maxFinalCleanupCheckContexts = 2000; const uint minimumFinalCleanupCheckContextsPercentage = 10; //Check at least n% of all top-contexts during cleanup //Set to true as soon as the duchain is deleted } namespace KDevelop { bool DUChain::m_deleted = false; ///Must be locked through KDevelop::SpinLock before using chainsByIndex ///This lock should be locked only for very short times QMutex DUChain::chainsByIndexLock; std::vector DUChain::chainsByIndex; //This thing is not actually used, but it's needed for compiling DEFINE_LIST_MEMBER_HASH(EnvironmentInformationListItem, items, uint) //An entry for the item-repository that holds some meta-data. Behind this entry, the actual ParsingEnvironmentFileData is stored. class EnvironmentInformationItem { public: EnvironmentInformationItem(uint topContext, uint size) : m_topContext(topContext), m_size(size) { } ~EnvironmentInformationItem() { } unsigned int hash() const { return m_topContext; } unsigned int itemSize() const { return sizeof(*this) + m_size; } uint m_topContext; uint m_size;//Size of the data behind, that holds the actual item }; struct ItemRepositoryIndexHash { uint operator()(unsigned int __x) const { return 173*(__x>>2) + 11 * (__x >> 16); } }; class EnvironmentInformationRequest { public: ///This constructor should only be used for lookup - EnvironmentInformationRequest(uint topContextIndex) : m_file(0), m_index(topContextIndex) { + EnvironmentInformationRequest(uint topContextIndex) : m_file(nullptr), m_index(topContextIndex) { } EnvironmentInformationRequest(const ParsingEnvironmentFile* file) : m_file(file), m_index(file->indexedTopContext().index()) { } enum { AverageSize = 32 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_index; } uint itemSize() const { return sizeof(EnvironmentInformationItem) + DUChainItemSystem::self().dynamicSize(*m_file->d_func()); } void createItem(EnvironmentInformationItem* item) const { new (item) EnvironmentInformationItem(m_index, DUChainItemSystem::self().dynamicSize(*m_file->d_func())); Q_ASSERT(m_file->d_func()->m_dynamic); DUChainBaseData* data = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); DUChainItemSystem::self().copy(*m_file->d_func(), *data, true); Q_ASSERT(data->m_range == m_file->d_func()->m_range); Q_ASSERT(data->classId == m_file->d_func()->classId); Q_ASSERT(data->m_dynamic == false); } static void destroy(EnvironmentInformationItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationItem(); //We don't need to call the destructor, because that's done in DUChainBase::makeDynamic() //We just need to make sure that every environment-file is dynamic when it's deleted // DUChainItemSystem::self().callDestructor((DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))); } static bool persistent(const EnvironmentInformationItem* ) { //Cleanup done separately return true; } bool equals(const EnvironmentInformationItem* item) const { return m_index == item->m_topContext; } const ParsingEnvironmentFile* m_file; uint m_index; }; ///A list of environment-information/top-contexts mapped to a file-name class EnvironmentInformationListItem { public: EnvironmentInformationListItem() { initializeAppendedLists(true); } EnvironmentInformationListItem(const EnvironmentInformationListItem& rhs, bool dynamic = true) { initializeAppendedLists(dynamic); m_file = rhs.m_file; copyListsFrom(rhs); } ~EnvironmentInformationListItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return m_file.hash(); } unsigned short int itemSize() const { return dynamicSize(); } IndexedString m_file; uint classSize() const { return sizeof(*this); } START_APPENDED_LISTS(EnvironmentInformationListItem); ///Contains the index of each contained environment-item APPENDED_LIST_FIRST(EnvironmentInformationListItem, uint, items); END_APPENDED_LISTS(EnvironmentInformationListItem, items); }; class EnvironmentInformationListRequest { public: ///This constructor should only be used for lookup - EnvironmentInformationListRequest(const IndexedString& file) : m_file(file), m_item(0) { + EnvironmentInformationListRequest(const IndexedString& file) : m_file(file), m_item(nullptr) { } ///This is used to actually construct the information in the repository EnvironmentInformationListRequest(const IndexedString& file, const EnvironmentInformationListItem& item) : m_file(file), m_item(&item) { } enum { AverageSize = 160 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_file.hash(); } uint itemSize() const { return m_item->itemSize(); } void createItem(EnvironmentInformationListItem* item) const { Q_ASSERT(m_item->m_file == m_file); new (item) EnvironmentInformationListItem(*m_item, false); } static void destroy(EnvironmentInformationListItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationListItem(); } static bool persistent(const EnvironmentInformationListItem*) { //Cleanup is done separately return true; } bool equals(const EnvironmentInformationListItem* item) const { return m_file == item->m_file; } IndexedString m_file; const EnvironmentInformationListItem* m_item; }; class DUChainPrivate; -static DUChainPrivate* duChainPrivateSelf = 0; +static DUChainPrivate* duChainPrivateSelf = nullptr; class DUChainPrivate { class CleanupThread : public QThread { public: CleanupThread(DUChainPrivate* data) : m_stopRunning(false), m_data(data) { } void stopThread() { { QMutexLocker lock(&m_waitMutex); m_stopRunning = true; m_wait.wakeAll(); //Wakes the thread up, so it notices it should exit } wait(); } private: void run() override { while(1) { for(uint s = 0; s < cleanupEverySeconds; ++s) { if(m_stopRunning) break; QMutexLocker lock(&m_waitMutex); m_wait.wait(&m_waitMutex, 1000); } if(m_stopRunning) break; //Just to make sure the cache is cleared periodically ModificationRevisionSet::clearCache(); m_data->doMoreCleanup(SOFT_CLEANUP_STEPS, TryLock); if(m_stopRunning) break; } } bool m_stopRunning; QWaitCondition m_wait; QMutex m_waitMutex; DUChainPrivate* m_data; }; public: - DUChainPrivate() : m_chainsMutex(QMutex::Recursive), m_cleanupMutex(QMutex::Recursive), instance(0), m_cleanupDisabled(false), m_destroyed(false), m_environmentListInfo(QStringLiteral("Environment Lists")), m_environmentInfo(QStringLiteral("Environment Information")) + DUChainPrivate() : m_chainsMutex(QMutex::Recursive), m_cleanupMutex(QMutex::Recursive), instance(nullptr), m_cleanupDisabled(false), m_destroyed(false), m_environmentListInfo(QStringLiteral("Environment Lists")), m_environmentInfo(QStringLiteral("Environment Information")) { #if defined(TEST_NO_CLEANUP) m_cleanupDisabled = true; #endif duChainPrivateSelf = this; qRegisterMetaType("KDevelop::DUChainBasePointer"); qRegisterMetaType("KDevelop::DUContextPointer"); qRegisterMetaType("KDevelop::TopDUContextPointer"); qRegisterMetaType("KDevelop::DeclarationPointer"); qRegisterMetaType("KDevelop::FunctionDeclarationPointer"); qRegisterMetaType("KDevelop::IndexedString"); qRegisterMetaType("KDevelop::IndexedTopDUContext"); qRegisterMetaType("KDevelop::ReferencedTopDUContext"); instance = new DUChain(); m_cleanup = new CleanupThread(this); m_cleanup->start(); DUChain::m_deleted = false; ///Loading of some static data: { ///@todo Solve this more duchain-like QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::ReadOnly); ///FIXME: ugh, so ugly ParsingEnvironmentFile::m_staticData = reinterpret_cast( new char[sizeof(StaticParsingEnvironmentData)]); if(opened) { qCDebug(LANGUAGE) << "reading parsing-environment static data"; //Read f.read((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); }else{ qCDebug(LANGUAGE) << "creating new parsing-environment static data"; //Initialize new (ParsingEnvironmentFile::m_staticData) StaticParsingEnvironmentData(); } } ///Read in the list of available top-context indices { QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::ReadOnly); if(opened) { Q_ASSERT( (f.size() % sizeof(uint)) == 0); m_availableTopContextIndices.resize(f.size()/(int)sizeof(uint)); f.read((char*)m_availableTopContextIndices.data(), f.size()); } } } ~DUChainPrivate() { qCDebug(LANGUAGE) << "Destroying"; DUChain::m_deleted = true; m_cleanup->stopThread(); delete m_cleanup; delete instance; } void clear() { if(!m_cleanupDisabled) doMoreCleanup(); DUChainWriteLocker writeLock(DUChain::lock()); QMutexLocker l(&m_chainsMutex); foreach(TopDUContext* top, m_chainsByUrl) removeDocumentChainFromMemory(top); m_indexEnvironmentInformations.clear(); m_fileEnvironmentInformations.clear(); Q_ASSERT(m_fileEnvironmentInformations.isEmpty()); Q_ASSERT(m_chainsByUrl.isEmpty()); } ///DUChain must be write-locked ///Also removes from the environment-manager if the top-context is not on disk void removeDocumentChainFromMemory(TopDUContext* context) { QMutexLocker l(&m_chainsMutex); { QMutexLocker l(&m_referenceCountsMutex); if(m_referenceCounts.contains(context)) { //This happens during shutdown, since everything is unloaded qCDebug(LANGUAGE) << "removed a top-context that was reference-counted:" << context->url().str() << context->ownIndex(); m_referenceCounts.remove(context); } } uint index = context->ownIndex(); // qCDebug(LANGUAGE) << "duchain: removing document" << context->url().str(); Q_ASSERT(hasChainForIndex(index)); Q_ASSERT(m_chainsByUrl.contains(context->url(), context)); m_chainsByUrl.remove(context->url(), context); if(!context->isOnDisk()) instance->removeFromEnvironmentManager(context); l.unlock(); //DUChain is write-locked, so we can do whatever we want on the top-context, including deleting it context->deleteSelf(); l.relock(); Q_ASSERT(hasChainForIndex(index)); QMutexLocker lock(&DUChain::chainsByIndexLock); - DUChain::chainsByIndex[index] = 0; + DUChain::chainsByIndex[index] = nullptr; } ///Must be locked before accessing content of this class. ///Should be released during expensive disk-operations and such. QMutex m_chainsMutex; QMutex m_cleanupMutex; CleanupThread* m_cleanup; DUChain* instance; DUChainLock lock; QMultiMap m_chainsByUrl; //Must be locked before accessing m_referenceCounts QMutex m_referenceCountsMutex; QHash m_referenceCounts; Definitions m_definitions; Uses m_uses; QSet m_loading; bool m_cleanupDisabled; //List of available top-context indices, protected by m_chainsMutex QVector m_availableTopContextIndices; ///Used to keep alive the top-context that belong to documents loaded in the editor QSet m_openDocumentContexts; bool m_destroyed; ///The item must not be stored yet ///m_chainsMutex should not be locked, since this can trigger I/O void addEnvironmentInformation(ParsingEnvironmentFilePointer info) { Q_ASSERT(!findInformation(info->indexedTopContext().index())); Q_ASSERT(m_environmentInfo.findIndex(info->indexedTopContext().index()) == 0); QMutexLocker lock(&m_chainsMutex); m_fileEnvironmentInformations.insert(info->url(), info); m_indexEnvironmentInformations.insert(info->indexedTopContext().index(), info); Q_ASSERT(info->d_func()->classId); } ///The item must be managed currently ///m_chainsMutex does not need to be locked void removeEnvironmentInformation(ParsingEnvironmentFilePointer info) { info->makeDynamic(); //By doing this, we make sure the data is actually being destroyed in the destructor bool removed = false; bool removed2 = false; { QMutexLocker lock(&m_chainsMutex); removed = m_fileEnvironmentInformations.remove(info->url(), info); removed2 = m_indexEnvironmentInformations.remove(info->indexedTopContext().index()); } { //Remove it from the environment information lists if it was there QMutexLocker lock(m_environmentListInfo.mutex()); uint index = m_environmentListInfo.findIndex(info->url()); if(index) { EnvironmentInformationListItem item(*m_environmentListInfo.itemFromIndex(index)); if(item.itemsList().removeOne(info->indexedTopContext().index())) { m_environmentListInfo.deleteItem(index); if(!item.itemsList().isEmpty()) m_environmentListInfo.index(EnvironmentInformationListRequest(info->url(), item)); } } } QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(info->indexedTopContext().index()); if(index) { m_environmentInfo.deleteItem(index); } Q_UNUSED(removed); Q_UNUSED(removed2); Q_ASSERT(index || (removed && removed2)); Q_ASSERT(!findInformation(info->indexedTopContext().index())); } ///m_chainsMutex should _not_ be locked, because this may trigger I/O QList getEnvironmentInformation(IndexedString url) { QList ret; uint listIndex = m_environmentListInfo.findIndex(url); if(listIndex) { KDevVarLengthArray topContextIndices; { //First store all the possible intices into the KDevVarLengthArray, so we can unlock the mutex before processing them. QMutexLocker lock(m_environmentListInfo.mutex()); //Lock the mutex to make sure the item isn't changed while it's being iterated const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(listIndex); FOREACH_FUNCTION(uint topContextIndex, item->items) topContextIndices << topContextIndex; } //Process the indices in a separate step after copying them from the array, so we don't need m_environmentListInfo.mutex locked, //and can call loadInformation(..) safely, which else might lead to a deadlock. foreach (uint topContextIndex, topContextIndices) { QExplicitlySharedDataPointer< ParsingEnvironmentFile > p = ParsingEnvironmentFilePointer(loadInformation(topContextIndex)); if(p) { ret << p; }else{ qCDebug(LANGUAGE) << "Failed to load enviromment-information for" << TopDUContextDynamicData::loadUrl(topContextIndex).str(); } } } QMutexLocker l(&m_chainsMutex); //Add those information that have not been added to the stored lists yet foreach(const ParsingEnvironmentFilePointer& file, m_fileEnvironmentInformations.values(url)) if(!ret.contains(file)) ret << file; return ret; } ///Must be called _without_ the chainsByIndex spin-lock locked static inline bool hasChainForIndex(uint index) { QMutexLocker lock(&DUChain::chainsByIndexLock); return (DUChain::chainsByIndex.size() > index) && DUChain::chainsByIndex[index]; } ///Must be called _without_ the chainsByIndex spin-lock locked. Returns the top-context if it is loaded. static inline TopDUContext* readChainForIndex(uint index) { QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() > index) return DUChain::chainsByIndex[index]; else - return 0; + return nullptr; } ///Makes sure that the chain with the given index is loaded ///@warning m_chainsMutex must NOT be locked when this is called void loadChain(uint index, QSet& loaded) { QMutexLocker l(&m_chainsMutex); if(!hasChainForIndex(index)) { if(m_loading.contains(index)) { //It's probably being loaded by another thread. So wait until the load is ready while(m_loading.contains(index)) { l.unlock(); qCDebug(LANGUAGE) << "waiting for another thread to load index" << index; QThread::usleep(50000); l.relock(); } loaded.insert(index); return; } m_loading.insert(index); loaded.insert(index); l.unlock(); qCDebug(LANGUAGE) << "loading top-context" << index; TopDUContext* chain = TopDUContextDynamicData::load(index); if(chain) { chain->setParsingEnvironmentFile(loadInformation(chain->ownIndex())); if(!chain->usingImportsCache()) { //Eventually also load all the imported chains, so the import-structure is built foreach(const DUContext::Import &import, chain->DUContext::importedParentContexts()) { if(!loaded.contains(import.topContextIndex())) { loadChain(import.topContextIndex(), loaded); } } } chain->rebuildDynamicImportStructure(); chain->setInDuChain(true); instance->addDocumentChain(chain); } l.relock(); m_loading.remove(index); } } ///Stores all environment-information ///Also makes sure that all information that stays is referenced, so it stays alive. ///@param atomic If this is false, the write-lock will be released time by time void storeAllInformation(bool atomic, DUChainWriteLocker& locker) { uint cnt = 0; QList urls; { QMutexLocker lock(&m_chainsMutex); urls += m_fileEnvironmentInformations.keys(); } foreach(const IndexedString &url, urls) { QList check; { QMutexLocker lock(&m_chainsMutex); check = m_fileEnvironmentInformations.values(url); } foreach(ParsingEnvironmentFilePointer file, check) { EnvironmentInformationRequest req(file.data()); QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(req); if(file->d_func()->isDynamic()) { //This item has been changed, or isn't in the repository yet //Eventually remove an old entry if(index) m_environmentInfo.deleteItem(index); //Add the new entry to the item repository index = m_environmentInfo.index(req); Q_ASSERT(index); EnvironmentInformationItem* item = const_cast(m_environmentInfo.itemFromIndex(index)); DUChainBaseData* theData = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); Q_ASSERT(theData->m_range == file->d_func()->m_range); Q_ASSERT(theData->m_dynamic == false); Q_ASSERT(theData->classId == file->d_func()->classId); file->setData( theData ); ++cnt; }else{ m_environmentInfo.itemFromIndex(index); //Prevent unloading of the data, by accessing the item } } ///We must not release the lock while holding a reference to a ParsingEnvironmentFilePointer, else we may miss the deletion of an ///information, and will get crashes. if(!atomic && (cnt % 100 == 0)) { //Release the lock on a regular basis locker.unlock(); locker.lock(); } storeInformationList(url); //Access the data in the repository, so the bucket isn't unloaded uint index = m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)); if(index) { m_environmentListInfo.itemFromIndex(index); }else{ QMutexLocker lock(&m_chainsMutex); qCDebug(LANGUAGE) << "Did not find stored item for" << url.str() << "count:" << m_fileEnvironmentInformations.values(url); } if(!atomic) { locker.unlock(); locker.lock(); } } } QMutex& cleanupMutex() { return m_cleanupMutex; } /// defines how we interact with the ongoing language parse jobs enum LockFlag { /// no locking required, only used when we locked previously NoLock = 0, /// lock all parse jobs and block until we succeeded. required at shutdown BlockingLock = 1, /// only try to lock and abort on failure, good for the intermittent cleanups TryLock = 2, }; ///@param retries When this is nonzero, then doMoreCleanup will do the specified amount of cycles ///doing the cleanup without permanently locking the du-chain. During these steps the consistency ///of the disk-storage is not guaranteed, but only few changes will be done during these steps, ///so the final step where the duchain is permanently locked is much faster. void doMoreCleanup(int retries = 0, LockFlag lockFlag = BlockingLock) { if(m_cleanupDisabled) return; //This mutex makes sure that there's never 2 threads at he same time trying to clean up QMutexLocker lockCleanupMutex(&cleanupMutex()); if(m_destroyed || m_cleanupDisabled) return; Q_ASSERT(!instance->lock()->currentThreadHasReadLock() && !instance->lock()->currentThreadHasWriteLock()); DUChainWriteLocker writeLock(instance->lock()); //This is used to stop all parsing before starting to do the cleanup. This way less happens during the //soft cleanups, and we have a good chance that during the "hard" cleanup only few data has to be written. QList locked; if (lockFlag != NoLock) { QList languages; if (ICore* core = ICore::self()) if (ILanguageController* lc = core->languageController()) languages = lc->loadedLanguages(); writeLock.unlock(); //Here we wait for all parsing-threads to stop their processing foreach(const auto language, languages) { if (lockFlag == TryLock) { if (!language->parseLock()->tryLockForWrite()) { qCDebug(LANGUAGE) << "Aborting cleanup because language plugin is still parsing:" << language->name(); // some language is still parsing, don't interfere with the cleanup foreach(auto* lock, locked) { lock->unlock(); } return; } } else { language->parseLock()->lockForWrite(); } locked << language->parseLock(); } writeLock.lock(); globalItemRepositoryRegistry().lockForWriting(); qCDebug(LANGUAGE) << "starting cleanup"; } QTime startTime = QTime::currentTime(); PersistentSymbolTable::self().clearCache(); storeAllInformation(!retries, writeLock); //Puts environment-information into a repository //We don't need to increase the reference-count, since the cleanup-mutex is locked QSet workOnContexts; { QMutexLocker l(&m_chainsMutex); workOnContexts.reserve(m_chainsByUrl.size()); foreach(TopDUContext* top, m_chainsByUrl) { workOnContexts << top; Q_ASSERT(hasChainForIndex(top->ownIndex())); } } foreach(TopDUContext* context, workOnContexts) { context->m_dynamicData->store(); if(retries) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between QThread::usleep(500); writeLock.lock(); } } //Unload all top-contexts that don't have a reference-count and that are not imported by a referenced one QSet unloadedNames; bool unloadedOne = true; bool unloadAllUnreferenced = !retries; //Now unload contexts, but only ones that are not imported by any other currently loaded context //The complication: Since during the lock-break new references may be added, we must never keep //the du-chain in an invalid state. Thus we can only unload contexts that are not imported by any //currently loaded contexts. In case of loops, we have to unload everything at once. while(unloadedOne) { unloadedOne = false; int hadUnloadable = 0; unloadContexts: foreach(TopDUContext* unload, workOnContexts) { bool hasReference = false; { QMutexLocker l(&m_referenceCountsMutex); //Test if the context is imported by a referenced one for (auto it = m_referenceCounts.constBegin(), end = m_referenceCounts.constEnd(); it != end; ++it) { auto* context = it.key(); if(context == unload || context->imports(unload, CursorInRevision())) { workOnContexts.remove(unload); hasReference = true; } } } if(!hasReference) ++hadUnloadable; //We have found a context that is not referenced else continue; //This context is referenced bool isImportedByLoaded = !unload->loadedImporters().isEmpty(); //If we unload a context that is imported by other contexts, we create a bad loaded state if(isImportedByLoaded && !unloadAllUnreferenced) continue; unloadedNames.insert(unload->url()); //Since we've released the write-lock in between, we've got to call store() again to be sure that none of the data is dynamic //If nothing has changed, it is only a low-cost call. unload->m_dynamicData->store(); Q_ASSERT(!unload->d_func()->m_dynamic); removeDocumentChainFromMemory(unload); workOnContexts.remove(unload); unloadedOne = true; if(!unloadAllUnreferenced) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between QThread::usleep(500); writeLock.lock(); } } if(hadUnloadable && !unloadedOne) { Q_ASSERT(!unloadAllUnreferenced); //This can happen in case of loops. We have o unload everything at one time. qCDebug(LANGUAGE) << "found" << hadUnloadable << "unloadable contexts, but could not unload separately. Unloading atomically."; unloadAllUnreferenced = true; hadUnloadable = 0; //Reset to 0, so we cannot loop forever goto unloadContexts; } } if(retries == 0) { QMutexLocker lock(&m_chainsMutex); //Do this atomically, since we must be sure that _everything_ is already saved for(QMultiMap::iterator it = m_fileEnvironmentInformations.begin(); it != m_fileEnvironmentInformations.end(); ) { ParsingEnvironmentFile* f = it->data(); Q_ASSERT(f->d_func()->classId); if(f->ref.load() == 1) { Q_ASSERT(!f->d_func()->isDynamic()); //It cannot be dynamic, since we have stored before //The ParsingEnvironmentFilePointer is only referenced once. This means that it does not belong to any //loaded top-context, so just remove it to save some memory and processing time. ///@todo use some kind of timeout before removing it = m_fileEnvironmentInformations.erase(it); }else{ ++it; } } } if(retries) writeLock.unlock(); //This must be the last step, due to the on-disk reference counting globalItemRepositoryRegistry().store(); //Stores all repositories { //Store the static parsing-environment file data ///@todo Solve this more elegantly, using a general mechanism to store static duchain-like data Q_ASSERT(ParsingEnvironmentFile::m_staticData); QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); } ///Write out the list of available top-context indices { QMutexLocker lock(&m_chainsMutex); QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)m_availableTopContextIndices.data(), m_availableTopContextIndices.size() * sizeof(uint)); } if(retries) { doMoreCleanup(retries-1, NoLock); writeLock.lock(); } if(lockFlag != NoLock) { globalItemRepositoryRegistry().unlockForWriting(); int elapsedSeconds = startTime.secsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "seconds spent doing cleanup: " << elapsedSeconds << "top-contexts still open:" << m_chainsByUrl.size(); } if(!retries) { int elapesedMilliSeconds = startTime.msecsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "milliseconds spent doing cleanup with locked duchain: " << elapesedMilliSeconds; } foreach(QReadWriteLock* lock, locked) lock->unlock(); #if HAVE_MALLOC_TRIM // trim unused memory but keep a pad buffer of about 50 MB // this can greatly decrease the perceived memory consumption of kdevelop // see: https://sourceware.org/bugzilla/show_bug.cgi?id=14827 malloc_trim(50 * 1024 * 1024); #endif } ///Checks whether the information is already loaded. ParsingEnvironmentFile* findInformation(uint topContextIndex) { QMutexLocker lock(&m_chainsMutex); QHash::iterator it = m_indexEnvironmentInformations.find(topContextIndex); if(it != m_indexEnvironmentInformations.end()) return (*it).data(); - return 0; + return nullptr; } ///Loads/gets the environment-information for the given top-context index, or returns zero if none exists ///@warning m_chainsMutex should NOT be locked when this is called, because it triggers I/O ///@warning no other mutexes should be locked, as that may lead to a dedalock ParsingEnvironmentFile* loadInformation(uint topContextIndex) { ParsingEnvironmentFile* alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; //Step two: Check if it is on disk, and if is, load it uint dataIndex = m_environmentInfo.findIndex(EnvironmentInformationRequest(topContextIndex)); if(!dataIndex) { //No environment-information stored for this top-context - return 0; + return nullptr; } const EnvironmentInformationItem& item(*m_environmentInfo.itemFromIndex(dataIndex)); QMutexLocker lock(&m_chainsMutex); //Due to multi-threading, we must do this check after locking the mutex, so we can be sure we don't create the same item twice at the same time alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; ///FIXME: ugly, and remove const_cast ParsingEnvironmentFile* ret = dynamic_cast(DUChainItemSystem::self().create( const_cast(reinterpret_cast(reinterpret_cast(&item) + sizeof(EnvironmentInformationItem))) )); if(ret) { Q_ASSERT(ret->d_func()->classId); Q_ASSERT(ret->indexedTopContext().index() == topContextIndex); ParsingEnvironmentFilePointer retPtr(ret); m_fileEnvironmentInformations.insert(ret->url(), retPtr); Q_ASSERT(!m_indexEnvironmentInformations.contains(ret->indexedTopContext().index())); m_indexEnvironmentInformations.insert(ret->indexedTopContext().index(), retPtr); } return ret; } struct CleanupListVisitor { QList checkContexts; bool operator()(const EnvironmentInformationItem* item) { checkContexts << item->m_topContext; return true; } }; ///Will check a selection of all top-contexts for up-to-date ness, and remove them if out of date void cleanupTopContexts() { DUChainWriteLocker lock( DUChain::lock() ); qCDebug(LANGUAGE) << "cleaning top-contexts"; CleanupListVisitor visitor; uint startPos = 0; m_environmentInfo.visitAllItems(visitor); int checkContextsCount = maxFinalCleanupCheckContexts; int percentageOfContexts = (visitor.checkContexts.size() * 100) / minimumFinalCleanupCheckContextsPercentage; if(checkContextsCount < percentageOfContexts) checkContextsCount = percentageOfContexts; if(visitor.checkContexts.size() > (int)checkContextsCount) startPos = qrand() % (visitor.checkContexts.size() - checkContextsCount); int endPos = startPos + maxFinalCleanupCheckContexts; if(endPos > visitor.checkContexts.size()) endPos = visitor.checkContexts.size(); QSet< uint > check; for(int a = startPos; a < endPos && check.size() < checkContextsCount; ++a) if(check.size() < checkContextsCount) addContextsForRemoval(check, IndexedTopDUContext(visitor.checkContexts[a])); foreach(uint topIndex, check) { IndexedTopDUContext top(topIndex); if(top.data()) { qCDebug(LANGUAGE) << "removing top-context for" << top.data()->url().str() << "because it is out of date"; instance->removeDocumentChain(top.data()); } } qCDebug(LANGUAGE) << "check ready"; } private: void addContextsForRemoval(QSet& topContexts, IndexedTopDUContext top) { if(topContexts.contains(top.index())) return; QExplicitlySharedDataPointer info( instance->environmentFileForDocument(top) ); ///@todo Also check if the context is "useful"(Not a duplicate context, imported by a useful one, ...) if(info && info->needsUpdate()) { //This context will be removed }else{ return; } topContexts.insert(top.index()); if(info) { //Check whether importers need to be removed as well QList< QExplicitlySharedDataPointer > importers = info->importers(); QSet< QExplicitlySharedDataPointer > checkNext; //Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached for(QList< QExplicitlySharedDataPointer >::iterator it = importers.begin(); it != importers.end(); ++it) { IndexedTopDUContext c = (*it)->indexedTopContext(); if(!topContexts.contains(c.index())) { topContexts.insert(c.index()); //Prevent useless recursion checkNext.insert(*it); } } for(QSet< QExplicitlySharedDataPointer >::const_iterator it = checkNext.begin(); it != checkNext.end(); ++it) { topContexts.remove((*it)->indexedTopContext().index()); //Enable full check again addContextsForRemoval(topContexts, (*it)->indexedTopContext()); } } } ///Stores the environment-information for the given url void storeInformationList(IndexedString url) { QMutexLocker lock(m_environmentListInfo.mutex()); EnvironmentInformationListItem newItem; newItem.m_file = url; QSet newItems; { QMutexLocker lock(&m_chainsMutex); QMultiMap::iterator start = m_fileEnvironmentInformations.lowerBound(url); QMultiMap::iterator end = m_fileEnvironmentInformations.upperBound(url); for(QMultiMap::iterator it = start; it != end; ++it) { uint topContextIndex = (*it)->indexedTopContext().index(); newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } uint index = m_environmentListInfo.findIndex(url); if(index) { //We only handle adding items here, since we can never be sure whether everything is loaded //Removal is handled directly in removeEnvironmentInformation const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(index); QSet oldItems; FOREACH_FUNCTION(uint topContextIndex, item->items) { oldItems.insert(topContextIndex); if(!newItems.contains(topContextIndex)) { newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } if(oldItems == newItems) return; ///Update/insert a new list m_environmentListInfo.deleteItem(index); //Remove the previous item } Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)) == 0); //Insert the new item m_environmentListInfo.index(EnvironmentInformationListRequest(url, newItem)); Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url))); } //Loaded environment-informations. Protected by m_chainsMutex QMultiMap m_fileEnvironmentInformations; QHash m_indexEnvironmentInformations; ///The following repositories are thread-safe, and m_chainsMutex should not be locked when using them, because ///they may trigger I/O. Still it may be required to lock their local mutexes. ///Maps filenames to a list of top-contexts/environment-information. ItemRepository m_environmentListInfo; ///Maps top-context-indices to environment-information item. ItemRepository m_environmentInfo; }; Q_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate) DUChain::DUChain() { Q_ASSERT(ICore::self()); connect(ICore::self()->documentController(), &IDocumentController::documentLoadedPrepare, this, &DUChain::documentLoadedPrepare); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &DUChain::documentRenamed); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &DUChain::documentActivated); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &DUChain::documentClosed); } DUChain::~DUChain() { DUChain::m_deleted = true; } DUChain* DUChain::self() { return sdDUChainPrivate->instance; } extern void initModificationRevisionSetRepository(); extern void initDeclarationRepositories(); extern void initIdentifierRepository(); extern void initTypeRepository(); extern void initInstantiationInformationRepository(); void DUChain::initialize() { // Initialize the global item repository as first thing after loading the session Q_ASSERT(ICore::self()); Q_ASSERT(ICore::self()->activeSession()); ItemRepositoryRegistry::initialize(ICore::self()->activeSessionLock()); initReferenceCounting(); // This needs to be initialized here too as the function is not threadsafe, but can // sometimes be called from different threads. This results in the underlying QFile // being 0 and hence crashes at some point later when accessing the contents via // read. See https://bugs.kde.org/show_bug.cgi?id=250779 RecursiveImportRepository::repository(); RecursiveImportCacheRepository::repository(); // similar to above, see https://bugs.kde.org/show_bug.cgi?id=255323 initDeclarationRepositories(); initModificationRevisionSetRepository(); initIdentifierRepository(); initTypeRepository(); initInstantiationInformationRepository(); Importers::self(); globalImportIdentifier(); globalIndexedImportIdentifier(); globalAliasIdentifier(); globalIndexedAliasIdentifier(); } DUChainLock* DUChain::lock() { return &sdDUChainPrivate->lock; } QList DUChain::allChains() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); return sdDUChainPrivate->m_chainsByUrl.values(); } void DUChain::updateContextEnvironment( TopDUContext* context, ParsingEnvironmentFile* file ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); removeFromEnvironmentManager( context ); context->setParsingEnvironmentFile( file ); addToEnvironmentManager( context ); } void DUChain::removeDocumentChain( TopDUContext* context ) { ENSURE_CHAIN_WRITE_LOCKED; IndexedTopDUContext indexed(context->indexed()); Q_ASSERT(indexed.data() == context); ///This assertion fails if you call removeDocumentChain(..) on a document that has not been added to the du-chain context->m_dynamicData->deleteOnDisk(); Q_ASSERT(indexed.data() == context); sdDUChainPrivate->removeDocumentChainFromMemory(context); Q_ASSERT(!indexed.data()); Q_ASSERT(!environmentFileForDocument(indexed)); QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); sdDUChainPrivate->m_availableTopContextIndices.push_back(indexed.index()); } void DUChain::addDocumentChain( TopDUContext * chain ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // qCDebug(LANGUAGE) << "duchain: adding document" << chain->url().str() << " " << chain; Q_ASSERT(chain); Q_ASSERT(!sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); { QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() <= chain->ownIndex()) - DUChain::chainsByIndex.resize(chain->ownIndex() + 100, 0); + DUChain::chainsByIndex.resize(chain->ownIndex() + 100, nullptr); DUChain::chainsByIndex[chain->ownIndex()] = chain; } { Q_ASSERT(DUChain::chainsByIndex[chain->ownIndex()]); } Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); sdDUChainPrivate->m_chainsByUrl.insert(chain->url(), chain); Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); chain->setInDuChain(true); l.unlock(); addToEnvironmentManager(chain); // This function might be called during shutdown by stale parse jobs // Make sure we don't access null-pointers here if (ICore::self() && ICore::self()->languageController() && ICore::self()->languageController()->backgroundParser()->trackerForUrl(chain->url())) { //Make sure the context stays alive at least as long as the context is open ReferencedTopDUContext ctx(chain); sdDUChainPrivate->m_openDocumentContexts.insert(ctx); } } void DUChain::addToEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage Q_ASSERT(file->indexedTopContext().index() == chain->ownIndex()); if(ParsingEnvironmentFile* alreadyHave = sdDUChainPrivate->findInformation(file->indexedTopContext().index())) { ///If this triggers, there has already been another environment-information registered for this top-context. ///removeFromEnvironmentManager should have been called before to remove the old environment-information. Q_ASSERT(alreadyHave == file.data()); Q_UNUSED(alreadyHave); return; } sdDUChainPrivate->addEnvironmentInformation(file); } void DUChain::removeFromEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage sdDUChainPrivate->removeEnvironmentInformation(file); } TopDUContext* DUChain::chainForDocument(const QUrl& document, bool proxyContext) const { return chainForDocument(IndexedString(document), proxyContext); } bool DUChain::isInMemory(uint topContextIndex) const { return DUChainPrivate::hasChainForIndex(topContextIndex); } IndexedString DUChain::urlForIndex(uint index) const { { TopDUContext* chain = DUChainPrivate::readChainForIndex(index); if(chain) return chain->url(); } return TopDUContextDynamicData::loadUrl(index); } TopDUContext* DUChain::loadChain(uint index) { QSet loaded; sdDUChainPrivate->loadChain(index, loaded); { QMutexLocker lock(&chainsByIndexLock); if(chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if(top) return top; } } - return 0; + return nullptr; } TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) - return 0; + return nullptr; QList list = sdDUChainPrivate->getEnvironmentInformation(document); foreach(const ParsingEnvironmentFilePointer &file, list) if(isInMemory(file->indexedTopContext().index()) && file->isProxyContext() == proxyContext) { return file->topContext(); } foreach(const ParsingEnvironmentFilePointer &file, list) if(proxyContext == file->isProxyContext()) { return file->topContext(); } //Allow selecting a top-context even if there is no ParsingEnvironmentFile QList< TopDUContext* > ret = chainsForDocument(document); foreach(TopDUContext* ctx, ret) { if(!ctx->parsingEnvironmentFile() || (ctx->parsingEnvironmentFile()->isProxyContext() == proxyContext)) return ctx; } - return 0; + return nullptr; } QList DUChain::chainsForDocument(const QUrl& document) const { return chainsForDocument(IndexedString(document)); } QList DUChain::chainsForDocument(const IndexedString& document) const { QList chains; if(sdDUChainPrivate->m_destroyed) return chains; QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // Match all parsed versions of this document for (auto it = sdDUChainPrivate->m_chainsByUrl.lowerBound(document); it != sdDUChainPrivate->m_chainsByUrl.end(); ++it) { if (it.key() == document) chains << it.value(); else break; } return chains; } TopDUContext* DUChain::chainForDocument( const QUrl& document, const KDevelop::ParsingEnvironment* environment, bool proxyContext ) const { return chainForDocument( IndexedString(document), environment, proxyContext ); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return ParsingEnvironmentFilePointer(); QList< ParsingEnvironmentFilePointer> list = sdDUChainPrivate->getEnvironmentInformation(document); // qCDebug(LANGUAGE) << document.str() << ": matching" << list.size() << (onlyProxyContexts ? "proxy-contexts" : (noProxyContexts ? "content-contexts" : "contexts")); auto it = list.constBegin(); while(it != list.constEnd()) { if(*it && ((*it)->isProxyContext() == proxyContext) && (*it)->matchEnvironment(environment) && // Verify that the environment-file and its top-context are "good": The top-context must exist, // and there must be a content-context associated to the proxy-context. (*it)->topContext() && (!proxyContext || DUChainUtils::contentContextFromProxyContext((*it)->topContext())) ) { return *it; } ++it; } return ParsingEnvironmentFilePointer(); } QList DUChain::allEnvironmentFiles(const IndexedString& document) { return sdDUChainPrivate->getEnvironmentInformation(document); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument(IndexedTopDUContext topContext) const { if(topContext.index() == 0) return ParsingEnvironmentFilePointer(); return ParsingEnvironmentFilePointer(sdDUChainPrivate->loadInformation(topContext.index())); } TopDUContext* DUChain::chainForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { if(sdDUChainPrivate->m_destroyed) - return 0; + return nullptr; ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext); if(envFile) { return envFile->topContext(); }else{ - return 0; + return nullptr; } } QList DUChain::documents() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; ret.reserve(sdDUChainPrivate->m_chainsByUrl.count()); foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl) { ret << top->url().toUrl(); } return ret; } QList DUChain::indexedDocuments() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; ret.reserve(sdDUChainPrivate->m_chainsByUrl.count()); foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl) { ret << top->url(); } return ret; } void DUChain::documentActivated(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; DUChainReadLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); auto backgroundParser = ICore::self()->languageController()->backgroundParser(); auto addWithHighPriority = [backgroundParser, doc]() { backgroundParser->addDocument(IndexedString(doc->url()), TopDUContext::VisibleDeclarationsAndContexts, BackgroundParser::BestPriority); }; TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url(), true); //Check whether the document has an attached environment-manager, and whether that one thinks the document needs to be updated. //If yes, update it. if (ctx && ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->needsUpdate()) { qCDebug(LANGUAGE) << "Document needs update, using best priority since it just got activated:" << doc->url(); addWithHighPriority(); } else if (backgroundParser->managedDocuments().contains(IndexedString(doc->url()))) { // increase priority if there's already parse job of this document in the queue qCDebug(LANGUAGE) << "Prioritizing activated document:" << doc->url(); addWithHighPriority(); } } void DUChain::documentClosed(IDocument* document) { if(sdDUChainPrivate->m_destroyed) return; IndexedString url(document->url()); foreach(const ReferencedTopDUContext &top, sdDUChainPrivate->m_openDocumentContexts) if(top->url() == url) sdDUChainPrivate->m_openDocumentContexts.remove(top); } void DUChain::documentLoadedPrepare(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; const IndexedString url(doc->url()); DUChainWriteLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(doc->url()); QList chains = chainsForDocument(url); auto languages = ICore::self()->languageController()->languagesForUrl(doc->url()); if(standardContext) { Q_ASSERT(chains.contains(standardContext)); //We have just loaded it Q_ASSERT((standardContext->url() == url)); sdDUChainPrivate->m_openDocumentContexts.insert(standardContext); bool needsUpdate = standardContext->parsingEnvironmentFile() && standardContext->parsingEnvironmentFile()->needsUpdate(); if(!needsUpdate) { //Only apply the highlighting if we don't need to update, else we might highlight total crap //Do instant highlighting only if all imports are loaded, to make sure that we don't block the user-interface too long //Else the highlighting will be done in the background-thread //This is not exactly right, as the direct imports don't necessarily equal the real imports used by uses //but it approximates the correct behavior. bool allImportsLoaded = true; foreach(const DUContext::Import& import, standardContext->importedParentContexts()) if(!import.indexedContext().indexedTopContext().isLoaded()) allImportsLoaded = false; if(allImportsLoaded) { l.unlock(); lock.unlock(); foreach(const auto language, languages) { if(language->codeHighlighting()) { language->codeHighlighting()->highlightDUChain(standardContext); } } qCDebug(LANGUAGE) << "highlighted" << doc->url() << "in foreground"; return; } }else{ qCDebug(LANGUAGE) << "not highlighting the duchain because the documents needs an update"; } if(needsUpdate || !(standardContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); return; } } //Add for highlighting etc. ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), TopDUContext::AllDeclarationsContextsAndUses); } void DUChain::documentRenamed(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; if(!doc->url().isValid()) { ///Maybe this happens when a file was deleted? qCWarning(LANGUAGE) << "Strange, url of renamed document is invalid!"; }else{ ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); } } Uses* DUChain::uses() { return &sdDUChainPrivate->m_uses; } Definitions* DUChain::definitions() { return &sdDUChainPrivate->m_definitions; } static void finalCleanup() { DUChainWriteLocker writeLock(DUChain::lock()); qCDebug(LANGUAGE) << "doing final cleanup"; int cleaned = 0; while((cleaned = globalItemRepositoryRegistry().finalCleanup())) { qCDebug(LANGUAGE) << "cleaned" << cleaned << "B"; if(cleaned < 1000) { qCDebug(LANGUAGE) << "cleaned enough"; break; } } qCDebug(LANGUAGE) << "final cleanup ready"; } void DUChain::shutdown() { // if core is not shutting down, we can end up in deadlocks or crashes // since language plugins might still try to access static duchain stuff Q_ASSERT(!ICore::self() || ICore::self()->shuttingDown()); qCDebug(LANGUAGE) << "Cleaning up and shutting down DUChain"; QMutexLocker lock(&sdDUChainPrivate->cleanupMutex()); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); sdDUChainPrivate->cleanupTopContexts(); globalItemRepositoryRegistry().unlockForWriting(); } sdDUChainPrivate->doMoreCleanup(); //Must be done _before_ finalCleanup, else we may be deleting yet needed data sdDUChainPrivate->m_openDocumentContexts.clear(); sdDUChainPrivate->m_destroyed = true; sdDUChainPrivate->clear(); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); finalCleanup(); globalItemRepositoryRegistry().unlockForWriting(); } globalItemRepositoryRegistry().shutdown(); } uint DUChain::newTopContextIndex() { { QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); if(!sdDUChainPrivate->m_availableTopContextIndices.isEmpty()) { uint ret = sdDUChainPrivate->m_availableTopContextIndices.back(); sdDUChainPrivate->m_availableTopContextIndices.pop_back(); if(TopDUContextDynamicData::fileExists(ret)) { qCWarning(LANGUAGE) << "Problem in the management of availalbe top-context indices"; return newTopContextIndex(); } return ret; } } static QAtomicInt& currentId( globalItemRepositoryRegistry().getCustomCounter(QStringLiteral("Top-Context Counter"), 1) ); return currentId.fetchAndAddRelaxed(1); } void DUChain::refCountUp(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); // note: value is default-constructed to zero if it does not exist ++sdDUChainPrivate->m_referenceCounts[top]; } bool DUChain::deleted() { return m_deleted; } void DUChain::refCountDown(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); auto it = sdDUChainPrivate->m_referenceCounts.find(top); if (it == sdDUChainPrivate->m_referenceCounts.end()) { //qCWarning(LANGUAGE) << "tried to decrease reference-count for" << top->url().str() << "but this top-context is not referenced"; return; } auto& refCount = *it; --refCount; if (!refCount) { sdDUChainPrivate->m_referenceCounts.erase(it); } } void DUChain::emitDeclarationSelected(const DeclarationPointer& decl) { if(sdDUChainPrivate->m_destroyed) return; emit declarationSelected(decl); } void DUChain::emitUpdateReady(const IndexedString& url, const ReferencedTopDUContext& topContext) { if(sdDUChainPrivate->m_destroyed) return; emit updateReady(url, topContext); } KDevelop::ReferencedTopDUContext DUChain::waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext) { Q_ASSERT(!lock()->currentThreadHasReadLock() && !lock()->currentThreadHasWriteLock()); WaitForUpdate waiter; updateContextForUrl(document, minFeatures, &waiter); // waiter.m_waitMutex.lock(); // waiter.m_dataMutex.unlock(); while(!waiter.m_ready) { // we might have been shut down in the meanwhile if (!ICore::self()) { - return 0; + return nullptr; } QMetaObject::invokeMethod(ICore::self()->languageController()->backgroundParser(), "parseDocuments"); QApplication::processEvents(); QThread::usleep(1000); } if(!proxyContext) { DUChainReadLocker readLock(DUChain::lock()); return DUChainUtils::contentContextFromProxyContext(waiter.m_topContext); } return waiter.m_topContext; } void DUChain::updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures, QObject* notifyReady, int priority) const { DUChainReadLocker lock( DUChain::lock() ); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(document.toUrl()); if(standardContext && standardContext->parsingEnvironmentFile() && !standardContext->parsingEnvironmentFile()->needsUpdate() && standardContext->parsingEnvironmentFile()->featuresSatisfied(minFeatures)) { lock.unlock(); if(notifyReady) QMetaObject::invokeMethod(notifyReady, "updateReady", Qt::DirectConnection, Q_ARG(KDevelop::IndexedString, document), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext(standardContext))); }else{ ///Start a parse-job for the given document ICore::self()->languageController()->backgroundParser()->addDocument(document, minFeatures, priority, notifyReady); } } void DUChain::disablePersistentStorage(bool disable) { sdDUChainPrivate->m_cleanupDisabled = disable; } void DUChain::storeToDisk() { bool wasDisabled = sdDUChainPrivate->m_cleanupDisabled; sdDUChainPrivate->m_cleanupDisabled = false; sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_cleanupDisabled = wasDisabled; } bool DUChain::compareToDisk() { DUChainWriteLocker writeLock(DUChain::lock()); ///Step 1: Compare the repositories return true; } } diff --git a/language/duchain/duchainbase.cpp b/language/duchain/duchainbase.cpp index f59ce443d8..37f5d5d46b 100644 --- a/language/duchain/duchainbase.cpp +++ b/language/duchain/duchainbase.cpp @@ -1,239 +1,239 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007/2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchainbase.h" #include #include #include "duchainpointer.h" #include "parsingenvironment.h" #include #include "topducontext.h" #include "duchainregister.h" #include #include #include #include #include #include #include namespace KDevelop { REGISTER_DUCHAIN_ITEM(DUChainBase); uint DUChainBaseData::classSize() const { return DUChainItemSystem::self().dataClassSize(*this); } DUChainBase::DUChainBase(const RangeInRevision& range) - : d_ptr(new DUChainBaseData), m_ptr( 0L ) + : d_ptr(new DUChainBaseData), m_ptr( nullptr ) { d_func_dynamic()->m_range = range; d_func_dynamic()->setClassId(this); } DUChainBase::DUChainBase( DUChainBaseData & dd, const RangeInRevision& range ) - : d_ptr( &dd ), m_ptr( 0 ) + : d_ptr( &dd ), m_ptr( nullptr ) { d_func_dynamic()->m_range = range; } DUChainBase::DUChainBase( DUChainBaseData & dd ) - : d_ptr( &dd ), m_ptr( 0 ) + : d_ptr( &dd ), m_ptr( nullptr ) { } DUChainBase::DUChainBase( DUChainBase& rhs ) - : d_ptr( new DUChainBaseData(*rhs.d_func()) ), m_ptr( 0 ) + : d_ptr( new DUChainBaseData(*rhs.d_func()) ), m_ptr( nullptr ) { d_func_dynamic()->setClassId(this); } IndexedString DUChainBase::url() const { TopDUContext* top = topContext(); if(top) return top->TopDUContext::url(); else return IndexedString(); } void DUChainBase::setData(DUChainBaseData* data, bool constructorCalled) { Q_ASSERT(data); Q_ASSERT(d_ptr); if(constructorCalled) KDevelop::DUChainItemSystem::self().callDestructor(static_cast(d_ptr)); if(d_ptr->m_dynamic) // If the data object isn't dynamic, then it is part of a central repository, and cannot be deleted here. delete d_ptr; d_ptr = data; } DUChainBase::~DUChainBase() { if (m_ptr) - m_ptr->m_base = 0; + m_ptr->m_base = nullptr; if(d_ptr->m_dynamic) { KDevelop::DUChainItemSystem::self().callDestructor(d_ptr); delete d_ptr; - d_ptr = 0; + d_ptr = nullptr; } } TopDUContext* DUChainBase::topContext() const { ///@todo Move the reference to the top-context right into this class, as it's common to all inheriters - return 0; + return nullptr; } namespace { QMutex weakPointerMutex; }; const QExplicitlySharedDataPointer& DUChainBase::weakPointer() const { if (!m_ptr) { QMutexLocker lock(&weakPointerMutex); // The mutex is used to make sure we don't create m_ptr twice at the same time m_ptr = new DUChainPointerData(const_cast(this)); m_ptr->m_base = const_cast(this); } return m_ptr; } void DUChainBase::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_UNUSED(parent) Q_UNUSED(ownIndex) } void DUChainBase::makeDynamic() { Q_ASSERT(d_ptr); if(!d_func()->m_dynamic) { Q_ASSERT(d_func()->classId); DUChainBaseData* newData = DUChainItemSystem::self().cloneData(*d_func()); enableDUChainReferenceCounting(d_ptr, DUChainItemSystem::self().dynamicSize(*static_cast(d_ptr))); //We don't delete the previous data, because it's embedded in the top-context when it isn't dynamic. //However we do call the destructor, to keep semantic stuff like reference-counting within the data class working correctly. KDevelop::DUChainItemSystem::self().callDestructor(static_cast(d_ptr)); disableDUChainReferenceCounting(d_ptr); d_ptr = newData; Q_ASSERT(d_ptr); Q_ASSERT(d_func()->m_dynamic); Q_ASSERT(d_func()->classId); } } RangeInRevision DUChainBase::range() const { return d_func()->m_range; } KTextEditor::Range DUChainBase::rangeInCurrentRevision() const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToCurrentRevision(d_func()->m_range, revision); } // If the document is not open, we can simply cast the range over, as no translation can be done return d_func()->m_range.castToSimpleRange(); } PersistentMovingRange::Ptr DUChainBase::createRangeMoving() const { VERIFY_FOREGROUND_LOCKED return PersistentMovingRange::Ptr(new PersistentMovingRange(rangeInCurrentRevision(), url())); } CursorInRevision DUChainBase::transformToLocalRevision(const KTextEditor::Cursor& cursor) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToRevision(cursor, revision); } return CursorInRevision::castFromSimpleCursor(cursor); } RangeInRevision DUChainBase::transformToLocalRevision(const KTextEditor::Range& range) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToRevision(range, revision); } return RangeInRevision::castFromSimpleRange(range); } KTextEditor::Range DUChainBase::transformFromLocalRevision(const KDevelop::RangeInRevision& range) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToCurrentRevision(range, revision); } return range.castToSimpleRange(); } KTextEditor::Cursor DUChainBase::transformFromLocalRevision(const KDevelop::CursorInRevision& cursor) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToCurrentRevision(cursor, revision); } return cursor.castToSimpleCursor(); } void DUChainBase::setRange(const RangeInRevision& range) { d_func_dynamic()->m_range = range; } QThreadStorage shouldCreateConstantDataStorage; bool& DUChainBaseData::shouldCreateConstantData() { return shouldCreateConstantDataStorage.localData(); } } diff --git a/language/duchain/duchainlock.cpp b/language/duchain/duchainlock.cpp index 6b2413a178..27da289663 100644 --- a/language/duchain/duchainlock.cpp +++ b/language/duchain/duchainlock.cpp @@ -1,271 +1,271 @@ /* This file is part of KDevelop Copyright 2007 Kris Wong Copyright 2007 Hamish Rodda Copyright 2007-2009 David Nolden Copyright 2013 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 "duchainlock.h" #include "duchain.h" #include #include #include ///@todo Always prefer exactly that lock that is requested by the thread that has the foreground mutex, /// to reduce the amount of UI blocking. //Microseconds to sleep when waiting for a lock const uint uSleepTime = 500; namespace KDevelop { class DUChainLockPrivate { public: DUChainLockPrivate() - : m_writer(0) + : m_writer(nullptr) , m_writerRecursion(0) , m_totalReaderRecursion(0) { } int ownReaderRecursion() const { return m_readerRecursion.localData(); } void changeOwnReaderRecursion(int difference) { m_readerRecursion.localData() += difference; Q_ASSERT(m_readerRecursion.localData() >= 0); m_totalReaderRecursion.fetchAndAddOrdered(difference); } ///Holds the writer that currently has the write-lock, or zero. Is protected by m_writerRecursion. QAtomicPointer m_writer; ///How often is the chain write-locked by the writer? This value protects m_writer, ///m_writer may only be changed by the thread that successfully increases this value from 0 to 1 QAtomicInt m_writerRecursion; ///How often is the chain read-locked recursively by all readers? Should be sum of all m_readerRecursion values QAtomicInt m_totalReaderRecursion; QThreadStorage m_readerRecursion; }; DUChainLock::DUChainLock() : d(new DUChainLockPrivate) { } DUChainLock::~DUChainLock() { delete d; } bool DUChainLock::lockForRead(unsigned int timeout) { ///Step 1: Increase the own reader-recursion. This will make sure no further write-locks will succeed d->changeOwnReaderRecursion(1); QThread* w = d->m_writer.loadAcquire(); - if (w == 0 || w == QThread::currentThread()) { + if (w == nullptr || w == QThread::currentThread()) { //Successful lock: Either there is no writer, or we hold the write-lock by ourselves } else { ///Step 2: Start spinning until there is no writer any more QElapsedTimer t; if (timeout) { t.start(); } while (d->m_writer.loadAcquire()) { if (!timeout || t.elapsed() < timeout) { QThread::usleep(uSleepTime); } else { //Fail! d->changeOwnReaderRecursion(-1); return false; } } } return true; } void DUChainLock::releaseReadLock() { d->changeOwnReaderRecursion(-1); } bool DUChainLock::currentThreadHasReadLock() { return (bool)d->ownReaderRecursion(); } bool DUChainLock::lockForWrite(uint timeout) { //It is not allowed to acquire a write-lock while holding read-lock Q_ASSERT(d->ownReaderRecursion() == 0); if (d->m_writer.load() == QThread::currentThread()) { //We already hold the write lock, just increase the recursion count and return d->m_writerRecursion.fetchAndAddRelaxed(1); return true; } QElapsedTimer t; if (timeout) { t.start(); } while (1) { //Try acquiring the write-lcok if (d->m_totalReaderRecursion.load() == 0 && d->m_writerRecursion.testAndSetOrdered(0, 1)) { //Now we can be sure that there is no other writer, as we have increased m_writerRecursion from 0 to 1 d->m_writer = QThread::currentThread(); if (d->m_totalReaderRecursion.load() == 0) { //There is still no readers, we have successfully acquired a write-lock return true; } else { //There may be readers.. we have to continue spinning - d->m_writer = 0; + d->m_writer = nullptr; d->m_writerRecursion = 0; } } if (!timeout || t.elapsed() < timeout) { QThread::usleep(uSleepTime); } else { //Fail! return false; } } return false; } void DUChainLock::releaseWriteLock() { Q_ASSERT(currentThreadHasWriteLock()); //The order is important here, m_writerRecursion protects m_writer //TODO: could testAndSet here if (d->m_writerRecursion.load() == 1) { - d->m_writer = 0; + d->m_writer = nullptr; d->m_writerRecursion = 0; } else { d->m_writerRecursion.fetchAndAddOrdered(-1); } } bool DUChainLock::currentThreadHasWriteLock() { return d->m_writer.load() == QThread::currentThread(); } DUChainReadLocker::DUChainReadLocker(DUChainLock* duChainLock, uint timeout) : m_lock(duChainLock ? duChainLock : DUChain::lock()) , m_locked(false) , m_timeout(timeout) { lock(); } DUChainReadLocker::~DUChainReadLocker() { unlock(); } bool DUChainReadLocker::locked() const { return m_locked; } bool DUChainReadLocker::lock() { if (m_locked) { return true; } bool l = false; if (m_lock) { l = m_lock->lockForRead(m_timeout); Q_ASSERT(m_timeout || l); }; m_locked = l; return l; } void DUChainReadLocker::unlock() { if (m_locked && m_lock) { m_lock->releaseReadLock(); m_locked = false; } } DUChainWriteLocker::DUChainWriteLocker(DUChainLock* duChainLock, uint timeout) : m_lock(duChainLock ? duChainLock : DUChain::lock()) , m_locked(false) , m_timeout(timeout) { lock(); } DUChainWriteLocker::~DUChainWriteLocker() { unlock(); } bool DUChainWriteLocker::lock() { if (m_locked) { return true; } bool l = false; if (m_lock) { l = m_lock->lockForWrite(m_timeout); Q_ASSERT(m_timeout || l); }; m_locked = l; return l; } bool DUChainWriteLocker::locked() const { return m_locked; } void DUChainWriteLocker::unlock() { if (m_locked && m_lock) { m_lock->releaseWriteLock(); m_locked = false; } } } diff --git a/language/duchain/duchainpointer.cpp b/language/duchain/duchainpointer.cpp index 035e5f0124..6fc0ef1b7b 100644 --- a/language/duchain/duchainpointer.cpp +++ b/language/duchain/duchainpointer.cpp @@ -1,49 +1,49 @@ /* This is part of KDevelop Copyright 2007 Bernd Buschinski 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 "duchainpointer.h" namespace KDevelop { DUChainBase* DUChainPointerData::base() { return m_base; } DUChainBase* DUChainPointerData::base() const { return m_base; } DUChainPointerData::DUChainPointerData() - : m_base(0) + : m_base(nullptr) { } DUChainPointerData::~DUChainPointerData() { } DUChainPointerData::DUChainPointerData( DUChainBase* base ) : m_base(base) { } } //KDevelop diff --git a/language/duchain/duchainpointer.h b/language/duchain/duchainpointer.h index e12fb7d496..77781751e1 100644 --- a/language/duchain/duchainpointer.h +++ b/language/duchain/duchainpointer.h @@ -1,192 +1,192 @@ /* Copyright 2007 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_DUCHAINPOINTER_H #define KDEVPLATFORM_DUCHAINPOINTER_H #include #include #include #include //krazy:excludeall=dpointer namespace KDevelop { class DUContext; class TopDUContext; class DUChainBase; class Declaration; class AbstractFunctionDeclaration; /** * Whenever the du-chain is unlocked and locked again, any du-chain item may have been deleted in between. * For that reason, the following class should be used to make sure that no deleted objects are accessed. It contains a pointer * that will be reset to zero once the pointed object is deleted. * * Access to the data must still be serialized through duchain-locking. Using this comes with no additional cost. * * In practice this means: * Store an instance of DUChainPointer instead of a pointer to the du-chain object. * Then, access the eventually still existing object by calling pointer->base(). * * To make it even more convenient see DUChainPointer * */ class KDEVPLATFORMLANGUAGE_EXPORT DUChainPointerData : public QSharedData { public: /** * Will return zero once the pointed-to object was deleted * */ DUChainBase* base(); /** * Will return zero once the pointed-to object was deleted * */ DUChainBase* base() const; ///Default-initialization of an invalid reference DUChainPointerData(); ~DUChainPointerData(); private: ///Should not be used from outside, but is needed sometimes to construct an invalid dummy-pointer explicit DUChainPointerData( DUChainBase* base ); friend class DUChainBase; DUChainBase * m_base; Q_DISABLE_COPY(DUChainPointerData) }; /** * A smart-pointer similar class that conveniently wraps around DUChainPointerData without * too many dynamic casts. * * It can be used like a normal pointer. In order to cast between pointer types, you should * use the staticCast() and dynamicCast() functions as appropriate. * * Access must be serialized by holding the KDevelop::DUChain::lock() as appropriate for the * function(s) being called. **/ template class DUChainPointer { template friend class DUChainPointer; public: - DUChainPointer() : d(QExplicitlySharedDataPointer(0)) { + DUChainPointer() : d(QExplicitlySharedDataPointer(nullptr)) { } DUChainPointer(const DUChainPointer& rhs) : d(rhs.d) { } ///This constructor includes dynamic casting. If the object cannot be casted to the type, the constructed DUChainPointer will have value zero. template explicit DUChainPointer( OtherType* rhs ) { if( dynamic_cast(rhs) ) d = rhs->weakPointer(); } template explicit DUChainPointer( DUChainPointer rhs ) { if( dynamic_cast(rhs.data()) ) d = rhs.d; } explicit DUChainPointer( QExplicitlySharedDataPointer rhs ) { if( dynamic_cast(rhs->base()) ) d = rhs; } explicit DUChainPointer( Type* rhs ) { if( rhs ) d = rhs->weakPointer(); } bool operator ==( const DUChainPointer& rhs ) const { return d.data() == rhs.d.data(); } bool operator !=( const DUChainPointer& rhs ) const { return d.data() != rhs.d.data(); } ///Returns whether the pointed object is still existing operator bool() const { return d && d->base(); } Type& operator* () const { Q_ASSERT(d); return *static_cast(d->base()); } Type* operator->() const { Q_ASSERT(d); return static_cast(d->base()); } bool operator<(const DUChainPointer& rhs) const { return d.data() < rhs.d.data(); } template DUChainPointer dynamicCast() const { if( dynamic_cast( d->base() ) ) //When the reference to the pointer is constant that doesn't mean that the pointed object needs to be constant return DUChainPointer( static_cast(d->base()) ); else return DUChainPointer(); } Type* data() const { if( !d ) return 0; return static_cast(d->base()); } DUChainPointer& operator= ( Type* rhs ) { if( rhs ) d = rhs->weakPointer(); else d = 0; return *this; } private: QExplicitlySharedDataPointer d; }; typedef DUChainPointer DUChainBasePointer; typedef DUChainPointer DUContextPointer; typedef DUChainPointer TopDUContextPointer; typedef DUChainPointer DeclarationPointer; typedef DUChainPointer FunctionDeclarationPointer; } Q_DECLARE_METATYPE( KDevelop::DUChainBasePointer ) Q_DECLARE_METATYPE( KDevelop::DeclarationPointer ) Q_DECLARE_METATYPE( KDevelop::DUContextPointer ) Q_DECLARE_METATYPE( KDevelop::TopDUContextPointer ) Q_DECLARE_METATYPE( QList ) #endif diff --git a/language/duchain/duchainregister.cpp b/language/duchain/duchainregister.cpp index 09d8a2f0f5..60fa3358af 100644 --- a/language/duchain/duchainregister.cpp +++ b/language/duchain/duchainregister.cpp @@ -1,87 +1,87 @@ /* This file is part of KDevelop 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 "duchainregister.h" #include "duchainbase.h" #include #define ENSURE_VALID_CLASSID(id) \ qFatal("Invalid class id: %i", id); namespace KDevelop { DUChainItemSystem::~DUChainItemSystem() { qDeleteAll(m_factories); } DUChainBase* DUChainItemSystem::create(DUChainBaseData* data) const { - if(uint(m_factories.size()) <= data->classId || m_factories[data->classId] == 0) - return 0; + if(uint(m_factories.size()) <= data->classId || m_factories[data->classId] == nullptr) + return nullptr; return m_factories[data->classId]->create(data); } DUChainBaseData* DUChainItemSystem::cloneData(const DUChainBaseData& data) const { - if(uint(m_factories.size()) <= data.classId || m_factories[data.classId] == 0) { + if(uint(m_factories.size()) <= data.classId || m_factories[data.classId] == nullptr) { ENSURE_VALID_CLASSID(data.classId) - return 0; + return nullptr; } return m_factories[data.classId]->cloneData(data); } void DUChainItemSystem::callDestructor(DUChainBaseData* data) const { - if(uint(m_factories.size()) <= data->classId || m_factories[data->classId] == 0) + if(uint(m_factories.size()) <= data->classId || m_factories[data->classId] == nullptr) return; return m_factories[data->classId]->callDestructor(data); } void DUChainItemSystem::freeDynamicData(KDevelop::DUChainBaseData* data) const { - if(uint(m_factories.size()) <= data->classId || m_factories[data->classId] == 0) + if(uint(m_factories.size()) <= data->classId || m_factories[data->classId] == nullptr) return; return m_factories[data->classId]->freeDynamicData(data); } uint DUChainItemSystem::dynamicSize(const DUChainBaseData& data) const { - if(uint(m_factories.size()) <= data.classId || m_factories[data.classId] == 0) + if(uint(m_factories.size()) <= data.classId || m_factories[data.classId] == nullptr) return 0; return m_factories[data.classId]->dynamicSize(data); } uint DUChainItemSystem::dataClassSize(const DUChainBaseData& data) const { if(uint(m_dataClassSizes.size()) <= data.classId || m_dataClassSizes[data.classId] == 0) return 0; return m_dataClassSizes[data.classId]; } void DUChainItemSystem::copy(const DUChainBaseData& from, DUChainBaseData& to, bool constant) const { - if(uint(m_factories.size()) <= from.classId || m_factories[from.classId] == 0) { + if(uint(m_factories.size()) <= from.classId || m_factories[from.classId] == nullptr) { ENSURE_VALID_CLASSID(from.classId) return; } return m_factories[from.classId]->copy(from, to, constant); } DUChainItemSystem& DUChainItemSystem::self() { static DUChainItemSystem system; return system; } } diff --git a/language/duchain/duchainutils.cpp b/language/duchain/duchainutils.cpp index 32381f238d..0051afd906 100644 --- a/language/duchain/duchainutils.cpp +++ b/language/duchain/duchainutils.cpp @@ -1,627 +1,627 @@ /* * DUChain Utilities * * Copyright 2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchainutils.h" #include #include #include #include "../interfaces/ilanguagesupport.h" #include "../assistant/staticassistantsmanager.h" #include "util/debug.h" #include "declaration.h" #include "classfunctiondeclaration.h" #include "ducontext.h" #include "duchain.h" #include "use.h" #include "duchainlock.h" #include "classmemberdeclaration.h" #include "functiondefinition.h" #include "specializationstore.h" #include "persistentsymboltable.h" #include "classdeclaration.h" #include "parsingenvironment.h" #include using namespace KDevelop; using namespace KTextEditor; CodeCompletionModel::CompletionProperties DUChainUtils::completionProperties(const Declaration* dec) { CodeCompletionModel::CompletionProperties p; if(dec->context()->type() == DUContext::Class) { if (const ClassMemberDeclaration* member = dynamic_cast(dec)) { switch (member->accessPolicy()) { case Declaration::Public: p |= CodeCompletionModel::Public; break; case Declaration::Protected: p |= CodeCompletionModel::Protected; break; case Declaration::Private: p |= CodeCompletionModel::Private; break; default: break; } if (member->isStatic()) p |= CodeCompletionModel::Static; if (member->isAuto()) {}//TODO if (member->isFriend()) p |= CodeCompletionModel::Friend; if (member->isRegister()) {}//TODO if (member->isExtern()) {}//TODO if (member->isMutable()) {}//TODO } } if (const AbstractFunctionDeclaration* function = dynamic_cast(dec)) { p |= CodeCompletionModel::Function; if (function->isVirtual()) p |= CodeCompletionModel::Virtual; if (function->isInline()) p |= CodeCompletionModel::Inline; if (function->isExplicit()) {}//TODO } if( dec->isTypeAlias() ) p |= CodeCompletionModel::TypeAlias; if (dec->abstractType()) { switch (dec->abstractType()->whichType()) { case AbstractType::TypeIntegral: p |= CodeCompletionModel::Variable; break; case AbstractType::TypePointer: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeReference: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeFunction: p |= CodeCompletionModel::Function; break; case AbstractType::TypeStructure: p |= CodeCompletionModel::Class; break; case AbstractType::TypeArray: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeEnumeration: p |= CodeCompletionModel::Enum; break; case AbstractType::TypeEnumerator: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeAbstract: case AbstractType::TypeDelayed: case AbstractType::TypeUnsure: case AbstractType::TypeAlias: // TODO break; } if( dec->abstractType()->modifiers() & AbstractType::ConstModifier ) p |= CodeCompletionModel::Const; if( dec->kind() == Declaration::Instance && !dec->isFunctionDeclaration() ) p |= CodeCompletionModel::Variable; } if (dec->context()) { if( dec->context()->type() == DUContext::Global ) p |= CodeCompletionModel::GlobalScope; else if( dec->context()->type() == DUContext::Namespace ) p |= CodeCompletionModel::NamespaceScope; else if( dec->context()->type() != DUContext::Class && dec->context()->type() != DUContext::Enum ) p |= CodeCompletionModel::LocalScope; } return p; } /**We have to construct the item from the pixmap, else the icon will be marked as "load on demand", * and for some reason will be loaded every time it's used(this function returns a QIcon marked "load on demand" * each time this is called). And the loading is very slow. Seems like a bug somewhere, it cannot be ment to be that slow. */ #define RETURN_CACHED_ICON(name) {static QIcon icon(QIcon( \ QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/pics/" name ".png"))\ ).pixmap(QSize(16, 16)));\ return icon;} QIcon DUChainUtils::iconForProperties(KTextEditor::CodeCompletionModel::CompletionProperties p) { if( (p & CodeCompletionModel::Variable) ) if( (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("CVprotected_var") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_var") else RETURN_CACHED_ICON("CVpublic_var") else if( (p & CodeCompletionModel::Union) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_union") else if( p & CodeCompletionModel::Enum ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_enum") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_enum") else RETURN_CACHED_ICON("enum") else if( p & CodeCompletionModel::Struct ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_struct") else RETURN_CACHED_ICON("struct") else if( p & CodeCompletionModel::Slot ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_slot") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_slot") else if(p & CodeCompletionModel::Public ) RETURN_CACHED_ICON("CVpublic_slot") else RETURN_CACHED_ICON("slot") else if( p & CodeCompletionModel::Signal ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_signal") else RETURN_CACHED_ICON("signal") else if( p & CodeCompletionModel::Class ) if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_class") else if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Private) ) RETURN_CACHED_ICON("private_class") else RETURN_CACHED_ICON("code-class") else if( p & CodeCompletionModel::Union ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_union") else RETURN_CACHED_ICON("union") else if( p & CodeCompletionModel::TypeAlias ) if ((p & CodeCompletionModel::Const) /*|| (p & CodeCompletionModel::Volatile)*/) RETURN_CACHED_ICON("CVtypedef") else RETURN_CACHED_ICON("typedef") else if( p & CodeCompletionModel::Function ) { if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_function") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_function") else RETURN_CACHED_ICON("code-function") } if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_field") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_field") else RETURN_CACHED_ICON("field") return QIcon(); } QIcon DUChainUtils::iconForDeclaration(const Declaration* dec) { return iconForProperties(completionProperties(dec)); } TopDUContext* DUChainUtils::contentContextFromProxyContext(TopDUContext* top) { if(!top) - return 0; + return nullptr; if(top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->isProxyContext()) { if(!top->importedParentContexts().isEmpty()) { - DUContext* ctx = top->importedParentContexts().at(0).context(0); + DUContext* ctx = top->importedParentContexts().at(0).context(nullptr); if(!ctx) - return 0; + return nullptr; TopDUContext* ret = ctx->topContext(); if(!ret) - return 0; + return nullptr; if(ret->url() != top->url()) qCDebug(LANGUAGE) << "url-mismatch between content and proxy:" << top->url().toUrl() << ret->url().toUrl(); if(ret->url() == top->url() && !ret->parsingEnvironmentFile()->isProxyContext()) return ret; } else { qCDebug(LANGUAGE) << "Proxy-context imports no content-context"; } } else return top; - return 0; + return nullptr; } TopDUContext* DUChainUtils::standardContextForUrl(const QUrl& url, bool preferProxyContext) { - KDevelop::TopDUContext* chosen = 0; + KDevelop::TopDUContext* chosen = nullptr; auto languages = ICore::self()->languageController()->languagesForUrl(url); foreach(const auto language, languages) { if(!chosen) { chosen = language->standardContext(url, preferProxyContext); } } if(!chosen) chosen = DUChain::self()->chainForDocument(IndexedString(url), preferProxyContext); if(!chosen && preferProxyContext) return standardContextForUrl(url, false); // Fall back to a normal context return chosen; } struct ItemUnderCursorInternal { Declaration* declaration; DUContext* context; RangeInRevision range; }; ItemUnderCursorInternal itemUnderCursorInternal(const CursorInRevision& c, DUContext* ctx, RangeInRevision::ContainsBehavior behavior) { //Search all collapsed sub-contexts. In C++, those can contain declarations that have ranges out of the context foreach(DUContext* subCtx, ctx->childContexts()) { //This is a little hacky, but we need it in case of foreach macros and similar stuff if(subCtx->range().contains(c, behavior) || subCtx->range().isEmpty() || subCtx->range().start.line == c.line || subCtx->range().end.line == c.line) { ItemUnderCursorInternal sub = itemUnderCursorInternal(c, subCtx, behavior); if(sub.declaration) { return sub; } } } foreach(Declaration* decl, ctx->localDeclarations()) { if(decl->range().contains(c, behavior)) { return {decl, ctx, decl->range()}; } } //Try finding a use under the cursor for(int a = 0; a < ctx->usesCount(); ++a) { if(ctx->uses()[a].m_range.contains(c, behavior)) { return {ctx->topContext()->usedDeclarationForIndex(ctx->uses()[a].m_declarationIndex), ctx, ctx->uses()[a].m_range}; } } return {nullptr, nullptr, RangeInRevision()}; } DUChainUtils::ItemUnderCursor DUChainUtils::itemUnderCursor(const QUrl& url, const KTextEditor::Cursor& cursor) { KDevelop::TopDUContext* top = standardContextForUrl(url.adjusted(QUrl::NormalizePathSegments)); if(!top) { return {nullptr, nullptr, KTextEditor::Range()}; } ItemUnderCursorInternal decl = itemUnderCursorInternal(top->transformToLocalRevision(cursor), top, RangeInRevision::Default); if (decl.declaration == nullptr) { decl = itemUnderCursorInternal(top->transformToLocalRevision(cursor), top, RangeInRevision::IncludeBackEdge); } return {decl.declaration, decl.context, top->transformFromLocalRevision(decl.range)}; } Declaration* DUChainUtils::declarationForDefinition(Declaration* definition, TopDUContext* topContext) { if(!definition) - return 0; + return nullptr; if(!topContext) topContext = definition->topContext(); if(dynamic_cast(definition)) { Declaration* ret = static_cast(definition)->declaration(); if(ret) return ret; } return definition; } Declaration* DUChainUtils::declarationInLine(const KTextEditor::Cursor& _cursor, DUContext* ctx) { if(!ctx) - return 0; + return nullptr; CursorInRevision cursor = ctx->transformToLocalRevision(_cursor); foreach(Declaration* decl, ctx->localDeclarations()) { if(decl->range().start.line == cursor.line) return decl; DUContext* funCtx = getFunctionContext(decl); if(funCtx && funCtx->range().contains(cursor)) return decl; } foreach(DUContext* child, ctx->childContexts()){ Declaration* decl = declarationInLine(_cursor, child); if(decl) return decl; } - return 0; + return nullptr; } DUChainUtils::DUChainItemFilter::~DUChainItemFilter() { } void DUChainUtils::collectItems( DUContext* context, DUChainItemFilter& filter ) { QVector children = context->childContexts(); QVector localDeclarations = context->localDeclarations(); QVector::const_iterator childIt = children.constBegin(); QVector::const_iterator declIt = localDeclarations.constBegin(); while(childIt != children.constEnd() || declIt != localDeclarations.constEnd()) { - DUContext* child = 0; + DUContext* child = nullptr; if(childIt != children.constEnd()) child = *childIt; - Declaration* decl = 0; + Declaration* decl = nullptr; if(declIt != localDeclarations.constEnd()) decl = *declIt; if(decl) { if(child && child->range().start.line >= decl->range().start.line) - child = 0; + child = nullptr; } if(child) { if(decl && decl->range().start >= child->range().start) - decl = 0; + decl = nullptr; } if(decl) { if( filter.accept(decl) ) { //Action is done in the filter } ++declIt; continue; } if(child) { if( filter.accept(child) ) collectItems(child, filter); ++childIt; continue; } } } KDevelop::DUContext* DUChainUtils::getArgumentContext(KDevelop::Declaration* decl) { DUContext* internal = decl->internalContext(); if( !internal ) - return 0; + return nullptr; if( internal->type() == DUContext::Function ) return internal; foreach( const DUContext::Import &ctx, internal->importedParentContexts() ) { if( ctx.context(decl->topContext()) ) if( ctx.context(decl->topContext())->type() == DUContext::Function ) return ctx.context(decl->topContext()); } - return 0; + return nullptr; } QList DUChainUtils::collectAllVersions(Declaration* decl) { QList ret; ret << IndexedDeclaration(decl); if(decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) if(!(allDeclarations[a] == IndexedDeclaration(decl))) ret << allDeclarations[a]; } return ret; } static QList getInheritersInternal(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions) { QList ret; if(!dynamic_cast(decl)) return ret; if(maxAllowedSteps == 0) return ret; if(decl->internalContext() && decl->internalContext()->type() == DUContext::Class) { foreach (const IndexedDUContext importer, decl->internalContext()->indexedImporters()) { DUContext* imp = importer.data(); if(!imp) continue; if(imp->type() == DUContext::Class && imp->owner()) ret << imp->owner(); --maxAllowedSteps; if(maxAllowedSteps == 0) return ret; } } if(collectVersions && decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) { ++maxAllowedSteps; if(allDeclarations[a].data() && allDeclarations[a].data() != decl) { ret += getInheritersInternal(allDeclarations[a].data(), maxAllowedSteps, false); } if(maxAllowedSteps == 0) return ret; } } return ret; } QList DUChainUtils::getInheriters(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions) { auto inheriters = getInheritersInternal(decl, maxAllowedSteps, collectVersions); // remove duplicates std::sort(inheriters.begin(), inheriters.end()); inheriters.erase(std::unique(inheriters.begin(), inheriters.end()), inheriters.end()); return inheriters; } QList DUChainUtils::getOverriders(const Declaration* currentClass, const Declaration* overriddenDeclaration, uint& maxAllowedSteps) { QList ret; if(maxAllowedSteps == 0) return ret; if(currentClass != overriddenDeclaration->context()->owner() && currentClass->internalContext()) ret += currentClass->internalContext()->findLocalDeclarations(overriddenDeclaration->identifier(), CursorInRevision::invalid(), currentClass->topContext(), overriddenDeclaration->abstractType()); foreach(Declaration* inheriter, getInheriters(currentClass, maxAllowedSteps)) ret += getOverriders(inheriter, overriddenDeclaration, maxAllowedSteps); return ret; } static bool hasUse(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return false; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) return true; foreach(DUContext* child, context->childContexts()) if(hasUse(child, usedDeclarationIndex)) return true; return false; } bool DUChainUtils::contextHasUse(DUContext* context, Declaration* declaration) { return hasUse(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } static uint countUses(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return 0; uint ret = 0; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) ++ret; foreach(DUContext* child, context->childContexts()) ret += countUses(child, usedDeclarationIndex); return ret; } uint DUChainUtils::contextCountUses(DUContext* context, Declaration* declaration) { return countUses(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } Declaration* DUChainUtils::getOverridden(const Declaration* decl) { const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl); if(!classFunDecl || !classFunDecl->isVirtual()) - return 0; + return nullptr; QList decls; foreach(const DUContext::Import &import, decl->context()->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx) decls += ctx->findDeclarations(QualifiedIdentifier(decl->identifier()), CursorInRevision::invalid(), decl->abstractType(), decl->topContext(), DUContext::DontSearchInParent); } foreach(Declaration* found, decls) { const ClassFunctionDeclaration* foundClassFunDecl = dynamic_cast(found); if(foundClassFunDecl && foundClassFunDecl->isVirtual()) return found; } - return 0; + return nullptr; } DUContext* DUChainUtils::getFunctionContext(Declaration* decl) { DUContext* functionContext = decl->internalContext(); if(functionContext && functionContext->type() != DUContext::Function) { foreach(const DUContext::Import& import, functionContext->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx && ctx->type() == DUContext::Function) functionContext = ctx; } } if(functionContext && functionContext->type() == DUContext::Function) return functionContext; - return 0; + return nullptr; } QVector KDevelop::DUChainUtils::allProblemsForContext(KDevelop::ReferencedTopDUContext top) { QVector ret; Q_FOREACH ( const auto& p, top->problems() ) { ret << p; } Q_FOREACH ( const auto& p, ICore::self()->languageController()->staticAssistantsManager()->problemsForContext(top) ) { ret << p; } return ret; } diff --git a/language/duchain/ducontext.cpp b/language/duchain/ducontext.cpp index 5d4b1e1d36..32e080ad72 100644 --- a/language/duchain/ducontext.cpp +++ b/language/duchain/ducontext.cpp @@ -1,1708 +1,1708 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 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 "ducontext.h" #include #include #include #include #include #include "ducontextdata.h" #include "declaration.h" #include "duchain.h" #include "duchainlock.h" #include "use.h" #include "identifier.h" #include "topducontext.h" #include "persistentsymboltable.h" #include "aliasdeclaration.h" #include "namespacealiasdeclaration.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "importers.h" #include "uses.h" #include "navigation/abstractdeclarationnavigationcontext.h" #include "navigation/abstractnavigationwidget.h" #include "ducontextdynamicdata.h" #include "util/debug.h" // maximum depth for DUContext::findDeclarationsInternal searches const uint maxParentDepth = 20; using namespace KTextEditor; #ifndef NDEBUG #define ENSURE_CAN_WRITE_(x) {if(x->inDUChain()) { ENSURE_CHAIN_WRITE_LOCKED }} #define ENSURE_CAN_READ_(x) {if(x->inDUChain()) { ENSURE_CHAIN_READ_LOCKED }} #else #define ENSURE_CAN_WRITE_(x) #define ENSURE_CAN_READ_(x) #endif QDebug operator<<(QDebug dbg, const KDevelop::DUContext::Import& import) { QDebugStateSaver saver(dbg); dbg.nospace() << "Import(" << import.indexedContext().data() << ')'; return dbg; } namespace KDevelop { DEFINE_LIST_MEMBER_HASH(DUContextData, m_childContexts, LocalIndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importers, IndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importedContexts, DUContext::Import) DEFINE_LIST_MEMBER_HASH(DUContextData, m_localDeclarations, LocalIndexedDeclaration) DEFINE_LIST_MEMBER_HASH(DUContextData, m_uses, Use) REGISTER_DUCHAIN_ITEM(DUContext); DUChainVisitor::~DUChainVisitor() { } /** * We leak here, to prevent a possible crash during destruction, as the destructor * of Identifier is not safe to be called after the duchain has been destroyed */ const Identifier& globalImportIdentifier() { static const Identifier globalImportIdentifierObject(QStringLiteral("{...import...}")); return globalImportIdentifierObject; } const Identifier& globalAliasIdentifier() { static const Identifier globalAliasIdentifierObject(QStringLiteral("{...alias...}")); return globalAliasIdentifierObject; } const IndexedIdentifier& globalIndexedImportIdentifier() { static const IndexedIdentifier id(globalImportIdentifier()); return id; } const IndexedIdentifier& globalIndexedAliasIdentifier() { static const IndexedIdentifier id(globalAliasIdentifier()); return id; } void DUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_ASSERT(!parent || ownIndex); m_dynamicData->m_topContext = parent ? parent->topContext() : static_cast(this); m_dynamicData->m_indexInTopContext = ownIndex; m_dynamicData->m_parentContext = DUContextPointer(parent); m_dynamicData->m_context = this; m_dynamicData->m_childContexts.clear(); m_dynamicData->m_childContexts.reserve(d_func()->m_childContextsSize()); FOREACH_FUNCTION(const LocalIndexedDUContext& ctx, d_func()->m_childContexts) { m_dynamicData->m_childContexts << ctx.data(m_dynamicData->m_topContext); } m_dynamicData->m_localDeclarations.clear(); m_dynamicData->m_localDeclarations.reserve(d_func()->m_localDeclarationsSize()); FOREACH_FUNCTION(const LocalIndexedDeclaration& idx, d_func()->m_localDeclarations) { auto declaration = idx.data(m_dynamicData->m_topContext); if (!declaration) { qCWarning(LANGUAGE) << "child declaration number" << idx.localIndex() << "of" << d_func_dynamic()->m_localDeclarationsSize() << "is invalid"; continue; } m_dynamicData->m_localDeclarations << declaration; } DUChainBase::rebuildDynamicData(parent, ownIndex); } DUContextData::DUContextData() : m_inSymbolTable(false) , m_anonymousInParent(false) , m_propagateDeclarations(false) { initializeAppendedLists(); } DUContextData::~DUContextData() { freeAppendedLists(); } DUContextData::DUContextData(const DUContextData& rhs) : DUChainBaseData(rhs) , m_inSymbolTable(rhs.m_inSymbolTable) , m_anonymousInParent(rhs.m_anonymousInParent) , m_propagateDeclarations(rhs.m_propagateDeclarations) { initializeAppendedLists(); copyListsFrom(rhs); m_scopeIdentifier = rhs.m_scopeIdentifier; m_contextType = rhs.m_contextType; m_owner = rhs.m_owner; } DUContextDynamicData::DUContextDynamicData(DUContext* d) - : m_topContext(0) + : m_topContext(nullptr) , m_indexInTopContext(0) , m_context(d) { } void DUContextDynamicData::scopeIdentifier(bool includeClasses, QualifiedIdentifier& target) const { if (m_parentContext) m_parentContext->m_dynamicData->scopeIdentifier(includeClasses, target); if (includeClasses || d_func()->m_contextType != DUContext::Class) target += d_func()->m_scopeIdentifier; } bool DUContextDynamicData::imports(const DUContext* context, const TopDUContext* source, QSet* recursionGuard) const { if( this == context->m_dynamicData ) return true; if (recursionGuard->contains(this)) { return false; } recursionGuard->insert(this); FOREACH_FUNCTION( const DUContext::Import& ctx, d_func()->m_importedContexts ) { DUContext* import = ctx.context(source); if(import == context || (import && import->m_dynamicData->imports(context, source, recursionGuard))) return true; } return false; } inline bool isContextTemporary(uint index) { return index > (0xffffffff/2); } void DUContextDynamicData::addDeclaration( Declaration * newDeclaration ) { // The definition may not have its identifier set when it's assigned... // allow dupes here, TODO catch the error elsewhere //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(newDeclaration->ownIndex())); CursorInRevision start = newDeclaration->range().start; bool inserted = false; ///@todo Do binary search to find the position for (int i = m_localDeclarations.size() - 1; i >= 0; --i) { Declaration* child = m_localDeclarations[i]; Q_ASSERT(d_func()->m_localDeclarations()[i].data(m_topContext) == child); if(child == newDeclaration) return; //TODO: All declarations in a macro will have the same empty range, and just get appended //that may not be Good Enough in complex cases. if (start >= child->range().start) { m_localDeclarations.insert(i + 1, newDeclaration); d_func_dynamic()->m_localDeclarationsList().insert(i+1, newDeclaration); Q_ASSERT(d_func()->m_localDeclarations()[i+1].data(m_topContext) == newDeclaration); inserted = true; break; } } if (!inserted) { // We haven't found any child that is before this one, so prepend it m_localDeclarations.insert(0, newDeclaration); d_func_dynamic()->m_localDeclarationsList().insert(0, newDeclaration); Q_ASSERT(d_func()->m_localDeclarations()[0].data(m_topContext) == newDeclaration); } } bool DUContextDynamicData::removeDeclaration(Declaration* declaration) { const int idx = m_localDeclarations.indexOf(declaration); if (idx != -1) { Q_ASSERT(d_func()->m_localDeclarations()[idx].data(m_topContext) == declaration); m_localDeclarations.remove(idx); d_func_dynamic()->m_localDeclarationsList().remove(idx); return true; } else { Q_ASSERT(d_func_dynamic()->m_localDeclarationsList().indexOf(LocalIndexedDeclaration(declaration)) == -1); return false; } } void DUContextDynamicData::addChildContext( DUContext * context ) { // Internal, don't need to assert a lock Q_ASSERT(!context->m_dynamicData->m_parentContext || context->m_dynamicData->m_parentContext.data()->m_dynamicData == this ); LocalIndexedDUContext indexed(context->m_dynamicData->m_indexInTopContext); //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(indexed.localIndex())); bool inserted = false; int childCount = m_childContexts.size(); for (int i = childCount-1; i >= 0; --i) {///@todo Do binary search to find the position DUContext* child = m_childContexts[i]; Q_ASSERT(d_func_dynamic()->m_childContexts()[i] == LocalIndexedDUContext(child)); if (context == child) return; if (context->range().start >= child->range().start) { m_childContexts.insert(i+1, context); d_func_dynamic()->m_childContextsList().insert(i+1, indexed); context->m_dynamicData->m_parentContext = m_context; inserted = true; break; } } if( !inserted ) { m_childContexts.insert(0, context); d_func_dynamic()->m_childContextsList().insert(0, indexed); context->m_dynamicData->m_parentContext = m_context; } } bool DUContextDynamicData::removeChildContext( DUContext* context ) { // ENSURE_CAN_WRITE const int idx = m_childContexts.indexOf(context); if (idx != -1) { m_childContexts.remove(idx); Q_ASSERT(d_func()->m_childContexts()[idx] == LocalIndexedDUContext(context)); d_func_dynamic()->m_childContextsList().remove(idx); return true; } else { Q_ASSERT(d_func_dynamic()->m_childContextsList().indexOf(LocalIndexedDUContext(context)) == -1); return false; } } void DUContextDynamicData::addImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { //Direct importers are registered directly within the data if(d_func_dynamic()->m_importersList().contains(IndexedDUContext(context))) { qCDebug(LANGUAGE) << m_context->scopeIdentifier(true).toString() << "importer added multiple times:" << context->scopeIdentifier(true).toString(); return; } d_func_dynamic()->m_importersList().append(context); }else{ //Indirect importers are registered separately Importers::self().addImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } } //Can also be called with a context that is not in the list void DUContextDynamicData::removeImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { d_func_dynamic()->m_importersList().removeOne(IndexedDUContext(context)); }else{ //Indirect importers are registered separately Importers::self().removeImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } } int DUContext::depth() const { { if (!parentContext()) return 0; return parentContext()->depth() + 1; } } DUContext::DUContext(DUContextData& data) : DUChainBase(data) , m_dynamicData(new DUContextDynamicData(this)) { } DUContext::DUContext(const RangeInRevision& range, DUContext* parent, bool anonymous) : DUChainBase(*new DUContextData(), range) , m_dynamicData(new DUContextDynamicData(this)) { d_func_dynamic()->setClassId(this); if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); d_func_dynamic()->setClassId(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; - m_dynamicData->m_parentContext = 0; + m_dynamicData->m_parentContext = nullptr; d->m_anonymousInParent = anonymous; d->m_inSymbolTable = false; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); Q_ASSERT(m_dynamicData->m_indexInTopContext); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } if(parent && !anonymous && parent->inSymbolTable()) setInSymbolTable(true); } bool DUContext::isAnonymous() const { return d_func()->m_anonymousInParent || (m_dynamicData->m_parentContext && m_dynamicData->m_parentContext->isAnonymous()); } DUContext::DUContext( DUContextData& dd, const RangeInRevision& range, DUContext * parent, bool anonymous ) : DUChainBase(dd, range) , m_dynamicData(new DUContextDynamicData(this)) { if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; - m_dynamicData->m_parentContext = 0; + m_dynamicData->m_parentContext = nullptr; d->m_inSymbolTable = false; d->m_anonymousInParent = anonymous; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } } DUContext::DUContext(DUContext& useDataFrom) : DUChainBase(useDataFrom) , m_dynamicData(useDataFrom.m_dynamicData) { } DUContext::~DUContext( ) { TopDUContext* top = topContext(); if(!top->deleting() || !top->isOnDisk()) { DUCHAIN_D_DYNAMIC(DUContext); if(d->m_owner.declaration()) - d->m_owner.declaration()->setInternalContext(0); + d->m_owner.declaration()->setInternalContext(nullptr); while( d->m_importersSize() != 0 ) { if(d->m_importers()[0].data()) d->m_importers()[0].data()->removeImportedParentContext(this); else { qCDebug(LANGUAGE) << "importer disappeared"; d->m_importersList().removeOne(d->m_importers()[0]); } } clearImportedParentContexts(); } deleteChildContextsRecursively(); if(!topContext()->deleting() || !topContext()->isOnDisk()) deleteUses(); deleteLocalDeclarations(); //If the top-context is being delete, we don't need to spend time rebuilding the inner structure. //That's expensive, especially when the data is not dynamic. if(!top->deleting() || !top->isOnDisk()) { if (m_dynamicData->m_parentContext) m_dynamicData->m_parentContext->m_dynamicData->removeChildContext(this); } top->m_dynamicData->clearContextIndex(this); Q_ASSERT(d_func()->isDynamic() == (!top->deleting() || !top->isOnDisk() || top->m_dynamicData->isTemporaryContextIndex(m_dynamicData->m_indexInTopContext))); delete m_dynamicData; } QVector< DUContext * > DUContext::childContexts( ) const { ENSURE_CAN_READ return m_dynamicData->m_childContexts; } Declaration* DUContext::owner() const { ENSURE_CAN_READ return d_func()->m_owner.declaration(); } void DUContext::setOwner(Declaration* owner) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); if( owner == d->m_owner.declaration() ) return; Declaration* oldOwner = d->m_owner.declaration(); d->m_owner = owner; //Q_ASSERT(!oldOwner || oldOwner->internalContext() == this); if( oldOwner && oldOwner->internalContext() == this ) - oldOwner->setInternalContext(0); + oldOwner->setInternalContext(nullptr); //The context set as internal context should always be the last opened context if( owner ) owner->setInternalContext(this); } DUContext* DUContext::parentContext( ) const { //ENSURE_CAN_READ Commented out for performance reasons return m_dynamicData->m_parentContext.data(); } void DUContext::setPropagateDeclarations(bool propagate) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); if(propagate == d->m_propagateDeclarations) return; d->m_propagateDeclarations = propagate; } bool DUContext::isPropagateDeclarations() const { return d_func()->m_propagateDeclarations; } QList DUContext::findLocalDeclarations( const IndexedIdentifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, const AbstractType::Ptr& dataType, SearchFlags flags ) const { ENSURE_CAN_READ DeclarationList ret; findLocalDeclarationsInternal(identifier, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags); return ret; } QList DUContext::findLocalDeclarations( const Identifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, const AbstractType::Ptr& dataType, SearchFlags flags ) const { return findLocalDeclarations(IndexedIdentifier(identifier), position, topContext, dataType, flags); } namespace { bool contextIsChildOrEqual(const DUContext* childContext, const DUContext* context) { if(childContext == context) return true; if(childContext->parentContext()) return contextIsChildOrEqual(childContext->parentContext(), context); else return false; } struct Checker { Checker(DUContext::SearchFlags flags, const AbstractType::Ptr& dataType, const CursorInRevision & position, DUContext::ContextType ownType) : m_flags(flags) , m_dataType(dataType) , m_position(position) , m_ownType(ownType) { } Declaration* check(Declaration* declaration) const { ///@todo This is C++-specific if (m_ownType != DUContext::Class && m_ownType != DUContext::Template && m_position.isValid() && m_position <= declaration->range().start) { return nullptr; } if (declaration->kind() == Declaration::Alias && !(m_flags & DUContext::DontResolveAliases)) { //Apply alias declarations AliasDeclaration* alias = static_cast(declaration); if (alias->aliasedDeclaration().isValid()) { declaration = alias->aliasedDeclaration().declaration(); } else { qCDebug(LANGUAGE) << "lost aliased declaration"; } } if (declaration->kind() == Declaration::NamespaceAlias && !(m_flags & DUContext::NoFiltering)) { return nullptr; } if ((m_flags & DUContext::OnlyFunctions) && !declaration->isFunctionDeclaration()) { return nullptr; } if (m_dataType && m_dataType->indexed() != declaration->indexedType()) { return nullptr; } return declaration; } DUContext::SearchFlags m_flags; const AbstractType::Ptr m_dataType; const CursorInRevision m_position; DUContext::ContextType m_ownType; }; } void DUContext::findLocalDeclarationsInternal(const Identifier& identifier, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags) const { return findLocalDeclarationsInternal(IndexedIdentifier(identifier), position, dataType, ret, source, flags); } void DUContext::findLocalDeclarationsInternal( const IndexedIdentifier& identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags ) const { Checker checker(flags, dataType, position, type()); DUCHAIN_D(DUContext); if (d->m_inSymbolTable && !d->m_scopeIdentifier.isEmpty() && !identifier.isEmpty()) { //This context is in the symbol table, use the symbol-table to speed up the search QualifiedIdentifier id(scopeIdentifier(true) + identifier); TopDUContext* top = topContext(); uint count; const IndexedDeclaration* declarations; PersistentSymbolTable::self().declarations(id, count, declarations); for (uint a = 0; a < count; ++a) { ///@todo Eventually do efficient iteration-free filtering if (declarations[a].topContextIndex() == top->ownIndex()) { Declaration* decl = declarations[a].declaration(); if (decl && contextIsChildOrEqual(decl->context(), this)) { Declaration* checked = checker.check(decl); if (checked) { ret.append(checked); } } } } } else { //Iterate through all declarations DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); while (it) { Declaration* declaration = *it; if (declaration && declaration->indexedIdentifier() == identifier) { Declaration* checked = checker.check(declaration); if (checked) ret.append(checked); } ++it; } } } bool DUContext::foundEnough( const DeclarationList& ret, SearchFlags flags ) const { if( !ret.isEmpty() && !(flags & DUContext::NoFiltering)) return true; else return false; } bool DUContext::findDeclarationsInternal( const SearchItem::PtrList & baseIdentifiers, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth ) const { if (depth > maxParentDepth) { qCDebug(LANGUAGE) << "maximum depth reached in" << scopeIdentifier(true); return false; } DUCHAIN_D(DUContext); if (d->m_contextType != Namespace) { // If we're in a namespace, delay all the searching into the top-context, because only that has the overview to pick the correct declarations. for (int a = 0; a < baseIdentifiers.size(); ++a) { if (!baseIdentifiers[a]->isExplicitlyGlobal && baseIdentifiers[a]->next.isEmpty()) { // It makes no sense searching locally for qualified identifiers findLocalDeclarationsInternal(baseIdentifiers[a]->identifier, position, dataType, ret, source, flags); } } if (foundEnough(ret, flags)) { return true; } } ///Step 1: Apply namespace-aliases and -imports SearchItem::PtrList aliasedIdentifiers; //Because of namespace-imports and aliases, this identifier may need to be searched under multiple names applyAliases(baseIdentifiers, aliasedIdentifiers, position, false, type() != DUContext::Namespace && type() != DUContext::Global); if (d->m_importedContextsSize() != 0) { ///Step 2: Give identifiers that are not marked as explicitly-global to imported contexts(explicitly global ones are treatead in TopDUContext) SearchItem::PtrList nonGlobalIdentifiers; foreach (const SearchItem::Ptr& identifier, aliasedIdentifiers) { if (!identifier->isExplicitlyGlobal) { nonGlobalIdentifiers << identifier; } } if (!nonGlobalIdentifiers.isEmpty()) { const auto& url = this->url(); for(int import = d->m_importedContextsSize()-1; import >= 0; --import ) { if (position.isValid() && d->m_importedContexts()[import].position.isValid() && position < d->m_importedContexts()[import].position) { continue; } DUContext* context = d->m_importedContexts()[import].context(source); if (!context) { continue; } else if (context == this) { qCDebug(LANGUAGE) << "resolved self as import:" << scopeIdentifier(true); continue; } if (!context->findDeclarationsInternal(nonGlobalIdentifiers, url == context->url() ? position : context->range().end, dataType, ret, source, flags | InImportedParentContext, depth+1)) { return false; } } } } if (foundEnough(ret, flags)) { return true; } ///Step 3: Continue search in parent-context if (!(flags & DontSearchInParent) && shouldSearchInParent(flags) && m_dynamicData->m_parentContext) { applyUpwardsAliases(aliasedIdentifiers, source); return m_dynamicData->m_parentContext->findDeclarationsInternal(aliasedIdentifiers, url() == m_dynamicData->m_parentContext->url() ? position : m_dynamicData->m_parentContext->range().end, dataType, ret, source, flags, depth); } return true; } QList< QualifiedIdentifier > DUContext::fullyApplyAliases(const QualifiedIdentifier& id, const TopDUContext* source) const { ENSURE_CAN_READ if(!source) source = topContext(); SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(id)); const DUContext* current = this; while(current) { SearchItem::PtrList aliasedIdentifiers; current->applyAliases(identifiers, aliasedIdentifiers, CursorInRevision::invalid(), true, false); current->applyUpwardsAliases(identifiers, source); current = current->parentContext(); } QList ret; foreach (const SearchItem::Ptr& item, identifiers) ret += item->toList(); return ret; } QList DUContext::findDeclarations( const QualifiedIdentifier & identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; SearchItem::PtrList identifiers; // optimize: we don't want to allocate the top node always // so create it on stack but ref it so its not deleted by the smart pointer SearchItem item(identifier); item.ref.ref(); identifiers << SearchItem::Ptr(&item); findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags, 0); return ret; } bool DUContext::imports(const DUContext* origin, const CursorInRevision& /*position*/ ) const { ENSURE_CAN_READ QSet recursionGuard; recursionGuard.reserve(8); return m_dynamicData->imports(origin, topContext(), &recursionGuard); } bool DUContext::addIndirectImport(const DUContext::Import& import) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList()[a].position = import.position; return true; } } ///Do not sort the imported contexts by their own line-number, it makes no sense. ///Contexts added first, aka template-contexts, should stay in first place, so they are searched first. d->m_importedContextsList().append(import); return false; } void DUContext::addImportedParentContext( DUContext * context, const CursorInRevision& position, bool anonymous, bool /*temporary*/ ) { ENSURE_CAN_WRITE if(context == this) { qCDebug(LANGUAGE) << "Tried to import self"; return; } if(!context) { qCDebug(LANGUAGE) << "Tried to import invalid context"; return; } Import import(context, this, position); if(addIndirectImport(import)) return; if( !anonymous ) { ENSURE_CAN_WRITE_(context) context->m_dynamicData->addImportedChildContext(this); } } void DUContext::removeImportedParentContext( DUContext * context ) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); Import import(context, this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList().remove(a); break; } } if( !context ) return; context->m_dynamicData->removeImportedChildContext(this); } KDevVarLengthArray DUContext::indexedImporters() const { KDevVarLengthArray ret; if(owner()) ret = Importers::self().importers(owner()->id()); //Add indirect importers to the list FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret.append(ctx); return ret; } QVector DUContext::importers() const { ENSURE_CAN_READ QVector ret; FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret << ctx.context(); if(owner()) { //Add indirect importers to the list KDevVarLengthArray indirect = Importers::self().importers(owner()->id()); foreach (const IndexedDUContext ctx, indirect) { ret << ctx.context(); } } return ret; } DUContext * DUContext::findContext( const CursorInRevision& position, DUContext* parent) const { ENSURE_CAN_READ if (!parent) parent = const_cast(this); foreach (DUContext* context, parent->m_dynamicData->m_childContexts) { if (context->range().contains(position)) { DUContext* ret = findContext(position, context); if (!ret) { ret = context; } return ret; } } - return 0; + return nullptr; } bool DUContext::parentContextOf(DUContext* context) const { if (this == context) return true; foreach (DUContext* child, m_dynamicData->m_childContexts) { if (child->parentContextOf(context)) { return true; } } return false; } QList< QPair > DUContext::allDeclarations(const CursorInRevision& position, const TopDUContext* topContext, bool searchInParents) const { ENSURE_CAN_READ QList< QPair > ret; QHash hadContexts; // Iterate back up the chain mergeDeclarationsInternal(ret, position, hadContexts, topContext ? topContext : this->topContext(), searchInParents); return ret; } QVector DUContext::localDeclarations(const TopDUContext* source) const { ENSURE_CAN_READ // TODO: remove this parameter once we kill old-cpp Q_UNUSED(source); return m_dynamicData->m_localDeclarations; } void DUContext::mergeDeclarationsInternal(QList< QPair >& definitions, const CursorInRevision& position, QHash& hadContexts, const TopDUContext* source, bool searchInParents, int currentDepth) const { ENSURE_CAN_READ if((currentDepth > 300 && currentDepth < 1000) || currentDepth > 1300) { qCDebug(LANGUAGE) << "too much depth"; return; } DUCHAIN_D(DUContext); if(hadContexts.contains(this) && !searchInParents) return; if(!hadContexts.contains(this)) { hadContexts[this] = true; if( (type() == DUContext::Namespace || type() == DUContext::Global) && currentDepth < 1000 ) currentDepth += 1000; { DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); while(it) { Declaration* decl = *it; if ( decl && (!position.isValid() || decl->range().start <= position) ) definitions << qMakePair(decl, currentDepth); ++it; } } for(int a = d->m_importedContextsSize()-1; a >= 0; --a) { const Import* import(&d->m_importedContexts()[a]); DUContext* context = import->context(source); while( !context && a > 0 ) { --a; import = &d->m_importedContexts()[a]; context = import->context(source); } if( !context ) break; if(context == this) { qCDebug(LANGUAGE) << "resolved self as import:" << scopeIdentifier(true); continue; } if( position.isValid() && import->position.isValid() && position < import->position ) continue; context->mergeDeclarationsInternal(definitions, CursorInRevision::invalid(), hadContexts, source, searchInParents && context->shouldSearchInParent(InImportedParentContext) && context->parentContext()->type() == DUContext::Helper, currentDepth+1); } } ///Only respect the position if the parent-context is not a class(@todo this is language-dependent) if (parentContext() && searchInParents ) parentContext()->mergeDeclarationsInternal(definitions, parentContext()->type() == DUContext::Class ? parentContext()->range().end : position, hadContexts, source, searchInParents, currentDepth+1); } void DUContext::deleteLocalDeclarations() { ENSURE_CAN_WRITE // It may happen that the deletion of one declaration triggers the deletion of another one // Therefore we copy the list of indexed declarations and work on those. Indexed declarations // will return zero for already deleted declarations. KDevVarLengthArray indexedLocal; if (d_func()->m_localDeclarations()) { indexedLocal.append(d_func()->m_localDeclarations(), d_func()->m_localDeclarationsSize()); } foreach (const LocalIndexedDeclaration& indexed, m_dynamicData->m_localDeclarations) { delete indexed.data(topContext()); } m_dynamicData->m_localDeclarations.clear(); } void DUContext::deleteChildContextsRecursively() { ENSURE_CAN_WRITE // note: don't use qDeleteAll here because child ctx deletion changes m_dynamicData->m_childContexts // also note: foreach iterates on a copy, so this is safe foreach (DUContext* ctx, m_dynamicData->m_childContexts) { delete ctx; } m_dynamicData->m_childContexts.clear(); } QVector DUContext::clearLocalDeclarations( ) { auto copy = m_dynamicData->m_localDeclarations; foreach (Declaration* dec, copy) { - dec->setContext(0); + dec->setContext(nullptr); } return copy; } QualifiedIdentifier DUContext::scopeIdentifier(bool includeClasses) const { ENSURE_CAN_READ QualifiedIdentifier ret; m_dynamicData->scopeIdentifier(includeClasses, ret); return ret; } bool DUContext::equalScopeIdentifier(const DUContext* rhs) const { ENSURE_CAN_READ const DUContext* left = this; const DUContext* right = rhs; while(left || right) { if(!left || !right) return false; if(!(left->d_func()->m_scopeIdentifier == right->d_func()->m_scopeIdentifier)) return false; left = left->parentContext(); right = right->parentContext(); } return true; } void DUContext::setLocalScopeIdentifier(const QualifiedIdentifier & identifier) { ENSURE_CAN_WRITE bool wasInSymbolTable = inSymbolTable(); setInSymbolTable(false); d_func_dynamic()->m_scopeIdentifier = identifier; setInSymbolTable(wasInSymbolTable); } QualifiedIdentifier DUContext::localScopeIdentifier() const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_scopeIdentifier; } IndexedQualifiedIdentifier DUContext::indexedLocalScopeIdentifier() const { return d_func()->m_scopeIdentifier; } DUContext::ContextType DUContext::type() const { //ENSURE_CAN_READ This is disabled, because type() is called very often while searching, and it costs us performance return d_func()->m_contextType; } void DUContext::setType(ContextType type) { ENSURE_CAN_WRITE d_func_dynamic()->m_contextType = type; } QList DUContext::findDeclarations(const Identifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, SearchFlags flags) const { return findDeclarations(IndexedIdentifier(identifier), position, topContext, flags); } QList DUContext::findDeclarations(const IndexedIdentifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(false, identifier, SearchItem::PtrList())); findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, AbstractType::Ptr(), ret, topContext ? topContext : this->topContext(), flags, 0); return ret; } void DUContext::deleteUse(int index) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().remove(index); } void DUContext::deleteUses() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().clear(); } void DUContext::deleteUsesRecursively() { deleteUses(); foreach (DUContext* childContext, m_dynamicData->m_childContexts) { childContext->deleteUsesRecursively(); } } bool DUContext::inDUChain() const { if( d_func()->m_anonymousInParent || !m_dynamicData->m_parentContext) return false; TopDUContext* top = topContext(); return top && top->inDUChain(); } DUContext* DUContext::specialize(const IndexedInstantiationInformation& /*specialization*/, const TopDUContext* topContext, int /*upDistance*/) { if(!topContext) - return 0; + return nullptr; return this; } CursorInRevision DUContext::importPosition(const DUContext* target) const { ENSURE_CAN_READ DUCHAIN_D(DUContext); Import import(const_cast(target), this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) if(d->m_importedContexts()[a] == import) return d->m_importedContexts()[a].position; return CursorInRevision::invalid(); } QVector DUContext::importedParentContexts() const { ENSURE_CAN_READ QVector ret; ret.reserve(d_func()->m_importedContextsSize()); FOREACH_FUNCTION(const DUContext::Import& import, d_func()->m_importedContexts) ret << import; return ret; } void DUContext::applyAliases(const SearchItem::PtrList& baseIdentifiers, SearchItem::PtrList& identifiers, const CursorInRevision& position, bool canBeNamespace, bool onlyImports) const { DeclarationList imports; findLocalDeclarationsInternal(globalIndexedImportIdentifier(), position, AbstractType::Ptr(), imports, topContext(), DUContext::NoFiltering); if(imports.isEmpty() && onlyImports) { identifiers = baseIdentifiers; return; } for ( const SearchItem::Ptr& identifier : baseIdentifiers ) { bool addUnmodified = true; if( !identifier->isExplicitlyGlobal ) { if( !imports.isEmpty() ) { //We have namespace-imports. foreach ( Declaration* importDecl, imports ) { //Search for the identifier with the import-identifier prepended if(dynamic_cast(importDecl)) { NamespaceAliasDeclaration* alias = static_cast(importDecl); identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier ) ) ) ; }else{ qCDebug(LANGUAGE) << "Declaration with namespace alias identifier has the wrong type" << importDecl->url().str() << importDecl->range().castToSimpleRange(); } } } if( !identifier->isEmpty() && (identifier->hasNext() || canBeNamespace) ) { DeclarationList aliases; - findLocalDeclarationsInternal(identifier->identifier, position, AbstractType::Ptr(), imports, 0, DUContext::NoFiltering); + findLocalDeclarationsInternal(identifier->identifier, position, AbstractType::Ptr(), imports, nullptr, DUContext::NoFiltering); if(!aliases.isEmpty()) { //The first part of the identifier has been found as a namespace-alias. //In c++, we only need the first alias. However, just to be correct, follow them all for now. foreach ( Declaration* aliasDecl, aliases ) { if(!dynamic_cast(aliasDecl)) continue; addUnmodified = false; //The un-modified identifier can be ignored, because it will be replaced with the resolved alias NamespaceAliasDeclaration* alias = static_cast(aliasDecl); //Create an identifier where namespace-alias part is replaced with the alias target identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier->next ) ) ) ; } } } } if( addUnmodified ) identifiers.append(identifier); } } void DUContext::applyUpwardsAliases(SearchItem::PtrList& identifiers, const TopDUContext* /*source*/) const { if(type() == Namespace) { if(d_func()->m_scopeIdentifier.isEmpty()) return; //Make sure we search for the items in all namespaces of the same name, by duplicating each one with the namespace-identifier prepended. //We do this by prepending items to the current identifiers that equal the local scope identifier. SearchItem::Ptr newItem( new SearchItem(d_func()->m_scopeIdentifier.identifier()) ); //This will exclude explictly global identifiers newItem->addToEachNode( identifiers ); if(!newItem->next.isEmpty()) { //Prepend the full scope before newItem DUContext* parent = m_dynamicData->m_parentContext.data(); while(parent) { newItem = SearchItem::Ptr( new SearchItem(parent->d_func()->m_scopeIdentifier, newItem) ); parent = parent->m_dynamicData->m_parentContext.data(); } newItem->isExplicitlyGlobal = true; identifiers.insert(0, newItem); } } } bool DUContext::shouldSearchInParent(SearchFlags flags) const { return (parentContext() && parentContext()->type() == DUContext::Helper && (flags & InImportedParentContext)) || !(flags & InImportedParentContext); } const Use* DUContext::uses() const { ENSURE_CAN_READ return d_func()->m_uses(); } bool DUContext::declarationHasUses(Declaration* decl) { return DUChain::uses()->hasUses(decl->id()); } int DUContext::usesCount() const { return d_func()->m_usesSize(); } bool usesRangeLessThan(const Use& left, const Use& right) { return left.m_range.start < right.m_range.start; } int DUContext::createUse(int declarationIndex, const RangeInRevision& range, int insertBefore) { DUCHAIN_D_DYNAMIC(DUContext); ENSURE_CAN_WRITE Use use(range, declarationIndex); if(insertBefore == -1) { //Find position where to insert const unsigned int size = d->m_usesSize(); const Use* uses = d->m_uses(); const Use* lowerBound = std::lower_bound(uses, uses + size, use, usesRangeLessThan); insertBefore = lowerBound - uses; // comment out to test this: /* unsigned int a = 0; for(; a < size && range.start > uses[a].m_range.start; ++a) { } Q_ASSERT(a == insertBefore); */ } d->m_usesList().insert(insertBefore, use); return insertBefore; } void DUContext::changeUseRange(int useIndex, const RangeInRevision& range) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useIndex].m_range = range; } void DUContext::setUseDeclaration(int useNumber, int declarationIndex) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useNumber].m_declarationIndex = declarationIndex; } DUContext * DUContext::findContextAt(const CursorInRevision & position, bool includeRightBorder) const { ENSURE_CAN_READ // qCDebug(LANGUAGE) << "searchign" << position << "in:" << scopeIdentifier(true).toString() << range() << includeRightBorder; if (!range().contains(position) && (!includeRightBorder || range().end != position)) { // qCDebug(LANGUAGE) << "mismatch"; - return 0; + return nullptr; } const auto childContexts = m_dynamicData->m_childContexts; for(int a = childContexts.size() - 1; a >= 0; --a) { if (DUContext* specific = childContexts[a]->findContextAt(position, includeRightBorder)) { return specific; } } return const_cast(this); } Declaration * DUContext::findDeclarationAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) - return 0; + return nullptr; foreach (Declaration* child, m_dynamicData->m_localDeclarations) { if (child->range().contains(position)) { return child; } } - return 0; + return nullptr; } DUContext* DUContext::findContextIncluding(const RangeInRevision& range) const { ENSURE_CAN_READ if (!this->range().contains(range)) - return 0; + return nullptr; foreach (DUContext* child, m_dynamicData->m_childContexts) { if (DUContext* specific = child->findContextIncluding(range)) { return specific; } } return const_cast(this); } int DUContext::findUseAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) return -1; for(unsigned int a = 0; a < d_func()->m_usesSize(); ++a) if (d_func()->m_uses()[a].m_range.contains(position)) return a; return -1; } bool DUContext::inSymbolTable() const { return d_func()->m_inSymbolTable; } void DUContext::setInSymbolTable(bool inSymbolTable) { d_func_dynamic()->m_inSymbolTable = inSymbolTable; } void DUContext::clearImportedParentContexts() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); while( d->m_importedContextsSize() != 0 ) { - DUContext* ctx = d->m_importedContexts()[0].context(0, false); + DUContext* ctx = d->m_importedContexts()[0].context(nullptr, false); if(ctx) ctx->m_dynamicData->removeImportedChildContext(this); d->m_importedContextsList().removeOne(d->m_importedContexts()[0]); } } void DUContext::cleanIfNotEncountered(const QSet& encountered) { ENSURE_CAN_WRITE // It may happen that the deletion of one declaration triggers the deletion of another one // Therefore we copy the list of indexed declarations and work on those. Indexed declarations // will return zero for already deleted declarations. KDevVarLengthArray indexedLocal; if (d_func()->m_localDeclarations()) { indexedLocal.append(d_func()->m_localDeclarations(), d_func()->m_localDeclarationsSize()); } foreach (const LocalIndexedDeclaration& indexed, m_dynamicData->m_localDeclarations) { auto dec = indexed.data(topContext()); if (dec && !encountered.contains(dec) && (!dec->isAutoDeclaration() || !dec->hasUses())) { delete dec; } } foreach (DUContext* childContext, m_dynamicData->m_childContexts) { if (!encountered.contains(childContext)) { delete childContext; } } } TopDUContext* DUContext::topContext() const { return m_dynamicData->m_topContext; } QWidget* DUContext::createNavigationWidget(Declaration* decl, TopDUContext* topContext, const QString& htmlPrefix, const QString& htmlSuffix, AbstractNavigationWidget::DisplayHints hints) const { if (decl) { AbstractNavigationWidget* widget = new AbstractNavigationWidget; widget->setDisplayHints(hints); AbstractDeclarationNavigationContext* context = new AbstractDeclarationNavigationContext(DeclarationPointer(decl), TopDUContextPointer(topContext)); context->setPrefixSuffix(htmlPrefix, htmlSuffix); widget->setContext(NavigationContextPointer(context)); return widget; } else { - return 0; + return nullptr; } } QList allUses(DUContext* context, int declarationIndex, bool noEmptyUses) { QList ret; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == declarationIndex) if(!noEmptyUses || !context->uses()[a].m_range.isEmpty()) ret << context->uses()[a].m_range; foreach(DUContext* child, context->childContexts()) ret += allUses(child, declarationIndex, noEmptyUses); return ret; } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, const Ptr& nextItem, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(!id.isEmpty()) { if(id.count() > start) identifier = id.indexedAt(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItem, start+1) )); else if(nextItem) next.append(nextItem); }else if(nextItem) { ///If there is no prefix, just copy nextItem isExplicitlyGlobal = nextItem->isExplicitlyGlobal; identifier = nextItem->identifier; next = nextItem->next; } } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, const PtrList& nextItems, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(id.count() > start) identifier = id.indexedAt(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItems, start+1) )); else next = nextItems; } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const PtrList& nextItems) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) , next(nextItems) { } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const Ptr& nextItem) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) { next.append(nextItem); } bool DUContext::SearchItem::match(const QualifiedIdentifier& id, int offset) const { if(id.isEmpty()) { if(identifier.isEmpty() && next.isEmpty()) return true; else return false; } if(id.at(offset) != identifier) //The identifier is different return false; if(offset == id.count()-1) { if(next.isEmpty()) return true; //match else return false; //id is too short } for(int a = 0; a < next.size(); ++a) if(next[a]->match(id, offset+1)) return true; return false; } bool DUContext::SearchItem::isEmpty() const { return identifier.isEmpty(); } bool DUContext::SearchItem::hasNext() const { return !next.isEmpty(); } QList DUContext::SearchItem::toList(const QualifiedIdentifier& prefix) const { QList ret; QualifiedIdentifier id = prefix; if(id.isEmpty()) id.setExplicitlyGlobal(isExplicitlyGlobal); if(!identifier.isEmpty()) id.push(identifier); if(next.isEmpty()) { ret << id; } else { for(int a = 0; a < next.size(); ++a) ret += next[a]->toList(id); } return ret; } void DUContext::SearchItem::addNext(const SearchItem::Ptr& other) { next.append(other); } void DUContext::SearchItem::addToEachNode(const SearchItem::Ptr& other) { if(other->isExplicitlyGlobal) return; next.append(other); for(int a = 0; a < next.size()-1; ++a) next[a]->addToEachNode(other); } void DUContext::SearchItem::addToEachNode(const SearchItem::PtrList& other) { int added = 0; for (const SearchItem::Ptr& o : other) { if(!o->isExplicitlyGlobal) { next.append(o); ++added; } } for(int a = 0; a < next.size()-added; ++a) next[a]->addToEachNode(other); } DUContext::Import::Import(DUContext* _context, const DUContext* importer, const CursorInRevision& _position) : position(_position) { if(_context && _context->owner() && (_context->owner()->specialization().index() || (importer && importer->topContext() != _context->topContext()))) { m_declaration = _context->owner()->id(); }else{ m_context = _context; } } DUContext::Import::Import(const DeclarationId& id, const CursorInRevision& _position) : position(_position) { m_declaration = id; } DUContext* DUContext::Import::context(const TopDUContext* topContext, bool instantiateIfRequired) const { if(m_declaration.isValid()) { Declaration* decl = m_declaration.getDeclaration(topContext, instantiateIfRequired); //This first case rests on the assumption that no context will ever import a function's expression context //More accurately, that no specialized or cross-topContext imports will, but if the former assumption fails the latter will too if (AbstractFunctionDeclaration *functionDecl = dynamic_cast(decl)) { if (functionDecl->internalFunctionContext()) { return functionDecl->internalFunctionContext(); } else { qCWarning(LANGUAGE) << "Import of function declaration without internal function context encountered!"; } } if(decl) return decl->logicalInternalContext(topContext); else - return 0; + return nullptr; }else{ return m_context.data(); } } bool DUContext::Import::isDirect() const { return m_context.isValid(); } void DUContext::visit(DUChainVisitor& visitor) { ENSURE_CAN_READ visitor.visit(this); foreach (Declaration* decl, m_dynamicData->m_localDeclarations) { visitor.visit(decl); } foreach (DUContext* childContext, m_dynamicData->m_childContexts) { childContext->visit(visitor); } } static bool sortByRange(const DUChainBase* lhs, const DUChainBase* rhs) { return lhs->range() < rhs->range(); } void DUContext::resortLocalDeclarations() { ENSURE_CAN_WRITE std::sort(m_dynamicData->m_localDeclarations.begin(), m_dynamicData->m_localDeclarations.end(), sortByRange); auto top = topContext(); auto& declarations = d_func_dynamic()->m_localDeclarationsList(); std::sort(declarations.begin(), declarations.end(), [top] (const LocalIndexedDeclaration& lhs, const LocalIndexedDeclaration& rhs) { return lhs.data(top)->range() < rhs.data(top)->range(); }); } void DUContext::resortChildContexts() { ENSURE_CAN_WRITE std::sort(m_dynamicData->m_childContexts.begin(), m_dynamicData->m_childContexts.end(), sortByRange); auto top = topContext(); auto& contexts = d_func_dynamic()->m_childContextsList(); std::sort(contexts.begin(), contexts.end(), [top] (const LocalIndexedDUContext& lhs, const LocalIndexedDUContext& rhs) { return lhs.data(top)->range() < rhs.data(top)->range(); }); } } diff --git a/language/duchain/dumpdotgraph.cpp b/language/duchain/dumpdotgraph.cpp index 597950da81..8ec2241569 100644 --- a/language/duchain/dumpdotgraph.cpp +++ b/language/duchain/dumpdotgraph.cpp @@ -1,201 +1,201 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "dumpdotgraph.h" #include "ducontext.h" #include "topducontext.h" #include "declaration.h" #include "duchainpointer.h" #include "parsingenvironment.h" #include "identifier.h" #include "functiondefinition.h" namespace KDevelop { QString shortLabel(KDevelop::DUContext* context) { return QStringLiteral("q%1").arg((quint64)context); } QString shortLabel(KDevelop::Declaration* declaration) { return QStringLiteral("q%1").arg((quint64)declaration); } QString rangeToString( const KTextEditor::Range& r ) { return QStringLiteral("%1:%2->%3:%4").arg(r.start().line()).arg(r.start().column()).arg(r.end().line()).arg(r.end().column()); } class DumpDotGraphPrivate { public: QString dotGraphInternal(KDevelop::DUContext* contex, bool isMaster, bool shortened); void addDeclaration(QTextStream& stream, Declaration* decl); QMap m_hadVersions; QMap m_hadObjects; TopDUContext* m_topContext; }; QString DumpDotGraph::dotGraph(KDevelop::DUContext* context, bool shortened ) { d->m_hadObjects.clear(); d->m_hadVersions.clear(); d->m_topContext = context->topContext(); ///@todo maybe get this as a parameter return d->dotGraphInternal(context, true, shortened); } DumpDotGraph::DumpDotGraph() : d(new DumpDotGraphPrivate()) { } DumpDotGraph::~DumpDotGraph() { delete d; } void DumpDotGraphPrivate::addDeclaration(QTextStream& stream, Declaration* dec) { if( m_hadObjects.contains(dec) ) return; m_hadObjects[dec] = true; - Declaration* declarationForDefinition = 0; + Declaration* declarationForDefinition = nullptr; if(dynamic_cast(dec)) declarationForDefinition = static_cast(dec)->declaration(m_topContext); if(!declarationForDefinition) { //Declaration stream << shortLabel(dec) << "[shape=box, label=\"" << dec->toString() << " [" << dec->qualifiedIdentifier().toString() << "]" << " " << rangeToString(dec->range().castToSimpleRange()) << "\"];\n"; stream << shortLabel(dec->context()) << " -> " << shortLabel(dec) << "[color=green];\n"; if( dec->internalContext() ) stream << shortLabel(dec) << " -> " << shortLabel(dec->internalContext()) << "[label=\"internal\", color=blue];\n"; }else{ //Definition stream << shortLabel(dec) << "[shape=regular,color=yellow,label=\"" << declarationForDefinition->toString() << " "<< rangeToString(dec->range().castToSimpleRange()) << "\"];\n"; stream << shortLabel(dec->context()) << " -> " << shortLabel(dec) << ";\n"; stream << shortLabel(dec) << " -> " << shortLabel(declarationForDefinition) << "[label=\"defines\",color=green];\n"; addDeclaration(stream, declarationForDefinition); if( dec->internalContext() ) stream << shortLabel(dec) << " -> " << shortLabel(dec->internalContext()) << "[label=\"internal\", color=blue];\n"; } } QString DumpDotGraphPrivate::dotGraphInternal(KDevelop::DUContext* context, bool isMaster, bool shortened) { if( m_hadObjects.contains(context) ) return QString(); m_hadObjects[context] = true; QTextStream stream; QString ret; stream.setString(&ret, QIODevice::WriteOnly); if( isMaster ) stream << "Digraph chain {\n"; QString shape = QStringLiteral("parallelogram"); //QString shape = "box"; QString label = QStringLiteral("unknown"); if( dynamic_cast(context) ) { TopDUContext* topCtx = static_cast(context); if( topCtx->parsingEnvironmentFile() ) { QUrl url = topCtx->parsingEnvironmentFile()->url().toUrl(); const QString fileName = url.fileName(); QString file = url.toDisplayString(QUrl::PreferLocalFile); if(topCtx->parsingEnvironmentFile() && topCtx->parsingEnvironmentFile()->isProxyContext()) url.setPath(url.path() + QLatin1String("/_[proxy]_")); //Find the context this one is derived from. If there is one, connect it with a line, and shorten the url. if( m_hadVersions.contains(url) ) { stream << shortLabel(context) << " -> " << m_hadVersions[url] << "[color=blue,label=\"version\"];\n"; file = fileName; } else { m_hadVersions[url] = shortLabel(context); } label = file; if( topCtx->importers().count() != 0 ) label += QStringLiteral(" imported by %1").arg(topCtx->importers().count()); } else { label = QStringLiteral("unknown file"); } if(topCtx->parsingEnvironmentFile() && topCtx->parsingEnvironmentFile()->isProxyContext()) label = "Proxy-context " + label; }else{ label = /*"context " + */context->localScopeIdentifier().toString(); label += ' ' + rangeToString(context->range().castToSimpleRange()); } //label = QStringLiteral("%1 ").arg((size_t)context) + label; if( isMaster && !dynamic_cast(context) ) { //Also draw contexts that import this one foreach( DUContext* ctx, context->importers() ) stream << dotGraphInternal(ctx, false, true); } foreach (const DUContext::Import &parent, context->importedParentContexts()) { if( parent.context(m_topContext) ) { stream << dotGraphInternal(parent.context(m_topContext), false, true); QString label = QStringLiteral("imports"); if( (!dynamic_cast(parent.context(m_topContext)) || !dynamic_cast(context)) && !(parent.context(m_topContext)->url() == context->url()) ) { label += " from " + parent.context(m_topContext)->url().toUrl().fileName() + " to " + context->url().toUrl().fileName(); } stream << shortLabel(context) << " -> " << shortLabel(parent.context(m_topContext)) << "[style=dotted,label=\"" << label << "\"];\n"; } } if( !context->childContexts().isEmpty() ) label += QStringLiteral(", %1 C.").arg(context->childContexts().count()); if( !shortened ) { foreach (DUContext* child, context->childContexts()) { stream << dotGraphInternal(child, false, false); stream << shortLabel(context) << " -> " << shortLabel(child) << "[style=dotted,color=green];\n"; } } if( !context->localDeclarations().isEmpty() ) label += QStringLiteral(", %1 D.").arg(context->localDeclarations().count()); if(!shortened ) { foreach (Declaration* dec, context->localDeclarations()) addDeclaration(stream, dec); } if( context->owner() ) { addDeclaration(stream, context->owner()); } stream << shortLabel(context) << "[shape=" << shape << ",label=\"" << label << "\"" << (isMaster ? QStringLiteral("color=red") : QStringLiteral("color=blue")) << "];\n"; if( isMaster ) stream << "}\n"; return ret; } } diff --git a/language/duchain/forwarddeclaration.cpp b/language/duchain/forwarddeclaration.cpp index 9cdcda726a..3328596869 100644 --- a/language/duchain/forwarddeclaration.cpp +++ b/language/duchain/forwarddeclaration.cpp @@ -1,115 +1,115 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "forwarddeclaration.h" #include #include #include "duchain.h" #include "duchainlock.h" #include "ducontext.h" #include "use.h" #include "duchainregister.h" #include "types/identifiedtype.h" using namespace KTextEditor; namespace KDevelop { REGISTER_DUCHAIN_ITEM(ForwardDeclaration); ForwardDeclaration::ForwardDeclaration(const ForwardDeclaration& rhs) : Declaration(*new ForwardDeclarationData(*rhs.d_func())) { } ForwardDeclaration::ForwardDeclaration(ForwardDeclarationData& data) : Declaration(data) { } ForwardDeclaration::ForwardDeclaration(const RangeInRevision& range, DUContext* context ) : Declaration(*new ForwardDeclarationData, range) { d_func_dynamic()->setClassId(this); if( context ) setContext( context ); } ForwardDeclaration::~ForwardDeclaration() { } QString ForwardDeclaration::toString() const { if(context() ) return qualifiedIdentifier().toString(); else return i18n("context-free forward-declaration %1", identifier().toString()); } Declaration * ForwardDeclaration::resolve(const TopDUContext* topContext) const { ENSURE_CAN_READ //If we've got a type assigned, that counts as a way of resolution. AbstractType::Ptr t = abstractType(); IdentifiedType* idType = dynamic_cast(t.data()); if( idType ) { Declaration* decl = idType->declaration(topContext); if(decl && !decl->isForwardDeclaration()) return decl; else - return 0; + return nullptr; } if(!topContext) topContext = this->topContext(); QualifiedIdentifier globalIdentifier = qualifiedIdentifier(); globalIdentifier.setExplicitlyGlobal(true); //We've got to use DUContext::DirectQualifiedLookup so C++ works correctly. - QList declarations = topContext->findDeclarations(globalIdentifier, CursorInRevision::invalid(), AbstractType::Ptr(), 0, DUContext::DirectQualifiedLookup); + QList declarations = topContext->findDeclarations(globalIdentifier, CursorInRevision::invalid(), AbstractType::Ptr(), nullptr, DUContext::DirectQualifiedLookup); foreach(Declaration* decl, declarations) { if( !decl->isForwardDeclaration() ) return decl; } - return 0; + return nullptr; } DUContext * ForwardDeclaration::logicalInternalContext(const TopDUContext* topContext) const { ENSURE_CAN_READ Declaration* resolved = resolve(topContext); if(resolved && resolved != this) return resolved->logicalInternalContext(topContext); else return Declaration::logicalInternalContext(topContext); } bool ForwardDeclaration::isForwardDeclaration() const { return true; } Declaration* ForwardDeclaration::clonePrivate() const { return new ForwardDeclaration(*this); } } diff --git a/language/duchain/functiondefinition.cpp b/language/duchain/functiondefinition.cpp index 038a44ec8c..ae9c1d85b6 100644 --- a/language/duchain/functiondefinition.cpp +++ b/language/duchain/functiondefinition.cpp @@ -1,101 +1,101 @@ /* This file is part of KDevelop 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 "functiondefinition.h" #include "duchainregister.h" #include "definitions.h" namespace KDevelop { REGISTER_DUCHAIN_ITEM(FunctionDefinition); FunctionDefinition::FunctionDefinition(FunctionDefinitionData& data) : FunctionDeclaration(data) { } FunctionDefinition::FunctionDefinition(const RangeInRevision& range, DUContext* context) : FunctionDeclaration(*new FunctionDefinitionData, range) { d_func_dynamic()->setClassId(this); setDeclarationIsDefinition(true); if( context ) setContext( context ); } FunctionDefinition::FunctionDefinition(const FunctionDefinition& rhs) : FunctionDeclaration(*new FunctionDefinitionData(*rhs.d_func())) { } FunctionDefinition::~FunctionDefinition() { if(!topContext()->isOnDisk()) DUChain::definitions()->removeDefinition(d_func()->m_declaration, this); } Declaration* FunctionDefinition::declaration(const TopDUContext* topContext) const { ENSURE_CAN_READ KDevVarLengthArray declarations = d_func()->m_declaration.getDeclarations(topContext ? topContext : this->topContext()); foreach (Declaration* decl, declarations) { if(!dynamic_cast(decl)) return decl; } - return 0; + return nullptr; } bool FunctionDefinition::hasDeclaration() const { return d_func()->m_declaration.isValid(); } void FunctionDefinition::setDeclaration(Declaration* declaration) { ENSURE_CAN_WRITE if(declaration) { DUChain::definitions()->addDefinition(declaration->id(), this); d_func_dynamic()->m_declaration = declaration->id(); }else{ if(d_func()->m_declaration.isValid()) { DUChain::definitions()->removeDefinition(d_func()->m_declaration, this); d_func_dynamic()->m_declaration = DeclarationId(); } } } FunctionDefinition* FunctionDefinition::definition(const Declaration* decl) { ENSURE_CHAIN_READ_LOCKED if (!decl) { return nullptr; } KDevVarLengthArray allDefinitions = DUChain::definitions()->definitions(decl->id()); foreach (const IndexedDeclaration decl, allDefinitions) { if(decl.data()) ///@todo Find better ways of deciding which definition to use return dynamic_cast(decl.data()); } - return 0; + return nullptr; } Declaration* FunctionDefinition::clonePrivate() const { return new FunctionDefinition(*new FunctionDefinitionData(*d_func())); } } diff --git a/language/duchain/identifier.cpp b/language/duchain/identifier.cpp index 37cf772506..b193f8acae 100644 --- a/language/duchain/identifier.cpp +++ b/language/duchain/identifier.cpp @@ -1,1604 +1,1604 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "identifier.h" #include #include "stringhelpers.h" #include "appendedlist_static.h" #include "serialization/itemrepository.h" #include "util/kdevhash.h" #include "util/debug.h" #include #include #define ifDebug(x) namespace KDevelop { template class IdentifierPrivate { public: IdentifierPrivate() : m_unique(0) , m_refCount(0) , m_hash(0) { } template IdentifierPrivate(const IdentifierPrivate& rhs) : m_unique(rhs.m_unique) , m_identifier(rhs.m_identifier) , m_refCount(0) , m_hash(rhs.m_hash) { copyListsFrom(rhs); } ~IdentifierPrivate() { templateIdentifiersList.free(const_cast(templateIdentifiers())); } //Flags the stored hash-value invalid void clearHash() { //This is always called on an object private to an Identifier, so there is no threading-problem. Q_ASSERT(dynamic); m_hash = 0; } uint hash() const { // Since this only needs reading and the data needs not to be private, this may be called by // multiple threads simultaneously, so computeHash() must be thread-safe. if( !m_hash && dynamic ) computeHash(); return m_hash; } int m_unique; IndexedString m_identifier; uint m_refCount; START_APPENDED_LISTS_STATIC(IdentifierPrivate) APPENDED_LIST_FIRST_STATIC(IndexedTypeIdentifier, templateIdentifiers) END_APPENDED_LISTS_STATIC(templateIdentifiers) uint itemSize() const { return sizeof(IdentifierPrivate) + lastOffsetBehind(); } void computeHash() const { Q_ASSERT(dynamic); //this must stay thread-safe(may be called by multiple threads at a time) //The thread-safety is given because all threads will have the same result, and it will only be written once at the end. KDevHash kdevhash; kdevhash << m_identifier.hash() << m_unique; FOREACH_FUNCTION_STATIC(const IndexedTypeIdentifier& templateIdentifier, templateIdentifiers) kdevhash << templateIdentifier.hash(); m_hash = kdevhash; } mutable uint m_hash; }; typedef IdentifierPrivate DynamicIdentifierPrivate; typedef IdentifierPrivate ConstantIdentifierPrivate; struct IdentifierItemRequest { IdentifierItemRequest(const DynamicIdentifierPrivate& identifier) : m_identifier(identifier) { identifier.hash(); //Make sure the hash is valid by calling this } enum { AverageSize = sizeof(IdentifierPrivate)+4 }; //Should return the hash-value associated with this request(For example the hash of a string) uint hash() const { return m_identifier.hash(); } //Should return the size of an item created with createItem uint itemSize() const { return m_identifier.itemSize(); } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(ConstantIdentifierPrivate* item) const { new (item) ConstantIdentifierPrivate(m_identifier); } static bool persistent(const ConstantIdentifierPrivate* item) { return (bool)item->m_refCount; } static void destroy(ConstantIdentifierPrivate* item, AbstractItemRepository&) { item->~ConstantIdentifierPrivate(); } //Should return whether the here requested item equals the given item bool equals(const ConstantIdentifierPrivate* item) const { return item->m_hash == m_identifier.m_hash && item->m_unique == m_identifier.m_unique && item->m_identifier == m_identifier.m_identifier && m_identifier.listsEqual(*item); } const DynamicIdentifierPrivate& m_identifier; }; using IdentifierRepository = RepositoryManager< ItemRepository, false>; static IdentifierRepository& identifierRepository() { static IdentifierRepository identifierRepositoryObject(QStringLiteral("Identifier Repository")); return identifierRepositoryObject; } static uint emptyConstantIdentifierPrivateIndex() { static const uint index = identifierRepository()->index(DynamicIdentifierPrivate()); return index; } static const ConstantIdentifierPrivate* emptyConstantIdentifierPrivate() { static const ConstantIdentifierPrivate item; return &item; } bool IndexedIdentifier::isEmpty() const { return index == emptyConstantIdentifierPrivateIndex(); } /** * Before something is modified in QualifiedIdentifierPrivate, it must be made sure that * it is private to the QualifiedIdentifier it is used in(@see QualifiedIdentifier::prepareWrite) */ template class QualifiedIdentifierPrivate { public: QualifiedIdentifierPrivate() : m_explicitlyGlobal(false) , m_isExpression(false) , m_hash(0) , m_refCount(0) { } template QualifiedIdentifierPrivate(const QualifiedIdentifierPrivate& rhs) : m_explicitlyGlobal(rhs.m_explicitlyGlobal) , m_isExpression(rhs.m_isExpression) , m_hash(rhs.m_hash) , m_refCount(0) { copyListsFrom(rhs); } ~QualifiedIdentifierPrivate() { identifiersList.free(const_cast(identifiers())); } bool m_explicitlyGlobal:1; bool m_isExpression:1; mutable uint m_hash; uint m_refCount; START_APPENDED_LISTS_STATIC(QualifiedIdentifierPrivate) APPENDED_LIST_FIRST_STATIC(IndexedIdentifier, identifiers) END_APPENDED_LISTS_STATIC(identifiers) uint itemSize() const { return sizeof(QualifiedIdentifierPrivate) + lastOffsetBehind(); } //Constructs m_identifiers void splitIdentifiers( const QString& str, int start ) { Q_ASSERT(dynamic); uint currentStart = start; while( currentStart < (uint)str.length() ) { identifiersList.append(IndexedIdentifier(Identifier( str, currentStart, ¤tStart ))); while( currentStart < (uint)str.length() && (str[currentStart] == ' ' ) ) ++currentStart; currentStart += 2; //Skip "::" } } inline void clearHash() const { m_hash = 0; } uint hash() const { if( m_hash == 0 ) { KDevHash hash; quint32 bitfields = m_explicitlyGlobal | (m_isExpression << 1); hash << bitfields << identifiersSize(); FOREACH_FUNCTION_STATIC( const IndexedIdentifier& identifier, identifiers ) { hash << identifier.getIndex(); } m_hash = hash; } return m_hash; } }; typedef QualifiedIdentifierPrivate DynamicQualifiedIdentifierPrivate; typedef QualifiedIdentifierPrivate ConstantQualifiedIdentifierPrivate; struct QualifiedIdentifierItemRequest { QualifiedIdentifierItemRequest(const DynamicQualifiedIdentifierPrivate& identifier) : m_identifier(identifier) { identifier.hash(); //Make sure the hash is valid by calling this } enum { AverageSize = sizeof(QualifiedIdentifierPrivate)+8 }; //Should return the hash-value associated with this request(For example the hash of a string) uint hash() const { return m_identifier.hash(); } //Should return the size of an item created with createItem uint itemSize() const { return m_identifier.itemSize(); } /** * Should create an item where the information of the requested item is permanently stored. The pointer * @param item equals an allocated range with the size of itemSize(). */ void createItem(ConstantQualifiedIdentifierPrivate* item) const { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); new (item) ConstantQualifiedIdentifierPrivate(m_identifier); } static bool persistent(const ConstantQualifiedIdentifierPrivate* item) { return (bool)item->m_refCount; } static void destroy(ConstantQualifiedIdentifierPrivate* item, AbstractItemRepository&) { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); item->~ConstantQualifiedIdentifierPrivate(); } //Should return whether the here requested item equals the given item bool equals(const ConstantQualifiedIdentifierPrivate* item) const { return item->m_explicitlyGlobal == m_identifier.m_explicitlyGlobal && item->m_isExpression == m_identifier.m_isExpression && item->m_hash == m_identifier.m_hash && m_identifier.listsEqual(*item); } const DynamicQualifiedIdentifierPrivate& m_identifier; }; using QualifiedIdentifierRepository = RepositoryManager< ItemRepository, false>; static QualifiedIdentifierRepository& qualifiedidentifierRepository() { static QualifiedIdentifierRepository repo(QStringLiteral("Qualified Identifier Repository"), 1, [] () -> AbstractRepositoryManager* { return &identifierRepository(); }); return repo; } static uint emptyConstantQualifiedIdentifierPrivateIndex() { static const uint index = qualifiedidentifierRepository()->index(DynamicQualifiedIdentifierPrivate()); return index; } static const ConstantQualifiedIdentifierPrivate* emptyConstantQualifiedIdentifierPrivate() { static const ConstantQualifiedIdentifierPrivate item; return &item; } Identifier::Identifier(const Identifier& rhs) { rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; } Identifier::Identifier(uint index) : m_index(index) { Q_ASSERT(m_index); cd = identifierRepository()->itemFromIndex(index); } Identifier::Identifier(const IndexedString& str) { if (str.isEmpty()) { m_index = emptyConstantIdentifierPrivateIndex(); cd = emptyConstantIdentifierPrivate(); } else { m_index = 0; dd = new IdentifierPrivate; dd->m_identifier = str; } } Identifier::Identifier(const QString& id, uint start, uint* takenRange) { if (id.isEmpty()) { m_index = emptyConstantIdentifierPrivateIndex(); cd = emptyConstantIdentifierPrivate(); return; } m_index = 0; dd = new IdentifierPrivate; ///Extract template-parameters ParamIterator paramIt(QStringLiteral("<>:"), id, start); dd->m_identifier = IndexedString(paramIt.prefix().trimmed()); while( paramIt ) { appendTemplateIdentifier( IndexedTypeIdentifier(IndexedQualifiedIdentifier(QualifiedIdentifier(*paramIt))) ); ++paramIt; } if( takenRange ) *takenRange = paramIt.position(); } Identifier::Identifier() : m_index(emptyConstantIdentifierPrivateIndex()) , cd(emptyConstantIdentifierPrivate()) { } Identifier& Identifier::operator=(const Identifier& rhs) { if(dd == rhs.dd && cd == rhs.cd) return *this; if(!m_index) delete dd; - dd = 0; + dd = nullptr; rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; Q_ASSERT(cd); return *this; } Identifier::Identifier(Identifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantIdentifierPrivate(); rhs.m_index = emptyConstantIdentifierPrivateIndex(); } Identifier& Identifier::operator=(Identifier&& rhs) Q_DECL_NOEXCEPT { if(dd == rhs.dd && cd == rhs.cd) return *this; if (!m_index) { delete dd; - dd = 0; + dd = nullptr; } m_index = rhs.m_index; if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantIdentifierPrivate(); rhs.m_index = emptyConstantIdentifierPrivateIndex(); return *this; } Identifier::~Identifier() { if(!m_index) delete dd; } bool Identifier::nameEquals(const Identifier& rhs) const { return identifier() == rhs.identifier(); } uint Identifier::hash() const { if(!m_index) return dd->hash(); else return cd->hash(); } bool Identifier::isEmpty() const { if(!m_index) return dd->m_identifier.isEmpty() && dd->m_unique == 0 && dd->templateIdentifiersSize() == 0; else return cd->m_identifier.isEmpty() && cd->m_unique == 0 && cd->templateIdentifiersSize() == 0; } Identifier Identifier::unique(int token) { Identifier ret; ret.setUnique(token); return ret; } bool Identifier::isUnique() const { if(!m_index) return dd->m_unique; else return cd->m_unique; } int Identifier::uniqueToken() const { if(!m_index) return dd->m_unique; else return cd->m_unique; } void Identifier::setUnique(int token) { if (token != uniqueToken()) { prepareWrite(); dd->m_unique = token; } } const IndexedString Identifier::identifier() const { if(!m_index) return dd->m_identifier; else return cd->m_identifier; } void Identifier::setIdentifier(const QString& identifier) { IndexedString id(identifier); if (id != this->identifier()) { prepareWrite(); dd->m_identifier = std::move(id); } } void Identifier::setIdentifier(const IndexedString& identifier) { if (identifier != this->identifier()) { prepareWrite(); dd->m_identifier = identifier; } } IndexedTypeIdentifier Identifier::templateIdentifier(int num) const { if(!m_index) return dd->templateIdentifiers()[num]; else return cd->templateIdentifiers()[num]; } uint Identifier::templateIdentifiersCount() const { if(!m_index) return dd->templateIdentifiersSize(); else return cd->templateIdentifiersSize(); } void Identifier::appendTemplateIdentifier(const IndexedTypeIdentifier& identifier) { prepareWrite(); dd->templateIdentifiersList.append(identifier); } void Identifier::clearTemplateIdentifiers() { prepareWrite(); dd->templateIdentifiersList.clear(); } uint Identifier::index() const { makeConstant(); Q_ASSERT(m_index); return m_index; } bool Identifier::inRepository() const { return m_index; } void Identifier::setTemplateIdentifiers(const QList& templateIdentifiers) { prepareWrite(); dd->templateIdentifiersList.clear(); foreach(const IndexedTypeIdentifier& id, templateIdentifiers) dd->templateIdentifiersList.append(id); } QString Identifier::toString() const { QString ret = identifier().str(); if (templateIdentifiersCount()) { ret.append("< "); for (uint i = 0; i < templateIdentifiersCount(); ++i) { ret.append(templateIdentifier(i).toString()); if (i != templateIdentifiersCount() - 1) ret.append(", "); } ret.append(" >"); } return ret; } bool Identifier::operator==(const Identifier& rhs) const { return index() == rhs.index(); } bool Identifier::operator!=(const Identifier& rhs) const { return !operator==(rhs); } uint QualifiedIdentifier::index() const { makeConstant(); Q_ASSERT(m_index); return m_index; } void Identifier::makeConstant() const { if(m_index) return; m_index = identifierRepository()->index( IdentifierItemRequest(*dd) ); delete dd; cd = identifierRepository()->itemFromIndex( m_index ); } void Identifier::prepareWrite() { if(m_index) { const IdentifierPrivate* oldCc = cd; dd = new IdentifierPrivate; dd->m_hash = oldCc->m_hash; dd->m_unique = oldCc->m_unique; dd->m_identifier = oldCc->m_identifier; dd->copyListsFrom(*oldCc); m_index = 0; } dd->clearHash(); } bool QualifiedIdentifier::inRepository() const { if(m_index) return true; else return (bool)qualifiedidentifierRepository()->findIndex( QualifiedIdentifierItemRequest(*dd) ); } QualifiedIdentifier::QualifiedIdentifier(uint index) : m_index(index) , cd( qualifiedidentifierRepository()->itemFromIndex(index) ) { } QualifiedIdentifier::QualifiedIdentifier(const QString& id, bool isExpression) { if (id.isEmpty()) { m_index = emptyConstantQualifiedIdentifierPrivateIndex(); cd = emptyConstantQualifiedIdentifierPrivate(); return; } m_index = 0; dd = new DynamicQualifiedIdentifierPrivate; if(isExpression) { setIsExpression(true); if(!id.isEmpty()) { //Prevent tokenization, since we may lose information there Identifier finishedId; finishedId.setIdentifier(id); push(finishedId); } }else{ if (id.startsWith(QStringLiteral("::"))) { dd->m_explicitlyGlobal = true; dd->splitIdentifiers(id, 2); } else { dd->m_explicitlyGlobal = false; dd->splitIdentifiers(id, 0); } } } QualifiedIdentifier::QualifiedIdentifier(const Identifier& id) { if (id.isEmpty()) { m_index = emptyConstantQualifiedIdentifierPrivateIndex(); cd = emptyConstantQualifiedIdentifierPrivate(); return; } m_index = 0; dd = new DynamicQualifiedIdentifierPrivate; if (id.dd->m_identifier.str().isEmpty()) { dd->m_explicitlyGlobal = true; } else { dd->m_explicitlyGlobal = false; dd->identifiersList.append(IndexedIdentifier(id)); } } QualifiedIdentifier::QualifiedIdentifier() : m_index(emptyConstantQualifiedIdentifierPrivateIndex()) , cd(emptyConstantQualifiedIdentifierPrivate()) { } QualifiedIdentifier::QualifiedIdentifier(const QualifiedIdentifier& id) { if(id.m_index) { m_index = id.m_index; cd = id.cd; }else{ m_index = 0; dd = new QualifiedIdentifierPrivate(*id.dd); } } QualifiedIdentifier::QualifiedIdentifier(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); rhs.cd = emptyConstantQualifiedIdentifierPrivate(); } QualifiedIdentifier& QualifiedIdentifier::operator=(const QualifiedIdentifier& rhs) { if(dd == rhs.dd && cd == rhs.cd) return *this; if(!m_index) delete dd; rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; return *this; } QualifiedIdentifier& QualifiedIdentifier::operator=(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT { if(!m_index) delete dd; m_index = rhs.m_index; if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantQualifiedIdentifierPrivate(); rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); return *this; } QualifiedIdentifier::~QualifiedIdentifier() { if(!m_index) delete dd; } QStringList QualifiedIdentifier::toStringList() const { QStringList ret; ret.reserve(explicitlyGlobal() + count()); if (explicitlyGlobal()) ret.append(QString()); if(m_index) { FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, cd->identifiers) ret << index.identifier().toString(); }else{ FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, dd->identifiers) ret << index.identifier().toString(); } return ret; } QString QualifiedIdentifier::toString(bool ignoreExplicitlyGlobal) const { const QString doubleColon = QStringLiteral("::"); QString ret; if( !ignoreExplicitlyGlobal && explicitlyGlobal() ) ret = doubleColon; bool first = true; if(m_index) { FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, cd->identifiers) { if( !first ) ret += doubleColon; else first = false; ret += index.identifier().toString(); } }else{ FOREACH_FUNCTION_STATIC(const IndexedIdentifier& index, dd->identifiers) { if( !first ) ret += doubleColon; else first = false; ret += index.identifier().toString(); } } return ret; } QualifiedIdentifier QualifiedIdentifier::merge(const QualifiedIdentifier& base) const { QualifiedIdentifier ret(base); ret.push(*this); return ret; } QualifiedIdentifier QualifiedIdentifier::operator+(const QualifiedIdentifier& rhs) const { return rhs.merge(*this); } QualifiedIdentifier& QualifiedIdentifier::operator+=(const QualifiedIdentifier& rhs) { push(rhs); return *this; } QualifiedIdentifier QualifiedIdentifier::operator+(const Identifier& rhs) const { QualifiedIdentifier ret(*this); ret.push(rhs); return ret; } QualifiedIdentifier& QualifiedIdentifier::operator+=(const Identifier& rhs) { push(rhs); return *this; } QualifiedIdentifier QualifiedIdentifier::operator+(const IndexedIdentifier& rhs) const { QualifiedIdentifier ret(*this); ret.push(rhs); return ret; } QualifiedIdentifier& QualifiedIdentifier::operator+=(const IndexedIdentifier& rhs) { push(rhs); return *this; } bool QualifiedIdentifier::isExpression() const { if(m_index) return cd->m_isExpression; else return dd->m_isExpression; } void QualifiedIdentifier::setIsExpression(bool is) { if (is != isExpression()) { prepareWrite(); dd->m_isExpression = is; } } bool QualifiedIdentifier::explicitlyGlobal() const { // True if started with "::" if(m_index) return cd->m_explicitlyGlobal; else return dd->m_explicitlyGlobal; } void QualifiedIdentifier::setExplicitlyGlobal(bool eg) { if (eg != explicitlyGlobal()) { prepareWrite(); dd->m_explicitlyGlobal = eg; } } bool QualifiedIdentifier::sameIdentifiers(const QualifiedIdentifier& rhs) const { if(m_index && rhs.m_index) return cd->listsEqual(*rhs.cd); else if(m_index && !rhs.m_index) return cd->listsEqual(*rhs.dd); else if(!m_index && !rhs.m_index) return dd->listsEqual(*rhs.dd); else return dd->listsEqual(*rhs.cd); } bool QualifiedIdentifier::operator==(const QualifiedIdentifier& rhs) const { if( cd == rhs.cd ) return true; return hash() == rhs.hash() && sameIdentifiers(rhs); } bool QualifiedIdentifier::operator!=(const QualifiedIdentifier& rhs) const { return !operator==(rhs); } bool QualifiedIdentifier::beginsWith(const QualifiedIdentifier& other) const { uint c = count(); uint oc = other.count(); for (uint i = 0; i < c && i < oc; ++i) if (at(i) == other.at(i)) { continue; } else { return false; } return true; } struct Visitor { Visitor(KDevVarLengthArray& target, uint hash) : target(target) , hash(hash) { } bool operator()(const ConstantQualifiedIdentifierPrivate* item, uint index) const { if(item->m_hash == hash) target.append(QualifiedIdentifier(index)); return true; } KDevVarLengthArray& target; const uint hash; }; uint QualifiedIdentifier::hash() const { if(m_index) return cd->hash(); else return dd->hash(); } uint qHash(const IndexedTypeIdentifier& id) { return id.hash(); } uint qHash(const QualifiedIdentifier& id) { return id.hash(); } uint qHash(const Identifier& id) { return id.hash(); } bool QualifiedIdentifier::isQualified() const { return count() > 1 || explicitlyGlobal(); } void QualifiedIdentifier::push(const Identifier& id) { if(id.isEmpty()) return; push(IndexedIdentifier(id)); } void QualifiedIdentifier::push(const IndexedIdentifier& id) { if (id.isEmpty()) { return; } prepareWrite(); dd->identifiersList.append(id); } void QualifiedIdentifier::push(const QualifiedIdentifier& id) { if (id.isEmpty()) { return; } prepareWrite(); if (id.m_index) { dd->identifiersList.append(id.cd->identifiers(), id.cd->identifiersSize()); } else { dd->identifiersList.append(id.dd->identifiers(), id.dd->identifiersSize()); } if (id.explicitlyGlobal()) { setExplicitlyGlobal(true); } } void QualifiedIdentifier::pop() { prepareWrite(); if(!dd->identifiersSize()) return; dd->identifiersList.resize(dd->identifiersList.size()-1); } void QualifiedIdentifier::clear() { prepareWrite(); dd->identifiersList.clear(); dd->m_explicitlyGlobal = false; dd->m_isExpression = false; } bool QualifiedIdentifier::isEmpty() const { if(m_index) return cd->identifiersSize() == 0; else return dd->identifiersSize() == 0; } int QualifiedIdentifier::count() const { if(m_index) return cd->identifiersSize(); else return dd->identifiersSize(); } Identifier QualifiedIdentifier::first() const { return indexedFirst().identifier(); } IndexedIdentifier QualifiedIdentifier::indexedFirst() const { if( (m_index && cd->identifiersSize() == 0) || (!m_index && dd->identifiersSize() == 0) ) return IndexedIdentifier(); else return indexedAt(0); } Identifier QualifiedIdentifier::last() const { return indexedLast().identifier(); } IndexedIdentifier QualifiedIdentifier::indexedLast() const { uint c = count(); if(c) return indexedAt(c-1); else return IndexedIdentifier(); } Identifier QualifiedIdentifier::top() const { return last(); } QualifiedIdentifier QualifiedIdentifier::mid(int pos, int len) const { QualifiedIdentifier ret; if( pos == 0 ) ret.setExplicitlyGlobal(explicitlyGlobal()); int cnt = (int)count(); if( len == -1 ) len = cnt - pos; if( pos+len > cnt ) len -= cnt - (pos+len); for( int a = pos; a < pos+len; a++ ) ret.push(at(a)); return ret; } Identifier QualifiedIdentifier::at(int i) const { return indexedAt(i).identifier(); } IndexedIdentifier QualifiedIdentifier::indexedAt(int i) const { if (m_index) { Q_ASSERT(i >= 0 && i < (int)cd->identifiersSize()); return cd->identifiers()[i]; } else { Q_ASSERT(i >= 0 && i < (int)dd->identifiersSize()); return dd->identifiers()[i]; } } void QualifiedIdentifier::makeConstant() const { if(m_index) return; m_index = qualifiedidentifierRepository()->index( QualifiedIdentifierItemRequest(*dd) ); delete dd; cd = qualifiedidentifierRepository()->itemFromIndex( m_index ); } void QualifiedIdentifier::prepareWrite() { if(m_index) { const QualifiedIdentifierPrivate* oldCc = cd; dd = new QualifiedIdentifierPrivate; dd->m_explicitlyGlobal = oldCc->m_explicitlyGlobal; dd->m_isExpression = oldCc->m_isExpression; dd->m_hash = oldCc->m_hash; dd->copyListsFrom(*oldCc); m_index = 0; } dd->clearHash(); } uint IndexedTypeIdentifier::hash() const { quint32 bitfields = m_isConstant | (m_isReference << 1) | (m_isRValue << 2) | (m_isVolatile << 3) | (m_pointerDepth << 4) | (m_pointerConstMask << 9); return KDevHash() << m_identifier.getIndex() << bitfields; } bool IndexedTypeIdentifier::operator==(const IndexedTypeIdentifier& rhs) const { return m_identifier == rhs.m_identifier && m_isConstant == rhs.m_isConstant && m_isReference == rhs.m_isReference && m_isRValue == rhs.m_isRValue && m_isVolatile == rhs.m_isVolatile && m_pointerConstMask == rhs.m_pointerConstMask && m_pointerDepth == rhs.m_pointerDepth; } bool IndexedTypeIdentifier::operator!=(const IndexedTypeIdentifier& rhs) const { return !operator==(rhs); } bool IndexedTypeIdentifier::isReference() const { return m_isReference; } void IndexedTypeIdentifier::setIsReference(bool isRef) { m_isReference = isRef; } bool IndexedTypeIdentifier::isRValue() const { return m_isRValue; } void IndexedTypeIdentifier::setIsRValue(bool isRVal) { m_isRValue = isRVal; } bool IndexedTypeIdentifier::isConstant() const { return m_isConstant; } void IndexedTypeIdentifier::setIsConstant(bool isConst) { m_isConstant = isConst; } bool IndexedTypeIdentifier::isVolatile() const { return m_isVolatile; } void IndexedTypeIdentifier::setIsVolatile(bool isVolatile) { m_isVolatile = isVolatile; } int IndexedTypeIdentifier::pointerDepth() const { return m_pointerDepth; } void IndexedTypeIdentifier::setPointerDepth(int depth) { Q_ASSERT(depth <= 23 && depth >= 0); ///Clear the mask in removed fields for(int s = depth; s < (int)m_pointerDepth; ++s) setIsConstPointer(s, false); m_pointerDepth = depth; } bool IndexedTypeIdentifier::isConstPointer(int depthNumber) const { return m_pointerConstMask & (1 << depthNumber); } void IndexedTypeIdentifier::setIsConstPointer(int depthNumber, bool constant) { if(constant) m_pointerConstMask |= (1 << depthNumber); else m_pointerConstMask &= (~(1 << depthNumber)); } QString IndexedTypeIdentifier::toString(bool ignoreExplicitlyGlobal) const { QString ret; if(isConstant()) ret += QLatin1String("const "); if(isVolatile()) ret += QLatin1String("volatile "); ret += m_identifier.identifier().toString(ignoreExplicitlyGlobal); for(int a = 0; a < pointerDepth(); ++a) { ret += '*'; if( isConstPointer(a) ) ret += QLatin1String("const"); } if(isRValue()) ret += QLatin1String("&&"); else if(isReference()) ret += '&'; return ret; } IndexedTypeIdentifier::IndexedTypeIdentifier(const IndexedQualifiedIdentifier& identifier) : m_identifier(identifier) , m_isConstant(false) , m_isReference(false) , m_isRValue(false) , m_isVolatile(false) , m_pointerDepth(0) , m_pointerConstMask(0) { } IndexedTypeIdentifier::IndexedTypeIdentifier(const QString& identifier, bool isExpression) : m_identifier(QualifiedIdentifier(identifier, isExpression)) , m_isConstant(false) , m_isReference(false) , m_isRValue(false) , m_isVolatile(false) , m_pointerDepth(0) , m_pointerConstMask(0) { } IndexedIdentifier::IndexedIdentifier() : index(emptyConstantIdentifierPrivateIndex()) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier::IndexedIdentifier(const Identifier& id) : index(id.index()) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier::IndexedIdentifier(const IndexedIdentifier& rhs) : index(rhs.index) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier::IndexedIdentifier(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT : index(rhs.index) { rhs.index = emptyConstantIdentifierPrivateIndex(); } IndexedIdentifier::~IndexedIdentifier() { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedIdentifier& IndexedIdentifier::operator=(const Identifier& id) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } index = id.index(); if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } IndexedIdentifier& IndexedIdentifier::operator=(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else if (shouldDoDUChainReferenceCounting(&rhs)) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(identifierRepository()->dynamicItemFromIndexSimple(rhs.index)->m_refCount, rhs.index); } index = rhs.index; rhs.index = emptyConstantIdentifierPrivateIndex(); if(shouldDoDUChainReferenceCounting(this) && !(shouldDoDUChainReferenceCounting(&rhs))) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "increasing"; ) increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } IndexedIdentifier& IndexedIdentifier::operator=(const IndexedIdentifier& id) { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } index = id.index; if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } bool IndexedIdentifier::operator==(const IndexedIdentifier& rhs) const { return index == rhs.index; } bool IndexedIdentifier::operator!=(const IndexedIdentifier& rhs) const { return index != rhs.index; } bool IndexedIdentifier::operator==(const Identifier& id) const { return index == id.index(); } Identifier IndexedIdentifier::identifier() const { return Identifier(index); } IndexedIdentifier::operator Identifier() const { return Identifier(index); } bool IndexedQualifiedIdentifier::isValid() const { return index != emptyConstantQualifiedIdentifierPrivateIndex(); } bool IndexedQualifiedIdentifier::isEmpty() const { return index == emptyConstantQualifiedIdentifierPrivateIndex(); } int cnt = 0; IndexedQualifiedIdentifier IndexedTypeIdentifier::identifier() const { return m_identifier; } void IndexedTypeIdentifier::setIdentifier(const IndexedQualifiedIdentifier& id) { m_identifier = id; } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier() : index(emptyConstantQualifiedIdentifierPrivateIndex()) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << "increasing"; ) //qCDebug(LANGUAGE) << "(" << ++cnt << ")" << this << identifier().toString() << "inc" << index; QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(const QualifiedIdentifier& id) : index(id.index()) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << "increasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(const IndexedQualifiedIdentifier& id) : index(id.index) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << "increasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT : index(rhs.index) { rhs.index = emptyConstantQualifiedIdentifierPrivateIndex(); } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(const QualifiedIdentifier& id) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); index = id.index(); ifDebug( qCDebug(LANGUAGE) << index << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else { index = id.index(); } return *this; } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(const IndexedQualifiedIdentifier& rhs) { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); index = rhs.index; ifDebug( qCDebug(LANGUAGE) << index << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else { index = rhs.index; } return *this; } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT { if(shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } else if (shouldDoDUChainReferenceCounting(&rhs)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(rhs.index)->m_refCount, rhs.index); } index = rhs.index; rhs.index = emptyConstantQualifiedIdentifierPrivateIndex(); if(shouldDoDUChainReferenceCounting(this) && !(shouldDoDUChainReferenceCounting(&rhs))) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug( qCDebug(LANGUAGE) << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } return *this; } IndexedQualifiedIdentifier::~IndexedQualifiedIdentifier() { ifDebug( qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if(shouldDoDUChainReferenceCounting(this)) { ifDebug( qCDebug(LANGUAGE) << index << "decreasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(index)->m_refCount, index); } } bool IndexedQualifiedIdentifier::operator==(const IndexedQualifiedIdentifier& rhs) const { return index == rhs.index; } bool IndexedQualifiedIdentifier::operator==(const QualifiedIdentifier& id) const { return index == id.index(); } QualifiedIdentifier IndexedQualifiedIdentifier::identifier() const { return QualifiedIdentifier(index); } IndexedQualifiedIdentifier::operator QualifiedIdentifier() const { return QualifiedIdentifier(index); } void initIdentifierRepository() { emptyConstantIdentifierPrivateIndex(); emptyConstantIdentifierPrivate(); emptyConstantQualifiedIdentifierPrivateIndex(); emptyConstantQualifiedIdentifierPrivate(); } } QDebug operator<<(QDebug s, const KDevelop::Identifier& identifier) { s.nospace() << identifier.toString(); return s.space(); } QDebug operator<<(QDebug s, const KDevelop::QualifiedIdentifier& identifier) { s.nospace() << identifier.toString(); return s.space(); } diff --git a/language/duchain/indexeddeclaration.cpp b/language/duchain/indexeddeclaration.cpp index 2a110f6fc9..a07f071015 100644 --- a/language/duchain/indexeddeclaration.cpp +++ b/language/duchain/indexeddeclaration.cpp @@ -1,57 +1,57 @@ /* This file is part of KDevelop Copyright 2007 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 "indexeddeclaration.h" #include "declaration.h" #include "duchain.h" #include "topducontextdynamicdata.h" using namespace KDevelop; IndexedDeclaration::IndexedDeclaration(uint topContext, uint declarationIndex) : m_topContext(topContext) , m_declarationIndex(declarationIndex) { } IndexedDeclaration::IndexedDeclaration(const Declaration* decl) { if(decl) { m_topContext = decl->topContext()->ownIndex(); m_declarationIndex = decl->m_indexInTopContext; }else{ m_topContext = 0; m_declarationIndex = 0; } } Declaration* IndexedDeclaration::declaration() const { if(isDummy()) - return 0; + return nullptr; // ENSURE_CHAIN_READ_LOCKED if(!m_topContext || !m_declarationIndex) - return 0; + return nullptr; TopDUContext* ctx = DUChain::self()->chainForIndex(m_topContext); if(!ctx) - return 0; + return nullptr; return ctx->m_dynamicData->getDeclarationForIndex(m_declarationIndex); } diff --git a/language/duchain/indexedducontext.cpp b/language/duchain/indexedducontext.cpp index f9b05520f2..a1581c35cb 100644 --- a/language/duchain/indexedducontext.cpp +++ b/language/duchain/indexedducontext.cpp @@ -1,72 +1,72 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 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 "indexedducontext.h" #include "ducontext.h" #include "ducontextdata.h" #include "ducontextdynamicdata.h" #include "topducontext.h" #include "duchain.h" #include "topducontextdynamicdata.h" using namespace KDevelop; IndexedDUContext::IndexedDUContext(uint topContext, uint contextIndex) : m_topContext(topContext) , m_contextIndex(contextIndex) { } IndexedDUContext::IndexedDUContext(DUContext* ctx) { if(ctx) { m_topContext = ctx->topContext()->ownIndex(); m_contextIndex = ctx->m_dynamicData->m_indexInTopContext; }else{ m_topContext = 0; m_contextIndex = 0; } } IndexedTopDUContext IndexedDUContext::indexedTopContext() const { if(isDummy()) { return IndexedTopDUContext(); } return IndexedTopDUContext(m_topContext); } DUContext* IndexedDUContext::context() const { if(isDummy()) - return 0; + return nullptr; // ENSURE_CHAIN_READ_LOCKED if(!m_topContext) - return 0; + return nullptr; TopDUContext* ctx = DUChain::self()->chainForIndex(m_topContext); if(!ctx) - return 0; + return nullptr; if(!m_contextIndex) return ctx; return ctx->m_dynamicData->getContextForIndex(m_contextIndex); } diff --git a/language/duchain/indexedtopducontext.cpp b/language/duchain/indexedtopducontext.cpp index 1a59a7ba72..cddff5b0dd 100644 --- a/language/duchain/indexedtopducontext.cpp +++ b/language/duchain/indexedtopducontext.cpp @@ -1,52 +1,52 @@ /* This file is part of KDevelop Copyright 2007 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 "indexedtopducontext.h" #include "duchain.h" using namespace KDevelop; IndexedTopDUContext::IndexedTopDUContext(TopDUContext* context) { if(context) m_index = context->ownIndex(); else m_index = DummyMask; } bool IndexedTopDUContext::isLoaded() const { if(index()) return DUChain::self()->isInMemory(index()); else return false; } IndexedString IndexedTopDUContext::url() const { if(index()) return DUChain::self()->urlForIndex(index()); else return IndexedString(); } TopDUContext* IndexedTopDUContext::data() const { // ENSURE_CHAIN_READ_LOCKED if(index()) return DUChain::self()->chainForIndex(index()); else - return 0; + return nullptr; } diff --git a/language/duchain/localindexeddeclaration.cpp b/language/duchain/localindexeddeclaration.cpp index 0b07e0b185..7cbd30924b 100644 --- a/language/duchain/localindexeddeclaration.cpp +++ b/language/duchain/localindexeddeclaration.cpp @@ -1,52 +1,52 @@ /* This file is part of KDevelop Copyright 2007 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 "localindexeddeclaration.h" #include "declaration.h" #include "topducontextdynamicdata.h" #include "topducontext.h" using namespace KDevelop; LocalIndexedDeclaration::LocalIndexedDeclaration(Declaration* decl) : m_declarationIndex(decl ? decl->m_indexInTopContext : 0) { } LocalIndexedDeclaration::LocalIndexedDeclaration(uint declarationIndex) : m_declarationIndex(declarationIndex) { } Declaration* LocalIndexedDeclaration::data(TopDUContext* top) const { if(!m_declarationIndex) - return 0; + return nullptr; Q_ASSERT(top); return top->m_dynamicData->getDeclarationForIndex(m_declarationIndex); } bool LocalIndexedDeclaration::isLoaded(TopDUContext* top) const { if(!m_declarationIndex) return false; Q_ASSERT(top); return top->m_dynamicData->isDeclarationForIndexLoaded(m_declarationIndex); } diff --git a/language/duchain/localindexedducontext.cpp b/language/duchain/localindexedducontext.cpp index d151db1ab4..75b8e8e22e 100644 --- a/language/duchain/localindexedducontext.cpp +++ b/language/duchain/localindexedducontext.cpp @@ -1,58 +1,58 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 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 "localindexedducontext.h" #include "ducontextdata.h" #include "ducontext.h" #include "topducontextdynamicdata.h" #include "ducontextdynamicdata.h" #include "topducontext.h" using namespace KDevelop; LocalIndexedDUContext::LocalIndexedDUContext(uint contextIndex) : m_contextIndex(contextIndex) { } LocalIndexedDUContext::LocalIndexedDUContext(DUContext* ctx) { if(ctx) { m_contextIndex = ctx->m_dynamicData->m_indexInTopContext; }else{ m_contextIndex = 0; } } bool LocalIndexedDUContext::isLoaded(TopDUContext* top) const { if(!m_contextIndex) return false; else return top->m_dynamicData->isContextForIndexLoaded(m_contextIndex); } DUContext* LocalIndexedDUContext::data(TopDUContext* top) const { if(!m_contextIndex) - return 0; + return nullptr; else return top->m_dynamicData->getContextForIndex(m_contextIndex); } diff --git a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp index e54eef1955..3a3ace5e3c 100644 --- a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp +++ b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp @@ -1,769 +1,769 @@ /* Copyright 2007 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 "abstractdeclarationnavigationcontext.h" #include #include #include "../functiondeclaration.h" #include "../functiondefinition.h" #include "../classfunctiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../forwarddeclaration.h" #include "../types/enumeratortype.h" #include "../types/enumerationtype.h" #include "../types/functiontype.h" #include "../duchainutils.h" #include "../types/pointertype.h" #include "../types/referencetype.h" #include "../types/typeutils.h" #include "../types/typesystem.h" #include "../persistentsymboltable.h" #include "util/debug.h" #include #include #include #include #include namespace KDevelop { AbstractDeclarationNavigationContext::AbstractDeclarationNavigationContext( DeclarationPointer decl, TopDUContextPointer topContext, AbstractNavigationContext* previousContext) - : AbstractNavigationContext((topContext ? topContext : TopDUContextPointer(decl ? decl->topContext() : 0)), previousContext), m_declaration(decl), m_fullBackwardSearch(false) + : AbstractNavigationContext((topContext ? topContext : TopDUContextPointer(decl ? decl->topContext() : nullptr)), previousContext), m_declaration(decl), m_fullBackwardSearch(false) { //Jump from definition to declaration if possible FunctionDefinition* definition = dynamic_cast(m_declaration.data()); if(definition && definition->declaration()) m_declaration = DeclarationPointer(definition->declaration()); } QString AbstractDeclarationNavigationContext::name() const { if(m_declaration.data()) return prettyQualifiedIdentifier(m_declaration).toString(); else return declarationName(m_declaration); } QString AbstractDeclarationNavigationContext::html(bool shorten) { DUChainReadLocker lock(DUChain::lock(), 300); if ( !lock.locked() ) { return {}; } clear(); m_shorten = shorten; modifyHtml() += "

" + fontSizePrefix(shorten); addExternalHtml(m_prefix); if(!m_declaration.data()) { modifyHtml() += i18n("
lost declaration
"); return currentHtml(); } if( m_previousContext ) { QString link = createLink( m_previousContext->name(), m_previousContext->name(), NavigationAction(m_previousContext) ); modifyHtml() += navigationHighlight(i18n("Back to %1
", link)); } QExplicitlySharedDataPointer doc; if( !shorten ) { doc = ICore::self()->documentationController()->documentationForDeclaration(m_declaration.data()); const AbstractFunctionDeclaration* function = dynamic_cast(m_declaration.data()); if( function ) { htmlFunction(); } else if( m_declaration->isTypeAlias() || m_declaration->type() || m_declaration->kind() == Declaration::Instance ) { if( m_declaration->isTypeAlias() ) modifyHtml() += importantHighlight(QStringLiteral("typedef ")); if(m_declaration->type()) modifyHtml() += i18n("enumerator "); AbstractType::Ptr useType = m_declaration->abstractType(); if(m_declaration->isTypeAlias()) { //Do not show the own name as type of typedefs if(useType.cast()) useType = useType.cast()->type(); } eventuallyMakeTypeLinks( useType ); modifyHtml() += ' ' + identifierHighlight(declarationName(m_declaration).toHtmlEscaped(), m_declaration); if(auto integralType = m_declaration->type()) { const QString plainValue = integralType->valueAsString(); if (!plainValue.isEmpty()) { modifyHtml() += QStringLiteral(" = ") + plainValue; } } modifyHtml() += QStringLiteral("
"); }else{ if( m_declaration->kind() == Declaration::Type && m_declaration->abstractType().cast() ) { htmlClass(); } if ( m_declaration->kind() == Declaration::Namespace ) { modifyHtml() += i18n("namespace %1 ", identifierHighlight(m_declaration->qualifiedIdentifier().toString().toHtmlEscaped(), m_declaration)); } else if ( m_declaration->kind() == Declaration::NamespaceAlias ) { modifyHtml() += identifierHighlight(declarationName(m_declaration).toHtmlEscaped(), m_declaration); } if(m_declaration->type()) { EnumerationType::Ptr enumeration = m_declaration->type(); modifyHtml() += i18n("enumeration %1 ", identifierHighlight(m_declaration->identifier().toString().toHtmlEscaped(), m_declaration)); } if(m_declaration->isForwardDeclaration()) { ForwardDeclaration* forwardDec = static_cast(m_declaration.data()); Declaration* resolved = forwardDec->resolve(m_topContext.data()); if(resolved) { modifyHtml() += i18n("(resolved forward-declaration: "); makeLink(resolved->identifier().toString(), DeclarationPointer(resolved), NavigationAction::NavigateDeclaration ); modifyHtml() += i18n(") "); }else{ modifyHtml() += i18n("(unresolved forward-declaration) "); QualifiedIdentifier id = forwardDec->qualifiedIdentifier(); const auto& forwardDecFile = forwardDec->topContext()->parsingEnvironmentFile(); uint count; const IndexedDeclaration* decls; PersistentSymbolTable::self().declarations(id, count, decls); for(uint a = 0; a < count; ++a) { auto dec = decls[a].data(); if (!dec || dec->isForwardDeclaration()) { continue; } const auto& decFile = forwardDec->topContext()->parsingEnvironmentFile(); if ((static_cast(decFile) != static_cast(forwardDecFile)) || (decFile && forwardDecFile && decFile->language() != forwardDecFile->language())) { // the language of the declarations must match continue; } modifyHtml() += QStringLiteral("
"); makeLink(i18n("possible resolution from"), DeclarationPointer(dec), NavigationAction::NavigateDeclaration); modifyHtml() += ' ' + dec->url().str(); } } } modifyHtml() += QStringLiteral("
"); } }else{ AbstractType::Ptr showType = m_declaration->abstractType(); if(showType && showType.cast()) { showType = showType.cast()->returnType(); if(showType) modifyHtml() += labelHighlight(i18n("Returns: ")); }else if(showType) { modifyHtml() += labelHighlight(i18n("Type: ")); } if(showType) { eventuallyMakeTypeLinks(showType); modifyHtml() += QStringLiteral(" "); } } QualifiedIdentifier identifier = m_declaration->qualifiedIdentifier(); if( identifier.count() > 1 ) { if( m_declaration->context() && m_declaration->context()->owner() ) { Declaration* decl = m_declaration->context()->owner(); FunctionDefinition* definition = dynamic_cast(decl); if(definition && definition->declaration()) decl = definition->declaration(); if(decl->abstractType().cast()) modifyHtml() += labelHighlight(i18n("Enum: ")); else modifyHtml() += labelHighlight(i18n("Container: ")); makeLink( declarationName(DeclarationPointer(decl)), DeclarationPointer(decl), NavigationAction::NavigateDeclaration ); modifyHtml() += QStringLiteral(" "); } else { QualifiedIdentifier parent = identifier; parent.pop(); modifyHtml() += labelHighlight(i18n("Scope: %1 ", typeHighlight(parent.toString().toHtmlEscaped()))); } } if( shorten && !m_declaration->comment().isEmpty() ) { QString comment = QString::fromUtf8(m_declaration->comment()); if( comment.length() > 60 ) { comment.truncate(60); comment += QLatin1String("..."); } comment.replace('\n', QLatin1String(" ")); comment.replace(QLatin1String("
"), QLatin1String(" ")); comment.replace(QLatin1String("
"), QLatin1String(" ")); modifyHtml() += commentHighlight(comment.toHtmlEscaped()) + " "; } QString access = stringFromAccess(m_declaration); if( !access.isEmpty() ) modifyHtml() += labelHighlight(i18n("Access: %1 ", propertyHighlight(access.toHtmlEscaped()))); ///@todo Enumerations QString detailsHtml; QStringList details = declarationDetails(m_declaration); if( !details.isEmpty() ) { bool first = true; foreach( const QString &str, details ) { if( !first ) detailsHtml += QLatin1String(", "); first = false; detailsHtml += propertyHighlight(str); } } QString kind = declarationKind(m_declaration); if( !kind.isEmpty() ) { if( !detailsHtml.isEmpty() ) modifyHtml() += labelHighlight(i18n("Kind: %1 %2 ", importantHighlight(kind.toHtmlEscaped()), detailsHtml)); else modifyHtml() += labelHighlight(i18n("Kind: %1 ", importantHighlight(kind.toHtmlEscaped()))); } if (m_declaration->isDeprecated()) { modifyHtml() += labelHighlight(i18n("Status: %1 ", propertyHighlight(i18n("Deprecated")))); } modifyHtml() += QStringLiteral("
"); if(!shorten) htmlAdditionalNavigation(); if( !shorten ) { if(dynamic_cast(m_declaration.data())) modifyHtml() += labelHighlight(i18n( "Def.: " )); else modifyHtml() += labelHighlight(i18n( "Decl.: " )); makeLink( QStringLiteral("%1 :%2").arg( m_declaration->url().toUrl().fileName() ).arg( m_declaration->rangeInCurrentRevision().start().line()+1 ), m_declaration, NavigationAction::JumpToSource ); modifyHtml() += QStringLiteral(" "); //modifyHtml() += "
"; if(!dynamic_cast(m_declaration.data())) { if( FunctionDefinition* definition = FunctionDefinition::definition(m_declaration.data()) ) { modifyHtml() += labelHighlight(i18n( " Def.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->url().toUrl().fileName() ).arg( definition->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition), NavigationAction::JumpToSource ); } } if( FunctionDefinition* definition = dynamic_cast(m_declaration.data()) ) { if(definition->declaration()) { modifyHtml() += labelHighlight(i18n( " Decl.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->declaration()->url().toUrl().fileName() ).arg( definition->declaration()->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition->declaration()), NavigationAction::JumpToSource ); } } modifyHtml() += QStringLiteral(" "); //The action name _must_ stay "show_uses", since that is also used from outside makeLink(i18n("Show uses"), QStringLiteral("show_uses"), NavigationAction(m_declaration, NavigationAction::NavigateUses)); } QByteArray declarationComment = m_declaration->comment(); if( !shorten && (!declarationComment.isEmpty() || doc) ) { modifyHtml() += QStringLiteral("

"); if(doc) { QString comment = doc->description(); connect(doc.data(), &IDocumentation::descriptionChanged, this, &AbstractDeclarationNavigationContext::contentsChanged); if(!comment.isEmpty()) { modifyHtml() += "

" + commentHighlight(comment) + "

"; } } QString comment = QString::fromUtf8(declarationComment); if(!comment.isEmpty()) { // if the first paragraph does not contain a tag, we assume that this is a plain-text comment if (!Qt::mightBeRichText(comment)) { // still might contain extra html tags for line breaks (this is the case for doxygen-style comments sometimes) // let's protect them from being removed completely comment.replace(QRegExp("
"), QStringLiteral("\n")); comment = comment.toHtmlEscaped(); comment.replace('\n', QLatin1String("
")); //Replicate newlines in html } modifyHtml() += commentHighlight(comment); modifyHtml() += QStringLiteral("

"); } } if(!shorten && doc) { modifyHtml() += "

" + i18n("Show documentation for "); makeLink(prettyQualifiedName(m_declaration), m_declaration, NavigationAction::ShowDocumentation); modifyHtml() += "

"; } //modifyHtml() += "
"; addExternalHtml(m_suffix); modifyHtml() += fontSizeSuffix(shorten) + "

"; return currentHtml(); } AbstractType::Ptr AbstractDeclarationNavigationContext::typeToShow(AbstractType::Ptr type) { return type; } void AbstractDeclarationNavigationContext::htmlFunction() { const AbstractFunctionDeclaration* function = dynamic_cast(m_declaration.data()); Q_ASSERT(function); const ClassFunctionDeclaration* classFunDecl = dynamic_cast(m_declaration.data()); const FunctionType::Ptr type = m_declaration->abstractType().cast(); if( !type ) { modifyHtml() += errorHighlight(QStringLiteral("Invalid type
")); return; } if( !classFunDecl || (!classFunDecl->isConstructor() && !classFunDecl->isDestructor()) ) { // only print return type for global functions and non-ctor/dtor methods eventuallyMakeTypeLinks( type->returnType() ); } modifyHtml() += ' ' + identifierHighlight(prettyIdentifier(m_declaration).toString().toHtmlEscaped(), m_declaration); if( type->indexedArgumentsSize() == 0 ) { modifyHtml() += QStringLiteral("()"); } else { modifyHtml() += QStringLiteral("( "); bool first = true; int firstDefaultParam = type->indexedArgumentsSize() - function->defaultParametersSize(); int currentArgNum = 0; QVector decls; if (DUContext* argumentContext = DUChainUtils::getArgumentContext(m_declaration.data())) { decls = argumentContext->localDeclarations(m_topContext.data()); } foreach(const AbstractType::Ptr& argType, type->arguments()) { if( !first ) modifyHtml() += QStringLiteral(", "); first = false; eventuallyMakeTypeLinks( argType ); if (currentArgNum < decls.size()) { modifyHtml() += ' ' + identifierHighlight(decls[currentArgNum]->identifier().toString().toHtmlEscaped(), m_declaration); } if( currentArgNum >= firstDefaultParam ) modifyHtml() += " = " + function->defaultParameters()[ currentArgNum - firstDefaultParam ].str().toHtmlEscaped(); ++currentArgNum; } modifyHtml() += QStringLiteral(" )"); } modifyHtml() += QStringLiteral("
"); } Identifier AbstractDeclarationNavigationContext::prettyIdentifier(DeclarationPointer decl) const { Identifier ret; QualifiedIdentifier q = prettyQualifiedIdentifier(decl); if(!q.isEmpty()) ret = q.last(); return ret; } QualifiedIdentifier AbstractDeclarationNavigationContext::prettyQualifiedIdentifier(DeclarationPointer decl) const { if(decl) return decl->qualifiedIdentifier(); else return QualifiedIdentifier(); } QString AbstractDeclarationNavigationContext::prettyQualifiedName(DeclarationPointer decl) const { const auto qid = prettyQualifiedIdentifier(decl); if (qid.isEmpty()) { return i18nc("An anonymous declaration (class, function, etc.)", ""); } return qid.toString(); } void AbstractDeclarationNavigationContext::htmlAdditionalNavigation() { ///Check if the function overrides or hides another one const ClassFunctionDeclaration* classFunDecl = dynamic_cast(m_declaration.data()); if(classFunDecl) { Declaration* overridden = DUChainUtils::getOverridden(m_declaration.data()); if(overridden) { modifyHtml() += i18n("Overrides a "); makeLink(i18n("function"), QStringLiteral("jump_to_overridden"), NavigationAction(DeclarationPointer(overridden), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(overridden->context()->owner())), QStringLiteral("jump_to_overridden_container"), NavigationAction(DeclarationPointer(overridden->context()->owner()), NavigationAction::NavigateDeclaration)); modifyHtml() += QStringLiteral("
"); }else{ //Check if this declarations hides other declarations QList decls; foreach(const DUContext::Import &import, m_declaration->context()->importedParentContexts()) if(import.context(m_topContext.data())) decls += import.context(m_topContext.data())->findDeclarations(QualifiedIdentifier(m_declaration->identifier()), CursorInRevision::invalid(), AbstractType::Ptr(), m_topContext.data(), DUContext::DontSearchInParent); uint num = 0; foreach(Declaration* decl, decls) { modifyHtml() += i18n("Hides a "); makeLink(i18n("function"), QStringLiteral("jump_to_hide_%1").arg(num), NavigationAction(DeclarationPointer(decl), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(decl->context()->owner())), QStringLiteral("jump_to_hide_container_%1").arg(num), NavigationAction(DeclarationPointer(decl->context()->owner()), NavigationAction::NavigateDeclaration)); modifyHtml() += QStringLiteral("
"); ++num; } } ///Show all places where this function is overridden if(classFunDecl->isVirtual()) { Declaration* classDecl = m_declaration->context()->owner(); if(classDecl) { uint maxAllowedSteps = m_fullBackwardSearch ? (uint)-1 : 10; QList overriders = DUChainUtils::getOverriders(classDecl, classFunDecl, maxAllowedSteps); if(!overriders.isEmpty()) { modifyHtml() += i18n("Overridden in "); bool first = true; foreach(Declaration* overrider, overriders) { if(!first) modifyHtml() += QStringLiteral(", "); first = false; const auto owner = DeclarationPointer(overrider->context()->owner()); const QString name = prettyQualifiedName(owner); makeLink(name, name, NavigationAction(DeclarationPointer(overrider), NavigationAction::NavigateDeclaration)); } modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(overriders.isEmpty() ? i18n("Overriders possible, show all") : i18n("More overriders possible, show all")); } } } ///Show all classes that inherit this one uint maxAllowedSteps = m_fullBackwardSearch ? (uint)-1 : 10; QList inheriters = DUChainUtils::getInheriters(m_declaration.data(), maxAllowedSteps); if(!inheriters.isEmpty()) { modifyHtml() += i18n("Inherited by "); bool first = true; foreach(Declaration* importer, inheriters) { if(!first) modifyHtml() += QStringLiteral(", "); first = false; const QString importerName = prettyQualifiedName(DeclarationPointer(importer)); makeLink(importerName, importerName, NavigationAction(DeclarationPointer(importer), NavigationAction::NavigateDeclaration)); } modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(inheriters.isEmpty() ? i18n("Inheriters possible, show all") : i18n("More inheriters possible, show all")); } void AbstractDeclarationNavigationContext::createFullBackwardSearchLink(QString string) { makeLink(string, QStringLiteral("m_fullBackwardSearch=true"), NavigationAction(QStringLiteral("m_fullBackwardSearch=true"))); modifyHtml() += QStringLiteral("
"); } NavigationContextPointer AbstractDeclarationNavigationContext::executeKeyAction( QString key ) { if(key == QLatin1String("m_fullBackwardSearch=true")) { m_fullBackwardSearch = true; clear(); } return NavigationContextPointer(this); } void AbstractDeclarationNavigationContext::htmlClass() { StructureType::Ptr klass = m_declaration->abstractType().cast(); Q_ASSERT(klass); ClassDeclaration* classDecl = dynamic_cast(klass->declaration(m_topContext.data())); if(classDecl) { switch ( classDecl->classType() ) { case ClassDeclarationData::Class: modifyHtml() += QStringLiteral("class "); break; case ClassDeclarationData::Struct: modifyHtml() += QStringLiteral("struct "); break; case ClassDeclarationData::Union: modifyHtml() += QStringLiteral("union "); break; case ClassDeclarationData::Interface: modifyHtml() += QStringLiteral("interface "); break; case ClassDeclarationData::Trait: modifyHtml() += QStringLiteral("trait "); break; default: modifyHtml() += QStringLiteral(" "); break; } eventuallyMakeTypeLinks( klass.cast() ); FOREACH_FUNCTION( const BaseClassInstance& base, classDecl->baseClasses ) { modifyHtml() += ", " + stringFromAccess(base.access) + " " + (base.virtualInheritance ? QStringLiteral("virtual") : QString()) + " "; eventuallyMakeTypeLinks(base.baseClass.abstractType()); } } else { /// @todo How can we get here? and should this really be a class? modifyHtml() += QStringLiteral("class "); eventuallyMakeTypeLinks( klass.cast() ); } modifyHtml() += QStringLiteral(" "); } void AbstractDeclarationNavigationContext::htmlIdentifiedType(AbstractType::Ptr type, const IdentifiedType* idType) { Q_ASSERT(type); Q_ASSERT(idType); if( Declaration* decl = idType->declaration(m_topContext.data()) ) { //Remove the last template-identifiers, because we create those directly QualifiedIdentifier id = prettyQualifiedIdentifier(DeclarationPointer(decl)); Identifier lastId = id.last(); id.pop(); lastId.clearTemplateIdentifiers(); id.push(lastId); if(decl->context() && decl->context()->owner()) { //Also create full type-links for the context around AbstractType::Ptr contextType = decl->context()->owner()->abstractType(); IdentifiedType* contextIdType = dynamic_cast(contextType.data()); if(contextIdType && !contextIdType->equals(idType)) { //Create full type information for the context if(!id.isEmpty()) id = id.mid(id.count()-1); htmlIdentifiedType(contextType, contextIdType); modifyHtml() += QStringLiteral("::").toHtmlEscaped(); } } //We leave out the * and & reference and pointer signs, those are added to the end makeLink(id.toString() , DeclarationPointer(idType->declaration(m_topContext.data())), NavigationAction::NavigateDeclaration ); } else { qCDebug(LANGUAGE) << "could not resolve declaration:" << idType->declarationId().isDirect() << idType->qualifiedIdentifier().toString() << "in top-context" << m_topContext->url().str(); modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } void AbstractDeclarationNavigationContext::eventuallyMakeTypeLinks( AbstractType::Ptr type ) { type = typeToShow(type); if( !type ) { modifyHtml() += typeHighlight(QStringLiteral("").toHtmlEscaped()); return; } AbstractType::Ptr target = TypeUtils::targetTypeKeepAliases( type, m_topContext.data() ); const IdentifiedType* idType = dynamic_cast( target.data() ); qCDebug(LANGUAGE) << "making type-links for" << type->toString(); if( idType && idType->declaration(m_topContext.data()) ) { ///@todo This is C++ specific, move into subclass if(target->modifiers() & AbstractType::ConstModifier) modifyHtml() += typeHighlight(QStringLiteral("const ")); htmlIdentifiedType(target, idType); //We need to exchange the target type, else template-parameters may confuse this SimpleTypeExchanger exchangeTarget(target, AbstractType::Ptr()); AbstractType::Ptr exchanged = exchangeTarget.exchange(type); if(exchanged) { QString typeSuffixString = exchanged->toString(); QRegExp suffixExp("\\&|\\*"); int suffixPos = typeSuffixString.indexOf(suffixExp); if(suffixPos != -1) modifyHtml() += typeHighlight(typeSuffixString.mid(suffixPos)); } } else { if(idType) { qCDebug(LANGUAGE) << "identified type could not be resolved:" << idType->qualifiedIdentifier() << idType->declarationId().isValid() << idType->declarationId().isDirect(); } modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } DeclarationPointer AbstractDeclarationNavigationContext::declaration() const { return m_declaration; } QString AbstractDeclarationNavigationContext::identifierHighlight(const QString& identifier, const DeclarationPointer& decl) const { QString ret = nameHighlight(identifier); if (!decl) { return ret; } if (decl->isDeprecated()) { ret = QStringLiteral("") + ret + QStringLiteral(""); } return ret; } QString AbstractDeclarationNavigationContext::stringFromAccess(Declaration::AccessPolicy access) { switch(access) { case Declaration::Private: return QStringLiteral("private"); case Declaration::Protected: return QStringLiteral("protected"); case Declaration::Public: return QStringLiteral("public"); default: break; } return QString(); } QString AbstractDeclarationNavigationContext::stringFromAccess(DeclarationPointer decl) { const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { return stringFromAccess(memberDecl->accessPolicy()); } return QString(); } QString AbstractDeclarationNavigationContext::declarationName( DeclarationPointer decl ) const { if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) return "using namespace " + alias->importIdentifier().toString(); else return "namespace " + alias->identifier().toString() + " = " + alias->importIdentifier().toString(); } if( !decl ) return i18nc("A declaration that is unknown", "Unknown"); else return prettyIdentifier(decl).toString(); } QStringList AbstractDeclarationNavigationContext::declarationDetails(DeclarationPointer decl) { QStringList details; const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { if( memberDecl->isMutable() ) details << QStringLiteral("mutable"); if( memberDecl->isRegister() ) details << QStringLiteral("register"); if( memberDecl->isStatic() ) details << QStringLiteral("static"); if( memberDecl->isAuto() ) details << QStringLiteral("auto"); if( memberDecl->isExtern() ) details << QStringLiteral("extern"); if( memberDecl->isFriend() ) details << QStringLiteral("friend"); } if( decl->isDefinition() ) details << i18nc("tells if a declaration is defining the variable's value", "definition"); if( decl->isExplicitlyDeleted() ) details << QStringLiteral("deleted"); if( memberDecl && memberDecl->isForwardDeclaration() ) details << i18nc("as in c++ forward declaration", "forward"); AbstractType::Ptr t(decl->abstractType()); if( t ) { if( t->modifiers() & AbstractType::ConstModifier ) details << i18nc("a variable that won't change, const", "constant"); if( t->modifiers() & AbstractType::VolatileModifier ) details << QStringLiteral("volatile"); } if( function ) { if( function->isInline() ) details << QStringLiteral("inline"); if( function->isExplicit() ) details << QStringLiteral("explicit"); if( function->isVirtual() ) details << QStringLiteral("virtual"); const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl.data()); if( classFunDecl ) { if( classFunDecl->isSignal() ) details << QStringLiteral("signal"); if( classFunDecl->isSlot() ) details << QStringLiteral("slot"); if( classFunDecl->isConstructor() ) details << QStringLiteral("constructor"); if( classFunDecl->isDestructor() ) details << QStringLiteral("destructor"); if( classFunDecl->isConversionFunction() ) details << QStringLiteral("conversion-function"); if( classFunDecl->isAbstract() ) details << QStringLiteral("abstract"); } } return details; } } diff --git a/language/duchain/navigation/abstractincludenavigationcontext.cpp b/language/duchain/navigation/abstractincludenavigationcontext.cpp index 0ef646a269..ef5791f5d1 100644 --- a/language/duchain/navigation/abstractincludenavigationcontext.cpp +++ b/language/duchain/navigation/abstractincludenavigationcontext.cpp @@ -1,176 +1,176 @@ /* Copyright 2007 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 "abstractincludenavigationcontext.h" #include #include #include #include #include namespace KDevelop { AbstractIncludeNavigationContext::AbstractIncludeNavigationContext(const IncludeItem& item, TopDUContextPointer topContext, const ParsingEnvironmentType& type) : AbstractNavigationContext(topContext), m_type(type), m_item(item) {} TopDUContext* pickContextWithData(QList duchains, uint maxDepth, const ParsingEnvironmentType& type, bool forcePick = true) { - TopDUContext* duchain = 0; + TopDUContext* duchain = nullptr; foreach(TopDUContext* ctx, duchains) { if(!ctx->parsingEnvironmentFile() || ctx->parsingEnvironmentFile()->type() != type) continue; if(ctx->childContexts().count() != 0 - && (duchain == 0 || ctx->childContexts().count() > duchain->childContexts().count())) { + && (duchain == nullptr || ctx->childContexts().count() > duchain->childContexts().count())) { duchain = ctx; } if(ctx->localDeclarations().count() != 0 - && (duchain == 0 || ctx->localDeclarations().count() > duchain->localDeclarations().count())) { + && (duchain == nullptr || ctx->localDeclarations().count() > duchain->localDeclarations().count())) { duchain = ctx; } } if(!duchain && maxDepth != 0) { if(maxDepth != 0) { foreach(TopDUContext* ctx, duchains) { QList children; foreach(const DUContext::Import &import, ctx->importedParentContexts()) - if(import.context(0)) - children << import.context(0)->topContext(); + if(import.context(nullptr)) + children << import.context(nullptr)->topContext(); duchain = pickContextWithData(children, maxDepth-1, type, false); if(duchain) break; } } } if(!duchain && !duchains.isEmpty() && forcePick) duchain = duchains.first(); return duchain; } QString AbstractIncludeNavigationContext::html(bool shorten) { clear(); modifyHtml() += "

" + fontSizePrefix(shorten); addExternalHtml(m_prefix); QUrl u = m_item.url(); NavigationAction action(u, KTextEditor::Cursor(0,0)); makeLink(u.toDisplayString(QUrl::PreferLocalFile), u.toString(), action); modifyHtml() += QStringLiteral("
"); QList duchains = DUChain::self()->chainsForDocument(u); //Pick the one duchain for this document that has the most child-contexts/declarations. //This prevents picking a context that is empty due to header-guards. TopDUContext* duchain = pickContextWithData(duchains, 2, m_type); if(duchain) { getFileInfo(duchain); if(!shorten) { modifyHtml() += labelHighlight(i18n("Declarations:")) + "
"; bool first = true; QList decs; addDeclarationsFromContext(duchain, first, decs); } }else if(duchains.isEmpty()) { modifyHtml() += i18n("not parsed yet"); } addExternalHtml(m_suffix); modifyHtml() += fontSizeSuffix(shorten) + "

"; return currentHtml(); } void AbstractIncludeNavigationContext::getFileInfo(TopDUContext* duchain) { modifyHtml() += QStringLiteral("%1: %2 %3: %4").arg(labelHighlight(i18nc("Files included into this file", "Includes"))).arg(duchain->importedParentContexts().count()).arg(labelHighlight(i18nc("Count of files this file was included into", "Included by"))).arg(duchain->importers().count()); modifyHtml() += QStringLiteral("
"); } QString AbstractIncludeNavigationContext::name() const { return m_item.name; } bool AbstractIncludeNavigationContext::filterDeclaration(Declaration* /*decl*/) { return true; } void AbstractIncludeNavigationContext::addDeclarationsFromContext(KDevelop::DUContext* ctx, bool& first, QList< IdentifierPair > &addedDeclarations, const QString& indent ) { //modifyHtml() += indent + ctx->localScopeIdentifier().toString() + "{
"; QVector children = ctx->childContexts(); QVector declarations = ctx->localDeclarations(); QVector::const_iterator childIterator = children.constBegin(); QVector::const_iterator declarationIterator = declarations.constBegin(); while(childIterator != children.constEnd() || declarationIterator != declarations.constEnd()) { //Show declarations/contexts in the order they appear in the file int currentDeclarationLine = -1; int currentContextLine = -1; if(declarationIterator != declarations.constEnd()) currentDeclarationLine = (*declarationIterator)->rangeInCurrentRevision().start().line(); if(childIterator != children.constEnd()) currentDeclarationLine = (*childIterator)->rangeInCurrentRevision().start().line(); if((currentDeclarationLine <= currentContextLine || currentContextLine == -1 || childIterator == children.constEnd()) && declarationIterator != declarations.constEnd() ) { IdentifierPair id = qMakePair(static_cast((*declarationIterator)->kind()), (*declarationIterator)->qualifiedIdentifier().index()); if(!addedDeclarations.contains(id) && filterDeclaration(*declarationIterator) ) { //Show the declaration if(!first) modifyHtml() += QStringLiteral(", "); else first = false; modifyHtml() += QString(indent + declarationKind(DeclarationPointer(*declarationIterator)) + " ").toHtmlEscaped(); makeLink((*declarationIterator)->qualifiedIdentifier().toString(), DeclarationPointer(*declarationIterator), NavigationAction::NavigateDeclaration); addedDeclarations << id; } ++declarationIterator; } else { //Eventually Recurse into the context if((*childIterator)->type() == DUContext::Global || (*childIterator)->type() == DUContext::Namespace /*|| (*childIterator)->type() == DUContext::Class*/) addDeclarationsFromContext(*childIterator, first, addedDeclarations, indent + ' '); ++childIterator; } } //modifyHtml() += "}
"; } } diff --git a/language/duchain/navigation/abstractnavigationcontext.cpp b/language/duchain/navigation/abstractnavigationcontext.cpp index 8973a2289c..5747958cef 100644 --- a/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,507 +1,507 @@ /* Copyright 2007 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 "abstractnavigationcontext.h" #include #include #include "abstractdeclarationnavigationcontext.h" #include "abstractnavigationwidget.h" #include "usesnavigationcontext.h" #include "../../../interfaces/icore.h" #include "../../../interfaces/idocumentcontroller.h" #include "../functiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../types/functiontype.h" #include "../types/structuretype.h" #include "util/debug.h" #include #include #include namespace KDevelop { void AbstractNavigationContext::setTopContext(KDevelop::TopDUContextPointer context) { m_topContext = context; } KDevelop::TopDUContextPointer AbstractNavigationContext::topContext() const { return m_topContext; } AbstractNavigationContext::AbstractNavigationContext( KDevelop::TopDUContextPointer topContext, AbstractNavigationContext* previousContext) : m_selectedLink(0), m_shorten(false), m_linkCount(-1), m_currentPositionLine(0), m_previousContext(previousContext), m_topContext(topContext) { } void AbstractNavigationContext::addExternalHtml( const QString& text ) { int lastPos = 0; int pos = 0; QString fileMark = QStringLiteral("KDEV_FILE_LINK{"); while( pos < text.length() && (pos = text.indexOf( fileMark, pos)) != -1 ) { modifyHtml() += text.mid(lastPos, pos-lastPos); pos += fileMark.length(); if( pos != text.length() ) { int fileEnd = text.indexOf('}', pos); if( fileEnd != -1 ) { QString file = text.mid( pos, fileEnd - pos ); pos = fileEnd + 1; const QUrl url = QUrl::fromUserInput(file); makeLink( url.fileName(), file, NavigationAction( url, KTextEditor::Cursor() ) ); } } lastPos = pos; } modifyHtml() += text.mid(lastPos, text.length()-lastPos); } void AbstractNavigationContext::makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ) { NavigationAction action( declaration, actionType ); makeLink(name, QString(), action); } QString AbstractNavigationContext::createLink(const QString& name, QString, const NavigationAction& action) { if(m_shorten) { //Do not create links in shortened mode, it's only for viewing return typeHighlight(name.toHtmlEscaped()); } // NOTE: Since the by definition in the HTML standard some uri components // are case-insensitive, we define a new lowercase link-id for each // link. Otherwise Qt 5 seems to mess up the casing and the link // cannot be matched when it's executed. QString hrefId = QString("link_%1").arg(m_links.count()); m_links[ hrefId ] = action; m_intLinks[ m_linkCount ] = action; m_linkLines[ m_linkCount ] = m_currentLine; if(m_currentPositionLine == m_currentLine) { m_currentPositionLine = -1; m_selectedLink = m_linkCount; } QString str = name.toHtmlEscaped(); if( m_linkCount == m_selectedLink ) str = "" + str + ""; QString ret = "" + str + ""; if( m_selectedLink == m_linkCount ) m_selectedLinkAction = action; ++m_linkCount; return ret; } void AbstractNavigationContext::makeLink( const QString& name, QString targetId, const NavigationAction& action) { modifyHtml() += createLink(name, targetId, action); } void AbstractNavigationContext::clear() { m_linkCount = 0; m_currentLine = 0; m_currentText.clear(); m_links.clear(); m_intLinks.clear(); m_linkLines.clear(); } NavigationContextPointer AbstractNavigationContext::executeLink (QString link) { if(!m_links.contains(link)) return NavigationContextPointer(this); return execute(m_links[link]); } NavigationContextPointer AbstractNavigationContext::executeKeyAction(QString key) { Q_UNUSED(key); return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) { if(action.targetContext) return NavigationContextPointer(action.targetContext); if(action.type == NavigationAction::ExecuteKey) return executeKeyAction(action.key); if( !action.decl && (action.type != NavigationAction::JumpToSource || action.document.isEmpty()) ) { qCDebug(LANGUAGE) << "Navigation-action has invalid declaration" << endl; return NavigationContextPointer(this); } qRegisterMetaType("KTextEditor::Cursor"); switch( action.type ) { case NavigationAction::ExecuteKey: break; case NavigationAction::None: qCDebug(LANGUAGE) << "Tried to execute an invalid action in navigation-widget" << endl; break; case NavigationAction::NavigateDeclaration: { AbstractDeclarationNavigationContext* ctx = dynamic_cast(m_previousContext); if( ctx && ctx->declaration() == action.decl ) return NavigationContextPointer( m_previousContext ); return AbstractNavigationContext::registerChild(action.decl); } break; case NavigationAction::NavigateUses: { IContextBrowser* browser = ICore::self()->pluginController()->extensionForPlugin(); if (browser) { browser->showUses(action.decl); return NavigationContextPointer(this); } // fall-through } case NavigationAction::ShowUses: return registerChild(new UsesNavigationContext(action.decl.data(), this)); case NavigationAction::JumpToSource: { QUrl doc = action.document; KTextEditor::Cursor cursor = action.cursor; { DUChainReadLocker lock(DUChain::lock()); if(action.decl) { if(doc.isEmpty()) { doc = action.decl->url().toUrl(); /* if(action.decl->internalContext()) cursor = action.decl->internalContext()->range().start() + KTextEditor::Cursor(0, 1); else*/ cursor = action.decl->rangeInCurrentRevision().start(); } action.decl->activateSpecialization(); } } //This is used to execute the slot delayed in the event-loop, so crashes are avoided QMetaObject::invokeMethod( ICore::self()->documentController(), "openDocument", Qt::QueuedConnection, Q_ARG(QUrl, doc), Q_ARG(KTextEditor::Cursor, cursor) ); break; } case NavigationAction::ShowDocumentation: { auto doc = ICore::self()->documentationController()->documentationForDeclaration(action.decl.data()); ICore::self()->documentationController()->showDocumentation(doc); } break; } return NavigationContextPointer( this ); } void AbstractNavigationContext::setPreviousContext(KDevelop::AbstractNavigationContext* previous) { m_previousContext = previous; } NavigationContextPointer AbstractNavigationContext::registerChild( AbstractNavigationContext* context ) { m_children << NavigationContextPointer(context); return m_children.last(); } NavigationContextPointer AbstractNavigationContext::registerChild(DeclarationPointer declaration) { //We create a navigation-widget here, and steal its context.. evil ;) QScopedPointer navigationWidget(declaration->context()->createNavigationWidget(declaration.data())); if (AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget.data()) ) { NavigationContextPointer ret = abstractNavigationWidget->context(); ret->setPreviousContext(this); m_children << ret; return ret; } else { return NavigationContextPointer(this); } } const int lineJump = 3; void AbstractNavigationContext::down() { //Make sure link-count is valid if( m_linkCount == -1 ) { DUChainReadLocker lock; html(); } int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink+1; newSelectedLink < m_linkCount; ++newSelectedLink) { if(m_linkLines[newSelectedLink] > fromLine && m_linkLines[newSelectedLink] - fromLine <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = 0; m_currentPositionLine = fromLine + lineJump; if(m_currentPositionLine > m_currentLine) m_currentPositionLine = m_currentLine; } void AbstractNavigationContext::up() { //Make sure link-count is valid if( m_linkCount == -1 ) { DUChainReadLocker lock; html(); } int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink-1; newSelectedLink >= 0; --newSelectedLink) { if(m_linkLines[newSelectedLink] < fromLine && fromLine - m_linkLines[newSelectedLink] <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = m_currentLine; m_currentPositionLine = fromLine - lineJump; if(m_currentPositionLine < 0) m_currentPositionLine = 0; } void AbstractNavigationContext::nextLink() { //Make sure link-count is valid if( m_linkCount == -1 ) { DUChainReadLocker lock; html(); } m_currentPositionLine = -1; if( m_linkCount > 0 ) m_selectedLink = (m_selectedLink+1) % m_linkCount; } void AbstractNavigationContext::previousLink() { //Make sure link-count is valid if( m_linkCount == -1 ) { DUChainReadLocker lock; html(); } m_currentPositionLine = -1; if( m_linkCount > 0 ) { --m_selectedLink; if( m_selectedLink < 0 ) m_selectedLink += m_linkCount; } Q_ASSERT(m_selectedLink >= 0); } int AbstractNavigationContext::linkCount() const { return m_linkCount; } void AbstractNavigationContext::setPrefixSuffix( const QString& prefix, const QString& suffix ) { m_prefix = prefix; m_suffix = suffix; } NavigationContextPointer AbstractNavigationContext::back() { if(m_previousContext) return NavigationContextPointer(m_previousContext); else return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept() { if( m_selectedLink >= 0 && m_selectedLink < m_linkCount ) { NavigationAction action = m_intLinks[m_selectedLink]; return execute(action); } return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) { if(decl.data()) { NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); return execute(action); }else{ return NavigationContextPointer(this); } } NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) { if( !m_links.contains(link) ) { qCDebug(LANGUAGE) << "Executed unregistered link " << link << endl; return NavigationContextPointer(this); } return execute(m_links[link]); } NavigationAction AbstractNavigationContext::currentAction() const { return m_selectedLinkAction; } QString AbstractNavigationContext::declarationKind(DeclarationPointer decl) { const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); QString kind; if( decl->isTypeAlias() ) kind = i18n("Typedef"); else if( decl->kind() == Declaration::Type ) { if( decl->type() ) kind = i18n("Class"); } else if( decl->kind() == Declaration::Instance ) { kind = i18n("Variable"); } else if ( decl->kind() == Declaration::Namespace ) { kind = i18n("Namespace"); } if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) kind = i18n("Namespace import"); else kind = i18n("Namespace alias"); } if(function) kind = i18n("Function"); if( decl->isForwardDeclaration() ) kind = i18n("Forward Declaration"); return kind; } QString AbstractNavigationContext::html(bool shorten) { m_shorten = shorten; return QString(); } bool AbstractNavigationContext::alreadyComputed() const { return !m_currentText.isEmpty(); } bool AbstractNavigationContext::isWidgetMaximized() const { return true; } QWidget* AbstractNavigationContext::widget() const { - return 0; + return nullptr; } ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line static QStringList splitAndKeep(QString str, QRegExp regExp) { QStringList ret; int place = regExp.indexIn(str); while(place != -1) { ret << str.left(place + regExp.matchedLength()); str = str.mid(place + regExp.matchedLength()); place = regExp.indexIn(str); } ret << str; return ret; } void AbstractNavigationContext::addHtml(QString html) { QRegExp newLineRegExp("
|
"); foreach(const QString& line, splitAndKeep(html, newLineRegExp)) { m_currentText += line; if(line.indexOf(newLineRegExp) != -1) { ++m_currentLine; if(m_currentLine == m_currentPositionLine) { m_currentText += QStringLiteral(" <-> "); // ><-> is <-> } } } } QString AbstractNavigationContext::currentHtml() const { return m_currentText; } QString AbstractNavigationContext::fontSizePrefix(bool /*shorten*/) const { return QString(); } QString AbstractNavigationContext::fontSizeSuffix(bool /*shorten*/) const { return QString(); } QString Colorizer::operator() ( const QString& str ) const { QString ret = "" + str + ""; if( m_formatting & Fixed ) ret = ""+ret+""; if ( m_formatting & Bold ) ret = ""+ret+""; if ( m_formatting & Italic ) ret = ""+ret+""; return ret; } const Colorizer AbstractNavigationContext::typeHighlight(QStringLiteral("006000")); const Colorizer AbstractNavigationContext::errorHighlight(QStringLiteral("990000")); const Colorizer AbstractNavigationContext::labelHighlight(QStringLiteral("000000")); const Colorizer AbstractNavigationContext::codeHighlight(QStringLiteral("005000")); const Colorizer AbstractNavigationContext::propertyHighlight(QStringLiteral("009900")); const Colorizer AbstractNavigationContext::navigationHighlight(QStringLiteral("000099")); const Colorizer AbstractNavigationContext::importantHighlight(QStringLiteral("000000"), Colorizer::Bold | Colorizer::Italic); const Colorizer AbstractNavigationContext::commentHighlight(QStringLiteral("303030")); const Colorizer AbstractNavigationContext::nameHighlight(QStringLiteral("000000"), Colorizer::Bold); } diff --git a/language/duchain/navigation/abstractnavigationwidget.cpp b/language/duchain/navigation/abstractnavigationwidget.cpp index 1945822a64..febd7e974b 100644 --- a/language/duchain/navigation/abstractnavigationwidget.cpp +++ b/language/duchain/navigation/abstractnavigationwidget.cpp @@ -1,300 +1,300 @@ /* Copyright 2007 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 "abstractnavigationwidget.h" #include #include #include #include #include #include #include "../duchainlock.h" #include "util/debug.h" namespace KDevelop { AbstractNavigationWidget::AbstractNavigationWidget() - : m_browser(0), m_currentWidget(0) + : m_browser(nullptr), m_currentWidget(nullptr) { setPalette( QApplication::palette() ); setFocusPolicy(Qt::NoFocus); resize(100, 100); } const int maxNavigationWidgetWidth = 580; QSize AbstractNavigationWidget::sizeHint() const { if(m_browser) { updateIdealSize(); QSize ret = QSize(qMin(m_idealTextSize.width(), maxNavigationWidgetWidth), qMin(m_idealTextSize.height(), 300)); if(m_idealTextSize.height()>=300) { //make space for the scrollbar in case it's not fitting ret.rwidth() += 17; //m_browser->verticalScrollBar()->width() returns 100, for some reason } if(m_currentWidget) { ret.setHeight( ret.height() + m_currentWidget->sizeHint().height() ); if(m_currentWidget->sizeHint().width() > ret.width()) ret.setWidth(m_currentWidget->sizeHint().width()); if(ret.width() < 500) //When we embed a widget, give it some space, even if it doesn't have a large size-hint ret.setWidth(500); } return ret; } else return QWidget::sizeHint(); } void AbstractNavigationWidget::initBrowser(int height) { Q_UNUSED(height); m_browser = new QTextBrowser; // since we can embed arbitrary HTML we have to make sure it stays readable by forcing a black-white palette QPalette p; p.setColor(QPalette::AlternateBase, Qt::white); p.setColor(QPalette::Base, Qt::white); p.setColor(QPalette::Text, Qt::black); m_browser->setPalette( p ); m_browser->setOpenLinks(false); m_browser->setOpenExternalLinks(false); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_browser); layout->setMargin(0); setLayout(layout); connect( m_browser.data(), &QTextBrowser::anchorClicked, this, &AbstractNavigationWidget::anchorClicked ); foreach(QWidget* w, findChildren()) w->setContextMenuPolicy(Qt::NoContextMenu); } AbstractNavigationWidget::~AbstractNavigationWidget() { if(m_currentWidget) layout()->removeWidget(m_currentWidget); } void AbstractNavigationWidget::setContext(NavigationContextPointer context, int initBrows) { - if(m_browser == 0) + if(m_browser == nullptr) initBrowser(initBrows); if(!context) { qCDebug(LANGUAGE) << "no new context created"; return; } if(context == m_context && (!context || context->alreadyComputed())) return; if (!m_startContext) m_startContext = m_context; bool wasInitial = (m_context == m_startContext); m_context = context; update(); emit contextChanged(wasInitial, m_context == m_startContext); emit sizeHintChanged(); } void AbstractNavigationWidget::updateIdealSize() const { if(m_context && !m_idealTextSize.isValid()) { QTextDocument doc; doc.setHtml(m_currentText); if(doc.idealWidth() > maxNavigationWidgetWidth) { doc.setTextWidth(maxNavigationWidgetWidth); m_idealTextSize.setWidth(maxNavigationWidgetWidth); }else{ m_idealTextSize.setWidth(doc.idealWidth()); } m_idealTextSize.setHeight(doc.size().height()); } } void AbstractNavigationWidget::setDisplayHints(DisplayHints hints) { m_hints = hints; } void AbstractNavigationWidget::update() { setUpdatesEnabled(false); Q_ASSERT( m_context ); QString html; { DUChainReadLocker lock; html = m_context->html(); } if(!html.isEmpty()) { int scrollPos = m_browser->verticalScrollBar()->value(); if ( !(m_hints & EmbeddableWidget)) { // TODO: Only show that the first time, or the first few times this context is shown? html += QStringLiteral("

"); if (m_context->linkCount() > 0) { html += i18n("(Hold 'Alt' to show. Navigate via arrow keys, activate by pressing 'Enter')"); } else { html += i18n("(Hold 'Alt' to show this tooltip)"); } html += QStringLiteral("

"); } m_browser->setHtml( html ); m_currentText = html; m_idealTextSize = QSize(); QSize hint = sizeHint(); if(hint.height() >= m_idealTextSize.height()) { m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }else{ m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } m_browser->verticalScrollBar()->setValue(scrollPos); m_browser->scrollToAnchor(QStringLiteral("currentPosition")); m_browser->show(); }else{ m_browser->hide(); } if(m_currentWidget) { layout()->removeWidget(m_currentWidget); - m_currentWidget->setParent(0); + m_currentWidget->setParent(nullptr); } m_currentWidget = m_context->widget(); m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_browser->setMaximumHeight(10000); if(m_currentWidget) { if (m_currentWidget->metaObject() ->indexOfSignal(QMetaObject::normalizedSignature("navigateDeclaration(KDevelop::IndexedDeclaration)")) != -1) { connect(m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), this, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); } layout()->addWidget(m_currentWidget); if(m_context->isWidgetMaximized()) { //Leave unused room to the widget m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_browser->setMaximumHeight(m_idealTextSize.height()); } } setUpdatesEnabled(true); } NavigationContextPointer AbstractNavigationWidget::context() { return m_context; } void AbstractNavigationWidget::navigateDeclaration(KDevelop::IndexedDeclaration decl) { setContext(m_context->accept(decl)); } void AbstractNavigationWidget::anchorClicked(const QUrl& url) { //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->acceptLink(url.toString()); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::next() { Q_ASSERT( m_context ); m_context->nextLink(); update(); } void AbstractNavigationWidget::previous() { Q_ASSERT( m_context ); m_context->previousLink(); update(); } void AbstractNavigationWidget::accept() { Q_ASSERT( m_context ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->accept(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::back() { QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->back(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::up() { m_context->up(); update(); } void AbstractNavigationWidget::down() { m_context->down(); update(); } void AbstractNavigationWidget::embeddedWidgetAccept() { accept(); } void AbstractNavigationWidget::embeddedWidgetDown() { down(); } void AbstractNavigationWidget::embeddedWidgetRight() { next(); } void AbstractNavigationWidget::embeddedWidgetLeft() { previous(); } void AbstractNavigationWidget::embeddedWidgetUp() { up(); } void AbstractNavigationWidget::wheelEvent(QWheelEvent* event ) { QWidget::wheelEvent(event); event->accept(); return; } } diff --git a/language/duchain/navigation/usescollector.cpp b/language/duchain/navigation/usescollector.cpp index 935273422e..41cb42de98 100644 --- a/language/duchain/navigation/usescollector.cpp +++ b/language/duchain/navigation/usescollector.cpp @@ -1,427 +1,427 @@ /* 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 "usescollector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../classmemberdeclaration.h" #include "../abstractfunctiondeclaration.h" #include "../functiondefinition.h" #include "util/debug.h" #include #include #include using namespace KDevelop; ///@todo make this language-neutral static Identifier destructorForName(Identifier name) { QString str = name.identifier().str(); if(str.startsWith('~')) return Identifier(str); return Identifier('~'+str); } ///@todo Only collect uses within currently loaded projects template void collectImporters(ImportanceChecker& checker, ParsingEnvironmentFile* current, QSet& visited, QSet& collected) { //Ignore proxy-contexts while collecting. Those build a parallel and much more complicated structure. if(current->isProxyContext()) return; if(visited.contains(current)) return; visited.insert(current); if(checker(current)) collected.insert(current); foreach(const ParsingEnvironmentFilePointer& importer, current->importers()) if(importer.data()) collectImporters(checker, importer.data(), visited, collected); else qCDebug(LANGUAGE) << "missing environment-file, strange"; } ///The returned set does not include the file itself ///@parm visited should be empty on each call, used to prevent endless recursion void allImportedFiles(ParsingEnvironmentFilePointer file, QSet& set, QSet& visited) { foreach(const ParsingEnvironmentFilePointer &import, file->imports()) { if(!import) { qCDebug(LANGUAGE) << "warning: missing import"; continue; } if(!visited.contains(import)) { visited.insert(import); set.insert(import->url()); allImportedFiles(import, set, visited); } } } void UsesCollector::setCollectConstructors(bool process) { m_collectConstructors = process; } void UsesCollector::setProcessDeclarations(bool process) { m_processDeclarations = process; } void UsesCollector::setCollectOverloads(bool collect) { m_collectOverloads = collect; } void UsesCollector::setCollectDefinitions(bool collect) { m_collectDefinitions = collect; } QList UsesCollector::declarations() { return m_declarations; } bool UsesCollector::isReady() const { return m_waitForUpdate.size() == m_updateReady.size(); } bool UsesCollector::shouldRespectFile(IndexedString document) { return (bool)ICore::self()->projectController()->findProjectForUrl(document.toUrl()) || (bool)ICore::self()->documentController()->documentForUrl(document.toUrl()); } struct ImportanceChecker { ImportanceChecker(UsesCollector& collector) : m_collector(collector) { } bool operator ()(ParsingEnvironmentFile* file) { return m_collector.shouldRespectFile(file->url()); } UsesCollector& m_collector; }; void UsesCollector::startCollecting() { DUChainReadLocker lock(DUChain::lock()); if(Declaration* decl = m_declaration.data()) { if(m_collectDefinitions) { if(FunctionDefinition* def = dynamic_cast(decl)) { //Jump from definition to declaration Declaration* declaration = def->declaration(); if(declaration) decl = declaration; } } ///Collect all overloads into "decls" QList decls; if(m_collectOverloads && decl->context()->owner() && decl->context()->type() == DUContext::Class) { //First find the overridden base, and then all overriders of that base. while(Declaration* overridden = DUChainUtils::getOverridden(decl)) decl = overridden; uint maxAllowedSteps = 10000; decls += DUChainUtils::getOverriders( decl->context()->owner(), decl, maxAllowedSteps ); if(maxAllowedSteps == 10000) { ///@todo Fail! } } decls << decl; ///Collect all "parsed versions" or forward-declarations etc. here, into allDeclarations QSet allDeclarations; foreach(Declaration* overload, decls) { m_declarations = DUChainUtils::collectAllVersions(overload); foreach(const IndexedDeclaration &d, m_declarations) { if(!d.data() || d.data()->id() != overload->id()) continue; allDeclarations.insert(d); if(m_collectConstructors && d.data() && d.data()->internalContext() && d.data()->internalContext()->type() == DUContext::Class) { - QList constructors = d.data()->internalContext()->findLocalDeclarations(d.data()->identifier(), CursorInRevision::invalid(), 0, AbstractType::Ptr(), DUContext::OnlyFunctions); + QList constructors = d.data()->internalContext()->findLocalDeclarations(d.data()->identifier(), CursorInRevision::invalid(), nullptr, AbstractType::Ptr(), DUContext::OnlyFunctions); foreach(Declaration* constructor, constructors) { ClassFunctionDeclaration* classFun = dynamic_cast(constructor); if(classFun && classFun->isConstructor()) allDeclarations.insert(IndexedDeclaration(constructor)); } Identifier destructorId = destructorForName(d.data()->identifier()); - QList destructors = d.data()->internalContext()->findLocalDeclarations(destructorId, CursorInRevision::invalid(), 0, AbstractType::Ptr(), DUContext::OnlyFunctions); + QList destructors = d.data()->internalContext()->findLocalDeclarations(destructorId, CursorInRevision::invalid(), nullptr, AbstractType::Ptr(), DUContext::OnlyFunctions); foreach(Declaration* destructor, destructors) { ClassFunctionDeclaration* classFun = dynamic_cast(destructor); if(classFun && classFun->isDestructor()) allDeclarations.insert(IndexedDeclaration(destructor)); } } } } ///Collect definitions for declarations if(m_collectDefinitions) { foreach(const IndexedDeclaration d, allDeclarations) { Declaration* definition = FunctionDefinition::definition(d.data()); if(definition) { qCDebug(LANGUAGE) << "adding definition"; allDeclarations.insert(IndexedDeclaration(definition)); } } } m_declarations.clear(); ///Step 4: Copy allDeclarations into m_declarations, build top-context list, etc. QList candidateTopContexts; foreach(const IndexedDeclaration d, allDeclarations) { m_declarations << d; m_declarationTopContexts.insert(d.indexedTopContext()); //We only collect declarations with the same type here.. candidateTopContexts << d.indexedTopContext().data(); } ImportanceChecker checker(*this); QSet visited; QSet collected; qCDebug(LANGUAGE) << "count of source candidate top-contexts:" << candidateTopContexts.size(); ///We use ParsingEnvironmentFile to collect all the relevant importers, because loading those is very cheap, compared ///to loading a whole TopDUContext. if(decl->inSymbolTable()) { //The declaration can only be used from other contexts if it is in the symbol table foreach(const ReferencedTopDUContext &top, candidateTopContexts) { if(top->parsingEnvironmentFile()) { collectImporters(checker, top->parsingEnvironmentFile().data(), visited, collected); //In C++, visibility is not handled strictly through the import-structure. //It may happen that an object is visible because of an earlier include. //We can not perfectly handle that, but we can at least handle it if the header includes //the header that contains the declaration. That header may be parsed empty due to header-guards, //but we still need to pick it up here. QList allVersions = DUChain::self()->allEnvironmentFiles(top->url()); foreach(const ParsingEnvironmentFilePointer& version, allVersions) collectImporters(checker, version.data(), visited, collected); } } } KDevelop::ParsingEnvironmentFile* file=decl->topContext()->parsingEnvironmentFile().data(); if(!file) return; if(checker(file)) collected.insert(file); { QSet filteredCollected; QMap grepCache; // Filter the collected files by performing a grep foreach(ParsingEnvironmentFile* file, collected) { IndexedString url = file->url(); QMap< IndexedString, bool >::iterator grepCacheIt = grepCache.find(url); if(grepCacheIt == grepCache.end()) { CodeRepresentation::Ptr repr = KDevelop::createCodeRepresentation( url ); if(repr) { QVector found = repr->grep(decl->identifier().identifier().str()); grepCacheIt = grepCache.insert(url, !found.isEmpty()); } } if(grepCacheIt.value()) filteredCollected << file; } qCDebug(LANGUAGE) << "Collected contexts for full re-parse, before filtering: " << collected.size() << " after filtering: " << filteredCollected.size(); collected = filteredCollected; } ///We have all importers now. However since we can tell parse-jobs to also update all their importers, we only need to ///update the "root" top-contexts that open the whole set with their imports. QSet rootFiles; QSet allFiles; foreach(ParsingEnvironmentFile* importer, collected) { QSet allImports; QSet visited; allImportedFiles(ParsingEnvironmentFilePointer(importer), allImports, visited); //Remove all files from the "root" set that are imported by this one ///@todo more intelligent rootFiles -= allImports; allFiles += allImports; allFiles.insert(importer->url()); rootFiles.insert(importer->url()); } emit maximumProgressSignal(rootFiles.size()); maximumProgress(rootFiles.size()); //If we used the AllDeclarationsContextsAndUsesRecursive flag here, we would compute way too much. This way we only //set the minimum-features selectively on the files we really require them on. foreach(ParsingEnvironmentFile* file, collected) m_staticFeaturesManipulated.insert(file->url()); m_staticFeaturesManipulated.insert(decl->url()); foreach(const IndexedString &file, m_staticFeaturesManipulated) ParseJob::setStaticMinimumFeatures(file, TopDUContext::AllDeclarationsContextsAndUses); m_waitForUpdate = rootFiles; foreach(const IndexedString &file, rootFiles) { qCDebug(LANGUAGE) << "updating root file:" << file.str(); DUChain::self()->updateContextForUrl(file, TopDUContext::AllDeclarationsContextsAndUses, this); } }else{ emit maximumProgressSignal(0); maximumProgress(0); } } void UsesCollector::maximumProgress(uint max) { Q_UNUSED(max); } UsesCollector::UsesCollector(IndexedDeclaration declaration) : m_declaration(declaration), m_collectOverloads(true), m_collectDefinitions(true), m_collectConstructors(false), m_processDeclarations(true) { } UsesCollector::~UsesCollector() { ICore::self()->languageController()->backgroundParser()->revertAllRequests(this); foreach(const IndexedString &file, m_staticFeaturesManipulated) ParseJob::unsetStaticMinimumFeatures(file, TopDUContext::AllDeclarationsContextsAndUses); } void UsesCollector::progress(uint processed, uint total) { Q_UNUSED(processed); Q_UNUSED(total); } void UsesCollector::updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext) { DUChainReadLocker lock(DUChain::lock()); if(!topContext) { qCDebug(LANGUAGE) << "failed updating" << url.str(); }else{ if(topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->isProxyContext()) { ///Use the attached content-context instead foreach(const DUContext::Import &import, topContext->importedParentContexts()) { - if(import.context(0) && import.context(0)->topContext()->parsingEnvironmentFile() && !import.context(0)->topContext()->parsingEnvironmentFile()->isProxyContext()) { - if((import.context(0)->topContext()->features() & TopDUContext::AllDeclarationsContextsAndUses)) { - ReferencedTopDUContext newTop(import.context(0)->topContext()); + if(import.context(nullptr) && import.context(nullptr)->topContext()->parsingEnvironmentFile() && !import.context(nullptr)->topContext()->parsingEnvironmentFile()->isProxyContext()) { + if((import.context(nullptr)->topContext()->features() & TopDUContext::AllDeclarationsContextsAndUses)) { + ReferencedTopDUContext newTop(import.context(nullptr)->topContext()); topContext = newTop; break; } } } if(topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->isProxyContext()) { qCDebug(LANGUAGE) << "got bad proxy-context for" << url.str(); - topContext = 0; + topContext = nullptr; } } } if(m_waitForUpdate.contains(url) && !m_updateReady.contains(url)) { m_updateReady << url; m_checked.clear(); emit progressSignal(m_updateReady.size(), m_waitForUpdate.size()); progress(m_updateReady.size(), m_waitForUpdate.size()); } if(!topContext || !topContext->parsingEnvironmentFile()) { qCDebug(LANGUAGE) << "bad top-context"; return; } if(!m_staticFeaturesManipulated.contains(url)) return; //Not interesting if(!(topContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ///@todo With simplified environment-matching, the same file may have been imported multiple times, ///while only one of those was updated. We have to check here whether this file is just such an import, ///or whether we work on with it. ///@todo We will lose files that were edited right after their update here. qCWarning(LANGUAGE) << "WARNING: context" << topContext->url().str() << "does not have the required features!!"; ICore::self()->uiController()->showErrorMessage("Updating " + ICore::self()->projectController()->prettyFileName(topContext->url().toUrl(), KDevelop::IProjectController::FormatPlain) + " failed!", 5); return; } if(topContext->parsingEnvironmentFile()->needsUpdate()) { qCWarning(LANGUAGE) << "WARNING: context" << topContext->url().str() << "is not up to date!"; ICore::self()->uiController()->showErrorMessage(i18n("%1 still needs an update!", ICore::self()->projectController()->prettyFileName(topContext->url().toUrl(), KDevelop::IProjectController::FormatPlain)), 5); // return; } IndexedTopDUContext indexed(topContext.data()); if(m_checked.contains(indexed)) return; if(!topContext.data()) { qCDebug(LANGUAGE) << "updated top-context is zero:" << url.str(); return; } m_checked.insert(indexed); if(m_declaration.data() && ((m_processDeclarations && m_declarationTopContexts.contains(indexed)) || DUChainUtils::contextHasUse(topContext.data(), m_declaration.data()))) { if(!m_processed.contains(topContext->url())) { m_processed.insert(topContext->url()); lock.unlock(); emit processUsesSignal(topContext); processUses(topContext); lock.lock(); } } else { if(!m_declaration.data()) { qCDebug(LANGUAGE) << "declaration has become invalid"; } } QList imports; foreach(const DUContext::Import &imported, topContext->importedParentContexts()) - if(imported.context(0) && imported.context(0)->topContext()) - imports << KDevelop::ReferencedTopDUContext(imported.context(0)->topContext()); + if(imported.context(nullptr) && imported.context(nullptr)->topContext()) + imports << KDevelop::ReferencedTopDUContext(imported.context(nullptr)->topContext()); foreach(const KDevelop::ReferencedTopDUContext &import, imports) { IndexedString url = import->url(); lock.unlock(); updateReady(url, import); lock.lock(); } } IndexedDeclaration UsesCollector::declaration() const { return m_declaration; } diff --git a/language/duchain/navigation/useswidget.cpp b/language/duchain/navigation/useswidget.cpp index f6c0eb1817..bea2eba463 100644 --- a/language/duchain/navigation/useswidget.cpp +++ b/language/duchain/navigation/useswidget.cpp @@ -1,663 +1,663 @@ /* 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 "util/debug.h" #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, 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 background(251, 250, 150); const QColor foreground(0, 0, 0); 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() + ""; } /** * 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::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton && !event->modifiers()) { ICore::self()->documentController()->openDocument(m_document.toUrl(), m_range->range().start()); 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(0); + 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(0); + 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(0) { +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 = 0; + 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/language/duchain/parsingenvironment.cpp b/language/duchain/parsingenvironment.cpp index 781b0ff62e..5899cee190 100644 --- a/language/duchain/parsingenvironment.cpp +++ b/language/duchain/parsingenvironment.cpp @@ -1,388 +1,388 @@ /* Copyright 2007 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 "parsingenvironment.h" #include "topducontext.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "duchain.h" #include "duchainlock.h" #include "topducontextdata.h" #include "util/debug.h" #include #define ENSURE_READ_LOCKED if(indexedTopContext().isValid()) { ENSURE_CHAIN_READ_LOCKED } #define ENSURE_WRITE_LOCKED if(indexedTopContext().isValid()) { ENSURE_CHAIN_READ_LOCKED } namespace KDevelop { -StaticParsingEnvironmentData* ParsingEnvironmentFile::m_staticData = 0; +StaticParsingEnvironmentData* ParsingEnvironmentFile::m_staticData = nullptr; #if 0 ///Wrapper class around objects that are managed through the DUChain, and may contain arbitrary objects ///that support duchain-like store (IndexedString, StorableSet, and the likes). The object must not contain pointers ///or other non-persistent data. /// ///The object is stored during the normal duchain storage/cleanup cycles. template struct PersistentDUChainObject { ///@param fileName File-name that will be used to store the data of the object in the duchain directory PersistentDUChainObject(QString fileName) { object = (T*) new char[sizeof(T)]; if(!DUChain::self()->addPersistentObject(object, fileName, sizeof(T))) { //The constructor is called only if the object did not exist yet new (object) T(); } } ~PersistentDUChainObject() { DUChain::self()->unregisterPersistentObject(object); delete[] object; } T* object; }; #endif REGISTER_DUCHAIN_ITEM(ParsingEnvironmentFile); TopDUContext::Features ParsingEnvironmentFile::features() const { ENSURE_READ_LOCKED return d_func()->m_features; } ParsingEnvironment::ParsingEnvironment() { } ParsingEnvironment::~ParsingEnvironment() { } IndexedString ParsingEnvironmentFile::url() const { ENSURE_READ_LOCKED return d_func()->m_url; } bool ParsingEnvironmentFile::needsUpdate(const ParsingEnvironment* /*environment*/) const { ENSURE_READ_LOCKED return d_func()->m_allModificationRevisions.needsUpdate(); } bool ParsingEnvironmentFile::matchEnvironment(const ParsingEnvironment* /*environment*/) const { ENSURE_READ_LOCKED return true; } void ParsingEnvironmentFile::setTopContext(KDevelop::IndexedTopDUContext context) { if(d_func()->m_topContext == context) return; ENSURE_WRITE_LOCKED d_func_dynamic()->m_topContext = context; //Enforce an update of the 'features satisfied' caches TopDUContext::Features oldFeatures = features(); setFeatures(TopDUContext::Empty); setFeatures(oldFeatures); } KDevelop::IndexedTopDUContext ParsingEnvironmentFile::indexedTopContext() const { return d_func()->m_topContext; } const ModificationRevisionSet& ParsingEnvironmentFile::allModificationRevisions() const { ENSURE_READ_LOCKED return d_func()->m_allModificationRevisions; } void ParsingEnvironmentFile::addModificationRevisions(const ModificationRevisionSet& revisions) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions += revisions; } ParsingEnvironmentFile::ParsingEnvironmentFile(ParsingEnvironmentFileData& data, const IndexedString& url) : DUChainBase(data) { d_func_dynamic()->m_url = url; d_func_dynamic()->m_modificationTime = ModificationRevision::revisionForFile(url); addModificationRevision(url, d_func_dynamic()->m_modificationTime); Q_ASSERT(d_func()->m_allModificationRevisions.index()); } ParsingEnvironmentFile::ParsingEnvironmentFile(const IndexedString& url) : DUChainBase(*new ParsingEnvironmentFileData()) { d_func_dynamic()->setClassId(this); d_func_dynamic()->m_url = url; d_func_dynamic()->m_modificationTime = ModificationRevision::revisionForFile(url); addModificationRevision(url, d_func_dynamic()->m_modificationTime); Q_ASSERT(d_func()->m_allModificationRevisions.index()); } TopDUContext* ParsingEnvironmentFile::topContext() const { ENSURE_READ_LOCKED return indexedTopContext().data(); } ParsingEnvironmentFile::~ParsingEnvironmentFile() { } ParsingEnvironmentFile::ParsingEnvironmentFile(ParsingEnvironmentFileData& data) : DUChainBase(data) { //If this triggers, the item has most probably not been initialized with the correct constructor that takes an IndexedString. Q_ASSERT(d_func()->m_allModificationRevisions.index()); } int ParsingEnvironment::type() const { return StandardParsingEnvironment; } int ParsingEnvironmentFile::type() const { ENSURE_READ_LOCKED return StandardParsingEnvironment; } bool ParsingEnvironmentFile::isProxyContext() const { ENSURE_READ_LOCKED return d_func()->m_isProxyContext; } void ParsingEnvironmentFile::setIsProxyContext(bool is) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_isProxyContext = is; } QList< QExplicitlySharedDataPointer > ParsingEnvironmentFile::imports() const { ENSURE_READ_LOCKED QList imp; IndexedTopDUContext top = indexedTopContext(); if(top.isLoaded()) { TopDUContext* topCtx = top.data(); FOREACH_FUNCTION(const DUContext::Import& import, topCtx->d_func()->m_importedContexts) imp << import.indexedContext(); }else{ imp = TopDUContextDynamicData::loadImports(top.index()); } QList< QExplicitlySharedDataPointer > ret; foreach(const IndexedDUContext ctx, imp) { QExplicitlySharedDataPointer item = DUChain::self()->environmentFileForDocument(ctx.topContextIndex()); if(item) { ret << item; }else{ qCDebug(LANGUAGE) << url().str() << indexedTopContext().index() << ": invalid import" << ctx.topContextIndex(); } } return ret; } QList< QExplicitlySharedDataPointer > ParsingEnvironmentFile::importers() const { ENSURE_READ_LOCKED QList imp; IndexedTopDUContext top = indexedTopContext(); if(top.isLoaded()) { TopDUContext* topCtx = top.data(); FOREACH_FUNCTION(const IndexedDUContext& ctx, topCtx->d_func()->m_importers) imp << ctx; }else{ imp = TopDUContextDynamicData::loadImporters(top.index()); } QList< QExplicitlySharedDataPointer > ret; foreach(const IndexedDUContext ctx, imp) { QExplicitlySharedDataPointer f = DUChain::self()->environmentFileForDocument(ctx.topContextIndex()); if(f) ret << f; else qCDebug(LANGUAGE) << url().str() << indexedTopContext().index() << ": invalid importer context" << ctx.topContextIndex(); } return ret; } QMutex featureSatisfactionMutex; inline bool satisfied(TopDUContext::Features features, TopDUContext::Features required) { return (features & required) == required; } ///Makes sure the file has the correct features attached, and if minimumFeatures contains AllDeclarationsContextsAndUsesForRecursive, then also checks all imports. bool ParsingEnvironmentFile::featuresMatch(TopDUContext::Features minimumFeatures, QSet& checked) const { if(checked.contains(this)) return true; checked.insert(this); TopDUContext::Features localRequired = (TopDUContext::Features) (minimumFeatures | ParseJob::staticMinimumFeatures(url())); //Check other 'local' requirements localRequired = (TopDUContext::Features)(localRequired & (TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST)); if(!satisfied(features(), localRequired)) return false; if(ParseJob::hasStaticMinimumFeatures()) { //Do a manual recursion to check whether any of the relevant contexts has static minimum features set ///@todo Only do this if one of the imports actually has static features attached (by RecursiveImports set intersection) foreach(const ParsingEnvironmentFilePointer &import, imports()) if(!import->featuresMatch(minimumFeatures & TopDUContext::Recursive ? minimumFeatures : ((TopDUContext::Features)0), checked)) return false; }else if(minimumFeatures & TopDUContext::Recursive) { QMutexLocker lock(&featureSatisfactionMutex); TopDUContext::IndexedRecursiveImports recursiveImportIndices = d_func()->m_importsCache; if(recursiveImportIndices.isEmpty()) { //Unfortunately, we have to load the top-context TopDUContext* top = topContext(); if(top) recursiveImportIndices = top->recursiveImportIndices(); } ///@todo Do not create temporary intersected sets //Use the features-cache to efficiently check the recursive satisfaction of the features if(satisfied(minimumFeatures, TopDUContext::AST) && !((m_staticData->ASTSatisfied & recursiveImportIndices) == recursiveImportIndices)) return false; if(satisfied(minimumFeatures, TopDUContext::AllDeclarationsContextsAndUses)) return (m_staticData->allDeclarationsAndUsesSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::AllDeclarationsAndContexts)) return (m_staticData->allDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::VisibleDeclarationsAndContexts)) return (m_staticData->visibleDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::SimplifiedVisibleDeclarationsAndContexts)) return (m_staticData->simplifiedVisibleDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; } return true; } void ParsingEnvironmentFile::setFeatures(TopDUContext::Features features) { if(d_func()->m_features == features) return; ENSURE_WRITE_LOCKED d_func_dynamic()->m_features = features; if(indexedTopContext().isValid()) { QMutexLocker lock(&featureSatisfactionMutex); if(!satisfied(features, TopDUContext::SimplifiedVisibleDeclarationsAndContexts)) m_staticData->simplifiedVisibleDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->simplifiedVisibleDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::VisibleDeclarationsAndContexts)) m_staticData->visibleDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->visibleDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AllDeclarationsAndContexts)) m_staticData->allDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->allDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AllDeclarationsContextsAndUses)) m_staticData->allDeclarationsAndUsesSatisfied.remove(indexedTopContext()); else m_staticData->allDeclarationsAndUsesSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AST)) m_staticData->ASTSatisfied.remove(indexedTopContext()); else m_staticData->ASTSatisfied.insert(indexedTopContext()); } } bool ParsingEnvironmentFile::featuresSatisfied(KDevelop::TopDUContext::Features minimumFeatures) const { ENSURE_READ_LOCKED QSet checked; if(minimumFeatures & TopDUContext::ForceUpdate) return false; return featuresMatch(minimumFeatures, checked); } void ParsingEnvironmentFile::clearModificationRevisions() { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions.clear(); d_func_dynamic()->m_allModificationRevisions.addModificationRevision(d_func()->m_url, d_func()->m_modificationTime); } void ParsingEnvironmentFile::addModificationRevision(const IndexedString& url, const ModificationRevision& revision) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions.addModificationRevision(url, revision); { //Test Q_ASSERT(d_func_dynamic()->m_allModificationRevisions.index()); bool result = d_func_dynamic()->m_allModificationRevisions.removeModificationRevision(url, revision); Q_UNUSED( result ); Q_ASSERT( result ); d_func_dynamic()->m_allModificationRevisions.addModificationRevision(url, revision); } } void ParsingEnvironmentFile::setModificationRevision( const KDevelop::ModificationRevision& rev ) { ENSURE_WRITE_LOCKED Q_ASSERT(d_func_dynamic()->m_allModificationRevisions.index()); bool result = d_func_dynamic()->m_allModificationRevisions.removeModificationRevision(d_func()->m_url, d_func()->m_modificationTime); Q_ASSERT( result ); Q_UNUSED( result ); #ifdef LEXERCACHE_DEBUG if(debugging()) { qCDebug(LANGUAGE) << id(this) << "setting modification-revision" << rev.toString(); } #endif d_func_dynamic()->m_modificationTime = rev; #ifdef LEXERCACHE_DEBUG if(debugging()) { qCDebug(LANGUAGE) << id(this) << "new modification-revision" << m_modificationTime; } #endif d_func_dynamic()->m_allModificationRevisions.addModificationRevision(d_func()->m_url, d_func()->m_modificationTime); } KDevelop::ModificationRevision ParsingEnvironmentFile::modificationRevision() const { ENSURE_READ_LOCKED return d_func()->m_modificationTime; } IndexedString ParsingEnvironmentFile::language() const { return d_func()->m_language; } void ParsingEnvironmentFile::setLanguage(IndexedString language) { d_func_dynamic()->m_language = language; } const KDevelop::TopDUContext::IndexedRecursiveImports& ParsingEnvironmentFile::importsCache() const { return d_func()->m_importsCache; } void ParsingEnvironmentFile::setImportsCache(const KDevelop::TopDUContext::IndexedRecursiveImports& importsCache) { d_func_dynamic()->m_importsCache = importsCache; } } //KDevelop diff --git a/language/duchain/persistentsymboltable.cpp b/language/duchain/persistentsymboltable.cpp index 1afa5074a3..4cfe822f07 100644 --- a/language/duchain/persistentsymboltable.cpp +++ b/language/duchain/persistentsymboltable.cpp @@ -1,422 +1,422 @@ /* This file is part of KDevelop 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 "persistentsymboltable.h" #include #include #include "declaration.h" #include "declarationid.h" #include "appendedlist.h" #include "serialization/itemrepository.h" #include "identifier.h" #include "ducontext.h" #include "topducontext.h" #include "duchain.h" #include "duchainlock.h" #include //For now, just _always_ use the cache const uint MinimumCountForCache = 1; namespace { QDebug fromTextStream(const QTextStream& out) { if (out.device()) return {out.device()}; return {out.string()}; } } namespace KDevelop { Utils::BasicSetRepository* RecursiveImportCacheRepository::repository() { - static Utils::BasicSetRepository recursiveImportCacheRepositoryObject(QStringLiteral("Recursive Imports Cache"), 0, false); + static Utils::BasicSetRepository recursiveImportCacheRepositoryObject(QStringLiteral("Recursive Imports Cache"), nullptr, false); return &recursiveImportCacheRepositoryObject; } DEFINE_LIST_MEMBER_HASH(PersistentSymbolTableItem, declarations, IndexedDeclaration) class PersistentSymbolTableItem { public: PersistentSymbolTableItem() : centralFreeItem(-1) { initializeAppendedLists(); } PersistentSymbolTableItem(const PersistentSymbolTableItem& rhs, bool dynamic = true) : id(rhs.id), centralFreeItem(rhs.centralFreeItem) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~PersistentSymbolTableItem() { freeAppendedLists(); } inline unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return id.getIndex(); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(PersistentSymbolTableItem); } IndexedQualifiedIdentifier id; int centralFreeItem; START_APPENDED_LISTS(PersistentSymbolTableItem); APPENDED_LIST_FIRST(PersistentSymbolTableItem, IndexedDeclaration, declarations); END_APPENDED_LISTS(PersistentSymbolTableItem, declarations); }; class PersistentSymbolTableRequestItem { public: PersistentSymbolTableRequestItem(const PersistentSymbolTableItem& item) : m_item(item) { } enum { AverageSize = 30 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.itemSize(); } void createItem(PersistentSymbolTableItem* item) const { new (item) PersistentSymbolTableItem(m_item, false); } static void destroy(PersistentSymbolTableItem* item, KDevelop::AbstractItemRepository&) { item->~PersistentSymbolTableItem(); } static bool persistent(const PersistentSymbolTableItem*) { return true; //Nothing to do } bool equals(const PersistentSymbolTableItem* item) const { return m_item.id == item->id; } const PersistentSymbolTableItem& m_item; }; template struct CacheEntry { typedef KDevVarLengthArray Data; typedef QHash DataHash; DataHash m_hash; }; class PersistentSymbolTablePrivate { public: PersistentSymbolTablePrivate() : m_declarations(QStringLiteral("Persistent Declaration Table")) { } //Maps declaration-ids to declarations ItemRepository m_declarations; QHash > m_declarationsCache; //We cache the imports so the currently used nodes are very close in memory, which leads to much better CPU cache utilization QHash m_importsCache; }; void PersistentSymbolTable::clearCache() { ENSURE_CHAIN_WRITE_LOCKED { QMutexLocker lock(d->m_declarations.mutex()); d->m_importsCache.clear(); d->m_declarationsCache.clear(); } } PersistentSymbolTable::PersistentSymbolTable() : d(new PersistentSymbolTablePrivate()) { } PersistentSymbolTable::~PersistentSymbolTable() { //Workaround for a strange destruction-order related crash duing shutdown //We just let the data leak. This doesn't hurt, as there is no meaningful destructors. // delete d; } void PersistentSymbolTable::addDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration) { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_WRITE_LOCKED d->m_declarationsCache.remove(id); PersistentSymbolTableItem item; item.id = id; PersistentSymbolTableRequestItem request(item); uint index = d->m_declarations.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const PersistentSymbolTableItem* oldItem = d->m_declarations.itemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem); if(alg.indexOf(declaration) != -1) return; DynamicItem editableItem = d->m_declarations.dynamicItemFromIndex(index); EmbeddedTreeAddItem add(const_cast(editableItem->declarations()), editableItem->declarationsSize(), editableItem->centralFreeItem, declaration); uint newSize = add.newItemCount(); if(newSize != editableItem->declarationsSize()) { //We need to resize. Update and fill the new item, and delete the old item. item.declarationsList().resize(newSize); add.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem); d->m_declarations.deleteItem(index); Q_ASSERT(!d->m_declarations.findIndex(request)); }else{ //We're fine, the item could be added to the existing list return; } }else{ item.declarationsList().append(declaration); } //This inserts the changed item d->m_declarations.index(request); } void PersistentSymbolTable::removeDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration) { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_WRITE_LOCKED d->m_declarationsCache.remove(id); Q_ASSERT(!d->m_declarationsCache.contains(id)); PersistentSymbolTableItem item; item.id = id; PersistentSymbolTableRequestItem request(item); uint index = d->m_declarations.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const PersistentSymbolTableItem* oldItem = d->m_declarations.itemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem); if(alg.indexOf(declaration) == -1) return; DynamicItem editableItem = d->m_declarations.dynamicItemFromIndex(index); EmbeddedTreeRemoveItem remove(const_cast(editableItem->declarations()), editableItem->declarationsSize(), editableItem->centralFreeItem, declaration); uint newSize = remove.newItemCount(); if(newSize != editableItem->declarationsSize()) { //We need to resize. Update and fill the new item, and delete the old item. item.declarationsList().resize(newSize); remove.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem); d->m_declarations.deleteItem(index); Q_ASSERT(!d->m_declarations.findIndex(request)); }else{ //We're fine, the item could be added to the existing list return; } } //This inserts the changed item if(item.declarationsSize()) d->m_declarations.index(request); } struct DeclarationCacheVisitor { DeclarationCacheVisitor(KDevVarLengthArray& _cache) : cache(_cache) { } bool operator()(const IndexedDeclaration& decl) const { cache.append(decl); return true; } KDevVarLengthArray& cache; }; PersistentSymbolTable::FilteredDeclarationIterator PersistentSymbolTable::getFilteredDeclarations(const IndexedQualifiedIdentifier& id, const TopDUContext::IndexedRecursiveImports& visibility) const { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_READ_LOCKED Declarations decls = getDeclarations(id).iterator(); CachedIndexedRecursiveImports cachedImports; QHash::const_iterator it = d->m_importsCache.constFind(visibility); if(it != d->m_importsCache.constEnd()) { cachedImports = *it; }else{ cachedImports = CachedIndexedRecursiveImports(visibility.set().stdSet()); d->m_importsCache.insert(visibility, cachedImports); } if(decls.dataSize() > MinimumCountForCache) { //Do visibility caching CacheEntry& cached(d->m_declarationsCache[id]); CacheEntry::DataHash::const_iterator cacheIt = cached.m_hash.constFind(visibility); if(cacheIt != cached.m_hash.constEnd()) return FilteredDeclarationIterator(Declarations::Iterator(cacheIt->constData(), cacheIt->size(), -1), cachedImports); CacheEntry::DataHash::iterator insertIt = cached.m_hash.insert(visibility, KDevVarLengthArray()); KDevVarLengthArray& cache(*insertIt); { typedef ConvenientEmbeddedSetTreeFilterVisitor FilteredDeclarationCacheVisitor; //The visitor visits all the declarations from within its constructor DeclarationCacheVisitor v(cache); FilteredDeclarationCacheVisitor visitor(v, decls.iterator(), cachedImports); } return FilteredDeclarationIterator(Declarations::Iterator(cache.constData(), cache.size(), -1), cachedImports, true); }else{ return FilteredDeclarationIterator(decls.iterator(), cachedImports); } } PersistentSymbolTable::Declarations PersistentSymbolTable::getDeclarations(const IndexedQualifiedIdentifier& id) const { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_READ_LOCKED PersistentSymbolTableItem item; item.id = id; uint index = d->m_declarations.findIndex(item); if(index) { const PersistentSymbolTableItem* repositoryItem = d->m_declarations.itemFromIndex(index); return PersistentSymbolTable::Declarations(repositoryItem->declarations(), repositoryItem->declarationsSize(), repositoryItem->centralFreeItem); }else{ return PersistentSymbolTable::Declarations(); } } void PersistentSymbolTable::declarations(const IndexedQualifiedIdentifier& id, uint& countTarget, const IndexedDeclaration*& declarationsTarget) const { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_READ_LOCKED PersistentSymbolTableItem item; item.id = id; uint index = d->m_declarations.findIndex(item); if(index) { const PersistentSymbolTableItem* repositoryItem = d->m_declarations.itemFromIndex(index); countTarget = repositoryItem->declarationsSize(); declarationsTarget = repositoryItem->declarations(); }else{ countTarget = 0; - declarationsTarget = 0; + declarationsTarget = nullptr; } } struct DebugVisitor { DebugVisitor(const QTextStream& _out) : out(_out) { } bool operator() (const PersistentSymbolTableItem* item) { QDebug qout = fromTextStream(out); QualifiedIdentifier id(item->id.identifier()); if(identifiers.contains(id)) { qout << "identifier" << id.toString() << "appears for" << identifiers[id] << "th time"; } ++identifiers[id]; for(uint a = 0; a < item->declarationsSize(); ++a) { IndexedDeclaration decl(item->declarations()[a]); if(!decl.isDummy()) { if(declarations.contains(decl)) { qout << "declaration found for multiple identifiers. Previous identifier:" << declarations[decl].toString() << "current identifier:" << id.toString() << endl; }else{ declarations.insert(decl, id); } } if(decl.data() && decl.data()->qualifiedIdentifier() != item->id.identifier()) { qout << decl.data()->url().str() << "declaration" << decl.data()->qualifiedIdentifier() << "is registered as" << item->id.identifier() << endl; } const QString url = IndexedTopDUContext(decl.topContextIndex()).url().str(); if(!decl.data() && !decl.isDummy()) { qout << "Item in symbol-table is invalid:" << id.toString() << "- localIndex:" << decl.localIndex() << "- url:" << url << endl; } else { qout << "Item in symbol-table:" << id.toString() << "- localIndex:" << decl.localIndex() << "- url:" << url; if (auto d = decl.data()) { qout << "- range:" << d->range(); } else { qout << "- null declaration"; } qout << endl; } } return true; } const QTextStream& out; QHash identifiers; QHash declarations; }; void PersistentSymbolTable::dump(const QTextStream& out) { { QMutexLocker lock(d->m_declarations.mutex()); QDebug qout = fromTextStream(out); DebugVisitor v(out); d->m_declarations.visitAllItems(v); qout << "Statistics:" << endl; qout << d->m_declarations.statistics() << endl; } } PersistentSymbolTable& PersistentSymbolTable::self() { static PersistentSymbolTable ret; return ret; } } diff --git a/language/duchain/problem.cpp b/language/duchain/problem.cpp index bf7de7813a..6103154f10 100644 --- a/language/duchain/problem.cpp +++ b/language/duchain/problem.cpp @@ -1,277 +1,277 @@ /* This file is part of KDevelop Copyright 2007 Hamish Rodda 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 "problem.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "topducontext.h" #include "topducontextdata.h" #include "duchain.h" #include "duchainlock.h" #include #include namespace KDevelop { REGISTER_DUCHAIN_ITEM(Problem); DEFINE_LIST_MEMBER_HASH(ProblemData, diagnostics, LocalIndexedProblem) } using namespace KDevelop; LocalIndexedProblem::LocalIndexedProblem(const ProblemPointer& problem, const TopDUContext* top) : m_index(problem->m_indexInTopContext) { ENSURE_CHAIN_READ_LOCKED // ensure child problems are properly serialized before we serialize the parent problem // see below, the diagnostic size is kept in sync by the mutable API of Problem // the const cast is ugly but we don't really "change" the state as observed from the outside auto& serialized = const_cast(problem.data())->d_func_dynamic()->diagnosticsList(); serialized.clear(); foreach(const ProblemPointer& child, problem->m_diagnostics) { serialized << LocalIndexedProblem(child, top); } if (!m_index) { m_index = top->m_dynamicData->allocateProblemIndex(problem); } } ProblemPointer LocalIndexedProblem::data(const TopDUContext* top) const { if (!m_index) { return {}; } return top->m_dynamicData->getProblemForIndex(m_index); } Problem::Problem() : DUChainBase(*new ProblemData) , m_topContext(nullptr) , m_indexInTopContext(0) { d_func_dynamic()->setClassId(this); } Problem::Problem(ProblemData& data) : DUChainBase(data) , m_topContext(nullptr) , m_indexInTopContext(0) { } Problem::~Problem() { } TopDUContext* Problem::topContext() const { return m_topContext.data(); } IndexedString Problem::url() const { return d_func()->url; } DocumentRange Problem::finalLocation() const { return DocumentRange(d_func()->url, d_func()->m_range.castToSimpleRange()); } void Problem::setFinalLocation(const DocumentRange& location) { setRange(RangeInRevision::castFromSimpleRange(location)); d_func_dynamic()->url = location.document; } void Problem::clearDiagnostics() { m_diagnostics.clear(); // keep serialization in sync, see also LocalIndexedProblem ctor above d_func_dynamic()->diagnosticsList().clear(); } QVector Problem::diagnostics() const { QVector vector; foreach(ProblemPointer ptr, m_diagnostics) { vector.push_back(ptr); } return vector; } void Problem::setDiagnostics(const QVector &diagnostics) { clearDiagnostics(); foreach(const IProblem::Ptr& problem, diagnostics) { addDiagnostic(problem); } } void Problem::addDiagnostic(const IProblem::Ptr &diagnostic) { Problem *problem = dynamic_cast(diagnostic.data()); - Q_ASSERT(problem != NULL); + Q_ASSERT(problem != nullptr); ProblemPointer ptr(problem); m_diagnostics << ptr; } QString Problem::description() const { return d_func()->description.str(); } void Problem::setDescription(const QString& description) { d_func_dynamic()->description = IndexedString(description); } QString Problem::explanation() const { return d_func()->explanation.str(); } void Problem::setExplanation(const QString& explanation) { d_func_dynamic()->explanation = IndexedString(explanation); } IProblem::Source Problem::source() const { return d_func()->source; } void Problem::setSource(IProblem::Source source) { d_func_dynamic()->source = source; } QExplicitlySharedDataPointer Problem::solutionAssistant() const { return {}; } IProblem::Severity Problem::severity() const { return d_func()->severity; } void Problem::setSeverity(Severity severity) { d_func_dynamic()->severity = severity; } QString Problem::severityString() const { switch(severity()) { case IProblem::NoSeverity: return {}; case IProblem::Error: return i18n("Error"); case IProblem::Warning: return i18n("Warning"); case IProblem::Hint: return i18n("Hint"); } return QString(); } QString Problem::sourceString() const { switch (source()) { case IProblem::Disk: return i18n("Disk"); case IProblem::Preprocessor: return i18n("Preprocessor"); case IProblem::Lexer: return i18n("Lexer"); case IProblem::Parser: return i18n("Parser"); case IProblem::DUChainBuilder: return i18n("Definition-Use Chain"); case IProblem::SemanticAnalysis: return i18n("Semantic analysis"); case IProblem::ToDo: return i18n("To-do"); case IProblem::Unknown: default: return i18n("Unknown"); } } QString Problem::toString() const { return i18nc(": in :[]: (found by )", "%1: %2 in %3:[(%4,%5),(%6,%7)]: %8 (found by %9)" , severityString() , description() , url().str() , range().start.line , range().start.column , range().end.line , range().end.column , (explanation().isEmpty() ? i18n("") : explanation()) , sourceString()); } void Problem::rebuildDynamicData(DUContext* parent, uint ownIndex) { auto top = dynamic_cast(parent); Q_ASSERT(top); m_topContext = top; m_indexInTopContext = ownIndex; // deserialize child diagnostics here, as the top-context might get unloaded // but we still want to keep the child-diagnostics in-tact, as one would assume // a shared-ptr works. const auto data = d_func(); m_diagnostics.reserve(data->diagnosticsSize()); for (uint i = 0; i < data->diagnosticsSize(); ++i) { m_diagnostics << ProblemPointer(data->diagnostics()[i].data(top)); } DUChainBase::rebuildDynamicData(parent, ownIndex); } QDebug operator<<(QDebug s, const Problem& problem) { s.nospace() << problem.toString(); return s.space(); } QDebug operator<<(QDebug s, const ProblemPointer& problem) { if (!problem) { s.nospace() << ""; } else { s.nospace() << problem->toString(); } return s.space(); } diff --git a/language/duchain/specializationstore.cpp b/language/duchain/specializationstore.cpp index 69a74aecd8..4bdd938e56 100644 --- a/language/duchain/specializationstore.cpp +++ b/language/duchain/specializationstore.cpp @@ -1,125 +1,125 @@ /* This file is part of KDevelop 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 "specializationstore.h" #include "declarationid.h" #include "ducontext.h" #include "declaration.h" namespace KDevelop { SpecializationStore::SpecializationStore() { } SpecializationStore::~SpecializationStore() { } SpecializationStore& SpecializationStore::self() { static SpecializationStore store; return store; } void SpecializationStore::set(const DeclarationId& declaration, const IndexedInstantiationInformation& specialization) { Q_ASSERT(specialization.index() >> 16); m_specializations[declaration] = specialization; } IndexedInstantiationInformation SpecializationStore::get(const DeclarationId& declaration) { QHash::const_iterator it = m_specializations.constFind(declaration); if(it != m_specializations.constEnd()) return *it; else return IndexedInstantiationInformation(); } void SpecializationStore::clear(const DeclarationId& declaration) { QHash::iterator it = m_specializations.find(declaration); if(it != m_specializations.end()) m_specializations.erase(it); } void SpecializationStore::clear() { m_specializations.clear(); } Declaration* SpecializationStore::applySpecialization(Declaration* declaration, TopDUContext* source, bool recursive) { if(!declaration) - return 0; + return nullptr; IndexedInstantiationInformation specialization = get(declaration->id()); if(specialization.index()) return declaration->specialize(specialization, source); if(declaration->context() && recursive) { //Find a parent that has a specialization, and specialize this with the info and required depth int depth = 0; DUContext* ctx = declaration->context(); IndexedInstantiationInformation specialization; while(ctx && !specialization.index()) { if(ctx->owner()) specialization = get(ctx->owner()->id()); ++depth; ctx = ctx->parentContext(); } if(specialization.index()) return declaration->specialize(specialization, source, depth); } return declaration; } DUContext* SpecializationStore::applySpecialization(DUContext* context, TopDUContext* source, bool recursive) { if(!context) - return 0; + return nullptr; if(Declaration* declaration = context->owner()) return applySpecialization(declaration, source, recursive)->internalContext(); if(context->parentContext() && recursive) { //Find a parent that has a specialization, and specialize this with the info and required depth int depth = 0; DUContext* ctx = context->parentContext(); IndexedInstantiationInformation specialization; while(ctx && !specialization.index()) { if(ctx->owner()) specialization = get(ctx->owner()->id()); ++depth; ctx = ctx->parentContext(); } if(specialization.index()) return context->specialize(specialization, source, depth); } return context; } } diff --git a/language/duchain/topducontext.cpp b/language/duchain/topducontext.cpp index 87adc64913..97ee66edca 100644 --- a/language/duchain/topducontext.cpp +++ b/language/duchain/topducontext.cpp @@ -1,1167 +1,1167 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 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 "topducontext.h" #include "topducontextutils.h" #include #include #include "persistentsymboltable.h" #include "problem.h" #include "declaration.h" #include "duchain.h" #include "duchainlock.h" #include "parsingenvironment.h" #include "duchainpointer.h" #include "declarationid.h" #include "namespacealiasdeclaration.h" #include "aliasdeclaration.h" #include "uses.h" #include "topducontextdata.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "util/debug.h" // #define DEBUG_SEARCH const uint maxApplyAliasesRecursion = 100; namespace KDevelop { Utils::BasicSetRepository* RecursiveImportRepository::repository() { static Utils::BasicSetRepository recursiveImportRepositoryObject(QStringLiteral("Recursive Imports"), &KDevelop::globalItemRepositoryRegistry()); return &recursiveImportRepositoryObject; } ReferencedTopDUContext::ReferencedTopDUContext(TopDUContext* context) : m_topContext(context) { if(m_topContext) DUChain::self()->refCountUp(m_topContext); } ReferencedTopDUContext::ReferencedTopDUContext(const ReferencedTopDUContext& rhs) : m_topContext(rhs.m_topContext) { if(m_topContext) DUChain::self()->refCountUp(m_topContext); } ReferencedTopDUContext::~ReferencedTopDUContext() { if(m_topContext && !DUChain::deleted()) DUChain::self()->refCountDown(m_topContext); } ReferencedTopDUContext& ReferencedTopDUContext::operator=(const ReferencedTopDUContext& rhs) { if(m_topContext == rhs.m_topContext) return *this; if(m_topContext) DUChain::self()->refCountDown(m_topContext); m_topContext = rhs.m_topContext; if(m_topContext) DUChain::self()->refCountUp(m_topContext); return *this; } DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_usedDeclarationIds, DeclarationId) DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_problems, LocalIndexedProblem) REGISTER_DUCHAIN_ITEM(TopDUContext); template void removeFromVector(QVector& vec, const T& t) { for(int a =0; a < vec.size(); ++a) { if(vec[a] == t) { vec.remove(a); removeFromVector(vec, t); return; } } } QMutex importStructureMutex(QMutex::Recursive); //Contains data that is not shared among top-contexts that share their duchain entries class TopDUContextLocalPrivate { public: TopDUContextLocalPrivate (TopDUContext* ctxt, uint index) : m_ctxt(ctxt), m_ownIndex(index), m_inDuChain(false) { m_indexedRecursiveImports.insert(index); } ~TopDUContextLocalPrivate() { //Either we use some other contexts data and have no users, or we own the data and have users that share it. QMutexLocker lock(&importStructureMutex); foreach(const DUContext::Import& import, m_importedContexts) - if(DUChain::self()->isInMemory(import.topContextIndex()) && dynamic_cast(import.context(0))) - dynamic_cast(import.context(0))->m_local->m_directImporters.remove(m_ctxt); + if(DUChain::self()->isInMemory(import.topContextIndex()) && dynamic_cast(import.context(nullptr))) + dynamic_cast(import.context(nullptr))->m_local->m_directImporters.remove(m_ctxt); } ///@todo Make all this work consistently together with import-caching //After loading, should rebuild the links void rebuildDynamicImportStructure() { //Currently we do not store the whole data in TopDUContextLocalPrivate, so we reconstruct it from what was stored by DUContext. Q_ASSERT(m_importedContexts.isEmpty()); FOREACH_FUNCTION(const DUContext::Import& import, m_ctxt->d_func()->m_importedContexts) { if(DUChain::self()->isInMemory(import.topContextIndex())) { - Q_ASSERT(import.context(0)); - TopDUContext* top = import.context(0)->topContext(); + Q_ASSERT(import.context(nullptr)); + TopDUContext* top = import.context(nullptr)->topContext(); Q_ASSERT(top); addImportedContextRecursively(top, false, true); } } FOREACH_FUNCTION(const IndexedDUContext& importer, m_ctxt->d_func()->m_importers) { if(DUChain::self()->isInMemory(importer.topContextIndex())) { Q_ASSERT(importer.context()); TopDUContext* top = importer.context()->topContext(); Q_ASSERT(top); top->m_local->addImportedContextRecursively(m_ctxt, false, true); } } } //Index of this top-context within the duchain //Since the data of top-contexts can be shared among multiple, this can be used to add imports that are local to this top-context. QVector m_importedContexts; // mutable bool m_haveImportStructure : 1; TopDUContext* m_ctxt; QSet m_directImporters; ParsingEnvironmentFilePointer m_file; QExplicitlySharedDataPointer m_ast; uint m_ownIndex; bool m_inDuChain; void clearImportedContextsRecursively() { QMutexLocker lock(&importStructureMutex); // Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1); QSet > rebuild; foreach(const DUContext::Import &import, m_importedContexts) { - TopDUContext* top = dynamic_cast(import.context(0)); + TopDUContext* top = dynamic_cast(import.context(nullptr)); if(top) { top->m_local->m_directImporters.remove(m_ctxt); if(!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(top, top, 1, rebuild); QHash > b = top->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == top) removeImportedContextRecursion(top, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context } } } } m_importedContexts.clear(); rebuildImportStructureRecursion(rebuild); Q_ASSERT(m_recursiveImports.isEmpty()); // Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1); } //Adds the context to this and all contexts that import this, and manages m_recursiveImports void addImportedContextRecursively(TopDUContext* context, bool temporary, bool local) { QMutexLocker lock(&importStructureMutex); context->m_local->m_directImporters.insert(m_ctxt); if(local) m_importedContexts << DUContext::Import(context, m_ctxt); if(!m_ctxt->usingImportsCache()) { addImportedContextRecursion(context, context, 1, temporary); QHash > b = context->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) addImportedContextRecursion(context, it.key(), (*it).first+1, temporary); //Add contexts that were imported earlier into the given one } } //Removes the context from this and all contexts that import this, and manages m_recursiveImports void removeImportedContextRecursively(TopDUContext* context, bool local) { QMutexLocker lock(&importStructureMutex); context->m_local->m_directImporters.remove(m_ctxt); if(local) removeFromVector(m_importedContexts, DUContext::Import(context, m_ctxt)); QSet > rebuild; if(!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(context, context, 1, rebuild); QHash > b = context->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context) removeImportedContextRecursion(context, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context } } rebuildImportStructureRecursion(rebuild); } void removeImportedContextsRecursively(const QList& contexts, bool local) { QMutexLocker lock(&importStructureMutex); QSet > rebuild; foreach(TopDUContext* context, contexts) { context->m_local->m_directImporters.remove(m_ctxt); if(local) removeFromVector(m_importedContexts, DUContext::Import(context, m_ctxt)); if(!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(context, context, 1, rebuild); QHash > b = context->m_local->m_recursiveImports; for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context) removeImportedContextRecursion(context, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context } } } rebuildImportStructureRecursion(rebuild); } //Has an entry for every single recursively imported file, that contains the shortest path, and the next context on that path to the imported context. //This does not need to be stored to disk, because it is defined implicitly. //What makes this most complicated is the fact that loops are allowed in the import structure. typedef QHash > RecursiveImports; mutable RecursiveImports m_recursiveImports; mutable TopDUContext::IndexedRecursiveImports m_indexedRecursiveImports; private: void addImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int depth, bool temporary = false) { if(m_ctxt->usingImportsCache()) return; // if(!m_haveImportStructure) // return; if(imported == m_ctxt) return; const bool computeShortestPaths = false; ///@todo We do not compute the shortest path. Think what's right. // traceNext->m_local->needImportStructure(); // imported->m_local->needImportStructure(); RecursiveImports::iterator it = m_recursiveImports.find(imported); if(it == m_recursiveImports.end()) { //Insert new path to "imported" m_recursiveImports[imported] = qMakePair(depth, traceNext); m_indexedRecursiveImports.insert(imported->indexed()); // Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size()+1); Q_ASSERT(traceNext != m_ctxt); }else{ if(!computeShortestPaths) return; if(temporary) //For temporary imports, we don't record the best path. return; //It would be better if we would use the following code, but it creates too much cost in updateImportedContextRecursion when imports are removed again. //Check whether the new way to "imported" is shorter than the stored one if((*it).first > depth) { //Add a shorter path (*it).first = depth; Q_ASSERT(traceNext); (*it).second = traceNext; Q_ASSERT(traceNext == imported || (traceNext->m_local->m_recursiveImports.contains(imported) && traceNext->m_local->m_recursiveImports[imported].first < (*it).first)); }else{ //The imported context is already imported through a same/better path, so we can just stop processing. This saves us from endless recursion. return; } } if(temporary) return; for(QSet::const_iterator it = m_directImporters.constBegin(); it != m_directImporters.constEnd(); ++it) { TopDUContext* top = dynamic_cast(const_cast(*it)); //Avoid detaching, so use const_cast if(top) ///@todo also record this for local imports top->m_local->addImportedContextRecursion(m_ctxt, imported, depth+1); } } void removeImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int distance, QSet >& rebuild) { if(m_ctxt->usingImportsCache()) return; if(imported == m_ctxt) return; // if(!m_haveImportStructure) // return; RecursiveImports::iterator it = m_recursiveImports.find(imported); if(it == m_recursiveImports.end()) { //We don't import. Just return, this saves us from endless recursion. return; }else{ //Check whether we have imported "imported" through "traceNext". If not, return. Else find a new trace. if((*it).second == traceNext && (*it).first == distance) { //We need to remove the import through traceNext. Check whether there is another imported context that imports it. m_recursiveImports.erase(it); //In order to prevent problems, we completely remove everything, and re-add it. //Just updating these complex structures is very hard. Q_ASSERT(imported != m_ctxt); m_indexedRecursiveImports.remove(imported->indexed()); // Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size()); rebuild.insert(qMakePair(m_ctxt, imported)); //We MUST do this before finding another trace, because else we would create loops for(QSet::const_iterator childIt = m_directImporters.constBegin(); childIt != m_directImporters.constEnd(); ++childIt) { TopDUContext* top = dynamic_cast(const_cast(*childIt)); //Avoid detaching, so use const iterator if(top) top->m_local->removeImportedContextRecursion(m_ctxt, imported, distance+1, rebuild); //Don't use 'it' from here on, it may be invalid } } } } //Updates the trace to 'imported' void rebuildStructure(const TopDUContext* imported); void rebuildImportStructureRecursion(const QSet >& rebuild) { for(QSet >::const_iterator it = rebuild.constBegin(); it != rebuild.constEnd(); ++it) { //for(int a = rebuild.size()-1; a >= 0; --a) { //Find the best imported parent it->first->m_local->rebuildStructure(it->second); } } }; const TopDUContext::IndexedRecursiveImports& TopDUContext::recursiveImportIndices() const { // No lock-check for performance reasons QMutexLocker lock(&importStructureMutex); if(!d_func()->m_importsCache.isEmpty()) return d_func()->m_importsCache; return m_local->m_indexedRecursiveImports; } void TopDUContextData::updateImportCacheRecursion(uint baseIndex, IndexedTopDUContext currentContext, TopDUContext::IndexedRecursiveImports& visited) { if(visited.contains(currentContext.index())) return; Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called if(!currentContext.data()) { qCDebug(LANGUAGE) << "importing invalid context"; return; } visited.insert(currentContext.index()); const TopDUContextData* currentData = currentContext.data()->topContext()->d_func(); if(currentData->m_importsCache.contains(baseIndex) || currentData->m_importsCache.isEmpty()) { //If we have a loop or no imports-cache is used, we have to look at each import separately. const KDevelop::DUContext::Import* imports = currentData->m_importedContexts(); uint importsSize = currentData->m_importedContextsSize(); for(uint a = 0; a < importsSize; ++a) { IndexedTopDUContext next(imports[a].topContextIndex()); if(next.isValid()) updateImportCacheRecursion(baseIndex, next, visited); } }else{ //If we don't have a loop with baseIndex, we can safely just merge with the imported importscache visited += currentData->m_importsCache; } } void TopDUContextData::updateImportCacheRecursion(IndexedTopDUContext currentContext, std::set& visited) { if(visited.find(currentContext.index()) != visited.end()) return; Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called if(!currentContext.data()) { qCDebug(LANGUAGE) << "importing invalid context"; return; } visited.insert(currentContext.index()); const TopDUContextData* currentData = currentContext.data()->topContext()->d_func(); const KDevelop::DUContext::Import* imports = currentData->m_importedContexts(); uint importsSize = currentData->m_importedContextsSize(); for(uint a = 0; a < importsSize; ++a) { IndexedTopDUContext next(imports[a].topContextIndex()); if(next.isValid()) updateImportCacheRecursion(next, visited); } } void TopDUContext::updateImportsCache() { QMutexLocker lock(&importStructureMutex); const bool use_fully_recursive_import_cache_computation = false; if(use_fully_recursive_import_cache_computation) { std::set visited; TopDUContextData::updateImportCacheRecursion(this, visited); Q_ASSERT(visited.find(ownIndex()) != visited.end()); d_func_dynamic()->m_importsCache = IndexedRecursiveImports(visited); }else{ d_func_dynamic()->m_importsCache = IndexedRecursiveImports(); TopDUContextData::updateImportCacheRecursion(ownIndex(), this, d_func_dynamic()->m_importsCache); } Q_ASSERT(d_func_dynamic()->m_importsCache.contains(IndexedTopDUContext(this))); Q_ASSERT(usingImportsCache()); Q_ASSERT(imports(this, CursorInRevision::invalid())); if(parsingEnvironmentFile()) parsingEnvironmentFile()->setImportsCache(d_func()->m_importsCache); } bool TopDUContext::usingImportsCache() const { return !d_func()->m_importsCache.isEmpty(); } CursorInRevision TopDUContext::importPosition(const DUContext* target) const { ENSURE_CAN_READ DUCHAIN_D(DUContext); Import import(const_cast(target), const_cast(this), CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) if(d->m_importedContexts()[a] == import) return d->m_importedContexts()[a].position; return DUContext::importPosition(target); } void TopDUContextLocalPrivate::rebuildStructure(const TopDUContext* imported) { if(m_ctxt == imported) return; for(QVector::const_iterator parentIt = m_importedContexts.constBegin(); parentIt != m_importedContexts.constEnd(); ++parentIt) { - TopDUContext* top = dynamic_cast(const_cast(parentIt->context(0))); //To avoid detaching, use const iterator + TopDUContext* top = dynamic_cast(const_cast(parentIt->context(nullptr))); //To avoid detaching, use const iterator if(top) { // top->m_local->needImportStructure(); if(top == imported) { addImportedContextRecursion(top, imported, 1); }else{ RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported); if(it2 != top->m_local->m_recursiveImports.constEnd()) { addImportedContextRecursion(top, imported, (*it2).first + 1); } } } } for(unsigned int a = 0; a < m_ctxt->d_func()->m_importedContextsSize(); ++a) { - TopDUContext* top = dynamic_cast(const_cast(m_ctxt->d_func()->m_importedContexts()[a].context(0))); //To avoid detaching, use const iterator + TopDUContext* top = dynamic_cast(const_cast(m_ctxt->d_func()->m_importedContexts()[a].context(nullptr))); //To avoid detaching, use const iterator if(top) { // top->m_local->needImportStructure(); if(top == imported) { addImportedContextRecursion(top, imported, 1); }else{ RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported); if(it2 != top->m_local->m_recursiveImports.constEnd()) { addImportedContextRecursion(top, imported, (*it2).first + 1); } } } } } void TopDUContext::rebuildDynamicImportStructure() { m_local->rebuildDynamicImportStructure(); } void TopDUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) { - Q_ASSERT(parent == 0 && ownIndex != 0); + Q_ASSERT(parent == nullptr && ownIndex != 0); m_local->m_ownIndex = ownIndex; DUContext::rebuildDynamicData(parent, 0); } IndexedTopDUContext TopDUContext::indexed() const { return IndexedTopDUContext(m_local->m_ownIndex); } uint TopDUContext::ownIndex() const { return m_local->m_ownIndex; } TopDUContext::TopDUContext(TopDUContextData& data) : DUContext(data), m_local(new TopDUContextLocalPrivate(this, data.m_ownIndex)), m_dynamicData(new TopDUContextDynamicData(this)) { } TopDUContext::TopDUContext(const IndexedString& url, const RangeInRevision& range, ParsingEnvironmentFile* file) : DUContext(*new TopDUContextData(url), range) , m_local(new TopDUContextLocalPrivate(this, DUChain::newTopContextIndex())) , m_dynamicData(new TopDUContextDynamicData(this)) { Q_ASSERT(url.toUrl().isValid() && !url.toUrl().isRelative()); d_func_dynamic()->setClassId(this); setType(Global); DUCHAIN_D_DYNAMIC(TopDUContext); d->m_features = VisibleDeclarationsAndContexts; d->m_ownIndex = m_local->m_ownIndex; setParsingEnvironmentFile(file); setInSymbolTable(true); } QExplicitlySharedDataPointer TopDUContext::parsingEnvironmentFile() const { return m_local->m_file; } TopDUContext::~TopDUContext( ) { m_dynamicData->m_deleting = true; //Clear the AST, so that the 'feature satisfaction' cache is eventually updated clearAst(); if(!isOnDisk()) { //Clear the 'feature satisfaction' cache which is managed in ParsingEnvironmentFile setFeatures(Empty); clearUsedDeclarationIndices(); } deleteChildContextsRecursively(); deleteLocalDeclarations(); m_dynamicData->clear(); } void TopDUContext::deleteSelf() { //We've got to make sure that m_dynamicData and m_local are still valid while all the sub-contexts are destroyed TopDUContextLocalPrivate* local = m_local; TopDUContextDynamicData* dynamicData = m_dynamicData; m_dynamicData->m_deleting = true; delete this; delete local; delete dynamicData; } TopDUContext::Features TopDUContext::features() const { uint ret = d_func()->m_features; if(ast()) ret |= TopDUContext::AST; return (TopDUContext::Features)ret; } void TopDUContext::setFeatures(Features features) { features = (TopDUContext::Features)(features & (~Recursive)); //Remove the "Recursive" flag since that's only for searching features = (TopDUContext::Features)(features & (~ForceUpdateRecursive)); //Remove the update flags features = (TopDUContext::Features)(features & (~AST)); //Remove the AST flag, it's only used while updating d_func_dynamic()->m_features = features; //Replicate features to ParsingEnvironmentFile if(parsingEnvironmentFile()) parsingEnvironmentFile()->setFeatures(this->features()); } void TopDUContext::setAst(QExplicitlySharedDataPointer ast) { ENSURE_CAN_WRITE m_local->m_ast = ast; if(parsingEnvironmentFile()) parsingEnvironmentFile()->setFeatures(features()); } void TopDUContext::setParsingEnvironmentFile(ParsingEnvironmentFile* file) { if(m_local->m_file) //Clear the "feature satisfaction" cache m_local->m_file->setFeatures(Empty); //We do not enforce a duchain lock here, since this is also used while loading a top-context m_local->m_file = QExplicitlySharedDataPointer(file); //Replicate features to ParsingEnvironmentFile if(file) { file->setTopContext(IndexedTopDUContext(ownIndex())); Q_ASSERT(file->indexedTopContext().isValid()); file->setFeatures(d_func()->m_features); file->setImportsCache(d_func()->m_importsCache); } } struct TopDUContext::FindDeclarationsAcceptor { FindDeclarationsAcceptor(const TopDUContext* _top, DeclarationList& _target, const DeclarationChecker& _check, SearchFlags _flags) : top(_top), target(_target), check(_check) { flags = _flags; } bool operator() (const QualifiedIdentifier& id) { #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "accepting" << id.toString(); #endif PersistentSymbolTable::Declarations allDecls; //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter; //This is used if filtering is disabled PersistentSymbolTable::Declarations::Iterator unchecked; if(check.flags & DUContext::NoImportsCheck) { allDecls = PersistentSymbolTable::self().getDeclarations(id); unchecked = allDecls.iterator(); } else filter = PersistentSymbolTable::self().getFilteredDeclarations(id, top->recursiveImportIndices()); while(filter || unchecked) { IndexedDeclaration iDecl; if(filter) { iDecl = *filter; ++filter; } else { iDecl = *unchecked; ++unchecked; } Declaration* decl = iDecl.data(); if(!decl) continue; if(!check(decl)) continue; if( ! (flags & DontResolveAliases) && decl->kind() == Declaration::Alias ) { //Apply alias declarations AliasDeclaration* alias = static_cast(decl); if(alias->aliasedDeclaration().isValid()) { decl = alias->aliasedDeclaration().declaration(); } else { qCDebug(LANGUAGE) << "lost aliased declaration"; } } target.append(decl); } - check.createVisibleCache = 0; + check.createVisibleCache = nullptr; return !top->foundEnough(target, flags); } const TopDUContext* top; DeclarationList& target; const DeclarationChecker& check; QFlags< KDevelop::DUContext::SearchFlag > flags; }; bool TopDUContext::findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags, uint /*depth*/) const { ENSURE_CAN_READ #ifdef DEBUG_SEARCH for (const SearchItem::Ptr& idTree : identifiers) foreach(const QualifiedIdentifier &id, idTree->toList()) qCDebug(LANGUAGE) << "searching item" << id.toString(); #endif DeclarationChecker check(this, position, dataType, flags); FindDeclarationsAcceptor storer(this, ret, check, flags); ///The actual scopes are found within applyAliases, and each complete qualified identifier is given to FindDeclarationsAcceptor. ///That stores the found declaration to the output. applyAliases(identifiers, storer, position, false); return true; } //This is used to prevent endless recursion due to "using namespace .." declarations, by storing all imports that are already being used. struct TopDUContext::ApplyAliasesBuddyInfo { ApplyAliasesBuddyInfo(uint importChainType, ApplyAliasesBuddyInfo* predecessor, IndexedQualifiedIdentifier importId) : m_importChainType(importChainType), m_predecessor(predecessor), m_importId(importId) { if(m_predecessor && m_predecessor->m_importChainType != importChainType) - m_predecessor = 0; + m_predecessor = nullptr; } bool alreadyImporting(IndexedQualifiedIdentifier id) { ApplyAliasesBuddyInfo* current = this; while(current) { if(current->m_importId == id) return true; current = current->m_predecessor; } return false; } uint m_importChainType; ApplyAliasesBuddyInfo* m_predecessor; IndexedQualifiedIdentifier m_importId; }; ///@todo Implement a cache so at least the global import checks don't need to be done repeatedly. The cache should be thread-local, using DUChainPointer for the hashed items, and when an item was deleted, it should be discarded template bool TopDUContext::applyAliases( const QualifiedIdentifier& previous, const SearchItem::Ptr& identifier, Acceptor& accept, const CursorInRevision& position, bool canBeNamespace, ApplyAliasesBuddyInfo* buddy, uint recursionDepth ) const { if(recursionDepth > maxApplyAliasesRecursion) { QList searches = identifier->toList(); QualifiedIdentifier id; if(!searches.isEmpty()) id = searches.first(); qCDebug(LANGUAGE) << "maximum apply-aliases recursion reached while searching" << id; } bool foundAlias = false; QualifiedIdentifier id(previous); id.push(identifier->identifier); if(!id.inRepository()) return true; //If the qualified identifier is not in the identifier repository, it cannot be registered anywhere, so there's nothing we need to do if( !identifier->next.isEmpty() || canBeNamespace ) { //If it cannot be a namespace, the last part of the scope will be ignored //Search for namespace-aliases, by using globalAliasIdentifier, which is inserted into the symbol-table by NamespaceAliasDeclaration QualifiedIdentifier aliasId(id); aliasId.push(globalIndexedAliasIdentifier()); #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "checking" << id.toString(); #endif if(aliasId.inRepository()) { //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(aliasId, recursiveImportIndices()); if(filter) { - DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, 0); + DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, nullptr); //The first part of the identifier has been found as a namespace-alias. //In c++, we only need the first alias. However, just to be correct, follow them all for now. for(; filter; ++filter) { Declaration* aliasDecl = filter->data(); if(!aliasDecl) continue; if(!check(aliasDecl)) continue; if(aliasDecl->kind() != Declaration::NamespaceAlias) continue; if(foundAlias) break; Q_ASSERT(dynamic_cast(aliasDecl)); NamespaceAliasDeclaration* alias = static_cast(aliasDecl); foundAlias = true; QualifiedIdentifier importIdentifier = alias->importIdentifier(); if(importIdentifier.isEmpty()) { qCDebug(LANGUAGE) << "found empty import"; continue; } if(buddy && buddy->alreadyImporting( importIdentifier )) continue; //This import has already been applied to this search ApplyAliasesBuddyInfo info(1, buddy, importIdentifier); if(identifier->next.isEmpty()) { //Just insert the aliased namespace identifier if(!accept(importIdentifier)) return false; }else{ //Create an identifiers where namespace-alias part is replaced with the alias target foreach (const SearchItem::Ptr& item, identifier->next) if(!applyAliases(importIdentifier, item, accept, position, canBeNamespace, &info, recursionDepth+1)) return false; } } } } } if(!foundAlias) { //If we haven't found an alias, put the current versions into the result list. Additionally we will compute the identifiers transformed through "using". if(identifier->next.isEmpty()) { if(!accept(id)) //We're at the end of a qualified identifier, accept it return false; } else { foreach (const SearchItem::Ptr& next, identifier->next) - if(!applyAliases(id, next, accept, position, canBeNamespace, 0, recursionDepth+1)) + if(!applyAliases(id, next, accept, position, canBeNamespace, nullptr, recursionDepth+1)) return false; } } /*if( !prefix.explicitlyGlobal() || !prefix.isEmpty() ) {*/ ///@todo check iso c++ if using-directives should be respected on top-level when explicitly global ///@todo this is bad for a very big repository(the chains should be walked for the top-context instead) //Find all namespace-imports at given scope { QualifiedIdentifier importId(previous); importId.push(globalIndexedImportIdentifier()); #ifdef DEBUG_SEARCH // qCDebug(LANGUAGE) << "checking imports in" << (backPointer ? id.toString() : QStringLiteral("global")); #endif if(importId.inRepository()) { //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(importId, recursiveImportIndices()); if(filter) { - DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, 0); + DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, nullptr); for(; filter; ++filter) { Declaration* importDecl = filter->data(); if(!importDecl) continue; //We must never break or return from this loop, because else we might be creating a bad cache if(!check(importDecl)) continue; //Search for the identifier with the import-identifier prepended Q_ASSERT(dynamic_cast(importDecl)); NamespaceAliasDeclaration* alias = static_cast(importDecl); #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "found import of" << alias->importIdentifier().toString(); #endif QualifiedIdentifier importIdentifier = alias->importIdentifier(); if(importIdentifier.isEmpty()) { qCDebug(LANGUAGE) << "found empty import"; continue; } if(buddy && buddy->alreadyImporting( importIdentifier )) continue; //This import has already been applied to this search ApplyAliasesBuddyInfo info(2, buddy, importIdentifier); if(previous != importIdentifier) if(!applyAliases(importIdentifier, identifier, accept, importDecl->topContext() == this ? importDecl->range().start : position, canBeNamespace, &info, recursionDepth+1)) return false; } } } } return true; } template void TopDUContext::applyAliases( const SearchItem::PtrList& identifiers, Acceptor& acceptor, const CursorInRevision& position, bool canBeNamespace ) const { QualifiedIdentifier emptyId; for (const SearchItem::Ptr& item : identifiers) - applyAliases(emptyId, item, acceptor, position, canBeNamespace, 0, 0); + applyAliases(emptyId, item, acceptor, position, canBeNamespace, nullptr, 0); } TopDUContext * TopDUContext::topContext() const { return const_cast(this); } bool TopDUContext::deleting() const { return m_dynamicData->m_deleting; } QList TopDUContext::problems() const { ENSURE_CAN_READ const auto data = d_func(); QList ret; ret.reserve(data->m_problemsSize()); for (uint i = 0; i < data->m_problemsSize(); ++i) { ret << ProblemPointer(data->m_problems()[i].data(this)); } return ret; } void TopDUContext::setProblems(const QList& problems) { ENSURE_CAN_WRITE clearProblems(); for (const auto& problem : problems) { addProblem(problem); } } void TopDUContext::addProblem(const ProblemPointer& problem) { ENSURE_CAN_WRITE Q_ASSERT(problem); auto data = d_func_dynamic(); // store for indexing LocalIndexedProblem indexedProblem(problem, this); Q_ASSERT(indexedProblem.isValid()); data->m_problemsList().append(indexedProblem); Q_ASSERT(indexedProblem.data(this)); } void TopDUContext::clearProblems() { ENSURE_CAN_WRITE d_func_dynamic()->m_problemsList().clear(); m_dynamicData->clearProblems(); } QVector TopDUContext::importers() const { ENSURE_CAN_READ return QVector::fromList( m_local->m_directImporters.toList() ); } QList TopDUContext::loadedImporters() const { ENSURE_CAN_READ return m_local->m_directImporters.toList(); } QVector TopDUContext::importedParentContexts() const { ENSURE_CAN_READ return DUContext::importedParentContexts(); } bool TopDUContext::imports(const DUContext * origin, const CursorInRevision& position) const { return importsPrivate(origin, position); } bool TopDUContext::importsPrivate(const DUContext * origin, const CursorInRevision& position) const { Q_UNUSED(position); if( const TopDUContext* top = dynamic_cast(origin) ) { QMutexLocker lock(&importStructureMutex); bool ret = recursiveImportIndices().contains(IndexedTopDUContext(const_cast(top))); if(top == this) Q_ASSERT(ret); return ret; } else { //Cannot import a non top-context return false; } } void TopDUContext::clearImportedParentContexts() { if(usingImportsCache()) { d_func_dynamic()->m_importsCache = IndexedRecursiveImports(); d_func_dynamic()->m_importsCache.insert(IndexedTopDUContext(this)); } DUContext::clearImportedParentContexts(); m_local->clearImportedContextsRecursively(); Q_ASSERT(m_local->m_recursiveImports.count() == 0); Q_ASSERT(m_local->m_indexedRecursiveImports.count() == 1); Q_ASSERT(imports(this, CursorInRevision::invalid())); } void TopDUContext::addImportedParentContext(DUContext* context, const CursorInRevision& position, bool anonymous, bool temporary) { if(context == this) return; if(!dynamic_cast(context)) { //We cannot do this, because of the extended way we treat top-context imports. qCDebug(LANGUAGE) << "tried to import a non top-context into a top-context. This is not possible."; return; } //Always make the contexts anonymous, because we care about importers in TopDUContextLocalPrivate DUContext::addImportedParentContext(context, position, anonymous, temporary); m_local->addImportedContextRecursively(static_cast(context), temporary, true); } void TopDUContext::removeImportedParentContext(DUContext* context) { DUContext::removeImportedParentContext(context); m_local->removeImportedContextRecursively(static_cast(context), true); } void TopDUContext::addImportedParentContexts(const QList >& contexts, bool temporary) { typedef QPair Pair; foreach(const Pair pair, contexts) addImportedParentContext(pair.first, pair.second, false, temporary); } void TopDUContext::removeImportedParentContexts(const QList& contexts) { foreach(TopDUContext* context, contexts) DUContext::removeImportedParentContext(context); m_local->removeImportedContextsRecursively(contexts, true); } /// Returns true if this object is registered in the du-chain. If it is not, all sub-objects(context, declarations, etc.) bool TopDUContext::inDUChain() const { return m_local->m_inDuChain; } /// This flag is only used by DUChain, never change it from outside. void TopDUContext::setInDuChain(bool b) { m_local->m_inDuChain = b; } bool TopDUContext::isOnDisk() const { ///@todo Change this to releasingToDisk, and only enable it while saving a top-context to disk. return m_dynamicData->isOnDisk(); } void TopDUContext::clearUsedDeclarationIndices() { ENSURE_CAN_WRITE for(unsigned int a = 0; a < d_func()->m_usedDeclarationIdsSize(); ++a) DUChain::uses()->removeUse(d_func()->m_usedDeclarationIds()[a], this); d_func_dynamic()->m_usedDeclarationIdsList().clear(); } void TopDUContext::deleteUsesRecursively() { clearUsedDeclarationIndices(); KDevelop::DUContext::deleteUsesRecursively(); } Declaration* TopDUContext::usedDeclarationForIndex(unsigned int declarationIndex) const { ENSURE_CAN_READ if(declarationIndex & (1<<31)) { //We use the highest bit to mark direct indices into the local declarations declarationIndex &= ~(1<<31); //unset the highest bit return m_dynamicData->getDeclarationForIndex(declarationIndex); }else if(declarationIndex < d_func()->m_usedDeclarationIdsSize()) return d_func()->m_usedDeclarationIds()[declarationIndex].getDeclaration(this); else - return 0; + return nullptr; } int TopDUContext::indexForUsedDeclaration(Declaration* declaration, bool create) { if(create) { ENSURE_CAN_WRITE }else{ ENSURE_CAN_READ } if(!declaration) { return std::numeric_limits::max(); } if(declaration->topContext() == this && !declaration->inSymbolTable() && !m_dynamicData->isTemporaryDeclarationIndex(declaration->ownIndex())) { uint index = declaration->ownIndex(); Q_ASSERT(!(index & (1<<31))); return (int)(index | (1<<31)); //We don't put context-local declarations into the list, that's a waste. We just use the mark them with the highest bit. } // if the declaration can not be found from this top-context, we create a direct // reference by index, to ensure that the use can be resolved in // usedDeclarationForIndex bool useDirectId = !recursiveImportIndices().contains(declaration->topContext()); DeclarationId id(declaration->id(useDirectId)); int index = -1; uint size = d_func()->m_usedDeclarationIdsSize(); const DeclarationId* ids = d_func()->m_usedDeclarationIds(); ///@todo Make m_usedDeclarationIds sorted, and find the decl. using binary search for(unsigned int a = 0; a < size; ++a) if(ids[a] == id) { index = a; break; } if(index != -1) return index; if(!create) return std::numeric_limits::max(); d_func_dynamic()->m_usedDeclarationIdsList().append(id); if(declaration->topContext() != this) DUChain::uses()->addUse(id, this); return d_func()->m_usedDeclarationIdsSize()-1; } QList allUses(TopDUContext* context, Declaration* declaration, bool noEmptyRanges) { QList ret; int declarationIndex = context->indexForUsedDeclaration(declaration, false); if(declarationIndex == std::numeric_limits::max()) return ret; return allUses(context, declarationIndex, noEmptyRanges); } QExplicitlySharedDataPointer TopDUContext::ast() const { return m_local->m_ast; } void TopDUContext::clearAst() { - setAst(QExplicitlySharedDataPointer(0)); + setAst(QExplicitlySharedDataPointer(nullptr)); } IndexedString TopDUContext::url() const { return d_func()->m_url; } } diff --git a/language/duchain/topducontextdynamicdata.cpp b/language/duchain/topducontextdynamicdata.cpp index de254ff6ca..b3ce85416d 100644 --- a/language/duchain/topducontextdynamicdata.cpp +++ b/language/duchain/topducontextdynamicdata.cpp @@ -1,842 +1,842 @@ /* This is part of KDevelop Copyright 2014 Milian Wolff 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 "topducontextdynamicdata.h" #include #include #include #include #include "declaration.h" #include "declarationdata.h" #include "ducontext.h" #include "topducontext.h" #include "topducontextdata.h" #include "ducontextdata.h" #include "ducontextdynamicdata.h" #include "duchainregister.h" #include "serialization/itemrepository.h" #include "problem.h" #include "util/debug.h" //#define DEBUG_DATA_INFO //This might be problematic on some systems, because really many mmaps are created #define USE_MMAP using namespace KDevelop; namespace { /** * Serialize @p item into @p data and update @p totalDataOffset. * * If @p isSharedDataItem is true, then the item's internal data pointer is not updated * to point to the serialized data. Otherwise the dynamic data is deleted and the items * data will point to the constant serialized data. * * NOTE: The above is required to support serialization of shared-data such as from ProblemPointer. * If we'd set the data to point to the constant region, we'd get crashes due to use-after-free when * we unmap the data and a shared pointer outlives that. */ void saveDUChainItem(QVector& data, DUChainBase& item, uint& totalDataOffset, bool isSharedDataItem) { if(!item.d_func()->classId) { //If this triggers, you have probably created an own DUChainBase based class, but haven't called setClassId(this) in the constructor. qCritical() << "no class-id set for data attached to a declaration of type" << typeid(item).name(); Q_ASSERT(0); } int size = DUChainItemSystem::self().dynamicSize(*item.d_func()); if(data.back().array.size() - int(data.back().position) < size) //Create a new data item data.append({QByteArray(size > 10000 ? size : 10000, 0), 0u}); uint pos = data.back().position; data.back().position += size; totalDataOffset += size; DUChainBaseData& target(*(reinterpret_cast(data.back().array.data() + pos))); if(item.d_func()->isDynamic()) { //Change from dynamic data to constant data enableDUChainReferenceCounting(data.back().array.data(), data.back().array.size()); DUChainItemSystem::self().copy(*item.d_func(), target, true); Q_ASSERT(!target.isDynamic()); if (!isSharedDataItem) { item.setData(&target); } disableDUChainReferenceCounting(data.back().array.data()); }else{ //Just copy the data into another place, expensive copy constructors are not needed memcpy(&target, item.d_func(), size); if (!isSharedDataItem) { item.setData(&target, false); } } if (!isSharedDataItem) { Q_ASSERT(item.d_func() == &target); Q_ASSERT(!item.d_func()->isDynamic()); } } uint indexForParentContext(DUContext* context) { return LocalIndexedDUContext(context->parentContext()).localIndex(); } uint indexForParentContext(Declaration* declaration) { return LocalIndexedDUContext(declaration->context()).localIndex(); } uint indexForParentContext(const ProblemPointer& /*problem*/) { // always stored in the top context return 0; } #ifndef QT_NO_DEBUG void validateItem(const DUChainBaseData* const data, const uchar* const mappedData, const size_t mappedDataSize) { Q_ASSERT(!data->isDynamic()); if (mappedData) { Q_ASSERT(((size_t)data) < ((size_t)mappedData) || ((size_t)data) > ((size_t)mappedData) + mappedDataSize); } } #endif const char* pointerInData(const QVector& data, uint totalOffset) { for(int a = 0; a < data.size(); ++a) { if(totalOffset < data[a].position) return data[a].array.constData() + totalOffset; totalOffset -= data[a].position; } Q_ASSERT_X(false, Q_FUNC_INFO, "Offset doesn't exist in the data."); - return 0; + return nullptr; } void verifyDataInfo(const TopDUContextDynamicData::ItemDataInfo& info, const QVector& data) { Q_UNUSED(info); Q_UNUSED(data); #ifdef DEBUG_DATA_INFO DUChainBaseData* item = (DUChainBaseData*)(pointerInData(data, info.dataOffset)); int size = DUChainItemSystem::self().dynamicSize(*item); Q_ASSERT(size); #endif } QString basePath() { return globalItemRepositoryRegistry().path() + "/topcontexts/"; } QString pathForTopContext(const uint topContextIndex) { return basePath() + QString::number(topContextIndex); } enum LoadType { PartialLoad, ///< Only load the direct member data FullLoad ///< Load everything, including appended lists }; template void loadTopDUContextData(const uint topContextIndex, LoadType loadType, F callback) { QFile file(pathForTopContext(topContextIndex)); if (!file.open(QIODevice::ReadOnly)) { return; } uint readValue; file.read((char*)&readValue, sizeof(uint)); // now readValue is filled with the top-context data size Q_ASSERT(readValue >= sizeof(TopDUContextData)); const QByteArray data = file.read(loadType == FullLoad ? readValue : sizeof(TopDUContextData)); const TopDUContextData* topData = reinterpret_cast(data.constData()); callback(topData); } template struct PtrType; template struct PtrType { using value = T*; }; template struct PtrType> { using value = T*; }; template Q_DECL_CONSTEXPR bool isSharedDataItem() { return false; } template<> Q_DECL_CONSTEXPR bool isSharedDataItem() { return true; } } //BEGIN DUChainItemStorage template TopDUContextDynamicData::DUChainItemStorage::DUChainItemStorage(TopDUContextDynamicData* data) : data(data) { } template TopDUContextDynamicData::DUChainItemStorage::~DUChainItemStorage() { clearItems(); } template void TopDUContextDynamicData::DUChainItemStorage::clearItems() { //Due to template specialization it's possible that a declaration is not reachable through the normal context structure. //For that reason we have to check here, and delete all remaining declarations. qDeleteAll(temporaryItems); temporaryItems.clear(); qDeleteAll(items); items.clear(); } namespace KDevelop { template<> void TopDUContextDynamicData::DUChainItemStorage::clearItems() { // don't delete anything - the problem is shared items.clear(); } } template void TopDUContextDynamicData::DUChainItemStorage::clearItemIndex(const Item& item, const uint index) { if(!data->m_dataLoaded) data->loadData(); if (index < (0x0fffffff/2)) { if (index == 0 || index > uint(items.size())) { return; } else { const uint realIndex = index - 1; Q_ASSERT(items[realIndex] == item); items[realIndex] = nullptr; if (realIndex < (uint)offsets.size()) { offsets[realIndex] = ItemDataInfo(); } } } else { const uint realIndex = 0x0fffffff - index; //We always keep the highest bit at zero if (realIndex == 0 || realIndex > uint(temporaryItems.size())) { return; } else { Q_ASSERT(temporaryItems[realIndex-1] == item); temporaryItems[realIndex-1] = nullptr; } } Q_UNUSED(item); } template void TopDUContextDynamicData::DUChainItemStorage::storeData(uint& currentDataOffset, const QVector& oldData) { auto const oldOffsets = offsets; offsets.clear(); for (int a = 0; a < items.size(); ++a) { auto item = items[a]; if (!item) { if (oldOffsets.size() > a && oldOffsets[a].dataOffset) { //Directly copy the old data range into the new data const DUChainBaseData* itemData = nullptr; if (data->m_mappedData) { itemData = reinterpret_cast(data->m_mappedData + oldOffsets[a].dataOffset); } else { itemData = reinterpret_cast(::pointerInData(oldData, oldOffsets[a].dataOffset)); } offsets << data->writeDataInfo(oldOffsets[a], itemData, currentDataOffset); } else { offsets << ItemDataInfo(); } } else { offsets << ItemDataInfo{currentDataOffset, indexForParentContext(item)}; saveDUChainItem(data->m_data, *item, currentDataOffset, isSharedDataItem()); } } #ifndef QT_NO_DEBUG if (!isSharedDataItem()) { for (auto item : items) { if (item) { validateItem(item->d_func(), data->m_mappedData, data->m_mappedDataSize); } } } #endif } template bool TopDUContextDynamicData::DUChainItemStorage::itemsHaveChanged() const { for (auto item : items) { if (item && item->d_func()->m_dynamic) { return true; } } return false; } template uint TopDUContextDynamicData::DUChainItemStorage::allocateItemIndex(const Item& item, const bool temporary) { if (!data->m_dataLoaded) { data->loadData(); } if (!temporary) { items.append(item); return items.size(); } else { temporaryItems.append(item); return 0x0fffffff - temporaryItems.size(); //We always keep the highest bit at zero } } template bool TopDUContextDynamicData::DUChainItemStorage::isItemForIndexLoaded(uint index) const { if (!data->m_dataLoaded) { return false; } if (index < (0x0fffffff/2)) { if (index == 0 || index > uint(items.size())) { return false; } return items[index-1]; } else { // temporary item return true; } } template Item TopDUContextDynamicData::DUChainItemStorage::getItemForIndex(uint index) const { if (index >= (0x0fffffff/2)) { index = 0x0fffffff - index; //We always keep the highest bit at zero if(index == 0 || index > uint(temporaryItems.size())) return {}; else return temporaryItems.at(index-1); } if (index == 0 || index > static_cast(items.size())) { qCWarning(LANGUAGE) << "item index out of bounds:" << index << "count:" << items.size(); return {}; } const uint realIndex = index - 1;; const auto& item = items.at(realIndex); if (item) { //Shortcut, because this is the most common case return item; } if (realIndex < (uint)offsets.size() && offsets[realIndex].dataOffset) { Q_ASSERT(!data->m_itemRetrievalForbidden); //Construct the context, and eventuall its parent first ///TODO: ugly, remove need for const_cast auto itemData = const_cast( reinterpret_cast(data->pointerInData(offsets[realIndex].dataOffset)) ); auto& item = items[realIndex]; item = dynamic_cast::value>(DUChainItemSystem::self().create(itemData)); if (!item) { //When this happens, the item has not been registered correctly. //We can stop here, because else we will get crashes later. qCritical() << "Failed to load item with identity" << itemData->classId; return {}; } if (isSharedDataItem()) { // NOTE: shared data must never point to mmapped data regions as otherwise we might end up with // use-after-free or double-deletions etc. pp. // thus, make the item always dynamic after deserialization item->makeDynamic(); } auto parent = data->getContextForIndex(offsets[realIndex].parentContext); Q_ASSERT_X(parent, Q_FUNC_INFO, "Could not find parent context for loaded item.\n" "Potentially, the context has been deleted without deleting its children."); item->rebuildDynamicData(parent, index); } else { qCWarning(LANGUAGE) << "invalid item for index" << index << offsets.size() << offsets.value(realIndex).dataOffset; } return item; } template void TopDUContextDynamicData::DUChainItemStorage::deleteOnDisk() { for (auto& item : items) { if (item) { item->makeDynamic(); } } } template void TopDUContextDynamicData::DUChainItemStorage::loadData(QFile* file) const { Q_ASSERT(offsets.isEmpty()); Q_ASSERT(items.isEmpty()); uint readValue; file->read((char*)&readValue, sizeof(uint)); offsets.resize(readValue); file->read((char*)offsets.data(), sizeof(ItemDataInfo) * offsets.size()); //Fill with zeroes for now, will be initialized on-demand items.resize(offsets.size()); } template void TopDUContextDynamicData::DUChainItemStorage::writeData(QFile* file) { uint writeValue = offsets.size(); file->write((char*)&writeValue, sizeof(uint)); file->write((char*)offsets.data(), sizeof(ItemDataInfo) * offsets.size()); } //END DUChainItemStorage const char* TopDUContextDynamicData::pointerInData(uint totalOffset) const { Q_ASSERT(!m_mappedData || m_data.isEmpty()); if(m_mappedData && m_mappedDataSize) return (char*)m_mappedData + totalOffset; return ::pointerInData(m_data, totalOffset); } TopDUContextDynamicData::TopDUContextDynamicData(TopDUContext* topContext) : m_deleting(false) , m_topContext(topContext) , m_contexts(this) , m_declarations(this) , m_problems(this) , m_onDisk(false) , m_dataLoaded(true) - , m_mappedFile(0) - , m_mappedData(0) + , m_mappedFile(nullptr) + , m_mappedData(nullptr) , m_mappedDataSize(0) , m_itemRetrievalForbidden(false) { } void KDevelop::TopDUContextDynamicData::clear() { m_contexts.clearItems(); m_declarations.clearItems(); m_problems.clearItems(); } TopDUContextDynamicData::~TopDUContextDynamicData() { unmap(); } void KDevelop::TopDUContextDynamicData::unmap() { delete m_mappedFile; - m_mappedFile = 0; - m_mappedData = 0; + m_mappedFile = nullptr; + m_mappedData = nullptr; m_mappedDataSize = 0; } bool TopDUContextDynamicData::fileExists(uint topContextIndex) { return QFile::exists(pathForTopContext(topContextIndex)); } QList TopDUContextDynamicData::loadImporters(uint topContextIndex) { QList ret; loadTopDUContextData(topContextIndex, FullLoad, [&ret] (const TopDUContextData* topData) { ret.reserve(topData->m_importersSize()); FOREACH_FUNCTION(const IndexedDUContext& importer, topData->m_importers) ret << importer; }); return ret; } QList TopDUContextDynamicData::loadImports(uint topContextIndex) { QList ret; loadTopDUContextData(topContextIndex, FullLoad, [&ret] (const TopDUContextData* topData) { ret.reserve(topData->m_importedContextsSize()); FOREACH_FUNCTION(const DUContext::Import& import, topData->m_importedContexts) ret << import.indexedContext(); }); return ret; } IndexedString TopDUContextDynamicData::loadUrl(uint topContextIndex) { IndexedString url; loadTopDUContextData(topContextIndex, PartialLoad, [&url] (const TopDUContextData* topData) { Q_ASSERT(topData->m_url.isEmpty() || topData->m_url.index() >> 16); url = topData->m_url; }); return url; } void TopDUContextDynamicData::loadData() const { //This function has to be protected by an additional mutex, since it can be triggered from multiple threads at the same time static QMutex mutex; QMutexLocker lock(&mutex); if(m_dataLoaded) return; Q_ASSERT(!m_dataLoaded); Q_ASSERT(m_data.isEmpty()); QFile* file = new QFile(pathForTopContext(m_topContext->ownIndex())); bool open = file->open(QIODevice::ReadOnly); Q_UNUSED(open); Q_ASSERT(open); Q_ASSERT(file->size()); //Skip the offsets, we're already read them //Skip top-context data uint readValue; file->read((char*)&readValue, sizeof(uint)); file->seek(readValue + file->pos()); m_contexts.loadData(file); m_declarations.loadData(file); m_problems.loadData(file); #ifdef USE_MMAP m_mappedData = file->map(file->pos(), file->size() - file->pos()); if(m_mappedData) { m_mappedFile = file; m_mappedDataSize = file->size() - file->pos(); file->close(); //Close the file, so there is less open file descriptors(May be problematic) }else{ qCDebug(LANGUAGE) << "Failed to map" << file->fileName(); } #endif if(!m_mappedFile) { QByteArray data = file->readAll(); m_data.append({data, (uint)data.size()}); delete file; } m_dataLoaded = true; } TopDUContext* TopDUContextDynamicData::load(uint topContextIndex) { QFile file(pathForTopContext(topContextIndex)); if(file.open(QIODevice::ReadOnly)) { if(file.size() == 0) { qCWarning(LANGUAGE) << "Top-context file is empty" << file.fileName(); - return 0; + return nullptr; } QVector contextDataOffsets; QVector declarationDataOffsets; uint readValue; file.read((char*)&readValue, sizeof(uint)); //now readValue is filled with the top-context data size QByteArray topContextData = file.read(readValue); DUChainBaseData* topData = reinterpret_cast(topContextData.data()); TopDUContext* ret = dynamic_cast(DUChainItemSystem::self().create(topData)); if(!ret) { qCWarning(LANGUAGE) << "Cannot load a top-context from file" << file.fileName() << "- the required language-support for handling ID" << topData->classId << "is probably not loaded"; - return 0; + return nullptr; } TopDUContextDynamicData& target(*ret->m_dynamicData); target.m_data.clear(); target.m_dataLoaded = false; target.m_onDisk = true; - ret->rebuildDynamicData(0, topContextIndex); + ret->rebuildDynamicData(nullptr, topContextIndex); target.m_topContextData.append({topContextData, (uint)0}); return ret; }else{ - return 0; + return nullptr; } } bool TopDUContextDynamicData::isOnDisk() const { return m_onDisk; } void TopDUContextDynamicData::deleteOnDisk() { if(!isOnDisk()) return; qCDebug(LANGUAGE) << "deleting" << m_topContext->ownIndex() << m_topContext->url().str(); if(!m_dataLoaded) loadData(); m_contexts.deleteOnDisk(); m_declarations.deleteOnDisk(); m_problems.deleteOnDisk(); m_topContext->makeDynamic(); m_onDisk = false; bool successfullyRemoved = QFile::remove(filePath()); Q_UNUSED(successfullyRemoved); Q_ASSERT(successfullyRemoved); qCDebug(LANGUAGE) << "deletion ready"; } QString KDevelop::TopDUContextDynamicData::filePath() const { return pathForTopContext(m_topContext->ownIndex()); } bool TopDUContextDynamicData::hasChanged() const { return !m_onDisk || m_topContext->d_func()->m_dynamic || m_contexts.itemsHaveChanged() || m_declarations.itemsHaveChanged() || m_problems.itemsHaveChanged(); } void TopDUContextDynamicData::store() { // qCDebug(LANGUAGE) << "storing" << m_topContext->url().str() << m_topContext->ownIndex() << "import-count:" << m_topContext->importedParentContexts().size(); //Check if something has changed. If nothing has changed, don't store to disk. bool contentDataChanged = hasChanged(); if (!contentDataChanged) { return; } ///@todo Save the meta-data into a repository, and only the actual content data into a file. /// This will make saving+loading more efficient, and will reduce the disk-usage. /// Then we also won't need to load the data if only the meta-data changed. if(!m_dataLoaded) loadData(); ///If the data is mapped, and we re-write the file, we must make sure that the data is copied out of the map, ///even if only metadata is changed. ///@todo If we split up data and metadata, we don't need to do this if(m_mappedData) contentDataChanged = true; m_topContext->makeDynamic(); m_topContextData.clear(); Q_ASSERT(m_topContext->d_func()->m_ownIndex == m_topContext->ownIndex()); uint topContextDataSize = DUChainItemSystem::self().dynamicSize(*m_topContext->d_func()); m_topContextData.append({QByteArray(DUChainItemSystem::self().dynamicSize(*m_topContext->d_func()), topContextDataSize), 0u}); uint actualTopContextDataSize = 0; if (contentDataChanged) { //We don't need these structures any more, since we have loaded all the declarations/contexts, and m_data //will be reset which these structures pointed into //Load all lazy declarations/contexts const auto oldData = m_data; //Keep the old data alive until everything is stored into a new data structure m_data.clear(); uint newDataSize = 0; foreach(const ArrayWithPosition &array, oldData) newDataSize += array.position; newDataSize = std::max(newDataSize, 10000u); //We always put 1 byte to the front, so we don't have zero data-offsets, since those are used for "invalid". uint currentDataOffset = 1; m_data.append({QByteArray(newDataSize, 0), currentDataOffset}); m_itemRetrievalForbidden = true; m_contexts.storeData(currentDataOffset, oldData); m_declarations.storeData(currentDataOffset, oldData); m_problems.storeData(currentDataOffset, oldData); m_itemRetrievalForbidden = false; } saveDUChainItem(m_topContextData, *m_topContext, actualTopContextDataSize, false); Q_ASSERT(actualTopContextDataSize == topContextDataSize); Q_ASSERT(m_topContextData.size() == 1); Q_ASSERT(!m_topContext->d_func()->isDynamic()); unmap(); QDir().mkpath(basePath()); QFile file(filePath()); if(file.open(QIODevice::WriteOnly)) { file.resize(0); file.write((char*)&topContextDataSize, sizeof(uint)); foreach(const ArrayWithPosition& pos, m_topContextData) file.write(pos.array.constData(), pos.position); m_contexts.writeData(&file); m_declarations.writeData(&file); m_problems.writeData(&file); foreach(const ArrayWithPosition& pos, m_data) file.write(pos.array.constData(), pos.position); m_onDisk = true; if (file.size() == 0) { qCWarning(LANGUAGE) << "Saving zero size top ducontext data"; } file.close(); } else { qCWarning(LANGUAGE) << "Cannot open top-context for writing"; } // qCDebug(LANGUAGE) << "stored" << m_topContext->url().str() << m_topContext->ownIndex() << "import-count:" << m_topContext->importedParentContexts().size(); } TopDUContextDynamicData::ItemDataInfo TopDUContextDynamicData::writeDataInfo(const ItemDataInfo& info, const DUChainBaseData* data, uint& totalDataOffset) { ItemDataInfo ret(info); Q_ASSERT(info.dataOffset); const auto size = DUChainItemSystem::self().dynamicSize(*data); Q_ASSERT(size); if(m_data.back().array.size() - m_data.back().position < size) { //Create a new m_data item m_data.append({QByteArray(std::max(size, 10000u), 0), 0u}); } ret.dataOffset = totalDataOffset; uint pos = m_data.back().position; m_data.back().position += size; totalDataOffset += size; auto target = reinterpret_cast(m_data.back().array.data() + pos); memcpy(target, data, size); verifyDataInfo(ret, m_data); return ret; } uint TopDUContextDynamicData::allocateDeclarationIndex(Declaration* decl, bool temporary) { return m_declarations.allocateItemIndex(decl, temporary); } uint TopDUContextDynamicData::allocateContextIndex(DUContext* context, bool temporary) { return m_contexts.allocateItemIndex(context, temporary); } uint TopDUContextDynamicData::allocateProblemIndex(ProblemPointer problem) { return m_problems.allocateItemIndex(problem, false); } bool TopDUContextDynamicData::isDeclarationForIndexLoaded(uint index) const { return m_declarations.isItemForIndexLoaded(index); } bool TopDUContextDynamicData::isContextForIndexLoaded(uint index) const { return m_contexts.isItemForIndexLoaded(index); } bool TopDUContextDynamicData::isTemporaryContextIndex(uint index) const { return !(index < (0x0fffffff/2)); } bool TopDUContextDynamicData::isTemporaryDeclarationIndex(uint index) const { return !(index < (0x0fffffff/2)); } DUContext* TopDUContextDynamicData::getContextForIndex(uint index) const { if(!m_dataLoaded) loadData(); if (index == 0) { return m_topContext; } return m_contexts.getItemForIndex(index); } Declaration* TopDUContextDynamicData::getDeclarationForIndex(uint index) const { if(!m_dataLoaded) loadData(); return m_declarations.getItemForIndex(index); } ProblemPointer TopDUContextDynamicData::getProblemForIndex(uint index) const { if(!m_dataLoaded) loadData(); return m_problems.getItemForIndex(index); } void TopDUContextDynamicData::clearDeclarationIndex(Declaration* decl) { m_declarations.clearItemIndex(decl, decl->m_indexInTopContext); } void TopDUContextDynamicData::clearContextIndex(DUContext* context) { m_contexts.clearItemIndex(context, context->m_dynamicData->m_indexInTopContext); } void TopDUContextDynamicData::clearProblems() { m_problems.clearItems(); } diff --git a/language/duchain/types/identifiedtype.cpp b/language/duchain/types/identifiedtype.cpp index 6c51e19a4a..3281066748 100644 --- a/language/duchain/types/identifiedtype.cpp +++ b/language/duchain/types/identifiedtype.cpp @@ -1,96 +1,96 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat Copyright 2006 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "identifiedtype.h" #include "../declaration.h" #include "../duchainpointer.h" #include "../declarationid.h" #include "util/debug.h" namespace KDevelop { IdentifiedType::~IdentifiedType() { } void IdentifiedType::clear() { idData()->m_id = DeclarationId(); } bool IdentifiedType::equals(const IdentifiedType* rhs) const { bool ret = false; if( idData()->m_id == rhs->idData()->m_id ) ret = true; //qCDebug(LANGUAGE) << this << rhs << true; return ret; } // QualifiedIdentifier IdentifiedType::identifier() const // { // return idData()->m_id ? idData()->m_iidData()->qualifiedIdentifier() : QualifiedIdentifier(); // } QualifiedIdentifier IdentifiedType::qualifiedIdentifier() const { return idData()->m_id.qualifiedIdentifier(); } uint IdentifiedType::hash() const { return idData()->m_id.hash(); } DeclarationId IdentifiedType::declarationId() const { return idData()->m_id; } void IdentifiedType::setDeclarationId(const DeclarationId& id) { idData()->m_id = id; } Declaration* IdentifiedType::declaration(const TopDUContext* top) const { return idData()->m_id.getDeclaration(top); } KDevelop::DUContext* IdentifiedType::internalContext(const KDevelop::TopDUContext* top) const { Declaration* decl = declaration(top); if(decl) return decl->internalContext(); else - return 0; + return nullptr; } void IdentifiedType::setDeclaration(Declaration* declaration) { if(declaration) idData()->m_id = declaration->id(); else idData()->m_id = DeclarationId(); } // QString IdentifiedType::idMangled() const // { // return identifier().mangled(); // } } diff --git a/language/duchain/types/typeregister.cpp b/language/duchain/types/typeregister.cpp index 411737ac0e..3c68572433 100644 --- a/language/duchain/types/typeregister.cpp +++ b/language/duchain/types/typeregister.cpp @@ -1,87 +1,87 @@ /* This file is part of KDevelop 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 "typeregister.h" namespace KDevelop { AbstractType* TypeSystem::create(AbstractTypeData* data) const { if (!isFactoryLoaded(*data)) { - return 0; + return nullptr; } return m_factories.value(data->typeClassId)->create(data); } void TypeSystem::callDestructor(AbstractTypeData* data) const { if (!isFactoryLoaded(*data)) { return; } return m_factories.value(data->typeClassId)->callDestructor(data); } uint TypeSystem::dynamicSize(const AbstractTypeData& data) const { if (!isFactoryLoaded(data)) { return 0; } return m_factories.value(data.typeClassId)->dynamicSize(data); } uint TypeSystem::dataClassSize(const AbstractTypeData& data) const { Q_ASSERT(m_dataClassSizes.contains(data.typeClassId)); return m_dataClassSizes.value(data.typeClassId); } bool TypeSystem::isFactoryLoaded(const AbstractTypeData& data) const { return m_factories.contains(data.typeClassId); } void TypeSystem::copy(const AbstractTypeData& from, AbstractTypeData& to, bool constant) const { //Shouldn't try to copy an unknown type Q_ASSERT(isFactoryLoaded(from)); return m_factories.value(from.typeClassId)->copy(from, to, constant); } TypeSystem& TypeSystem::self() { static TypeSystem system; return system; } void TypeSystem::registerTypeClassInternal(AbstractTypeFactory* repo, uint dataClassSize, uint identity) { Q_ASSERT(!m_factories.contains(identity)); m_factories.insert(identity, repo); Q_ASSERT(!m_dataClassSizes.contains(identity)); m_dataClassSizes.insert(identity, dataClassSize); } void TypeSystem::unregisterTypeClassInternal(uint identity) { AbstractTypeFactory* repo = m_factories.take(identity); Q_ASSERT(repo); delete repo; int removed = m_dataClassSizes.remove(identity); Q_ASSERT(removed); Q_UNUSED(removed); } } diff --git a/language/editor/persistentmovingrangeprivate.cpp b/language/editor/persistentmovingrangeprivate.cpp index 91bb313e91..d1c9849de5 100644 --- a/language/editor/persistentmovingrangeprivate.cpp +++ b/language/editor/persistentmovingrangeprivate.cpp @@ -1,95 +1,95 @@ /* Copyright 2010 David Nolden 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. */ #include "persistentmovingrangeprivate.h" #include #include #include #include void KDevelop::PersistentMovingRangePrivate::connectTracker() { - Q_ASSERT(m_tracker == 0); - Q_ASSERT(m_movingRange == 0); + Q_ASSERT(m_tracker == nullptr); + Q_ASSERT(m_movingRange == nullptr); m_tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(m_document); if(m_tracker) { // Create a moving range m_movingRange = m_tracker->documentMovingInterface()->newMovingRange(m_range); if (m_shouldExpand) m_movingRange->setInsertBehaviors(KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); // can't use new connect syntax here, MovingInterface is not a QObject connect(m_tracker->document(), SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent())); connect(m_tracker->document(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent())); m_movingRange->setAttribute(m_attribte); m_movingRange->setZDepth(m_zDepth); } } void KDevelop::PersistentMovingRangePrivate::disconnectTracker() { Q_ASSERT(m_tracker); Q_ASSERT(m_movingRange); // can't use new connect syntax here, MovingInterface is not a QObject disconnect(m_tracker->document(), SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent())); disconnect(m_tracker->document(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent())); delete m_movingRange; m_tracker.clear(); - m_movingRange = 0; + m_movingRange = nullptr; } void KDevelop::PersistentMovingRangePrivate::aboutToInvalidateMovingInterfaceContent() { if(m_movingRange) { Q_ASSERT(m_tracker); Q_ASSERT(m_movingRange); m_valid = false; /// @todo More precise tracking: Why is the document being invalidated? Try /// keeping the range alive. DocumentChangeTracker to the rescue. delete m_movingRange; - m_movingRange = 0; + m_movingRange = nullptr; m_range = KTextEditor::Range::invalid(); } } void KDevelop::PersistentMovingRangePrivate::aboutToDeleteMovingInterfaceContent() { // The whole document is being closed. Map the range back to the last saved revision, and use that. updateRangeFromMoving(); if(m_tracker && m_tracker->diskRevision()) { if(m_movingRange) m_range = m_tracker->diskRevision()->transformFromCurrentRevision(m_range).castToSimpleRange(); }else{ m_valid = false; m_range = KTextEditor::Range::invalid(); } // No need to disconnect, as the document is being deleted. Simply set the referenes to zero. delete m_movingRange; - m_movingRange = 0; + m_movingRange = nullptr; m_tracker.clear(); } diff --git a/language/highlighting/codehighlighting.cpp b/language/highlighting/codehighlighting.cpp index 46d366e996..c47a0cf067 100644 --- a/language/highlighting/codehighlighting.cpp +++ b/language/highlighting/codehighlighting.cpp @@ -1,642 +1,642 @@ /* * This file is part of KDevelop * * Copyright 2007-2010 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codehighlighting.h" #include #include "../../interfaces/icore.h" #include "../../interfaces/ilanguagecontroller.h" #include "../../interfaces/icompletionsettings.h" #include "../../util/foregroundlock.h" #include "util/debug.h" #include "../duchain/declaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumeratortype.h" #include "../duchain/types/typealiastype.h" #include "../duchain/types/enumerationtype.h" #include "../duchain/types/structuretype.h" #include "../duchain/functiondefinition.h" #include "../duchain/use.h" #include "colorcache.h" #include "configurablecolors.h" #include #include #include #include using namespace KTextEditor; static const float highlightingZDepth = -500; #define ifDebug(x) namespace KDevelop { ///@todo Don't highlighting everything, only what is visible on-demand CodeHighlighting::CodeHighlighting( QObject * parent ) : QObject(parent), m_localColorization(true), m_globalColorization(true), m_dataMutex(QMutex::Recursive) { qRegisterMetaType("KDevelop::IndexedString"); adaptToColorChanges(); connect(ColorCache::self(), &ColorCache::colorsGotChanged, this, &CodeHighlighting::adaptToColorChanges); } CodeHighlighting::~CodeHighlighting( ) { qDeleteAll(m_highlights); } void CodeHighlighting::adaptToColorChanges() { QMutexLocker lock(&m_dataMutex); // disable local highlighting if the ratio is set to 0 m_localColorization = ICore::self()->languageController()->completionSettings()->localColorizationLevel() > 0; // disable global highlighting if the ratio is set to 0 m_globalColorization = ICore::self()->languageController()->completionSettings()->globalColorizationLevel() > 0; m_declarationAttributes.clear(); m_definitionAttributes.clear(); m_depthAttributes.clear(); m_referenceAttributes.clear(); } KTextEditor::Attribute::Ptr CodeHighlighting::attributeForType( Types type, Contexts context, const QColor &color ) const { QMutexLocker lock(&m_dataMutex); KTextEditor::Attribute::Ptr a; switch (context) { case DefinitionContext: a = m_definitionAttributes[type]; break; case DeclarationContext: a = m_declarationAttributes[type]; break; case ReferenceContext: a = m_referenceAttributes[type]; break; } if ( !a || color.isValid() ) { a = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(*ColorCache::self()->defaultColors()->getAttribute(type))); if ( context == DefinitionContext || context == DeclarationContext ) { if (ICore::self()->languageController()->completionSettings()->boldDeclarations()) { a->setFontBold(); } } if( color.isValid() ) { a->setForeground(color); // a->setBackground(QColor(mix(0xffffff-color, backgroundColor(), 255-backgroundTinting))); } else { switch (context) { case DefinitionContext: m_definitionAttributes.insert(type, a); break; case DeclarationContext: m_declarationAttributes.insert(type, a); break; case ReferenceContext: m_referenceAttributes.insert(type, a); break; } } } return a; } ColorMap emptyColorMap() { - ColorMap ret(ColorCache::self()->validColorCount()+1, 0); + ColorMap ret(ColorCache::self()->validColorCount()+1, nullptr); return ret; } CodeHighlightingInstance* CodeHighlighting::createInstance() const { return new CodeHighlightingInstance(this); } bool CodeHighlighting::hasHighlighting(IndexedString url) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); if(tracker) { QMutexLocker lock(&m_dataMutex); return m_highlights.contains(tracker) && !m_highlights[tracker]->m_highlightedRanges.isEmpty(); } return false; } void CodeHighlighting::highlightDUChain(ReferencedTopDUContext context) { ENSURE_CHAIN_NOT_LOCKED IndexedString url; { DUChainReadLocker lock; if (!context) return; url = context->url(); } // This prevents the background-parser from updating the top-context while we're working with it UrlParseLock urlLock(context->url()); DUChainReadLocker lock; qint64 revision = context->parsingEnvironmentFile()->modificationRevision().revision; qCDebug(LANGUAGE) << "highlighting du chain" << url.toUrl(); if ( !m_localColorization && !m_globalColorization ) { qCDebug(LANGUAGE) << "highlighting disabled"; QMetaObject::invokeMethod(this, "clearHighlightingForDocument", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url)); return; } CodeHighlightingInstance* instance = createInstance(); lock.unlock(); instance->highlightDUChain(context.data()); DocumentHighlighting* highlighting = new DocumentHighlighting; highlighting->m_document = url; highlighting->m_waitingRevision = revision; highlighting->m_waiting = instance->m_highlight; std::sort(highlighting->m_waiting.begin(), highlighting->m_waiting.end()); QMetaObject::invokeMethod(this, "applyHighlighting", Qt::QueuedConnection, Q_ARG(void*, highlighting)); delete instance; } void CodeHighlightingInstance::highlightDUChain(TopDUContext* context) { m_contextClasses.clear(); m_useClassCache = true; //Highlight highlightDUChain(context, QHash(), emptyColorMap()); m_functionColorsForDeclarations.clear(); m_functionDeclarationsForColors.clear(); m_useClassCache = false; m_contextClasses.clear(); } void CodeHighlightingInstance::highlightDUChain(DUContext* context, QHash colorsForDeclarations, ColorMap declarationsForColors) { DUChainReadLocker lock; TopDUContext* top = context->topContext(); //Merge the colors from the function arguments foreach( const DUContext::Import &imported, context->importedParentContexts() ) { if(!imported.context(top) || (imported.context(top)->type() != DUContext::Other && imported.context(top)->type() != DUContext::Function)) continue; //For now it's enough simply copying them, because we only pass on colors within function bodies. if (m_functionColorsForDeclarations.contains(imported.context(top))) colorsForDeclarations = m_functionColorsForDeclarations[imported.context(top)]; if (m_functionDeclarationsForColors.contains(imported.context(top))) declarationsForColors = m_functionDeclarationsForColors[imported.context(top)]; } QList takeFreeColors; foreach (Declaration* dec, context->localDeclarations()) { if (!useRainbowColor(dec)) { highlightDeclaration(dec, QColor(QColor::Invalid)); continue; } //Initially pick a color using the hash, so the chances are good that the same identifier gets the same color always. uint colorNum = dec->identifier().hash() % ColorCache::self()->primaryColorCount(); if( declarationsForColors[colorNum] ) { takeFreeColors << dec; //Use one of the colors that stays free continue; } colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } foreach (Declaration* dec, takeFreeColors) { uint colorNum = dec->identifier().hash() % ColorCache::self()->primaryColorCount(); uint oldColorNum = colorNum; while (declarationsForColors[colorNum]) { colorNum = (colorNum + 1) % ColorCache::self()->primaryColorCount(); if (colorNum == oldColorNum) { colorNum = ColorCache::self()->primaryColorCount(); break; } } if (colorNum < ColorCache::self()->primaryColorCount()) { // Use primary color colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } else { // Try to use supplementary color colorNum = ColorCache::self()->primaryColorCount(); while (declarationsForColors[colorNum]) { colorNum++; if (colorNum == ColorCache::self()->validColorCount()) { //If no color could be found, use default color highlightDeclaration(dec, QColor(QColor::Invalid)); break; } } if (colorNum < ColorCache::self()->validColorCount()) { // Use supplementary color colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } } } for(int a = 0; a < context->usesCount(); ++a) { Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[a].m_declarationIndex); QColor color(QColor::Invalid); if( colorsForDeclarations.contains(decl) ) color = ColorCache::self()->generatedColor(colorsForDeclarations[decl]); highlightUse(context, a, color); } if(context->type() == DUContext::Other || context->type() == DUContext::Function) { m_functionColorsForDeclarations[IndexedDUContext(context)] = colorsForDeclarations; m_functionDeclarationsForColors[IndexedDUContext(context)] = declarationsForColors; } QVector< DUContext* > children = context->childContexts(); lock.unlock(); // Periodically release the lock, so that the UI won't be blocked too much foreach (DUContext* child, children) highlightDUChain(child, colorsForDeclarations, declarationsForColors ); } KTextEditor::Attribute::Ptr CodeHighlighting::attributeForDepth(int depth) const { while (depth >= m_depthAttributes.count()) { KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute()); a->setBackground(QColor(Qt::white).dark(100 + (m_depthAttributes.count() * 25))); a->setBackgroundFillWhitespace(true); if (depth % 2) a->setOutline(Qt::red); m_depthAttributes.append(a); } return m_depthAttributes[depth]; } KDevelop::Declaration* CodeHighlightingInstance::localClassFromCodeContext(KDevelop::DUContext* context) const { if(!context) - return 0; + return nullptr; if(m_contextClasses.contains(context)) return m_contextClasses[context]; DUContext* startContext = context; while( context->type() == DUContext::Other ) { //Move context to the top context of type "Other". This is needed because every compound-statement creates a new sub-context. auto parent = context->parentContext(); if (!parent || (parent->type() != DUContext::Other && parent->type() != DUContext::Function)) { break; } context = context->parentContext(); } ///Step 1: Find the function-declaration for the function we are in - Declaration* functionDeclaration = 0; + Declaration* functionDeclaration = nullptr; if( FunctionDefinition* def = dynamic_cast(context->owner()) ) { if(m_contextClasses.contains(context)) return m_contextClasses[context]; functionDeclaration = def->declaration(startContext->topContext()); } if( !functionDeclaration && context->owner() ) functionDeclaration = context->owner(); if(!functionDeclaration) { if(m_useClassCache) - m_contextClasses[context] = 0; - return 0; + m_contextClasses[context] = nullptr; + return nullptr; } Declaration* decl = functionDeclaration->context()->owner(); if(m_useClassCache) m_contextClasses[context] = decl; return decl; } CodeHighlightingInstance::Types CodeHighlightingInstance::typeForDeclaration(Declaration * dec, DUContext* context) const { /** * We highlight in 3 steps by priority: * 1. Is the item in the local class or an inherited class? If yes, highlight. * 2. What kind of item is it? If it's a type/function/enumerator, highlight by type. * 3. Else, highlight by scope. * * */ // if(ClassMemberDeclaration* classMember = dynamic_cast(dec)) // if(!Cpp::isAccessible(context, classMember)) // return ErrorVariableType; if(!dec) return ErrorVariableType; Types type = LocalVariableType; if(dec->kind() == Declaration::Namespace) return NamespaceType; if(dec->kind() == Declaration::Macro){ return MacroType; } if (context && dec->context() && dec->context()->type() == DUContext::Class) { //It is a use. //Determine the class we're in Declaration* klass = localClassFromCodeContext(context); if(klass) { if (klass->internalContext() == dec->context()) type = LocalClassMemberType; //Using Member of the local class else if (klass->internalContext() && klass->internalContext()->imports(dec->context())) type = InheritedClassMemberType; //Using Member of an inherited class } } if (type == LocalVariableType) { if (dec->kind() == Declaration::Type || dec->type() || dec->type()) { if (dec->isForwardDeclaration()) type = ForwardDeclarationType; else if (dec->type()) type = FunctionType; else if(dec->type()) type = ClassType; else if(dec->type()) type = TypeAliasType; else if(dec->type()) type = EnumType; else if(dec->type()) type = EnumeratorType; } } if (type == LocalVariableType) { switch (dec->context()->type()) { case DUContext::Namespace: type = NamespaceVariableType; break; case DUContext::Class: type = MemberVariableType; break; case DUContext::Function: type = FunctionVariableType; break; case DUContext::Global: type = GlobalVariableType; break; default: break; } } return type; } bool CodeHighlightingInstance::useRainbowColor(Declaration* dec) const { return dec->context()->type() == DUContext::Function || (dec->context()->type() == DUContext::Other && dec->context()->owner()); } void CodeHighlightingInstance::highlightDeclaration(Declaration * declaration, const QColor &color) { HighlightedRange h; h.range = declaration->range(); - h.attribute = m_highlighting->attributeForType(typeForDeclaration(declaration, 0), DeclarationContext, color); + h.attribute = m_highlighting->attributeForType(typeForDeclaration(declaration, nullptr), DeclarationContext, color); m_highlight.push_back(h); } void CodeHighlightingInstance::highlightUse(DUContext* context, int index, const QColor &color) { Types type = ErrorVariableType; Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[index].m_declarationIndex); type = typeForDeclaration(decl, context); if(type != ErrorVariableType || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems()) { HighlightedRange h; h.range = context->uses()[index].m_range; h.attribute = m_highlighting->attributeForType(type, ReferenceContext, color); m_highlight.push_back(h); } } void CodeHighlightingInstance::highlightUses(DUContext* context) { for(int a = 0; a < context->usesCount(); ++a) highlightUse(context, a, QColor(QColor::Invalid)); } void CodeHighlighting::clearHighlightingForDocument(IndexedString document) { VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document); if(m_highlights.contains(tracker)) { disconnect(tracker, &DocumentChangeTracker::destroyed, this, &CodeHighlighting::trackerDestroyed); qDeleteAll(m_highlights[tracker]->m_highlightedRanges); delete m_highlights[tracker]; m_highlights.remove(tracker); } } void CodeHighlighting::applyHighlighting(void* _highlighting) { CodeHighlighting::DocumentHighlighting* highlighting = static_cast(_highlighting); VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(highlighting->m_document); if(!tracker) { qCDebug(LANGUAGE) << "no document found for the planned highlighting of" << highlighting->m_document.str(); delete highlighting; return; } if(!tracker->holdingRevision(highlighting->m_waitingRevision)) { qCDebug(LANGUAGE) << "not holding revision" << highlighting->m_waitingRevision << "not applying highlighting;" << "probably a new parse job has already updated the context"; delete highlighting; return; } QVector< MovingRange* > oldHighlightedRanges; if(m_highlights.contains(tracker)) { oldHighlightedRanges = m_highlights[tracker]->m_highlightedRanges; delete m_highlights[tracker]; }else{ // we newly add this tracker, so add the connection // This can't use new style connect syntax since MovingInterface is not a QObject connect(tracker->document(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); connect(tracker->document(), SIGNAL(aboutToRemoveText(KTextEditor::Range)), this, SLOT(aboutToRemoveText(KTextEditor::Range))); connect(tracker, &DocumentChangeTracker::destroyed, this, &CodeHighlighting::trackerDestroyed); } m_highlights[tracker] = highlighting; // Now create MovingRanges (match old ones with the incoming ranges) KTextEditor::Range tempRange; QVector::iterator movingIt = oldHighlightedRanges.begin(); QVector::iterator rangeIt = highlighting->m_waiting.begin(); while(rangeIt != highlighting->m_waiting.end()) { // Translate the range into the current revision KTextEditor::Range transformedRange = tracker->transformToCurrentRevision(rangeIt->range, highlighting->m_waitingRevision); while(movingIt != oldHighlightedRanges.end() && ((*movingIt)->start().line() < transformedRange.start().line() || ((*movingIt)->start().line() == transformedRange.start().line() && (*movingIt)->start().column() < transformedRange.start().column()))) { delete *movingIt; // Skip ranges that are in front of the current matched range ++movingIt; } tempRange = transformedRange; if(movingIt == oldHighlightedRanges.end() || transformedRange.start().line() != (*movingIt)->start().line() || transformedRange.start().column() != (*movingIt)->start().column() || transformedRange.end().line() != (*movingIt)->end().line() || transformedRange.end().column() != (*movingIt)->end().column()) { Q_ASSERT(rangeIt->attribute); // The moving range is behind or unequal, create a new range highlighting->m_highlightedRanges.push_back(tracker->documentMovingInterface()->newMovingRange(tempRange)); highlighting->m_highlightedRanges.back()->setAttribute(rangeIt->attribute); highlighting->m_highlightedRanges.back()->setZDepth(highlightingZDepth); } else { // Update the existing moving range (*movingIt)->setAttribute(rangeIt->attribute); (*movingIt)->setRange(tempRange); highlighting->m_highlightedRanges.push_back(*movingIt); ++movingIt; } ++rangeIt; } for(; movingIt != oldHighlightedRanges.end(); ++movingIt) delete *movingIt; // Delete unmatched moving ranges behind } void CodeHighlighting::trackerDestroyed(QObject* object) { // Called when a document is destroyed VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = static_cast(object); Q_ASSERT(m_highlights.contains(tracker)); delete m_highlights[tracker]; // No need to care about the individual ranges, as the document is being destroyed m_highlights.remove(tracker); } void CodeHighlighting::aboutToInvalidateMovingInterfaceContent(Document* doc) { clearHighlightingForDocument(IndexedString(doc->url())); } void CodeHighlighting::aboutToRemoveText( const KTextEditor::Range& range ) { if (range.onSingleLine()) // don't try to optimize this return; VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); Q_ASSERT(dynamic_cast(sender())); KTextEditor::Document* doc = static_cast(sender()); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser() ->trackerForUrl(IndexedString(doc->url())); if(m_highlights.contains(tracker)) { QVector& ranges = m_highlights.value(tracker)->m_highlightedRanges; QVector::iterator it = ranges.begin(); while(it != ranges.end()) { if (range.contains((*it)->toRange())) { delete (*it); it = ranges.erase(it); } else { ++it; } } } } } // kate: space-indent on; indent-width 2; replace-trailing-space-save on; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/highlighting/colorcache.cpp b/language/highlighting/colorcache.cpp index 762dbffbee..e6da750174 100644 --- a/language/highlighting/colorcache.cpp +++ b/language/highlighting/colorcache.cpp @@ -1,327 +1,327 @@ /* * This file is part of KDevelop * * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "colorcache.h" #include "configurablecolors.h" #include #include "../../interfaces/icore.h" #include "../../interfaces/ilanguagecontroller.h" #include "../../interfaces/icompletionsettings.h" #include "../../interfaces/idocument.h" #include "../../interfaces/idocumentcontroller.h" #include "../interfaces/ilanguagesupport.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" #include "util/debug.h" #include "widgetcolorizer.h" #include #include #include #define ifDebug(x) namespace KDevelop { -ColorCache* ColorCache::m_self = 0; +ColorCache* ColorCache::m_self = nullptr; ColorCache::ColorCache(QObject* parent) - : QObject(parent), m_defaultColors(0), m_validColorCount(0), m_colorOffset(0), + : QObject(parent), m_defaultColors(nullptr), m_validColorCount(0), m_colorOffset(0), m_localColorRatio(0), m_globalColorRatio(0), m_boldDeclarations(true) { - Q_ASSERT(m_self == 0); + Q_ASSERT(m_self == nullptr); updateColorsFromScheme(); // default / fallback updateColorsFromSettings(); connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ColorCache::updateColorsFromSettings, Qt::QueuedConnection); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ColorCache::slotDocumentActivated); bool hadDoc = tryActiveDocument(); updateInternal(); m_self = this; if (!hadDoc) { // try to update later on again QMetaObject::invokeMethod(this, "tryActiveDocument", Qt::QueuedConnection); } } bool ColorCache::tryActiveDocument() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if ( view ) { updateColorsFromView(view); return true; } return false; } ColorCache::~ColorCache() { - m_self = 0; + m_self = nullptr; delete m_defaultColors; - m_defaultColors = 0; + m_defaultColors = nullptr; } ColorCache* ColorCache::self() { if (!m_self) { m_self = new ColorCache; } return m_self; } void ColorCache::generateColors() { if ( m_defaultColors ) { delete m_defaultColors; } m_defaultColors = new CodeHighlightingColors(this); // Primary colors taken from: http://colorbrewer2.org/?type=qualitative&scheme=Paired&n=12 const QColor colors[] = { {"#b15928"}, {"#ff7f00"}, {"#b2df8a"}, {"#33a02c"}, {"#a6cee3"}, {"#1f78b4"}, {"#6a3d9a"}, {"#cab2d6"}, {"#e31a1c"}, {"#fb9a99"} }; // Supplementary colors generated by: http://tools.medialab.sciences-po.fr/iwanthue/ const QColor supplementaryColors[] = { {"#D33B67"}, {"#5EC764"}, {"#6CC82D"}, {"#995729"}, {"#FB4D84"}, {"#4B8828"}, {"#D847D0"}, {"#B56AC5"}, {"#E96F0C"}, {"#DC7161"}, {"#4D7279"}, {"#01AAF1"}, {"#D2A237"}, {"#F08CA5"}, {"#C83E93"}, {"#5D7DF7"}, {"#EFBB51"}, {"#108BBB"}, {"#5C84B8"}, {"#02F8BC"}, {"#A5A9F7"}, {"#F28E64"}, {"#A461E6"}, {"#6372D3"} }; m_colors.clear(); for(const auto& color: colors){ m_colors.append(blendLocalColor(color)); } m_primaryColorCount = m_colors.count(); for(const auto& color: supplementaryColors){ m_colors.append(blendLocalColor(color)); } m_validColorCount = m_colors.count(); } void ColorCache::slotDocumentActivated() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); ifDebug(qCDebug(LANGUAGE) << "doc activated:" << doc;) if ( view ) { updateColorsFromView(view); } } void ColorCache::slotViewSettingsChanged() { KTextEditor::View* view = qobject_cast(sender()); Q_ASSERT(view); ifDebug(qCDebug(LANGUAGE) << "settings changed" << view;) updateColorsFromView(view); } void ColorCache::updateColorsFromView(KTextEditor::View* view) { if ( !view ) { // yeah, the HighlightInterface methods returning an Attribute // require a View... kill me for that mess return; } QColor foreground(QColor::Invalid); QColor background(QColor::Invalid); KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal); foreground = style->foreground().color(); if (style->hasProperty(QTextFormat::BackgroundBrush)) { background = style->background().color(); } // FIXME: this is in kateview // qCDebug(LANGUAGE) << "got foreground:" << foreground.name() << "old is:" << m_foregroundColor.name(); //NOTE: this slot is defined in KatePart > 4.4, see ApiDocs of the ConfigInterface // the signal is not defined in ConfigInterface, but according to the docs it should be // can't use new signal slot syntax here, since ConfigInterface is not a QObject if ( KTextEditor::View* view = m_view.data() ) { Q_ASSERT(qobject_cast(view)); // we only listen to a single view, i.e. the active one disconnect(view, SIGNAL(configChanged()), this, SLOT(slotViewSettingsChanged())); } Q_ASSERT(qobject_cast(view)); connect(view, SIGNAL(configChanged()), this, SLOT(slotViewSettingsChanged())); m_view = view; if ( !foreground.isValid() ) { // fallback to colorscheme variant ifDebug(qCDebug(LANGUAGE) << "updating from scheme";) updateColorsFromScheme(); } else if ( m_foregroundColor != foreground || m_backgroundColor != background ) { m_foregroundColor = foreground; m_backgroundColor = background; ifDebug(qCDebug(LANGUAGE) << "updating from document";) update(); } } void ColorCache::updateColorsFromScheme() { KColorScheme scheme(QPalette::Normal, KColorScheme::View); QColor foreground = scheme.foreground(KColorScheme::NormalText).color(); QColor background = scheme.background(KColorScheme::NormalBackground).color(); if ( foreground != m_foregroundColor || background != m_backgroundColor ) { m_foregroundColor = foreground; m_backgroundColor = background; update(); } } void ColorCache::updateColorsFromSettings() { int localRatio = ICore::self()->languageController()->completionSettings()->localColorizationLevel(); int globalRatio = ICore::self()->languageController()->completionSettings()->globalColorizationLevel(); bool boldDeclartions = ICore::self()->languageController()->completionSettings()->boldDeclarations(); if ( localRatio != m_localColorRatio || globalRatio != m_globalColorRatio ) { m_localColorRatio = localRatio; m_globalColorRatio = globalRatio; update(); } if (boldDeclartions != m_boldDeclarations) { m_boldDeclarations = boldDeclartions; update(); } } void ColorCache::update() { if ( !m_self ) { ifDebug(qCDebug(LANGUAGE) << "not updating - still initializating";) // don't update on startup, updateInternal is called directly there return; } QMetaObject::invokeMethod(this, "updateInternal", Qt::QueuedConnection); } void ColorCache::updateInternal() { ifDebug(qCDebug(LANGUAGE) << "update internal" << m_self;) generateColors(); if ( !m_self ) { // don't do anything else fancy on startup return; } emit colorsGotChanged(); // rehighlight open documents if (!ICore::self() || ICore::self()->shuttingDown()) { return; } foreach (IDocument* doc, ICore::self()->documentController()->openDocuments()) { foreach (const auto lang, ICore::self()->languageController()->languagesForUrl(doc->url())) { ReferencedTopDUContext top; { DUChainReadLocker lock; top = lang->standardContext(doc->url()); } if(top) { if ( ICodeHighlighting* highlighting = lang->codeHighlighting() ) { highlighting->highlightDUChain(top); } } } } } QColor ColorCache::blend(QColor color, uchar ratio) const { Q_ASSERT(m_backgroundColor.isValid()); Q_ASSERT(m_foregroundColor.isValid()); return WidgetColorizer::blendForeground(color, float(ratio) / float(0xff), m_foregroundColor, m_backgroundColor); } QColor ColorCache::blendBackground(QColor color, uchar ratio) const { return WidgetColorizer::blendBackground(color, float(ratio) / float(0xff), m_foregroundColor, m_backgroundColor); } QColor ColorCache::blendGlobalColor(QColor color) const { return blend(color, m_globalColorRatio); } QColor ColorCache::blendLocalColor(QColor color) const { return blend(color, m_localColorRatio); } CodeHighlightingColors* ColorCache::defaultColors() const { Q_ASSERT(m_defaultColors); return m_defaultColors; } QColor ColorCache::generatedColor(uint num) const { return num > (uint)m_colors.size() ? foregroundColor() : m_colors[num]; } uint ColorCache::validColorCount() const { return m_validColorCount; } uint ColorCache::primaryColorCount() const { return m_primaryColorCount; } QColor ColorCache::foregroundColor() const { return m_foregroundColor; } } // kate: space-indent on; indent-width 2; remove-trailing-spaces all; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/interfaces/ilanguagesupport.cpp b/language/interfaces/ilanguagesupport.cpp index 65179747da..9eacc17b94 100644 --- a/language/interfaces/ilanguagesupport.cpp +++ b/language/interfaces/ilanguagesupport.cpp @@ -1,108 +1,108 @@ /*************************************************************************** * Copyright 2008 David Nolden * * Copyright 2014 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ilanguagesupport.h" #include "../duchain/duchain.h" #include #include #include namespace KDevelop { class ILanguageSupportPrivate { public: mutable QReadWriteLock lock; }; ILanguageSupport::ILanguageSupport() : d(new ILanguageSupportPrivate) { } ILanguageSupport::~ILanguageSupport() { } TopDUContext* ILanguageSupport::standardContext(const QUrl& url, bool proxyContext) { Q_UNUSED(proxyContext) return DUChain::self()->chainForDocument(url); } KTextEditor::Range ILanguageSupport::specialLanguageObjectRange(const QUrl& url, const KTextEditor::Cursor& position) { Q_UNUSED(url) Q_UNUSED(position) return KTextEditor::Range::invalid(); } QPair ILanguageSupport::specialLanguageObjectJumpCursor(const QUrl& url, const KTextEditor::Cursor& position) { Q_UNUSED(url) Q_UNUSED(position) return QPair(QUrl(), KTextEditor::Cursor::invalid()); } QWidget* ILanguageSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) { Q_UNUSED(url) Q_UNUSED(position) - return 0; + return nullptr; } ICodeHighlighting* ILanguageSupport::codeHighlighting() const { - return 0; + return nullptr; } BasicRefactoring* ILanguageSupport::refactoring() const { return nullptr; } ICreateClassHelper* ILanguageSupport::createClassHelper() const { - return 0; + return nullptr; } SourceFormatterItemList ILanguageSupport::sourceFormatterItems() const { return SourceFormatterItemList(); } QString ILanguageSupport::indentationSample() const { return QString(); } QReadWriteLock* ILanguageSupport::parseLock() const { return &d->lock; } int ILanguageSupport::suggestedReparseDelayForChange(KTextEditor::Document* doc, const KTextEditor::Range& changedRange, const QString& /*removedText*/, bool /*removal*/) const { auto text = doc->text(changedRange); bool joinedWord = doc->wordRangeAt(changedRange.start()).isEmpty() || doc->wordRangeAt(changedRange.end()).isEmpty(); auto isWhitespace = std::all_of(text.begin(), text.end(), [](const QChar& c) { return c.isSpace(); }); return (isWhitespace && !joinedWord) ? NoUpdateRequired : DefaultDelay; } } diff --git a/language/interfaces/quickopendataprovider.cpp b/language/interfaces/quickopendataprovider.cpp index 0138e0a8b7..176dfa4018 100644 --- a/language/interfaces/quickopendataprovider.cpp +++ b/language/interfaces/quickopendataprovider.cpp @@ -1,80 +1,80 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopendataprovider.h" #include #include namespace KDevelop { QuickOpenFileSetInterface::~QuickOpenFileSetInterface() { } QuickOpenEmbeddedWidgetInterface::~QuickOpenEmbeddedWidgetInterface() { } QuickOpenDataBase::~QuickOpenDataBase() { } QIcon QuickOpenDataBase::icon() const { return QIcon(); } bool QuickOpenDataBase::isExpandable() const { return false; } QWidget* QuickOpenDataBase::expandingWidget() const { - return 0; + return nullptr; } QList QuickOpenDataBase::highlighting() const { return QList(); } QuickOpenDataProviderBase::~QuickOpenDataProviderBase() { } void QuickOpenDataProviderBase::enableData( const QStringList& , const QStringList& ) { } bool extractLineNumber(const QString& from, QString& path, uint& lineNumber) { int colonIndex = from.indexOf(':'); if (colonIndex != -1) { if (colonIndex == from.count() - 1) { path = from.mid(0, colonIndex); lineNumber = 0; } else { bool ok; uint number = from.midRef(colonIndex + 1).toUInt(&ok); if (ok) { path = from.mid(0, colonIndex); lineNumber = number; } else { return false; } } return true; } else { return false; } } } diff --git a/language/util/setrepository.cpp b/language/util/setrepository.cpp index 81485c086b..503ccc25fc 100644 --- a/language/util/setrepository.cpp +++ b/language/util/setrepository.cpp @@ -1,1121 +1,1121 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "setrepository.h" #include "util/debug.h" #include #include #include #include #include #include #include #include //#define DEBUG_SETREPOSITORY #ifdef DEBUG_SETREPOSITORY #define ifDebug(X) X #else #define ifDebug(x) #undef Q_ASSERT #define Q_ASSERT(x) #endif #ifndef DEBUG_SETREPOSITORY #define CHECK_SPLIT_POSITION(Node) #else #define CHECK_SPLIT_POSITION(node) Q_ASSERT(!(node).leftNode || (getLeftNode(&node)->end() <= splitPositionForRange((node).start, (node).end) && getRightNode(&node)->start() >= splitPositionForRange((node).start, (node).end))) #endif namespace Utils { /** * To achieve a maximum re-usage of nodes, we make sure that sub-nodes of a node always split at specific boundaries. * For each range we can compute a position where that range should be split into its child-nodes. * When creating a new node with 2 sub-nodes, we re-create those child-nodes if their boundaries don't represent those split-positions. * * We pick the split-positions deterministically, they are in order of priority: * ((1<<31)*n, n = [0,...] * ((1<<30)*n, n = [0,...] * ((1<<29)*n, n = [0,...] * ((1<<...)*n, n = [0,...] * ... * */ typedef BasicSetRepository::Index Index; ///The returned split position shall be the end of the first sub-range, and the start of the second ///@param splitBit should be initialized with 31, unless you know better. The value can then be used on while computing child split positions. ///In the end, it will contain the bit used to split the range. It will also contain zero if no split-position exists(length 1) uint splitPositionForRange(uint start, uint end, uchar& splitBit) { if(end-start == 1) { splitBit = 0; return 0; } while(true) { uint position = ((end-1) >> splitBit) << splitBit; //Round to the split-position in this interval that is smaller than end if(position > start && position < end) return position; Q_ASSERT(splitBit != 0); --splitBit; } return 0; } uint splitPositionForRange(uint start, uint end) { uchar splitBit = 31; return splitPositionForRange(start, end, splitBit); } class SetNodeDataRequest; #define getLeftNode(node) repository.itemFromIndex(node->leftNode()) #define getRightNode(node) repository.itemFromIndex(node->rightNode()) #define nodeFromIndex(index) repository.itemFromIndex(index) struct SetRepositoryAlgorithms { SetRepositoryAlgorithms(SetDataRepository& _repository, BasicSetRepository* _setRepository) : repository(_repository), setRepository(_setRepository) { } ///Expensive Index count(const SetNodeData* node) const; void localCheck(const SetNodeData* node); void check(uint node); void check(const SetNodeData* node); QString shortLabel(const SetNodeData& node) const; uint set_union(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); - uint createSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left = 0, const SetNodeData* right = 0); + uint createSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left = nullptr, const SetNodeData* right = nullptr); uint computeSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right, uchar splitBit); uint set_intersect(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); bool set_contains(const SetNodeData* node, Index index); uint set_subtract(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); //Required both nodes to be split correctly bool set_equals(const SetNodeData* lhs, const SetNodeData* rhs); QString dumpDotGraph(uint node) const; ///Finds or inserts the given ranges into the repository, and returns the set-index that represents them uint setForIndices(std::vector::const_iterator begin, std::vector::const_iterator end, uchar splitBit = 31) { Q_ASSERT(begin != end); uint startIndex = *begin; uint endIndex = *(end-1)+1; if(endIndex == startIndex+1) { SetNodeData data(startIndex, endIndex); return repository.index( SetNodeDataRequest(&data, repository, setRepository) ); } uint split = splitPositionForRange(startIndex, endIndex, splitBit); Q_ASSERT(split); std::vector::const_iterator splitIterator = std::lower_bound(begin, end, split); Q_ASSERT(*splitIterator >= split); Q_ASSERT(splitIterator > begin); Q_ASSERT(*(splitIterator-1) < split); return createSetFromNodes(setForIndices(begin, splitIterator, splitBit), setForIndices(splitIterator, end, splitBit)); } private: QString dumpDotGraphInternal(uint node, bool master=false) const; SetDataRepository& repository; BasicSetRepository* setRepository; }; void SetNodeDataRequest::destroy(SetNodeData* data, KDevelop::AbstractItemRepository& _repository) { SetDataRepository& repository(static_cast(_repository)); if(repository.setRepository->delayedDeletion()) { if(data->leftNode()){ SetDataRepositoryBase::MyDynamicItem left = repository.dynamicItemFromIndex(data->leftNode()); SetDataRepositoryBase::MyDynamicItem right = repository.dynamicItemFromIndex(data->rightNode()); Q_ASSERT(left->m_refCount > 0); --left->m_refCount; Q_ASSERT(right->m_refCount > 0); --right->m_refCount; }else { //Deleting a leaf Q_ASSERT(data->end() - data->start() == 1); repository.setRepository->itemRemovedFromSets(data->start()); } } } SetNodeDataRequest::SetNodeDataRequest(const SetNodeData* _data, SetDataRepository& _repository, BasicSetRepository* _setRepository) : data(*_data), m_hash(_data->hash()), repository(_repository), setRepository(_setRepository), m_created(false) { ifDebug( SetRepositoryAlgorithms alg(repository); alg.check(_data) ); } SetNodeDataRequest::~SetNodeDataRequest() { //Eventually increase the reference-count of direct children if(m_created) { if(data.leftNode()) ++repository.dynamicItemFromIndex(data.leftNode())->m_refCount; if(data.rightNode()) ++repository.dynamicItemFromIndex(data.rightNode())->m_refCount; } } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void SetNodeDataRequest::createItem(SetNodeData* item) const { Q_ASSERT((data.rightNode() && data.leftNode()) || (!data.rightNode() && !data.leftNode())); m_created = true; *item = data; Q_ASSERT((item->rightNode() && item->leftNode()) || (!item->rightNode() && !item->leftNode())); #ifdef DEBUG_SETREPOSITORY //Make sure we split at the correct split position if(item->hasSlaves()) { uint split = splitPositionForRange(data.start, data.end); const SetNodeData* left = repository.itemFromIndex(item->leftNode()); const SetNodeData* right = repository.itemFromIndex(item->rightNode()); Q_ASSERT(split >= left->end() && split <= right->start()); } #endif if(!data.leftNode() && setRepository) { for(uint a = item->start(); a < item->end(); ++a) setRepository->itemAddedToSets(a); } } bool SetNodeDataRequest::equals(const SetNodeData* item) const { Q_ASSERT((item->rightNode() && item->leftNode()) || (!item->rightNode() && !item->leftNode())); //Just compare child nodes, since data must be correctly split, this is perfectly ok //Since this happens in very tight loops, we don't call an additional function here, but just do the check. return item->leftNode() == data.leftNode() && item->rightNode() == data.rightNode() && item->start() == data.start() && item->end() == data.end(); } class BasicSetRepository::Private { public: Private(QString _name) : name(_name) { } ~Private() { } QString name; private: }; -Set::Set() : m_tree(0), m_repository(0) { +Set::Set() : m_tree(0), m_repository(nullptr) { } Set::~Set() { } unsigned int Set::count() const { if(!m_repository || !m_tree) return 0; QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.count(m_repository->dataRepository.itemFromIndex(m_tree)); } Set::Set(uint treeNode, BasicSetRepository* repository) : m_tree(treeNode), m_repository(repository) { } Set::Set(const Set& rhs) { m_repository = rhs.m_repository; m_tree = rhs.m_tree; } Set& Set::operator=(const Set& rhs) { m_repository = rhs.m_repository; m_tree = rhs.m_tree; return *this; } QString Set::dumpDotGraph() const { if(!m_repository || !m_tree) return QString(); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.dumpDotGraph(m_tree); } Index SetRepositoryAlgorithms::count(const SetNodeData* node) const { if(node->leftNode() && node->rightNode()) return count(getLeftNode(node)) + count(getRightNode(node)); else return node->end() - node->start(); } void SetRepositoryAlgorithms::localCheck(const SetNodeData* ifDebug(node) ) { // Q_ASSERT(node->start() > 0); Q_ASSERT(node->start() < node->end()); Q_ASSERT((node->leftNode() && node->rightNode()) || (!node->leftNode() && !node->rightNode())); Q_ASSERT(!node->leftNode() || (getLeftNode(node())->start() == node->start() && getRightNode(node)->end() == node->end())); Q_ASSERT(!node->leftNode() || (getLeftNode(node())->end() <= getRightNode(node)->start())); } void SetRepositoryAlgorithms::check(uint node) { if(!node) return; check(nodeFromIndex(node)); } void SetRepositoryAlgorithms::check(const SetNodeData* node) { localCheck(node); if(node->leftNode()) check(getLeftNode(node)); if(node->rightNode()) check(getRightNode(node)); // CHECK_SPLIT_POSITION(*node); Re-enable this } QString SetRepositoryAlgorithms::shortLabel(const SetNodeData& node) const { return QStringLiteral("n%1_%2").arg(node.start()).arg(node.end()); } QString SetRepositoryAlgorithms::dumpDotGraphInternal(uint nodeIndex, bool master) const { if(!nodeIndex) return QStringLiteral("empty node"); const SetNodeData& node(*repository.itemFromIndex(nodeIndex)); QString color = QStringLiteral("blue"); if(master) color = QStringLiteral("red"); QString label = QStringLiteral("%1 -> %2").arg(node.start()).arg(node.end()); if(!node.contiguous()) label += QLatin1String(", with gaps"); QString ret = QStringLiteral("%1[label=\"%2\", color=\"%3\"];\n").arg(shortLabel(node), label, color); if(node.leftNode()) { const SetNodeData& left(*repository.itemFromIndex(node.leftNode())); const SetNodeData& right(*repository.itemFromIndex(node.rightNode())); Q_ASSERT(node.rightNode()); ret += QStringLiteral("%1 -> %2;\n").arg(shortLabel(node), shortLabel(left)); ret += QStringLiteral("%1 -> %2;\n").arg(shortLabel(node), shortLabel(right)); ret += dumpDotGraphInternal(node.leftNode()); ret += dumpDotGraphInternal(node.rightNode()); } return ret; } QString SetRepositoryAlgorithms::dumpDotGraph(uint nodeIndex) const { QString ret = QStringLiteral("digraph Repository {\n"); ret += dumpDotGraphInternal(nodeIndex, true); ret += QLatin1String("}\n"); return ret; } const int nodeStackAlloc = 500; class Set::Iterator::IteratorPrivate { public: - IteratorPrivate() : nodeStackSize(0), currentIndex(0), repository(0) { + IteratorPrivate() : nodeStackSize(0), currentIndex(0), repository(nullptr) { nodeStackData.resize(nodeStackAlloc); nodeStack = nodeStackData.data(); } IteratorPrivate(const IteratorPrivate& rhs) : nodeStackData(rhs.nodeStackData), nodeStackSize(rhs.nodeStackSize), currentIndex(rhs.currentIndex), repository(rhs.repository) { nodeStack = nodeStackData.data(); } void resizeNodeStack() { nodeStackData.resize(nodeStackSize + 1); nodeStack = nodeStackData.data(); } KDevVarLengthArray nodeStackData; const SetNodeData** nodeStack; int nodeStackSize; Index currentIndex; BasicSetRepository* repository; /** * Pushes the noed on top of the stack, changes currentIndex, and goes as deep as necessary for iteration. * */ void startAtNode(const SetNodeData* node) { Q_ASSERT(node->start() != node->end()); currentIndex = node->start(); do { nodeStack[nodeStackSize++] = node; if(nodeStackSize >= nodeStackAlloc) resizeNodeStack(); if(node->contiguous()) break; //We need no finer granularity, because the range is contiguous node = Set::Iterator::getDataRepository(repository).itemFromIndex(node->leftNode()); } while(node); Q_ASSERT(currentIndex >= nodeStack[0]->start()); } }; std::set Set::stdSet() const { Set::Iterator it = iterator(); std::set ret; while(it) { Q_ASSERT(ret.find(*it) == ret.end()); ret.insert(*it); ++it; } return ret; } Set::Iterator::Iterator(const Iterator& rhs) : d(new IteratorPrivate(*rhs.d)) { } Set::Iterator& Set::Iterator::operator=(const Iterator& rhs) { delete d; d = new IteratorPrivate(*rhs.d); return *this; } Set::Iterator::Iterator() : d(new IteratorPrivate) { } Set::Iterator::~Iterator() { delete d; } Set::Iterator::operator bool() const { return d->nodeStackSize; } Set::Iterator& Set::Iterator::operator++() { Q_ASSERT(d->nodeStackSize); if(d->repository->m_mutex) d->repository->m_mutex->lock(); ++d->currentIndex; //const SetNodeData** currentNode = &d->nodeStack[d->nodeStackSize - 1]; if(d->currentIndex >= d->nodeStack[d->nodeStackSize - 1]->end()) { //Advance to the next node while(d->nodeStackSize && d->currentIndex >= d->nodeStack[d->nodeStackSize - 1]->end()) { --d->nodeStackSize; } if(!d->nodeStackSize) { //ready }else{ //++d->nodeStackSize; //We were iterating the left slave of the node, now continue with the right. ifDebug( const SetNodeData& left = *d->repository->dataRepository.itemFromIndex(d->nodeStack[d->nodeStackSize - 1]->leftNode()); Q_ASSERT(left.end == d->currentIndex); ) const SetNodeData& right = *d->repository->dataRepository.itemFromIndex(d->nodeStack[d->nodeStackSize - 1]->rightNode()); d->startAtNode(&right); } } Q_ASSERT(d->nodeStackSize == 0 || d->currentIndex < d->nodeStack[0]->end()); if(d->repository->m_mutex) d->repository->m_mutex->unlock(); return *this; } BasicSetRepository::Index Set::Iterator::operator*() const { return d->currentIndex; } Set::Iterator Set::iterator() const { if(!m_tree || !m_repository) return Iterator(); QMutexLocker lock(m_repository->m_mutex); Iterator ret; ret.d->repository = m_repository; if(m_tree) ret.d->startAtNode(m_repository->dataRepository.itemFromIndex(m_tree)); return ret; } //Creates a set item with the given children., they must be valid, and they must be split around their split-position. uint SetRepositoryAlgorithms::createSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right) { if(!left) left = nodeFromIndex(leftNode); if(!right) right = nodeFromIndex(rightNode); Q_ASSERT(left->end() <= right->start()); SetNodeData set(left->start(), right->end(), leftNode, rightNode); Q_ASSERT(set.start() < set.end()); uint ret = repository.index(SetNodeDataRequest(&set, repository, setRepository)); Q_ASSERT(set.leftNode() >= 0x10000); Q_ASSERT(set.rightNode() >= 0x10000); Q_ASSERT(ret == repository.findIndex(SetNodeDataRequest(&set, repository, setRepository))); ifDebug( check(ret) ); return ret; } //Constructs a set node from the given two sub-nodes. Those must be valid, they must not intersect, and they must have a correct split-hierarchy. //The do not need to be split around their computed split-position. uint SetRepositoryAlgorithms::computeSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right, uchar splitBit) { Q_ASSERT(left->end() <= right->start()); uint splitPosition = splitPositionForRange(left->start(), right->end(), splitBit); Q_ASSERT(splitPosition); if(splitPosition < left->end()) { //The split-position intersects the left node uint leftLeftNode = left->leftNode(); uint leftRightNode = left->rightNode(); const SetNodeData* leftLeft = this->getLeftNode(left); const SetNodeData* leftRight = this->getRightNode(left); Q_ASSERT(splitPosition >= leftLeft->end() && splitPosition <= leftRight->start()); //Create a new set from leftLeft, and from leftRight + right. That set will have the correct split-position. uint newRightNode = computeSetFromNodes(leftRightNode, rightNode, leftRight, right, splitBit); return createSetFromNodes(leftLeftNode, newRightNode, leftLeft); }else if(splitPosition > right->start()) { //The split-position intersects the right node uint rightLeftNode = right->leftNode(); uint rightRightNode = right->rightNode(); const SetNodeData* rightLeft = this->getLeftNode(right); const SetNodeData* rightRight = this->getRightNode(right); Q_ASSERT(splitPosition >= rightLeft->end() && splitPosition <= rightRight->start()); //Create a new set from left + rightLeft, and from rightRight. That set will have the correct split-position. uint newLeftNode = computeSetFromNodes(leftNode, rightLeftNode, left, rightLeft, splitBit); - return createSetFromNodes(newLeftNode, rightRightNode, 0, rightRight); + return createSetFromNodes(newLeftNode, rightRightNode, nullptr, rightRight); }else{ return createSetFromNodes(leftNode, rightNode, left, right); } } uint SetRepositoryAlgorithms::set_union(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if(firstNode == secondNode) return firstNode; uint firstStart = first->start(), secondEnd = second->end(); if(firstStart >= secondEnd) return computeSetFromNodes(secondNode, firstNode, second, first, splitBit); uint firstEnd = first->end(), secondStart = second->start(); if(secondStart >= firstEnd) return computeSetFromNodes(firstNode, secondNode, first, second, splitBit); //The ranges of first and second do intersect uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if(splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the union on both sides of the split-position, and merge it. uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); return createSetFromNodes( set_union(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit), set_union(firstRightNode, secondRightNode, firstRight, secondRight, splitBit) ); }else if(splitPosition > firstStart && splitPosition < firstEnd) { uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we only need to union that side of first with second. if(secondEnd <= splitPosition) { - return createSetFromNodes( set_union(firstLeftNode, secondNode, firstLeft, second, splitBit), firstRightNode, 0, firstRight ); + return createSetFromNodes( set_union(firstLeftNode, secondNode, firstLeft, second, splitBit), firstRightNode, nullptr, firstRight ); }else{ Q_ASSERT(secondStart >= splitPosition); return createSetFromNodes( firstLeftNode, set_union(firstRightNode, secondNode, firstRight, second, splitBit), firstLeft ); } }else if(splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if(firstEnd <= splitPosition) { - return createSetFromNodes( set_union(secondLeftNode, firstNode, secondLeft, first, splitBit), secondRightNode, 0, secondRight ); + return createSetFromNodes( set_union(secondLeftNode, firstNode, secondLeft, first, splitBit), secondRightNode, nullptr, secondRight ); }else{ Q_ASSERT(firstStart >= splitPosition); return createSetFromNodes( secondLeftNode, set_union(secondRightNode, firstNode, secondRight, first, splitBit), secondLeft ); } }else{ //We would have stopped earlier of first and second don't intersect ifDebug( uint test = repository.findIndex(SetNodeDataRequest(first, repository, setRepository)); qCDebug(LANGUAGE) << "found index:" << test; ) Q_ASSERT(0); return 0; } } bool SetRepositoryAlgorithms::set_equals(const SetNodeData* lhs, const SetNodeData* rhs) { if(lhs->leftNode() != rhs->leftNode() || lhs->rightNode() != rhs->rightNode()) return false; else return true; } uint SetRepositoryAlgorithms::set_intersect(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if(firstNode == secondNode) return firstNode; if(first->start() >= second->end()) return 0; if(second->start() >= first->end()) return 0; //The ranges of first and second do intersect uint firstStart = first->start(), firstEnd = first->end(), secondStart = second->start(), secondEnd = second->end(); uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if(splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the intersection on both sides uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); uint newLeftNode = set_intersect(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit); uint newRightNode = set_intersect(firstRightNode, secondRightNode, firstRight, secondRight, splitBit); if(newLeftNode && newRightNode) return createSetFromNodes( newLeftNode, newRightNode ); else if(newLeftNode) return newLeftNode; else return newRightNode; }else if(splitPosition > firstStart && splitPosition < firstEnd) { uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we can completely ignore the other side of first. if(secondEnd <= splitPosition) { return set_intersect(firstLeftNode, secondNode, firstLeft, second, splitBit); }else{ Q_ASSERT(secondStart >= splitPosition); return set_intersect(firstRightNode, secondNode, firstRight, second, splitBit); } }else if(splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if(firstEnd <= splitPosition) { return set_intersect(secondLeftNode, firstNode, secondLeft, first, splitBit); }else{ Q_ASSERT(firstStart >= splitPosition); return set_intersect(secondRightNode, firstNode, secondRight, first, splitBit); } }else{ //We would have stopped earlier of first and second don't intersect Q_ASSERT(0); return 0; } Q_ASSERT(0); } bool SetRepositoryAlgorithms::set_contains(const SetNodeData* node, Index index) { while(true) { if(node->start() > index || node->end() <= index) return false; if(node->contiguous()) return true; const SetNodeData* leftNode = nodeFromIndex(node->leftNode()); if(index < leftNode->end()) node = leftNode; else { const SetNodeData* rightNode = nodeFromIndex(node->rightNode()); node = rightNode; } } return false; } uint SetRepositoryAlgorithms::set_subtract(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if(firstNode == secondNode) return 0; if(first->start() >= second->end() || second->start() >= first->end()) return firstNode; //The ranges of first and second do intersect uint firstStart = first->start(), firstEnd = first->end(), secondStart = second->start(), secondEnd = second->end(); uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if(splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the subtract on both sides of the split-position, and merge it. uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); uint newLeftNode = set_subtract(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit); uint newRightNode = set_subtract(firstRightNode, secondRightNode, firstRight, secondRight, splitBit); if(newLeftNode && newRightNode) return createSetFromNodes(newLeftNode, newRightNode); else if(newLeftNode) return newLeftNode; else return newRightNode; }else if(splitPosition > firstStart && splitPosition < firstEnd) { // Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we only need to subtract that side of first with second. uint newLeftNode = firstLeftNode, newRightNode = firstRightNode; if(secondEnd <= splitPosition) { newLeftNode = set_subtract(firstLeftNode, secondNode, firstLeft, second, splitBit); }else{ Q_ASSERT(secondStart >= splitPosition); newRightNode = set_subtract(firstRightNode, secondNode, firstRight, second, splitBit); } if(newLeftNode && newRightNode) return createSetFromNodes(newLeftNode, newRightNode); else if(newLeftNode) return newLeftNode; else return newRightNode; }else if(splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if(firstEnd <= splitPosition) { return set_subtract(firstNode, secondLeftNode, first, secondLeft, splitBit); }else{ Q_ASSERT(firstStart >= splitPosition); return set_subtract(firstNode, secondRightNode, first, secondRight, splitBit); } }else{ //We would have stopped earlier of first and second don't intersect Q_ASSERT(0); return 0; } Q_ASSERT(0); } Set BasicSetRepository::createSetFromIndices(const std::vector& indices) { QMutexLocker lock(m_mutex); if(indices.empty()) return Set(); SetRepositoryAlgorithms alg(dataRepository, this); return Set(alg.setForIndices(indices.begin(), indices.end()), this); } Set BasicSetRepository::createSet(Index i) { QMutexLocker lock(m_mutex); SetNodeData data(i, i+1); return Set(dataRepository.index( SetNodeDataRequest(&data, dataRepository, this) ), this); } Set BasicSetRepository::createSet(const std::set& indices) { if(indices.empty()) return Set(); QMutexLocker lock(m_mutex); std::vector indicesVector; indicesVector.reserve(indices.size()); for( std::set::const_iterator it = indices.begin(); it != indices.end(); ++it ) indicesVector.push_back(*it); return createSetFromIndices(indicesVector); } -BasicSetRepository::BasicSetRepository(QString name, KDevelop::ItemRepositoryRegistry* registry, bool delayedDeletion) : d(new Private(name)), dataRepository(this, name, registry), m_mutex(0), m_delayedDeletion(delayedDeletion) { +BasicSetRepository::BasicSetRepository(QString name, KDevelop::ItemRepositoryRegistry* registry, bool delayedDeletion) : d(new Private(name)), dataRepository(this, name, registry), m_mutex(nullptr), m_delayedDeletion(delayedDeletion) { m_mutex = dataRepository.mutex(); } struct StatisticsVisitor { StatisticsVisitor(const SetDataRepository& _rep) : nodeCount(0), badSplitNodeCount(0), zeroRefCountNodes(0), rep(_rep) { } bool operator() (const SetNodeData* item) { if(item->m_refCount == 0) ++zeroRefCountNodes; ++nodeCount; uint split = splitPositionForRange(item->start(), item->end()); if(item->hasSlaves()) if(split < rep.itemFromIndex(item->leftNode())->end() || split > rep.itemFromIndex(item->rightNode())->start()) ++badSplitNodeCount; return true; } uint nodeCount; uint badSplitNodeCount; uint zeroRefCountNodes; const SetDataRepository& rep; }; void BasicSetRepository::printStatistics() const { StatisticsVisitor stats(dataRepository); dataRepository.visitAllItems(stats); qCDebug(LANGUAGE) << "count of nodes:" << stats.nodeCount << "count of nodes with bad split:" << stats.badSplitNodeCount << "count of nodes with zero reference-count:" << stats.zeroRefCountNodes; } BasicSetRepository::~BasicSetRepository() { delete d; } void BasicSetRepository::itemRemovedFromSets(uint /*index*/) { } void BasicSetRepository::itemAddedToSets(uint /*index*/) { } ////////////Set convenience functions////////////////// bool Set::contains(Index index) const { if(!m_tree || !m_repository) return false; QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); return alg.set_contains(m_repository->dataRepository.itemFromIndex(m_tree), index); } Set Set::operator +(const Set& first) const { if(!first.m_tree) return *this; else if(!m_tree || !m_repository) return first; Q_ASSERT(m_repository == first.m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); uint retNode = alg.set_union(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(retNode)); return Set(retNode, m_repository); } Set& Set::operator +=(const Set& first) { if(!first.m_tree) return *this; else if(!m_tree || !m_repository) { m_tree = first.m_tree; m_repository = first.m_repository; return *this; } QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_union(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator &(const Set& first) const { if(!first.m_tree || !m_tree) return Set(); Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); Set ret( alg.set_intersect(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)), m_repository ); ifDebug(alg.check(ret.m_tree)); return ret; } Set& Set::operator &=(const Set& first) { if(!first.m_tree || !m_tree) { m_tree = 0; return *this; } Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_intersect(m_tree, first.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator -(const Set& rhs) const { if(!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); Set ret( alg.set_subtract(m_tree, rhs.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(rhs.m_tree)), m_repository ); ifDebug( alg.check(ret.m_tree) ); return ret; } Set& Set::operator -=(const Set& rhs) { if(!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->dataRepository, m_repository); m_tree = alg.set_subtract(m_tree, rhs.m_tree, m_repository->dataRepository.itemFromIndex(m_tree), m_repository->dataRepository.itemFromIndex(rhs.m_tree)); ifDebug(alg.check(m_tree)); return *this; } BasicSetRepository* Set::repository() const { return m_repository; } void Set::staticRef() { if(!m_tree) return; QMutexLocker lock(m_repository->m_mutex); SetNodeData* data = m_repository->dataRepository.dynamicItemFromIndexSimple(m_tree); ++data->m_refCount; } ///Mutex must be locked void Set::unrefNode(uint current) { SetNodeData* data = m_repository->dataRepository.dynamicItemFromIndexSimple(current); Q_ASSERT(data->m_refCount); --data->m_refCount; if(!m_repository->delayedDeletion()) { if(data->m_refCount == 0) { if(data->leftNode()){ Q_ASSERT(data->rightNode()); unrefNode(data->rightNode()); unrefNode(data->leftNode()); }else { //Deleting a leaf Q_ASSERT(data->end() - data->start() == 1); m_repository->itemRemovedFromSets(data->start()); } m_repository->dataRepository.deleteItem(current); } } } ///Decrease the static reference-count of this set by one. This set must have a reference-count > 1. ///If this set reaches the reference-count zero, it will be deleted, and all sub-nodes that also reach the reference-count zero ///will be deleted as well. @warning Either protect ALL your sets by using reference-counting, or don't use it at all. void Set::staticUnref() { if(!m_tree) return; QMutexLocker lock(m_repository->m_mutex); unrefNode(m_tree); } StringSetRepository::StringSetRepository(QString name) : Utils::BasicSetRepository(name) { } void StringSetRepository::itemRemovedFromSets(uint index) { ///Call the IndexedString destructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); KDevelop::enableDUChainReferenceCounting(&string, sizeof(KDevelop::IndexedString)); string.~IndexedString(); //Call destructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(&string); } void StringSetRepository::itemAddedToSets(uint index) { ///Call the IndexedString constructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); char data[sizeof(KDevelop::IndexedString)]; KDevelop::enableDUChainReferenceCounting(data, sizeof(KDevelop::IndexedString)); new (data) KDevelop::IndexedString(string); //Call constructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(data); } } diff --git a/outputview/outputjob.cpp b/outputview/outputjob.cpp index 6a6d1d6967..26064902bd 100644 --- a/outputview/outputjob.cpp +++ b/outputview/outputjob.cpp @@ -1,183 +1,183 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda 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 "outputjob.h" #include #include #include "interfaces/icore.h" #include "interfaces/iplugincontroller.h" #include "outputview/ioutputview.h" using namespace KDevelop; OutputJob::OutputJob(QObject* parent, OutputJobVerbosity verbosity) : KJob(parent) , m_standardToolView(-1) , m_type(IOutputView::OneView) , m_behaviours(IOutputView::AllowUserClose) , m_killJobOnOutputClose(true) , m_verbosity(verbosity) , m_outputId(-1) - , m_outputDelegate(0) + , m_outputDelegate(nullptr) { } void OutputJob::startOutput() { IPlugin* i = ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IOutputView")); if( i ) { KDevelop::IOutputView* view = i->extension(); if( view ) { int tvid; if (m_standardToolView != -1) { tvid = view->standardToolView( static_cast(m_standardToolView) ); } else { tvid = view->registerToolView(m_toolTitle, m_type, m_toolIcon); } if (m_title.isEmpty()) m_title = objectName(); m_outputId = view->registerOutputInToolView( tvid, m_title, m_behaviours ); if (!m_outputModel) { - m_outputModel = new QStandardItemModel(0); + m_outputModel = new QStandardItemModel(nullptr); } // Keep the item model around after the job is gone view->setModel(m_outputId, m_outputModel); if (!m_outputDelegate) { - m_outputDelegate = new QItemDelegate(0); + m_outputDelegate = new QItemDelegate(nullptr); } view->setDelegate(m_outputId, m_outputDelegate); if (m_killJobOnOutputClose) { // can't use qt5 signal slot syntax here, IOutputView is no a QObject connect(i, SIGNAL(outputRemoved(int,int)), this, SLOT(outputViewRemoved(int,int))); } if (m_verbosity == OutputJob::Verbose) view->raiseOutput(m_outputId); } } } void OutputJob::outputViewRemoved(int toolViewId, int id) { Q_UNUSED(toolViewId); if (id == m_outputId && m_killJobOnOutputClose) { // Make sure that the job emits result signal as the job // might be used in composite jobs and that one depends // on result being emitted to know whether a subjob // is done. kill( KJob::EmitResult ); } } void KDevelop::OutputJob::setTitle(const QString & title) { m_title = title; if (m_outputId >= 0 && m_standardToolView >= 0) { IPlugin* i = ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IOutputView")); if( i ) { KDevelop::IOutputView* view = i->extension(); if( view ) { view->setTitle(m_outputId, title); } } } } void KDevelop::OutputJob::setViewType(IOutputView::ViewType type) { m_type = type; } void KDevelop::OutputJob::setBehaviours(IOutputView::Behaviours behaviours) { m_behaviours = behaviours; } void KDevelop::OutputJob::setKillJobOnOutputClose(bool killJobOnOutputClose) { m_killJobOnOutputClose = killJobOnOutputClose; } void KDevelop::OutputJob::setModel(QAbstractItemModel * model) { if (m_outputModel) { delete m_outputModel; } m_outputModel = model; if (m_outputModel) { m_outputModel->setParent(this); } } void KDevelop::OutputJob::setDelegate(QAbstractItemDelegate * delegate) { m_outputDelegate = delegate; } QAbstractItemModel * KDevelop::OutputJob::model() const { return m_outputModel; } void KDevelop::OutputJob::setStandardToolView(IOutputView::StandardToolView standard) { m_standardToolView = standard; } void OutputJob::setToolTitle(const QString& title) { m_toolTitle = title; } void OutputJob::setToolIcon(const QIcon& icon) { m_toolIcon = icon; } int OutputJob::outputId() const { return m_outputId; } OutputJob::OutputJobVerbosity OutputJob::verbosity() const { return m_verbosity; } void OutputJob::setVerbosity(OutputJob::OutputJobVerbosity verbosity) { m_verbosity = verbosity; } diff --git a/outputview/outputmodel.cpp b/outputview/outputmodel.cpp index a539958881..50fbfce6d5 100644 --- a/outputview/outputmodel.cpp +++ b/outputview/outputmodel.cpp @@ -1,468 +1,468 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "outputmodel.h" #include "filtereditem.h" #include "outputfilteringstrategies.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QVector) namespace KDevelop { /** * Number of lines that are processed in one go before we notify the GUI thread * about the result. It is generally faster to add multiple items to a model * in one go compared to adding each item independently. */ static const int BATCH_SIZE = 50; /** * Time in ms that we wait in the parse worker for new incoming lines before * actually processing them. If we already have enough for one batch though * we process immediately. */ static const int BATCH_AGGREGATE_TIME_DELAY = 50; class ParseWorker : public QObject { Q_OBJECT public: ParseWorker() - : QObject(0) + : QObject(nullptr) , m_filter(new NoFilterStrategy) , m_timer(new QTimer(this)) { m_timer->setInterval(BATCH_AGGREGATE_TIME_DELAY); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &ParseWorker::process); } public slots: void changeFilterStrategy( KDevelop::IFilterStrategy* newFilterStrategy ) { m_filter = QSharedPointer( newFilterStrategy ); } void addLines( const QStringList& lines ) { m_cachedLines << lines; if (m_cachedLines.size() >= BATCH_SIZE) { // if enough lines were added, process immediately m_timer->stop(); process(); } else if (!m_timer->isActive()) { m_timer->start(); } } void flushBuffers() { m_timer->stop(); process(); emit allDone(); } signals: void parsedBatch(const QVector& filteredItems); void progress(const KDevelop::IFilterStrategy::Progress& progress); void allDone(); private slots: /** * Process *all* cached lines, emit parsedBatch for each batch */ void process() { QVector filteredItems; filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); // apply pre-filtering functions std::transform(m_cachedLines.constBegin(), m_cachedLines.constEnd(), m_cachedLines.begin(), &KDevelop::stripAnsiSequences); // apply filtering strategy foreach(const QString& line, m_cachedLines) { FilteredItem item = m_filter->errorInLine(line); if( item.type == FilteredItem::InvalidItem ) { item = m_filter->actionInLine(line); } filteredItems << item; auto progress = m_filter->progressInLine(line); if (progress.percent >= 0 && m_progress.percent != progress.percent) { m_progress = progress; emit this->progress(m_progress); } if( filteredItems.size() == BATCH_SIZE ) { emit parsedBatch(filteredItems); filteredItems.clear(); filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); } } // Make sure to emit the rest as well if( !filteredItems.isEmpty() ) { emit parsedBatch(filteredItems); } m_cachedLines.clear(); } private: QSharedPointer m_filter; QStringList m_cachedLines; QTimer* m_timer; IFilterStrategy::Progress m_progress; }; class ParsingThread { public: ParsingThread() { m_thread.setObjectName(QStringLiteral("OutputFilterThread")); } virtual ~ParsingThread() { if (m_thread.isRunning()) { m_thread.quit(); m_thread.wait(); } } void addWorker(ParseWorker* worker) { if (!m_thread.isRunning()) { m_thread.start(); } worker->moveToThread(&m_thread); } private: QThread m_thread; }; Q_GLOBAL_STATIC(ParsingThread, s_parsingThread); struct OutputModelPrivate { OutputModelPrivate( OutputModel* model, const QUrl& builddir = QUrl() ); ~OutputModelPrivate(); bool isValidIndex( const QModelIndex&, int currentRowCount ) const; OutputModel* model; ParseWorker* worker; QVector m_filteredItems; // We use std::set because that is ordered std::set m_errorItems; // Indices of all items that we want to move to using previous and next QUrl m_buildDir; void linesParsed(const QVector& items) { model->beginInsertRows( QModelIndex(), model->rowCount(), model->rowCount() + items.size() - 1); foreach( const FilteredItem& item, items ) { if( item.type == FilteredItem::ErrorItem ) { m_errorItems.insert(m_filteredItems.size()); } m_filteredItems << item; } model->endInsertRows(); } }; OutputModelPrivate::OutputModelPrivate( OutputModel* model_, const QUrl& builddir) : model(model_) , worker(new ParseWorker ) , m_buildDir( builddir ) { qRegisterMetaType >(); qRegisterMetaType(); qRegisterMetaType(); s_parsingThread->addWorker(worker); model->connect(worker, &ParseWorker::parsedBatch, model, [=] (const QVector& items) { linesParsed(items); }); model->connect(worker, &ParseWorker::allDone, model, &OutputModel::allDone); model->connect(worker, &ParseWorker::progress, model, &OutputModel::progress); } bool OutputModelPrivate::isValidIndex( const QModelIndex& idx, int currentRowCount ) const { return ( idx.isValid() && idx.row() >= 0 && idx.row() < currentRowCount && idx.column() == 0 ); } OutputModelPrivate::~OutputModelPrivate() { worker->deleteLater(); } OutputModel::OutputModel( const QUrl& builddir, QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this, builddir ) ) { } OutputModel::OutputModel( QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this ) ) { } OutputModel::~OutputModel() { delete d; } QVariant OutputModel::data(const QModelIndex& idx , int role ) const { if( d->isValidIndex(idx, rowCount()) ) { switch( role ) { case Qt::DisplayRole: return d->m_filteredItems.at( idx.row() ).originalLine; break; case OutputModel::OutputItemTypeRole: return static_cast(d->m_filteredItems.at( idx.row() ).type); break; case Qt::FontRole: return QFontDatabase::systemFont(QFontDatabase::FixedFont); break; default: break; } } return QVariant(); } int OutputModel::rowCount( const QModelIndex& parent ) const { if( !parent.isValid() ) return d->m_filteredItems.count(); return 0; } QVariant OutputModel::headerData( int, Qt::Orientation, int ) const { return QVariant(); } void OutputModel::activate( const QModelIndex& index ) { if( index.model() != this || !d->isValidIndex(index, rowCount()) ) { return; } qCDebug(OUTPUTVIEW) << "Model activated" << index.row(); FilteredItem item = d->m_filteredItems.at( index.row() ); if( item.isActivatable ) { qCDebug(OUTPUTVIEW) << "activating:" << item.lineNo << item.url; KTextEditor::Cursor range( item.lineNo, item.columnNo ); KDevelop::IDocumentController *docCtrl = KDevelop::ICore::self()->documentController(); QUrl url = item.url; if (item.url.isEmpty()) { qWarning() << "trying to open empty url"; return; } if(url.isRelative()) { url = d->m_buildDir.resolved(url); } Q_ASSERT(!url.isRelative()); docCtrl->openDocument( url, range ); } else { qCDebug(OUTPUTVIEW) << "not an activateable item"; } } QModelIndex OutputModel::firstHighlightIndex() { if( !d->m_errorItems.empty() ) { return index( *d->m_errorItems.begin(), 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { if( d->m_filteredItems.at( row ).isActivatable ) { return index( row, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::nextHighlightIndex( const QModelIndex ¤tIdx ) { int startrow = d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() + 1 : 0; if( !d->m_errorItems.empty() ) { qCDebug(OUTPUTVIEW) << "searching next error"; // Jump to the next error item std::set< int >::const_iterator next = d->m_errorItems.lower_bound( startrow ); if( next == d->m_errorItems.end() ) next = d->m_errorItems.begin(); return index( *next, 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { int currow = (startrow + row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::previousHighlightIndex( const QModelIndex ¤tIdx ) { //We have to ensure that startrow is >= rowCount - 1 to get a positive value from the % operation. int startrow = rowCount() + (d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() : rowCount()) - 1; if(!d->m_errorItems.empty()) { qCDebug(OUTPUTVIEW) << "searching previous error"; // Jump to the previous error item std::set< int >::const_iterator previous = d->m_errorItems.lower_bound( currentIdx.row() ); if( previous == d->m_errorItems.begin() ) previous = d->m_errorItems.end(); --previous; return index( *previous, 0, QModelIndex() ); } for ( int row = 0; row < rowCount(); ++row ) { int currow = (startrow - row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::lastHighlightIndex() { if( !d->m_errorItems.empty() ) { return index( *d->m_errorItems.rbegin(), 0, QModelIndex() ); } for( int row = rowCount()-1; row >=0; --row ) { if( d->m_filteredItems.at( row ).isActivatable ) { return index( row, 0, QModelIndex() ); } } return QModelIndex(); } void OutputModel::setFilteringStrategy(const OutputFilterStrategy& currentStrategy) { // TODO: Turn into factory, decouple from OutputModel - IFilterStrategy* filter = 0; + IFilterStrategy* filter = nullptr; switch( currentStrategy ) { case NoFilter: filter = new NoFilterStrategy; break; case CompilerFilter: filter = new CompilerFilterStrategy( d->m_buildDir ); break; case ScriptErrorFilter: filter = new ScriptErrorFilterStrategy; break; case NativeAppErrorFilter: filter = new NativeAppErrorFilterStrategy; break; case StaticAnalysisFilter: filter = new StaticAnalysisFilterStrategy; break; default: // assert(false); filter = new NoFilterStrategy; break; } Q_ASSERT(filter); QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filter)); } void OutputModel::setFilteringStrategy(IFilterStrategy* filterStrategy) { QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filterStrategy)); } void OutputModel::appendLines( const QStringList& lines ) { if( lines.isEmpty() ) return; QMetaObject::invokeMethod(d->worker, "addLines", Q_ARG(QStringList, lines)); } void OutputModel::appendLine( const QString& l ) { appendLines( QStringList() << l ); } void OutputModel::ensureAllDone() { QMetaObject::invokeMethod(d->worker, "flushBuffers"); } } #include "outputmodel.moc" #include "moc_outputmodel.cpp" diff --git a/plugins/appwizard/appwizardplugin.cpp b/plugins/appwizard/appwizardplugin.cpp index d06b6bc70e..59d17a5e0d 100644 --- a/plugins/appwizard/appwizardplugin.cpp +++ b/plugins/appwizard/appwizardplugin.cpp @@ -1,516 +1,516 @@ /*************************************************************************** * Copyright 2001 Bernd Gehrmann * * Copyright 2004-2005 Sascha Cunz * * Copyright 2005 Ian Reinhart Geiser * * Copyright 2007 Alexander Dymo * * Copyright 2008 Evgeniy Ivanov * * * * 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. * * * ***************************************************************************/ #include "appwizardplugin.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 #include #include #include #include #include #include #include "appwizarddialog.h" #include "projectselectionpage.h" #include "projectvcspage.h" #include "projecttemplatesmodel.h" #include "debug.h" using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_APPWIZARD, "kdevplatform.plugins.appwizard") K_PLUGIN_FACTORY_WITH_JSON(AppWizardFactory, "kdevappwizard.json", registerPlugin();) AppWizardPlugin::AppWizardPlugin(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevappwizard"), parent) - , m_templatesModel(0) + , m_templatesModel(nullptr) { KDEV_USE_EXTENSION_INTERFACE(KDevelop::ITemplateProvider); setXMLFile(QStringLiteral("kdevappwizard.rc")); m_newFromTemplate = actionCollection()->addAction(QStringLiteral("project_new")); m_newFromTemplate->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template"))); m_newFromTemplate->setText(i18n("New From Template...")); connect(m_newFromTemplate, &QAction::triggered, this, &AppWizardPlugin::slotNewProject); m_newFromTemplate->setToolTip( i18n("Generate a new project from a template") ); m_newFromTemplate->setWhatsThis( i18n("This starts KDevelop's application wizard. " "It helps you to generate a skeleton for your " "application from a set of templates.") ); } AppWizardPlugin::~AppWizardPlugin() { } void AppWizardPlugin::slotNewProject() { model()->refresh(); AppWizardDialog dlg(core()->pluginController(), m_templatesModel); if (dlg.exec() == QDialog::Accepted) { QString project = createProject( dlg.appInfo() ); if (!project.isEmpty()) { core()->projectController()->openProject(QUrl::fromLocalFile(project)); KConfig templateConfig(dlg.appInfo().appTemplate); KConfigGroup general(&templateConfig, "General"); QString file = general.readEntry("ShowFilesAfterGeneration"); if (!file.isEmpty()) { file = KMacroExpander::expandMacros(file, m_variables); core()->documentController()->openDocument(QUrl::fromUserInput(file)); } } else { KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n("Could not create project from template\n"), i18n("Failed to create project") ); } } } namespace { IDistributedVersionControl* toDVCS(IPlugin* plugin) { Q_ASSERT(plugin); return plugin->extension(); } ICentralizedVersionControl* toCVCS(IPlugin* plugin) { Q_ASSERT(plugin); return plugin->extension(); } /*! Trouble while initializing version control. Show failure message to user. */ void vcsError(const QString &errorMsg, QTemporaryDir &tmpdir, const QUrl &dest, const QString &details = QString()) { QString displayDetails = details; if (displayDetails.isEmpty()) { displayDetails = i18n("Please see the Version Control toolview"); } - KMessageBox::detailedError(0, errorMsg, displayDetails, i18n("Version Control System Error")); + KMessageBox::detailedError(nullptr, errorMsg, displayDetails, i18n("Version Control System Error")); KIO::del(dest)->exec(); tmpdir.remove(); } /*! Setup distributed version control for a new project defined by @p info. Use @p scratchArea for temporary files */ bool initializeDVCS(IDistributedVersionControl* dvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea) { Q_ASSERT(dvcs); qCDebug(PLUGIN_APPWIZARD) << "DVCS system is used, just initializing DVCS"; const QUrl& dest = info.location; //TODO: check if we want to handle KDevelop project files (like now) or only SRC dir VcsJob* job = dvcs->init(dest); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not initialize DVCS repository"), scratchArea, dest); return false; } qCDebug(PLUGIN_APPWIZARD) << "Initializing DVCS repository:" << dest; job = dvcs->add({dest}, KDevelop::IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not add files to the DVCS repository"), scratchArea, dest); return false; } job = dvcs->commit(QStringLiteral("initial project import from KDevelop"), {dest}, KDevelop::IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not import project into %1.", dvcs->name()), scratchArea, dest, job ? job->errorString() : QString()); return false; } return true; // We're good } /*! Setup version control for a new project defined by @p info. Use @p scratchArea for temporary files */ bool initializeCVCS(ICentralizedVersionControl* cvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea) { Q_ASSERT(cvcs); qCDebug(PLUGIN_APPWIZARD) << "Importing" << info.sourceLocation << "to" << info.repository.repositoryServer(); VcsJob* job = cvcs->import( info.importCommitMessage, QUrl::fromLocalFile(scratchArea.path()), info.repository); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded ) { vcsError(i18n("Could not import project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer())); return false; } qCDebug(PLUGIN_APPWIZARD) << "Checking out"; job = cvcs->createWorkingCopy( info.repository, info.location, IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded ) { vcsError(i18n("Could not checkout imported project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer())); return false; } return true; // initialization phase complete } QString generateIdentifier( const QString& appname ) { QString tmp = appname; QRegExp re("[^a-zA-Z0-9_]"); return tmp.replace(re, QStringLiteral("_")); } } // end anonymous namespace QString AppWizardPlugin::createProject(const ApplicationInfo& info) { QFileInfo templateInfo(info.appTemplate); if (!templateInfo.exists()) { qWarning() << "Project app template does not exist:" << info.appTemplate; return QString(); } QString templateName = templateInfo.baseName(); QString templateArchive; const QStringList filters = {templateName + QStringLiteral(".*")}; const QStringList matchesPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevappwizard/templates/"), QStandardPaths::LocateDirectory); foreach(const QString& matchesPath, matchesPaths) { const QStringList files = QDir(matchesPath).entryList(filters); if(!files.isEmpty()) { templateArchive = matchesPath + files.first(); } } if(templateArchive.isEmpty()) { qWarning() << "Template name does not exist in the template list"; return QString(); } QUrl dest = info.location; //prepare variable substitution hash m_variables.clear(); m_variables[QStringLiteral("APPNAME")] = info.name; m_variables[QStringLiteral("APPNAMEUC")] = info.name.toUpper(); m_variables[QStringLiteral("APPNAMELC")] = info.name.toLower(); m_variables[QStringLiteral("APPNAMEID")] = generateIdentifier(info.name); m_variables[QStringLiteral("PROJECTDIR")] = dest.toLocalFile(); // backwards compatibility m_variables[QStringLiteral("dest")] = m_variables[QStringLiteral("PROJECTDIR")]; m_variables[QStringLiteral("PROJECTDIRNAME")] = dest.fileName(); m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = info.vcsPluginName; - KArchive* arch = 0; + KArchive* arch = nullptr; if( templateArchive.endsWith(QLatin1String(".zip")) ) { arch = new KZip(templateArchive); } else { arch = new KTar(templateArchive, QStringLiteral("application/x-bzip")); } if (arch->open(QIODevice::ReadOnly)) { QTemporaryDir tmpdir; QString unpackDir = tmpdir.path(); //the default value for all Centralized VCS IPlugin* plugin = core()->pluginController()->loadPlugin( info.vcsPluginName ); if( info.vcsPluginName.isEmpty() || ( plugin && plugin->extension() ) ) { if( !QFileInfo::exists( dest.toLocalFile() ) ) { QDir::root().mkpath( dest.toLocalFile() ); } unpackDir = dest.toLocalFile(); //in DVCS we unpack template directly to the project's directory } else { QUrl url = KIO::upUrl(dest); if(!QFileInfo::exists(url.toLocalFile())) { QDir::root().mkpath(url.toLocalFile()); } } if ( !unpackArchive( arch->directory(), unpackDir ) ) { QString errorMsg = i18n("Could not create new project"); vcsError(errorMsg, tmpdir, QUrl::fromLocalFile(unpackDir)); return QString(); } if( !info.vcsPluginName.isEmpty() ) { if (!plugin) { // Red Alert, serious program corruption. // This should never happen, the vcs dialog presented a list of vcs // systems and now the chosen system doesn't exist anymore?? tmpdir.remove(); return QString(); } IDistributedVersionControl* dvcs = toDVCS(plugin); ICentralizedVersionControl* cvcs = toCVCS(plugin); bool success = false; if (dvcs) { success = initializeDVCS(dvcs, info, tmpdir); } else if (cvcs) { success = initializeCVCS(cvcs, info, tmpdir); } else { if (KMessageBox::Continue == - KMessageBox::warningContinueCancel(0, + KMessageBox::warningContinueCancel(nullptr, QStringLiteral("Failed to initialize version control system, " "plugin is neither VCS nor DVCS."))) success = true; } if (!success) return QString(); } tmpdir.remove(); }else { qCDebug(PLUGIN_APPWIZARD) << "failed to open template archive"; return QString(); } QString projectFileName = QDir::cleanPath( dest.toLocalFile() + '/' + info.name + ".kdev4" ); // Loop through the new project directory and try to detect the first .kdev4 file. // If one is found this file will be used. So .kdev4 file can be stored in any subdirectory and the // project templates can be more complex. QDirIterator it(QDir::cleanPath( dest.toLocalFile()), QStringList() << QStringLiteral("*.kdev4"), QDir::NoFilter, QDirIterator::Subdirectories); if(it.hasNext() == true) { projectFileName = it.next(); } qCDebug(PLUGIN_APPWIZARD) << "Returning" << projectFileName << QFileInfo::exists( projectFileName ) ; if( ! QFileInfo::exists( projectFileName ) ) { qCDebug(PLUGIN_APPWIZARD) << "creating .kdev4 file"; KSharedConfigPtr cfg = KSharedConfig::openConfig( projectFileName, KConfig::SimpleConfig ); KConfigGroup project = cfg->group( "Project" ); project.writeEntry( "Name", info.name ); QString manager = QStringLiteral("KDevGenericManager"); QDir d( dest.toLocalFile() ); auto data = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); foreach(const KPluginMetaData& info, data) { QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); if (!filter.isEmpty()) { if (!d.entryList(filter).isEmpty()) { manager = info.pluginId(); break; } } } project.writeEntry( "Manager", manager ); project.sync(); cfg->sync(); KConfigGroup project2 = cfg->group( "Project" ); qCDebug(PLUGIN_APPWIZARD) << "kdev4 file contents:" << project2.readEntry("Name", "") << project2.readEntry("Manager", "" ); } return projectFileName; } bool AppWizardPlugin::unpackArchive(const KArchiveDirectory *dir, const QString &dest) { qCDebug(PLUGIN_APPWIZARD) << "unpacking dir:" << dir->name() << "to" << dest; const QStringList entries = dir->entries(); qCDebug(PLUGIN_APPWIZARD) << "entries:" << entries.join(QStringLiteral(",")); //This extra tempdir is needed just for the files files have special names, //which may contain macros also files contain content with macros. So the //easiest way to extract the files from the archive and then rename them //and replace the macros is to use a tempdir and copy the file (and //replacing while copying). This also allows one to easily remove all files, //by just unlinking the tempdir QTemporaryDir tdir; bool ret = true; foreach (const QString& entry, entries) { if (entry.endsWith(QLatin1String(".kdevtemplate"))) continue; if (dir->entry(entry)->isDirectory()) { const KArchiveDirectory *file = (KArchiveDirectory *)dir->entry(entry); QString newdest = dest + '/' + KMacroExpander::expandMacros(file->name(), m_variables); if( !QFileInfo::exists( newdest ) ) { QDir::root().mkdir( newdest ); } ret |= unpackArchive(file, newdest); } else if (dir->entry(entry)->isFile()) { const KArchiveFile *file = (KArchiveFile *)dir->entry(entry); file->copyTo(tdir.path()); QString destName = dest + '/' + file->name(); if (!copyFileAndExpandMacros(QDir::cleanPath(tdir.path()+'/'+file->name()), KMacroExpander::expandMacros(destName, m_variables))) { - KMessageBox::sorry(0, i18n("The file %1 cannot be created.", dest)); + KMessageBox::sorry(nullptr, i18n("The file %1 cannot be created.", dest)); return false; } } } tdir.remove(); return ret; } bool AppWizardPlugin::copyFileAndExpandMacros(const QString &source, const QString &dest) { qCDebug(PLUGIN_APPWIZARD) << "copy:" << source << "to" << dest; QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(source); if( !mime.inherits(QStringLiteral("text/plain")) ) { KIO::CopyJob* job = KIO::copy( QUrl::fromUserInput(source), QUrl::fromUserInput(dest), KIO::HideProgressInfo ); if( !job->exec() ) { return false; } return true; } else { QFile inputFile(source); QFile outputFile(dest); if (inputFile.open(QFile::ReadOnly) && outputFile.open(QFile::WriteOnly)) { QTextStream input(&inputFile); input.setCodec(QTextCodec::codecForName("UTF-8")); QTextStream output(&outputFile); output.setCodec(QTextCodec::codecForName("UTF-8")); while(!input.atEnd()) { QString line = input.readLine(); output << KMacroExpander::expandMacros(line, m_variables) << "\n"; } #ifndef Q_OS_WIN // Preserve file mode... QT_STATBUF statBuf; QT_FSTAT(inputFile.handle(), &statBuf); // Unix only, won't work in Windows, maybe KIO::chmod could be used ::fchmod(outputFile.handle(), statBuf.st_mode); #endif return true; } else { inputFile.close(); outputFile.close(); return false; } } } KDevelop::ContextMenuExtension AppWizardPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension ext; if ( context->type() != KDevelop::Context::ProjectItemContext || !static_cast(context)->items().isEmpty() ) { return ext; } ext.addAction(KDevelop::ContextMenuExtension::ProjectGroup, m_newFromTemplate); return ext; } ProjectTemplatesModel* AppWizardPlugin::model() { if(!m_templatesModel) m_templatesModel = new ProjectTemplatesModel(this); return m_templatesModel; } QAbstractItemModel* AppWizardPlugin::templatesModel() { return model(); } QString AppWizardPlugin::knsConfigurationFile() const { return QStringLiteral("kdevappwizard.knsrc"); } QStringList AppWizardPlugin::supportedMimeTypes() const { QStringList types; types << QStringLiteral("application/x-desktop"); types << QStringLiteral("application/x-bzip-compressed-tar"); types << QStringLiteral("application/zip"); return types; } QIcon AppWizardPlugin::icon() const { return QIcon::fromTheme(QStringLiteral("project-development-new-template")); } QString AppWizardPlugin::name() const { return i18n("Project Templates"); } void AppWizardPlugin::loadTemplate(const QString& fileName) { model()->loadTemplateFile(fileName); } void AppWizardPlugin::reload() { model()->refresh(); } #include "appwizardplugin.moc" diff --git a/plugins/classbrowser/classbrowserplugin.cpp b/plugins/classbrowser/classbrowserplugin.cpp index eb1beb31d6..2993f720cf 100644 --- a/plugins/classbrowser/classbrowserplugin.cpp +++ b/plugins/classbrowser/classbrowserplugin.cpp @@ -1,187 +1,187 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classbrowserplugin.h" #include #include #include #include "interfaces/icore.h" #include "interfaces/iuicontroller.h" #include "interfaces/idocumentcontroller.h" #include "interfaces/contextmenuextension.h" #include "language/interfaces/codecontext.h" #include "language/duchain/duchainbase.h" #include "language/duchain/duchain.h" #include "language/duchain/duchainlock.h" #include "language/duchain/declaration.h" #include #include "debug.h" #include "language/classmodel/classmodel.h" #include "classtree.h" #include "classwidget.h" #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CLASSBROWSER, "kdevplatform.plugins.classbrowser") K_PLUGIN_FACTORY_WITH_JSON(KDevClassBrowserFactory, "kdevclassbrowser.json", registerPlugin(); ) using namespace KDevelop; class ClassBrowserFactory: public KDevelop::IToolViewFactory { public: ClassBrowserFactory(ClassBrowserPlugin *plugin): m_plugin(plugin) {} - QWidget* create(QWidget *parent = 0) override + QWidget* create(QWidget *parent = nullptr) override { return new ClassWidget(parent, m_plugin); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ClassBrowserView"); } private: ClassBrowserPlugin *m_plugin; }; ClassBrowserPlugin::ClassBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevclassbrowser"), parent) , m_factory(new ClassBrowserFactory(this)) - , m_activeClassTree(0) + , m_activeClassTree(nullptr) { core()->uiController()->addToolView(i18n("Classes"), m_factory); setXMLFile( QStringLiteral("kdevclassbrowser.rc") ); m_findInBrowser = new QAction(i18n("Find in &Class Browser"), this); connect(m_findInBrowser, &QAction::triggered, this, &ClassBrowserPlugin::findInClassBrowser); } ClassBrowserPlugin::~ClassBrowserPlugin() { } void ClassBrowserPlugin::unload() { core()->uiController()->removeToolView(m_factory); } KDevelop::ContextMenuExtension ClassBrowserPlugin::contextMenuExtension( KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); // No context menu if we don't have a class browser at hand. - if ( m_activeClassTree == 0 ) + if ( m_activeClassTree == nullptr ) return menuExt; KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker readLock(DUChain::lock()); Declaration* decl(codeContext->declaration().data()); if (decl) { if(decl->inSymbolTable()) { if(!ClassTree::populatingClassBrowserContextMenu() && ICore::self()->projectController()->findProjectForUrl(decl->url().toUrl()) && decl->kind() == Declaration::Type && decl->internalContext() && decl->internalContext()->type() == DUContext::Class) { //Currently "Find in Class Browser" seems to only work for classes, so only show it in that case m_findInBrowser->setData(QVariant::fromValue(DUChainBasePointer(decl))); menuExt.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, m_findInBrowser); } } } return menuExt; } void ClassBrowserPlugin::findInClassBrowser() { ICore::self()->uiController()->findToolView(i18n("Classes"), m_factory, KDevelop::IUiController::CreateAndRaise); Q_ASSERT(qobject_cast(sender())); - if ( m_activeClassTree == 0 ) + if ( m_activeClassTree == nullptr ) return; DUChainReadLocker readLock(DUChain::lock()); QAction* a = static_cast(sender()); Q_ASSERT(a->data().canConvert()); DeclarationPointer decl = qvariant_cast(a->data()).dynamicCast(); if (decl) m_activeClassTree->highlightIdentifier(decl->qualifiedIdentifier()); } void ClassBrowserPlugin::showDefinition(DeclarationPointer declaration) { DUChainReadLocker readLock(DUChain::lock()); if ( !declaration ) return; Declaration* decl = declaration.data(); // If it's a function, find the function definition to go to the actual declaration. if ( decl && decl->isFunctionDeclaration() ) { FunctionDefinition* funcDefinition = dynamic_cast(decl); - if ( funcDefinition == 0 ) + if ( funcDefinition == nullptr ) funcDefinition = FunctionDefinition::definition(decl); if ( funcDefinition ) decl = funcDefinition; } if (decl) { QUrl url = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); readLock.unlock(); ICore::self()->documentController()->openDocument(url, range.start()); } } #include "classbrowserplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/classbrowser/classtree.cpp b/plugins/classbrowser/classtree.cpp index 919d58fea7..27af26983b 100644 --- a/plugins/classbrowser/classtree.cpp +++ b/plugins/classbrowser/classtree.cpp @@ -1,163 +1,163 @@ /* * KDevelop Class viewer * * Copyright 2006 Adam Treat * Copyright (c) 2006-2007 Hamish Rodda * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classtree.h" #include #include #include #include "interfaces/contextmenuextension.h" #include "interfaces/icore.h" #include "interfaces/idocumentcontroller.h" #include "interfaces/idocument.h" #include "interfaces/iplugincontroller.h" #include "language/interfaces/codecontext.h" #include "language/duchain/duchainbase.h" #include "language/duchain/duchain.h" #include "language/duchain/duchainlock.h" #include "language/duchain/declaration.h" #include #include "language/classmodel/classmodel.h" #include "classbrowserplugin.h" using namespace KDevelop; ClassTree::ClassTree( QWidget* parent, ClassBrowserPlugin* plugin ) : QTreeView( parent ) - , m_plugin( plugin ), m_tooltip( 0 ) + , m_plugin( plugin ), m_tooltip( nullptr ) { header()->hide(); setIndentation( 10 ); setUniformRowHeights( true ); connect( this, &ClassTree::activated, this, &ClassTree::itemActivated ); } ClassTree::~ClassTree() { } static bool _populatingClassBrowserContextMenu = false; bool ClassTree::populatingClassBrowserContextMenu() { return _populatingClassBrowserContextMenu; } void ClassTree::contextMenuEvent( QContextMenuEvent* e ) { QMenu *menu = new QMenu( this ); QModelIndex index = indexAt( e->pos() ); if ( index.isValid() ) { Context* c; { DUChainReadLocker readLock( DUChain::lock() ); if( Declaration* decl = dynamic_cast( model()->duObjectForIndex( index ) ) ) c = new DeclarationContext( decl ); else { delete menu; return; } } _populatingClassBrowserContextMenu = true; QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( c ); ContextMenuExtension::populateMenu( menu, extensions ); _populatingClassBrowserContextMenu = false; } if ( !menu->actions().isEmpty() ) menu->exec( QCursor::pos() ); } bool ClassTree::event( QEvent* event ) { if ( event->type() == QEvent::ToolTip ) { // if we request a tooltip over a duobject item, show a tooltip for it const QPoint &p = mapFromGlobal( QCursor::pos() ); const QModelIndex &idxView = indexAt( p ); DUChainReadLocker readLock( DUChain::lock() ); if ( Declaration* decl = dynamic_cast( model()->duObjectForIndex( idxView ) ) ) { if ( m_tooltip ) { m_tooltip->close(); } QWidget* navigationWidget = decl->topContext()->createNavigationWidget( decl ); if ( navigationWidget ) { m_tooltip = new KDevelop::NavigationToolTip( this, mapToGlobal( p ) + QPoint( 40, 0 ), navigationWidget ); m_tooltip->resize( navigationWidget->sizeHint() + QSize( 10, 10 ) ); ActiveToolTip::showToolTip( m_tooltip ); return true; } } } return QAbstractItemView::event( event ); } ClassModel* ClassTree::model() { return static_cast( QTreeView::model() ); } void ClassTree::itemActivated( const QModelIndex& index ) { DUChainReadLocker readLock( DUChain::lock() ); DeclarationPointer decl = DeclarationPointer( dynamic_cast( model()->duObjectForIndex( index ) ) ); readLock.unlock(); // Delegate to plugin function m_plugin->showDefinition( decl ); if( isExpanded( index ) ) collapse( index ); else expand( index ); } void ClassTree::highlightIdentifier( KDevelop::IndexedQualifiedIdentifier a_id ) { QModelIndex index = model()->getIndexForIdentifier( a_id ); if ( !index.isValid() ) return; // expand and select the item. selectionModel()->select( index, QItemSelectionModel::ClearAndSelect ); scrollTo( index, PositionAtCenter ); expand( index ); } // kate: space-indent on; indent-width 2; tab-width: 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/contextbrowser/browsemanager.cpp b/plugins/contextbrowser/browsemanager.cpp index 4985a6bc4d..7ebeadf0ea 100644 --- a/plugins/contextbrowser/browsemanager.cpp +++ b/plugins/contextbrowser/browsemanager.cpp @@ -1,342 +1,342 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "browsemanager.h" #include #include #include #include #include #include #include #include #include #include "contextbrowserview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "contextbrowser.h" #include "debug.h" using namespace KDevelop; using namespace KTextEditor; EditorViewWatcher::EditorViewWatcher(QObject* parent) : QObject(parent) { connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &EditorViewWatcher::documentCreated); foreach(KDevelop::IDocument* document, ICore::self()->documentController()->openDocuments()) documentCreated(document); } void EditorViewWatcher::documentCreated( KDevelop::IDocument* document ) { KTextEditor::Document* textDocument = document->textDocument(); if(textDocument) { connect(textDocument, &Document::viewCreated, this, &EditorViewWatcher::viewCreated); foreach(KTextEditor::View* view, textDocument->views()) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } } } void EditorViewWatcher::addViewInternal(KTextEditor::View* view) { m_views << view; viewAdded(view); connect(view, &View::destroyed, this, &EditorViewWatcher::viewDestroyed); } void EditorViewWatcher::viewAdded(KTextEditor::View*) { } void EditorViewWatcher::viewDestroyed(QObject* view) { m_views.removeAll(static_cast(view)); } void EditorViewWatcher::viewCreated(KTextEditor::Document* /*doc*/, KTextEditor::View* view) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } QList EditorViewWatcher::allViews() { return m_views; } void BrowseManager::eventuallyStartDelayedBrowsing() { avoidMenuAltFocus(); if(m_browsingByKey == Qt::Key_Alt && m_browsingStartedInView) emit startDelayedBrowsing(m_browsingStartedInView); } BrowseManager::BrowseManager(ContextBrowserPlugin* controller) : QObject(controller) , m_plugin(controller) , m_browsing(false) , m_browsingByKey(0) , m_watcher(this) { m_delayedBrowsingTimer = new QTimer(this); m_delayedBrowsingTimer->setSingleShot(true); connect(m_delayedBrowsingTimer, &QTimer::timeout, this, &BrowseManager::eventuallyStartDelayedBrowsing); foreach(KTextEditor::View* view, m_watcher.allViews()) viewAdded(view); } KTextEditor::View* viewFromWidget(QWidget* widget) { if(!widget) - return 0; + return nullptr; KTextEditor::View* view = qobject_cast(widget); if(view) return view; else return viewFromWidget(widget->parentWidget()); } BrowseManager::JumpLocation BrowseManager::determineJumpLoc(KTextEditor::Cursor textCursor, const QUrl& viewUrl) const { // @todo find out why this is needed, fix the code in kate if (textCursor.column() > 0) { textCursor.setColumn(textCursor.column() - 1); } // Step 1: Look for a special language object(Macro, included header, etc.) foreach (const auto& language, ICore::self()->languageController()->languagesForUrl(viewUrl)) { auto jumpTo = language->specialLanguageObjectJumpCursor(viewUrl, textCursor); if (jumpTo.first.isValid() && jumpTo.second.isValid()) { return {jumpTo}; } } // Step 2: Look for a declaration/use DUChainReadLocker lock; // Jump to definition by default, unless a definition itself was selected, // in which case jump to declaration. if (auto selectedDeclaration = DUChainUtils::itemUnderCursor(viewUrl, textCursor).declaration) { auto jumpDestination = selectedDeclaration; if (selectedDeclaration->isDefinition()) { // A definition was clicked directly - jump to declaration instead. if (auto declaration = DUChainUtils::declarationForDefinition(selectedDeclaration)) { jumpDestination = declaration; } } else if (selectedDeclaration == DUChainUtils::declarationForDefinition(selectedDeclaration)) { // Clicked the declaration - jump to definition if (auto definition = FunctionDefinition::definition(selectedDeclaration)) { jumpDestination = definition; } } return {{jumpDestination->url().toUrl(), jumpDestination->rangeInCurrentRevision().start()}}; } return {}; } bool BrowseManager::eventFilter(QObject * watched, QEvent * event) { QWidget* widget = qobject_cast(watched); Q_ASSERT(widget); QKeyEvent* keyEvent = dynamic_cast(event); const int browseKey = Qt::Key_Control; const int magicModifier = Qt::Key_Alt; KTextEditor::View* view = viewFromWidget(widget); //Eventually start key-browsing if(keyEvent && (keyEvent->key() == browseKey || keyEvent->key() == magicModifier) && !m_browsingByKey && keyEvent->type() == QEvent::KeyPress) { m_delayedBrowsingTimer->start(300); // always start the timer, to get consistent behavior regarding the ALT key and the menu activation m_browsingByKey = keyEvent->key(); if(!view) { return false; } if(keyEvent->key() == magicModifier) { if(dynamic_cast(view) && dynamic_cast(view)->isCompletionActive()) { //Completion is active. avoidMenuAltFocus(); m_delayedBrowsingTimer->stop(); }else{ m_browsingStartedInView = view; } } } if (keyEvent && m_browsingByKey && m_browsingStartedInView && keyEvent->type() == QEvent::KeyPress) { if (keyEvent->key() >= Qt::Key_1 && keyEvent->key() <= Qt::Key_9) { // user wants to trigger an action in the code browser const int index = keyEvent->key() - Qt::Key_1; emit invokeAction(index); stopDelayedBrowsing(); return true; } } if(!view) { return false; } QFocusEvent* focusEvent = dynamic_cast(event); //Eventually stop key-browsing if((keyEvent && m_browsingByKey && keyEvent->key() == m_browsingByKey && keyEvent->type() == QEvent::KeyRelease) || (focusEvent && focusEvent->lostFocus()) || event->type() == QEvent::WindowDeactivate) { m_browsingByKey = 0; emit stopDelayedBrowsing(); } QMouseEvent* mouseEvent = dynamic_cast(event); if(mouseEvent) { if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton1) { m_plugin->historyPrevious(); return true; } if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton2) { m_plugin->historyNext(); return true; } } if(!m_browsing && !m_browsingByKey) { resetChangedCursor(); return false; } if(mouseEvent) { QPoint coordinatesInView = widget->mapTo(view, mouseEvent->pos()); KTextEditor::Cursor textCursor = view->coordinatesToCursor(coordinatesInView); if (textCursor.isValid()) { JumpLocation jumpTo = determineJumpLoc(textCursor, view->document()->url()); if (jumpTo.isValid()) { if(mouseEvent->button() == Qt::LeftButton) { if(mouseEvent->type() == QEvent::MouseButtonPress) { m_buttonPressPosition = textCursor; // view->setCursorPosition(textCursor); // return false; }else if(mouseEvent->type() == QEvent::MouseButtonRelease && textCursor == m_buttonPressPosition) { ICore::self()->documentController()->openDocument(jumpTo.url, jumpTo.cursor); // event->accept(); // return true; } }else if(mouseEvent->type() == QEvent::MouseMove) { //Make the cursor a "hand" setHandCursor(widget); return false; } } } resetChangedCursor(); } return false; } void BrowseManager::resetChangedCursor() { QMap, QCursor> cursors = m_oldCursors; m_oldCursors.clear(); for(QMap, QCursor>::iterator it = cursors.begin(); it != cursors.end(); ++it) if(it.key()) it.key()->setCursor(QCursor(Qt::IBeamCursor)); } void BrowseManager::setHandCursor(QWidget* widget) { if(m_oldCursors.contains(widget)) return; //Nothing to do m_oldCursors[widget] = widget->cursor(); widget->setCursor(QCursor(Qt::PointingHandCursor)); } void BrowseManager::avoidMenuAltFocus() { // send an invalid key event to the main menu bar. The menu bar will // stop listening when observing another key than ALT between the press // and the release. QKeyEvent event1(QEvent::KeyPress, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event1); QKeyEvent event2(QEvent::KeyRelease, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event2); } void BrowseManager::applyEventFilter(QWidget* object, bool install) { if(install) object->installEventFilter(this); else object->removeEventFilter(this); foreach(QObject* child, object->children()) if(qobject_cast(child)) applyEventFilter(qobject_cast(child), install); } void BrowseManager::viewAdded(KTextEditor::View* view) { applyEventFilter(view, true); //We need to listen for cursorPositionChanged, to clear the shift-detector. The problem: Kate listens for the arrow-keys using shortcuts, //so those keys are not passed to the event-filter // can't use new signal/slot syntax here, these signals are only defined in KateView // TODO: should we really depend on kate internals here? connect(view, SIGNAL(navigateLeft()), m_plugin, SLOT(navigateLeft())); connect(view, SIGNAL(navigateRight()), m_plugin, SLOT(navigateRight())); connect(view, SIGNAL(navigateUp()), m_plugin, SLOT(navigateUp())); connect(view, SIGNAL(navigateDown()), m_plugin, SLOT(navigateDown())); connect(view, SIGNAL(navigateAccept()), m_plugin, SLOT(navigateAccept())); connect(view, SIGNAL(navigateBack()), m_plugin, SLOT(navigateBack())); } void Watcher::viewAdded(KTextEditor::View* view) { m_manager->viewAdded(view); } void BrowseManager::setBrowsing(bool enabled) { if(enabled == m_browsing) return; m_browsing = enabled; //This collects all the views if(enabled) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Enabled browsing-mode"; }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "Disabled browsing-mode"; resetChangedCursor(); } } Watcher::Watcher(BrowseManager* manager) : EditorViewWatcher(manager), m_manager(manager) { foreach(KTextEditor::View* view, allViews()) m_manager->applyEventFilter(view, true); } diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index 69203f7397..1ace8408c4 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1469 +1,1469 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextbrowser.h" #include "contextbrowserview.h" #include "browsemanager.h" #include "debug.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 #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser") using KTextEditor::Attribute; using KTextEditor::View; // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent. QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } namespace { const unsigned int highlightingTimeout = 150; const float highlightingZDepth = -5000; const int maxHistoryLength = 30; // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext) { DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position)); while(ctx && ctx->parentContext() && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper || ctx->localScopeIdentifier().isEmpty())) { ctx = ctx->parentContext(); } return ctx; } ///Duchain must be locked DUContext* getContextAt(const QUrl& url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); - if (!topContext) return 0; + if (!topContext) return nullptr; return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext); } DeclarationPointer cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return DeclarationPointer(); } DUChainReadLocker lock; Declaration *decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); return DeclarationPointer(decl); } } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} - QWidget* create(QWidget *parent = 0) override + QWidget* create(QWidget *parent = nullptr) override { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ContextBrowser"); } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { m_browseManager = new BrowseManager(this); KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious); connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext); connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow); IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IQuickOpen")); if(quickOpen) { m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n("Outline"), IQuickOpen::Outline); m_outlineLine->setDefaultText(i18n("Outline...")); m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); } connect(m_browseManager, &BrowseManager::startDelayedBrowsing, this, &ContextBrowserPlugin::startDelayedBrowsing); connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, this, &ContextBrowserPlugin::stopDelayedBrowsing); connect(m_browseManager, &BrowseManager::invokeAction, this, &ContextBrowserPlugin::invokeAction); m_toolbarWidget = toolbarWidgetForMainWindow(window); m_toolbarWidgetLayout = new QHBoxLayout; m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); if (m_outlineLine) { m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear); } m_toolbarWidgetLayout->addWidget(m_nextButton); if(m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ContextBrowserPlugin::documentActivated); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevcontextbrowser.rc"); QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode")); sourceBrowseMode->setText( i18n("Source &Browse Mode") ); sourceBrowseMode->setIcon( QIcon::fromTheme(QStringLiteral("arrow-up")) ); sourceBrowseMode->setCheckable(true); connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing); QAction* previousContext = actions.addAction(QStringLiteral("previous_context")); previousContext->setText( i18n("&Previous Visited Context") ); previousContext->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-context") ) ); actions.setDefaultShortcut( previousContext, Qt::META | Qt::Key_Left ); QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut); QAction* nextContext = actions.addAction(QStringLiteral("next_context")); nextContext->setText( i18n("&Next Visited Context") ); nextContext->setIcon( QIcon::fromTheme(QStringLiteral("go-next-context") ) ); actions.setDefaultShortcut( nextContext, Qt::META | Qt::Key_Right ); QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut); QAction* previousUse = actions.addAction(QStringLiteral("previous_use")); previousUse->setText( i18n("&Previous Use") ); previousUse->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-use")) ); actions.setDefaultShortcut( previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left ); QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut); QAction* nextUse = actions.addAction(QStringLiteral("next_use")); nextUse->setText( i18n("&Next Use") ); nextUse->setIcon( QIcon::fromTheme(QStringLiteral("go-next-use")) ); actions.setDefaultShortcut( nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right ); QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut); QWidgetAction* outline = new QWidgetAction(this); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); actions.addAction(QStringLiteral("outline_line"), outline); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction(QStringLiteral("find_uses"), m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin();) ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) , m_textHintProvider(this) { KDEV_USE_EXTENSION_INTERFACE( IContextBrowser ) core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated ); connect( DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady); connect( ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged ); connect( DUChain::self(), &DUChain::declarationSelected, this, &ContextBrowserPlugin::declarationSelectedInUI ); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect( m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews ); //Needed global action for the context-menu extensions m_findUses = new QAction(i18n("Find Uses"), this); connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses); } ContextBrowserPlugin::~ContextBrowserPlugin() { ///TODO: QObject inheritance should suffice? delete m_nextMenu; delete m_previousMenu; delete m_toolbarWidgetLayout; delete m_previousButton; delete m_outlineLine; delete m_nextButton; } void ContextBrowserPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker lock(DUChain::lock()); if(!codeContext->declaration().data()) return menuExt; qRegisterMetaType("KDevelop::IndexedDeclaration"); menuExt.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_findUses); return menuExt; } void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration) { QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection, Q_ARG(KDevelop::DeclarationPointer, declaration)); } void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration) { DUChainReadLocker lock; Declaration* decl = declaration.data(); if(!decl) { return; } QWidget* toolView = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if(!toolView) { return; } ContextBrowserView* view = dynamic_cast(toolView); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl, decl->topContext(), true); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer widget = dynamic_cast(view->navigationWidget()); if(widget && widget->context()) { NavigationContextPointer nextContext = widget->context()->execute( NavigationAction(declaration, KDevelop::NavigationAction::ShowUses)); if(widget) { widget->setContext( nextContext ); } } } void ContextBrowserPlugin::findUses() { showUses(cursorDeclaration()); } ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin) : m_plugin(plugin) { } QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor) { m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor); if(!view) { qWarning() << "could not cast to view"; }else{ m_plugin->m_mouseHoverDocument = view->document()->url(); m_plugin->m_updateViews << view; } m_plugin->m_updateTimer->start(1); // triggers updateViews() m_plugin->showToolTip(view, cursor); return QString(); } void ContextBrowserPlugin::stopDelayedBrowsing() { hideToolTip(); } void ContextBrowserPlugin::invokeAction(int index) { if (!m_currentNavigationWidget) return; auto navigationWidget = qobject_cast(m_currentNavigationWidget); if (!navigationWidget) return; // TODO: Add API in AbstractNavigation{Widget,Context}? QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index)); } void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { if(!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); - m_currentToolTip = 0; - m_currentNavigationWidget = 0; + m_currentToolTip = nullptr; + m_currentNavigationWidget = nullptr; m_currentToolTipProblem = {}; m_currentToolTipDeclaration = {}; } } static ProblemPointer findProblemUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position) { const auto top = ReferencedTopDUContext(topContext); foreach (auto problem, DUChainUtils::allProblemsForContext(top)) { if (problem->rangeInCurrentRevision().contains(position)) { return problem; } } return {}; } static ProblemPointer findProblemCloseToCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) { const auto top = ReferencedTopDUContext(topContext); auto problems = DUChainUtils::allProblemsForContext(top); if (problems.isEmpty()) return {}; auto closestProblem = std::min_element(problems.constBegin(), problems.constEnd(), [position](const ProblemPointer& a, const ProblemPointer& b) { const auto aRange = a->rangeInCurrentRevision(); const auto bRange = b->rangeInCurrentRevision(); const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()), qAbs(aRange.end().line() - position.line())); const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()), qAbs(bRange.end().line() - position.line())); if (aLineDistance != bLineDistance) { return aLineDistance < bLineDistance; } if (aRange.start().line() == bRange.start().line()) { return qAbs(aRange.start().column() - position.column()) < qAbs(bRange.start().column() - position.column()); } return qAbs(aRange.end().column() - position.column()) < qAbs(bRange.end().column() - position.column()); }); auto r = (*closestProblem)->rangeInCurrentRevision(); if (!r.contains(position)) { if (r.start().line() == position.line() || r.end().line() == position.line()) { // problem is on the same line, let's use it return *closestProblem; } // if not, only show it in case there's only whitespace between the current cursor pos and the problem auto dist = position < r.start() ? KTextEditor::Range(position, r.start()) : KTextEditor::Range(r.end(), position); auto textBetween = view->document()->text(dist); auto isSpace = std::all_of(textBetween.begin(), textBetween.end(), [](QChar c) { return c.isSpace(); }); if (!isSpace) { return {}; } } return *closestProblem; } QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position) { QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); DUChainReadLocker lock(DUChain::lock()); foreach (const auto language, languages) { auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); auto navigationWidget = qobject_cast(widget); if(navigationWidget) return navigationWidget; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (topContext) { // first pass: find problems under the cursor const auto problem = findProblemUnderCursor(topContext, position); if (problem) { if (problem == m_currentToolTipProblem && m_currentToolTip) { return nullptr; } m_currentToolTipProblem = problem; auto widget = new AbstractNavigationWidget; auto context = new ProblemNavigationContext(problem); context->setTopContext(TopDUContextPointer(topContext)); widget->setContext(NavigationContextPointer(context)); return widget; } } auto declUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position).declaration; Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) return nullptr; m_currentToolTipDeclaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } if (topContext) { // second pass: find closest problem to the cursor const auto problem = findProblemCloseToCursor(topContext, position, view); if (problem) { if (problem == m_currentToolTipProblem && m_currentToolTip) { return nullptr; } m_currentToolTipProblem = problem; auto widget = new AbstractNavigationWidget; // since the problem is not under cursor: show location widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problem, ProblemNavigationContext::ShowLocation))); return widget; } } return nullptr; } void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView && contextView->isVisible() && !contextView->isLocked()) return; // If the context-browser view is visible, it will care about updating by itself auto navigationWidget = navigationWidgetForPosition(view, position); if(navigationWidget) { // If we have an invisible context-view, assign the tooltip navigation-widget to it. // If the user makes the context-view visible, it will instantly contain the correct widget. if(contextView && !contextView->isLocked()) contextView->setNavigationWidget(navigationWidget); if(m_currentToolTip) { m_currentToolTip->deleteLater(); - m_currentToolTip = 0; - m_currentNavigationWidget = 0; + m_currentToolTip = nullptr; + m_currentNavigationWidget = nullptr; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); KTextEditor::Range itemRange; { DUChainReadLocker lock; auto viewUrl = view->document()->url(); itemRange = DUChainUtils::itemUnderCursor(viewUrl, position).range; } tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, itemRange)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) { connect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection); } else { disconnect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip); } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = KTextEditor::Cursor::invalid(); m_mouseHoverDocument.clear(); } Attribute::Ptr ContextBrowserPlugin::highlightedUseAttribute(KTextEditor::View* view) const { if( !m_highlightAttribute ) { m_highlightAttribute = Attribute::Ptr( new Attribute() ); m_highlightAttribute->setDefaultStyle(KTextEditor::dsNormal); m_highlightAttribute->setForeground(m_highlightAttribute->selectedForeground()); m_highlightAttribute->setBackgroundFillWhitespace(true); auto iface = qobject_cast(view); auto background = iface->configValue(QStringLiteral("search-highlight-color")).value(); m_highlightAttribute->setBackground(background); } return m_highlightAttribute; } void ContextBrowserPlugin::colorSetupChanged() { m_highlightAttribute = Attribute::Ptr(); } Attribute::Ptr ContextBrowserPlugin::highlightedSpecialObjectAttribute(KTextEditor::View* view) const { return highlightedUseAttribute(view); } void ContextBrowserPlugin::addHighlight( View* view, KDevelop::Declaration* decl ) { if( !view || !decl ) { qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration"; return; } ViewHighlights& highlights(m_highlightedRanges[view]); KDevelop::DUChainReadLocker lock; // Highlight the declaration highlights.highlights << decl->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); // Highlight uses { QMap< IndexedString, QList< KTextEditor::Range > > currentRevisionUses = decl->usesCurrentRevision(); for(QMap< IndexedString, QList< KTextEditor::Range > >::iterator fileIt = currentRevisionUses.begin(); fileIt != currentRevisionUses.end(); ++fileIt) { for(QList< KTextEditor::Range >::const_iterator useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key())); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } } if( FunctionDefinition* def = FunctionDefinition::definition(decl) ) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight) { Q_UNUSED(mouseHighlight); - Declaration* foundDeclaration = 0; + Declaration* foundDeclaration = nullptr; if(m_useDeclaration.data()) { foundDeclaration = m_useDeclaration.data(); }else{ //If we haven't found a special language object, search for a use/declaration and eventually highlight it foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), position).declaration ); if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(foundDeclaration); Q_ASSERT(alias); DUChainReadLocker lock; foundDeclaration = alias->aliasedDeclaration().declaration(); } } return foundDeclaration; } ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) { foreach(ContextBrowserView* contextView, m_views) { if(masterWidget(contextView) == masterWidget(widget)) { return contextView; } } - return 0; + return nullptr; } void ContextBrowserPlugin::updateForView(View* view) { bool allowHighlight = true; if(view->selection()) { // If something is selected, we unhighlight everything, so that we don't conflict with the // kate plugin that highlights occurrences of the selected string, and also to reduce the // overall amount of concurrent highlighting. allowHighlight = false; } if(m_highlightedRanges[view].keep) { m_highlightedRanges[view].keep = false; return; } // Clear all highlighting m_highlightedRanges.clear(); // Re-highlight ViewHighlights& highlights = m_highlightedRanges[view]; QUrl url = view->document()->url(); IDocument* activeDoc = core()->documentController()->activeDocument(); bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid()); bool shouldUpdateBrowser = (mouseHighlight || (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc && activeDoc->textDocument() == view->document())); KTextEditor::Cursor highlightPosition; if (mouseHighlight) highlightPosition = m_mouseHoverCursor; else highlightPosition = KTextEditor::Cursor(view->cursorPosition()); ///Pick a language ILanguageSupport* language = nullptr; if(ICore::self()->languageController()->languagesForUrl(url).isEmpty()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url; return; }else{ language = ICore::self()->languageController()->languagesForUrl(url).front(); } ///Check whether there is a special language object to highlight (for example a macro) KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition); - ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : 0; + ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : nullptr; if(specialRange.isValid()) { // Highlight a special language object if(allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } if(updateBrowserView) updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition)); }else{ KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 ); if(!lock.locked()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time"; return; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (!topContext) return; DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext); if (!ctx) return; //Only update the history if this context is around the text cursor if(core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument()) { updateHistory(ctx, highlightPosition); } Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight); if( foundDeclaration ) { m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration); if(allowHighlight) addHighlight( view, foundDeclaration ); if(updateBrowserView) updateBrowserView->setDeclaration(foundDeclaration, topContext); }else{ if(updateBrowserView) updateBrowserView->setContext(ctx); } } } void ContextBrowserPlugin::updateViews() { foreach( View* view, m_updateViews ) { updateForView(view); } m_updateViews.clear(); m_useDeclaration = IndexedDeclaration(); } void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl) { m_useDeclaration = IndexedDeclaration(decl.data()); KTextEditor::View* view = core()->documentController()->activeTextDocumentView(); if(view) m_updateViews << view; if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/) { const auto url = file.toUrl(); for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == url) { if(!m_updateViews.contains(it.key())) { qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); } void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document ) { Q_ASSERT(document->textDocument()); connect( document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated ); foreach( View* view, document->textDocument()->views() ) viewCreated( document->textDocument(), view ); } void ContextBrowserPlugin::documentActivated( IDocument* doc ) { if (m_outlineLine) m_outlineLine->clear(); if (View* view = doc->activeTextView()) { cursorPositionChanged(view, view->cursorPosition()); } } void ContextBrowserPlugin::viewDestroyed( QObject* obj ) { m_highlightedRanges.remove(static_cast(obj)); m_updateViews.remove(static_cast(obj)); } void ContextBrowserPlugin::selectionChanged( View* view ) { clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::cursorPositionChanged( View* view, const KTextEditor::Cursor& newPosition ) { if(view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos) { //Do not update the highlighting while typing - m_lastInsertionDocument = 0; + m_lastInsertionDocument = nullptr; m_lastInsertionPos = KTextEditor::Cursor(); if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = true; }else{ if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = false; } clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { m_lastInsertionDocument = doc; m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size()); } void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v ) { disconnect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); ///Just to make sure that multiple connections don't happen connect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); connect( v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed ); disconnect( v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged); KTextEditor::TextHintInterface *iface = dynamic_cast(v); if( !iface ) return; iface->setTextHintDelay(highlightingTimeout); iface->registerTextHintProvider(&m_textHintProvider); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) { return KTextEditor::Range(cursor, cursor); } void ContextBrowserPlugin::switchUse(bool forward) { View* view = core()->documentController()->activeTextDocumentView(); if(view) { KTextEditor::Document* doc = view->document(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if( chosen ) { KTextEditor::Cursor cCurrent(view->cursorPosition()); KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent); - Declaration* decl = 0; + Declaration* decl = nullptr; //If we have a locked declaration, use that for jumping foreach(ContextBrowserView* view, m_views) { decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple if(decl) break; } if(!decl) //Try finding a declaration under the cursor decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration; if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { - Declaration* target = 0; + Declaration* target = nullptr; if(forward) //Try jumping from definition to declaration target = DUChainUtils::declarationForDefinition(decl, chosen); else if(decl->url().toUrl() == doc->url() && decl->range().contains(c)) //Try jumping from declaration to definition target = FunctionDefinition::definition(decl); if(target && target != decl) { KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start(); QUrl document = target->url().toUrl(); lock.unlock(); core()->documentController()->openDocument( document, cursorToRange(jumpTo) ); return; }else{ //Always work with the declaration instead of the definition decl = DUChainUtils::declarationForDefinition(decl, chosen); } } if(!decl) { //Pick the last use we have highlighted decl = m_lastHighlightedDeclaration.data(); } if(decl) { KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id()); if(DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1) usingFiles.insert(0, decl->topContext()); if(decl->range().contains(c) && decl->url() == chosen->url()) { //The cursor is directly on the declaration. Jump to the first or last use. if(!usingFiles.isEmpty()) { TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data(); if(top) { QList useRanges = allUses(top, decl, true); std::sort(useRanges.begin(), useRanges.end()); if(!useRanges.isEmpty()) { QUrl url = top->url().toUrl(); KTextEditor::Range selectUse = chosen->transformFromLocalRevision(forward ? useRanges.first() : useRanges.back()); lock.unlock(); core()->documentController()->openDocument(url, cursorToRange(selectUse.start())); } } } return; } //Check whether we are within a use QList localUses = allUses(chosen, decl, true); std::sort(localUses.begin(), localUses.end()); for(int a = 0; a < localUses.size(); ++a) { int nextUse = (forward ? a+1 : a-1); bool pick = localUses[a].contains(c); if(!pick && forward && a+1 < localUses.size() && localUses[a].end <= c && localUses[a+1].start > c) { //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use pick = true; } if(!pick && !forward && a-1 >= 0 && c < localUses[a].start && c >= localUses[a-1].end) { //Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one pick = true; } if(!pick && a == 0 && c < localUses[a].start) { if(!forward) { //Will automatically jump to previous file }else{ nextUse = 0; //We are before the first use, so jump to it. } pick = true; } if(!pick && a == localUses.size()-1 && c >= localUses[a].end) { if(forward) { //Will automatically jump to next file }else{ //We are behind the last use, but moving backward. So pick the last use. nextUse = a; } pick = true; } if(pick) { //Make sure we end up behind the use if(nextUse != a) while(forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty())) ++nextUse; //Make sure we end up before the use if(nextUse != a) while(!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty())) --nextUse; //Jump to the next use qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse; if(nextUse < 0 || nextUse == localUses.size()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file"; //Jump to the first use in the next using top-context int indexInFiles = usingFiles.indexOf(chosen); if(indexInFiles != -1) { int nextFile = (forward ? indexInFiles+1 : indexInFiles-1); qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile; if(nextFile < 0 || nextFile >= usingFiles.size()) { //Open the declaration, or the definition if(nextFile >= usingFiles.size()) { Declaration* definition = FunctionDefinition::definition(decl); if(definition) decl = definition; } QUrl u = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); return; }else{ TopDUContext* nextTop = usingFiles[nextFile].data(); QUrl u = nextTop->url().toUrl(); QList nextTopUses = allUses(nextTop, decl, true); std::sort(nextTopUses.begin(), nextTopUses.end()); if(!nextTopUses.isEmpty()) { KTextEditor::Range range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back()); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); } return; } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list"; } }else{ QUrl url = chosen->url().toUrl(); KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(url, range); return; } } } } } } } void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view) { m_views.removeAll(view); } // history browsing QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow( Sublime::MainWindow* window ) { //TODO: support multiple windows (if that ever gets revived) if (!m_toolbarWidget) { m_toolbarWidget = new QWidget(window); } return m_toolbarWidget; } void ContextBrowserPlugin::documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor) { DUChainReadLocker lock(DUChain::lock()); /*TODO: support multiple windows if that ever gets revived if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this)) return; */ if(previousDocument && previousCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source"; DUContext* context = getContextAt(previousDocument->url(), previousCursor); if(context) { updateHistory(context, KTextEditor::Cursor(previousCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor)))); ++m_nextHistoryIndex; } } qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor; if(newDocument && newCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target"; DUContext* context = getContextAt(newDocument->url(), newCursor); if(context) { updateHistory(context, KTextEditor::Cursor(newCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor)))); ++m_nextHistoryIndex; if (m_outlineLine) m_outlineLine->clear(); } } } void ContextBrowserPlugin::updateButtonState() { m_nextButton->setEnabled( m_nextHistoryIndex < m_history.size() ); m_previousButton->setEnabled( m_nextHistoryIndex >= 2 ); } void ContextBrowserPlugin::historyNext() { if(m_nextHistoryIndex >= m_history.size()) { return; } openDocument(m_nextHistoryIndex); // opening the document at given position // will update the widget for us ++m_nextHistoryIndex; updateButtonState(); } void ContextBrowserPlugin::openDocument(int historyIndex) { Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index"); Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range"); DocumentCursor c = m_history[historyIndex].computePosition(); if (c.isValid() && !c.document.str().isEmpty()) { disconnect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); ICore::self()->documentController()->openDocument(c.document.toUrl(), c); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); updateDeclarationListBox(m_history[historyIndex].context.data()); } } void ContextBrowserPlugin::historyPrevious() { if(m_nextHistoryIndex < 2) { return; } --m_nextHistoryIndex; openDocument(m_nextHistoryIndex-1); // opening the document at given position // will update the widget for us updateButtonState(); } QString ContextBrowserPlugin::actionTextFor(int historyIndex) const { const HistoryEntry& entry = m_history.at(historyIndex); QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString(); if(actionText.isEmpty()) actionText = entry.alternativeString; if(actionText.isEmpty()) actionText = QStringLiteral(""); actionText += QLatin1String(" @ "); QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1); return actionText; } /* inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he) { DocumentCursor c = he.computePosition(); debug << "\n\tHistoryEntry " << c.line << " " << c.document.str(); return debug; } */ void ContextBrowserPlugin::nextMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) { indices << a; } fillHistoryPopup(m_nextMenu, indices); } void ContextBrowserPlugin::previousMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex-2; a >= 0; --a) { indices << a; } fillHistoryPopup(m_previousMenu, indices); } void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) { menu->clear(); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); foreach(int index, historyIndices) { QAction* action = new QAction(actionTextFor(index), menu); action->setData(index); menu->addAction(action); connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KTextEditor::Cursor& /*position*/) const { if (m_nextHistoryIndex == 0) return false; Q_ASSERT(m_nextHistoryIndex <= m_history.count()); const HistoryEntry& he = m_history.at(m_nextHistoryIndex-1); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); // is this necessary?? Q_ASSERT(context); return IndexedDUContext(context) == he.context; } void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history"; if(m_outlineLine && m_outlineLine->isVisible()) updateDeclarationListBox(context); if(!context || (!context->owner() && !force)) { return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes //This keeps the history cleaner } if (isPreviousEntry(context, position)) { if(m_nextHistoryIndex) { HistoryEntry& he = m_history[m_nextHistoryIndex-1]; he.setCursorPosition(position); } return; } else { // Append new history entry m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(IndexedDUContext(context), position)); ++m_nextHistoryIndex; updateButtonState(); if(m_history.size() > (maxHistoryLength + 5)) { m_history = m_history.mid(m_history.size() - maxHistoryLength); m_nextHistoryIndex = m_history.size(); } } } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if(!context || !context->owner()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here if (m_outlineLine) m_outlineLine->clear(); return; } Declaration* decl = context->owner(); m_listUrl = context->url(); Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext()); FunctionType::Ptr function = specialDecl->type(); QString text = specialDecl->qualifiedIdentifier().toString(); if(function) text += function->partToString(KDevelop::FunctionType::SignatureArguments); if(m_outlineLine && !m_outlineLine->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text; } void ContextBrowserPlugin::actionTriggered() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int); int historyPosition = action->data().toInt(); // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history; if(historyPosition >= 0 && historyPosition < m_history.size()) { m_nextHistoryIndex = historyPosition + 1; openDocument(historyPosition); updateButtonState(); } } void ContextBrowserPlugin::doNavigate(NavigationActionType action) { KTextEditor::View* view = qobject_cast(sender()); if(!view) { qWarning() << "sender is not a view"; return; } KTextEditor::CodeCompletionInterface* iface = qobject_cast(view); if(!iface || iface->isCompletionActive()) return; // If code completion is active, the actions should be handled by the completion widget QWidget* widget = m_currentNavigationWidget.data(); if(!widget || !widget->isVisible()) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView) widget = contextView->navigationWidget(); } if(widget) { AbstractNavigationWidget* navWidget = qobject_cast(widget); if (navWidget) { switch(action) { case Accept: navWidget->accept(); break; case Back: navWidget->back(); break; case Left: navWidget->previous(); break; case Right: navWidget->next(); break; case Up: navWidget->up(); break; case Down: navWidget->down(); break; } } } } void ContextBrowserPlugin::navigateAccept() { doNavigate(Accept); } void ContextBrowserPlugin::navigateBack() { doNavigate(Back); } void ContextBrowserPlugin::navigateDown() { doNavigate(Down); } void ContextBrowserPlugin::navigateLeft() { doNavigate(Left); } void ContextBrowserPlugin::navigateRight() { doNavigate(Right); } void ContextBrowserPlugin::navigateUp() { doNavigate(Up); } //BEGIN HistoryEntry ContextBrowserPlugin::HistoryEntry::HistoryEntry(KDevelop::DocumentCursor pos) : absoluteCursorPosition(pos) { } ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) { //Use a position relative to the context setCursorPosition(cursorPosition); if(ctx.data()) alternativeString = ctx.data()->scopeIdentifier(true).toString(); if(!alternativeString.isEmpty()) alternativeString += i18n("(changed)"); //This is used when the context was deleted in between } DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); DocumentCursor ret; if(context.data()) { ret = DocumentCursor(context.data()->url(), relativeCursorPosition); ret.setLine(ret.line() + context.data()->range().start.line); }else{ ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); if(context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line); } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on #include "contextbrowser.moc" diff --git a/plugins/contextbrowser/contextbrowserview.cpp b/plugins/contextbrowser/contextbrowserview.cpp index a39a71cdf9..3460e7432f 100644 --- a/plugins/contextbrowser/contextbrowserview.cpp +++ b/plugins/contextbrowser/contextbrowserview.cpp @@ -1,315 +1,315 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextbrowserview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "contextbrowser.h" #include "debug.h" #include #include #include #include "browsemanager.h" #include #include #include #include #include #include using namespace KDevelop; QWidget* ContextBrowserView::createWidget(KDevelop::DUContext* context) { m_context = IndexedDUContext(context); if(m_context.data()) { return m_context.data()->createNavigationWidget(nullptr, nullptr, {}, {}, AbstractNavigationWidget::EmbeddableWidget); } - return 0; + return nullptr; } KDevelop::IndexedDeclaration ContextBrowserView::declaration() const { return m_declaration; } QWidget* ContextBrowserView::createWidget(Declaration* decl, TopDUContext* topContext) { m_declaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, topContext, {}, {}, AbstractNavigationWidget::EmbeddableWidget); } void ContextBrowserView::resetWidget() { if (m_navigationWidget) { delete m_navigationWidget; - m_navigationWidget = 0; + m_navigationWidget = nullptr; } } void ContextBrowserView::declarationMenu() { DUChainReadLocker lock(DUChain::lock()); AbstractNavigationWidget* navigationWidget = dynamic_cast(m_navigationWidget.data()); if(navigationWidget) { AbstractDeclarationNavigationContext* navigationContext = dynamic_cast(navigationWidget->context().data()); if(navigationContext && navigationContext->declaration().data()) { KDevelop::DeclarationContext* c = new KDevelop::DeclarationContext(navigationContext->declaration().data()); lock.unlock(); QMenu menu; QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( c ); ContextMenuExtension::populateMenu(&menu, extensions); menu.exec(QCursor::pos()); } } } void ContextBrowserView::updateLockIcon(bool checked) { m_lockButton->setIcon(QIcon::fromTheme(checked ? QStringLiteral("document-encrypt") : QStringLiteral("document-decrypt"))); } ContextBrowserView::ContextBrowserView( ContextBrowserPlugin* plugin, QWidget* parent ) : QWidget(parent), m_plugin(plugin), m_navigationWidget(new QTextBrowser()), m_autoLocked(false) { setWindowIcon( QIcon::fromTheme(QStringLiteral("code-context"), windowIcon()) ); m_allowLockedUpdate = false; m_buttons = new QHBoxLayout; m_buttons->addStretch(); m_declarationMenuButton = new QToolButton(); m_declarationMenuButton->setIcon(QIcon::fromTheme(QStringLiteral("code-class"))); m_declarationMenuButton->setToolTip(i18n("Declaration menu")); connect(m_declarationMenuButton, &QToolButton::clicked, this, &ContextBrowserView::declarationMenu); m_buttons->addWidget(m_declarationMenuButton); m_lockButton = new QToolButton(); m_lockButton->setCheckable(true); m_lockButton->setChecked(false); m_lockButton->setToolTip(i18n("Lock current view")); updateLockIcon(m_lockButton->isChecked()); connect(m_lockButton, &QToolButton::toggled, this, &ContextBrowserView::updateLockIcon); m_buttons->addWidget(m_lockButton); m_layout = new QVBoxLayout; m_layout->setSpacing(0); m_layout->setMargin(0); m_layout->addLayout(m_buttons); m_layout->addWidget(m_navigationWidget); //m_layout->addStretch(); setLayout(m_layout); m_plugin->registerToolView(this); } ContextBrowserView::~ContextBrowserView() { m_plugin->unRegisterToolView(this); } void ContextBrowserView::focusInEvent(QFocusEvent* event) { //Indicate that we have focus qCDebug(PLUGIN_CONTEXTBROWSER) << "got focus"; // parentWidget()->setBackgroundRole(QPalette::ToolTipBase); /* m_layout->removeItem(m_buttons);*/ return QWidget::focusInEvent(event); } void ContextBrowserView::focusOutEvent(QFocusEvent* event) { qCDebug(PLUGIN_CONTEXTBROWSER) << "lost focus"; // parentWidget()->setBackgroundRole(QPalette::Background); /* m_layout->insertLayout(0, m_buttons); for(int a = 0; a < m_buttons->count(); ++a) { QWidgetItem* item = dynamic_cast(m_buttons->itemAt(a)); }*/ QWidget::focusOutEvent(event); } bool ContextBrowserView::event(QEvent* event) { QKeyEvent* keyEvent = dynamic_cast(event); if(hasFocus() && keyEvent) { AbstractNavigationWidget* navigationWidget = dynamic_cast(m_navigationWidget.data()); if(navigationWidget && event->type() == QEvent::KeyPress) { int key = keyEvent->key(); if(key == Qt::Key_Left) navigationWidget->previous(); if(key == Qt::Key_Right) navigationWidget->next(); if(key == Qt::Key_Up) navigationWidget->up(); if(key == Qt::Key_Down) navigationWidget->down(); if(key == Qt::Key_Return || key == Qt::Key_Enter) navigationWidget->accept(); if(key == Qt::Key_L) m_lockButton->toggle(); } } return QWidget::event(event); } void ContextBrowserView::showEvent(QShowEvent* event) { DUChainReadLocker lock(DUChain::lock(), 200); if (!lock.locked()) { QWidget::showEvent(event); return; } TopDUContext* top = m_lastUsedTopContext.data(); if(top && m_navigationWidgetDeclaration.isValid() && m_navigationWidgetDeclaration.getDeclaration(top)) { if(top) { //Update the navigation-widget Declaration* decl = m_navigationWidgetDeclaration.getDeclaration(top); setDeclaration(decl, top, true); } } QWidget::showEvent(event); } bool ContextBrowserView::isLocked() const { bool isLocked; if (m_allowLockedUpdate) { isLocked = false; } else { isLocked = m_lockButton->isChecked(); } return isLocked; } void ContextBrowserView::updateMainWidget(QWidget* widget) { if (widget) { setUpdatesEnabled(false); qCDebug(PLUGIN_CONTEXTBROWSER) << ""; resetWidget(); m_navigationWidget = widget; m_layout->insertWidget(1, widget, 1); m_allowLockedUpdate = false; setUpdatesEnabled(true); if (widget->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("contextChanged(bool,bool)")) != -1) { connect(widget, SIGNAL(contextChanged(bool,bool)), this, SLOT(navigationContextChanged(bool,bool))); } } } void ContextBrowserView::navigationContextChanged(bool wasInitial, bool isInitial) { if(wasInitial && !isInitial && !m_lockButton->isChecked()) { m_autoLocked = true; m_lockButton->setChecked(true); }else if(!wasInitial && isInitial && m_autoLocked) { m_autoLocked = false; m_lockButton->setChecked(false); }else if(isInitial) { m_autoLocked = false; } } void ContextBrowserView::setDeclaration(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext, bool force) { m_lastUsedTopContext = IndexedTopDUContext(topContext); if(isLocked() && (!m_navigationWidget.data() || !isVisible())) { // Automatically remove the locked state if the view is not visible or the widget was deleted, // because the locked state has side-effects on other navigation functionality. m_autoLocked = false; m_lockButton->setChecked(false); } if(m_navigationWidgetDeclaration == decl->id() && !force) return; m_navigationWidgetDeclaration = decl->id(); if (!isLocked() && (isVisible() || force)) { // NO-OP if toolview is hidden, for performance reasons QWidget* w = createWidget(decl, topContext); updateMainWidget(w); } } KDevelop::IndexedDeclaration ContextBrowserView::lockedDeclaration() const { if(m_lockButton->isChecked()) return declaration(); else return KDevelop::IndexedDeclaration(); } void ContextBrowserView::allowLockedUpdate() { m_allowLockedUpdate = true; } void ContextBrowserView::setNavigationWidget(QWidget* widget) { updateMainWidget(widget); } void ContextBrowserView::setContext(KDevelop::DUContext* context) { if(!context) return; m_lastUsedTopContext = IndexedTopDUContext(context->topContext()); if(context->owner()) { if(context->owner()->id() == m_navigationWidgetDeclaration) return; m_navigationWidgetDeclaration = context->owner()->id(); }else{ m_navigationWidgetDeclaration = DeclarationId(); } if (!isLocked() && isVisible()) { // NO-OP if toolview is hidden, for performance reasons QWidget* w = createWidget(context); updateMainWidget(w); } } void ContextBrowserView::setSpecialNavigationWidget(QWidget* widget) { if (!isLocked() && isVisible()) { Q_ASSERT(widget); updateMainWidget(widget); } else if(widget) { widget->deleteLater(); } } diff --git a/plugins/cvs/cvsplugin.cpp b/plugins/cvs/cvsplugin.cpp index 44b307b239..976aea0916 100644 --- a/plugins/cvs/cvsplugin.cpp +++ b/plugins/cvs/cvsplugin.cpp @@ -1,489 +1,489 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * 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. * * * ***************************************************************************/ #include "cvsplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cvsmainview.h" #include "cvsproxy.h" #include "cvsjob.h" #include "editorsview.h" #include "commitdialog.h" #include "cvsgenericoutputview.h" #include "checkoutdialog.h" #include "importdialog.h" #include "importmetadatawidget.h" #include "debug.h" #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CVS, "kdevplatform.plugins.cvs") K_PLUGIN_FACTORY(KDevCvsFactory, registerPlugin();) // K_EXPORT_PLUGIN(KDevCvsFactory(KAboutData("kdevcvs", "kdevcvs", ki18n("CVS"), "0.1", ki18n("Support for CVS version control system"), KAboutData::License_GPL))) class KDevCvsViewFactory: public KDevelop::IToolViewFactory { public: KDevCvsViewFactory(CvsPlugin *plugin): m_plugin(plugin) {} - QWidget* create(QWidget *parent = 0) override { + QWidget* create(QWidget *parent = nullptr) override { return new CvsMainView(m_plugin, parent); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.CVSView"); } private: CvsPlugin *m_plugin; }; class CvsPluginPrivate { public: explicit CvsPluginPrivate(CvsPlugin *pThis) : m_factory(new KDevCvsViewFactory(pThis)) , m_proxy(new CvsProxy(pThis)) , m_common(new KDevelop::VcsPluginHelper(pThis, pThis)) {} KDevCvsViewFactory* m_factory; QPointer m_proxy; QScopedPointer m_common; }; CvsPlugin::CvsPlugin(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevcvs"), parent) , d(new CvsPluginPrivate(this)) { KDEV_USE_EXTENSION_INTERFACE(KDevelop::IBasicVersionControl) KDEV_USE_EXTENSION_INTERFACE(KDevelop::ICentralizedVersionControl) core()->uiController()->addToolView(i18n("CVS"), d->m_factory); setXMLFile(QStringLiteral("kdevcvs.rc")); setupActions(); } CvsPlugin::~CvsPlugin() { } void CvsPlugin::unload() { core()->uiController()->removeToolView( d->m_factory ); } CvsProxy* CvsPlugin::proxy() { return d->m_proxy; } void CvsPlugin::setupActions() { QAction *action; action = actionCollection()->addAction(QStringLiteral("cvs_import")); action->setText(i18n("Import Directory...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotImport); action = actionCollection()->addAction(QStringLiteral("cvs_checkout")); action->setText(i18n("Checkout...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotCheckout); action = actionCollection()->addAction(QStringLiteral("cvs_status")); action->setText(i18n("Status...")); connect(action, &QAction::triggered, this, &CvsPlugin::slotStatus); } const QUrl CvsPlugin::urlFocusedDocument() const { KParts::ReadOnlyPart *plugin = dynamic_cast(core()->partController()->activePart()); if (plugin) { if (plugin->url().isLocalFile()) { return plugin->url(); } } return QUrl(); } void CvsPlugin::slotImport() { QUrl url = urlFocusedDocument(); ImportDialog dlg(this, url); dlg.exec(); } void CvsPlugin::slotCheckout() { ///@todo don't use proxy directly; use interface instead CheckoutDialog dlg(this); dlg.exec(); } void CvsPlugin::slotStatus() { QUrl url = urlFocusedDocument(); QList urls; urls << url; KDevelop::VcsJob* j = status(urls, KDevelop::IBasicVersionControl::Recursive); CvsJob* job = dynamic_cast(j); if (job) { CvsGenericOutputView* view = new CvsGenericOutputView(job); emit addNewTabToMainView(view, i18n("Status")); KDevelop::ICore::self()->runController()->registerJob(job); } } KDevelop::ContextMenuExtension CvsPlugin::contextMenuExtension(KDevelop::Context* context) { d->m_common->setupFromContext(context); QList const & ctxUrlList = d->m_common->contextUrlList(); bool hasVersionControlledEntries = false; foreach(const QUrl &url, ctxUrlList) { if (d->m_proxy->isValidDirectory(url)) { hasVersionControlledEntries = true; break; } } qCDebug(PLUGIN_CVS) << "version controlled?" << hasVersionControlledEntries; if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context); QMenu* menu = d->m_common->commonActions(); menu->addSeparator(); QAction *action; // Just add actions which are not covered by the cvscommon plugin action = new QAction(i18n("Edit"), this); connect(action, &QAction::triggered, this, &CvsPlugin::ctxEdit); menu->addAction(action); action = new QAction(i18n("Unedit"), this); connect(action, &QAction::triggered, this, &CvsPlugin::ctxUnEdit); menu->addAction(action); action = new QAction(i18n("Show Editors"), this); connect(action, &QAction::triggered, this, &CvsPlugin::ctxEditors); menu->addAction(action); KDevelop::ContextMenuExtension menuExt; menuExt.addAction(KDevelop::ContextMenuExtension::VcsGroup, menu->menuAction()); return menuExt; } void CvsPlugin::ctxEdit() { QList const & urls = d->m_common->contextUrlList(); Q_ASSERT(!urls.empty()); KDevelop::VcsJob* j = edit(urls.front()); CvsJob* job = dynamic_cast(j); if (job) { connect(job, &CvsJob::result, this, &CvsPlugin::jobFinished); KDevelop::ICore::self()->runController()->registerJob(job); } } void CvsPlugin::ctxUnEdit() { QList const & urls = d->m_common->contextUrlList(); Q_ASSERT(!urls.empty()); KDevelop::VcsJob* j = unedit(urls.front()); CvsJob* job = dynamic_cast(j); if (job) { connect(job, &CvsJob::result, this, &CvsPlugin::jobFinished); KDevelop::ICore::self()->runController()->registerJob(job); } } void CvsPlugin::ctxEditors() { QList const & urls = d->m_common->contextUrlList(); Q_ASSERT(!urls.empty()); CvsJob* job = d->m_proxy->editors(findWorkingDir(urls.front()), urls); if (job) { KDevelop::ICore::self()->runController()->registerJob(job); EditorsView* view = new EditorsView(job); emit addNewTabToMainView(view, i18n("Editors")); } } QString CvsPlugin::findWorkingDir(const QUrl& location) { QFileInfo fileInfo(location.toLocalFile()); // find out correct working directory if (fileInfo.isFile()) { return fileInfo.absolutePath(); } else { return fileInfo.absoluteFilePath(); } } // Begin: KDevelop::IBasicVersionControl bool CvsPlugin::isVersionControlled(const QUrl & localLocation) { return d->m_proxy->isVersionControlled(localLocation); } KDevelop::VcsJob * CvsPlugin::repositoryLocation(const QUrl & localLocation) { Q_UNUSED(localLocation); - return NULL; + return nullptr; } KDevelop::VcsJob * CvsPlugin::add(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { CvsJob* job = d->m_proxy->add(findWorkingDir(localLocations[0]), localLocations, (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false); return job; } KDevelop::VcsJob * CvsPlugin::remove(const QList & localLocations) { CvsJob* job = d->m_proxy->remove(findWorkingDir(localLocations[0]), localLocations); return job; } KDevelop::VcsJob * CvsPlugin::localRevision(const QUrl & localLocation, KDevelop::VcsRevision::RevisionType) { Q_UNUSED(localLocation) - return NULL; + return nullptr; } KDevelop::VcsJob * CvsPlugin::status(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { CvsJob* job = d->m_proxy->status(findWorkingDir(localLocations[0]), localLocations, (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false); return job; } KDevelop::VcsJob * CvsPlugin::unedit(const QUrl& localLocation) { CvsJob* job = d->m_proxy->unedit(findWorkingDir(localLocation), QList() << localLocation); return job; } KDevelop::VcsJob * CvsPlugin::edit(const QUrl& localLocation) { CvsJob* job = d->m_proxy->edit(findWorkingDir(localLocation), QList() << localLocation); return job; } KDevelop::VcsJob * CvsPlugin::copy(const QUrl & localLocationSrc, const QUrl & localLocationDstn) { bool ok = QFile::copy(localLocationSrc.toLocalFile(), localLocationDstn.path()); if (!ok) { - return NULL; + return nullptr; } QList listDstn; listDstn << localLocationDstn; CvsJob* job = d->m_proxy->add(findWorkingDir(localLocationDstn), listDstn, true); return job; } KDevelop::VcsJob * CvsPlugin::move(const QUrl &, const QUrl &) { - return NULL; + return nullptr; } KDevelop::VcsJob * CvsPlugin::revert(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { KDevelop::VcsRevision rev; CvsJob* job = d->m_proxy->update(findWorkingDir(localLocations[0]), localLocations, rev, QStringLiteral("-C"), (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false, false, false); return job; } KDevelop::VcsJob * CvsPlugin::update(const QList & localLocations, const KDevelop::VcsRevision & rev, KDevelop::IBasicVersionControl::RecursionMode recursion) { CvsJob* job = d->m_proxy->update(findWorkingDir(localLocations[0]), localLocations, rev, QString(), (recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false, false, false); return job; } KDevelop::VcsJob * CvsPlugin::commit(const QString & message, const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); QString msg = message; if (msg.isEmpty()) { CommitDialog dlg; if (dlg.exec() == QDialog::Accepted) { msg = dlg.message(); } } CvsJob* job = d->m_proxy->commit(findWorkingDir(localLocations[0]), localLocations, msg); return job; } KDevelop::VcsJob * CvsPlugin::diff(const QUrl & fileOrDirectory, const KDevelop::VcsRevision & srcRevision, const KDevelop::VcsRevision & dstRevision, KDevelop::VcsDiff::Type, KDevelop::IBasicVersionControl::RecursionMode) { CvsJob* job = d->m_proxy->diff(fileOrDirectory, srcRevision, dstRevision, QStringLiteral("-uN")/*always unified*/); return job; } KDevelop::VcsJob * CvsPlugin::log(const QUrl & localLocation, const KDevelop::VcsRevision & rev, unsigned long limit) { Q_UNUSED(limit) CvsJob* job = d->m_proxy->log(localLocation, rev); return job; } KDevelop::VcsJob * CvsPlugin::log(const QUrl & localLocation, const KDevelop::VcsRevision & rev, const KDevelop::VcsRevision & limit) { Q_UNUSED(limit) return log(localLocation, rev, 0); } KDevelop::VcsJob * CvsPlugin::annotate(const QUrl & localLocation, const KDevelop::VcsRevision & rev) { CvsJob* job = d->m_proxy->annotate(localLocation, rev); return job; } KDevelop::VcsJob * CvsPlugin::resolve(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(localLocations); Q_UNUSED(recursion); - return NULL; + return nullptr; } KDevelop::VcsJob * CvsPlugin::import(const QString& commitMessage, const QUrl& sourceDirectory, const KDevelop::VcsLocation& destinationRepository) { if (commitMessage.isEmpty() || !sourceDirectory.isLocalFile() || !destinationRepository.isValid() || destinationRepository.type() != KDevelop::VcsLocation::RepositoryLocation) { - return 0; + return nullptr; } qCDebug(PLUGIN_CVS) << "CVS Import requested " << "src:" << sourceDirectory.toLocalFile() << "srv:" << destinationRepository.repositoryServer() << "module:" << destinationRepository.repositoryModule(); CvsJob* job = d->m_proxy->import(sourceDirectory, destinationRepository.repositoryServer(), destinationRepository.repositoryModule(), destinationRepository.userData().toString(), destinationRepository.repositoryTag(), commitMessage); return job; } KDevelop::VcsJob * CvsPlugin::createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl & destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); if (!destinationDirectory.isLocalFile() || !sourceRepository.isValid() || sourceRepository.type() != KDevelop::VcsLocation::RepositoryLocation) { - return 0; + return nullptr; } qCDebug(PLUGIN_CVS) << "CVS Checkout requested " << "dest:" << destinationDirectory.toLocalFile() << "srv:" << sourceRepository.repositoryServer() << "module:" << sourceRepository.repositoryModule() << "branch:" << sourceRepository.repositoryBranch() << endl; CvsJob* job = d->m_proxy->checkout(destinationDirectory, sourceRepository.repositoryServer(), sourceRepository.repositoryModule(), QString(), sourceRepository.repositoryBranch(), true, true); return job; } QString CvsPlugin::name() const { return i18n("CVS"); } KDevelop::VcsImportMetadataWidget* CvsPlugin::createImportMetadataWidget(QWidget* parent) { return new ImportMetadataWidget(parent); } KDevelop::VcsLocationWidget* CvsPlugin::vcsLocation(QWidget* parent) const { return new KDevelop::StandardVcsLocationWidget(parent); } // End: KDevelop::IBasicVersionControl #include "cvsplugin.moc" diff --git a/plugins/cvs/cvsproxy.cpp b/plugins/cvs/cvsproxy.cpp index 14c7f0a5d3..75e8f80538 100644 --- a/plugins/cvs/cvsproxy.cpp +++ b/plugins/cvs/cvsproxy.cpp @@ -1,480 +1,480 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * 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. * * * ***************************************************************************/ #include "cvsproxy.h" #include #include #include #include #include #include #include #include "cvsjob.h" #include "cvsannotatejob.h" #include "cvslogjob.h" #include "cvsstatusjob.h" #include "cvsdiffjob.h" #include "debug.h" #include CvsProxy::CvsProxy(KDevelop::IPlugin* parent) : QObject(parent), vcsplugin(parent) { } CvsProxy::~CvsProxy() { } bool CvsProxy::isValidDirectory(QUrl dirPath) const { const QFileInfo fsObject( dirPath.toLocalFile() ); QDir dir = fsObject.isDir() ? fsObject.absoluteDir() : fsObject.dir(); return dir.exists(QStringLiteral("CVS")); } bool CvsProxy::isVersionControlled(QUrl filePath) const { const QFileInfo fsObject( filePath.toLocalFile() ); QDir dir = fsObject.isDir() ? fsObject.absoluteDir() : fsObject.dir(); if( !dir.cd(QStringLiteral("CVS")) ) return false; if( fsObject.isDir() ) return true; QFile cvsEntries( dir.absoluteFilePath(QStringLiteral("Entries")) ); cvsEntries.open( QIODevice::ReadOnly ); QString cvsEntriesData = cvsEntries.readAll(); cvsEntries.close(); return ( cvsEntriesData.indexOf( fsObject.fileName() ) != -1 ); } bool CvsProxy::prepareJob(CvsJob* job, const QString& repository, enum RequestedOperation op) { // Only do this check if it's a normal operation like diff, log ... // For other operations like "cvs import" isValidDirectory() would fail as the // directory is not yet under CVS control if (op == CvsProxy::NormalOperation && !isValidDirectory(QUrl::fromLocalFile(repository))) { qCDebug(PLUGIN_CVS) << repository << " is not a valid CVS repository"; return false; } // clear commands and args from a possible previous run job->clear(); // setup the working directory for the new job job->setDirectory(repository); return true; } bool CvsProxy::addFileList(CvsJob* job, const QString& repository, const QList& urls) { QStringList args; QDir repoDir(repository); foreach(const QUrl &url, urls) { ///@todo this is ok for now, but what if some of the urls are not /// to the given repository const QString file = repoDir.relativeFilePath(url.toLocalFile()); args << KShell::quoteArg( file ); } *job << args; return true; } QString CvsProxy::convertVcsRevisionToString(const KDevelop::VcsRevision & rev) { QString str; switch (rev.revisionType()) { case KDevelop::VcsRevision::Special: break; case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) str = "-r"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::Date: if (rev.revisionValue().isValid()) str = "-D"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::GlobalNumber: // !! NOT SUPPORTED BY CVS !! default: break; } return str; } QString CvsProxy::convertRevisionToPrevious(const KDevelop::VcsRevision& rev) { QString str; // this only works if the revision is a real revisionnumber and not a date or special switch (rev.revisionType()) { case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) { QString orig = rev.revisionValue().toString(); // First we need to find the base (aka branch-part) of the revision number which will not change QString base(orig); base.truncate(orig.lastIndexOf(QLatin1Char('.'))); // next we need to cut off the last part of the revision number // this number is a count of revisions with a branch // so if we want to diff to the previous we just need to lower it by one int number = orig.midRef(orig.lastIndexOf(QLatin1Char('.'))+1).toInt(); if (number > 1) // of course this is only possible if our revision is not the first on the branch number--; str = QStringLiteral("-r") + base + '.' + QString::number(number); qCDebug(PLUGIN_CVS) << "Converted revision "<(); if (specialtype == KDevelop::VcsRevision::Previous) { rA = convertRevisionToPrevious(revB); } } else { rA = convertVcsRevisionToString(revA); } if (!rA.isEmpty()) *job << rA; QString rB = convertVcsRevisionToString(revB); if (!rB.isEmpty()) *job << rB; // in case the QUrl is a directory there is no filename if (!info.fileName().isEmpty()) *job << KShell::quoteArg(info.fileName()); return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob * CvsProxy::annotate(const QUrl& url, const KDevelop::VcsRevision& rev) { QFileInfo info(url.toLocalFile()); CvsAnnotateJob* job = new CvsAnnotateJob(vcsplugin); if ( prepareJob(job, info.absolutePath()) ) { *job << "cvs"; *job << "annotate"; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; *job << KShell::quoteArg(info.fileName()); return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob* CvsProxy::edit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "edit"; addFileList(job, repo, files); return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob* CvsProxy::unedit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "unedit"; addFileList(job, repo, files); return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob* CvsProxy::editors(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "editors"; addFileList(job, repo, files); return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob* CvsProxy::commit(const QString& repo, const QList& files, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "commit"; *job << "-m"; *job << KShell::quoteArg( message ); addFileList(job, repo, files); return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob* CvsProxy::add(const QString& repo, const QList& files, bool recursiv, bool binary) { Q_UNUSED(recursiv); // FIXME recursiv is not implemented yet CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "add"; if (binary) { *job << "-kb"; } addFileList(job, repo, files); return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob * CvsProxy::remove(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "remove"; *job << "-f"; //existing files will be deleted addFileList(job, repo, files); return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob * CvsProxy::update(const QString& repo, const QList& files, const KDevelop::VcsRevision & rev, const QString & updateOptions, bool recursive, bool pruneDirs, bool createDirs) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "update"; if (recursive) *job << "-R"; else *job << "-L"; if (pruneDirs) *job << "-P"; if (createDirs) *job << "-d"; if (!updateOptions.isEmpty()) *job << updateOptions; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; addFileList(job, repo, files); return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob * CvsProxy::import(const QUrl& directory, const QString & server, const QString & repositoryName, const QString & vendortag, const QString & releasetag, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, directory.toLocalFile(), CvsProxy::Import) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d"; *job << server; *job << "import"; *job << "-m"; *job << KShell::quoteArg( message ); *job << repositoryName; *job << vendortag; *job << releasetag; return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob * CvsProxy::checkout(const QUrl& targetDir, const QString & server, const QString & module, const QString & checkoutOptions, const QString & revision, bool recursive, bool pruneDirs) { CvsJob* job = new CvsJob(vcsplugin); ///@todo when doing a checkout we don't have the targetdir yet, /// for now it'll work to just run the command from the root if ( prepareJob(job, QStringLiteral("/"), CvsProxy::CheckOut) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d" << server; *job << "checkout"; if (!checkoutOptions.isEmpty()) *job << checkoutOptions; if (!revision.isEmpty()) { *job << "-r" << revision; } if (pruneDirs) *job << "-P"; if (!recursive) *job << "-l"; *job << "-d" << targetDir.toString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); *job << module; return job; } if (job) delete job; - return NULL; + return nullptr; } CvsJob * CvsProxy::status(const QString & repo, const QList & files, bool recursive, bool taginfo) { CvsStatusJob* job = new CvsStatusJob(vcsplugin); job->setCommunicationMode( KProcess::MergedChannels ); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "status"; if (recursive) *job << "-R"; else *job << "-l"; if (taginfo) *job << "-v"; addFileList(job, repo, files); return job; } if (job) delete job; - return NULL; + return nullptr; } diff --git a/plugins/documentswitcher/documentswitcherplugin.cpp b/plugins/documentswitcher/documentswitcherplugin.cpp index 88da1c1d1b..2218c3b84c 100644 --- a/plugins/documentswitcher/documentswitcherplugin.cpp +++ b/plugins/documentswitcher/documentswitcherplugin.cpp @@ -1,396 +1,396 @@ /*************************************************************************** * Copyright 2009,2013 Andreas Pakulat * * Copyright 2013 Jarosław Sierant * * * * 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "documentswitcherplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "documentswitchertreeview.h" #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_DOCUMENTSWITCHER, "kdevplatform.plugins.documentswitcher") K_PLUGIN_FACTORY_WITH_JSON(DocumentSwitcherFactory, "kdevdocumentswitcher.json", registerPlugin();) //TODO: Show frame around view's widget while walking through //TODO: Make the widget transparent DocumentSwitcherPlugin::DocumentSwitcherPlugin(QObject *parent, const QVariantList &/*args*/) - :KDevelop::IPlugin(QStringLiteral("kdevdocumentswitcher"), parent), view(0) + :KDevelop::IPlugin(QStringLiteral("kdevdocumentswitcher"), parent), view(nullptr) { setXMLFile(QStringLiteral("kdevdocumentswitcher.rc")); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "Adding active mainwindow from constructor" << KDevelop::ICore::self()->uiController()->activeMainWindow(); addMainWindow( qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ) ); connect( KDevelop::ICore::self()->uiController()->controller(), &Sublime::Controller::mainWindowAdded, this, &DocumentSwitcherPlugin::addMainWindow ); forwardAction = actionCollection()->addAction ( QStringLiteral( "last_used_views_forward" ) ); forwardAction->setText( i18n( "Last Used Views" ) ); forwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "go-next-view-page") ) ); actionCollection()->setDefaultShortcut( forwardAction, Qt::CTRL | Qt::Key_Tab ); forwardAction->setWhatsThis( i18n( "Opens a list to walk through the list of last used views." ) ); forwardAction->setStatusTip( i18n( "Walk through the list of last used views" ) ); connect( forwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkForward ); backwardAction = actionCollection()->addAction ( QStringLiteral( "last_used_views_backward" ) ); backwardAction->setText( i18n( "Last Used Views (Reverse)" ) ); backwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "go-previous-view-page") ) ); actionCollection()->setDefaultShortcut( backwardAction, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab ); backwardAction->setWhatsThis( i18n( "Opens a list to walk through the list of last used views in reverse." ) ); backwardAction->setStatusTip( i18n( "Walk through the list of last used views" ) ); connect( backwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkBackward ); view = new DocumentSwitcherTreeView( this ); view->setSelectionBehavior( QAbstractItemView::SelectRows ); view->setSelectionMode( QAbstractItemView::SingleSelection ); view->setUniformRowHeights( true ); view->setTextElideMode( Qt::ElideMiddle ); view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); view->addAction( forwardAction ); view->addAction( backwardAction ); view->setHeaderHidden( true ); view->setIndentation( 10 ); connect( view, &QListView::pressed, this, &DocumentSwitcherPlugin::switchToClicked ); connect( view, &QListView::activated, this, &DocumentSwitcherPlugin::itemActivated ); model = new QStandardItemModel( view ); view->setModel( model ); } void DocumentSwitcherPlugin::setViewGeometry(Sublime::MainWindow* window) { const QSize centralSize = window->centralWidget()->size(); // Maximum size of the view is 3/4th of the central widget (the editor area) so the view does not overlap the // mainwindow since that looks awkward. const QSize viewMaxSize( centralSize.width() * 3/4, centralSize.height() * 3/4 ); // The actual view size should be as big as the columns/rows need it, but smaller than the max-size. This means // the view will get quite high with many open files but I think that is ok. Otherwise one can easily tweak the // max size to be only 1/2th of the central widget size const int rowHeight = view->sizeHintForRow(0); const int frameWidth = view->frameWidth(); const QSize viewSize( std::min( view->sizeHintForColumn(0) + 2 * frameWidth + view->verticalScrollBar()->width(), viewMaxSize.width() ), std::min( std::max( rowHeight * view->model()->rowCount() + 2 * frameWidth, rowHeight * 6 ), viewMaxSize.height() ) ); // Position should be central over the editor area, so map to global from parent of central widget since // the view is positioned in global coords QPoint centralWidgetPos = window->mapToGlobal( window->centralWidget()->pos() ); const int xPos = std::max(0, centralWidgetPos.x() + (centralSize.width() - viewSize.width() ) / 2); const int yPos = std::max(0, centralWidgetPos.y() + (centralSize.height() - viewSize.height() ) / 2); view->setFixedSize(viewSize); view->move(xPos, yPos); } void DocumentSwitcherPlugin::walk(const int from, const int to) { Sublime::MainWindow* window = qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ); if( !window || !documentLists.contains( window ) || !documentLists[window].contains( window->area() ) ) { qWarning() << "This should not happen, tried to walk through document list of an unknown mainwindow!"; return; } QModelIndex idx; const int step = from < to ? 1 : -1; if(!view->isVisible()) { fillModel(window); setViewGeometry(window); idx = model->index(from + step, 0); if(!idx.isValid()) { idx = model->index(0, 0); } view->show(); } else { int newRow = view->selectionModel()->currentIndex().row() + step; if(newRow == to + step) { newRow = from; } idx = model->index(newRow, 0); } view->selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); view->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } void DocumentSwitcherPlugin::walkForward() { walk(0, model->rowCount()-1); } void DocumentSwitcherPlugin::walkBackward() { walk(model->rowCount()-1, 0); } void DocumentSwitcherPlugin::fillModel( Sublime::MainWindow* window ) { model->clear(); auto projectController = KDevelop::ICore::self()->projectController(); foreach( Sublime::View* v, documentLists[window][window->area()] ) { using namespace KDevelop; Sublime::Document const* const slDoc = v->document(); if( !slDoc ) { continue; } QString itemText = slDoc->title();// file name IDocument const* const doc = dynamic_cast(v->document()); IProject* project = nullptr; if( doc ) { QString path = projectController->prettyFilePath(doc->url(), IProjectController::FormatPlain); const bool isPartOfOpenProject = QDir::isRelativePath(path); if( path.endsWith('/') ) { path.remove(path.length() - 1, 1); } if( isPartOfOpenProject ) { const int projectNameSize = path.indexOf(QLatin1Char(':')); // first: project name, second: path to file in project (might be just '/' when the file is in the project root dir) const QPair fileInProjectInfo = (projectNameSize < 0) ? qMakePair(path, QStringLiteral("/")) : qMakePair(path.left(projectNameSize), path.mid(projectNameSize + 1)); itemText = QStringLiteral("%1 (%2:%3)").arg(itemText, fileInProjectInfo.first, fileInProjectInfo.second); } else { itemText = itemText + " (" + path + ')'; } project = projectController->findProjectForUrl(doc->url()); } auto item = new QStandardItem( slDoc->icon(), itemText ); item->setData(QVariant::fromValue(project), DocumentSwitcherTreeView::ProjectRole); model->appendRow( item ); } } DocumentSwitcherPlugin::~DocumentSwitcherPlugin() { } void DocumentSwitcherPlugin::switchToClicked( const QModelIndex& idx ) { view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); itemActivated(idx); } void DocumentSwitcherPlugin::itemActivated( const QModelIndex& idx ) { Q_UNUSED( idx ); if( view->selectionModel()->selectedRows().isEmpty() ) { return; } int row = view->selectionModel()->selectedRows().first().row(); Sublime::MainWindow* window = qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ); - Sublime::View* activatedView = 0; + Sublime::View* activatedView = nullptr; if( window && documentLists.contains( window ) && documentLists[window].contains( window->area() ) ) { const QList l = documentLists[window][window->area()]; if( row >= 0 && row < l.size() ) { activatedView = l.at( row ); } } if( activatedView ) { if( QApplication::mouseButtons() & Qt::MiddleButton ) { window->area()->closeView( activatedView ); fillModel( window ); if ( model->rowCount() == 0 ) { view->hide(); } else { view->selectionModel()->select( view->model()->index(0, 0), QItemSelectionModel::ClearAndSelect ); } } else { window->activateView( activatedView ); view->hide(); } } } void DocumentSwitcherPlugin::unload() { foreach( QObject* mw, documentLists.keys() ) { removeMainWindow( mw ); } delete forwardAction; delete backwardAction; view->deleteLater(); } void DocumentSwitcherPlugin::storeAreaViewList( Sublime::MainWindow* mainwindow, Sublime::Area* area ) { if( !documentLists.contains( mainwindow ) || !documentLists[mainwindow].contains(area) ) { QHash > areas; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "adding area views for area:" << area << area->title() << "mainwindow:" << mainwindow << mainwindow->windowTitle(); foreach( Sublime::View* v, area->views() ) { qCDebug(PLUGIN_DOCUMENTSWITCHER) << "view:" << v << v->document()->title(); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "done"; areas.insert( area, area->views() ); documentLists.insert( mainwindow, areas ); } } void DocumentSwitcherPlugin::addMainWindow( Sublime::MainWindow* mainwindow ) { if( !mainwindow ) { return; } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "adding mainwindow:" << mainwindow << mainwindow->windowTitle(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "storing all views from area:" << mainwindow->area()->title() << mainwindow->area(); storeAreaViewList( mainwindow, mainwindow->area() ); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "connecting signals on mainwindow"; connect( mainwindow, &Sublime::MainWindow::areaChanged, this, &DocumentSwitcherPlugin::changeArea ); connect( mainwindow, &Sublime::MainWindow::activeViewChanged, this, &DocumentSwitcherPlugin::changeView ); connect( mainwindow, &Sublime::MainWindow::viewAdded, this, &DocumentSwitcherPlugin::addView ); connect( mainwindow, &Sublime::MainWindow::aboutToRemoveView, this, &DocumentSwitcherPlugin::removeView ); connect( mainwindow, &Sublime::MainWindow::destroyed, this, &DocumentSwitcherPlugin::removeMainWindow); mainwindow->installEventFilter( this ); } bool DocumentSwitcherPlugin::eventFilter( QObject* watched, QEvent* ev ) { Sublime::MainWindow* mw = dynamic_cast( watched ); if( mw && ev->type() == QEvent::WindowActivate ) { enableActions(); } return QObject::eventFilter( watched, ev ); } void DocumentSwitcherPlugin::addView( Sublime::View* view ) { Sublime::MainWindow* mainwindow = qobject_cast( sender() ); if( !mainwindow ) return; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "got signal from mainwindow:" << mainwindow << mainwindow->windowTitle() << "its area is:" << mainwindow->area() << mainwindow->area()->title() << "adding view:" << view << view->document()->title(); enableActions(); documentLists[mainwindow][mainwindow->area()].append( view ); } void DocumentSwitcherPlugin::enableActions() { forwardAction->setEnabled(true); backwardAction->setEnabled(true); } void DocumentSwitcherPlugin::removeMainWindow( QObject* obj ) { if( !obj || !documentLists.contains(obj) ) { return; } obj->removeEventFilter( this ); - disconnect( obj, 0, this, 0 ); + disconnect( obj, nullptr, this, nullptr ); documentLists.remove( obj ); } void DocumentSwitcherPlugin::changeArea( Sublime::Area* area ) { Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "area changed:" << area << area->title() << "mainwindow:" << mainwindow << mainwindow->windowTitle(); //Since the main-window only emits aboutToRemoveView for views within the current area, we must forget all areas except the active one documentLists.remove(mainwindow); if( !documentLists[mainwindow].contains( area ) ) { qCDebug(PLUGIN_DOCUMENTSWITCHER) << "got area change, storing its views"; storeAreaViewList( mainwindow, area ); } enableActions(); } void DocumentSwitcherPlugin::changeView( Sublime::View* view ) { if( !view ) return; Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); Sublime::Area* area = mainwindow->area(); int idx = documentLists[mainwindow][area].indexOf( view ); if( idx != -1 ) { documentLists[mainwindow][area].removeAt( idx ); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "moving view to front, list should now not contain this view anymore" << view << view->document()->title(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "current area is:" << area << area->title() << "mainwnidow:" << mainwindow << mainwindow->windowTitle();; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "idx of this view in list:" << documentLists[mainwindow][area].indexOf( view ); documentLists[mainwindow][area].prepend( view ); enableActions(); } void DocumentSwitcherPlugin::removeView( Sublime::View* view ) { if( !view ) return; Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); Sublime::Area* area = mainwindow->area(); int idx = documentLists[mainwindow][area].indexOf( view ); if( idx != -1 ) { documentLists[mainwindow][area].removeAt( idx ); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "removing view, list should now not contain this view anymore" << view << view->document()->title(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "current area is:" << area << area->title() << "mainwnidow:" << mainwindow << mainwindow->windowTitle();; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "idx of this view in list:" << documentLists[mainwindow][area].indexOf( view ); enableActions(); } #include "documentswitcherplugin.moc" diff --git a/plugins/documentview/kdevdocumentmodel.cpp b/plugins/documentview/kdevdocumentmodel.cpp index ab75a35ac7..76e385ca3c 100644 --- a/plugins/documentview/kdevdocumentmodel.cpp +++ b/plugins/documentview/kdevdocumentmodel.cpp @@ -1,166 +1,166 @@ /* This file is part of KDevelop Copyright 2005 Adam Treat Copyright 2013 Sebastian Kügler 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 "kdevdocumentmodel.h" #include using namespace KDevelop; KDevDocumentItem::KDevDocumentItem( const QString &name ) : QStandardItem(name) , m_documentState(IDocument::Clean) { setIcon( icon() ); } KDevDocumentItem::~KDevDocumentItem() {} QIcon KDevDocumentItem::icon() const { switch(m_documentState) { case IDocument::Clean: return QIcon::fromTheme(m_fileIcon); case IDocument::Modified: return QIcon::fromTheme(QStringLiteral("document-save")); case IDocument::Dirty: return QIcon::fromTheme(QStringLiteral("document-revert")); case IDocument::DirtyAndModified: return QIcon::fromTheme(QStringLiteral("edit-delete")); default: return QIcon(); } } IDocument::DocumentState KDevDocumentItem::documentState() const { return m_documentState; } void KDevDocumentItem::setDocumentState(IDocument::DocumentState state) { m_documentState = state; setIcon(icon()); } const QUrl KDevDocumentItem::url() const { return m_url; } void KDevDocumentItem::setUrl(const QUrl& url) { m_url = url; } QVariant KDevDocumentItem::data(int role) const { if (role == UrlRole) { return m_url; } return QStandardItem::data(role); } KDevCategoryItem::KDevCategoryItem( const QString &name ) : KDevDocumentItem( name ) { setFlags(Qt::ItemIsEnabled); setToolTip( name ); setIcon( QIcon::fromTheme( QStringLiteral( "folder") ) ); } KDevCategoryItem::~KDevCategoryItem() {} QList KDevCategoryItem::fileList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { if ( KDevFileItem * item = dynamic_cast( child( i ) ) ->fileItem() ) lst.append( item ); } return lst; } KDevFileItem* KDevCategoryItem::file( const QUrl &url ) const { foreach( KDevFileItem * item, fileList() ) { if ( item->url() == url ) return item; } - return 0; + return nullptr; } KDevFileItem::KDevFileItem( const QUrl &url ) : KDevDocumentItem( url.fileName() ) { setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); setUrl( url ); if (!url.isEmpty()) { m_fileIcon = KFileItem(url, QString(), 0).iconName(); } setIcon( QIcon::fromTheme( m_fileIcon ) ); } KDevFileItem::~KDevFileItem() {} KDevDocumentModel::KDevDocumentModel( QObject *parent ) : QStandardItemModel( parent ) { setRowCount(0); setColumnCount(1); } KDevDocumentModel::~KDevDocumentModel() {} QList KDevDocumentModel::categoryList() const { QList lst; for ( int i = 0; i < rowCount() ; ++i ) { if ( KDevCategoryItem * categoryitem = dynamic_cast( item( i ) ) ->categoryItem() ) { lst.append( categoryitem ); } } return lst; } KDevCategoryItem* KDevDocumentModel::category( const QString& category ) const { foreach( KDevCategoryItem * item, categoryList() ) { if ( item->toolTip() == category ) return item; } - return 0; + return nullptr; } diff --git a/plugins/documentview/kdevdocumentviewplugin.cpp b/plugins/documentview/kdevdocumentviewplugin.cpp index 15b68ba96e..50caa2e6d9 100644 --- a/plugins/documentview/kdevdocumentviewplugin.cpp +++ b/plugins/documentview/kdevdocumentviewplugin.cpp @@ -1,108 +1,108 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdevdocumentviewplugin.h" #include "kdevdocumentview.h" #include "kdevdocumentmodel.h" #include "kdevdocumentselection.h" #include #include #include #include #include #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(KDevDocumentViewFactory, "kdevdocumentview.json", registerPlugin();) class KDevDocumentViewPluginFactory: public KDevelop::IToolViewFactory { public: KDevDocumentViewPluginFactory( KDevDocumentViewPlugin *plugin ): m_plugin( plugin ) {} - QWidget* create( QWidget *parent = 0 ) override + QWidget* create( QWidget *parent = nullptr ) override { KDevDocumentView* view = new KDevDocumentView( m_plugin, parent ); KDevelop::IDocumentController* docController = m_plugin->core()->documentController(); foreach(KDevelop::IDocument* doc, docController->openDocuments()) { view->opened( doc ); } QObject::connect( docController, &IDocumentController::documentActivated, view, &KDevDocumentView::activated ); QObject::connect( docController, &IDocumentController::documentSaved, view, &KDevDocumentView::saved ); QObject::connect( docController, &IDocumentController::documentOpened, view, &KDevDocumentView::opened ); QObject::connect( docController, &IDocumentController::documentClosed, view, &KDevDocumentView::closed ); QObject::connect( docController, &IDocumentController::documentContentChanged, view, &KDevDocumentView::contentChanged ); QObject::connect( docController, &IDocumentController::documentStateChanged, view, &KDevDocumentView::stateChanged ); QObject::connect( docController, &IDocumentController::documentUrlChanged, view, &KDevDocumentView::documentUrlChanged ); return view; } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.DocumentsView"); } private: KDevDocumentViewPlugin* m_plugin; }; KDevDocumentViewPlugin::KDevDocumentViewPlugin( QObject *parent, const QVariantList& args ) : KDevelop::IPlugin( QStringLiteral( "kdevdocumentview" ), parent ) { Q_UNUSED( args ); factory = new KDevDocumentViewPluginFactory( this ); core()->uiController()->addToolView( i18n("Documents"), factory ); setXMLFile( QStringLiteral( "kdevdocumentview.rc" ) ); } KDevDocumentViewPlugin::~KDevDocumentViewPlugin() { } void KDevDocumentViewPlugin::unload() { core()->uiController()->removeToolView( factory ); } #include "kdevdocumentviewplugin.moc" diff --git a/plugins/execute/executeplugin.cpp b/plugins/execute/executeplugin.cpp index 9532c85df4..2a041a0535 100644 --- a/plugins/execute/executeplugin.cpp +++ b/plugins/execute/executeplugin.cpp @@ -1,252 +1,252 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "executeplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nativeappconfig.h" #include "debug.h" #include #include #include QString ExecutePlugin::_nativeAppConfigTypeId = QStringLiteral("Native Application"); QString ExecutePlugin::workingDirEntry = QStringLiteral("Working Directory"); QString ExecutePlugin::executableEntry = QStringLiteral("Executable"); QString ExecutePlugin::argumentsEntry = QStringLiteral("Arguments"); QString ExecutePlugin::isExecutableEntry = QStringLiteral("isExecutable"); QString ExecutePlugin::dependencyEntry = QStringLiteral("Dependencies"); QString ExecutePlugin::environmentGroupEntry = QStringLiteral("EnvironmentGroup"); QString ExecutePlugin::useTerminalEntry = QStringLiteral("Use External Terminal"); QString ExecutePlugin::terminalEntry = QStringLiteral("External Terminal"); QString ExecutePlugin::userIdToRunEntry = QStringLiteral("User Id to Run"); QString ExecutePlugin::dependencyActionEntry = QStringLiteral("Dependency Action"); QString ExecutePlugin::projectTargetEntry = QStringLiteral("Project Target"); using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_EXECUTE, "kdevplatform.plugins.execute") K_PLUGIN_FACTORY_WITH_JSON(KDevExecuteFactory, "kdevexecute.json", registerPlugin();) ExecutePlugin::ExecutePlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevexecute"), parent) { KDEV_USE_EXTENSION_INTERFACE( IExecutePlugin ) m_configType = new NativeAppConfigType(); m_configType->addLauncher( new NativeAppLauncher() ); qCDebug(PLUGIN_EXECUTE) << "adding native app launch config"; core()->runController()->addConfigurationType( m_configType ); } ExecutePlugin::~ExecutePlugin() { } void ExecutePlugin::unload() { core()->runController()->removeConfigurationType( m_configType ); delete m_configType; - m_configType = 0; + m_configType = nullptr; } QStringList ExecutePlugin::arguments( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { if( !cfg ) { return QStringList(); } KShell::Errors err; QStringList args = KShell::splitArgs( cfg->config().readEntry( ExecutePlugin::argumentsEntry, "" ), KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err != KShell::NoError ) { if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the arguments for " "the launch configuration '%1'. Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "arguments for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } args = QStringList(); qWarning() << "Launch Configuration:" << cfg->name() << "arguments have meta characters"; } return args; } KJob* ExecutePlugin::dependencyJob( KDevelop::ILaunchConfiguration* cfg ) const { QVariantList deps = KDevelop::stringToQVariant( cfg->config().readEntry( dependencyEntry, QString() ) ).toList(); QString depAction = cfg->config().readEntry( dependencyActionEntry, "Nothing" ); if( depAction != QLatin1String("Nothing") && !deps.isEmpty() ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList items; foreach( const QVariant& dep, deps ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex( dep.toStringList() ) ); if( item ) { items << item; } else { KMessageBox::error(core()->uiController()->activeMainWindow(), i18n("Couldn't resolve the dependency: %1", dep.toString())); } } KDevelop::BuilderJob* job = new KDevelop::BuilderJob(); if( depAction == QLatin1String("Build") ) { job->addItems( KDevelop::BuilderJob::Build, items ); } else if( depAction == QLatin1String("Install") ) { job->addItems( KDevelop::BuilderJob::Install, items ); } job->updateJobName(); return job; } - return 0; + return nullptr; } QString ExecutePlugin::environmentGroup( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QLatin1String(""); } return cfg->config().readEntry( ExecutePlugin::environmentGroupEntry, "" ); } QUrl ExecutePlugin::executable( KDevelop::ILaunchConfiguration* cfg, QString& err ) const { QUrl executable; if( !cfg ) { return executable; } KConfigGroup grp = cfg->config(); if( grp.readEntry(ExecutePlugin::isExecutableEntry, false ) ) { executable = grp.readEntry( ExecutePlugin::executableEntry, QUrl() ); } else { QStringList prjitem = grp.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex(prjitem) ); if( item && item->executable() ) { // TODO: Need an option in the gui to choose between installed and builddir url here, currently cmake only supports builddir url executable = item->executable()->builtUrl(); } } if( executable.isEmpty() ) { err = i18n("No valid executable specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid executable set"; } else { KShell::Errors err_; if( KShell::splitArgs( executable.toLocalFile(), KShell::TildeExpand | KShell::AbortOnMeta, &err_ ).isEmpty() || err_ != KShell::NoError ) { executable = QUrl(); if( err_ == KShell::BadQuoting ) { err = i18n("There is a quoting error in the executable " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err = i18n("A shell meta character was included in the " "executable for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "executable has meta characters"; } } return executable; } bool ExecutePlugin::useTerminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecutePlugin::useTerminalEntry, false ); } QString ExecutePlugin::terminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QString(); } return cfg->config().readEntry( ExecutePlugin::terminalEntry, QString() ); } QUrl ExecutePlugin::workingDirectory( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QUrl(); } return cfg->config().readEntry( ExecutePlugin::workingDirEntry, QUrl() ); } QString ExecutePlugin::nativeAppConfigTypeId() const { return _nativeAppConfigTypeId; } #include "executeplugin.moc" diff --git a/plugins/execute/nativeappconfig.cpp b/plugins/execute/nativeappconfig.cpp index a2a7b45b7e..b7f72c615c 100644 --- a/plugins/execute/nativeappconfig.cpp +++ b/plugins/execute/nativeappconfig.cpp @@ -1,395 +1,395 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat 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 "nativeappconfig.h" #include #include #include #include #include "nativeappjob.h" #include #include #include #include #include #include #include #include #include "executeplugin.h" #include "debug.h" #include #include #include #include "projecttargetscombobox.h" #include #include #include #include #include #include #include using namespace KDevelop; QIcon NativeAppConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("system-run")); } static KDevelop::ProjectBaseItem* itemForPath(const QStringList& path, KDevelop::ProjectModel* model) { return model->itemFromIndex(model->pathToIndex(path)); } //TODO: Make sure to auto-add the executable target to the dependencies when its used. void NativeAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project ) { bool b = blockSignals( true ); - projectTarget->setBaseItem( project ? project->projectItem() : 0, true); + projectTarget->setBaseItem( project ? project->projectItem() : nullptr, true); projectTarget->setCurrentItemPath( cfg.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ) ); QUrl exe = cfg.readEntry( ExecutePlugin::executableEntry, QUrl()); if( !exe.isEmpty() || project ){ executablePath->setUrl( !exe.isEmpty() ? exe : project->path().toUrl() ); }else{ KDevelop::IProjectController* pc = KDevelop::ICore::self()->projectController(); if( pc ){ executablePath->setUrl( pc->projects().isEmpty() ? QUrl() : pc->projects().at(0)->path().toUrl() ); } } dependencies->setSuggestion(project); //executablePath->setFilter("application/x-executable"); executableRadio->setChecked( true ); if ( !cfg.readEntry( ExecutePlugin::isExecutableEntry, false ) && projectTarget->count() ){ projectTargetRadio->setChecked( true ); } arguments->setClearButtonEnabled( true ); arguments->setText( cfg.readEntry( ExecutePlugin::argumentsEntry, "" ) ); workingDirectory->setUrl( cfg.readEntry( ExecutePlugin::workingDirEntry, QUrl() ) ); environment->setCurrentProfile( cfg.readEntry( ExecutePlugin::environmentGroupEntry, QString() ) ); runInTerminal->setChecked( cfg.readEntry( ExecutePlugin::useTerminalEntry, false ) ); terminal->setEditText( cfg.readEntry( ExecutePlugin::terminalEntry, terminal->itemText(0) ) ); dependencies->setDependencies(KDevelop::stringToQVariant( cfg.readEntry( ExecutePlugin::dependencyEntry, QString() ) ).toList()); dependencyAction->setCurrentIndex( dependencyAction->findData( cfg.readEntry( ExecutePlugin::dependencyActionEntry, "Nothing" ) ) ); blockSignals( b ); } NativeAppConfigPage::NativeAppConfigPage( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); //Setup data info for combobox dependencyAction->setItemData(0, "Nothing" ); dependencyAction->setItemData(1, "Build" ); dependencyAction->setItemData(2, "Install" ); dependencyAction->setItemData(3, "SudoInstall" ); //Set workingdirectory widget to ask for directories rather than files workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); configureEnvironment->setSelectionWidget(environment); //connect signals to changed signal connect( projectTarget, static_cast(&ProjectTargetsComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( projectTargetRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); connect( executableRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); connect( executablePath->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( executablePath, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); connect( arguments, &QLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( workingDirectory, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); connect( workingDirectory->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &NativeAppConfigPage::changed ); connect( dependencyAction, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( runInTerminal, &QCheckBox::toggled, this, &NativeAppConfigPage::changed ); connect( terminal, &KComboBox::editTextChanged, this, &NativeAppConfigPage::changed ); connect( terminal, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( dependencyAction, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::activateDeps ); connect( dependencies, &DependenciesWidget::changed, this, &NativeAppConfigPage::changed ); } void NativeAppConfigPage::activateDeps( int idx ) { dependencies->setEnabled( dependencyAction->itemData( idx ).toString() != QLatin1String("Nothing") ); } void NativeAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry( ExecutePlugin::isExecutableEntry, executableRadio->isChecked() ); cfg.writeEntry( ExecutePlugin::executableEntry, executablePath->url() ); cfg.writeEntry( ExecutePlugin::projectTargetEntry, projectTarget->currentItemPath() ); cfg.writeEntry( ExecutePlugin::argumentsEntry, arguments->text() ); cfg.writeEntry( ExecutePlugin::workingDirEntry, workingDirectory->url() ); cfg.writeEntry( ExecutePlugin::environmentGroupEntry, environment->currentProfile() ); cfg.writeEntry( ExecutePlugin::useTerminalEntry, runInTerminal->isChecked() ); cfg.writeEntry( ExecutePlugin::terminalEntry, terminal->currentText() ); cfg.writeEntry( ExecutePlugin::dependencyActionEntry, dependencyAction->itemData( dependencyAction->currentIndex() ).toString() ); QVariantList deps = dependencies->dependencies(); cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariant( deps ) ) ); } QString NativeAppConfigPage::title() const { return i18n("Configure Native Application"); } QList< KDevelop::LaunchConfigurationPageFactory* > NativeAppLauncher::configPages() const { return QList(); } QString NativeAppLauncher::description() const { return i18n("Executes Native Applications"); } QString NativeAppLauncher::id() { return QStringLiteral("nativeAppLauncher"); } QString NativeAppLauncher::name() const { return i18n("Native Application"); } NativeAppLauncher::NativeAppLauncher() { } KJob* NativeAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { - return 0; + return nullptr; } if( launchMode == QLatin1String("execute") ) { IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension(); Q_ASSERT(iface); KJob* depjob = iface->dependencyJob( cfg ); QList l; if( depjob ) { l << depjob; } l << new NativeAppJob( KDevelop::ICore::self()->runController(), cfg ); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l ); } qWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); - return 0; + return nullptr; } QStringList NativeAppLauncher::supportedModes() const { return QStringList() << QStringLiteral("execute"); } KDevelop::LaunchConfigurationPage* NativeAppPageFactory::createWidget(QWidget* parent) { return new NativeAppConfigPage( parent ); } NativeAppPageFactory::NativeAppPageFactory() { } NativeAppConfigType::NativeAppConfigType() { factoryList.append( new NativeAppPageFactory() ); } NativeAppConfigType::~NativeAppConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString NativeAppConfigType::name() const { return i18n("Compiled Binary"); } QList NativeAppConfigType::configPages() const { return factoryList; } QString NativeAppConfigType::id() const { return ExecutePlugin::_nativeAppConfigTypeId; } QIcon NativeAppConfigType::icon() const { return QIcon::fromTheme(QStringLiteral("application-x-executable")); } bool NativeAppConfigType::canLaunch ( KDevelop::ProjectBaseItem* item ) const { if( item->target() && item->target()->executable() ) { return canLaunch( item->target()->executable()->builtUrl() ); } return false; } bool NativeAppConfigType::canLaunch ( const QUrl& file ) const { return ( file.isLocalFile() && QFileInfo( file.toLocalFile() ).isExecutable() ); } void NativeAppConfigType::configureLaunchFromItem ( KConfigGroup cfg, KDevelop::ProjectBaseItem* item ) const { cfg.writeEntry( ExecutePlugin::isExecutableEntry, false ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); cfg.writeEntry( ExecutePlugin::projectTargetEntry, model->pathFromIndex( model->indexFromItem( item ) ) ); cfg.writeEntry( ExecutePlugin::workingDirEntry, item->executable()->builtUrl().adjusted(QUrl::RemoveFilename) ); cfg.sync(); } void NativeAppConfigType::configureLaunchFromCmdLineArguments ( KConfigGroup cfg, const QStringList& args ) const { cfg.writeEntry( ExecutePlugin::isExecutableEntry, true ); Q_ASSERT(QFile::exists(args.first())); // TODO: we probably want to flexibilize, but at least we won't be accepting wrong values anymore cfg.writeEntry( ExecutePlugin::executableEntry, QUrl::fromLocalFile(args.first()) ); QStringList a(args); a.removeFirst(); cfg.writeEntry( ExecutePlugin::argumentsEntry, KShell::joinArgs(a) ); cfg.sync(); } QList targetsInFolder(KDevelop::ProjectFolderItem* folder) { QList ret; foreach(KDevelop::ProjectFolderItem* f, folder->folderList()) ret += targetsInFolder(f); ret += folder->targetList(); return ret; } bool actionLess(QAction* a, QAction* b) { return a->text() < b->text(); } bool menuLess(QMenu* a, QMenu* b) { return a->title() < b->title(); } QMenu* NativeAppConfigType::launcherSuggestions() { QMenu* ret = new QMenu(i18n("Project Executables")); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList projects = KDevelop::ICore::self()->projectController()->projects(); foreach(KDevelop::IProject* project, projects) { if(project->projectFileManager()->features() & KDevelop::IProjectFileManager::Targets) { QList targets=targetsInFolder(project->projectItem()); QHash > targetsContainer; QMenu* projectMenu = ret->addMenu(QIcon::fromTheme(QStringLiteral("project-development")), project->name()); foreach(KDevelop::ProjectTargetItem* target, targets) { if(target->executable()) { QStringList path = model->pathFromIndex(target->index()); if(!path.isEmpty()){ QAction* act = new QAction(projectMenu); act->setData(KDevelop::joinWithEscaping(path, '/','\\')); act->setProperty("name", target->text()); path.removeFirst(); act->setText(path.join(QStringLiteral("/"))); act->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); connect(act, &QAction::triggered, this, &NativeAppConfigType::suggestionTriggered); targetsContainer[target->parent()].append(act); } } } QList separateActions; QList submenus; foreach(KDevelop::ProjectBaseItem* folder, targetsContainer.keys()) { QList actions = targetsContainer.value(folder); if(actions.size()==1 || !folder->parent()) { separateActions += actions.first(); } else { foreach(QAction* a, actions) { a->setText(a->property("name").toString()); } QStringList path = model->pathFromIndex(folder->index()); path.removeFirst(); QMenu* submenu = new QMenu(path.join(QStringLiteral("/"))); std::sort(actions.begin(), actions.end(), actionLess); submenu->addActions(actions); submenus += submenu; } } std::sort(separateActions.begin(), separateActions.end(), actionLess); std::sort(submenus.begin(), submenus.end(), menuLess); foreach(QMenu* m, submenus) projectMenu->addMenu(m); projectMenu->addActions(separateActions); projectMenu->setEnabled(!projectMenu->isEmpty()); } } return ret; } void NativeAppConfigType::suggestionTriggered() { QAction* action = qobject_cast(sender()); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectTargetItem* pitem = dynamic_cast(itemForPath(KDevelop::splitWithEscaping(action->data().toString(),'/', '\\'), model)); if(pitem) { QPair launcher = qMakePair( launchers().at( 0 )->supportedModes().at(0), launchers().at( 0 )->id() ); KDevelop::IProject* p = pitem->project(); KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, pitem->text()); KConfigGroup cfg = config->config(); QStringList splitPath = model->pathFromIndex(pitem->index()); // QString path = KDevelop::joinWithEscaping(splitPath,'/','\\'); cfg.writeEntry( ExecutePlugin::projectTargetEntry, splitPath ); cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariantList() << splitPath ) ); cfg.writeEntry( ExecutePlugin::dependencyActionEntry, "Build" ); cfg.sync(); emit signalAddLaunchConfiguration(config); } } diff --git a/plugins/executescript/executescriptplugin.cpp b/plugins/executescript/executescriptplugin.cpp index 8b9718cfa4..c97a54c392 100644 --- a/plugins/executescript/executescriptplugin.cpp +++ b/plugins/executescript/executescriptplugin.cpp @@ -1,263 +1,263 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "executescriptplugin.h" #include #include #include #include #include #include #include #include #include #include #include "scriptappconfig.h" #include "debug.h" #include #include #include QString ExecuteScriptPlugin::_scriptAppConfigTypeId = QStringLiteral("Script Application"); QString ExecuteScriptPlugin::interpreterEntry = QStringLiteral("Interpreter"); QString ExecuteScriptPlugin::workingDirEntry = QStringLiteral("Working Directory"); QString ExecuteScriptPlugin::executableEntry = QStringLiteral("Executable"); QString ExecuteScriptPlugin::executeOnRemoteHostEntry = QStringLiteral("Execute on Remote Host"); QString ExecuteScriptPlugin::runCurrentFileEntry = QStringLiteral("Run current file"); QString ExecuteScriptPlugin::remoteHostEntry = QStringLiteral("Remote Host"); QString ExecuteScriptPlugin::argumentsEntry = QStringLiteral("Arguments"); QString ExecuteScriptPlugin::isExecutableEntry = QStringLiteral("isExecutable"); QString ExecuteScriptPlugin::environmentGroupEntry = QStringLiteral("EnvironmentGroup"); //QString ExecuteScriptPlugin::useTerminalEntry = "Use External Terminal"; QString ExecuteScriptPlugin::userIdToRunEntry = QStringLiteral("User Id to Run"); QString ExecuteScriptPlugin::projectTargetEntry = QStringLiteral("Project Target"); QString ExecuteScriptPlugin::outputFilteringEntry = QStringLiteral("Output Filtering Mode"); using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_EXECUTESCRIPT, "kdevplatform.plugins.executescript") K_PLUGIN_FACTORY_WITH_JSON(KDevExecuteFactory, "kdevexecutescript.json", registerPlugin();) ExecuteScriptPlugin::ExecuteScriptPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevexecutescript"), parent) { KDEV_USE_EXTENSION_INTERFACE( IExecuteScriptPlugin ) m_configType = new ScriptAppConfigType(); m_configType->addLauncher( new ScriptAppLauncher( this ) ); qCDebug(PLUGIN_EXECUTESCRIPT) << "adding script launch config"; core()->runController()->addConfigurationType( m_configType ); } ExecuteScriptPlugin::~ExecuteScriptPlugin() { } void ExecuteScriptPlugin::unload() { core()->runController()->removeConfigurationType( m_configType ); delete m_configType; - m_configType = 0; + m_configType = nullptr; } QUrl ExecuteScriptPlugin::script( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { QUrl script; if( !cfg ) { return script; } KConfigGroup grp = cfg->config(); script = grp.readEntry( ExecuteScriptPlugin::executableEntry, QUrl() ); if( !script.isLocalFile() || script.isEmpty() ) { err_ = i18n("No valid executable specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid script set"; } else { KShell::Errors err; if( KShell::splitArgs( script.toLocalFile(), KShell::TildeExpand | KShell::AbortOnMeta, &err ).isEmpty() || err != KShell::NoError ) { script = QUrl(); if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the script " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "script for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "script has meta characters"; } } return script; } QString ExecuteScriptPlugin::remoteHost(ILaunchConfiguration* cfg, QString& err) const { if (!cfg) return QString(); KConfigGroup grp = cfg->config(); if(grp.readEntry(ExecuteScriptPlugin::executeOnRemoteHostEntry, false)) { QString host = grp.readEntry(ExecuteScriptPlugin::remoteHostEntry, ""); if (host.isEmpty()) { err = i18n("No remote host set for launch configuration '%1'. " "Aborting start.", cfg->name() ); qWarning() << "Launch Configuration:" << cfg->name() << "no remote host set"; } return host; } return QString(); } QStringList ExecuteScriptPlugin::arguments( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { if( !cfg ) { return QStringList(); } KShell::Errors err; QStringList args = KShell::splitArgs( cfg->config().readEntry( ExecuteScriptPlugin::argumentsEntry, "" ), KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err != KShell::NoError ) { if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the arguments for " "the launch configuration '%1'. Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "arguments for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } args = QStringList(); qWarning() << "Launch Configuration:" << cfg->name() << "arguments have meta characters"; } return args; } QString ExecuteScriptPlugin::environmentGroup( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QString(); } return cfg->config().readEntry( ExecuteScriptPlugin::environmentGroupEntry, "" ); } int ExecuteScriptPlugin::outputFilterModeId( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return 0; } return cfg->config().readEntry( ExecuteScriptPlugin::outputFilteringEntry, 0 ); } bool ExecuteScriptPlugin::runCurrentFile(ILaunchConfiguration* cfg) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecuteScriptPlugin::runCurrentFileEntry, true ); } QString ExecuteScriptPlugin::interpreter( KDevelop::ILaunchConfiguration* cfg, QString& err ) const { QString interpreter; if( !cfg ) { return interpreter; } KConfigGroup grp = cfg->config(); interpreter = grp.readEntry( ExecuteScriptPlugin::interpreterEntry, QString() ); if( interpreter.isEmpty() ) { err = i18n("No valid interpreter specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid interpreter set"; } else { KShell::Errors err_; if( KShell::splitArgs( interpreter, KShell::TildeExpand | KShell::AbortOnMeta, &err_ ).isEmpty() || err_ != KShell::NoError ) { interpreter.clear(); if( err_ == KShell::BadQuoting ) { err = i18n("There is a quoting error in the interpreter " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err = i18n("A shell meta character was included in the " "interpreter for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "interpreter has meta characters"; } } return interpreter; } /* bool ExecuteScriptPlugin::useTerminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecuteScriptPlugin::useTerminalEntry, false ); } */ QUrl ExecuteScriptPlugin::workingDirectory( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QUrl(); } return cfg->config().readEntry( ExecuteScriptPlugin::workingDirEntry, QUrl() ); } QString ExecuteScriptPlugin::scriptAppConfigTypeId() const { return _scriptAppConfigTypeId; } #include "executescriptplugin.moc" diff --git a/plugins/executescript/scriptappconfig.cpp b/plugins/executescript/scriptappconfig.cpp index 94f9af1f87..87e2aeaf4d 100644 --- a/plugins/executescript/scriptappconfig.cpp +++ b/plugins/executescript/scriptappconfig.cpp @@ -1,254 +1,254 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams 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 "scriptappconfig.h" #include #include #include #include #include #include #include #include #include #include #include "scriptappjob.h" #include #include #include #include #include #include #include #include #include "executescriptplugin.h" #include #include #include using namespace KDevelop; static const QString interpreterForUrl(const QUrl& url) { auto mimetype = QMimeDatabase().mimeTypeForUrl(url); static QHash knownMimetypes; if ( knownMimetypes.isEmpty() ) { knownMimetypes[QStringLiteral("text/x-python")] = QStringLiteral("python"); knownMimetypes[QStringLiteral("application/x-php")] = QStringLiteral("php"); knownMimetypes[QStringLiteral("application/x-ruby")] = QStringLiteral("ruby"); knownMimetypes[QStringLiteral("application/x-shellscript")] = QStringLiteral("bash"); knownMimetypes[QStringLiteral("application/x-perl")] = QStringLiteral("perl -e"); } const QString& interp = knownMimetypes.value(mimetype.name()); return interp; } QIcon ScriptAppConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("system-run")); } void ScriptAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project ) { bool b = blockSignals( true ); if( project ) { executablePath->setStartDir( project->path().toUrl() ); } auto doc = KDevelop::ICore::self()->documentController()->activeDocument(); interpreter->lineEdit()->setText( cfg.readEntry( ExecuteScriptPlugin::interpreterEntry, doc ? interpreterForUrl(doc->url()) : QString() ) ); executablePath->setUrl( QUrl::fromLocalFile(cfg.readEntry( ExecuteScriptPlugin::executableEntry, QString() )) ); remoteHostCheckbox->setChecked( cfg.readEntry( ExecuteScriptPlugin::executeOnRemoteHostEntry, false ) ); remoteHost->setText( cfg.readEntry( ExecuteScriptPlugin::remoteHostEntry, "" ) ); bool runCurrent = cfg.readEntry( ExecuteScriptPlugin::runCurrentFileEntry, true ); if ( runCurrent ) { runCurrentFile->setChecked( true ); } else { runFixedFile->setChecked( true ); } arguments->setText( cfg.readEntry( ExecuteScriptPlugin::argumentsEntry, "" ) ); workingDirectory->setUrl( cfg.readEntry( ExecuteScriptPlugin::workingDirEntry, QUrl() ) ); environment->setCurrentProfile( cfg.readEntry( ExecuteScriptPlugin::environmentGroupEntry, QString() ) ); outputFilteringMode->setCurrentIndex( cfg.readEntry( ExecuteScriptPlugin::outputFilteringEntry, 2u )); //runInTerminal->setChecked( cfg.readEntry( ExecuteScriptPlugin::useTerminalEntry, false ) ); blockSignals( b ); } ScriptAppConfigPage::ScriptAppConfigPage( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); interpreter->lineEdit()->setPlaceholderText(i18n("Type or select an interpreter")); //Set workingdirectory widget to ask for directories rather than files workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); //connect signals to changed signal connect( interpreter->lineEdit(), &QLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( executablePath->lineEdit(), &KLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( executablePath, &KUrlRequester::urlSelected, this, &ScriptAppConfigPage::changed ); connect( arguments, &QLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( workingDirectory, &KUrlRequester::urlSelected, this, &ScriptAppConfigPage::changed ); connect( workingDirectory->lineEdit(), &KLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &ScriptAppConfigPage::changed ); //connect( runInTerminal, SIGNAL(toggled(bool)), SIGNAL(changed()) ); } void ScriptAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry( ExecuteScriptPlugin::interpreterEntry, interpreter->lineEdit()->text() ); cfg.writeEntry( ExecuteScriptPlugin::executableEntry, executablePath->url() ); cfg.writeEntry( ExecuteScriptPlugin::executeOnRemoteHostEntry, remoteHostCheckbox->isChecked() ); cfg.writeEntry( ExecuteScriptPlugin::remoteHostEntry, remoteHost->text() ); cfg.writeEntry( ExecuteScriptPlugin::runCurrentFileEntry, runCurrentFile->isChecked() ); cfg.writeEntry( ExecuteScriptPlugin::argumentsEntry, arguments->text() ); cfg.writeEntry( ExecuteScriptPlugin::workingDirEntry, workingDirectory->url() ); cfg.writeEntry( ExecuteScriptPlugin::environmentGroupEntry, environment->currentProfile() ); cfg.writeEntry( ExecuteScriptPlugin::outputFilteringEntry, outputFilteringMode->currentIndex() ); //cfg.writeEntry( ExecuteScriptPlugin::useTerminalEntry, runInTerminal->isChecked() ); } QString ScriptAppConfigPage::title() const { return i18n("Configure Script Application"); } QList< KDevelop::LaunchConfigurationPageFactory* > ScriptAppLauncher::configPages() const { return QList(); } QString ScriptAppLauncher::description() const { return i18n("Executes Script Applications"); } QString ScriptAppLauncher::id() { return QStringLiteral("scriptAppLauncher"); } QString ScriptAppLauncher::name() const { return i18n("Script Application"); } ScriptAppLauncher::ScriptAppLauncher(ExecuteScriptPlugin* plugin) : m_plugin( plugin ) { } KJob* ScriptAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { - return 0; + return nullptr; } if( launchMode == QLatin1String("execute") ) { return new ScriptAppJob( m_plugin, cfg); } qWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); - return 0; + return nullptr; } QStringList ScriptAppLauncher::supportedModes() const { return QStringList() << QStringLiteral("execute"); } KDevelop::LaunchConfigurationPage* ScriptAppPageFactory::createWidget(QWidget* parent) { return new ScriptAppConfigPage( parent ); } ScriptAppPageFactory::ScriptAppPageFactory() { } ScriptAppConfigType::ScriptAppConfigType() { factoryList.append( new ScriptAppPageFactory() ); } ScriptAppConfigType::~ScriptAppConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString ScriptAppConfigType::name() const { return i18n("Script Application"); } QList ScriptAppConfigType::configPages() const { return factoryList; } QString ScriptAppConfigType::id() const { return ExecuteScriptPlugin::_scriptAppConfigTypeId; } QIcon ScriptAppConfigType::icon() const { return QIcon::fromTheme(QStringLiteral("preferences-plugin-script")); } bool ScriptAppConfigType::canLaunch(const QUrl& file) const { return ! interpreterForUrl(file).isEmpty(); } bool ScriptAppConfigType::canLaunch(KDevelop::ProjectBaseItem* item) const { return ! interpreterForUrl(item->path().toUrl()).isEmpty(); } void ScriptAppConfigType::configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const { config.writeEntry(ExecuteScriptPlugin::executableEntry, item->path().toUrl()); config.writeEntry(ExecuteScriptPlugin::interpreterEntry, interpreterForUrl(item->path().toUrl())); config.writeEntry(ExecuteScriptPlugin::outputFilteringEntry, 2u); config.writeEntry(ExecuteScriptPlugin::runCurrentFileEntry, false); config.sync(); } void ScriptAppConfigType::configureLaunchFromCmdLineArguments(KConfigGroup cfg, const QStringList &args) const { QStringList a(args); cfg.writeEntry( ExecuteScriptPlugin::interpreterEntry, a.takeFirst() ); cfg.writeEntry( ExecuteScriptPlugin::executableEntry, a.takeFirst() ); cfg.writeEntry( ExecuteScriptPlugin::argumentsEntry, KShell::joinArgs(a) ); cfg.writeEntry( ExecuteScriptPlugin::runCurrentFileEntry, false ); cfg.sync(); } diff --git a/plugins/externalscript/externalscriptjob.cpp b/plugins/externalscript/externalscriptjob.cpp index 5b48c2bfbd..5f49cfe6d4 100644 --- a/plugins/externalscript/externalscriptjob.cpp +++ b/plugins/externalscript/externalscriptjob.cpp @@ -1,399 +1,399 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "externalscriptjob.h" #include "externalscriptitem.h" #include "externalscriptdebug.h" #include "externalscriptplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; ExternalScriptJob::ExternalScriptJob( ExternalScriptItem* item, const QUrl& url, ExternalScriptPlugin* parent ) : KDevelop::OutputJob( parent ), - m_proc( 0 ), m_lineMaker( 0 ), + m_proc( nullptr ), m_lineMaker( nullptr ), m_outputMode( item->outputMode() ), m_inputMode( item->inputMode() ), m_errorMode( item->errorMode() ), m_filterMode( item->filterMode() ), - m_document( 0 ), m_url( url ), m_selectionRange( KTextEditor::Range::invalid() ), + m_document( nullptr ), m_url( url ), m_selectionRange( KTextEditor::Range::invalid() ), m_showOutput( item->showOutput() ) { qCDebug(PLUGIN_EXTERNALSCRIPT) << "creating external script job"; setCapabilities( Killable ); setStandardToolView( KDevelop::IOutputView::RunView ); setBehaviours( KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); KDevelop::OutputModel* model = new KDevelop::OutputModel; model->setFilteringStrategy(static_cast(m_filterMode)); setModel( model ); setDelegate( new KDevelop::OutputDelegate ); // also merge when error mode "equals" output mode if ( (m_outputMode == ExternalScriptItem::OutputInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorInsertAtCursor) || (m_outputMode == ExternalScriptItem::OutputReplaceDocument && m_errorMode == ExternalScriptItem::ErrorReplaceDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrDocument && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor) || // also these two otherwise they clash... (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrDocument && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor) ) { m_errorMode = ExternalScriptItem::ErrorMergeOutput; } KTextEditor::View* view = KDevelop::ICore::self()->documentController()->activeTextDocumentView(); if ( m_outputMode != ExternalScriptItem::OutputNone || m_inputMode != ExternalScriptItem::InputNone || m_errorMode != ExternalScriptItem::ErrorNone ) { if ( !view ) { KMessageBox::error( QApplication::activeWindow(), i18n( "Cannot run script '%1' since it tries to access " "the editor contents but no document is open.", item->text() ), i18n( "No Document Open" ) ); return; } m_document = view->document(); connect(m_document, &KTextEditor::Document::aboutToClose, this, [&] { kill(); }); m_selectionRange = view->selectionRange(); m_cursorPosition = view->cursorPosition(); } if ( item->saveMode() == ExternalScriptItem::SaveCurrentDocument && view ) { view->document()->save(); } else if ( item->saveMode() == ExternalScriptItem::SaveAllDocuments ) { foreach ( KDevelop::IDocument* doc, KDevelop::ICore::self()->documentController()->openDocuments() ) { doc->save(); } } QString command = item->command(); QString workingDir = item->workingDirectory(); if(item->performParameterReplacement()) command.replace( QLatin1String("%i"), QString::number( QCoreApplication::applicationPid() ) ); if ( !m_url.isEmpty() ) { const QUrl url = m_url; - KDevelop::ProjectFolderItem* folder = 0; + KDevelop::ProjectFolderItem* folder = nullptr; if ( KDevelop::ICore::self()->projectController()->findProjectForUrl( url ) ) { QList folders = KDevelop::ICore::self()->projectController()->findProjectForUrl(url)->foldersForPath(KDevelop::IndexedString(url)); if ( !folders.isEmpty() ) { folder = folders.first(); } } if ( folder ) { if ( folder->path().isLocalFile() && workingDir.isEmpty() ) { ///TODO: make configurable, use fallback to project dir workingDir = folder->path().toLocalFile(); } ///TODO: make those placeholders escapeable if(item->performParameterReplacement()) { command.replace( QLatin1String("%d"), KShell::quoteArg( m_url.toString(QUrl::PreferLocalFile) ) ); if ( KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl( m_url ) ) { command.replace( QLatin1String("%p"), project->path().pathOrUrl() ); } } } else { if ( m_url.isLocalFile() && workingDir.isEmpty() ) { ///TODO: make configurable, use fallback to project dir workingDir = view->document()->url().adjusted(QUrl::RemoveFilename).toLocalFile(); } ///TODO: make those placeholders escapeable if(item->performParameterReplacement()) { command.replace( QLatin1String("%u"), KShell::quoteArg( m_url.toString() ) ); ///TODO: does that work with remote files? QFileInfo info( m_url.toString(QUrl::PreferLocalFile) ); command.replace( QLatin1String("%f"), KShell::quoteArg( info.filePath() ) ); command.replace( QLatin1String("%b"), KShell::quoteArg( info.baseName() ) ); command.replace( QLatin1String("%n"), KShell::quoteArg( info.fileName() ) ); command.replace( QLatin1String("%d"), KShell::quoteArg( info.path() ) ); if ( view->document() && view->selection() ) { command.replace( QLatin1String("%s"), KShell::quoteArg( view->selectionText() ) ); } if ( KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl( m_url ) ) { command.replace( QLatin1String("%p"), project->path().pathOrUrl() ); } } } } m_proc = new KProcess( this ); if ( !workingDir.isEmpty() ) { m_proc->setWorkingDirectory( workingDir ); } m_lineMaker = new ProcessLineMaker( m_proc, this ); connect( m_lineMaker, &ProcessLineMaker::receivedStdoutLines, model, &OutputModel::appendLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &ExternalScriptJob::receivedStdoutLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStderrLines, model, &OutputModel::appendLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &ExternalScriptJob::receivedStderrLines ); connect( m_proc, static_cast(&KProcess::error), this, &ExternalScriptJob::processError ); connect( m_proc, static_cast(&KProcess::finished), this, &ExternalScriptJob::processFinished ); // Now setup the process parameters qCDebug(PLUGIN_EXTERNALSCRIPT) << "setting command:" << command; if ( m_errorMode == ExternalScriptItem::ErrorMergeOutput ) { m_proc->setOutputChannelMode( KProcess::MergedChannels ); } else { m_proc->setOutputChannelMode( KProcess::SeparateChannels ); } m_proc->setShellCommand( command ); setObjectName( command ); } void ExternalScriptJob::start() { qCDebug(PLUGIN_EXTERNALSCRIPT) << "launching?" << m_proc; if ( m_proc ) { if ( m_showOutput ) { startOutput(); } appendLine( i18n( "Running external script: %1", m_proc->program().join( QStringLiteral( " " ) ) ) ); m_proc->start(); if ( m_inputMode != ExternalScriptItem::InputNone ) { QString inputText; switch ( m_inputMode ) { case ExternalScriptItem::InputNone: // do nothing; break; case ExternalScriptItem::InputSelectionOrNone: if ( m_selectionRange.isValid() ) { inputText = m_document->text(m_selectionRange); } // else nothing break; case ExternalScriptItem::InputSelectionOrDocument: if ( m_selectionRange.isValid() ) { inputText = m_document->text(m_selectionRange); } else { inputText = m_document->text(); } break; case ExternalScriptItem::InputDocument: inputText = m_document->text(); break; } ///TODO: what to do with the encoding here? /// maybe ask Christoph for what kate returns... m_proc->write( inputText.toUtf8() ); m_proc->closeWriteChannel(); } } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool ExternalScriptJob::doKill() { if ( m_proc ) { m_proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void ExternalScriptJob::processFinished( int exitCode , QProcess::ExitStatus status ) { m_lineMaker->flushBuffers(); if ( exitCode == 0 && status == QProcess::NormalExit ) { if ( m_outputMode != ExternalScriptItem::OutputNone ) { if ( !m_stdout.isEmpty() ) { QString output = m_stdout.join( QStringLiteral("\n") ); switch ( m_outputMode ) { case ExternalScriptItem::OutputNone: // do nothing; break; case ExternalScriptItem::OutputCreateNewFile: KDevelop::ICore::self()->documentController()->openDocumentFromText( output ); break; case ExternalScriptItem::OutputInsertAtCursor: m_document->insertText( m_cursorPosition, output ); break; case ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->insertText( m_cursorPosition, output ); } break; case ExternalScriptItem::OutputReplaceSelectionOrDocument: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->setText( output ); } break; case ExternalScriptItem::OutputReplaceDocument: m_document->setText( output ); break; } } } if ( m_errorMode != ExternalScriptItem::ErrorNone && m_errorMode != ExternalScriptItem::ErrorMergeOutput ) { QString output = m_stderr.join( QStringLiteral("\n") ); if ( !output.isEmpty() ) { switch ( m_errorMode ) { case ExternalScriptItem::ErrorNone: case ExternalScriptItem::ErrorMergeOutput: // do nothing; break; case ExternalScriptItem::ErrorCreateNewFile: KDevelop::ICore::self()->documentController()->openDocumentFromText( output ); break; case ExternalScriptItem::ErrorInsertAtCursor: m_document->insertText( m_cursorPosition, output ); break; case ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->insertText( m_cursorPosition, output ); } break; case ExternalScriptItem::ErrorReplaceSelectionOrDocument: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->setText( output ); } break; case ExternalScriptItem::ErrorReplaceDocument: m_document->setText( output ); break; } } } appendLine( i18n( "*** Exited normally ***" ) ); } else { if ( status == QProcess::NormalExit ) appendLine( i18n( "*** Exited with return code: %1 ***", QString::number( exitCode ) ) ); else if ( error() == KJob::KilledJobError ) appendLine( i18n( "*** Process aborted ***" ) ); else appendLine( i18n( "*** Crashed with return code: %1 ***", QString::number( exitCode ) ) ); } qCDebug(PLUGIN_EXTERNALSCRIPT) << "Process done"; emitResult(); } void ExternalScriptJob::processError( QProcess::ProcessError error ) { if ( error == QProcess::FailedToStart ) { setError( -1 ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " "path is specified correctly ***", m_proc->program().join( QLatin1Char(' ') ) ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXTERNALSCRIPT) << "Process error"; } void ExternalScriptJob::appendLine( const QString& l ) { if ( KDevelop::OutputModel* m = model() ) { m->appendLine( l ); } } KDevelop::OutputModel* ExternalScriptJob::model() { return dynamic_cast( OutputJob::model() ); } void ExternalScriptJob::receivedStderrLines(const QStringList& lines) { m_stderr += lines; } void ExternalScriptJob::receivedStdoutLines(const QStringList& lines) { m_stdout += lines; } // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/externalscript/externalscriptplugin.cpp b/plugins/externalscript/externalscriptplugin.cpp index c3e64c9050..b72ea36740 100644 --- a/plugins/externalscript/externalscriptplugin.cpp +++ b/plugins/externalscript/externalscriptplugin.cpp @@ -1,369 +1,369 @@ /* This plugin is part of KDevelop. Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "externalscriptplugin.h" #include "externalscriptview.h" #include "externalscriptitem.h" #include "externalscriptjob.h" #include "externalscriptdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(ExternalScriptFactory, "kdevexternalscript.json", registerPlugin();) class ExternalScriptViewFactory: public KDevelop::IToolViewFactory { public: ExternalScriptViewFactory( ExternalScriptPlugin *plugin ): m_plugin( plugin ) {} - QWidget* create( QWidget *parent = 0 ) override { + QWidget* create( QWidget *parent = nullptr ) override { return new ExternalScriptView( m_plugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ExternalScriptView"); } private: ExternalScriptPlugin *m_plugin; }; -ExternalScriptPlugin* ExternalScriptPlugin::m_self = 0; +ExternalScriptPlugin* ExternalScriptPlugin::m_self = nullptr; ExternalScriptPlugin::ExternalScriptPlugin( QObject* parent, const QVariantList& /*args*/ ) : IPlugin( QStringLiteral("kdevexternalscript"), parent ), m_model( new QStandardItemModel( this ) ), m_factory( new ExternalScriptViewFactory( this ) ) { Q_ASSERT( !m_self ); m_self = this; QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ExternalScriptPlugin"), this, QDBusConnection::ExportScriptableSlots ); setXMLFile( QStringLiteral("kdevexternalscript.rc") ); //BEGIN load config KConfigGroup config = getConfig(); foreach( const QString& group, config.groupList() ) { KConfigGroup script = config.group( group ); if ( script.hasKey( "name" ) && script.hasKey( "command" ) ) { ExternalScriptItem* item = new ExternalScriptItem; item->setText( script.readEntry( "name" ) ); item->setCommand( script.readEntry( "command" )); item->setInputMode( static_cast( script.readEntry( "inputMode", 0u ) ) ); item->setOutputMode( static_cast( script.readEntry( "outputMode", 0u ) ) ); item->setErrorMode( static_cast( script.readEntry( "errorMode", 0u ) ) ); item->setSaveMode( static_cast( script.readEntry( "saveMode", 0u ) ) ); item->setFilterMode( script.readEntry( "filterMode", 0u )); item->action()->setShortcut( QKeySequence( script.readEntry( "shortcuts" ) ) ); item->setShowOutput( script.readEntry( "showOutput", true ) ); m_model->appendRow( item ); } } //END load config core()->uiController()->addToolView( i18n( "External Scripts" ), m_factory ); connect( m_model, &QStandardItemModel::rowsRemoved, this, &ExternalScriptPlugin::rowsRemoved ); connect( m_model, &QStandardItemModel::rowsInserted, this, &ExternalScriptPlugin::rowsInserted ); const bool firstUse = config.readEntry( "firstUse", true ); if ( firstUse ) { // some example scripts ExternalScriptItem* item = new ExternalScriptItem; item->setText( i18n("Quick Compile") ); item->setCommand( QStringLiteral("g++ -o %b %f && ./%b") ); m_model->appendRow( item ); item = new ExternalScriptItem; item->setText( i18n("Google Selection") ); item->setCommand( QStringLiteral("xdg-open \"http://www.google.de/search?q=%s\"") ); item->setShowOutput( false ); m_model->appendRow( item ); item = new ExternalScriptItem; item->setText( i18n("Sort Selection") ); item->setCommand( QStringLiteral("sort") ); item->setInputMode( ExternalScriptItem::InputSelectionOrDocument ); item->setOutputMode( ExternalScriptItem::OutputReplaceSelectionOrDocument ); item->setShowOutput( false ); m_model->appendRow( item ); config.writeEntry( "firstUse", false ); config.sync(); } } ExternalScriptPlugin* ExternalScriptPlugin::self() { return m_self; } ExternalScriptPlugin::~ExternalScriptPlugin() { - m_self = 0; + m_self = nullptr; } KDevelop::ContextMenuExtension ExternalScriptPlugin::contextMenuExtension( KDevelop::Context* context ) { m_urls.clear(); int folderCount = 0; if ( context->type() == KDevelop::Context::FileContext ) { KDevelop::FileContext* filectx = dynamic_cast( context ); m_urls = filectx->urls(); } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* projctx = dynamic_cast( context ); foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { if ( item->file() ) { m_urls << item->file()->path().toUrl(); } else if ( item->folder() ) { m_urls << item->folder()->path().toUrl(); folderCount++; } } } else if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast(context); m_urls << econtext->url(); } if ( !m_urls.isEmpty() ) { KDevelop::ContextMenuExtension ext; QMenu* menu = new QMenu(); menu->setTitle( i18n("External Scripts") ); for ( int row = 0; row < m_model->rowCount(); ++row ) { ExternalScriptItem* item = dynamic_cast( m_model->item( row ) ); Q_ASSERT( item ); if (context->type() != KDevelop::Context::EditorContext) { // filter scripts that depend on an opened document // if the context menu was not requested inside the editor if (item->performParameterReplacement() && item->command().contains(QStringLiteral("%s"))) { continue; } else if (item->inputMode() == ExternalScriptItem::InputSelectionOrNone) { continue; } } if ( folderCount == m_urls.count() ) { // when only folders filter items that don't have %d parameter (or another parameter) if (item->performParameterReplacement() && (!item->command().contains(QStringLiteral("%d")) || item->command().contains(QStringLiteral("%s")) || item->command().contains(QStringLiteral("%u")) || item->command().contains(QStringLiteral("%f")) || item->command().contains(QStringLiteral("%b")) || item->command().contains(QStringLiteral("%n")) ) ) { continue; } } QAction* scriptAction = new QAction( item->text(), this ); scriptAction->setData( QVariant::fromValue( item )); connect( scriptAction, &QAction::triggered, this, &ExternalScriptPlugin::executeScriptFromContextMenu ); menu->addAction( scriptAction ); } if (!menu->actions().isEmpty()) ext.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, menu->menuAction() ); return ext; } return KDevelop::IPlugin::contextMenuExtension( context ); } void ExternalScriptPlugin::unload() { core()->uiController()->removeToolView( m_factory ); KDevelop::IPlugin::unload(); } KConfigGroup ExternalScriptPlugin::getConfig() const { return KSharedConfig::openConfig()->group("External Scripts"); } QStandardItemModel* ExternalScriptPlugin::model() const { return m_model; } void ExternalScriptPlugin::execute( ExternalScriptItem* item, const QUrl& url ) const { ExternalScriptJob* job = new ExternalScriptJob( item, url, const_cast(this) ); KDevelop::ICore::self()->runController()->registerJob( job ); } void ExternalScriptPlugin::execute(ExternalScriptItem* item) const { auto document = KDevelop::ICore::self()->documentController()->activeDocument(); execute( item, document ? document->url() : QUrl() ); } bool ExternalScriptPlugin::executeCommand ( QString command, QString workingDirectory ) const { // We extend ExternalScriptJob so that it deletes the temporarily created item on destruction class ExternalScriptJobOwningItem : public ExternalScriptJob { public: ExternalScriptJobOwningItem( ExternalScriptItem* item, const QUrl &url, ExternalScriptPlugin* parent ) : ExternalScriptJob(item, url, parent), m_item(item) { } ~ExternalScriptJobOwningItem() override { delete m_item; } private: ExternalScriptItem* m_item; }; ExternalScriptItem* item = new ExternalScriptItem; item->setCommand(command); item->setWorkingDirectory(workingDirectory); item->setPerformParameterReplacement(false); qCDebug(PLUGIN_EXTERNALSCRIPT) << "executing command " << command << " in dir " << workingDirectory << " as external script"; ExternalScriptJobOwningItem* job = new ExternalScriptJobOwningItem( item, QUrl(), const_cast(this) ); // When a command is executed, for example through the terminal, we don't want the command output to be risen job->setVerbosity(KDevelop::OutputJob::Silent); KDevelop::ICore::self()->runController()->registerJob( job ); return true; } QString ExternalScriptPlugin::executeCommandSync ( QString command, QString workingDirectory ) const { qCDebug(PLUGIN_EXTERNALSCRIPT) << "executing command " << command << " in working-dir " << workingDirectory; KProcess process; process.setWorkingDirectory( workingDirectory ); process.setShellCommand( command ); process.setOutputChannelMode( KProcess::OnlyStdoutChannel ); process.execute(); return QString::fromLocal8Bit(process.readAll()); } void ExternalScriptPlugin::executeScriptFromActionData() const { QAction* action = dynamic_cast( sender() ); Q_ASSERT( action ); ExternalScriptItem* item = action->data().value(); Q_ASSERT( item ); execute( item ); } void ExternalScriptPlugin::executeScriptFromContextMenu() const { QAction* action = dynamic_cast( sender() ); Q_ASSERT( action ); ExternalScriptItem* item = action->data().value(); Q_ASSERT( item ); foreach( const QUrl& url, m_urls) { KDevelop::ICore::self()->documentController()->openDocument( url ); execute( item, url ); } } void ExternalScriptPlugin::rowsInserted( const QModelIndex& /*parent*/, int start, int end ) { for ( int i = start; i <= end; ++i ) { saveItemForRow( i ); } } void ExternalScriptPlugin::rowsRemoved( const QModelIndex& /*parent*/, int start, int end ) { KConfigGroup config = getConfig(); for ( int i = start; i <= end; ++i ) { KConfigGroup child = config.group( QStringLiteral("script %1").arg(i) ); qCDebug(PLUGIN_EXTERNALSCRIPT) << "removing config group:" << child.name(); child.deleteGroup(); } config.sync(); } void ExternalScriptPlugin::saveItem( const ExternalScriptItem* item ) { const QModelIndex index = m_model->indexFromItem( item ); Q_ASSERT( index.isValid() ); saveItemForRow( index.row() ); } void ExternalScriptPlugin::saveItemForRow( int row ) { const QModelIndex idx = m_model->index( row, 0 ); Q_ASSERT( idx.isValid() ); ExternalScriptItem* item = dynamic_cast( m_model->item( row ) ); Q_ASSERT( item ); qCDebug(PLUGIN_EXTERNALSCRIPT) << "save extern script:" << item << idx; KConfigGroup config = getConfig().group( QStringLiteral("script %1").arg( row ) ); config.writeEntry( "name", item->text() ); config.writeEntry( "command", item->command() ); config.writeEntry( "inputMode", (uint) item->inputMode() ); config.writeEntry( "outputMode", (uint) item->outputMode() ); config.writeEntry( "errorMode", (uint) item->errorMode() ); config.writeEntry( "saveMode", (uint) item->saveMode() ); config.writeEntry( "shortcuts", item->action()->shortcut().toString() ); config.writeEntry( "showOutput", item->showOutput() ); config.writeEntry( "filterMode", item->filterMode()); config.sync(); } #include "externalscriptplugin.moc" // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/externalscript/externalscriptview.cpp b/plugins/externalscript/externalscriptview.cpp index 5e222b2a6a..f7e4c55fa9 100644 --- a/plugins/externalscript/externalscriptview.cpp +++ b/plugins/externalscript/externalscriptview.cpp @@ -1,178 +1,178 @@ /* This plugin is part of KDevelop. Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "externalscriptview.h" #include "externalscriptplugin.h" #include "externalscriptitem.h" #include "editexternalscript.h" #include #include #include #include #include #include ExternalScriptView::ExternalScriptView( ExternalScriptPlugin* plugin, QWidget* parent ) : QWidget( parent ), m_plugin( plugin ) { Ui::ExternalScriptViewBase::setupUi( this ); setWindowTitle( i18n( "External Scripts" ) ); setWindowIcon( QIcon::fromTheme(QStringLiteral("dialog-scripts"), windowIcon()) ); m_model = new QSortFilterProxyModel( this ); m_model->setSourceModel( m_plugin->model() ); m_model->setDynamicSortFilter( true ); m_model->sort( 0 ); connect( filterText, &QLineEdit::textEdited, m_model, &QSortFilterProxyModel::setFilterWildcard ); scriptTree->setModel( m_model ); scriptTree->setContextMenuPolicy( Qt::CustomContextMenu ); scriptTree->viewport()->installEventFilter( this ); scriptTree->header()->hide(); connect(scriptTree, &QTreeView::customContextMenuRequested, this, &ExternalScriptView::contextMenu); m_addScriptAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add External Script"), this); connect(m_addScriptAction, &QAction::triggered, this, &ExternalScriptView::addScript); addAction(m_addScriptAction); m_editScriptAction = new QAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit External Script"), this); connect(m_editScriptAction, &QAction::triggered, this, &ExternalScriptView::editScript); addAction(m_editScriptAction); m_removeScriptAction = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Remove External Script"), this); connect(m_removeScriptAction, &QAction::triggered, this, &ExternalScriptView::removeScript); addAction(m_removeScriptAction); connect(scriptTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ExternalScriptView::validateActions); validateActions(); } ExternalScriptView::~ExternalScriptView() { } ExternalScriptItem* ExternalScriptView::currentItem() const { return itemForIndex( scriptTree->currentIndex() ); } ExternalScriptItem* ExternalScriptView::itemForIndex( const QModelIndex& index ) const { if ( !index.isValid() ) { - return 0; + return nullptr; } const QModelIndex mappedIndex = m_model->mapToSource( index ); return static_cast( m_plugin->model()->itemFromIndex( mappedIndex ) ); } void ExternalScriptView::validateActions() { bool itemSelected = currentItem(); m_removeScriptAction->setEnabled( itemSelected ); m_editScriptAction->setEnabled( itemSelected ); } void ExternalScriptView::contextMenu( const QPoint& pos ) { QMenu menu; menu.addActions( actions() ); menu.exec( scriptTree->mapToGlobal( pos ) ); } bool ExternalScriptView::eventFilter( QObject* obj, QEvent* e ) { // no, listening to activated() is not enough since that would also trigger the edit mode which we _dont_ want here // users may still rename stuff via select + F2 though if ( obj == scriptTree->viewport() ) { // const bool singleClick = KGlobalSettings::singleClick(); const bool singleClick = true; //FIXME: enable singleClick for the sake of porting, should find a proper way if ( ( !singleClick && e->type() == QEvent::MouseButtonDblClick ) || ( singleClick && e->type() == QEvent::MouseButtonRelease ) ) { QMouseEvent* mouseEvent = dynamic_cast(e); Q_ASSERT( mouseEvent ); ExternalScriptItem* item = itemForIndex( scriptTree->indexAt( mouseEvent->pos() ) ); if ( item ) { m_plugin->execute( item ); e->accept(); return true; } } } return QObject::eventFilter( obj, e ); } void ExternalScriptView::addScript() { ExternalScriptItem* item = new ExternalScriptItem; EditExternalScript dlg( item, this ); int ret = dlg.exec(); if ( ret == QDialog::Accepted) { m_plugin->model()->appendRow( item ); } else { delete item; } } void ExternalScriptView::removeScript() { ExternalScriptItem* item = currentItem(); if ( !item ) { return; } int ret = KMessageBox::questionYesNo( this, i18n("

Do you really want to remove the external script configuration for %1?

" "

Note: The script itself will not be removed.

", item->text()), i18n("Confirm External Script Removal") ); if ( ret == KMessageBox::Yes ) { m_plugin->model()->removeRow( m_plugin->model()->indexFromItem( item ).row() ); } } void ExternalScriptView::editScript() { ExternalScriptItem* item = currentItem(); if ( !item ) { return; } EditExternalScript dlg( item, this ); int ret = dlg.exec(); if (ret == QDialog::Accepted) { item->save(); } } // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/filemanager/kdevfilemanagerplugin.cpp b/plugins/filemanager/kdevfilemanagerplugin.cpp index 617402edb3..4a4944b45e 100644 --- a/plugins/filemanager/kdevfilemanagerplugin.cpp +++ b/plugins/filemanager/kdevfilemanagerplugin.cpp @@ -1,94 +1,94 @@ /*************************************************************************** * Copyright 2006 Alexander Dymo * * Copyright 2007 Andreas Pakulat * * * * 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "kdevfilemanagerplugin.h" #include #include #include #include #include #include "filemanager.h" K_PLUGIN_FACTORY_WITH_JSON(KDevFileManagerFactory, "kdevfilemanager.json", registerPlugin();) class KDevFileManagerViewFactory: public KDevelop::IToolViewFactory{ public: KDevFileManagerViewFactory(KDevFileManagerPlugin *plugin): m_plugin(plugin) {} - QWidget* create(QWidget *parent = 0) override + QWidget* create(QWidget *parent = nullptr) override { Q_UNUSED(parent) return new FileManager(m_plugin,parent); } QList toolBarActions( QWidget* w ) const override { FileManager* m = qobject_cast(w); if( m ) return m->toolBarActions(); return KDevelop::IToolViewFactory::toolBarActions( w ); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.FileManagerView"); } bool allowMultiple() const override { return true; } private: KDevFileManagerPlugin *m_plugin; }; KDevFileManagerPlugin::KDevFileManagerPlugin(QObject *parent, const QVariantList &/*args*/) :KDevelop::IPlugin(QStringLiteral("kdevfilemanager"), parent) { setXMLFile(QStringLiteral("kdevfilemanager.rc")); QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } void KDevFileManagerPlugin::init() { m_factory = new KDevFileManagerViewFactory(this); core()->uiController()->addToolView(i18n("Filesystem"), m_factory); } KDevFileManagerPlugin::~KDevFileManagerPlugin() { } void KDevFileManagerPlugin::unload() { core()->uiController()->removeToolView(m_factory); } #include "kdevfilemanagerplugin.moc" diff --git a/plugins/filetemplates/classidentifierpage.cpp b/plugins/filetemplates/classidentifierpage.cpp index 7bc62b5910..1709930b59 100644 --- a/plugins/filetemplates/classidentifierpage.cpp +++ b/plugins/filetemplates/classidentifierpage.cpp @@ -1,76 +1,76 @@ /* This file is part of KDevelop Copyright 2008 Hamish Rodda 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 "classidentifierpage.h" #include "language/duchain/identifier.h" #include #include "ui_newclass.h" struct ClassIdentifierPagePrivate { ClassIdentifierPagePrivate() - : classid(0) + : classid(nullptr) { } Ui::NewClassDialog* classid; }; ClassIdentifierPage::ClassIdentifierPage(QWidget* parent) : QWidget(parent) , d(new ClassIdentifierPagePrivate()) { d->classid = new Ui::NewClassDialog; d->classid->setupUi(this); d->classid->keditlistwidget->setContentsMargins(0, 0, 0, 0); d->classid->keditlistwidget->layout()->setContentsMargins(0, 0, 0, 0); d->classid->keditlistwidget->lineEdit()->setPlaceholderText(i18n("Inheritance type and base class name")); d->classid->inheritanceLabel->setBuddy(d->classid->keditlistwidget->lineEdit()); connect(d->classid->identifierLineEdit, &QLineEdit::textChanged, this, &ClassIdentifierPage::checkIdentifier); emit isValid(false); } ClassIdentifierPage::~ClassIdentifierPage() { delete d->classid; delete d; } QString ClassIdentifierPage::identifier() const { return d->classid->identifierLineEdit->text(); } void ClassIdentifierPage::checkIdentifier() { emit isValid(!identifier().isEmpty()); } QStringList ClassIdentifierPage::inheritanceList() const { return d->classid->keditlistwidget->items(); } void ClassIdentifierPage::setInheritanceList (const QStringList& list) { d->classid->keditlistwidget->setItems(list); } diff --git a/plugins/filetemplates/filetemplatesplugin.cpp b/plugins/filetemplates/filetemplatesplugin.cpp index 8d41c81ab0..5ea8c88a43 100644 --- a/plugins/filetemplates/filetemplatesplugin.cpp +++ b/plugins/filetemplates/filetemplatesplugin.cpp @@ -1,289 +1,289 @@ #include "filetemplatesplugin.h" #include "templateclassassistant.h" #include "templatepreviewtoolview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(FileTemplatesFactory, "kdevfiletemplates.json", registerPlugin();) class TemplatePreviewFactory : public KDevelop::IToolViewFactory { public: TemplatePreviewFactory(FileTemplatesPlugin* plugin) : KDevelop::IToolViewFactory() , m_plugin(plugin) { } - QWidget* create(QWidget* parent = 0) override + QWidget* create(QWidget* parent = nullptr) override { return new TemplatePreviewToolView(m_plugin, parent); } QString id() const override { return QStringLiteral("org.kdevelop.TemplateFilePreview"); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } private: FileTemplatesPlugin* m_plugin; }; FileTemplatesPlugin::FileTemplatesPlugin(QObject* parent, const QVariantList& args) : IPlugin(QStringLiteral("kdevfiletemplates"), parent) - , m_model(0) + , m_model(nullptr) { Q_UNUSED(args); KDEV_USE_EXTENSION_INTERFACE(ITemplateProvider) setXMLFile(QStringLiteral("kdevfiletemplates.rc")); QAction* action = actionCollection()->addAction(QStringLiteral("new_from_template")); action->setText( i18n( "New From Template" ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("code-class") ) ); action->setWhatsThis( i18n( "Allows you to create new source code files, such as classes or unit tests, using templates." ) ); action->setStatusTip( i18n( "Create new files from a template" ) ); connect (action, &QAction::triggered, this, &FileTemplatesPlugin::createFromTemplate); m_toolView = new TemplatePreviewFactory(this); core()->uiController()->addToolView(i18n("Template Preview"), m_toolView); } FileTemplatesPlugin::~FileTemplatesPlugin() { } void FileTemplatesPlugin::unload() { core()->uiController()->removeToolView(m_toolView); } ContextMenuExtension FileTemplatesPlugin::contextMenuExtension (Context* context) { ContextMenuExtension ext; QUrl fileUrl; if (context->type() == Context::ProjectItemContext) { ProjectItemContext* projectContext = dynamic_cast(context); QList items = projectContext->items(); if (items.size() != 1) { return ext; } QUrl url; ProjectBaseItem* item = items.first(); if (item->folder()) { url = item->path().toUrl(); } else if (item->target()) { url = item->parent()->path().toUrl(); } if (url.isValid()) { QAction* action = new QAction(i18n("Create From Template"), this); action->setIcon(QIcon::fromTheme(QStringLiteral("code-class"))); action->setData(url); connect(action, &QAction::triggered, this, &FileTemplatesPlugin::createFromTemplate); ext.addAction(ContextMenuExtension::FileGroup, action); } if (item->file()) { fileUrl = item->path().toUrl(); } } else if (context->type() == Context::EditorContext) { KDevelop::EditorContext* editorContext = dynamic_cast(context); fileUrl = editorContext->url(); } if (fileUrl.isValid() && determineTemplateType(fileUrl) != NoTemplate) { QAction* action = new QAction(i18n("Show Template Preview"), this); action->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); action->setData(fileUrl); connect(action, &QAction::triggered, this, &FileTemplatesPlugin::previewTemplate); ext.addAction(ContextMenuExtension::ExtensionGroup, action); } return ext; } QString FileTemplatesPlugin::name() const { return i18n("File Templates"); } QIcon FileTemplatesPlugin::icon() const { return QIcon::fromTheme(QStringLiteral("code-class")); } QAbstractItemModel* FileTemplatesPlugin::templatesModel() { if(!m_model) { m_model = new TemplatesModel(QStringLiteral("kdevfiletemplates"), this); } return m_model; } QString FileTemplatesPlugin::knsConfigurationFile() const { return QStringLiteral("kdevfiletemplates.knsrc"); } QStringList FileTemplatesPlugin::supportedMimeTypes() const { QStringList types; types << QStringLiteral("application/x-desktop"); types << QStringLiteral("application/x-bzip-compressed-tar"); types << QStringLiteral("application/zip"); return types; } void FileTemplatesPlugin::reload() { templatesModel(); m_model->refresh(); } void FileTemplatesPlugin::loadTemplate(const QString& fileName) { templatesModel(); m_model->loadTemplateFile(fileName); } void FileTemplatesPlugin::createFromTemplate() { QUrl baseUrl; if (QAction* action = qobject_cast(sender())) { baseUrl = action->data().toUrl(); } if (!baseUrl.isValid()) { // fall-back to currently active document's parent directory IDocument* doc = ICore::self()->documentController()->activeDocument(); if (doc && doc->url().isValid()) { baseUrl = doc->url().adjusted(QUrl::RemoveFilename); } } TemplateClassAssistant* assistant = new TemplateClassAssistant(QApplication::activeWindow(), baseUrl); assistant->setAttribute(Qt::WA_DeleteOnClose); assistant->show(); } FileTemplatesPlugin::TemplateType FileTemplatesPlugin::determineTemplateType(const QUrl& url) { QDir dir(url.toLocalFile()); /* * Search for a description file in the url's directory. * If it is not found there, try cascading up a maximum of 5 directories. */ int level = 0; while (dir.cdUp() && level < 5) { QStringList filters; filters << QStringLiteral("*.kdevtemplate") << QStringLiteral("*.desktop"); foreach (const QString& entry, dir.entryList(filters)) { qCDebug(PLUGIN_FILETEMPLATES) << "Trying entry" << entry; /* * This logic is not perfect, but it works for most cases. * * Project template description files usually have the suffix * ".kdevtemplate", so those are easy to find. For project templates, * all the files in the directory are template files. * * On the other hand, file templates use the generic suffix ".desktop". * Fortunately, those explicitly list input and output files, so we * only match the explicitly listed files */ if (entry.endsWith(QLatin1String(".kdevtemplate"))) { return ProjectTemplate; } KConfig* config = new KConfig(dir.absoluteFilePath(entry), KConfig::SimpleConfig); KConfigGroup group = config->group("General"); qCDebug(PLUGIN_FILETEMPLATES) << "General group keys:" << group.keyList(); if (!group.hasKey("Name") || !group.hasKey("Category")) { continue; } if (group.hasKey("Files")) { qCDebug(PLUGIN_FILETEMPLATES) << "Group has files " << group.readEntry("Files", QStringList()); foreach (const QString& outputFile, group.readEntry("Files", QStringList())) { if (dir.absoluteFilePath(config->group(outputFile).readEntry("File")) == url.toLocalFile()) { return FileTemplate; } } } if (group.hasKey("ShowFilesAfterGeneration")) { return ProjectTemplate; } } ++level; } return NoTemplate; } void FileTemplatesPlugin::previewTemplate() { QAction* action = qobject_cast(sender()); if (!action || !action->data().toUrl().isValid()) { return; } TemplatePreviewToolView* preview = qobject_cast(core()->uiController()->findToolView(i18n("Template Preview"), m_toolView)); if (!preview) { return; } core()->documentController()->activateDocument(core()->documentController()->openDocument(action->data().toUrl())); } #include "filetemplatesplugin.moc" diff --git a/plugins/filetemplates/licensepage.cpp b/plugins/filetemplates/licensepage.cpp index 95908410a2..c2cfe68fa5 100644 --- a/plugins/filetemplates/licensepage.cpp +++ b/plugins/filetemplates/licensepage.cpp @@ -1,277 +1,277 @@ /* This file is part of KDevelop Copyright 2008 Hamish Rodda 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 "licensepage.h" #include #include "ui_licensechooser.h" #include "debug.h" #include #include #include #include #include #include namespace KDevelop { struct LicensePagePrivate { struct LicenseInfo { QString name; QString path; QString contents; bool operator< (const LicenseInfo& o) const { return name.localeAwareCompare(o.name) < 0; } }; typedef QList LicenseList; LicensePagePrivate(LicensePage* page_) - : license(0) + : license(nullptr) , page(page_) { } // methods void initializeLicenses(); QString readLicense(int licenseIndex); bool saveLicense(); // slots void licenseComboChanged(int license); Ui::LicenseChooserDialog* license; LicenseList availableLicenses; LicensePage* page; }; //! Read all the license files in the global and local config dirs void LicensePagePrivate::initializeLicenses() { qCDebug(PLUGIN_FILETEMPLATES) << "Searching for available licenses"; QStringList licenseDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevcodegen/licenses"), QStandardPaths::LocateDirectory); //Iterate through the possible directories that contain licenses, and load their names foreach(const QString& currentDir, licenseDirs) { QDirIterator it(currentDir, QDir::Files | QDir::Readable); while(it.hasNext()) { LicenseInfo newLicense; newLicense.path = it.next(); newLicense.name = it.fileName(); qCDebug(PLUGIN_FILETEMPLATES) << "Found License: " << newLicense.name; availableLicenses.push_back(newLicense); } } std::sort(availableLicenses.begin(), availableLicenses.end()); foreach(const LicenseInfo& info, availableLicenses) { license->licenseComboBox->addItem(info.name); } //Finally add the option other for user specified licenses LicenseInfo otherLicense; availableLicenses.push_back(otherLicense); license->licenseComboBox->addItem(i18n("Other")); } // Read a license index, if it is not loaded, open it from the file QString LicensePagePrivate::readLicense(int licenseIndex) { //If the license is not loaded into memory, read it in if(availableLicenses[licenseIndex].contents.isEmpty()) { QString licenseText; //If we are dealing with the last option "other" just return a new empty string if(licenseIndex != (availableLicenses.size() - 1)) { qCDebug(PLUGIN_FILETEMPLATES) << "Reading license: " << availableLicenses[licenseIndex].name ; QFile newLicense(availableLicenses[licenseIndex].path); if(newLicense.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream newLicenseText(&newLicense); newLicenseText.setAutoDetectUnicode(true); licenseText = newLicenseText.readAll(); newLicense.close(); } else licenseText = QStringLiteral("Error, could not open license file.\n Was it deleted?"); } availableLicenses[licenseIndex].contents = licenseText; } return availableLicenses[licenseIndex].contents; } // ---Slots--- void LicensePagePrivate::licenseComboChanged(int selectedLicense) { //If the last slot is selected enable the save license combobox if(selectedLicense == (availableLicenses.size() - 1)) { license->licenseTextEdit->clear(); license->licenseTextEdit->setReadOnly(false); license->saveLicense->setEnabled(true); } else { license->saveLicense->setEnabled(false); license->licenseTextEdit->setReadOnly(true); } if(selectedLicense < 0 || selectedLicense >= availableLicenses.size()) license->licenseTextEdit->setText(i18n("Could not load previous license")); else license->licenseTextEdit->setText(readLicense(selectedLicense)); } bool LicensePagePrivate::saveLicense() { qCDebug(PLUGIN_FILETEMPLATES) << "Attempting to save custom license: " << license->licenseName->text(); QString localDataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+"/kdevcodegen/licenses/"; QString fullPath = localDataDir + license->licenseName->text(); QFile newFile(fullPath); if(newFile.exists()) { KMessageBox::sorry(page, i18n("The specified license already exists. " "Please provide a different name.")); return false; } QDir().mkpath(localDataDir); newFile.open(QIODevice::WriteOnly); qint64 result = newFile.write(license->licenseTextEdit->toPlainText().toUtf8()); newFile.close(); if(result == -1) { KMessageBox::sorry(page, i18n("Failed to write custom license template to file %1.", fullPath)); return false; } // also add to our data structures, this esp. needed for proper saving // of the license index so it can be restored the next time we show up LicenseInfo info; info.name = license->licenseName->text(); info.path = localDataDir; availableLicenses << info; // find index of the new the license, omitting the very last item ('Other') int idx = availableLicenses.count() - 1; for(int i = 0; i < availableLicenses.size() - 1; ++i) { if (info < availableLicenses.at(i)) { idx = i; break; } } availableLicenses.insert(idx, info); license->licenseComboBox->insertItem(idx, info.name); license->licenseComboBox->setCurrentIndex(idx); return true; } LicensePage::LicensePage(QWidget* parent) : QWidget(parent) , d(new LicensePagePrivate(this)) { d->license = new Ui::LicenseChooserDialog; d->license->setupUi(this); connect(d->license->licenseComboBox, static_cast(&KComboBox::currentIndexChanged), this, [&] (int selectedLicense) { d->licenseComboChanged(selectedLicense); }); connect(d->license->saveLicense, &QCheckBox::clicked, d->license->licenseName, &QLineEdit::setEnabled); // Read all the available licenses from the standard dirs d->initializeLicenses(); //Set the license selection to the previous one KConfigGroup config(KSharedConfig::openConfig()->group("CodeGeneration")); d->license->licenseComboBox->setCurrentIndex(config.readEntry( "LastSelectedLicense", 0 )); // Needed to avoid a bug where licenseComboChanged doesn't get // called by QComboBox if the past selection was 0 d->licenseComboChanged(d->license->licenseComboBox->currentIndex()); } LicensePage::~LicensePage() { if (d->license->saveLicense->isChecked() && !d->license->licenseName->text().isEmpty()) { d->saveLicense(); } KConfigGroup config(KSharedConfig::openConfig()->group("CodeGeneration")); //Do not save invalid license numbers' int index = d->license->licenseComboBox->currentIndex(); if( index >= 0 || index < d->availableLicenses.size() ) { config.writeEntry("LastSelectedLicense", index); config.config()->sync(); } else { qWarning() << "Attempted to save an invalid license number: " << index << ". Number of licenses:" << d->availableLicenses.size(); } delete d->license; delete d; } QString LicensePage::license() const { QString licenseText = d->license->licenseTextEdit->document()->toPlainText(); /* Add date, name and email to license text */ licenseText.replace(QLatin1String(""), QDate::currentDate().toString(QStringLiteral("yyyy"))); licenseText.replace(QLatin1String(""), QDate::currentDate().toString(QStringLiteral("MM"))); licenseText.replace(QLatin1String(""), QDate::currentDate().toString(QStringLiteral("dd"))); QString developer(QStringLiteral("%1 <%2>")); KEMailSettings emailSettings; QString name = emailSettings.getSetting(KEMailSettings::RealName); if (name.isEmpty()) { name = QStringLiteral(""); } developer = developer.arg(name); QString email = emailSettings.getSetting(KEMailSettings::EmailAddress); if (email.isEmpty()) { email = QStringLiteral("email"); //no < > as they are already through the email field } developer = developer.arg(email); licenseText.replace(QLatin1String(""), developer); return licenseText; } } Q_DECLARE_TYPEINFO(KDevelop::LicensePagePrivate::LicenseInfo, Q_MOVABLE_TYPE); #include "moc_licensepage.cpp" diff --git a/plugins/filetemplates/outputpage.cpp b/plugins/filetemplates/outputpage.cpp index 10dbb2b935..a7fb68b287 100644 --- a/plugins/filetemplates/outputpage.cpp +++ b/plugins/filetemplates/outputpage.cpp @@ -1,269 +1,269 @@ /* This file is part of KDevelop Copyright 2008 Hamish Rodda 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 "outputpage.h" #include "ui_outputlocation.h" #include "debug.h" #include #include #include #include #include #include #include #include #include namespace KDevelop { struct OutputPagePrivate { OutputPagePrivate(OutputPage* page_) : page(page_) - , output(0) + , output(nullptr) { } OutputPage* page; Ui::OutputLocationDialog* output; QSignalMapper urlChangedMapper; QHash outputFiles; QHash outputLines; QHash outputColumns; QList labels; QHash defaultUrls; QHash lowerCaseUrls; QStringList fileIdentifiers; void updateRanges(QSpinBox* line, QSpinBox* column, bool enable); void updateFileRange(const QString& field); void updateFileNames(); void validate(); }; void OutputPagePrivate::updateRanges(QSpinBox* line, QSpinBox* column, bool enable) { qCDebug(PLUGIN_FILETEMPLATES) << "Updating Ranges, file exists: " << enable; line->setEnabled(enable); column->setEnabled(enable); } void OutputPagePrivate::updateFileRange(const QString& field) { if (!outputFiles.contains(field)) { return; } QString url = outputFiles[field]->url().toLocalFile(); QFileInfo info(url); updateRanges(outputLines[field], outputColumns[field], info.exists() && !info.isDir()); validate(); } void OutputPagePrivate::updateFileNames() { bool lower = output->lowerFilenameCheckBox->isChecked(); const QHash urls = lower ? lowerCaseUrls : defaultUrls; for (QHash::const_iterator it = outputFiles.constBegin(); it != outputFiles.constEnd(); ++it) { const QUrl url = urls.value(it.key()); if (!url.isEmpty()) { it.value()->setUrl(url); } } //Save the setting for next time KConfigGroup codegenGroup( KSharedConfig::openConfig(), "CodeGeneration" ); codegenGroup.writeEntry( "LowerCaseFilenames", output->lowerFilenameCheckBox->isChecked() ); validate(); } void OutputPagePrivate::validate() { QStringList invalidFiles; for(QHash< QString, KUrlRequester* >::const_iterator it = outputFiles.constBegin(); it != outputFiles.constEnd(); ++it) { if (!it.value()->url().isValid()) { invalidFiles << it.key(); } else if (it.value()->url().isLocalFile() && !QFileInfo(it.value()->url().adjusted(QUrl::RemoveFilename).toLocalFile()).isWritable()) { invalidFiles << it.key(); } } bool valid = invalidFiles.isEmpty(); if (valid) { output->messageWidget->animatedHide(); } else { std::sort(invalidFiles.begin(), invalidFiles.end()); output->messageWidget->setMessageType(KMessageWidget::Error); output->messageWidget->setCloseButtonVisible(false); output->messageWidget->setText(i18np("Invalid output file: %2", "Invalid output files: %2", invalidFiles.count(), invalidFiles.join(QStringLiteral(", ")))); output->messageWidget->animatedShow(); } emit page->isValid(valid); } OutputPage::OutputPage(QWidget* parent) : QWidget(parent) , d(new OutputPagePrivate(this)) { d->output = new Ui::OutputLocationDialog; d->output->setupUi(this); d->output->messageWidget->setVisible(false); connect(&d->urlChangedMapper, static_cast(&QSignalMapper::mapped), this, [&] (const QString& field) { d->updateFileRange(field); }); connect(d->output->lowerFilenameCheckBox, &QCheckBox::stateChanged, this, [&] { d->updateFileNames(); }); } OutputPage::~OutputPage() { delete d->output; delete d; } void OutputPage::prepareForm(const SourceFileTemplate& fileTemplate) { // First clear any existing file configurations // This can happen when going back and forth between assistant pages d->fileIdentifiers.clear(); d->defaultUrls.clear(); d->lowerCaseUrls.clear(); while (d->output->urlFormLayout->count() > 0) { d->output->urlFormLayout->takeAt(0); } while (d->output->positionFormLayout->count() > 0) { d->output->positionFormLayout->takeAt(0); } foreach (KUrlRequester* req, d->outputFiles) { d->urlChangedMapper.removeMappings(req); } qDeleteAll(d->outputFiles); qDeleteAll(d->outputLines); qDeleteAll(d->outputColumns); qDeleteAll(d->labels); d->outputFiles.clear(); d->outputLines.clear(); d->outputColumns.clear(); d->labels.clear(); foreach (const SourceFileTemplate::OutputFile& file, fileTemplate.outputFiles()) { d->fileIdentifiers << file.identifier; QLabel* label = new QLabel(file.label, this); d->labels << label; KUrlRequester* requester = new KUrlRequester(this); requester->setMode( KFile::File | KFile::LocalOnly ); d->urlChangedMapper.setMapping(requester, file.identifier); connect(requester, &KUrlRequester::textChanged, &d->urlChangedMapper, static_cast(&QSignalMapper::map)); d->output->urlFormLayout->addRow(label, requester); d->outputFiles.insert(file.identifier, requester); label = new QLabel(file.label, this); d->labels << label; QHBoxLayout* layout = new QHBoxLayout(this); auto line = new QSpinBox(this); line->setPrefix(i18n("Line: ")); line->setValue(0); line->setMinimum(0); layout->addWidget(line); auto column = new QSpinBox(this); column->setPrefix(i18n("Column: ")); column->setValue(0); column->setMinimum(0); layout->addWidget(column); d->output->positionFormLayout->addRow(label, layout); d->outputLines.insert(file.identifier, line); d->outputColumns.insert(file.identifier, column); } } void OutputPage::loadFileTemplate(const SourceFileTemplate& fileTemplate, const QUrl& _baseUrl, TemplateRenderer* renderer) { QUrl baseUrl = _baseUrl; if (!baseUrl.path().endsWith('/')) { baseUrl.setPath(baseUrl.path()+'/'); } KConfigGroup codegenGroup( KSharedConfig::openConfig(), "CodeGeneration" ); bool lower = codegenGroup.readEntry( "LowerCaseFilenames", true ); d->output->lowerFilenameCheckBox->setChecked(lower); foreach (const SourceFileTemplate::OutputFile& file, fileTemplate.outputFiles()) { d->fileIdentifiers << file.identifier; QUrl url = baseUrl.resolved(QUrl(renderer->render(file.outputName))); d->defaultUrls.insert(file.identifier, url); url = baseUrl.resolved(QUrl(renderer->render(file.outputName).toLower())); d->lowerCaseUrls.insert(file.identifier, url); } d->updateFileNames(); } QHash< QString, QUrl > OutputPage::fileUrls() const { QHash urls; for (QHash::const_iterator it = d->outputFiles.constBegin(); it != d->outputFiles.constEnd(); ++it) { urls.insert(it.key(), it.value()->url()); } return urls; } QHash< QString, KTextEditor::Cursor > OutputPage::filePositions() const { QHash positions; foreach (const QString& identifier, d->fileIdentifiers) { positions.insert(identifier, KTextEditor::Cursor(d->outputLines[identifier]->value(), d->outputColumns[identifier]->value())); } return positions; } } #include "moc_outputpage.cpp" diff --git a/plugins/filetemplates/overridespage.cpp b/plugins/filetemplates/overridespage.cpp index 730738c523..200ae5fbeb 100644 --- a/plugins/filetemplates/overridespage.cpp +++ b/plugins/filetemplates/overridespage.cpp @@ -1,266 +1,266 @@ /* Copyright 2008 Hamish Rodda 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 "overridespage.h" #include "ui_overridevirtuals.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; enum Column { ClassOrFunctionColumn, ///< Column represents either a base class item or a function item AccessColumn, ///< Column represents the access policy of a function PropertiesColumn ///< Column represents the properties of a function (e.g. if it's a ctor, dtor, signal, slot, ...) }; static QString accessPolicyToString(Declaration::AccessPolicy accessPolicy) { switch (accessPolicy) { case Declaration::DefaultAccess: case Declaration::Public: return i18n("Public"); case Declaration::Protected: return i18n("Protected"); case Declaration::Private: return i18n("Private"); default: qCritical("Unexpected value for Declaration::AccessPolicy: %d", accessPolicy); Q_ASSERT(false); return QString(); } } static QString functionPropertiesToString(ClassFunctionDeclaration* decl) { Q_ASSERT(decl); QStringList properties; if (decl->isConstructor()) { properties << i18n("Constructor"); } else if (decl->isDestructor()) { properties << i18n("Destructor"); } else if (decl->isSignal()) { properties << i18n("Signal"); } else if (decl->isSlot()) { properties << i18n("Slot"); } else if (decl->isAbstract()) { properties << i18n("Abstract function"); } return properties.join(QStringLiteral(", ")); } struct KDevelop::OverridesPagePrivate { OverridesPagePrivate() - : overrides(0) + : overrides(nullptr) { } Ui::OverridesDialog* overrides; QMultiHash overriddenFunctions; QMap declarationMap; QList chosenOverrides; }; OverridesPage::OverridesPage(QWidget* parent) : QWidget(parent) , d(new OverridesPagePrivate) { d->overrides = new Ui::OverridesDialog; d->overrides->setupUi(this); connect(d->overrides->selectAllPushButton, &QPushButton::pressed, this, &OverridesPage::selectAll); connect(d->overrides->deselectAllPushButton, &QPushButton::pressed, this, &OverridesPage::deselectAll); } OverridesPage::~OverridesPage() { delete d->overrides; delete d; } QList< DeclarationPointer > OverridesPage::selectedOverrides() const { QList declarations; for (int i = 0; i < d->overrides->overridesTree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = d->overrides->overridesTree->topLevelItem(i); for (int j = 0; j < item->childCount(); ++j) { QTreeWidgetItem* child = item->child(j); if (child->checkState(ClassOrFunctionColumn) == Qt::Checked) { qCDebug(PLUGIN_FILETEMPLATES) << "Adding declaration" << d->declarationMap[child]->toString(); declarations << d->declarationMap[child]; } } } qCDebug(PLUGIN_FILETEMPLATES) << declarations.size(); return declarations; } void OverridesPage::clear() { d->overriddenFunctions.clear(); overrideTree()->clear(); d->chosenOverrides.clear(); d->declarationMap.clear(); } void OverridesPage::addBaseClasses(const QList& directBases, const QList& allBases) { DUChainReadLocker lock; foreach(const DeclarationPointer& baseClass, allBases) { DUContext* context = baseClass->internalContext(); QTreeWidgetItem* classItem = new QTreeWidgetItem(overrideTree(), QStringList() << baseClass->qualifiedIdentifier().toString()); classItem->setIcon(ClassOrFunctionColumn, DUChainUtils::iconForDeclaration(baseClass.data())); //For this internal context get all the function declarations inside the class foreach (Declaration * childDeclaration, context->localDeclarations()) { if (AbstractFunctionDeclaration * func = dynamic_cast(childDeclaration)) { if (func->isVirtual()) { // Its a virtual function, add it to the list unless it's a destructor ClassFunctionDeclaration* cFunc = dynamic_cast(childDeclaration); if (cFunc && !cFunc->isDestructor()) { addPotentialOverride(classItem, DeclarationPointer(childDeclaration)); } } else if (directBases.contains(baseClass)) { // add ctors of direct parents ClassFunctionDeclaration* cFunc = dynamic_cast(childDeclaration); if (cFunc && cFunc->isConstructor()) { addPotentialOverride(classItem, DeclarationPointer(childDeclaration)); } } } } } overrideTree()->expandAll(); overrideTree()->header()->resizeSections(QHeaderView::ResizeToContents); } void OverridesPage::addPotentialOverride(QTreeWidgetItem* classItem, const DeclarationPointer& childDeclaration) { ClassFunctionDeclaration* function = dynamic_cast(childDeclaration.data()); if (!function) { qCDebug(PLUGIN_FILETEMPLATES) << "Declaration is not a function:" << childDeclaration->identifier().toString(); return; } if (function->accessPolicy() == Declaration::Private) { qCDebug(PLUGIN_FILETEMPLATES) << "Declaration is private, returning:" << function->identifier().toString(); return; } qCDebug(PLUGIN_FILETEMPLATES) << childDeclaration->toString(); if (d->overriddenFunctions.contains(childDeclaration->identifier())) { foreach (const DeclarationPointer& decl, d->overriddenFunctions.values(childDeclaration->identifier())) { if (decl->indexedType() == childDeclaration->indexedType()) { qCDebug(PLUGIN_FILETEMPLATES) << "Declaration is already shown"; return; } } } d->overriddenFunctions.insert(childDeclaration->identifier(), childDeclaration); QTreeWidgetItem* overrideItem = new QTreeWidgetItem(classItem, QStringList() << childDeclaration->toString()); overrideItem->setFlags( Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable) ); overrideItem->setCheckState(ClassOrFunctionColumn, d->chosenOverrides.contains(childDeclaration) ? Qt::Checked : Qt::Unchecked); overrideItem->setIcon(ClassOrFunctionColumn, DUChainUtils::iconForDeclaration(childDeclaration.data())); overrideItem->setData(ClassOrFunctionColumn, Qt::UserRole, QVariant::fromValue(IndexedDeclaration(childDeclaration.data()))); overrideItem->setText(AccessColumn, accessPolicyToString(function->accessPolicy())); overrideItem->setText(PropertiesColumn, functionPropertiesToString(function)); if (function->isAbstract()) { overrideItem->setIcon(ClassOrFunctionColumn, QIcon::fromTheme(QStringLiteral("flag-red"))); overrideItem->setCheckState(ClassOrFunctionColumn, Qt::Checked); classItem->removeChild(overrideItem); classItem->insertChild(0, overrideItem); } d->declarationMap[overrideItem] = childDeclaration; } QTreeWidget* OverridesPage::overrideTree() const { return d->overrides->overridesTree; } QWidget* OverridesPage::extraFunctionsContainer() const { return d->overrides->extraFunctionWidget; } void OverridesPage::selectAll() { for (int i = 0; i < d->overrides->overridesTree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = d->overrides->overridesTree->topLevelItem(i); for (int j = 0; j < item->childCount(); ++j) item->child(j)->setCheckState(ClassOrFunctionColumn, Qt::Checked); } } void OverridesPage::deselectAll() { for (int i = 0; i < d->overrides->overridesTree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = d->overrides->overridesTree->topLevelItem(i); for (int j = 0; j < item->childCount(); ++j) item->child(j)->setCheckState(ClassOrFunctionColumn, Qt::Unchecked); } } void OverridesPage::addCustomDeclarations (const QString& category, const QList& declarations) { qCDebug(PLUGIN_FILETEMPLATES) << category << declarations.size(); DUChainReadLocker lock(DUChain::lock()); QTreeWidgetItem* item = new QTreeWidgetItem(overrideTree(), QStringList() << category); foreach (const DeclarationPointer& declaration, declarations) { addPotentialOverride(item, declaration); } overrideTree()->expandAll(); overrideTree()->header()->resizeSections(QHeaderView::ResizeToContents); } diff --git a/plugins/filetemplates/templateclassassistant.cpp b/plugins/filetemplates/templateclassassistant.cpp index 7e2335ae39..73a200cc43 100644 --- a/plugins/filetemplates/templateclassassistant.cpp +++ b/plugins/filetemplates/templateclassassistant.cpp @@ -1,587 +1,587 @@ /* 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 "templateclassassistant.h" #include "templateselectionpage.h" #include "templateoptionspage.h" #include "classmemberspage.h" #include "classidentifierpage.h" #include "overridespage.h" #include "licensepage.h" #include "outputpage.h" #include "testcasespage.h" #include "defaultcreateclasshelper.h" #include "debug.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 #define REMOVE_PAGE(name) \ if (d->name##Page) \ { \ removePage(d->name##Page); \ d->name##Page = 0; \ d->name##PageWidget = 0; \ } #define ZERO_PAGE(name) \ d->name##Page = 0; \ d->name##PageWidget = 0; using namespace KDevelop; class KDevelop::TemplateClassAssistantPrivate { public: TemplateClassAssistantPrivate(const QUrl& baseUrl); ~TemplateClassAssistantPrivate(); void addFilesToTarget (const QHash& fileUrls); KPageWidgetItem* templateSelectionPage; KPageWidgetItem* classIdentifierPage; KPageWidgetItem* overridesPage; KPageWidgetItem* membersPage; KPageWidgetItem* testCasesPage; KPageWidgetItem* licensePage; KPageWidgetItem* templateOptionsPage; KPageWidgetItem* outputPage; KPageWidgetItem* dummyPage; TemplateSelectionPage* templateSelectionPageWidget; ClassIdentifierPage* classIdentifierPageWidget; OverridesPage* overridesPageWidget; ClassMembersPage* membersPageWidget; TestCasesPage* testCasesPageWidget; LicensePage* licensePageWidget; TemplateOptionsPage* templateOptionsPageWidget; OutputPage* outputPageWidget; QUrl baseUrl; SourceFileTemplate fileTemplate; ICreateClassHelper* helper; TemplateClassGenerator* generator; TemplateRenderer* renderer; QString type; QVariantHash templateOptions; }; TemplateClassAssistantPrivate::TemplateClassAssistantPrivate(const QUrl& baseUrl) : baseUrl(baseUrl) -, helper(0) -, generator(0) -, renderer(0) +, helper(nullptr) +, generator(nullptr) +, renderer(nullptr) { } TemplateClassAssistantPrivate::~TemplateClassAssistantPrivate() { delete helper; if (generator) { delete generator; } else { // if we got a generator, it should keep ownership of the renderer // otherwise, we created a templaterenderer on our own delete renderer; } } void TemplateClassAssistantPrivate::addFilesToTarget (const QHash< QString, QUrl >& fileUrls) { // Add the generated files to a target, if one is found QUrl url = baseUrl; if (!url.isValid()) { // This was probably not launched from the project manager view // Still, we try to find the common URL where the generated files are located if (!fileUrls.isEmpty()) { url = fileUrls.constBegin().value().adjusted(QUrl::RemoveFilename); } } qCDebug(PLUGIN_FILETEMPLATES) << "Searching for targets with URL" << url; IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if (!project || !project->buildSystemManager()) { qCDebug(PLUGIN_FILETEMPLATES) << "No suitable project found"; return; } QList items = project->itemsForPath(IndexedString(url)); if (items.isEmpty()) { qCDebug(PLUGIN_FILETEMPLATES) << "No suitable project items found"; return; } QList targets; - ProjectTargetItem* target = 0; + ProjectTargetItem* target = nullptr; foreach (ProjectBaseItem* item, items) { if (ProjectTargetItem* target = item->target()) { targets << target; } } if (targets.isEmpty()) { // If no target was explicitly found yet, try all the targets in the current folder foreach (ProjectBaseItem* item, items) { targets << item->targetList(); } } if (targets.isEmpty()) { // If still no targets, we traverse the tree up to the first directory with targets ProjectBaseItem* item = items.first()->parent(); while (targets.isEmpty() && item) { targets = item->targetList(); item = item->parent(); } } if (targets.size() == 1) { qCDebug(PLUGIN_FILETEMPLATES) << "Only one candidate target," << targets.first()->text() << ", using it"; target = targets.first(); } else if (targets.size() > 1) { // More than one candidate target, show the chooser dialog QPointer d = new QDialog; auto mainLayout = new QVBoxLayout(d); mainLayout->addWidget(new QLabel(i18n("Choose one target to add the file or cancel if you do not want to do so."))); QListWidget* targetsWidget = new QListWidget(d); targetsWidget->setSelectionMode(QAbstractItemView::SingleSelection); foreach(ProjectTargetItem* target, targets) { targetsWidget->addItem(target->text()); } targetsWidget->setCurrentRow(0); mainLayout->addWidget(targetsWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); d->connect(buttonBox, &QDialogButtonBox::accepted, d.data(), &QDialog::accept); d->connect(buttonBox, &QDialogButtonBox::rejected, d.data(), &QDialog::reject); mainLayout->addWidget(buttonBox); if(d->exec() == QDialog::Accepted) { if (!targetsWidget->selectedItems().isEmpty()) { target = targets[targetsWidget->currentRow()]; } else { qCDebug(PLUGIN_FILETEMPLATES) << "Did not select anything, not adding to a target"; return; } } else { qCDebug(PLUGIN_FILETEMPLATES) << "Canceled select target dialog, not adding to a target"; return; } } else { // No target, not doing anything qCDebug(PLUGIN_FILETEMPLATES) << "No possible targets for URL" << url; return; } Q_ASSERT(target); QList fileItems; foreach (const QUrl &fileUrl, fileUrls) { foreach (ProjectBaseItem* item, project->itemsForPath(IndexedString(KIO::upUrl(fileUrl)))) { if (ProjectFolderItem* folder = item->folder()) { ///FIXME: use Path instead of QUrl in the template class assistant if (ProjectFileItem* file = project->projectFileManager()->addFile(Path(fileUrl), folder)) { fileItems << file; break; } } } } if (!fileItems.isEmpty()) { project->buildSystemManager()->addFilesToTarget(fileItems, target); } } TemplateClassAssistant::TemplateClassAssistant(QWidget* parent, const QUrl& baseUrl) : KAssistantDialog(parent) , d(new TemplateClassAssistantPrivate(baseUrl)) { ZERO_PAGE(templateSelection) ZERO_PAGE(templateOptions) ZERO_PAGE(members) ZERO_PAGE(classIdentifier) ZERO_PAGE(overrides) ZERO_PAGE(license) ZERO_PAGE(output) ZERO_PAGE(testCases) setup(); } TemplateClassAssistant::~TemplateClassAssistant() { delete d; } void TemplateClassAssistant::setup() { if (d->baseUrl.isValid()) { setWindowTitle(xi18n("Create Files from Template in %1", d->baseUrl.toDisplayString())); } else { setWindowTitle(i18n("Create Files from Template")); } d->templateSelectionPageWidget = new TemplateSelectionPage(this); connect(this, &TemplateClassAssistant::accepted, d->templateSelectionPageWidget, &TemplateSelectionPage::saveConfig); d->templateSelectionPage = addPage(d->templateSelectionPageWidget, i18n("Language and Template")); d->templateSelectionPage->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template"))); d->dummyPage = addPage(new QWidget(this), QStringLiteral("Dummy Page")); // showButton(QDialog::Help, false); } void TemplateClassAssistant::templateChosen(const QString& templateDescription) { d->fileTemplate.setTemplateDescription(templateDescription); d->type = d->fileTemplate.type(); - d->generator = 0; + d->generator = nullptr; if (!d->fileTemplate.isValid()) { return; } qCDebug(PLUGIN_FILETEMPLATES) << "Selected template" << templateDescription << "of type" << d->type; removePage(d->dummyPage); if (d->baseUrl.isValid()) { setWindowTitle(xi18n("Create Files from Template %1 in %2", d->fileTemplate.name(), d->baseUrl.toDisplayString())); } else { setWindowTitle(xi18n("Create Files from Template %1", d->fileTemplate.name())); } if (d->type == QLatin1String("Class")) { d->classIdentifierPageWidget = new ClassIdentifierPage(this); d->classIdentifierPage = addPage(d->classIdentifierPageWidget, i18n("Class Basics")); d->classIdentifierPage->setIcon(QIcon::fromTheme(QStringLiteral("classnew"))); connect(d->classIdentifierPageWidget, &ClassIdentifierPage::isValid, this, &TemplateClassAssistant::setCurrentPageValid); setValid(d->classIdentifierPage, false); d->overridesPageWidget = new OverridesPage(this); d->overridesPage = addPage(d->overridesPageWidget, i18n("Override Methods")); d->overridesPage->setIcon(QIcon::fromTheme(QStringLiteral("code-class"))); setValid(d->overridesPage, true); d->membersPageWidget = new ClassMembersPage(this); d->membersPage = addPage(d->membersPageWidget, i18n("Class Members")); d->membersPage->setIcon(QIcon::fromTheme(QStringLiteral("field"))); setValid(d->membersPage, true); - d->helper = 0; + d->helper = nullptr; QString languageName = d->fileTemplate.languageName(); auto language = ICore::self()->languageController()->language(languageName); if (language) { d->helper = language->createClassHelper(); } if (!d->helper) { qCDebug(PLUGIN_FILETEMPLATES) << "No class creation helper for language" << languageName; d->helper = new DefaultCreateClassHelper; } d->generator = d->helper->createGenerator(d->baseUrl); Q_ASSERT(d->generator); d->generator->setTemplateDescription(d->fileTemplate); d->renderer = d->generator->renderer(); } else { if (d->type == QLatin1String("Test")) { d->testCasesPageWidget = new TestCasesPage(this); d->testCasesPage = addPage(d->testCasesPageWidget, i18n("Test Cases")); connect(d->testCasesPageWidget, &TestCasesPage::isValid, this, &TemplateClassAssistant::setCurrentPageValid); setValid(d->testCasesPage, false); } d->renderer = new TemplateRenderer; d->renderer->setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); } d->licensePageWidget = new LicensePage(this); d->licensePage = addPage(d->licensePageWidget, i18n("License")); d->licensePage->setIcon(QIcon::fromTheme(QStringLiteral("text-x-copying"))); setValid(d->licensePage, true); d->outputPageWidget = new OutputPage(this); d->outputPageWidget->prepareForm(d->fileTemplate); d->outputPage = addPage(d->outputPageWidget, i18n("Output")); d->outputPage->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); connect(d->outputPageWidget, &OutputPage::isValid, this, &TemplateClassAssistant::setCurrentPageValid); setValid(d->outputPage, false); if (d->fileTemplate.hasCustomOptions()) { qCDebug(PLUGIN_FILETEMPLATES) << "Class generator has custom options"; d->templateOptionsPageWidget = new TemplateOptionsPage(this); d->templateOptionsPage = insertPage(d->outputPage, d->templateOptionsPageWidget, i18n("Template Options")); } setCurrentPage(d->templateSelectionPage); } void TemplateClassAssistant::next() { qCDebug(PLUGIN_FILETEMPLATES) << currentPage()->name() << currentPage()->header(); if (currentPage() == d->templateSelectionPage) { // We have chosen the template // Depending on the template's language, we can now create a helper QString description = d->templateSelectionPageWidget->selectedTemplate(); templateChosen(description); if (!d->fileTemplate.isValid()) { return; } } else if (currentPage() == d->classIdentifierPage) { d->generator->setIdentifier(d->classIdentifierPageWidget->identifier()); d->generator->setBaseClasses(d->classIdentifierPageWidget->inheritanceList()); } else if (currentPage() == d->overridesPage) { ClassDescription desc = d->generator->description(); desc.methods.clear(); foreach (const DeclarationPointer& declaration, d->overridesPageWidget->selectedOverrides()) { desc.methods << FunctionDescription(declaration); } d->generator->setDescription(desc); } else if (currentPage() == d->membersPage) { ClassDescription desc = d->generator->description(); desc.members = d->membersPageWidget->members(); d->generator->setDescription(desc); } else if (currentPage() == d->licensePage) { if (d->generator) { d->generator->setLicense(d->licensePageWidget->license()); } else { d->renderer->addVariable(QStringLiteral("license"), d->licensePageWidget->license()); } } else if (d->templateOptionsPage && (currentPage() == d->templateOptionsPage)) { if (d->generator) { d->generator->addVariables(d->templateOptionsPageWidget->templateOptions()); } else { d->renderer->addVariables(d->templateOptionsPageWidget->templateOptions()); } } else if (currentPage() == d->testCasesPage) { d->renderer->addVariable(QStringLiteral("name"), d->testCasesPageWidget->name()); d->renderer->addVariable(QStringLiteral("testCases"), d->testCasesPageWidget->testCases()); } KAssistantDialog::next(); if (currentPage() == d->classIdentifierPage) { d->classIdentifierPageWidget->setInheritanceList(d->fileTemplate.defaultBaseClasses()); } else if (currentPage() == d->membersPage) { d->membersPageWidget->setMembers(d->generator->description().members); } else if (currentPage() == d->overridesPage) { d->overridesPageWidget->clear(); d->overridesPageWidget->addCustomDeclarations(i18n("Default"), d->helper->defaultMethods(d->generator->name())); d->overridesPageWidget->addBaseClasses(d->generator->directBaseClasses(), d->generator->allBaseClasses()); } else if (d->templateOptionsPage && (currentPage() == d->templateOptionsPage)) { d->templateOptionsPageWidget->load(d->fileTemplate, d->renderer); } else if (currentPage() == d->outputPage) { d->outputPageWidget->loadFileTemplate(d->fileTemplate, d->baseUrl, d->renderer); } } void TemplateClassAssistant::back() { KAssistantDialog::back(); if (currentPage() == d->templateSelectionPage) { REMOVE_PAGE(classIdentifier) REMOVE_PAGE(overrides) REMOVE_PAGE(members) REMOVE_PAGE(testCases) REMOVE_PAGE(output) REMOVE_PAGE(templateOptions) REMOVE_PAGE(license) delete d->helper; - d->helper = 0; + d->helper = nullptr; if (d->generator) { delete d->generator; } else { delete d->renderer; } - d->generator = 0; - d->renderer = 0; + d->generator = nullptr; + d->renderer = nullptr; if (d->baseUrl.isValid()) { setWindowTitle(xi18n("Create Files from Template in %1", d->baseUrl.toDisplayString())); } else { setWindowTitle(i18n("Create Files from Template")); } d->dummyPage = addPage(new QWidget(this), QStringLiteral("Dummy Page")); } } void TemplateClassAssistant::accept() { // next() is not called for the last page (when the user clicks Finish), so we have to set output locations here QHash fileUrls = d->outputPageWidget->fileUrls(); QHash filePositions = d->outputPageWidget->filePositions(); DocumentChangeSet changes; if (d->generator) { QHash::const_iterator it = fileUrls.constBegin(); for (; it != fileUrls.constEnd(); ++it) { d->generator->setFileUrl(it.key(), it.value()); d->generator->setFilePosition(it.key(), filePositions.value(it.key())); } d->generator->addVariables(d->templateOptions); changes = d->generator->generate(); } else { changes = d->renderer->renderFileTemplate(d->fileTemplate, d->baseUrl, fileUrls); } d->addFilesToTarget(fileUrls); changes.applyAllChanges(); // Open the generated files in the editor foreach (const QUrl& url, fileUrls) { ICore::self()->documentController()->openDocument(url); } KAssistantDialog::accept(); } void TemplateClassAssistant::setCurrentPageValid(bool valid) { setValid(currentPage(), valid); } QUrl TemplateClassAssistant::baseUrl() const { return d->baseUrl; } diff --git a/plugins/filetemplates/templateoptionspage.cpp b/plugins/filetemplates/templateoptionspage.cpp index 73c48bb5ba..e4efe907a4 100644 --- a/plugins/filetemplates/templateoptionspage.cpp +++ b/plugins/filetemplates/templateoptionspage.cpp @@ -1,137 +1,137 @@ /* 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 "templateoptionspage.h" #include "templateclassassistant.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class KDevelop::TemplateOptionsPagePrivate { public: QList entries; QHash controls; QHash typeProperties; }; TemplateOptionsPage::TemplateOptionsPage(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new TemplateOptionsPagePrivate) { d->typeProperties.insert(QStringLiteral("String"), "text"); d->typeProperties.insert(QStringLiteral("Int"), "value"); d->typeProperties.insert(QStringLiteral("Bool"), "checked"); } TemplateOptionsPage::~TemplateOptionsPage() { delete d; } void TemplateOptionsPage::load(const SourceFileTemplate& fileTemplate, TemplateRenderer* renderer) { d->entries.clear(); QLayout* layout = new QVBoxLayout(); QHash > options = fileTemplate.customOptions(renderer); QHash >::const_iterator it; for (it = options.constBegin(); it != options.constEnd(); ++it) { QGroupBox* box = new QGroupBox(this); box->setTitle(it.key()); QFormLayout* formLayout = new QFormLayout; d->entries << it.value(); foreach (const SourceFileTemplate::ConfigOption& entry, it.value()) { QLabel* label = new QLabel(entry.label, box); - QWidget* control = 0; + QWidget* control = nullptr; const QString type = entry.type; if (type == QLatin1String("String")) { control = new QLineEdit(entry.value.toString(), box); } else if (type == QLatin1String("Int")) { auto input = new QSpinBox(box); input->setValue(entry.value.toInt()); if (!entry.minValue.isEmpty()) { input->setMinimum(entry.minValue.toInt()); } if (!entry.maxValue.isEmpty()) { input->setMaximum(entry.maxValue.toInt()); } control = input; } else if (type == QLatin1String("Bool")) { bool checked = (QString::compare(entry.value.toString(), QStringLiteral("true"), Qt::CaseInsensitive) == 0); QCheckBox* checkBox = new QCheckBox(entry.label, box); checkBox->setCheckState(checked ? Qt::Checked : Qt::Unchecked); } else { qCDebug(PLUGIN_FILETEMPLATES) << "Unrecognized option type" << entry.type; } if (control) { formLayout->addRow(label, control); d->controls.insert(entry.name, control); } } box->setLayout(formLayout); layout->addWidget(box); } setLayout(layout); } QVariantHash TemplateOptionsPage::templateOptions() const { QVariantHash values; foreach (const SourceFileTemplate::ConfigOption& entry, d->entries) { Q_ASSERT(d->controls.contains(entry.name)); Q_ASSERT(d->typeProperties.contains(entry.type)); values.insert(entry.name, d->controls[entry.name]->property(d->typeProperties[entry.type])); } qCDebug(PLUGIN_FILETEMPLATES) << values.size() << d->entries.size(); return values; } diff --git a/plugins/filetemplates/templatepreviewtoolview.cpp b/plugins/filetemplates/templatepreviewtoolview.cpp index 634d3f5fa5..f7ee90ea03 100644 --- a/plugins/filetemplates/templatepreviewtoolview.cpp +++ b/plugins/filetemplates/templatepreviewtoolview.cpp @@ -1,181 +1,181 @@ /* * 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 #include #include using namespace KDevelop; TemplatePreviewToolView::TemplatePreviewToolView(FileTemplatesPlugin* plugin, QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , ui(new Ui::TemplatePreviewToolView) -, m_original(0) +, m_original(nullptr) , m_plugin(plugin) { ui->setupUi(this); 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() : 0); + 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/filetemplates/templateselectionpage.cpp b/plugins/filetemplates/templateselectionpage.cpp index e592510664..d1f9c751c0 100644 --- a/plugins/filetemplates/templateselectionpage.cpp +++ b/plugins/filetemplates/templateselectionpage.cpp @@ -1,267 +1,267 @@ /* 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 "templateselectionpage.h" #include "templateclassassistant.h" #include "templatepreview.h" #include #include #include #include #include #include #include #include #include #include #include #include "ui_templateselection.h" #include #include #include #include #include #include #include using namespace KDevelop; static const char LastUsedTemplateEntry[] = "LastUsedTemplate"; static const char FileTemplatesGroup[] = "SourceFileTemplates"; class KDevelop::TemplateSelectionPagePrivate { public: TemplateSelectionPagePrivate(TemplateSelectionPage* page_) : page(page_) {} TemplateSelectionPage* page; Ui::TemplateSelection* ui; QString selectedTemplate; TemplateClassAssistant* assistant; TemplatesModel* model; void currentTemplateChanged(const QModelIndex& index); void getMoreClicked(); void loadFileClicked(); void previewTemplate(const QString& templateFile); }; void TemplateSelectionPagePrivate::currentTemplateChanged(const QModelIndex& index) { // delete preview tabs if (!index.isValid() || index.child(0, 0).isValid()) { // invalid or has child assistant->setValid(assistant->currentPage(), false); ui->previewLabel->setVisible(false); ui->tabWidget->setVisible(false); } else { selectedTemplate = model->data(index, TemplatesModel::DescriptionFileRole).toString(); assistant->setValid(assistant->currentPage(), true); previewTemplate(selectedTemplate); ui->previewLabel->setVisible(true); ui->tabWidget->setVisible(true); ui->previewLabel->setText(i18nc("%1: template comment", "Preview: %1", index.data(TemplatesModel::CommentRole).toString())); } } void TemplateSelectionPagePrivate::previewTemplate(const QString& file) { SourceFileTemplate fileTemplate(file); if (!fileTemplate.isValid() || fileTemplate.outputFiles().isEmpty()) { return; } TemplatePreviewRenderer renderer; renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); QTemporaryDir dir; QUrl base = QUrl::fromLocalFile(dir.path() + QLatin1Char('/')); QHash fileUrls; foreach(const SourceFileTemplate::OutputFile& out, fileTemplate.outputFiles()) { QUrl url = base.resolved(QUrl(renderer.render(out.outputName))); fileUrls.insert(out.identifier, url); } DocumentChangeSet changes = renderer.renderFileTemplate(fileTemplate, base, fileUrls); changes.setActivationPolicy(DocumentChangeSet::DoNotActivate); changes.setUpdateHandling(DocumentChangeSet::NoUpdate); DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); if (!result) { return; } int idx = 0; foreach(const SourceFileTemplate::OutputFile& out, fileTemplate.outputFiles()) { - TemplatePreview* preview = 0; + TemplatePreview* preview = nullptr; if (ui->tabWidget->count() > idx) { // reuse existing tab preview = qobject_cast(ui->tabWidget->widget(idx)); ui->tabWidget->setTabText(idx, out.label); Q_ASSERT(preview); } else { // create new tabs on demand preview = new TemplatePreview(page); ui->tabWidget->addTab(preview, out.label); } preview->document()->openUrl(fileUrls.value(out.identifier)); ++idx; } // remove superfluous tabs from last time while (ui->tabWidget->count() > fileUrls.size()) { delete ui->tabWidget->widget(fileUrls.size()); } return; } void TemplateSelectionPagePrivate::getMoreClicked() { KNS3::DownloadDialog(QStringLiteral("kdevfiletemplates.knsrc"), ui->view).exec(); model->refresh(); } void TemplateSelectionPagePrivate::loadFileClicked() { const QStringList filters{ QStringLiteral("application/x-desktop"), QStringLiteral("application/x-bzip-compressed-tar"), QStringLiteral("application/zip") }; QFileDialog dlg(page); dlg.setMimeTypeFilters(filters); dlg.setFileMode(QFileDialog::ExistingFiles); if (!dlg.exec()) { return; } foreach(const QString& fileName, dlg.selectedFiles()) { QString destination = model->loadTemplateFile(fileName); QModelIndexList indexes = model->templateIndexes(destination); int n = indexes.size(); if (n > 1) { ui->view->setCurrentIndex(indexes[1]); } } } void TemplateSelectionPage::saveConfig() { KSharedConfigPtr config; if (IProject* project = ICore::self()->projectController()->findProjectForUrl(d->assistant->baseUrl())) { config = project->projectConfiguration(); } else { config = ICore::self()->activeSession()->config(); } KConfigGroup group(config, FileTemplatesGroup); group.writeEntry(LastUsedTemplateEntry, d->selectedTemplate); group.sync(); } TemplateSelectionPage::TemplateSelectionPage(TemplateClassAssistant* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new TemplateSelectionPagePrivate(this)) { d->assistant = parent; d->ui = new Ui::TemplateSelection; d->ui->setupUi(this); d->model = new TemplatesModel(QStringLiteral("kdevfiletemplates"), this); d->model->refresh(); d->ui->view->setLevels(3); d->ui->view->setHeaderLabels(QStringList() << i18n("Language") << i18n("Framework") << i18n("Template")); d->ui->view->setModel(d->model); connect(d->ui->view, &MultiLevelListView::currentIndexChanged, this, [&] (const QModelIndex& index) { d->currentTemplateChanged(index); }); QModelIndex templateIndex = d->model->index(0, 0); while (templateIndex.child(0, 0).isValid()) { templateIndex = templateIndex.child(0, 0); } KSharedConfigPtr config; if (IProject* project = ICore::self()->projectController()->findProjectForUrl(d->assistant->baseUrl())) { config = project->projectConfiguration(); } else { config = ICore::self()->activeSession()->config(); } KConfigGroup group(config, FileTemplatesGroup); QString lastTemplate = group.readEntry(LastUsedTemplateEntry); QModelIndexList indexes = d->model->match(d->model->index(0, 0), TemplatesModel::DescriptionFileRole, lastTemplate, 1, Qt::MatchRecursive); if (!indexes.isEmpty()) { templateIndex = indexes.first(); } d->ui->view->setCurrentIndex(templateIndex); QPushButton* getMoreButton = new QPushButton(i18n("Get More Templates..."), d->ui->view); getMoreButton->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff"))); connect (getMoreButton, &QPushButton::clicked, this, [&] { d->getMoreClicked(); }); d->ui->view->addWidget(0, getMoreButton); QPushButton* loadButton = new QPushButton(QIcon::fromTheme(QStringLiteral("application-x-archive")), i18n("Load Template From File"), d->ui->view); connect (loadButton, &QPushButton::clicked, this, [&] { d->loadFileClicked(); }); d->ui->view->addWidget(0, loadButton); d->ui->view->setContentsMargins(0, 0, 0, 0); } TemplateSelectionPage::~TemplateSelectionPage() { delete d->ui; delete d; } QSize TemplateSelectionPage::minimumSizeHint() const { return QSize(400, 600); } QString TemplateSelectionPage::selectedTemplate() const { return d->selectedTemplate; } #include "moc_templateselectionpage.cpp" diff --git a/plugins/git/gitplugin.cpp b/plugins/git/gitplugin.cpp index b8666b56e5..02a3eab137 100644 --- a/plugins/git/gitplugin.cpp +++ b/plugins/git/gitplugin.cpp @@ -1,1511 +1,1511 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2009 Hugo Parente Lima * * Copyright 2010 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "gitplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gitclonejob.h" #include #include #include "stashmanagerdialog.h" #include #include #include #include #include #include #include "gitjob.h" #include "gitmessagehighlighter.h" #include "gitplugincheckinrepositoryjob.h" #include "gitnameemaildialog.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_GIT, "kdevplatform.plugins.git") using namespace KDevelop; QVariant runSynchronously(KDevelop::VcsJob* job) { QVariant ret; if(job->exec() && job->status()==KDevelop::VcsJob::JobSucceeded) { ret = job->fetchResults(); } delete job; return ret; } namespace { QDir dotGitDirectory(const QUrl& dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(finfo.filePath()): finfo.absoluteDir(); static const QString gitDir = QStringLiteral(".git"); while (!dir.exists(gitDir) && dir.cdUp()) {} // cdUp, until there is a sub-directory called .git if (dir.isRoot()) { qCWarning(PLUGIN_GIT) << "couldn't find the git root for" << dirPath; } return dir; } /** * Whenever a directory is provided, change it for all the files in it but not inner directories, * that way we make sure we won't get into recursion, */ static QList preventRecursion(const QList& urls) { QList ret; foreach(const QUrl& url, urls) { QDir d(url.toLocalFile()); if(d.exists()) { QStringList entries = d.entryList(QDir::Files | QDir::NoDotAndDotDot); foreach(const QString& entry, entries) { QUrl entryUrl = QUrl::fromLocalFile(d.absoluteFilePath(entry)); ret += entryUrl; } } else ret += url; } return ret; } QString toRevisionName(const KDevelop::VcsRevision& rev, QString currentRevision=QString()) { switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return QStringLiteral("^HEAD"); case VcsRevision::Base: return QString(); case VcsRevision::Working: return QString(); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); return currentRevision + "^1"; case VcsRevision::Start: return QString(); case VcsRevision::UserSpecialType: //Not used Q_ASSERT(false && "i don't know how to do that"); } break; case VcsRevision::GlobalNumber: return rev.revisionValue().toString(); case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserSpecialType: Q_ASSERT(false); } return QString(); } QString revisionInterval(const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) { QString ret; if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Start) //if we want it to the beginning just put the revisionInterval ret = toRevisionName(limit, QString()); else { QString dst = toRevisionName(limit); if(dst.isEmpty()) ret = dst; else { QString src = toRevisionName(rev, dst); if(src.isEmpty()) ret = src; else ret = src+".."+dst; } } return ret; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } QDir urlDir(const QList& urls) { return urlDir(urls.first()); } //TODO: could be improved } GitPlugin::GitPlugin( QObject *parent, const QVariantList & ) : DistributedVersionControlPlugin(parent, QStringLiteral("kdevgit")), m_oldVersion(false), m_usePrefix(true) { if (QStandardPaths::findExecutable(QStringLiteral("git")).isEmpty()) { setErrorDescription(i18n("Unable to find git executable. Is it installed on the system?")); return; } KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBasicVersionControl ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IDistributedVersionControl ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBranchingVersionControl ) setObjectName(QStringLiteral("Git")); DVcsJob* versionJob = new DVcsJob(QDir::tempPath(), this, KDevelop::OutputJob::Silent); *versionJob << "git" << "--version"; connect(versionJob, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitVersionOutput); ICore::self()->runController()->registerJob(versionJob); m_watcher = new KDirWatch(this); connect(m_watcher, &KDirWatch::dirty, this, &GitPlugin::fileChanged); connect(m_watcher, &KDirWatch::created, this, &GitPlugin::fileChanged); } GitPlugin::~GitPlugin() {} bool emptyOutput(DVcsJob* job) { QScopedPointer _job(job); if(job->exec() && job->status()==VcsJob::JobSucceeded) return job->rawOutput().trimmed().isEmpty(); return false; } bool GitPlugin::hasStashes(const QDir& repository) { return !emptyOutput(gitStash(repository, QStringList(QStringLiteral("list")), KDevelop::OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& d) { return !emptyOutput(lsFiles(d, QStringList(QStringLiteral("-m")), OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& repo, const QUrl& file) { return !emptyOutput(lsFiles(repo, QStringList() << QStringLiteral("-m") << file.path(), OutputJob::Silent)); } void GitPlugin::additionalMenuEntries(QMenu* menu, const QList& urls) { m_urls = urls; QDir dir=urlDir(urls); bool hasSt = hasStashes(dir); menu->addSeparator()->setText(i18n("Git Stashes")); menu->addAction(i18n("Stash Manager"), this, SLOT(ctxStashManager()))->setEnabled(hasSt); menu->addAction(i18n("Push Stash"), this, SLOT(ctxPushStash())); menu->addAction(i18n("Pop Stash"), this, SLOT(ctxPopStash()))->setEnabled(hasSt); } void GitPlugin::ctxPushStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxPopStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(QStringLiteral("pop")), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxStashManager() { - QPointer d = new StashManagerDialog(urlDir(m_urls), this, 0); + QPointer d = new StashManagerDialog(urlDir(m_urls), this, nullptr); d->exec(); delete d; } DVcsJob* GitPlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity=OutputJob::Verbose) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } QString GitPlugin::name() const { return QStringLiteral("Git"); } QUrl GitPlugin::repositoryRoot(const QUrl& path) { return QUrl::fromLocalFile(dotGitDirectory(path).absolutePath()); } bool GitPlugin::isValidDirectory(const QUrl & dirPath) { QDir dir=dotGitDirectory(dirPath); return dir.exists(QStringLiteral(".git/HEAD")); } bool GitPlugin::isVersionControlled(const QUrl &path) { QFileInfo fsObject(path.toLocalFile()); if (!fsObject.exists()) { return false; } if (fsObject.isDir()) { return isValidDirectory(path); } QString filename = fsObject.fileName(); QStringList otherFiles = getLsFiles(fsObject.dir(), QStringList(QStringLiteral("--")) << filename, KDevelop::OutputJob::Silent); return !otherFiles.empty(); } VcsJob* GitPlugin::init(const QUrl &directory) { DVcsJob* job = new DVcsJob(urlDir(directory), this); job->setType(VcsJob::Import); *job << "git" << "init"; return job; } VcsJob* GitPlugin::createWorkingCopy(const KDevelop::VcsLocation & source, const QUrl& dest, KDevelop::IBasicVersionControl::RecursionMode) { DVcsJob* job = new GitCloneJob(urlDir(dest), this); job->setType(VcsJob::Import); *job << "git" << "clone" << "--progress" << "--" << source.localUrl().url() << dest; return job; } VcsJob* GitPlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Add); *job << "git" << "add" << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } KDevelop::VcsJob* GitPlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(urlDir(localLocations), this, OutputJob::Silent); job->setType(VcsJob::Status); if(m_oldVersion) { *job << "git" << "ls-files" << "-t" << "-m" << "-c" << "-o" << "-d" << "-k" << "--directory"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput_old); } else { *job << "git" << "status" << "--porcelain"; job->setIgnoreError(true); connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput); } *job << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } VcsJob* GitPlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, VcsDiff::Type /*type*/, IBasicVersionControl::RecursionMode recursion) { //TODO: control different types DVcsJob* job = new GitJob(dotGitDirectory(fileOrDirectory), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Diff); *job << "git" << "diff" << "--no-color" << "--no-ext-diff"; if (!usePrefix()) { // KDE's ReviewBoard now requires p1 patchfiles, so `git diff --no-prefix` to generate p0 patches // has become optional. *job << "--no-prefix"; } if(srcRevision.revisionType()==VcsRevision::Special && dstRevision.revisionType()==VcsRevision::Special && srcRevision.specialType()==VcsRevision::Base && dstRevision.specialType()==VcsRevision::Working) *job << "HEAD"; else { QString revstr = revisionInterval(srcRevision, dstRevision); if(!revstr.isEmpty()) *job << revstr; } *job << "--"; if (recursion == IBasicVersionControl::Recursive) { *job << fileOrDirectory; } else { *job << preventRecursion(QList() << fileOrDirectory); } connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitDiffOutput); return job; } VcsJob* GitPlugin::revert(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { if(localLocations.isEmpty() ) return errorsFound(i18n("Could not revert changes"), OutputJob::Verbose); QDir repo = urlDir(repositoryRoot(localLocations.first())); QString modified; for (const auto& file: localLocations) { if (hasModifications(repo, file)) { modified.append(file.toDisplayString(QUrl::PreferLocalFile) + "
"); } } if (!modified.isEmpty()) { auto res = KMessageBox::questionYesNo(nullptr, i18n("The following files have uncommited changes, " "which will be lost. Continue?") + "

" + modified); if (res != KMessageBox::Yes) { return errorsFound(QString(), OutputJob::Silent); } } DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Revert); *job << "git" << "checkout" << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } //TODO: git doesn't like empty messages, but "KDevelop didn't provide any message, it may be a bug" looks ugly... //If no files specified then commit already added files VcsJob* GitPlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); const QDir dir = dotGitDirectory(localLocations.front()); if (!ensureValidGitIdentity(dir)) { return errorsFound(i18n("Email or name for Git not specified")); } DVcsJob* job = new DVcsJob(dir, this); job->setType(VcsJob::Commit); QList files = (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); addNotVersionedFiles(dir, files); *job << "git" << "commit" << "-m" << message; *job << "--" << files; return job; } bool GitPlugin::ensureValidGitIdentity(const QDir& dir) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath()); const QString name = readConfigOption(url, QStringLiteral("user.name")); const QString email = readConfigOption(url, QStringLiteral("user.email")); if (!email.isEmpty() && !name.isEmpty()) { return true; // already okay } GitNameEmailDialog dialog; dialog.setName(name); dialog.setEmail(email); if (!dialog.exec()) { return false; } runSynchronously(setConfigOption(url, QStringLiteral("user.name"), dialog.name(), dialog.isGlobal())); runSynchronously(setConfigOption(url, QStringLiteral("user.email"), dialog.email(), dialog.isGlobal())); return true; } void GitPlugin::addNotVersionedFiles(const QDir& dir, const QList& files) { QStringList otherStr = getLsFiles(dir, QStringList() << QStringLiteral("--others"), KDevelop::OutputJob::Silent); QList toadd, otherFiles; foreach(const QString& file, otherStr) { QUrl v = QUrl::fromLocalFile(dir.absoluteFilePath(file)); otherFiles += v; } //We add the files that are not versioned foreach(const QUrl& file, files) { if(otherFiles.contains(file) && QFileInfo(file.toLocalFile()).isFile()) toadd += file; } if(!toadd.isEmpty()) { VcsJob* job = add(toadd); job->exec(); } } bool isEmptyDirStructure(const QDir &dir) { foreach (const QFileInfo &i, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) { if (i.isDir()) { if (!isEmptyDirStructure(QDir(i.filePath()))) return false; } else if (i.isFile()) { return false; } } return true; } VcsJob* GitPlugin::remove(const QList& files) { if (files.isEmpty()) return errorsFound(i18n("No files to remove")); QDir dotGitDir = dotGitDirectory(files.front()); QList files_(files); QMutableListIterator i(files_); while (i.hasNext()) { QUrl file = i.next(); QFileInfo fileInfo(file.toLocalFile()); QStringList otherStr = getLsFiles(dotGitDir, QStringList() << QStringLiteral("--others") << QStringLiteral("--") << file.toLocalFile(), KDevelop::OutputJob::Silent); if(!otherStr.isEmpty()) { //remove files not under version control QList otherFiles; foreach(const QString &f, otherStr) { otherFiles << QUrl::fromLocalFile(dotGitDir.path()+'/'+f); } if (fileInfo.isFile()) { //if it's an unversioned file we are done, don't use git rm on it i.remove(); } auto trashJob = KIO::trash(otherFiles); trashJob->exec(); qCDebug(PLUGIN_GIT) << "other files" << otherFiles; } if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(file.toLocalFile()))) { //remove empty folders, git doesn't do that auto trashJob = KIO::trash(file); trashJob->exec(); qCDebug(PLUGIN_GIT) << "empty folder, removing" << file; //we already deleted it, don't use git rm on it i.remove(); } } } - if (files_.isEmpty()) return 0; + if (files_.isEmpty()) return nullptr; DVcsJob* job = new GitJob(dotGitDir, this); job->setType(VcsJob::Remove); // git refuses to delete files with local modifications // use --force to overcome this *job << "git" << "rm" << "-r" << "--force"; *job << "--" << files_; return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& src, const KDevelop::VcsRevision& dst) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString rev = revisionInterval(dst, src); if(!rev.isEmpty()) *job << rev; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long int limit) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) *job << revStr; if(limit>0) *job << QStringLiteral("-%1").arg(limit); *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } KDevelop::VcsJob* GitPlugin::annotate(const QUrl &localLocation, const KDevelop::VcsRevision&) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Annotate); *job << "git" << "blame" << "--porcelain" << "-w"; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBlameOutput); return job; } void GitPlugin::parseGitBlameOutput(DVcsJob *job) { QVariantList results; - VcsAnnotationLine* annotation = 0; + VcsAnnotationLine* annotation = nullptr; const auto output = job->output(); const auto lines = output.splitRef('\n'); bool skipNext=false; QMap definedRevisions; for(QVector::const_iterator it=lines.constBegin(), itEnd=lines.constEnd(); it!=itEnd; ++it) { if(skipNext) { skipNext=false; results += qVariantFromValue(*annotation); continue; } if(it->isEmpty()) continue; QStringRef name = it->left(it->indexOf(' ')); QStringRef value = it->right(it->size()-name.size()-1); if(name==QLatin1String("author")) annotation->setAuthor(value.toString()); else if(name==QLatin1String("author-mail")) {} //TODO: do smth with the e-mail? else if(name==QLatin1String("author-tz")) {} //TODO: does it really matter? else if(name==QLatin1String("author-time")) annotation->setDate(QDateTime::fromTime_t(value.toUInt())); else if(name==QLatin1String("summary")) annotation->setCommitMessage(value.toString()); else if(name.startsWith(QStringLiteral("committer"))) {} //We will just store the authors else if(name==QLatin1String("previous")) {} //We don't need that either else if(name==QLatin1String("filename")) { skipNext=true; } else if(name==QLatin1String("boundary")) { definedRevisions.insert(QStringLiteral("boundary"), VcsAnnotationLine()); } else { const auto values = value.split(' '); VcsRevision rev; rev.setRevisionValue(name.left(8).toString(), KDevelop::VcsRevision::GlobalNumber); skipNext = definedRevisions.contains(name.toString()); if(!skipNext) definedRevisions.insert(name.toString(), VcsAnnotationLine()); annotation = &definedRevisions[name.toString()]; annotation->setLineNumber(values[1].toInt() - 1); annotation->setRevision(rev); } } job->setResults(results); } DVcsJob* GitPlugin::lsFiles(const QDir &repository, const QStringList &args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "ls-files" << args; return job; } DVcsJob* GitPlugin::gitStash(const QDir& repository, const QStringList& args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "stash" << args; return job; } VcsJob* GitPlugin::tag(const QUrl& repository, const QString& commitMessage, const VcsRevision& rev, const QString& tagName) { DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "tag" << "-m" << commitMessage << tagName; if(rev.revisionValue().isValid()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::switchBranch(const QUrl &repository, const QString &branch) { QDir d=urlDir(repository); - if(hasModifications(d) && KMessageBox::questionYesNo(0, i18n("There are pending changes, do you want to stash them first?"))==KMessageBox::Yes) { + if(hasModifications(d) && KMessageBox::questionYesNo(nullptr, i18n("There are pending changes, do you want to stash them first?"))==KMessageBox::Yes) { QScopedPointer stash(gitStash(d, QStringList(), KDevelop::OutputJob::Verbose)); stash->exec(); } DVcsJob* job = new DVcsJob(d, this); *job << "git" << "checkout" << branch; return job; } VcsJob* GitPlugin::branch(const QUrl& repository, const KDevelop::VcsRevision& rev, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "branch" << "--" << branchName; if(!rev.prettyValue().isEmpty()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::deleteBranch(const QUrl& repository, const QString& branchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-D" << branchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::renameBranch(const QUrl& repository, const QString& oldBranchName, const QString& newBranchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-m" << newBranchName << oldBranchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::mergeBranch(const QUrl& repository, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "merge" << branchName; return job; } VcsJob* GitPlugin::currentBranch(const QUrl& repository) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); job->setIgnoreError(true); *job << "git" << "symbolic-ref" << "-q" << "--short" << "HEAD"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } void GitPlugin::parseGitCurrentBranch(DVcsJob* job) { QString out = job->output().trimmed(); job->setResults(out); } VcsJob* GitPlugin::branches(const QUrl &repository) { DVcsJob* job=new DVcsJob(urlDir(repository)); *job << "git" << "branch" << "-a"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBranchOutput); return job; } void GitPlugin::parseGitBranchOutput(DVcsJob* job) { const auto output = job->output(); const auto branchListDirty = output.splitRef('\n', QString::SkipEmptyParts); QStringList branchList; foreach(const auto & branch, branchListDirty) { // Skip pointers to another branches (one example of this is "origin/HEAD -> origin/master"); // "git rev-list" chokes on these entries and we do not need duplicate branches altogether. if (branch.contains(QStringLiteral("->"))) continue; // Skip entries such as '(no branch)' if (branch.contains(QStringLiteral("(no branch)"))) continue; QStringRef name = branch; if (name.startsWith('*')) name = branch.right(branch.size()-2); branchList << name.trimmed().toString(); } job->setResults(branchList); } /* Few words about how this hardcore works: 1. get all commits (with --paretns) 2. select master (root) branch and get all unicial commits for branches (git-rev-list br2 ^master ^br3) 3. parse allCommits. While parsing set mask (columns state for every row) for BRANCH, INITIAL, CROSS, MERGE and INITIAL are also set in DVCScommit::setParents (depending on parents count) another setType(INITIAL) is used for "bottom/root/first" commits of branches 4. find and set merges, HEADS. It's an ittaration through all commits. - first we check if parent is from the same branch, if no then we go through all commits searching parent's index and set CROSS/HCROSS for rows (in 3 rows are set EMPTY after commit with parent from another tree met) - then we check branchesShas[i][0] to mark heads 4 can be a seporate function. TODO: All this porn require refactoring (rewriting is better)! It's a very dirty implementation. FIXME: 1. HEAD which is head has extra line to connect it with further commit 2. If you menrge branch2 to master, only new commits of branch2 will be visible (it's fine, but there will be extra merge rectangle in master. If there are no extra commits in branch2, but there are another branches, then the place for branch2 will be empty (instead of be used for branch3). 3. Commits that have additional commit-data (not only history merging, but changes to fix conflicts) are shown incorrectly */ QList GitPlugin::getAllCommits(const QString &repo) { initBranchHash(repo); QStringList args; args << QStringLiteral("--all") << QStringLiteral("--pretty") << QStringLiteral("--parents"); QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); static QRegExp rx_com("commit \\w{40,40}"); QListcommitList; DVcsEvent item; //used to keep where we have empty/cross/branch entry //true if it's an active branch (then cross or branch) and false if not QVector additionalFlags(branchesShas.count()); additionalFlags.fill(false); //parse output for(int i = 0; i < commits.count(); ++i) { if (commits[i].contains(rx_com)) { qCDebug(PLUGIN_GIT) << "commit found in " << commits[i]; item.setCommit(commits[i].section(' ', 1, 1).trimmed()); // qCDebug(PLUGIN_GIT) << "commit is: " << commits[i].section(' ', 1); QStringList parents; QString parent = commits[i].section(' ', 2); int section = 2; while (!parent.isEmpty()) { /* qCDebug(PLUGIN_GIT) << "Parent is: " << parent;*/ parents.append(parent.trimmed()); section++; parent = commits[i].section(' ', section); } item.setParents(parents); //Avoid Merge string while (!commits[i].contains(QStringLiteral("Author: "))) ++i; item.setAuthor(commits[i].section(QStringLiteral("Author: "), 1).trimmed()); // qCDebug(PLUGIN_GIT) << "author is: " << commits[i].section("Author: ", 1); item.setDate(commits[++i].section(QStringLiteral("Date: "), 1).trimmed()); // qCDebug(PLUGIN_GIT) << "date is: " << commits[i].section("Date: ", 1); QString log; i++; //next line! while (i < commits.count() && !commits[i].contains(rx_com)) log += commits[i++]; --i; //while took commit line item.setLog(log.trimmed()); // qCDebug(PLUGIN_GIT) << "log is: " << log; //mask is used in CommitViewDelegate to understand what we should draw for each branch QList mask; //set mask (properties for each graph column in row) for(int i = 0; i < branchesShas.count(); ++i) { qCDebug(PLUGIN_GIT)<<"commit: " << item.getCommit(); if (branchesShas[i].contains(item.getCommit())) { mask.append(item.getType()); //we set type in setParents //check if parent from the same branch, if not then we have found a root of the branch //and will use empty column for all futher (from top to bottom) revisions //FIXME: we should set CROSS between parent and child (and do it when find merge point) additionalFlags[i] = false; foreach(const QString &sha, item.getParents()) { if (branchesShas[i].contains(sha)) additionalFlags[i] = true; } if (additionalFlags[i] == false) item.setType(DVcsEvent::INITIAL); //hasn't parents from the same branch, used in drawing } else { if (additionalFlags[i] == false) mask.append(DVcsEvent::EMPTY); else mask.append(DVcsEvent::CROSS); } qCDebug(PLUGIN_GIT) << "mask " << i << "is " << mask[i]; } item.setProperties(mask); commitList.append(item); } } //find and set merges, HEADS, require refactoring! for(QList::iterator iter = commitList.begin(); iter != commitList.end(); ++iter) { QStringList parents = iter->getParents(); //we need only only child branches if (parents.count() != 1) break; QString parent = parents[0]; QString commit = iter->getCommit(); bool parent_checked = false; int heads_checked = 0; for(int i = 0; i < branchesShas.count(); ++i) { //check parent if (branchesShas[i].contains(commit)) { if (!branchesShas[i].contains(parent)) { //parent and child are not in same branch //since it is list, than parent has i+1 index //set CROSS and HCROSS for(QList::iterator f_iter = iter; f_iter != commitList.end(); ++f_iter) { if (parent == f_iter->getCommit()) { for(int j = 0; j < i; ++j) { if(branchesShas[j].contains(parent)) f_iter->setPropetry(j, DVcsEvent::MERGE); else f_iter->setPropetry(j, DVcsEvent::HCROSS); } f_iter->setType(DVcsEvent::MERGE); f_iter->setPropetry(i, DVcsEvent::MERGE_RIGHT); qCDebug(PLUGIN_GIT) << parent << " is parent of " << commit; qCDebug(PLUGIN_GIT) << f_iter->getCommit() << " is merge"; parent_checked = true; break; } else f_iter->setPropetry(i, DVcsEvent::CROSS); } } } //mark HEADs if (!branchesShas[i].empty() && commit == branchesShas[i][0]) { iter->setType(DVcsEvent::HEAD); iter->setPropetry(i, DVcsEvent::HEAD); heads_checked++; qCDebug(PLUGIN_GIT) << "HEAD found"; } //some optimization if (heads_checked == branchesShas.count() && parent_checked) break; } } return commitList; } void GitPlugin::initBranchHash(const QString &repo) { const QUrl repoUrl = QUrl::fromLocalFile(repo); QStringList gitBranches = runSynchronously(branches(repoUrl)).toStringList(); qCDebug(PLUGIN_GIT) << "BRANCHES: " << gitBranches; //Now root branch is the current branch. In future it should be the longest branch //other commitLists are got with git-rev-lits branch ^br1 ^ br2 QString root = runSynchronously(currentBranch(repoUrl)).toString(); QScopedPointer job(gitRevList(repo, QStringList(root))); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); foreach(const QString &branch, gitBranches) { if (branch == root) continue; QStringList args(branch); foreach(const QString &branch_arg, gitBranches) { if (branch_arg != branch) //man gitRevList for '^' args<<'^' + branch_arg; } QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); } } //Actually we can just copy the output without parsing. So it's a kind of draft for future void GitPlugin::parseLogOutput(const DVcsJob * job, QList& commits) const { // static QRegExp rx_sep( "[-=]+" ); // static QRegExp rx_date( "date:\\s+([^;]*);\\s+author:\\s+([^;]*).*" ); static QRegularExpression rx_com( "commit \\w{1,40}" ); const auto output = job->output(); const auto lines = output.splitRef('\n', QString::SkipEmptyParts); DVcsEvent item; QString commitLog; for (int i=0; i= 0x050500 if (rx_com.match(lines[i]).hasMatch()) { #else if (rx_com.match(lines[i].toString()).hasMatch()) { #endif // qCDebug(PLUGIN_GIT) << "MATCH COMMIT"; item.setCommit(lines[++i].toString()); item.setAuthor(lines[++i].toString()); item.setDate(lines[++i].toString()); item.setLog(commitLog); commits.append(item); } else { //FIXME: add this in a loop to the if, like in getAllCommits() commitLog += lines[i].toString() +'\n'; } } } VcsItemEvent::Actions actionsFromString(char c) { switch(c) { case 'A': return VcsItemEvent::Added; case 'D': return VcsItemEvent::Deleted; case 'R': return VcsItemEvent::Replaced; case 'M': return VcsItemEvent::Modified; } return VcsItemEvent::Modified; } void GitPlugin::parseGitLogOutput(DVcsJob * job) { static QRegExp commitRegex( "^commit (\\w{8})\\w{32}" ); static QRegExp infoRegex( "^(\\w+):(.*)" ); static QRegExp modificationsRegex("^([A-Z])[0-9]*\t([^\t]+)\t?(.*)", Qt::CaseSensitive, QRegExp::RegExp2); //R099 plugins/git/kdevgit.desktop plugins/git/kdevgit.desktop.cmake //M plugins/grepview/CMakeLists.txt QList commits; QString contents = job->output(); // check if git-log returned anything if (contents.isEmpty()) { job->setResults(commits); // empty list return; } // start parsing the output QTextStream s(&contents); VcsEvent item; QString message; bool pushCommit = false; while (!s.atEnd()) { QString line = s.readLine(); if (commitRegex.exactMatch(line)) { if (pushCommit) { item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); item.setItems(QList()); } else { pushCommit = true; } VcsRevision rev; rev.setRevisionValue(commitRegex.cap(1), KDevelop::VcsRevision::GlobalNumber); item.setRevision(rev); message.clear(); } else if (infoRegex.exactMatch(line)) { QString cap1 = infoRegex.cap(1); if (cap1 == QLatin1String("Author")) { item.setAuthor(infoRegex.cap(2).trimmed()); } else if (cap1 == QLatin1String("Date")) { item.setDate(QDateTime::fromTime_t(infoRegex.cap(2).trimmed().split(' ')[0].toUInt())); } } else if (modificationsRegex.exactMatch(line)) { VcsItemEvent::Actions a = actionsFromString(modificationsRegex.cap(1).at(0).toLatin1()); QString filenameA = modificationsRegex.cap(2); VcsItemEvent itemEvent; itemEvent.setActions(a); itemEvent.setRepositoryLocation(filenameA); if(a==VcsItemEvent::Replaced) { QString filenameB = modificationsRegex.cap(3); itemEvent.setRepositoryCopySourceLocation(filenameB); } item.addItem(itemEvent); } else if (line.startsWith(QLatin1String(" "))) { message += line.remove(0, 4); message += '\n'; } } item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); job->setResults(commits); } void GitPlugin::parseGitDiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); diff.setBaseDiff(repositoryRoot(QUrl::fromLocalFile(job->directory().absolutePath()))); diff.setDepth(usePrefix()? 1 : 0); job->setResults(qVariantFromValue(diff)); } static VcsStatusInfo::State lsfilesToState(char id) { switch(id) { case 'H': return VcsStatusInfo::ItemUpToDate; //Cached case 'S': return VcsStatusInfo::ItemUpToDate; //Skip work tree case 'M': return VcsStatusInfo::ItemHasConflicts; //unmerged case 'R': return VcsStatusInfo::ItemDeleted; //removed/deleted case 'C': return VcsStatusInfo::ItemModified; //modified/changed case 'K': return VcsStatusInfo::ItemDeleted; //to be killed case '?': return VcsStatusInfo::ItemUnknown; //other } Q_ASSERT(false); return VcsStatusInfo::ItemUnknown; } void GitPlugin::parseGitStatusOutput_old(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QDir dir = job->directory(); QMap allStatus; foreach(const QString& line, outputLines) { VcsStatusInfo::State status = lsfilesToState(line[0].toLatin1()); QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(line.right(line.size()-2))); allStatus[url] = status; } QVariantList statuses; QMap< QUrl, VcsStatusInfo::State >::const_iterator it = allStatus.constBegin(), itEnd=allStatus.constEnd(); for(; it!=itEnd; ++it) { VcsStatusInfo status; status.setUrl(it.key()); status.setState(it.value()); statuses.append(qVariantFromValue(status)); } job->setResults(statuses); } void GitPlugin::parseGitStatusOutput(DVcsJob* job) { const auto output = job->output(); const auto outputLines = output.splitRef('\n', QString::SkipEmptyParts); QDir workingDir = job->directory(); QDir dotGit = dotGitDirectory(QUrl::fromLocalFile(workingDir.absolutePath())); QVariantList statuses; QList processedFiles; foreach(const QStringRef& line, outputLines) { //every line is 2 chars for the status, 1 space then the file desc QStringRef curr=line.right(line.size()-3); QStringRef state = line.left(2); int arrow = curr.indexOf(QStringLiteral(" -> ")); if(arrow>=0) { VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.toString().left(arrow)))); status.setState(VcsStatusInfo::ItemDeleted); statuses.append(qVariantFromValue(status)); processedFiles += status.url(); curr = curr.mid(arrow+4); } if(curr.startsWith('\"') && curr.endsWith('\"')) { //if the path is quoted, unquote curr = curr.mid(1, curr.size()-2); } VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.toString()))); status.setState(messageToState(state)); processedFiles.append(status.url()); qCDebug(PLUGIN_GIT) << "Checking git status for " << line << curr << status.state(); statuses.append(qVariantFromValue(status)); } QStringList paths; QStringList oldcmd=job->dvcsCommand(); QStringList::const_iterator it=oldcmd.constBegin()+oldcmd.indexOf(QStringLiteral("--"))+1, itEnd=oldcmd.constEnd(); for(; it!=itEnd; ++it) paths += *it; //here we add the already up to date files QStringList files = getLsFiles(job->directory(), QStringList() << QStringLiteral("-c") << QStringLiteral("--") << paths, OutputJob::Silent); foreach(const QString& file, files) { QUrl fileUrl = QUrl::fromLocalFile(workingDir.absoluteFilePath(file)); if(!processedFiles.contains(fileUrl)) { VcsStatusInfo status; status.setUrl(fileUrl); status.setState(VcsStatusInfo::ItemUpToDate); statuses.append(qVariantFromValue(status)); } } job->setResults(statuses); } void GitPlugin::parseGitVersionOutput(DVcsJob* job) { const auto output = job->output().trimmed(); auto versionString = output.midRef(output.lastIndexOf(' ')).split('.'); static const QList minimumVersion = QList() << 1 << 7; qCDebug(PLUGIN_GIT) << "checking git version" << versionString << "against" << minimumVersion; m_oldVersion = false; if (versionString.size() < minimumVersion.size()) { m_oldVersion = true; qCWarning(PLUGIN_GIT) << "invalid git version string:" << job->output().trimmed(); return; } foreach(int num, minimumVersion) { QStringRef curr = versionString.takeFirst(); int valcurr = curr.toInt(); if (valcurr < num) { m_oldVersion = true; break; } if (valcurr > num) { m_oldVersion = false; break; } } qCDebug(PLUGIN_GIT) << "the current git version is old: " << m_oldVersion; } QStringList GitPlugin::getLsFiles(const QDir &directory, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(lsFiles(directory, args, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) return job->output().split('\n', QString::SkipEmptyParts); return QStringList(); } DVcsJob* GitPlugin::gitRevParse(const QString &repository, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(QDir(repository), this, verbosity); *job << "git" << "rev-parse" << args; return job; } DVcsJob* GitPlugin::gitRevList(const QString& directory, const QStringList& args) { DVcsJob* job = new DVcsJob(urlDir(QUrl::fromLocalFile(directory)), this, KDevelop::OutputJob::Silent); { *job << "git" << "rev-list" << args; return job; } } VcsStatusInfo::State GitPlugin::messageToState(const QStringRef& msg) { Q_ASSERT(msg.size()==1 || msg.size()==2); VcsStatusInfo::State ret = VcsStatusInfo::ItemUnknown; if(msg.contains('U') || msg == QLatin1String("AA") || msg == QLatin1String("DD")) ret = VcsStatusInfo::ItemHasConflicts; else switch(msg.at(0).toLatin1()) { case 'M': ret = VcsStatusInfo::ItemModified; break; case 'A': ret = VcsStatusInfo::ItemAdded; break; case 'R': ret = VcsStatusInfo::ItemModified; break; case 'C': ret = VcsStatusInfo::ItemHasConflicts; break; case ' ': ret = msg.at(1) == 'M' ? VcsStatusInfo::ItemModified : VcsStatusInfo::ItemDeleted; break; case 'D': ret = VcsStatusInfo::ItemDeleted; break; case '?': ret = VcsStatusInfo::ItemUnknown; break; default: qCDebug(PLUGIN_GIT) << "Git status not identified:" << msg; break; } return ret; } StandardJob::StandardJob(IPlugin* parent, KJob* job, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity) , m_job(job) , m_plugin(parent) , m_status(JobNotStarted) {} void StandardJob::start() { connect(m_job, &KJob::result, this, &StandardJob::result); m_job->start(); m_status=JobRunning; } void StandardJob::result(KJob* job) { m_status=job->error() == 0? JobSucceeded : JobFailed; emitResult(); } VcsJob* GitPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) { //TODO: Probably we should "git add" after return new StandardJob(this, KIO::copy(localLocationSrc, localLocationDstn), KDevelop::OutputJob::Silent); } VcsJob* GitPlugin::move(const QUrl& source, const QUrl& destination) { QDir dir = urlDir(source); QFileInfo fileInfo(source.toLocalFile()); if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(source.toLocalFile()))) { //move empty folder, git doesn't do that qCDebug(PLUGIN_GIT) << "empty folder" << source; return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } QStringList otherStr = getLsFiles(dir, QStringList() << QStringLiteral("--others") << QStringLiteral("--") << source.toLocalFile(), KDevelop::OutputJob::Silent); if(otherStr.isEmpty()) { DVcsJob* job = new DVcsJob(dir, this, KDevelop::OutputJob::Verbose); *job << "git" << "mv" << source.toLocalFile() << destination.toLocalFile(); return job; } else { return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } void GitPlugin::parseGitRepoLocationOutput(DVcsJob* job) { job->setResults(QVariant::fromValue(QUrl::fromLocalFile(job->output()))); } VcsJob* GitPlugin::repositoryLocation(const QUrl& localLocation) { DVcsJob* job = new DVcsJob(urlDir(localLocation), this); //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "config" << "remote.origin.url"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitRepoLocationOutput); return job; } VcsJob* GitPlugin::pull(const KDevelop::VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "pull"; if(!localOrRepoLocationSrc.localUrl().isEmpty()) *job << localOrRepoLocationSrc.localUrl().url(); return job; } VcsJob* GitPlugin::push(const QUrl& localRepositoryLocation, const KDevelop::VcsLocation& localOrRepoLocationDst) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "push"; if(!localOrRepoLocationDst.localUrl().isEmpty()) *job << localOrRepoLocationDst.localUrl().url(); return job; } VcsJob* GitPlugin::resolve(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { return add(localLocations, recursion); } VcsJob* GitPlugin::update(const QList& localLocations, const KDevelop::VcsRevision& rev, IBasicVersionControl::RecursionMode recursion) { if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Head) { return pull(VcsLocation(), localLocations.first()); } else { DVcsJob* job = new DVcsJob(urlDir(localLocations.first()), this); { //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "checkout" << rev.revisionValue().toString() << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } } } void GitPlugin::setupCommitMessageEditor(const QUrl& localLocation, KTextEdit* editor) const { new GitMessageHighlighter(editor); QFile mergeMsgFile(dotGitDirectory(localLocation).filePath(QStringLiteral(".git/MERGE_MSG"))); // Some limit on the file size should be set since whole content is going to be read into // the memory. 1Mb seems to be good value since it's rather strange to have so huge commit // message. static const qint64 maxMergeMsgFileSize = 1024*1024; if (mergeMsgFile.size() > maxMergeMsgFileSize || !mergeMsgFile.open(QIODevice::ReadOnly)) return; QString mergeMsg = QString::fromLocal8Bit(mergeMsgFile.read(maxMergeMsgFileSize)); editor->setPlainText(mergeMsg); } class GitVcsLocationWidget : public KDevelop::StandardVcsLocationWidget { Q_OBJECT public: - GitVcsLocationWidget(QWidget* parent = 0, Qt::WindowFlags f = 0) + GitVcsLocationWidget(QWidget* parent = nullptr, Qt::WindowFlags f = nullptr) : StandardVcsLocationWidget(parent, f) {} bool isCorrect() const override { return !url().isEmpty(); } }; KDevelop::VcsLocationWidget* GitPlugin::vcsLocation(QWidget* parent) const { return new GitVcsLocationWidget(parent); } void GitPlugin::registerRepositoryForCurrentBranchChanges(const QUrl& repository) { QDir dir = dotGitDirectory(repository); QString headFile = dir.absoluteFilePath(QStringLiteral(".git/HEAD")); m_watcher->addFile(headFile); } void GitPlugin::fileChanged(const QString& file) { Q_ASSERT(file.endsWith(QStringLiteral("HEAD"))); //SMTH/.git/HEAD -> SMTH/ const QUrl fileUrl = Path(file).parent().parent().toUrl(); //We need to delay the emitted signal, otherwise the branch hasn't change yet //and the repository is not functional m_branchesChange.append(fileUrl); QTimer::singleShot(1000, this, SLOT(delayedBranchChanged())); } void GitPlugin::delayedBranchChanged() { emit repositoryBranchChanged(m_branchesChange.takeFirst()); } CheckInRepositoryJob* GitPlugin::isInRepository(KTextEditor::Document* document) { CheckInRepositoryJob* job = new GitPluginCheckInRepositoryJob(document, repositoryRoot(document->url()).path()); job->start(); return job; } DVcsJob* GitPlugin::setConfigOption(const QUrl& repository, const QString& key, const QString& value, bool global) { auto job = new DVcsJob(urlDir(repository), this); QStringList args; args << "git" << "config"; if(global) args << "--global"; args << key << value; *job << args; return job; } QString GitPlugin::readConfigOption(const QUrl& repository, const QString& key) { QProcess exec; exec.setWorkingDirectory(urlDir(repository).absolutePath()); exec.start("git", QStringList() << "config" << "--get" << key); exec.waitForFinished(); return exec.readAllStandardOutput().trimmed(); } #include "gitplugin.moc" diff --git a/plugins/git/gitplugincheckinrepositoryjob.cpp b/plugins/git/gitplugincheckinrepositoryjob.cpp index df95848110..71fea79921 100644 --- a/plugins/git/gitplugincheckinrepositoryjob.cpp +++ b/plugins/git/gitplugincheckinrepositoryjob.cpp @@ -1,93 +1,93 @@ /*************************************************************************** * Copyright 2014 Sven Brauch * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "gitplugincheckinrepositoryjob.h" #include "debug.h" #include #include #include GitPluginCheckInRepositoryJob::GitPluginCheckInRepositoryJob(KTextEditor::Document* document, const QString& rootDirectory) : CheckInRepositoryJob(document) - , m_hashjob(0) - , m_findjob(0) + , m_hashjob(nullptr) + , m_findjob(nullptr) , m_rootDirectory(rootDirectory) {} void GitPluginCheckInRepositoryJob::start() { const QTextCodec* codec = QTextCodec::codecForName(document()->encoding().toLatin1()); const QDir workingDirectory(m_rootDirectory); if ( !workingDirectory.exists() ) { emit finished(false); return; } m_findjob = new QProcess(this); m_findjob->setWorkingDirectory(m_rootDirectory); m_hashjob = new QProcess(this); m_hashjob->setWorkingDirectory(m_rootDirectory); m_hashjob->setStandardOutputProcess(m_findjob); connect(m_findjob, static_cast(&QProcess::finished), this, &GitPluginCheckInRepositoryJob::repositoryQueryFinished); connect(m_hashjob, static_cast(&QProcess::error), this, &GitPluginCheckInRepositoryJob::processFailed); connect(m_findjob, static_cast(&QProcess::error), this, &GitPluginCheckInRepositoryJob::processFailed); m_hashjob->start(QStringLiteral("git"), QStringList() << QStringLiteral("hash-object") << QStringLiteral("--stdin")); m_findjob->start(QStringLiteral("git"), QStringList() << QStringLiteral("cat-file") << QStringLiteral("--batch-check")); for ( int i = 0; i < document()->lines(); i++ ) { m_hashjob->write(codec->fromUnicode(document()->line(i))); if ( i != document()->lines() - 1 ) { m_hashjob->write("\n"); } } m_hashjob->closeWriteChannel(); } GitPluginCheckInRepositoryJob::~GitPluginCheckInRepositoryJob() { if ( m_findjob && m_findjob->state() == QProcess::Running ) { m_findjob->kill(); } if ( m_hashjob && m_hashjob->state() == QProcess::Running ) { m_hashjob->kill(); } } void GitPluginCheckInRepositoryJob::processFailed(QProcess::ProcessError err) { qCDebug(PLUGIN_GIT) << "calling git failed with error:" << err; emit finished(false); } void GitPluginCheckInRepositoryJob::repositoryQueryFinished(int) { const QByteArray output = m_findjob->readAllStandardOutput(); bool requestSucceeded = output.contains(" blob "); emit finished(requestSucceeded); } diff --git a/plugins/grepview/grepoutputdelegate.cpp b/plugins/grepview/grepoutputdelegate.cpp index a4f48fcfb4..54bb9cb04f 100644 --- a/plugins/grepview/grepoutputdelegate.cpp +++ b/plugins/grepview/grepoutputdelegate.cpp @@ -1,181 +1,181 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright (C) 2007 Andreas Pakulat * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "grepoutputdelegate.h" #include "grepoutputmodel.h" #include #include #include #include #include #include #include #include #include #include -GrepOutputDelegate* GrepOutputDelegate::m_self = 0; +GrepOutputDelegate* GrepOutputDelegate::m_self = nullptr; GrepOutputDelegate* GrepOutputDelegate::self() { Q_ASSERT(m_self); return m_self; } GrepOutputDelegate::GrepOutputDelegate( QObject* parent ) : QStyledItemDelegate(parent) { Q_ASSERT(!m_self); m_self = this; } GrepOutputDelegate::~GrepOutputDelegate() { - m_self = 0; + m_self = nullptr; } QColor GrepOutputDelegate::blendColor(QColor color1, QColor color2, double blend) const { return QColor(color1.red() * blend + color2.red() * (1-blend), color1.green() * blend + color2.green() * (1-blend), color1.blue() * blend + color2.blue() * (1-blend)); } void GrepOutputDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { // there is no function in QString to left-trim. A call to remove this this regexp does the job static const QRegExp leftspaces("^\\s*", Qt::CaseSensitive, QRegExp::RegExp); // rich text component const GrepOutputModel *model = dynamic_cast(index.model()); const GrepOutputItem *item = dynamic_cast(model->itemFromIndex(index)); QStyleOptionViewItemV4 options = option; initStyleOption(&options, index); // building item representation QTextDocument doc; QTextCursor cur(&doc); QPalette::ColorGroup cg = options.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; QPalette::ColorRole cr = options.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; QTextCharFormat fmt = cur.charFormat(); fmt.setFont(options.font); if(item && item->isText()) { // Use custom manual highlighting const KTextEditor::Range rng = item->change()->m_range; // the line number appears grayed fmt.setForeground(options.palette.brush(QPalette::Disabled, cr)); cur.insertText(i18n("Line %1: ",item->lineNumber()), fmt); // switch to normal color fmt.setForeground(options.palette.brush(cg, cr)); cur.insertText(item->text().left(rng.start().column()).remove(leftspaces), fmt); fmt.setFontWeight(QFont::Bold); if ( !(options.state & QStyle::State_Selected) ) { QColor bgHighlight = option.palette.color(QPalette::AlternateBase); fmt.setBackground(bgHighlight); } cur.insertText(item->text().mid(rng.start().column(), rng.end().column() - rng.start().column()), fmt); fmt.clearBackground(); fmt.setFontWeight(QFont::Normal); cur.insertText(item->text().right(item->text().length() - rng.end().column()), fmt); }else{ QString text; if(item) text = item->text(); else text = index.data().toString(); // Simply insert the text as html. We use this for the titles. doc.setHtml(text); } painter->save(); options.text = QString(); // text will be drawn separately options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget); // set correct draw area QRect clip = options.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &options); QFontMetrics metrics(options.font); painter->translate(clip.topLeft() - QPoint(0, metrics.descent())); // We disable the clipping for now, as it leads to strange clipping errors // clip.setTopLeft(QPoint(0,0)); // painter->setClipRect(clip); QAbstractTextDocumentLayout::PaintContext ctx; // ctx.clip = clip; painter->setBackground(Qt::transparent); doc.documentLayout()->draw(painter, ctx); painter->restore(); } QSize GrepOutputDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { const GrepOutputModel *model = dynamic_cast(index.model()); const GrepOutputItem *item = model ? dynamic_cast(model->itemFromIndex(index)) : nullptr; QSize ret = QStyledItemDelegate::sizeHint(option, index); //take account of additional width required for highlighting (bold text) //and line numbers. These are not included in the default Qt size calculation. if(item && item->isText()) { QFont font = option.font; QFontMetrics metrics(font); font.setBold(true); QFontMetrics bMetrics(font); const KTextEditor::Range rng = item->change()->m_range; int width = metrics.width(item->text().left(rng.start().column())) + metrics.width(item->text().right(item->text().length() - rng.end().column())) + bMetrics.width(item->text().mid(rng.start().column(), rng.end().column() - rng.start().column())) + option.fontMetrics.width(i18n("Line %1: ",item->lineNumber())) + std::max(option.decorationSize.width(), 0); ret.setWidth(width); }else{ // This is only used for titles, so not very performance critical QString text; if(item) text = item->text(); else text = index.data().toString(); QTextDocument doc; doc.setDocumentMargin(0); doc.setHtml(text); QSize newSize = doc.size().toSize(); if(newSize.height() > ret.height()) ret.setHeight(newSize.height()); } return ret; } diff --git a/plugins/grepview/grepoutputmodel.cpp b/plugins/grepview/grepoutputmodel.cpp index ae021ee790..08a24d2e76 100644 --- a/plugins/grepview/grepoutputmodel.cpp +++ b/plugins/grepview/grepoutputmodel.cpp @@ -1,480 +1,480 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * 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. * * * ***************************************************************************/ #include "grepoutputmodel.h" #include "grepviewplugin.h" #include "greputil.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputItem::GrepOutputItem(DocumentChangePointer change, const QString &text, bool checkable) : QStandardItem(), m_change(change) { setText(text); setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); setCheckable(checkable); if(checkable) setCheckState(Qt::Checked); } GrepOutputItem::GrepOutputItem(const QString& filename, const QString& text, bool checkable) : QStandardItem(), m_change(new DocumentChange(IndexedString(filename), KTextEditor::Range::invalid(), QString(), QString())) { setText(text); setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); setCheckable(checkable); if(checkable) { setTristate(true); setCheckState(Qt::Checked); } } int GrepOutputItem::lineNumber() const { // line starts at 0 for cursor but we want to start at 1 return m_change->m_range.start().line() + 1; } QString GrepOutputItem::filename() const { return m_change->m_document.str(); } DocumentChangePointer GrepOutputItem::change() const { return m_change; } bool GrepOutputItem::isText() const { return m_change->m_range.isValid(); } void GrepOutputItem::propagateState() { for(int i = 0; i < rowCount(); i++) { GrepOutputItem *item = static_cast(child(i)); if(item->isEnabled()) { item->setCheckState(checkState()); item->propagateState(); } } } void GrepOutputItem::refreshState() { if(rowCount() > 0) { int checked = 0; int unchecked = 0; int enabled = 0; //only enabled items are relevants for(int i = 0; i < rowCount(); i++) { QStandardItem *item = child(i); if(item->isEnabled()) { enabled += 1; switch(child(i)->checkState()) { case Qt::Checked: checked += 1; break; case Qt::Unchecked: unchecked += 1; break; default: break; } } } if(enabled == 0) { setCheckState(Qt::Unchecked); setEnabled(false); } else if(checked == enabled) { setCheckState(Qt::Checked); } else if (unchecked == enabled) { setCheckState(Qt::Unchecked); } else { setCheckState(Qt::PartiallyChecked); } } if(GrepOutputItem *p = static_cast(parent())) { p->refreshState(); } } QVariant GrepOutputItem::data ( int role ) const { GrepOutputModel *grepModel = static_cast(model()); if(role == Qt::ToolTipRole && grepModel && isText()) { QString start = text().left(m_change->m_range.start().column()).toHtmlEscaped(); QString repl = "" + grepModel->replacementFor(m_change->m_oldText).toHtmlEscaped() + ""; QString end = text().right(text().length() - m_change->m_range.end().column()).toHtmlEscaped(); return QVariant(QString(start + repl + end).trimmed()); } else if (role == Qt::FontRole) { return QFontDatabase::systemFont(QFontDatabase::FixedFont); } else { return QStandardItem::data(role); } } GrepOutputItem::~GrepOutputItem() {} /////////////////////////////////////////////////////////////// GrepOutputModel::GrepOutputModel( QObject *parent ) : QStandardItemModel( parent ), m_regExp(), m_replacement(), m_replacementTemplate(), m_finalReplacement(), - m_finalUpToDate(false), m_rootItem(0), m_fileCount(0), m_matchCount(0), m_itemsCheckable(false) + m_finalUpToDate(false), m_rootItem(nullptr), m_fileCount(0), m_matchCount(0), m_itemsCheckable(false) { connect(this, &GrepOutputModel::itemChanged, this, &GrepOutputModel::updateCheckState); } GrepOutputModel::~GrepOutputModel() {} void GrepOutputModel::clear() { QStandardItemModel::clear(); // the above clear() also destroys the root item, so invalidate the pointer - m_rootItem = 0; + m_rootItem = nullptr; m_fileCount = 0; m_matchCount = 0; } void GrepOutputModel::setRegExp(const QRegExp& re) { m_regExp = re; m_finalUpToDate = false; } void GrepOutputModel::setReplacement(const QString& repl) { m_replacement = repl; m_finalUpToDate = false; } void GrepOutputModel::setReplacementTemplate(const QString& tmpl) { m_replacementTemplate = tmpl; m_finalUpToDate = false; } QString GrepOutputModel::replacementFor(const QString &text) { if(!m_finalUpToDate) { m_finalReplacement = substitudePattern(m_replacementTemplate, m_replacement); m_finalUpToDate = true; } return QString(text).replace(m_regExp, m_finalReplacement); } void GrepOutputModel::activate( const QModelIndex &idx ) { QStandardItem *stditem = itemFromIndex(idx); GrepOutputItem *grepitem = dynamic_cast(stditem); if( !grepitem || !grepitem->isText() ) return; QUrl url = QUrl::fromLocalFile(grepitem->filename()); int line = grepitem->lineNumber() - 1; KTextEditor::Range range( line, 0, line+1, 0); // Try to find the actual text range we found during the grep IDocument* doc = ICore::self()->documentController()->documentForUrl( url ); if(!doc) doc = ICore::self()->documentController()->openDocument( url, range ); if(!doc) return; if (KTextEditor::Document* tdoc = doc->textDocument()) { KTextEditor::Range matchRange = grepitem->change()->m_range; QString actualText = tdoc->text(matchRange); QString expectedText = grepitem->change()->m_oldText; if (actualText == expectedText) { range = matchRange; } } ICore::self()->documentController()->activateDocument( doc, range ); } QModelIndex GrepOutputModel::previousItemIndex(const QModelIndex ¤tIdx) const { - GrepOutputItem* current_item = 0; + GrepOutputItem* current_item = nullptr; if (!currentIdx.isValid()) { // no item selected, search recursively for the last item in search results QStandardItem *it = item(0,0); while (it) { QStandardItem *child = it->child( it->rowCount() - 1 ); if (!child) return it->index(); it = child; } return QModelIndex(); } else current_item = dynamic_cast(itemFromIndex(currentIdx)); - if (current_item->parent() != 0) { + if (current_item->parent() != nullptr) { int row = currentIdx.row(); if(!current_item->isText()) // the item is a file { int item_row = current_item->row(); if(item_row > 0) { int idx_last_item = current_item->parent()->child(item_row - 1)->rowCount() - 1; return current_item->parent()->child(item_row - 1)->child(idx_last_item)->index(); } } else // the item is a match { if(row > 0) return current_item->parent()->child(row - 1)->index(); else // we return the index of the last item of the previous file { int parrent_row = current_item->parent()->row(); if(parrent_row > 0) { int idx_last_item = current_item->parent()->parent()->child(parrent_row - 1)->rowCount() - 1; return current_item->parent()->parent()->child(parrent_row - 1)->child(idx_last_item)->index(); } } } } return currentIdx; } QModelIndex GrepOutputModel::nextItemIndex(const QModelIndex ¤tIdx) const { - GrepOutputItem* current_item = 0; + GrepOutputItem* current_item = nullptr; if (!currentIdx.isValid()) { QStandardItem *it = item(0,0); if (!it) return QModelIndex(); current_item = dynamic_cast(it); } else current_item = dynamic_cast(itemFromIndex(currentIdx)); - if (current_item->parent() == 0) { + if (current_item->parent() == nullptr) { // root item with overview of search results if (current_item->rowCount() > 0) return nextItemIndex(current_item->child(0)->index()); else return QModelIndex(); } else { int row = currentIdx.row(); if(!current_item->isText()) // the item is a file { int item_row = current_item->row(); if(item_row < current_item->parent()->rowCount()) { return current_item->parent()->child(item_row)->child(0)->index(); } } else // the item is a match { if(row < current_item->parent()->rowCount() - 1) return current_item->parent()->child(row + 1)->index(); else // we return the index of the first item of the next file { int parrent_row = current_item->parent()->row(); if(parrent_row < current_item->parent()->parent()->rowCount() - 1) { return current_item->parent()->parent()->child(parrent_row + 1)->child(0)->index(); } } } } return currentIdx; } const GrepOutputItem *GrepOutputModel::getRootItem() const { return m_rootItem; } bool GrepOutputModel::itemsCheckable() const { return m_itemsCheckable; } void GrepOutputModel::makeItemsCheckable(bool checkable) { if(m_itemsCheckable == checkable) return; if(m_rootItem) makeItemsCheckable(checkable, m_rootItem); m_itemsCheckable = checkable; } void GrepOutputModel::makeItemsCheckable(bool checkable, GrepOutputItem* item) { item->setCheckable(checkable); if(checkable) { item->setCheckState(Qt::Checked); if(item->rowCount() && checkable) item->setTristate(true); } for(int row = 0; row < item->rowCount(); ++row) makeItemsCheckable(checkable, static_cast(item->child(row, 0))); } void GrepOutputModel::appendOutputs( const QString &filename, const GrepOutputItem::List &items ) { if(items.isEmpty()) return; if(rowCount() == 0) { m_rootItem = new GrepOutputItem(QString(), QString(), m_itemsCheckable); appendRow(m_rootItem); } m_fileCount += 1; m_matchCount += items.length(); const QString matchText = i18np("1 match", "%1 matches", m_matchCount); const QString fileText = i18np("1 file", "%1 files", m_fileCount); m_rootItem->setText(i18nc("%1 is e.g. '4 matches', %2 is e.g. '1 file'", "

%1 in %2

", matchText, fileText)); QString fnString = i18np("%2 (one match)", "%2 (%1 matches)", items.length(), ICore::self()->projectController()->prettyFileName(QUrl::fromLocalFile(filename))); GrepOutputItem *fileItem = new GrepOutputItem(filename, fnString, m_itemsCheckable); m_rootItem->appendRow(fileItem); foreach( const GrepOutputItem& item, items ) { GrepOutputItem* copy = new GrepOutputItem(item); copy->setCheckable(m_itemsCheckable); if(m_itemsCheckable) { copy->setCheckState(Qt::Checked); if(copy->rowCount()) copy->setTristate(true); } fileItem->appendRow(copy); } } void GrepOutputModel::updateCheckState(QStandardItem* item) { // if we don't disconnect the SIGNAL, the setCheckState will call it in loop disconnect(this, &GrepOutputModel::itemChanged, nullptr, nullptr); // try to update checkstate on non checkable items would make a checkbox appear if(item->isCheckable()) { GrepOutputItem *it = static_cast(item); it->propagateState(); it->refreshState(); } connect(this, &GrepOutputModel::itemChanged, this, &GrepOutputModel::updateCheckState); } void GrepOutputModel::doReplacements() { Q_ASSERT(m_rootItem); if (!m_rootItem) return; // nothing to do, abort DocumentChangeSet changeSet; changeSet.setFormatPolicy(DocumentChangeSet::NoAutoFormat); for(int fileRow = 0; fileRow < m_rootItem->rowCount(); fileRow++) { GrepOutputItem *file = static_cast(m_rootItem->child(fileRow)); for(int matchRow = 0; matchRow < file->rowCount(); matchRow++) { GrepOutputItem *match = static_cast(file->child(matchRow)); if(match->checkState() == Qt::Checked) { DocumentChangePointer change = match->change(); // setting replacement text based on current replace value change->m_newText = replacementFor(change->m_oldText); changeSet.addChange(change); // this item cannot be checked anymore match->setCheckState(Qt::Unchecked); match->setEnabled(false); } } } DocumentChangeSet::ChangeResult result = changeSet.applyAllChanges(); if(!result.m_success) { DocumentChangePointer ch = result.m_reasonChange; if(ch) emit showErrorMessage(i18nc("%1 is the old text, %2 is the new text, %3 is the file path, %4 and %5 are its row and column", "Failed to replace %1 by %2 in %3:%4:%5", ch->m_oldText.toHtmlEscaped(), ch->m_newText.toHtmlEscaped(), ch->m_document.toUrl().toLocalFile(), ch->m_range.start().line() + 1, ch->m_range.start().column() + 1)); } } void GrepOutputModel::showMessageSlot(IStatus* status, const QString& message) { m_savedMessage = message; m_savedIStatus = status; showMessageEmit(); } void GrepOutputModel::showMessageEmit() { emit showMessage(m_savedIStatus, m_savedMessage); } bool GrepOutputModel::hasResults() { return(m_matchCount > 0); } diff --git a/plugins/grepview/grepoutputview.cpp b/plugins/grepview/grepoutputview.cpp index f672391f3a..974cd61d2f 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,385 +1,385 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * 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. * * * ***************************************************************************/ #include "grepoutputview.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "ui_grepoutputview.h" #include "grepviewplugin.h" #include "grepdialog.h" #include "greputil.h" #include "grepjob.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputViewFactory::GrepOutputViewFactory(GrepViewPlugin* plugin) : m_plugin(plugin) {} QWidget* GrepOutputViewFactory::create(QWidget* parent) { return new GrepOutputView(parent, m_plugin); } Qt::DockWidgetArea GrepOutputViewFactory::defaultPosition() { return Qt::BottomDockWidgetArea; } QString GrepOutputViewFactory::id() const { return QStringLiteral("org.kdevelop.GrepOutputView"); } const int GrepOutputView::HISTORY_SIZE = 5; GrepOutputView::GrepOutputView(QWidget* parent, GrepViewPlugin* plugin) : QWidget(parent) - , m_next(0) - , m_prev(0) - , m_collapseAll(0) - , m_expandAll(0) - , m_clearSearchHistory(0) - , m_statusLabel(0) + , m_next(nullptr) + , m_prev(nullptr) + , m_collapseAll(nullptr) + , m_expandAll(nullptr) + , m_clearSearchHistory(nullptr) + , m_statusLabel(nullptr) , m_plugin(plugin) { Ui::GrepOutputView::setupUi(this); setWindowTitle(i18nc("@title:window", "Find/Replace Output View")); setWindowIcon(QIcon::fromTheme(QStringLiteral("edit-find"), windowIcon())); m_prev = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("&Previous Item"), this); m_prev->setEnabled(false); m_next = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("&Next Item"), this); m_next->setEnabled(false); m_collapseAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-left-double")), i18n("C&ollapse All"), this); // TODO change icon m_collapseAll->setEnabled(false); m_expandAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-right-double")), i18n("&Expand All"), this); // TODO change icon m_expandAll->setEnabled(false); QAction *separator = new QAction(this); separator->setSeparator(true); QAction *newSearchAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("New &Search"), this); m_clearSearchHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18n("Clear Search History"), this); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); addAction(m_clearSearchHistory); separator = new QAction(this); separator->setSeparator(true); addAction(separator); QWidgetAction *statusWidget = new QWidgetAction(this); m_statusLabel = new QLabel(this); statusWidget->setDefaultWidget(m_statusLabel); addAction(statusWidget); modelSelector->setEditable(false); modelSelector->setContextMenuPolicy(Qt::CustomContextMenu); connect(modelSelector, &KComboBox::customContextMenuRequested, this, &GrepOutputView::modelSelectorContextMenu); connect(modelSelector, static_cast(&KComboBox::currentIndexChanged), this, &GrepOutputView::changeModel); resultsTreeView->setItemDelegate(GrepOutputDelegate::self()); resultsTreeView->setHeaderHidden(true); resultsTreeView->setUniformRowHeights(false); resultsTreeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); connect(m_prev, &QAction::triggered, this, &GrepOutputView::selectPreviousItem); connect(m_next, &QAction::triggered, this, &GrepOutputView::selectNextItem); connect(m_collapseAll, &QAction::triggered, this, &GrepOutputView::collapseAllItems); connect(m_expandAll, &QAction::triggered, this, &GrepOutputView::expandAllItems); connect(applyButton, &QPushButton::clicked, this, &GrepOutputView::onApply); connect(m_clearSearchHistory, &QAction::triggered, this, &GrepOutputView::clearSearchHistory); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); replacementCombo->addItems( cg.readEntry("LastReplacementItems", QStringList()) ); replacementCombo->setInsertPolicy(QComboBox::InsertAtTop); applyButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); connect(replacementCombo, &KComboBox::editTextChanged, this, &GrepOutputView::replacementTextChanged); connect(replacementCombo, static_cast(&KComboBox::returnPressed), this, &GrepOutputView::onApply); connect(newSearchAction, &QAction::triggered, this, &GrepOutputView::showDialog); resultsTreeView->header()->setStretchLastSection(true); resultsTreeView->header()->setStretchLastSection(true); updateCheckable(); } void GrepOutputView::replacementTextChanged(QString) { updateCheckable(); if (model()) { // see https://bugs.kde.org/show_bug.cgi?id=274902 - renewModel can trigger a call here without an active model updateApplyState(model()->index(0, 0), model()->index(0, 0)); } } GrepOutputView::~GrepOutputView() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); cg.writeEntry("LastReplacementItems", qCombo2StringList(replacementCombo, true)); emit outputViewIsClosed(); } GrepOutputModel* GrepOutputView::renewModel(const QString& name, const QString& description) { // Crear oldest model while(modelSelector->count() > GrepOutputView::HISTORY_SIZE) { QVariant var = modelSelector->itemData(GrepOutputView::HISTORY_SIZE - 1); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(GrepOutputView::HISTORY_SIZE - 1); } replacementCombo->clearEditText(); GrepOutputModel* newModel = new GrepOutputModel(resultsTreeView); applyButton->setEnabled(false); // text may be already present newModel->setReplacement(replacementCombo->currentText()); connect(newModel, &GrepOutputModel::rowsRemoved, this, &GrepOutputView::rowsRemoved); connect(resultsTreeView, &QTreeView::activated, newModel, &GrepOutputModel::activate); connect(replacementCombo, &KComboBox::editTextChanged, newModel, &GrepOutputModel::setReplacement); connect(newModel, &GrepOutputModel::rowsInserted, this, &GrepOutputView::expandElements); connect(newModel, &GrepOutputModel::showErrorMessage, this, &GrepOutputView::showErrorMessage); connect(m_plugin, &GrepViewPlugin::grepJobFinished, this, &GrepOutputView::updateScrollArea); // appends new model to history const QString displayName = i18n("Search \"%1\" in %2 (at time %3)", name, description, QTime::currentTime().toString(QStringLiteral("hh:mm"))); modelSelector->insertItem(0, displayName, qVariantFromValue(newModel)); modelSelector->setCurrentIndex(0);//setCurrentItem(displayName); updateCheckable(); return newModel; } GrepOutputModel* GrepOutputView::model() { return static_cast(resultsTreeView->model()); } void GrepOutputView::changeModel(int index) { if (model()) { disconnect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); disconnect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); } replacementCombo->clearEditText(); //after deleting the whole search history, index is -1 if(index >= 0) { QVariant var = modelSelector->itemData(index); GrepOutputModel *resultModel = static_cast(qvariant_cast(var)); resultsTreeView->setModel(resultModel); connect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); connect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); model()->showMessageEmit(); applyButton->setEnabled(model()->hasResults() && model()->getRootItem() && model()->getRootItem()->checkState() != Qt::Unchecked && !replacementCombo->currentText().isEmpty()); if(model()->hasResults()) expandElements(QModelIndex()); } updateCheckable(); updateApplyState(model()->index(0, 0), model()->index(0, 0)); } void GrepOutputView::setMessage(const QString& msg, MessageType type) { if (type == Error) { QPalette palette = m_statusLabel->palette(); KColorScheme::adjustForeground(palette, KColorScheme::NegativeText, QPalette::WindowText); m_statusLabel->setPalette(palette); } else { m_statusLabel->setPalette(QPalette()); } m_statusLabel->setText(msg); } void GrepOutputView::showErrorMessage( const QString& errorMessage ) { setMessage(errorMessage, Error); } void GrepOutputView::showMessage( KDevelop::IStatus* , const QString& message ) { setMessage(message, Information); } void GrepOutputView::onApply() { if(model()) { Q_ASSERT(model()->rowCount()); // ask a confirmation before an empty string replacement if(replacementCombo->currentText().length() == 0 && KMessageBox::questionYesNo(this, i18n("Do you want to replace with an empty string?"), i18n("Start replacement")) == KMessageBox::No) { return; } setEnabled(false); model()->doReplacements(); setEnabled(true); } } void GrepOutputView::showDialog() { m_plugin->showDialog(true); } void GrepOutputView::expandElements(const QModelIndex& index) { m_prev->setEnabled(true); m_next->setEnabled(true); m_collapseAll->setEnabled(true); m_expandAll->setEnabled(true); resultsTreeView->expand(index); } void GrepOutputView::selectPreviousItem() { if (!model()) { return; } QModelIndex prev_idx = model()->previousItemIndex(resultsTreeView->currentIndex()); if (prev_idx.isValid()) { resultsTreeView->setCurrentIndex(prev_idx); model()->activate(prev_idx); } } void GrepOutputView::selectNextItem() { if (!model()) { return; } QModelIndex next_idx = model()->nextItemIndex(resultsTreeView->currentIndex()); if (next_idx.isValid()) { resultsTreeView->setCurrentIndex(next_idx); model()->activate(next_idx); } } void GrepOutputView::collapseAllItems() { // Collapse everything resultsTreeView->collapseAll(); // Now reopen the first children, which correspond to the files. resultsTreeView->expand(resultsTreeView->model()->index(0, 0)); } void GrepOutputView::expandAllItems() { resultsTreeView->expandAll(); } void GrepOutputView::rowsRemoved() { m_prev->setEnabled(model()->rowCount()); m_next->setEnabled(model()->rowCount()); } void GrepOutputView::updateApplyState(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(bottomRight); if (!model() || !model()->hasResults()) { applyButton->setEnabled(false); return; } // we only care about the root item if(!topLeft.parent().isValid()) { applyButton->setEnabled(topLeft.data(Qt::CheckStateRole) != Qt::Unchecked && model()->itemsCheckable()); } } void GrepOutputView::updateCheckable() { if(model()) model()->makeItemsCheckable(!replacementCombo->currentText().isEmpty() || model()->itemsCheckable()); } void GrepOutputView::clearSearchHistory() { GrepJob *runningJob = m_plugin->grepJob(); if(runningJob) { runningJob->kill(); } while(modelSelector->count() > 0) { QVariant var = modelSelector->itemData(0); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(0); } applyButton->setEnabled(false); m_statusLabel->setText(QString()); } void GrepOutputView::modelSelectorContextMenu(const QPoint& pos) { QPoint globalPos = modelSelector->mapToGlobal(pos); QMenu myMenu; myMenu.addAction(m_clearSearchHistory); myMenu.exec(globalPos); } void GrepOutputView::updateScrollArea() { for (int col = 0; col < model()->columnCount(); ++col) resultsTreeView->resizeColumnToContents(col); } diff --git a/plugins/grepview/grepviewplugin.cpp b/plugins/grepview/grepviewplugin.cpp index a0837a9282..12d5aeaa1f 100644 --- a/plugins/grepview/grepviewplugin.cpp +++ b/plugins/grepview/grepviewplugin.cpp @@ -1,232 +1,232 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Benjamin Port * * Copyright 2010 Julien Desgats * * 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. * * * ***************************************************************************/ #include "grepviewplugin.h" #include "grepdialog.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "grepjob.h" #include "grepoutputview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_GREPVIEW, "kdevplatform.plugins.grepview") static QString patternFromSelection(const KDevelop::IDocument* doc) { if (!doc) return QString(); QString pattern; KTextEditor::Range range = doc->textSelection(); if( range.isValid() ) { pattern = doc->textDocument()->text( range ); } if( pattern.isEmpty() ) { pattern = doc->textWord(); } // Before anything, this removes line feeds from the // beginning and the end. int len = pattern.length(); if (len > 0 && pattern[0] == '\n') { pattern.remove(0, 1); len--; } if (len > 0 && pattern[len-1] == '\n') pattern.truncate(len-1); return pattern; } GrepViewPlugin::GrepViewPlugin( QObject *parent, const QVariantList & ) - : KDevelop::IPlugin( QStringLiteral("kdevgrepview"), parent ), m_currentJob(0) + : KDevelop::IPlugin( QStringLiteral("kdevgrepview"), parent ), m_currentJob(nullptr) { setXMLFile(QStringLiteral("kdevgrepview.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/GrepViewPlugin"), this, QDBusConnection::ExportScriptableSlots ); QAction*action = actionCollection()->addAction(QStringLiteral("edit_grep")); action->setText(i18n("Find/Replace in Fi&les...")); actionCollection()->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Alt+F")) ); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); action->setToolTip( i18n("Search for expressions over several files") ); action->setWhatsThis( i18n("Opens the 'Find/Replace in files' dialog. There you " "can enter a regular expression which is then " "searched for within all files in the directories " "you specify. Matches will be displayed, you " "can switch to a match directly. You can also do replacement.") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); // instantiate delegate, it's supposed to be deleted via QObject inheritance new GrepOutputDelegate(this); m_factory = new GrepOutputViewFactory(this); core()->uiController()->addToolView(i18n("Find/Replace in Files"), m_factory); } GrepOutputViewFactory* GrepViewPlugin::toolViewFactory() const { return m_factory; } GrepViewPlugin::~GrepViewPlugin() { } void GrepViewPlugin::unload() { core()->uiController()->removeToolView(m_factory); } void GrepViewPlugin::startSearch(QString pattern, QString directory, bool show) { m_directory = directory; showDialog(false, pattern, show); } KDevelop::ContextMenuExtension GrepViewPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context); if( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); // verify if there is only one folder selected if ((items.count() == 1) && (items.first()->folder())) { QAction* action = new QAction( i18n( "Find/Replace in This Folder..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_contextMenuDirectory = items.at(0)->folder()->path().toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast(context); if ( econtext->view()->selection() ) { QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("&Find/Replace in Files..."), this); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); extension.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, action); } } if(context->type() == KDevelop::Context::FileContext) { KDevelop::FileContext *fcontext = dynamic_cast(context); // TODO: just stat() or QFileInfo().isDir() for local files? should be faster than mime type checking QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(fcontext->urls().at(0)); static const QMimeType directoryMime = QMimeDatabase().mimeTypeForName(QStringLiteral("inode/directory")); if (mimetype == directoryMime) { QAction* action = new QAction( i18n( "Find/Replace in This Folder..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_contextMenuDirectory = fcontext->urls().at(0).toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } return extension; } void GrepViewPlugin::showDialog(bool setLastUsed, QString pattern, bool show) { GrepDialog* dlg = new GrepDialog( this, core()->uiController()->activeMainWindow() ); KDevelop::IDocument* doc = core()->documentController()->activeDocument(); if(!pattern.isEmpty()) { dlg->setPattern(pattern); } else if(!setLastUsed) { QString pattern = patternFromSelection(doc); if (!pattern.isEmpty()) { dlg->setPattern( pattern ); } } //if directory is empty then use a default value from the config file. if (!m_directory.isEmpty()) { dlg->setSearchLocations(m_directory); } if(show) dlg->show(); else{ dlg->startSearch(); dlg->deleteLater(); } } void GrepViewPlugin::showDialogFromMenu() { showDialog(); } void GrepViewPlugin::showDialogFromProject() { rememberSearchDirectory(m_contextMenuDirectory); showDialog(); } void GrepViewPlugin::rememberSearchDirectory(QString const & directory) { m_directory = directory; } GrepJob* GrepViewPlugin::newGrepJob() { - if(m_currentJob != 0) + if(m_currentJob != nullptr) { m_currentJob->kill(); } m_currentJob = new GrepJob(); connect(m_currentJob, &GrepJob::finished, this, &GrepViewPlugin::jobFinished); return m_currentJob; } GrepJob* GrepViewPlugin::grepJob() { return m_currentJob; } void GrepViewPlugin::jobFinished(KJob* job) { if(job == m_currentJob) { emit grepJobFinished(); - m_currentJob = 0; + m_currentJob = nullptr; } } diff --git a/plugins/konsole/kdevkonsoleview.cpp b/plugins/konsole/kdevkonsoleview.cpp index f2c6e77771..eaaeded7cd 100644 --- a/plugins/konsole/kdevkonsoleview.cpp +++ b/plugins/konsole/kdevkonsoleview.cpp @@ -1,154 +1,154 @@ /*************************************************************************** * Copyright 2003, 2006 Adam Treat * * Copyright 2007 Andreas Pakulat * * * * 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. * * * ***************************************************************************/ #include "kdevkonsoleview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kdevkonsoleviewplugin.h" class KDevKonsoleViewPrivate { public: KDevKonsoleViewPlugin* mplugin; KDevKonsoleView* m_view; KParts::ReadOnlyPart *konsolepart; QVBoxLayout *m_vbox; // TODO: remove this once we can depend on a Qt version that includes https://codereview.qt-project.org/#/c/83800/ QMetaObject::Connection m_partDestroyedConnection; void _k_slotTerminalClosed(); void init( KPluginFactory* factory ) { - Q_ASSERT( konsolepart == 0 ); + Q_ASSERT( konsolepart == nullptr ); - Q_ASSERT( factory != 0 ); + Q_ASSERT( factory != nullptr ); if ( ( konsolepart = factory->create( m_view ) ) ) { QObject::disconnect(m_partDestroyedConnection); m_partDestroyedConnection = QObject::connect(konsolepart, &KParts::ReadOnlyPart::destroyed, m_view, [&] { _k_slotTerminalClosed(); }); konsolepart->widget() ->setFocusPolicy( Qt::WheelFocus ); konsolepart->widget() ->setFocus(); konsolepart->widget() ->installEventFilter( m_view ); if ( QFrame * frame = qobject_cast( konsolepart->widget() ) ) frame->setFrameStyle( QFrame::Panel | QFrame::Sunken ); m_vbox->addWidget( konsolepart->widget() ); m_view->setFocusProxy( konsolepart->widget() ); konsolepart->widget() ->show(); TerminalInterface* interface = qobject_cast(konsolepart); Q_ASSERT(interface); interface->showShellInDir( QString() ); interface->sendInput( " kdevelop! -s \"" + KDevelop::ICore::self()->activeSession()->id().toString() + "\"\n" ); }else { qCDebug(PLUGIN_KONSOLE) << "Couldn't create KParts::ReadOnlyPart from konsole factory!"; } } ~KDevKonsoleViewPrivate() { QObject::disconnect(m_partDestroyedConnection); } }; void KDevKonsoleViewPrivate::_k_slotTerminalClosed() { - konsolepart = 0; + konsolepart = nullptr; init( mplugin->konsoleFactory() ); } KDevKonsoleView::KDevKonsoleView( KDevKonsoleViewPlugin *plugin, QWidget* parent ) : QWidget( parent ), d(new KDevKonsoleViewPrivate) { d->mplugin = plugin; d->m_view = this; - d->konsolepart = 0; + d->konsolepart = nullptr; setObjectName( i18n( "Konsole" ) ); setWindowIcon( QIcon::fromTheme( QStringLiteral( "utilities-terminal" ), windowIcon() ) ); setWindowTitle( i18n( "Konsole" ) ); d->m_vbox = new QVBoxLayout( this ); d->m_vbox->setMargin( 0 ); d->m_vbox->setSpacing( 0 ); d->init( d->mplugin->konsoleFactory() ); //TODO Make this configurable in the future, // but by default the konsole shouldn't // automatically switch directories on you. // connect( KDevelop::Core::documentController(), SIGNAL(documentActivated(KDevDocument*)), // this, SLOT(documentActivated(KDevDocument*)) ); } KDevKonsoleView::~KDevKonsoleView() { delete d; } void KDevKonsoleView::setDirectory( const QUrl &url ) { if ( !url.isValid() || !url.isLocalFile() ) return ; if ( d->konsolepart && url != d->konsolepart->url() ) d->konsolepart->openUrl( url ); } bool KDevKonsoleView::eventFilter( QObject* obj, QEvent *e ) { switch( e->type() ) { case QEvent::ShortcutOverride: { QKeyEvent *k = static_cast(e); // Don't propagate Esc to the top level, it should be used by konsole if (k->key() == Qt::Key_Escape) { if (d->konsolepart && d->konsolepart->widget()) { e->accept(); return true; } } break; } default: break; } return QWidget::eventFilter( obj, e ); } #include "moc_kdevkonsoleview.cpp" diff --git a/plugins/konsole/kdevkonsoleviewplugin.cpp b/plugins/konsole/kdevkonsoleviewplugin.cpp index 234c6e984c..cb781cd82c 100644 --- a/plugins/konsole/kdevkonsoleviewplugin.cpp +++ b/plugins/konsole/kdevkonsoleviewplugin.cpp @@ -1,89 +1,89 @@ /*************************************************************************** * Copyright 2003, 2006 Adam Treat * * Copyright 2007 Andreas Pakulat * * * * 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. * * * ***************************************************************************/ #include "kdevkonsoleviewplugin.h" #include #include #include #include #include "kdevkonsoleview.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_KONSOLE, "kdevplatform.plugins.konsole") QObject* createKonsoleView( QWidget*, QObject* op, const QVariantList& args) { KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("konsolepart")); KPluginFactory* factory = nullptr; if (service) { factory = KPluginLoader(*service.data()).factory(); } if (!factory) { qWarning() << "Failed to load 'konsolepart' plugin"; } return new KDevKonsoleViewPlugin(factory, op, args); } K_PLUGIN_FACTORY_WITH_JSON(KonsoleViewFactory, "kdevkonsoleview.json", registerPlugin( QString(), &createKonsoleView );) class KDevKonsoleViewFactory: public KDevelop::IToolViewFactory{ public: KDevKonsoleViewFactory(KDevKonsoleViewPlugin *plugin): mplugin(plugin) {} - QWidget* create(QWidget *parent = 0) override + QWidget* create(QWidget *parent = nullptr) override { return new KDevKonsoleView(mplugin, parent); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.KonsoleView"); } private: KDevKonsoleViewPlugin *mplugin; }; KDevKonsoleViewPlugin::KDevKonsoleViewPlugin( KPluginFactory* konsoleFactory, QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevkonsoleview"), parent ) , m_konsoleFactory(konsoleFactory) , m_viewFactory(konsoleFactory ? new KDevKonsoleViewFactory(this) : nullptr) { if(!m_viewFactory) { setErrorDescription(i18n("Failed to load 'konsolepart' plugin")); } else { core()->uiController()->addToolView(QStringLiteral("Konsole"), m_viewFactory); } } void KDevKonsoleViewPlugin::unload() { if (m_viewFactory) { core()->uiController()->removeToolView(m_viewFactory); } } KPluginFactory* KDevKonsoleViewPlugin::konsoleFactory() const { return m_konsoleFactory; } KDevKonsoleViewPlugin::~KDevKonsoleViewPlugin() { } #include "kdevkonsoleviewplugin.moc" diff --git a/plugins/openwith/openwithplugin.cpp b/plugins/openwith/openwithplugin.cpp index e532d789d5..b117b66df6 100644 --- a/plugins/openwith/openwithplugin.cpp +++ b/plugins/openwith/openwithplugin.cpp @@ -1,291 +1,291 @@ /* * This file is part of KDevelop * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "openwithplugin.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 using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(KDevOpenWithFactory, "kdevopenwith.json", registerPlugin();) namespace { bool sortActions(QAction* left, QAction* right) { return left->text() < right->text(); } bool isTextEditor(const KService::Ptr& service) { return service->serviceTypes().contains( QStringLiteral("KTextEditor/Document") ); } QString defaultForMimeType(const QString& mimeType) { KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); if (config.hasKey(mimeType)) { QString storageId = config.readEntry(mimeType, QString()); if (!storageId.isEmpty() && KService::serviceByStorageId(storageId)) { return storageId; } } return QString(); } bool canOpenDefault(const QString& mimeType) { if (defaultForMimeType(mimeType).isEmpty() && mimeType == QLatin1String("inode/directory")) { // potentially happens in non-kde environments apparently, see https://git.reviewboard.kde.org/r/122373 return KMimeTypeTrader::self()->preferredService(mimeType); } else { return true; } } } OpenWithPlugin::OpenWithPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( QStringLiteral("kdevopenwith"), parent ), - m_actionMap( 0 ) + m_actionMap( nullptr ) { KDEV_USE_EXTENSION_INTERFACE( IOpenWith ) } OpenWithPlugin::~OpenWithPlugin() { } KDevelop::ContextMenuExtension OpenWithPlugin::contextMenuExtension( KDevelop::Context* context ) { // do not recurse if (context->hasType(KDevelop::Context::OpenWithContext)) { return ContextMenuExtension(); } m_urls.clear(); m_actionMap.reset(); m_services.clear(); FileContext* filectx = dynamic_cast( context ); ProjectItemContext* projctx = dynamic_cast( context ); if ( filectx && filectx->urls().count() > 0 ) { m_urls = filectx->urls(); } else if ( projctx && projctx->items().count() > 0 ) { // For now, let's handle *either* files only *or* directories only const int wantedType = projctx->items().at(0)->type(); foreach( ProjectBaseItem* item, projctx->items() ) { if (wantedType == ProjectBaseItem::File && item->file()) { m_urls << item->file()->path().toUrl(); } else if ((wantedType == ProjectBaseItem::Folder || wantedType == ProjectBaseItem::BuildFolder) && item->folder()) { m_urls << item->folder()->path().toUrl(); } } } if (m_urls.isEmpty()) { return KDevelop::ContextMenuExtension(); } m_actionMap.reset(new QSignalMapper( this )); connect( m_actionMap.data(), static_cast(&QSignalMapper::mapped), this, &OpenWithPlugin::open ); // Ok, lets fetch the mimetype for the !!first!! url and the relevant services // TODO: Think about possible alternatives to using the mimetype of the first url. QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(m_urls.first()); m_mimeType = mimetype.name(); QList partActions = actionsForServiceType(QStringLiteral("KParts/ReadOnlyPart")); QList appActions = actionsForServiceType(QStringLiteral("Application")); OpenWithContext subContext(m_urls, mimetype); QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &subContext ); foreach( const ContextMenuExtension& ext, extensions ) { appActions += ext.actions(ContextMenuExtension::OpenExternalGroup); partActions += ext.actions(ContextMenuExtension::OpenEmbeddedGroup); } { auto other = new QAction(i18n("Other..."), this); connect(other, &QAction::triggered, this, [this] { auto dialog = new KOpenWithDialog(m_urls, ICore::self()->uiController()->activeMainWindow()); if (dialog->exec() == QDialog::Accepted && dialog->service()) { openService(dialog->service()); } }); appActions << other; } // Now setup a menu with actions for each part and app QMenu* menu = new QMenu( i18n("Open With" ) ); auto documentOpenIcon = QIcon::fromTheme( QStringLiteral("document-open") ); menu->setIcon( documentOpenIcon ); if (!partActions.isEmpty()) { menu->addSection(i18n("Embedded Editors")); menu->addActions( partActions ); } if (!appActions.isEmpty()) { menu->addSection(i18n("External Applications")); menu->addActions( appActions ); } KDevelop::ContextMenuExtension ext; if (canOpenDefault(m_mimeType)) { QAction* openAction = new QAction( i18n( "Open" ), this ); openAction->setIcon( documentOpenIcon ); connect( openAction, SIGNAL(triggered()), SLOT(openDefault()) ); ext.addAction( KDevelop::ContextMenuExtension::FileGroup, openAction ); } ext.addAction(KDevelop::ContextMenuExtension::FileGroup, menu->menuAction()); return ext; } QList OpenWithPlugin::actionsForServiceType( const QString& serviceType ) { KService::List list = KMimeTypeTrader::self()->query( m_mimeType, serviceType ); KService::Ptr pref = KMimeTypeTrader::self()->preferredService( m_mimeType, serviceType ); m_services += list; QList actions; - QAction* standardAction = 0; + QAction* standardAction = nullptr; const QString defaultId = defaultForMimeType(m_mimeType); foreach( KService::Ptr svc, list ) { QAction* act = new QAction( isTextEditor(svc) ? i18n("Default Editor") : svc->name(), this ); act->setIcon( QIcon::fromTheme( svc->icon() ) ); if (svc->storageId() == defaultId || (defaultId.isEmpty() && isTextEditor(svc))) { QFont font = act->font(); font.setBold(true); act->setFont(font); } connect(act, &QAction::triggered, m_actionMap.data(), static_cast(&QSignalMapper::map)); m_actionMap->setMapping( act, svc->storageId() ); actions << act; if ( isTextEditor(svc) ) { standardAction = act; } else if ( svc->storageId() == pref->storageId() ) { standardAction = act; } } std::sort(actions.begin(), actions.end(), sortActions); if (standardAction) { actions.removeOne(standardAction); actions.prepend(standardAction); } return actions; } void OpenWithPlugin::openDefault() { // check preferred handler const QString defaultId = defaultForMimeType(m_mimeType); if (!defaultId.isEmpty()) { open(defaultId); return; } // default handlers if (m_mimeType == QLatin1String("inode/directory")) { KService::Ptr service = KMimeTypeTrader::self()->preferredService(m_mimeType); KRun::runService(*service, m_urls, ICore::self()->uiController()->activeMainWindow()); } else { foreach( const QUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u ); } } } void OpenWithPlugin::open( const QString& storageid ) { openService(KService::serviceByStorageId( storageid )); } void OpenWithPlugin::openService(const KService::Ptr& service) { if (service->isApplication()) { KRun::runService( *service, m_urls, ICore::self()->uiController()->activeMainWindow() ); } else { QString prefName = service->desktopEntryName(); if (isTextEditor(service)) { // If the user chose a KTE part, lets make sure we're creating a TextDocument instead of // a PartDocument by passing no preferredpart to the documentcontroller // TODO: Solve this rather inside DocumentController prefName.clear(); } foreach( const QUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u, prefName ); } } KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); if (service->storageId() != config.readEntry(m_mimeType, QString())) { int setDefault = KMessageBox::questionYesNo( qApp->activeWindow(), i18nc("%1: mime type name, %2: app/part name", "Do you want to open all '%1' files by default with %2?", m_mimeType, service->name() ), i18n("Set as default?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("OpenWith-%1").arg(m_mimeType) ); if (setDefault == KMessageBox::Yes) { config.writeEntry(m_mimeType, service->storageId()); } } } void OpenWithPlugin::openFilesInternal( const QList& files ) { if (files.isEmpty()) { return; } m_urls = files; m_mimeType = QMimeDatabase().mimeTypeForUrl(m_urls.first()).name(); openDefault(); } #include "openwithplugin.moc" diff --git a/plugins/outlineview/outlineviewplugin.cpp b/plugins/outlineview/outlineviewplugin.cpp index ab7b72f4a8..e1cacfb46b 100644 --- a/plugins/outlineview/outlineviewplugin.cpp +++ b/plugins/outlineview/outlineviewplugin.cpp @@ -1,76 +1,76 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "outlineviewplugin.h" #include #include #include #include #include #include "debug_outline.h" #include "outlinewidget.h" K_PLUGIN_FACTORY_WITH_JSON(KDevOutlineViewFactory, "kdevoutlineview.json", registerPlugin();) Q_LOGGING_CATEGORY(PLUGIN_OUTLINE, "kdevplatform.plugins.outline") using namespace KDevelop; class OutlineViewFactory: public KDevelop::IToolViewFactory { public: OutlineViewFactory(OutlineViewPlugin *plugin) : m_plugin(plugin) {} - QWidget* create(QWidget *parent = 0) override + QWidget* create(QWidget *parent = nullptr) override { return new OutlineWidget(parent, m_plugin); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.OutlineView"); } private: OutlineViewPlugin *m_plugin; }; OutlineViewPlugin::OutlineViewPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevoutlineview"), parent) , m_factory(new OutlineViewFactory(this)) { core()->uiController()->addToolView(i18n("Outline"), m_factory); } OutlineViewPlugin::~OutlineViewPlugin() { } void OutlineViewPlugin::unload() { core()->uiController()->removeToolView(m_factory); } #include "outlineviewplugin.moc" diff --git a/plugins/patchreview/localpatchsource.cpp b/plugins/patchreview/localpatchsource.cpp index 0c11f1c1a4..bfb90ea224 100644 --- a/plugins/patchreview/localpatchsource.cpp +++ b/plugins/patchreview/localpatchsource.cpp @@ -1,130 +1,130 @@ /*************************************************************************** Copyright 2006-2009 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "localpatchsource.h" #include #include #include #include #include #include #include #include "ui_localpatchwidget.h" #include "debug.h" LocalPatchSource::LocalPatchSource() : m_applied(false) - , m_widget(0) + , m_widget(nullptr) { } LocalPatchSource::~LocalPatchSource() { if ( !m_command.isEmpty() && !m_filename.isEmpty() ) { QFile::remove( m_filename.toLocalFile() ); } } QString LocalPatchSource::name() const { return i18n( "Custom Patch" ); } QIcon LocalPatchSource::icon() const { return QIcon::fromTheme(QStringLiteral("text-x-patch")); } void LocalPatchSource::update() { if( !m_command.isEmpty() ) { QTemporaryFile temp(QDir::tempPath() + QLatin1String("/patchreview_XXXXXX.diff")); if( temp.open() ) { temp.setAutoRemove( false ); QString filename = temp.fileName(); qCDebug(PLUGIN_PATCHREVIEW) << "temp file: " << filename; temp.close(); KProcess proc; proc.setWorkingDirectory( m_baseDir.toLocalFile() ); proc.setOutputChannelMode( KProcess::OnlyStdoutChannel ); proc.setStandardOutputFile( filename ); ///Try to apply, if it works, the patch is not applied proc << KShell::splitArgs( m_command ); qCDebug(PLUGIN_PATCHREVIEW) << "calling " << m_command; if ( proc.execute() ) { qWarning() << "returned with bad exit code"; return; } if ( !m_filename.isEmpty() ) { QFile::remove( m_filename.toLocalFile() ); } m_filename = QUrl::fromLocalFile( filename ); qCDebug(PLUGIN_PATCHREVIEW) << "success, diff: " << m_filename; }else{ qWarning() << "PROBLEM"; } } if (m_widget) { m_widget->updatePatchFromEdit(); } emit patchChanged(); } void LocalPatchSource::createWidget() { delete m_widget; - m_widget = new LocalPatchWidget(this, 0); + m_widget = new LocalPatchWidget(this, nullptr); } QWidget* LocalPatchSource::customWidget() const { return m_widget; } LocalPatchWidget::LocalPatchWidget(LocalPatchSource* lpatch, QWidget* parent) : QWidget(parent) , m_lpatch(lpatch) , m_ui(new Ui::LocalPatchWidget) { m_ui->setupUi(this); m_ui->baseDir->setMode( KFile::Directory ); syncPatch(); connect(m_lpatch, &LocalPatchSource::patchChanged, this, &LocalPatchWidget::syncPatch); } void LocalPatchWidget::syncPatch() { m_ui->command->setText( m_lpatch->command()); m_ui->filename->setUrl( m_lpatch->file() ); m_ui->baseDir->setUrl( m_lpatch->baseDir() ); m_ui->applied->setCheckState( m_lpatch->isAlreadyApplied() ? Qt::Checked : Qt::Unchecked ); if ( m_lpatch->command().isEmpty() ) m_ui->tabWidget->setCurrentIndex( m_ui->tabWidget->indexOf( m_ui->fileTab ) ); else m_ui->tabWidget->setCurrentIndex( m_ui->tabWidget->indexOf( m_ui->commandTab ) ); } void LocalPatchWidget::updatePatchFromEdit() { m_lpatch->setCommand(m_ui->command->text()); m_lpatch->setFilename(m_ui->filename->url()); m_lpatch->setBaseDir(m_ui->baseDir->url()); m_lpatch->setAlreadyApplied(m_ui->applied->checkState() == Qt::Checked); } diff --git a/plugins/patchreview/patchhighlighter.cpp b/plugins/patchreview/patchhighlighter.cpp index bce21ace11..00cae2aa9a 100644 --- a/plugins/patchreview/patchhighlighter.cpp +++ b/plugins/patchreview/patchhighlighter.cpp @@ -1,626 +1,626 @@ /*************************************************************************** Copyright 2006 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "patchhighlighter.h" #include #include #include "patchreview.h" #include "debug.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 { QPointer currentTooltip; KTextEditor::MovingRange* currentTooltipMark; QSize sizeHintForHtml( QString html, QSize maxSize ) { QTextDocument doc; doc.setHtml( html ); QSize ret; if( doc.idealWidth() > maxSize.width() ) { doc.setPageSize( QSize( maxSize.width(), 30 ) ); ret.setWidth( maxSize.width() ); }else{ ret.setWidth( doc.idealWidth() ); } ret.setHeight( doc.size().height() ); if( ret.height() > maxSize.height() ) ret.setHeight( maxSize.height() ); return ret; } } void PatchHighlighter::showToolTipForMark( QPoint pos, KTextEditor::MovingRange* markRange) { if( currentTooltipMark == markRange && currentTooltip ) return; delete currentTooltip; //Got the difference Diff2::Difference* diff = m_differencesForRanges[markRange]; QString html; #if 0 if( diff->hasConflict() ) html += i18n( "Conflict
" ); #endif Diff2::DifferenceStringList lines; html += QLatin1String(""); if( diff->applied() ) { if( !m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Applied.
" ); if( isInsertion( diff ) ) { html += i18n( "Insertion
" ); } else { if( isRemoval( diff ) ) html += i18n( "Removal
" ); html += i18n( "Previous:
" ); lines = diff->sourceLines(); } } else { if( m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Reverted.
" ); if( isRemoval( diff ) ) { html += i18n( "Removal
" ); } else { if( isInsertion( diff ) ) html += i18n( "Insertion
" ); html += i18n( "Alternative:
" ); lines = diff->destinationLines(); } } html += QLatin1String("
"); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; uint currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { QString spanText = string.mid( currentPos, markers[b]->offset() - currentPos ).toHtmlEscaped(); if( markers[b]->type() == Diff2::Marker::End && ( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) ) { html += "" + spanText + ""; }else{ html += spanText; } currentPos = markers[b]->offset(); } html += string.mid( currentPos, string.length()-currentPos ).toHtmlEscaped(); html += QLatin1String("
"); } auto browser = new QTextBrowser; browser->setPalette( QApplication::palette() ); browser->setHtml( html ); int maxHeight = 500; browser->setMinimumSize( sizeHintForHtml( html, QSize( ( ICore::self()->uiController()->activeMainWindow()->width()*2 )/3, maxHeight ) ) ); browser->setMaximumSize( browser->minimumSize() + QSize( 10, 10 ) ); if( browser->minimumHeight() != maxHeight ) browser->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); QVBoxLayout* layout = new QVBoxLayout; layout->setMargin( 0 ); layout->addWidget( browser ); KDevelop::ActiveToolTip* tooltip = new KDevelop::ActiveToolTip( ICore::self()->uiController()->activeMainWindow(), pos + QPoint( 5, -browser->sizeHint().height() - 30 ) ); tooltip->setLayout( layout ); tooltip->resize( tooltip->sizeHint() + QSize( 10, 10 ) ); tooltip->move( pos - QPoint( 0, 20 + tooltip->height() ) ); tooltip->setHandleRect( QRect( pos - QPoint( 15, 15 ), pos + QPoint( 15, 15 ) ) ); currentTooltip = tooltip; currentTooltipMark = markRange; ActiveToolTip::showToolTip( tooltip ); } void PatchHighlighter::markClicked( KTextEditor::Document* doc, const KTextEditor::Mark& mark, bool& handled ) { m_applying = true; if( handled ) return; handled = true; // TODO: reconsider workaround // if( doc->activeView() ) ///This is a workaround, if the cursor is somewhere else, the editor will always jump there when a mark was clicked // doc->activeView()->setCursorPosition( KTextEditor::Cursor( mark.line, 0 ) ); KTextEditor::MovingRange* range = rangeForMark( mark ); if( range ) { QString currentText = doc->text( range->toRange() ); Diff2::Difference* diff = m_differencesForRanges[range]; removeLineMarker( range ); QString sourceText; QString targetText; for( int a = 0; a < diff->sourceLineCount(); ++a ) { sourceText += diff->sourceLineAt( a )->string(); if( !sourceText.endsWith( '\n' ) ) sourceText += '\n'; } for( int a = 0; a < diff->destinationLineCount(); ++a ) { targetText += diff->destinationLineAt( a )->string(); if( !targetText.endsWith( '\n' ) ) targetText += '\n'; } QString replace; QString replaceWith; if( !diff->applied() ) { replace = sourceText; replaceWith = targetText; }else { replace = targetText; replaceWith = sourceText; } if( currentText.simplified() != replace.simplified() ) { KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n( "Could not apply the change: Text should be \"%1\", but is \"%2\".", replace, currentText ) ); return; } diff->apply( !diff->applied() ); KTextEditor::Cursor start = range->start().toCursor(); range->document()->replaceText( range->toRange(), replaceWith ); uint replaceWithLines = replaceWith.count( '\n' ); KTextEditor::Range newRange( start, KTextEditor::Cursor(start.line() + replaceWithLines, start.column()) ); range->setRange( newRange ); addLineMarker( range, diff ); } { // After applying the change, show the tooltip again, mainly to update an old tooltip delete currentTooltip; bool h = false; markToolTipRequested( doc, mark, QCursor::pos(), h ); } m_applying = false; } KTextEditor::MovingRange* PatchHighlighter::rangeForMark( const KTextEditor::Mark& mark ) { for( QMap::const_iterator it = m_differencesForRanges.constBegin(); it != m_differencesForRanges.constEnd(); ++it ) { if( it.key()->start().line() == mark.line ) { return it.key(); } } - return 0; + return nullptr; } void PatchHighlighter::markToolTipRequested( KTextEditor::Document*, const KTextEditor::Mark& mark, QPoint pos, bool& handled ) { if( handled ) return; handled = true; int myMarksPattern = KTextEditor::MarkInterface::markType22 | KTextEditor::MarkInterface::markType23 | KTextEditor::MarkInterface::markType24 | KTextEditor::MarkInterface::markType25 | KTextEditor::MarkInterface::markType26 | KTextEditor::MarkInterface::markType27; if( mark.type & myMarksPattern ) { //There is a mark in this line. Show the old text. KTextEditor::MovingRange* range = rangeForMark( mark ); if( range ) showToolTipForMark( pos, range ); } } bool PatchHighlighter::isInsertion( Diff2::Difference* diff ) { return diff->sourceLineCount() == 0; } bool PatchHighlighter::isRemoval( Diff2::Difference* diff ) { return diff->destinationLineCount() == 0; } QStringList PatchHighlighter::splitAndAddNewlines( const QString& text ) const { QStringList result = text.split( '\n', QString::KeepEmptyParts ); for( QStringList::iterator iter = result.begin(); iter != result.end(); ++iter ) { iter->append( '\n' ); } if ( !result.isEmpty() ) { QString & last = result.last(); last.remove( last.size() - 1, 1 ); } return result; } void PatchHighlighter::performContentChange( KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber ) { QPair, QList > diffChange = m_model->linesChanged( oldLines, newLines, editLineNumber ); QList inserted = diffChange.first; QList removed = diffChange.second; // Remove all ranges that are in the same line (the line markers) foreach( KTextEditor::MovingRange* r, m_differencesForRanges.keys() ) { Diff2::Difference* diff = m_differencesForRanges[r]; if ( removed.contains( diff ) ) { removeLineMarker( r ); m_ranges.remove( r ); m_differencesForRanges.remove( r ); delete r; delete diff; } } KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; foreach( Diff2::Difference* diff, inserted ) { int lineStart = diff->destinationLineNumber(); if ( lineStart > 0 ) { --lineStart; } int lineEnd = diff->destinationLineEnd(); if ( lineEnd > 0 ) { --lineEnd; } KTextEditor::Range newRange( lineStart, 0, lineEnd, 0 ); KTextEditor::MovingRange * r = moving->newMovingRange( newRange ); m_differencesForRanges[r] = diff; m_ranges.insert( r ); addLineMarker( r, diff ); } } void PatchHighlighter::textRemoved( KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& oldText ) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "removal range" << range; qCDebug(PLUGIN_PATCHREVIEW) << "removed text" << oldText; QStringList removedLines = splitAndAddNewlines( oldText ); int startLine = range.start().line(); QString remainingLine = doc->line( startLine ); remainingLine += '\n'; QString prefix = remainingLine.mid( 0, range.start().column() ); QString suffix = remainingLine.mid( range.start().column() ); if ( !removedLines.empty() ) { removedLines.first() = prefix + removedLines.first(); removedLines.last() = removedLines.last() + suffix; } performContentChange( doc, removedLines, QStringList() << remainingLine, startLine + 1 ); } void PatchHighlighter::highlightFromScratch(KTextEditor::Document* doc) { qCDebug(PLUGIN_PATCHREVIEW) << "re-doing"; //The document was loaded / reloaded if ( !m_model->differences() ) return; KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( doc ); if( !markIface ) return; clear(); KColorScheme scheme( QPalette::Active ); QImage tintedInsertion = QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedInsertion, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); QImage tintedRemoval = QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedRemoval, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); QImage tintedChange = QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedChange, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType22, i18n( "Insertion" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType22, QPixmap::fromImage( tintedInsertion ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType23, i18n( "Removal" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType23, QPixmap::fromImage( tintedRemoval ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType24, i18n( "Change" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType24, QPixmap::fromImage( tintedChange ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType25, i18n( "Insertion" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType25, QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType26, i18n( "Removal" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType26, QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType27, i18n( "Change" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType27, QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ) ); for ( Diff2::DifferenceList::const_iterator it = m_model->differences()->constBegin(); it != m_model->differences()->constEnd(); ++it ) { Diff2::Difference* diff = *it; int line, lineCount; Diff2::DifferenceStringList lines; if( diff->applied() ) { line = diff->destinationLineNumber(); lineCount = diff->destinationLineCount(); lines = diff->destinationLines(); } else { line = diff->sourceLineNumber(); lineCount = diff->sourceLineCount(); lines = diff->sourceLines(); } if ( line > 0 ) line -= 1; KTextEditor::Cursor c( line, 0 ); KTextEditor::Cursor endC( line + lineCount, 0 ); if ( doc->lines() <= c.line() ) c.setLine( doc->lines() - 1 ); if ( doc->lines() <= endC.line() ) endC.setLine( doc->lines() ); if ( endC.isValid() && c.isValid() ) { KTextEditor::MovingRange * r = moving->newMovingRange( KTextEditor::Range( c, endC ) ); m_ranges << r; m_differencesForRanges[r] = *it; addLineMarker( r, diff ); } } } void PatchHighlighter::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { KTextEditor::Range range(cursor, KTextEditor::Cursor(text.count('\n'), text.size()-text.lastIndexOf('\n')+1)); if( range == doc->documentRange() ) { highlightFromScratch(doc); } else { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "insertion range" << range; QString text = doc->text( range ); qCDebug(PLUGIN_PATCHREVIEW) << "inserted text" << text; QStringList insertedLines = splitAndAddNewlines( text ); int startLine = range.start().line(); int endLine = range.end().line(); QString prefix = doc->line( startLine ).mid( 0, range.start().column() ); QString suffix = doc->line( endLine ).mid( range.end().column() ); suffix += '\n'; QString removedLine = prefix + suffix; if ( !insertedLines.empty() ) { insertedLines.first() = prefix + insertedLines.first(); insertedLines.last() = insertedLines.last() + suffix; } performContentChange( doc, QStringList() << removedLine, insertedLines, startLine + 1 ); } } PatchHighlighter::PatchHighlighter( Diff2::DiffModel* model, IDocument* kdoc, PatchReviewPlugin* plugin, bool updatePatchFromEdits ) throw( QString ) : m_doc( kdoc ), m_plugin( plugin ), m_model( model ), m_applying( false ) { KTextEditor::Document* doc = kdoc->textDocument(); // connect( kdoc, SIGNAL(destroyed(QObject*)), this, SLOT(documentDestroyed()) ); if (updatePatchFromEdits) { connect(doc, &KTextEditor::Document::textInserted, this, &PatchHighlighter::textInserted); connect(doc, &KTextEditor::Document::textRemoved, this, &PatchHighlighter::textRemoved); } connect(doc, &KTextEditor::Document::destroyed, this, &PatchHighlighter::documentDestroyed); if ( doc->lines() == 0 ) return; if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MarkInterface is not a QObject connect(doc, SIGNAL(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), this, SLOT(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool &))); connect(doc, SIGNAL(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)), this, SLOT(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&))); } if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MovingInterface is not a QObject connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT( aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); } highlightFromScratch(doc); } void PatchHighlighter::removeLineMarker( KTextEditor::MovingRange* range ) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType22 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType23 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType24 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType25 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType26 ); markIface->removeMark( range->start().line(), KTextEditor::MarkInterface::markType27 ); // Remove all ranges that are in the same line (the line markers) foreach( KTextEditor::MovingRange* r, m_ranges ) { if( r != range && range->contains( r->toRange() ) ) { delete r; m_ranges.remove( r ); m_differencesForRanges.remove( r ); } } } void PatchHighlighter::addLineMarker( KTextEditor::MovingRange* range, Diff2::Difference* diff ) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); bool isOriginalState = diff->applied() == m_plugin->patch()->isAlreadyApplied(); if( isOriginalState ) { t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 0, 255, 255 ), 20 ) ) ); }else{ t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 255 ), 20 ) ) ); } range->setAttribute( t ); range->setZDepth( -500 ); KTextEditor::MarkInterface::MarkTypes mark; if( isOriginalState ) { mark = KTextEditor::MarkInterface::markType27; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType25; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType26; }else{ mark = KTextEditor::MarkInterface::markType24; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType22; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType23; } markIface->addMark( range->start().line(), mark ); Diff2::DifferenceStringList lines; if( diff->applied() ) lines = diff->destinationLines(); else lines = diff->sourceLines(); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; int currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { if( markers[b]->type() == Diff2::Marker::End ) { if( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) { KTextEditor::MovingRange * r2 = moving->newMovingRange( KTextEditor::Range( KTextEditor::Cursor( a + range->start().line(), currentPos ), KTextEditor::Cursor( a + range->start().line(), markers[b]->offset() ) ) ); m_ranges << r2; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 0 ), 70 ) ) ); r2->setAttribute( t ); r2->setZDepth( -600 ); } } currentPos = markers[b]->offset(); } } } void PatchHighlighter::clear() { if( m_ranges.empty() ) return; KTextEditor::MovingInterface* moving = dynamic_cast( m_doc->textDocument() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( m_doc->textDocument() ); if( !markIface ) return; QHash marks = markIface->marks(); foreach( int line, marks.keys() ) { markIface->removeMark( line, KTextEditor::MarkInterface::markType22 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType23 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType24 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType25 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType26 ); markIface->removeMark( line, KTextEditor::MarkInterface::markType27 ); } qDeleteAll( m_ranges ); m_ranges.clear(); m_differencesForRanges.clear(); } PatchHighlighter::~PatchHighlighter() { clear(); } IDocument* PatchHighlighter::doc() { return m_doc; } void PatchHighlighter::documentDestroyed() { qCDebug(PLUGIN_PATCHREVIEW) << "document destroyed"; m_ranges.clear(); m_differencesForRanges.clear(); } void PatchHighlighter::aboutToDeleteMovingInterfaceContent( KTextEditor::Document* ) { qCDebug(PLUGIN_PATCHREVIEW) << "about to delete"; clear(); } QList< KTextEditor::MovingRange* > PatchHighlighter::ranges() const { return m_differencesForRanges.keys(); } diff --git a/plugins/patchreview/patchreview.cpp b/plugins/patchreview/patchreview.cpp index 1ddec7f7a6..a7e5a729b7 100644 --- a/plugins/patchreview/patchreview.cpp +++ b/plugins/patchreview/patchreview.cpp @@ -1,626 +1,626 @@ /*************************************************************************** Copyright 2006-2009 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "patchreview.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 ///Whether arbitrary exceptions that occurred while diff-parsing within the library should be caught #define CATCHLIBDIFF /* Exclude this file from doublequote_chars check as krazy doesn't understand std::string*/ //krazy:excludeall=doublequote_chars #include #include #include #include #include #include "patchhighlighter.h" #include "patchreviewtoolview.h" #include "localpatchsource.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_PATCHREVIEW, "kdevplatform.plugins.patchreview") using namespace KDevelop; namespace { // Maximum number of files to open directly within a tab when the review is started const int maximumFilesToOpenDirectly = 15; } Q_DECLARE_METATYPE( const Diff2::DiffModel* ) void PatchReviewPlugin::seekHunk( bool forwards, const QUrl& fileName ) { try { qCDebug(PLUGIN_PATCHREVIEW) << forwards << fileName << fileName.isEmpty(); if ( !m_modelList ) throw "no model"; for ( int a = 0; a < m_modelList->modelCount(); ++a ) { const Diff2::DiffModel* model = m_modelList->modelAt( a ); if ( !model || !model->differences() ) continue; QUrl file = urlForFileModel( model ); if ( !fileName.isEmpty() && fileName != file ) continue; IDocument* doc = ICore::self()->documentController()->documentForUrl( file ); if ( doc && m_highlighters.contains( doc->url() ) && m_highlighters[doc->url()] ) { if ( doc->textDocument() ) { const QList ranges = m_highlighters[doc->url()]->ranges(); KTextEditor::View * v = doc->activeTextView(); int bestLine = -1; if ( v ) { KTextEditor::Cursor c = v->cursorPosition(); for ( QList::const_iterator it = ranges.begin(); it != ranges.end(); ++it ) { int line = ( *it )->start().line(); if ( forwards ) { if ( line > c.line() && ( bestLine == -1 || line < bestLine ) ) bestLine = line; } else { if ( line < c.line() && ( bestLine == -1 || line > bestLine ) ) bestLine = line; } } if ( bestLine != -1 ) { v->setCursorPosition( KTextEditor::Cursor( bestLine, 0 ) ); return; } else if(fileName.isEmpty()) { int next = qBound(0, forwards ? a+1 : a-1, m_modelList->modelCount()-1); ICore::self()->documentController()->openDocument(urlForFileModel(m_modelList->modelAt(next))); } } } } } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } qCDebug(PLUGIN_PATCHREVIEW) << "no matching hunk found"; } void PatchReviewPlugin::addHighlighting( const QUrl& highlightFile, IDocument* document ) { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); if ( file != highlightFile ) continue; qCDebug(PLUGIN_PATCHREVIEW) << "highlighting" << file.toDisplayString(); IDocument* doc = document; if( !doc ) doc = ICore::self()->documentController()->documentForUrl( file ); qCDebug(PLUGIN_PATCHREVIEW) << "highlighting file" << file << "with doc" << doc; if ( !doc || !doc->textDocument() ) continue; removeHighlighting( file ); m_highlighters[file] = new PatchHighlighter( model, doc, this, dynamic_cast(m_patch.data()) == nullptr ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::highlightPatch() { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { const Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); addHighlighting( file ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::removeHighlighting( const QUrl& file ) { if ( file.isEmpty() ) { ///Remove all highlighting qDeleteAll( m_highlighters ); m_highlighters.clear(); } else { HighlightMap::iterator it = m_highlighters.find( file ); if ( it != m_highlighters.end() ) { delete *it; m_highlighters.erase( it ); } } } void PatchReviewPlugin::notifyPatchChanged() { if (m_patch) { qCDebug(PLUGIN_PATCHREVIEW) << "notifying patch change: " << m_patch->file(); m_updateKompareTimer->start( 500 ); } else { m_updateKompareTimer->stop(); } } void PatchReviewPlugin::forceUpdate() { if( m_patch ) { m_patch->update(); notifyPatchChanged(); } } void PatchReviewPlugin::updateKompareModel() { if ( !m_patch ) { ///TODO: this method should be cleaned up, it can be called by the timer and /// e.g. https://bugs.kde.org/show_bug.cgi?id=267187 shows how it could /// lead to asserts before... return; } qCDebug(PLUGIN_PATCHREVIEW) << "updating model"; removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; delete m_diffSettings; { IDocument* patchDoc = ICore::self()->documentController()->documentForUrl( m_patch->file() ); if( patchDoc ) patchDoc->reload(); } QString patchFile; if( m_patch->file().isLocalFile() ) patchFile = m_patch->file().toLocalFile(); else if( m_patch->file().isValid() && !m_patch->file().isEmpty() ) { patchFile = QStandardPaths::writableLocation(QStandardPaths::TempLocation); bool ret = KIO::copy( m_patch->file(), QUrl::fromLocalFile(patchFile) )->exec(); if( !ret ) { qWarning() << "Problem while downloading: " << m_patch->file() << "to" << patchFile; patchFile.clear(); } } if (!patchFile.isEmpty()) //only try to construct the model if we have a patch to load try { - m_diffSettings = new DiffSettings( 0 ); + m_diffSettings = new DiffSettings( nullptr ); m_kompareInfo.reset( new Kompare::Info() ); m_kompareInfo->localDestination = patchFile; m_kompareInfo->localSource = m_patch->baseDir().toLocalFile(); m_kompareInfo->depth = m_patch->depth(); m_kompareInfo->applied = m_patch->isAlreadyApplied(); m_modelList.reset( new Diff2::KompareModelList( m_diffSettings.data(), new QWidget, this ) ); m_modelList->slotKompareInfo( m_kompareInfo.data() ); try { m_modelList->openDirAndDiff(); } catch ( const QString & str ) { throw; } catch ( ... ) { throw QStringLiteral( "lib/libdiff2 crashed, memory may be corrupted. Please restart kdevelop." ); } for (m_depth = 0; m_depth < 10; ++m_depth) { bool allFound = true; for( int i = 0; i < m_modelList->modelCount(); i++ ) { if (!QFile::exists(urlForFileModel(m_modelList->modelAt(i)).toLocalFile())) { allFound = false; } } if (allFound) { break; // found depth } } emit patchChanged(); for( int i = 0; i < m_modelList->modelCount(); i++ ) { const Diff2::DiffModel* model = m_modelList->modelAt( i ); for( int j = 0; j < model->differences()->count(); j++ ) { model->differences()->at( j )->apply( m_patch->isAlreadyApplied() ); } } highlightPatch(); return; } catch ( const QString & str ) { - KMessageBox::error( 0, str, i18n( "Kompare Model Update" ) ); + KMessageBox::error( nullptr, str, i18n( "Kompare Model Update" ) ); } catch ( const char * str ) { - KMessageBox::error( 0, str, i18n( "Kompare Model Update" ) ); + KMessageBox::error( nullptr, str, i18n( "Kompare Model Update" ) ); } removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; m_kompareInfo.reset( nullptr ); delete m_diffSettings; emit patchChanged(); } K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevpatchreview.json", registerPlugin();) class PatchReviewToolViewFactory : public KDevelop::IToolViewFactory { public: PatchReviewToolViewFactory( PatchReviewPlugin *plugin ) : m_plugin( plugin ) {} - QWidget* create( QWidget *parent = 0 ) override { + QWidget* create( QWidget *parent = nullptr ) override { return new PatchReviewToolView( parent, m_plugin ); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.PatchReview"); } private: PatchReviewPlugin *m_plugin; }; PatchReviewPlugin::~PatchReviewPlugin() { removeHighlighting(); // Tweak to work around a crash on OS X; see https://bugs.kde.org/show_bug.cgi?id=338829 // and http://qt-project.org/forums/viewthread/38406/#162801 // modified tweak: use setPatch() and deleteLater in that method. - setPatch(0); + setPatch(nullptr); } void PatchReviewPlugin::clearPatch( QObject* _patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "clearing patch" << _patch << "current:" << ( QObject* )m_patch; IPatchSource::Ptr patch( ( IPatchSource* )_patch ); if( patch == m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "is current patch"; setPatch( IPatchSource::Ptr( new LocalPatchSource ) ); } } void PatchReviewPlugin::closeReview() { if( m_patch ) { IDocument* patchDocument = ICore::self()->documentController()->documentForUrl( m_patch->file() ); if (patchDocument) { // Revert modifications to the text document which we've done in updateReview patchDocument->setPrettyName( QString() ); patchDocument->textDocument()->setReadWrite( true ); KTextEditor::ModificationInterface* modif = dynamic_cast( patchDocument->textDocument() ); modif->setModifiedOnDiskWarning( true ); } removeHighlighting(); - m_modelList.reset( 0 ); + m_modelList.reset( nullptr ); m_depth = 0; if( !dynamic_cast( m_patch.data() ) ) { // make sure "show" button still openes the file dialog to open a custom patch file setPatch( new LocalPatchSource ); } else emit patchChanged(); Sublime::Area* area = ICore::self()->uiController()->activeArea(); if( area->objectName() == QLatin1String("review") ) { if( ICore::self()->documentController()->saveAllDocuments() ) ICore::self()->uiController()->switchToArea( QStringLiteral("code"), KDevelop::IUiController::ThisWindow ); } } } void PatchReviewPlugin::cancelReview() { if( m_patch ) { m_patch->cancelReview(); closeReview(); } } void PatchReviewPlugin::finishReview( QList selection ) { if( m_patch && m_patch->finishReview( selection ) ) { closeReview(); } } void PatchReviewPlugin::startReview( IPatchSource* patch, IPatchReview::ReviewMode mode ) { Q_UNUSED( mode ); emit startingNewReview(); setPatch( patch ); QMetaObject::invokeMethod( this, "updateReview", Qt::QueuedConnection ); } void PatchReviewPlugin::switchToEmptyReviewArea() { foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == QLatin1String("review")) { area->clearDocuments(); } } if ( ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("review") ) ICore::self()->uiController()->switchToArea( QStringLiteral("review"), KDevelop::IUiController::ThisWindow ); } QUrl PatchReviewPlugin::urlForFileModel( const Diff2::DiffModel* model ) { KDevelop::Path path(QDir::cleanPath(m_patch->baseDir().toLocalFile())); QVector destPath = KDevelop::Path("/"+model->destinationPath()).segments(); if (destPath.size() >= (int)m_depth) { destPath = destPath.mid(m_depth); } foreach(QString segment, destPath) { path.addPath(segment); } path.addPath(model->destinationFile()); return path.toUrl(); } void PatchReviewPlugin::updateReview() { if( !m_patch ) return; m_updateKompareTimer->stop(); switchToEmptyReviewArea(); IDocument* futureActiveDoc = ICore::self()->documentController()->openDocument( m_patch->file() ); updateKompareModel(); if ( !m_modelList || !futureActiveDoc || !futureActiveDoc->textDocument() ) { // might happen if e.g. openDocument dialog was cancelled by user // or under the theoretic possibility of a non-text document getting opened return; } futureActiveDoc->textDocument()->setReadWrite( false ); futureActiveDoc->setPrettyName( i18n( "Overview" ) ); KTextEditor::ModificationInterface* modif = dynamic_cast( futureActiveDoc->textDocument() ); modif->setModifiedOnDiskWarning( false ); Q_ASSERT( futureActiveDoc ); ICore::self()->documentController()->activateDocument( futureActiveDoc ); PatchReviewToolView* toolView = qobject_cast(ICore::self()->uiController()->findToolView( i18n( "Patch Review" ), m_factory )); Q_ASSERT( toolView ); if( m_modelList->modelCount() < maximumFilesToOpenDirectly ) { //Open all relates files for( int a = 0; a < m_modelList->modelCount(); ++a ) { QUrl absoluteUrl = urlForFileModel( m_modelList->modelAt( a ) ); if (absoluteUrl.isRelative()) { - KMessageBox::error( 0, i18n("The base directory of the patch must be an absolute directory"), i18n( "Patch Review" ) ); + KMessageBox::error( nullptr, i18n("The base directory of the patch must be an absolute directory"), i18n( "Patch Review" ) ); break; } if( QFileInfo::exists( absoluteUrl.toLocalFile() ) && absoluteUrl.toLocalFile() != QLatin1String("/dev/null") ) { toolView->open( absoluteUrl, false ); }else{ // Maybe the file was deleted qCDebug(PLUGIN_PATCHREVIEW) << "could not open" << absoluteUrl << "because it doesn't exist"; } } } } void PatchReviewPlugin::setPatch( IPatchSource* patch ) { if ( patch == m_patch ) { return; } if( m_patch ) { disconnect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); if ( qobject_cast( m_patch ) ) { // make sure we don't leak this // TODO: what about other patch sources? m_patch->deleteLater(); } } m_patch = patch; if( m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "setting new patch" << patch->name() << "with file" << patch->file() << "basedir" << patch->baseDir(); connect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); } QString finishText = i18n( "Finish Review" ); if( m_patch && !m_patch->finishReviewCustomText().isEmpty() ) finishText = m_patch->finishReviewCustomText(); m_finishReview->setText( finishText ); m_finishReview->setEnabled( patch ); notifyPatchChanged(); } PatchReviewPlugin::PatchReviewPlugin( QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevpatchreview"), parent ), - m_patch( 0 ), m_factory( new PatchReviewToolViewFactory( this ) ) { + m_patch( nullptr ), m_factory( new PatchReviewToolViewFactory( this ) ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IPatchReview ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::ILanguageSupport ) qRegisterMetaType( "const Diff2::DiffModel*" ); setXMLFile( QStringLiteral("kdevpatchreview.rc") ); connect( ICore::self()->documentController(), &IDocumentController::documentClosed, this, &PatchReviewPlugin::documentClosed ); connect( ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &PatchReviewPlugin::textDocumentCreated ); connect( ICore::self()->documentController(), &IDocumentController::documentSaved, this, &PatchReviewPlugin::documentSaved ); m_updateKompareTimer = new QTimer( this ); m_updateKompareTimer->setSingleShot( true ); connect( m_updateKompareTimer, &QTimer::timeout, this, &PatchReviewPlugin::updateKompareModel ); m_finishReview = new QAction(i18n("Finish Review"), this); m_finishReview->setIcon( QIcon::fromTheme( QStringLiteral("dialog-ok") ) ); actionCollection()->setDefaultShortcut( m_finishReview, Qt::CTRL|Qt::Key_Return ); actionCollection()->addAction(QStringLiteral("commit_or_finish_review"), m_finishReview); foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == QLatin1String("review")) area->addAction(m_finishReview); } core()->uiController()->addToolView( i18n( "Patch Review" ), m_factory, IUiController::None ); areaChanged(ICore::self()->uiController()->activeArea()); } void PatchReviewPlugin::documentClosed( IDocument* doc ) { removeHighlighting( doc->url() ); } void PatchReviewPlugin::documentSaved( IDocument* doc ) { // Only update if the url is not the patch-file, because our call to // the reload() KTextEditor function also causes this signal, // which would lead to an endless update loop. // Also, don't automatically update local patch sources, because // they may correspond to static files which don't match any more // after an edit was done. if( m_patch && doc->url() != m_patch->file() && !dynamic_cast(m_patch.data()) ) forceUpdate(); } void PatchReviewPlugin::textDocumentCreated( IDocument* doc ) { if (m_patch) { addHighlighting( doc->url(), doc ); } } void PatchReviewPlugin::unload() { core()->uiController()->removeToolView( m_factory ); KDevelop::IPlugin::unload(); } void PatchReviewPlugin::areaChanged(Sublime::Area* area) { bool reviewing = area->objectName() == QLatin1String("review"); m_finishReview->setEnabled(reviewing); if(!reviewing) { closeReview(); } } KDevelop::ContextMenuExtension PatchReviewPlugin::contextMenuExtension( KDevelop::Context* context ) { QList urls; if ( context->type() == KDevelop::Context::FileContext ) { KDevelop::FileContext* filectx = dynamic_cast( context ); urls = filectx->urls(); } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* projctx = dynamic_cast( context ); foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { if ( item->file() ) { urls << item->file()->path().toUrl(); } } } else if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast( context ); urls << econtext->url(); } if (urls.size() == 1) { QString mimetype = QMimeDatabase().mimeTypeForUrl(urls.first()).name(); QAction* reviewAction = new QAction( i18n( "Review Patch" ), this ); reviewAction->setData(QVariant(urls[0])); connect( reviewAction, &QAction::triggered, this, &PatchReviewPlugin::executeFileReviewAction ); ContextMenuExtension cm; cm.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, reviewAction ); return cm; } return KDevelop::IPlugin::contextMenuExtension( context ); } void PatchReviewPlugin::executeFileReviewAction() { QAction* reviewAction = qobject_cast(sender()); KDevelop::Path path(reviewAction->data().toUrl()); LocalPatchSource* ps = new LocalPatchSource(); ps->setFilename(path.toUrl()); ps->setBaseDir(path.parent().toUrl()); ps->setAlreadyApplied(true); ps->createWidget(); startReview(ps, OpenAndRaise); } #include "patchreview.moc" // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on diff --git a/plugins/patchreview/patchreviewtoolview.cpp b/plugins/patchreview/patchreviewtoolview.cpp index de5280000e..e97cdfd535 100644 --- a/plugins/patchreview/patchreviewtoolview.cpp +++ b/plugins/patchreview/patchreviewtoolview.cpp @@ -1,597 +1,597 @@ /*************************************************************************** Copyright 2006-2009 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "patchreviewtoolview.h" #include "localpatchsource.h" #include "patchreview.h" #include "debug.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 #ifdef WITH_PURPOSE #include #include #endif using namespace KDevelop; class PatchFilesModel : public VcsFileChangesModel { Q_OBJECT public: PatchFilesModel( QObject *parent, bool allowSelection ) : VcsFileChangesModel( parent, allowSelection ) { }; enum ItemRoles { HunksNumberRole = LastItemRole+1 }; public slots: void updateState( const KDevelop::VcsStatusInfo &status, unsigned hunksNum ) { int row = VcsFileChangesModel::updateState( invisibleRootItem(), status ); if ( row == -1 ) return; QStandardItem *item = invisibleRootItem()->child( row, 0 ); setFileInfo( item, hunksNum ); item->setData( QVariant( hunksNum ), HunksNumberRole ); } void updateState( const KDevelop::VcsStatusInfo &status ) { int row = VcsFileChangesModel::updateState( invisibleRootItem(), status ); if ( row == -1 ) return; QStandardItem *item = invisibleRootItem()->child( row, 0 ); setFileInfo( invisibleRootItem()->child( row, 0 ), item->data( HunksNumberRole ).toUInt() ); } private: void setFileInfo( QStandardItem *item, unsigned int hunksNum ) { const auto url = item->index().data(VcsFileChangesModel::UrlRole).toUrl(); const QString path = ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain); const QString newText = i18ncp( "%1: number of changed hunks, %2: file name", "%2 (1 hunk)", "%2 (%1 hunks)", hunksNum, path); item->setText( newText ); } }; PatchReviewToolView::PatchReviewToolView( QWidget* parent, PatchReviewPlugin* plugin ) : QWidget( parent ), m_resetCheckedUrls( true ), m_plugin( plugin ) { connect( m_plugin->finishReviewAction(), &QAction::triggered, this, &PatchReviewToolView::finishReview ); connect( plugin, &PatchReviewPlugin::patchChanged, this, &PatchReviewToolView::patchChanged ); connect( plugin, &PatchReviewPlugin::startingNewReview, this, &PatchReviewToolView::startingNewReview ); connect( ICore::self()->documentController(), &IDocumentController::documentActivated, this, &PatchReviewToolView::documentActivated ); Sublime::MainWindow* w = dynamic_cast( ICore::self()->uiController()->activeMainWindow() ); connect(w, &Sublime::MainWindow::areaChanged, m_plugin, &PatchReviewPlugin::areaChanged); showEditDialog(); patchChanged(); } void PatchReviewToolView::resizeEvent(QResizeEvent* ev) { bool vertical = (width() < height()); m_editPatch.buttonsLayout->setDirection(vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); m_editPatch.contentLayout->setDirection(vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); m_editPatch.buttonsSpacer->changeSize(vertical ? 0 : 40, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); QWidget::resizeEvent(ev); if(m_customWidget) { m_editPatch.contentLayout->removeWidget( m_customWidget ); m_editPatch.contentLayout->insertWidget(0, m_customWidget ); } } void PatchReviewToolView::startingNewReview() { m_resetCheckedUrls = true; } void PatchReviewToolView::patchChanged() { fillEditFromPatch(); kompareModelChanged(); #ifdef WITH_PURPOSE IPatchSource::Ptr p = m_plugin->patch(); if (p) { m_exportMenu->model()->setInputData(QJsonObject { { QStringLiteral("urls"), QJsonArray { p->file().toString() } }, { QStringLiteral("mimeType"), { QStringLiteral("text/x-patch") } }, { QStringLiteral("localBaseDir"), { p->baseDir().toString() } } }); } #endif } PatchReviewToolView::~PatchReviewToolView() { } LocalPatchSource* PatchReviewToolView::GetLocalPatchSource() { IPatchSource::Ptr ips = m_plugin->patch(); if ( !ips ) - return 0; + return nullptr; return dynamic_cast( ips.data() ); } void PatchReviewToolView::fillEditFromPatch() { IPatchSource::Ptr ipatch = m_plugin->patch(); if ( !ipatch ) return; m_editPatch.cancelReview->setVisible( ipatch->canCancel() ); m_fileModel->setIsCheckbable( m_plugin->patch()->canSelectFiles() ); if( m_customWidget ) { qCDebug(PLUGIN_PATCHREVIEW) << "removing custom widget"; m_customWidget->hide(); m_editPatch.contentLayout->removeWidget( m_customWidget ); } m_customWidget = ipatch->customWidget(); if( m_customWidget ) { m_editPatch.contentLayout->insertWidget( 0, m_customWidget ); m_customWidget->show(); qCDebug(PLUGIN_PATCHREVIEW) << "got custom widget"; } bool showTests = false; - IProject* project = 0; + IProject* project = nullptr; QMap files = ipatch->additionalSelectableFiles(); QMap::const_iterator it = files.constBegin(); for (; it != files.constEnd(); ++it) { project = ICore::self()->projectController()->findProjectForUrl(it.key()); if (project && !ICore::self()->testController()->testSuitesForProject(project).isEmpty()) { showTests = true; break; } } m_editPatch.testsButton->setVisible(showTests); m_editPatch.testProgressBar->hide(); } void PatchReviewToolView::slotAppliedChanged( int newState ) { if ( LocalPatchSource* lpatch = GetLocalPatchSource() ) { lpatch->setAlreadyApplied( newState == Qt::Checked ); m_plugin->notifyPatchChanged(); } } void PatchReviewToolView::showEditDialog() { m_editPatch.setupUi( this ); bool allowSelection = m_plugin->patch() && m_plugin->patch()->canSelectFiles(); m_fileModel = new PatchFilesModel( this, allowSelection ); m_fileSortProxyModel = new VcsFileChangesSortProxyModel(this); m_fileSortProxyModel->setSourceModel(m_fileModel); m_fileSortProxyModel->sort(1); m_fileSortProxyModel->setDynamicSortFilter(true); m_editPatch.filesList->setModel( m_fileSortProxyModel ); m_editPatch.filesList->header()->hide(); m_editPatch.filesList->setRootIsDecorated( false ); m_editPatch.filesList->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_editPatch.filesList, &QTreeView::customContextMenuRequested, this, &PatchReviewToolView::customContextMenuRequested); connect(m_fileModel, &PatchFilesModel::itemChanged, this, &PatchReviewToolView::fileItemChanged); m_editPatch.previousFile->setIcon( QIcon::fromTheme( QStringLiteral("arrow-left") ) ); m_editPatch.previousHunk->setIcon( QIcon::fromTheme( QStringLiteral("arrow-up") ) ); m_editPatch.nextHunk->setIcon( QIcon::fromTheme( QStringLiteral("arrow-down") ) ); m_editPatch.nextFile->setIcon( QIcon::fromTheme( QStringLiteral("arrow-right") ) ); m_editPatch.cancelReview->setIcon( QIcon::fromTheme( QStringLiteral("dialog-cancel") ) ); m_editPatch.updateButton->setIcon( QIcon::fromTheme( QStringLiteral("view-refresh") ) ); m_editPatch.testsButton->setIcon( QIcon::fromTheme( QStringLiteral("preflight-verifier") ) ); m_editPatch.finishReview->setDefaultAction(m_plugin->finishReviewAction()); #ifdef WITH_PURPOSE m_exportMenu = new Purpose::Menu(this); connect(m_exportMenu, &Purpose::Menu::finished, this, [](const QJsonObject &output, int error, const QString &message) { if (error==0) { - KMessageBox::information(0, i18n("You can find the new request at:
%1
", output["url"].toString()), + KMessageBox::information(nullptr, i18n("You can find the new request at:
%1
", output["url"].toString()), QString(), QString(), KMessageBox::AllowLink); } else { QMessageBox::warning(nullptr, i18n("Error exporting"), i18n("Couldn't export the patch.\n%1", message)); } }); m_exportMenu->model()->setPluginType("Export"); m_editPatch.exportReview->setMenu( m_exportMenu ); #else m_editPatch.exportReview->setEnabled(false); #endif connect( m_editPatch.previousHunk, &QToolButton::clicked, this, &PatchReviewToolView::prevHunk ); connect( m_editPatch.nextHunk, &QToolButton::clicked, this, &PatchReviewToolView::nextHunk ); connect( m_editPatch.previousFile, &QToolButton::clicked, this, &PatchReviewToolView::prevFile ); connect( m_editPatch.nextFile, &QToolButton::clicked, this, &PatchReviewToolView::nextFile ); connect( m_editPatch.filesList, &QTreeView::activated , this, &PatchReviewToolView::fileDoubleClicked ); connect( m_editPatch.cancelReview, &QPushButton::clicked, m_plugin, &PatchReviewPlugin::cancelReview ); //connect( m_editPatch.cancelButton, SIGNAL(pressed()), this, SLOT(slotEditCancel()) ); //connect( this, SIGNAL(finished(int)), this, SLOT(slotEditDialogFinished(int)) ); connect( m_editPatch.updateButton, &QPushButton::clicked, m_plugin, &PatchReviewPlugin::forceUpdate ); connect( m_editPatch.testsButton, &QPushButton::clicked, this, &PatchReviewToolView::runTests ); m_selectAllAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-select-all")), i18n("Select All"), this ); connect( m_selectAllAction, &QAction::triggered, this, &PatchReviewToolView::selectAll ); m_deselectAllAction = new QAction( i18n("Deselect All"), this ); connect( m_deselectAllAction, &QAction::triggered, this, &PatchReviewToolView::deselectAll ); } void PatchReviewToolView::customContextMenuRequested(const QPoint& ) { QList urls; QModelIndexList selectionIdxs = m_editPatch.filesList->selectionModel()->selectedIndexes(); foreach(const QModelIndex& idx, selectionIdxs) { urls += idx.data(KDevelop::VcsFileChangesModel::UrlRole).toUrl(); } QPointer menu = new QMenu(m_editPatch.filesList); QList extensions; if(!urls.isEmpty()) { KDevelop::FileContext context(urls); extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); } QList vcsActions; foreach( const ContextMenuExtension& ext, extensions ) { vcsActions += ext.actions(ContextMenuExtension::VcsGroup); } menu->addAction(m_selectAllAction); menu->addAction(m_deselectAllAction); menu->addActions(vcsActions); if ( !menu->isEmpty() ) { menu->exec(QCursor::pos()); } delete menu; } void PatchReviewToolView::nextHunk() { IDocument* current = ICore::self()->documentController()->activeDocument(); if(current && current->textDocument()) m_plugin->seekHunk( true, current->textDocument()->url() ); } void PatchReviewToolView::prevHunk() { IDocument* current = ICore::self()->documentController()->activeDocument(); if(current && current->textDocument()) m_plugin->seekHunk( false, current->textDocument()->url() ); } void PatchReviewToolView::seekFile(bool forwards) { if(!m_plugin->patch()) return; QList checkedUrls = m_fileModel->checkedUrls(); QList allUrls = m_fileModel->urls(); IDocument* current = ICore::self()->documentController()->activeDocument(); if(!current || checkedUrls.empty()) return; qCDebug(PLUGIN_PATCHREVIEW) << "seeking direction" << forwards; int currentIndex = allUrls.indexOf(current->url()); QUrl newUrl; if((forwards && current->url() == checkedUrls.back()) || (!forwards && current->url() == checkedUrls[0])) { newUrl = m_plugin->patch()->file(); qCDebug(PLUGIN_PATCHREVIEW) << "jumping to patch"; } else if(current->url() == m_plugin->patch()->file() || currentIndex == -1) { if(forwards) newUrl = checkedUrls[0]; else newUrl = checkedUrls.back(); qCDebug(PLUGIN_PATCHREVIEW) << "jumping from patch"; } else { QSet checkedUrlsSet( checkedUrls.toSet() ); for(int offset = 1; offset < allUrls.size(); ++offset) { int pos; if(forwards) { pos = (currentIndex + offset) % allUrls.size(); }else{ pos = currentIndex - offset; if(pos < 0) pos += allUrls.size(); } if(checkedUrlsSet.contains(allUrls[pos])) { newUrl = allUrls[pos]; break; } } } if(newUrl.isValid()) { open( newUrl, true ); }else{ qCDebug(PLUGIN_PATCHREVIEW) << "found no valid target url"; } } void PatchReviewToolView::open( const QUrl& url, bool activate ) const { qCDebug(PLUGIN_PATCHREVIEW) << "activating url" << url; // If the document is already open in this area, just re-activate it if(KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url)) { foreach(Sublime::View* view, ICore::self()->uiController()->activeArea()->views()) { if(view->document() == dynamic_cast(doc)) { if (activate) { ICore::self()->documentController()->activateDocument(doc); } return; } } } QStandardItem* item = m_fileModel->itemForUrl( url ); IDocument* buddyDoc = nullptr; if (m_plugin->patch() && item) { for (int preRow = item->row() - 1; preRow >= 0; --preRow) { QStandardItem* preItem = m_fileModel->item(preRow); if (!m_fileModel->isCheckable() || preItem->checkState() == Qt::Checked) { // found valid predecessor, take it as buddy buddyDoc = ICore::self()->documentController()->documentForUrl(preItem->index().data(VcsFileChangesModel::UrlRole).toUrl()); if (buddyDoc) { break; } } } if (!buddyDoc) { buddyDoc = ICore::self()->documentController()->documentForUrl(m_plugin->patch()->file()); } } IDocument* newDoc = ICore::self()->documentController()->openDocument(url, KTextEditor::Range::invalid(), activate ? IDocumentController::DefaultMode : IDocumentController::DoNotActivate, QLatin1String(""), buddyDoc); - KTextEditor::View* view = 0; + KTextEditor::View* view = nullptr; if(newDoc) view = newDoc->activeTextView(); if(view && view->cursorPosition().line() == 0) m_plugin->seekHunk( true, url ); } void PatchReviewToolView::fileItemChanged( QStandardItem* item ) { if (item->column() != 0 || !m_plugin->patch()) return; QUrl url = item->index().data(VcsFileChangesModel::UrlRole).toUrl(); if (url.isEmpty()) return; KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if(m_fileModel->isCheckable() && item->checkState() != Qt::Checked) { // The file was deselected, so eventually close it if(doc && doc->state() == IDocument::Clean) { foreach(Sublime::View* view, ICore::self()->uiController()->activeArea()->views()) { if(view->document() == dynamic_cast(doc)) { ICore::self()->uiController()->activeArea()->closeView(view); return; } } } } else if (!doc) { // Maybe the file was unchecked before, or it was just loaded. open( url, false ); } } void PatchReviewToolView::nextFile() { seekFile(true); } void PatchReviewToolView::prevFile() { seekFile(false); } void PatchReviewToolView::deselectAll() { m_fileModel->setAllChecked(false); } void PatchReviewToolView::selectAll() { m_fileModel->setAllChecked(true); } void PatchReviewToolView::finishReview() { QList selectedUrls = m_fileModel->checkedUrls(); qCDebug(PLUGIN_PATCHREVIEW) << "finishing review with" << selectedUrls; m_plugin->finishReview( selectedUrls ); } void PatchReviewToolView::fileDoubleClicked( const QModelIndex& idx ) { const QUrl file = idx.data(VcsFileChangesModel::UrlRole).toUrl(); open( file, true ); } void PatchReviewToolView::kompareModelChanged() { QList oldCheckedUrls = m_fileModel->checkedUrls(); m_fileModel->clear(); if ( !m_plugin->modelList() ) return; QMap additionalUrls = m_plugin->patch()->additionalSelectableFiles(); const Diff2::DiffModelList* models = m_plugin->modelList()->models(); if( models ) { Diff2::DiffModelList::const_iterator it = models->constBegin(); for(; it != models->constEnd(); ++it ) { Diff2::DifferenceList * diffs = ( *it )->differences(); int cnt = 0; if ( diffs ) cnt = diffs->count(); const QUrl file = m_plugin->urlForFileModel( *it ); if( file.isLocalFile() && !QFileInfo( file.toLocalFile() ).isReadable() ) continue; VcsStatusInfo status; status.setUrl( file ); status.setState( cnt>0 ? VcsStatusInfo::ItemModified : VcsStatusInfo::ItemUpToDate ); m_fileModel->updateState( status, cnt ); } } for( QMap::const_iterator it = additionalUrls.constBegin(); it != additionalUrls.constEnd(); it++ ) { VcsStatusInfo status; status.setUrl( it.key() ); status.setState( it.value() ); m_fileModel->updateState( status ); } if(!m_resetCheckedUrls) m_fileModel->setCheckedUrls(oldCheckedUrls); else m_resetCheckedUrls = false; m_editPatch.filesList->resizeColumnToContents( 0 ); // Eventually select the active document documentActivated( ICore::self()->documentController()->activeDocument() ); } void PatchReviewToolView::documentActivated( IDocument* doc ) { if( !doc ) return; if ( !m_plugin->modelList() ) return; const auto matches = m_fileSortProxyModel->match( m_fileSortProxyModel->index(0, 0), VcsFileChangesModel::UrlRole, doc->url(), 1, Qt::MatchExactly); m_editPatch.filesList->setCurrentIndex(matches.value(0)); } void PatchReviewToolView::runTests() { IPatchSource::Ptr ipatch = m_plugin->patch(); if ( !ipatch ) { return; } - IProject* project = 0; + IProject* project = nullptr; QMap files = ipatch->additionalSelectableFiles(); QMap::const_iterator it = files.constBegin(); for (; it != files.constEnd(); ++it) { project = ICore::self()->projectController()->findProjectForUrl(it.key()); if (project) { break; } } if (!project) { return; } m_editPatch.testProgressBar->setFormat(i18n("Running tests: %p%")); m_editPatch.testProgressBar->setValue(0); m_editPatch.testProgressBar->show(); ProjectTestJob* job = new ProjectTestJob(project, this); connect(job, &ProjectTestJob::finished, this, &PatchReviewToolView::testJobResult); connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(testJobPercent(KJob*,ulong))); ICore::self()->runController()->registerJob(job); } void PatchReviewToolView::testJobPercent(KJob* job, ulong percent) { Q_UNUSED(job); m_editPatch.testProgressBar->setValue(percent); } void PatchReviewToolView::testJobResult(KJob* job) { ProjectTestJob* testJob = qobject_cast(job); if (!testJob) { return; } ProjectTestResult result = testJob->testResult(); QString format; if (result.passed > 0 && result.failed == 0 && result.error == 0) { format = i18np("Test passed", "All %1 tests passed", result.passed); } else { format = i18n("Test results: %1 passed, %2 failed, %3 errors", result.passed, result.failed, result.error); } m_editPatch.testProgressBar->setFormat(format); // Needed because some test jobs may raise their own output views ICore::self()->uiController()->raiseToolView(this); } #include "patchreviewtoolview.moc" diff --git a/plugins/problemreporter/problemreportermodel.cpp b/plugins/problemreporter/problemreportermodel.cpp index a2aa8f5661..743692fd3f 100644 --- a/plugins/problemreporter/problemreportermodel.cpp +++ b/plugins/problemreporter/problemreportermodel.cpp @@ -1,199 +1,199 @@ /* * Copyright 2007 Hamish Rodda * Copyright 2015 Laszlo Kis-Adam * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemreportermodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; const int ProblemReporterModel::MinTimeout = 1000; const int ProblemReporterModel::MaxTimeout = 5000; ProblemReporterModel::ProblemReporterModel(QObject* parent) : ProblemModel(parent, new FilteredProblemStore()) , m_showImports(false) { setFeatures(CanDoFullUpdate | CanShowImports | ScopeFilter | SeverityFilter); m_minTimer = new QTimer(this); m_minTimer->setInterval(MinTimeout); m_minTimer->setSingleShot(true); connect(m_minTimer, &QTimer::timeout, this, &ProblemReporterModel::timerExpired); m_maxTimer = new QTimer(this); m_maxTimer->setInterval(MaxTimeout); m_maxTimer->setSingleShot(true); connect(m_maxTimer, &QTimer::timeout, this, &ProblemReporterModel::timerExpired); connect(store(), &FilteredProblemStore::changed, this, &ProblemReporterModel::onProblemsChanged); connect(ICore::self()->languageController()->staticAssistantsManager(), &StaticAssistantsManager::problemsChanged, this, &ProblemReporterModel::onProblemsChanged); } ProblemReporterModel::~ProblemReporterModel() { } QVector ProblemReporterModel::problems(const KDevelop::IndexedString& url, bool showImports) const { QVector result; QSet visitedContexts; KDevelop::DUChainReadLocker lock; problemsInternal(KDevelop::DUChain::self()->chainForDocument(url), showImports, visitedContexts, result); return result; } QVector ProblemReporterModel::problems(const QSet& urls, bool showImports) const { QVector result; QSet visitedContexts; KDevelop::DUChainReadLocker lock; foreach (const KDevelop::IndexedString& url, urls) { problemsInternal(KDevelop::DUChain::self()->chainForDocument(url), showImports, visitedContexts, result); } return result; } void ProblemReporterModel::forceFullUpdate() { Q_ASSERT(thread() == QThread::currentThread()); QSet documents = store()->documents()->get(); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); foreach (const KDevelop::IndexedString& document, documents) { if (document.isEmpty()) continue; KDevelop::TopDUContext::Features updateType = KDevelop::TopDUContext::ForceUpdate; if (documents.size() == 1) updateType = KDevelop::TopDUContext::ForceUpdateRecursive; KDevelop::DUChain::self()->updateContextForUrl( document, (KDevelop::TopDUContext::Features)(updateType | KDevelop::TopDUContext::VisibleDeclarationsAndContexts)); } } void ProblemReporterModel::problemsInternal(KDevelop::TopDUContext* context, bool showImports, QSet& visitedContexts, QVector& result) const { if (!context || visitedContexts.contains(context)) { return; } ReferencedTopDUContext top(context); foreach (KDevelop::ProblemPointer p, DUChainUtils::allProblemsForContext(top)) { if (p && p->severity() <= store()->severity()) { result.append(p); } } visitedContexts.insert(context); if (showImports) { bool isProxy = context->parsingEnvironmentFile() && context->parsingEnvironmentFile()->isProxyContext(); foreach (const KDevelop::DUContext::Import& ctx, context->importedParentContexts()) { if (!ctx.indexedContext().indexedTopContext().isLoaded()) continue; - KDevelop::TopDUContext* topCtx = dynamic_cast(ctx.context(0)); + KDevelop::TopDUContext* topCtx = dynamic_cast(ctx.context(nullptr)); if (topCtx) { /// If we are starting at a proxy-context, only recurse into other proxy-contexts, because those contain the problems. if (!isProxy || (topCtx->parsingEnvironmentFile() && topCtx->parsingEnvironmentFile()->isProxyContext())) problemsInternal(topCtx, showImports, visitedContexts, result); } } } } void ProblemReporterModel::onProblemsChanged() { rebuildProblemList(); } void ProblemReporterModel::timerExpired() { m_minTimer->stop(); m_maxTimer->stop(); rebuildProblemList(); } void ProblemReporterModel::setCurrentDocument(KDevelop::IDocument* doc) { Q_ASSERT(thread() == QThread::currentThread()); beginResetModel(); QUrl currentDocument = doc->url(); /// Will trigger signal changed() if problems change store()->setCurrentDocument(KDevelop::IndexedString(currentDocument)); endResetModel(); } void ProblemReporterModel::problemsUpdated(const KDevelop::IndexedString& url) { Q_ASSERT(thread() == QThread::currentThread()); if (store()->documents()->get().contains(url)) { /// m_minTimer will expire in MinTimeout unless some other parsing job finishes in this period. m_minTimer->start(); /// m_maxTimer will expire unconditionally in MaxTimeout if (!m_maxTimer->isActive()) { m_maxTimer->start(); } } } void ProblemReporterModel::setShowImports(bool showImports) { if (m_showImports != showImports) { Q_ASSERT(thread() == QThread::currentThread()); m_showImports = showImports; rebuildProblemList(); } } void ProblemReporterModel::rebuildProblemList() { QVector problems; /// No locking here, because it may be called from an already locked context beginResetModel(); problems = this->problems(store()->documents()->get(), m_showImports); store()->setProblems(problems); endResetModel(); } diff --git a/plugins/problemreporter/problemreporterplugin.cpp b/plugins/problemreporter/problemreporterplugin.cpp index ee7227f507..6f54709fbd 100644 --- a/plugins/problemreporter/problemreporterplugin.cpp +++ b/plugins/problemreporter/problemreporterplugin.cpp @@ -1,213 +1,213 @@ /* * KDevelop Problem Reporter * * Copyright 2006 Adam Treat * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemreporterplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemhighlighter.h" #include "problemtreeview.h" #include "problemreportermodel.h" #include "language/assistant/staticassistantsmanager.h" #include #include #include #include #include #include #include "shell/problemmodelset.h" #include "problemsview.h" #include Q_LOGGING_CATEGORY(PLUGIN_PROBLEMREPORTER, "kdevplatform.plugins.problemreporter") K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevproblemreporter.json", registerPlugin();) using namespace KDevelop; class ProblemReporterFactory : public KDevelop::IToolViewFactory { public: - QWidget* create(QWidget* parent = 0) override + QWidget* create(QWidget* parent = nullptr) override { Q_UNUSED(parent); ProblemsView* v = new ProblemsView(); v->load(); return v; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ProblemReporterView"); } }; ProblemReporterPlugin::ProblemReporterPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevproblemreporter"), parent) , m_factory(new ProblemReporterFactory) , m_model(new ProblemReporterModel(this)) { KDevelop::ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->addModel(QStringLiteral("Parser"), m_model); core()->uiController()->addToolView(i18n("Problems"), m_factory); setXMLFile(QStringLiteral("kdevproblemreporter.rc")); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemReporterPlugin::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &ProblemReporterPlugin::textDocumentCreated); connect(DUChain::self(), &DUChain::updateReady, this, &ProblemReporterPlugin::updateReady); connect(ICore::self()->languageController()->staticAssistantsManager(), &StaticAssistantsManager::problemsChanged, this, &ProblemReporterPlugin::updateHighlight); connect(pms, &ProblemModelSet::showRequested, this, &ProblemReporterPlugin::showModel); } ProblemReporterPlugin::~ProblemReporterPlugin() { qDeleteAll(m_highlighters); } ProblemReporterModel* ProblemReporterPlugin::model() const { return m_model; } void ProblemReporterPlugin::unload() { KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); pms->removeModel(QStringLiteral("Parser")); core()->uiController()->removeToolView(m_factory); } void ProblemReporterPlugin::documentClosed(IDocument* doc) { if (!doc->textDocument()) return; IndexedString url(doc->url()); delete m_highlighters.take(url); } void ProblemReporterPlugin::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); m_highlighters.insert(IndexedString(document->url()), new ProblemHighlighter(document->textDocument())); DUChain::self()->updateContextForUrl(IndexedString(document->url()), KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this); } void ProblemReporterPlugin::updateReady(const IndexedString& url, const KDevelop::ReferencedTopDUContext&) { m_model->problemsUpdated(url); updateHighlight(url); } void ProblemReporterPlugin::updateHighlight(const KDevelop::IndexedString& url) { ProblemHighlighter* ph = m_highlighters.value(url); if (ph) { auto allProblems = m_model->problems(url, false); ph->setProblems(allProblems); } } void ProblemReporterPlugin::showModel(const QString& name) { auto w = dynamic_cast(core()->uiController()->findToolView(i18n("Problems"), m_factory)); if (w) w->showModel(name); } KDevelop::ContextMenuExtension ProblemReporterPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension; KDevelop::EditorContext* editorContext = dynamic_cast(context); if (editorContext) { DUChainReadLocker lock(DUChain::lock(), 1000); if (!lock.locked()) { qCDebug(PLUGIN_PROBLEMREPORTER) << "failed to lock duchain in time"; return extension; } QString title; QList actions; TopDUContext* top = DUChainUtils::standardContextForUrl(editorContext->url()); if (top) { foreach (KDevelop::ProblemPointer problem, top->problems()) { if (problem->range().contains( top->transformToLocalRevision(KTextEditor::Cursor(editorContext->position())))) { KDevelop::IAssistant::Ptr solution = problem->solutionAssistant(); if (solution) { title = solution->title(); foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) actions << action->toKAction(); } } } } if (!actions.isEmpty()) { QString text; if (title.isEmpty()) text = i18n("Solve Problem"); else { text = i18n("Solve: %1", KDevelop::htmlToPlainText(title)); } - QAction* menuAction = new QAction(text, 0); - QMenu* menu(new QMenu(text, 0)); + QAction* menuAction = new QAction(text, nullptr); + QMenu* menu(new QMenu(text, nullptr)); menuAction->setMenu(menu); foreach (QAction* action, actions) menu->addAction(action); extension.addAction(ContextMenuExtension::ExtensionGroup, menuAction); } } return extension; } #include "problemreporterplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/problemreporter/problemsview.cpp b/plugins/problemreporter/problemsview.cpp index b0e0210f28..277cf0a115 100644 --- a/plugins/problemreporter/problemsview.cpp +++ b/plugins/problemreporter/problemsview.cpp @@ -1,456 +1,456 @@ /* * Copyright 2015 Laszlo Kis-Adam * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemsview.h" #include #include #include #include #include #include #include #include #include #include #include #include "problemtreeview.h" #include "problemmodel.h" namespace KDevelop { void ProblemsView::setupActions() { { m_fullUpdateAction = new QAction(this); m_fullUpdateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_fullUpdateAction->setText(i18n("Force Full Update")); m_fullUpdateAction->setToolTip(i18nc("@info:tooltip", "Re-parse all watched documents")); m_fullUpdateAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(m_fullUpdateAction, &QAction::triggered, this, [this]() { currentView()->model()->forceFullUpdate(); }); addAction(m_fullUpdateAction); } { m_showImportsAction = new QAction(this); addAction(m_showImportsAction); m_showImportsAction->setCheckable(true); m_showImportsAction->setChecked(false); m_showImportsAction->setText(i18n("Show Imports")); m_showImportsAction->setToolTip(i18nc("@info:tooltip", "Display problems in imported files")); connect(m_showImportsAction, &QAction::triggered, this, [this](bool checked) { currentView()->model()->setShowImports(checked); }); } { m_scopeMenu = new KActionMenu(this); m_scopeMenu->setDelayed(false); m_scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); m_scopeMenu->setObjectName(QStringLiteral("scopeMenu")); QActionGroup* scopeActions = new QActionGroup(this); m_currentDocumentAction = new QAction(this); m_currentDocumentAction->setText(i18n("Current Document")); m_currentDocumentAction->setToolTip(i18nc("@info:tooltip", "Display problems in current document")); QAction* openDocumentsAction = new QAction(this); openDocumentsAction->setText(i18n("Open Documents")); openDocumentsAction->setToolTip(i18nc("@info:tooltip", "Display problems in all open documents")); QAction* currentProjectAction = new QAction(this); currentProjectAction->setText(i18n("Current Project")); currentProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in current project")); QAction* allProjectAction = new QAction(this); allProjectAction->setText(i18n("All Projects")); allProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in all projects")); QVector actions; actions.push_back(m_currentDocumentAction); actions.push_back(openDocumentsAction); actions.push_back(currentProjectAction); actions.push_back(allProjectAction); m_showAllAction = new QAction(this); m_showAllAction->setText(i18n("Show All")); m_showAllAction->setToolTip(i18nc("@info:tooltip", "Display ALL problems")); actions.push_back(m_showAllAction); foreach (QAction* action, actions) { action->setCheckable(true); scopeActions->addAction(action); m_scopeMenu->addAction(action); } addAction(m_scopeMenu); QSignalMapper* scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(m_currentDocumentAction, CurrentDocument); scopeMapper->setMapping(openDocumentsAction, OpenDocuments); scopeMapper->setMapping(currentProjectAction, CurrentProject); scopeMapper->setMapping(allProjectAction, AllProjects); connect(m_currentDocumentAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(openDocumentsAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(currentProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(allProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); { scopeMapper->setMapping(actions.last(), BypassScopeFilter); connect(actions.last(), &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); } connect(scopeMapper, static_cast(&QSignalMapper::mapped), this, [this](int index) { setScope(index); }); } { m_severityActions = new QActionGroup(this); m_errorSeverityAction = new QAction(this); m_errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors")); m_errorSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); m_warningSeverityAction = new QAction(this); m_warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display warnings")); m_warningSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_hintSeverityAction = new QAction(this); m_hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display hints")); m_hintSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); QAction* severityActionArray[] = { m_errorSeverityAction, m_warningSeverityAction, m_hintSeverityAction }; for (int i = 0; i < 3; ++i) { severityActionArray[i]->setCheckable(true); m_severityActions->addAction(severityActionArray[i]); addAction(severityActionArray[i]); } m_severityActions->setExclusive(false); m_hintSeverityAction->setChecked(true); m_warningSeverityAction->setChecked(true); m_errorSeverityAction->setChecked(true); connect(m_errorSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); connect(m_warningSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); connect(m_hintSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); } { m_groupingMenu = new KActionMenu(i18n("Grouping"), this); m_groupingMenu->setDelayed(false); QActionGroup* groupingActions = new QActionGroup(this); QAction* noGroupingAction = new QAction(i18n("None"), this); QAction* pathGroupingAction = new QAction(i18n("Path"), this); QAction* severityGroupingAction = new QAction(i18n("Severity"), this); QAction* groupingActionArray[] = { noGroupingAction, pathGroupingAction, severityGroupingAction }; for (unsigned i = 0; i < sizeof(groupingActionArray) / sizeof(QAction*); ++i) { QAction* action = groupingActionArray[i]; action->setCheckable(true); groupingActions->addAction(action); m_groupingMenu->addAction(action); } addAction(m_groupingMenu); noGroupingAction->setChecked(true); QSignalMapper* groupingMapper = new QSignalMapper(this); groupingMapper->setMapping(noGroupingAction, NoGrouping); groupingMapper->setMapping(pathGroupingAction, PathGrouping); groupingMapper->setMapping(severityGroupingAction, SeverityGrouping); connect(noGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(pathGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(severityGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(groupingMapper, static_cast(&QSignalMapper::mapped), this, [this](int index) { currentView()->model()->setGrouping(index); }); } } void ProblemsView::updateActions() { auto problemModel = currentView()->model(); Q_ASSERT(problemModel); m_fullUpdateAction->setVisible(problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)); m_showImportsAction->setVisible(problemModel->features().testFlag(ProblemModel::CanShowImports)); m_scopeMenu->setVisible(problemModel->features().testFlag(ProblemModel::ScopeFilter)); m_severityActions->setVisible(problemModel->features().testFlag(ProblemModel::SeverityFilter)); m_groupingMenu->setVisible(problemModel->features().testFlag(ProblemModel::Grouping)); m_showAllAction->setVisible(problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)); problemModel->setShowImports(false); setScope(CurrentDocument); // Show All should be default if it's supported. It helps with error messages that are otherwise invisible if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { //actions.last()->setChecked(true); setScope(BypassScopeFilter); } else { m_currentDocumentAction->setChecked(true); setScope(CurrentDocument); } problemModel->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); } /// TODO: Move to util? /// Note: Support for recursing into child indices would be nice class ItemViewWalker { public: ItemViewWalker(QItemSelectionModel* itemView); void selectNextIndex(); void selectPreviousIndex(); enum Direction { NextIndex, PreviousIndex }; void selectIndex(Direction direction); private: QItemSelectionModel* m_selectionModel; }; ItemViewWalker::ItemViewWalker(QItemSelectionModel* itemView) : m_selectionModel(itemView) { } void ItemViewWalker::selectNextIndex() { selectIndex(NextIndex); } void ItemViewWalker::selectPreviousIndex() { selectIndex(PreviousIndex); } void ItemViewWalker::selectIndex(Direction direction) { if (!m_selectionModel) { return; } const QModelIndexList list = m_selectionModel->selectedRows(); const QModelIndex currentIndex = list.value(0); if (!currentIndex.isValid()) { /// no selection yet, just select the first const QModelIndex firstIndex = m_selectionModel->model()->index(0, 0); m_selectionModel->setCurrentIndex(firstIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); return; } const int nextRow = currentIndex.row() + (direction == NextIndex ? 1 : -1); const QModelIndex nextIndex = currentIndex.sibling(nextRow, 0); if (!nextIndex.isValid()) { return; /// never invalidate the selection } m_selectionModel->setCurrentIndex(nextIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } ProblemsView::ProblemsView(QWidget* parent) : QWidget(parent) { setWindowTitle(i18n("Problems")); setWindowIcon(QIcon::fromTheme(QStringLiteral("script-error"), windowIcon())); auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); m_tabWidget = new QTabWidget(this); m_tabWidget->setTabPosition(QTabWidget::South); layout->addWidget(m_tabWidget); setupActions(); } ProblemsView::~ProblemsView() { } void ProblemsView::load() { m_tabWidget->clear(); KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); QVector v = pms->models(); QVectorIterator itr(v); while (itr.hasNext()) { const KDevelop::ModelData& data = itr.next(); addModel(data); } connect(pms, &ProblemModelSet::added, this, &ProblemsView::onModelAdded); connect(pms, &ProblemModelSet::removed, this, &ProblemsView::onModelRemoved); connect(m_tabWidget, &QTabWidget::currentChanged, this, &ProblemsView::onCurrentChanged); if (m_tabWidget->currentIndex() == 0) { updateActions(); return; } m_tabWidget->setCurrentIndex(0); } void ProblemsView::onModelAdded(const ModelData& data) { addModel(data); } /** * @brief Returns the name part of the label * * E.g.: Test (666) => Test */ QString nameFromLabel(const QString& label) { QString txt = label; int i = txt.lastIndexOf('('); if (i != -1) txt = txt.left(i - 1); /// ignore whitespace before '(' return txt; } int tabIndexForName(const QTabWidget* tabWidget, const QString& name) { for (int idx = 0; idx < tabWidget->count(); ++idx) { if (nameFromLabel(tabWidget->tabText(idx)) == name) { return idx; } } return -1; } void ProblemsView::showModel(const QString& name) { int idx = tabIndexForName(m_tabWidget, name); if (idx >= 0) m_tabWidget->setCurrentIndex(idx); } void ProblemsView::onModelRemoved(const QString& name) { int idx = tabIndexForName(m_tabWidget, name); if (idx >= 0) { QWidget* w = m_tabWidget->widget(idx); m_tabWidget->removeTab(idx); delete w; } } void ProblemsView::onCurrentChanged(int idx) { if (idx == -1) return; updateActions(); } void ProblemsView::onViewChanged() { ProblemTreeView* view = static_cast(sender()); int idx = m_tabWidget->indexOf(view); int rows = view->model()->rowCount(); updateTab(idx, rows); } void ProblemsView::addModel(const ModelData& data) { - ProblemTreeView* view = new ProblemTreeView(NULL, data.model); + ProblemTreeView* view = new ProblemTreeView(nullptr, data.model); connect(view, &ProblemTreeView::changed, this, &ProblemsView::onViewChanged); int idx = m_tabWidget->addTab(view, data.name); int rows = view->model()->rowCount(); updateTab(idx, rows); } void ProblemsView::updateTab(int idx, int rows) { const QString name = nameFromLabel(m_tabWidget->tabText(idx)); const QString tabText = i18nc("%1: tab name, %2: number of problems", "%1 (%2)", name, rows); m_tabWidget->setTabText(idx, tabText); } ProblemTreeView* ProblemsView::currentView() const { return qobject_cast(m_tabWidget->currentWidget()); } void ProblemsView::selectNextItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectNextIndex(); view->openDocumentForCurrentProblem(); } } void ProblemsView::selectPreviousItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectPreviousIndex(); view->openDocumentForCurrentProblem(); } } void ProblemsView::handleSeverityActionToggled() { currentView()->model()->setSeverities( (m_errorSeverityAction->isChecked() ? IProblem::Error : IProblem::Severities()) | (m_warningSeverityAction->isChecked() ? IProblem::Warning : IProblem::Severities()) | (m_hintSeverityAction->isChecked() ? IProblem::Hint : IProblem::Severities()) ); } void ProblemsView::setScope(int scope) { m_scopeMenu->setText(i18n("Scope: %1", m_scopeMenu->menu()->actions().at(scope)->text())); currentView()->model()->setScope(scope); } } diff --git a/plugins/projectmanagerview/projectbuildsetwidget.cpp b/plugins/projectmanagerview/projectbuildsetwidget.cpp index a97213261f..bde3f7fa26 100644 --- a/plugins/projectmanagerview/projectbuildsetwidget.cpp +++ b/plugins/projectmanagerview/projectbuildsetwidget.cpp @@ -1,271 +1,271 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "projectbuildsetwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "projectmanagerviewplugin.h" #include "projectmanagerview.h" #include "ui_projectbuildsetwidget.h" #include "debug.h" #include #include #include ProjectBuildSetWidget::ProjectBuildSetWidget( QWidget* parent ) - : QWidget( parent ), m_view( 0 ), + : QWidget( parent ), m_view( nullptr ), m_ui( new Ui::ProjectBuildSetWidget ) { m_ui->setupUi( this ); m_ui->addItemButton->setIcon( QIcon::fromTheme( QStringLiteral("list-add") ) ); connect( m_ui->addItemButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::addItems ); m_ui->removeItemButton->setIcon( QIcon::fromTheme( QStringLiteral("list-remove") ) ); connect( m_ui->removeItemButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::removeItems ); m_ui->upButton->setIcon( QIcon::fromTheme( QStringLiteral("go-up") ) ); connect( m_ui->upButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::moveUp ); m_ui->downButton->setIcon( QIcon::fromTheme( QStringLiteral("go-down") ) ); connect( m_ui->downButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::moveDown ); m_ui->topButton->setIcon( QIcon::fromTheme( QStringLiteral("go-top") ) ); connect( m_ui->topButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::moveToTop ); m_ui->bottomButton->setIcon( QIcon::fromTheme( QStringLiteral("go-bottom") ) ); connect( m_ui->bottomButton, &QToolButton::clicked, this, &ProjectBuildSetWidget::moveToBottom ); m_ui->itemView->setContextMenuPolicy( Qt::CustomContextMenu ); connect( m_ui->itemView, &QTreeView::customContextMenuRequested, this, &ProjectBuildSetWidget::showContextMenu ); layout()->setMargin(0); } void ProjectBuildSetWidget::setProjectView( ProjectManagerView* view ) { m_view = view; m_ui->itemView->setModel( KDevelop::ICore::self()->projectController()->buildSetModel() ); connect( m_ui->itemView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectBuildSetWidget::selectionChanged ); } void ProjectBuildSetWidget::selectionChanged() { QModelIndexList selectedRows = m_ui->itemView->selectionModel()->selectedRows(); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "checking selectionmodel:" << selectedRows; m_ui->removeItemButton->setEnabled( !selectedRows.isEmpty() ); m_ui->addItemButton->setEnabled( !m_view->selectedItems().isEmpty() ); bool enableUp = selectedRows.count() > 0 && selectedRows.first().row() != 0; bool enableDown = selectedRows.count() > 0 && selectedRows.last().row() != m_ui->itemView->model()->rowCount() - 1; m_ui->upButton->setEnabled( enableUp ); m_ui->downButton->setEnabled( enableDown ); m_ui->bottomButton->setEnabled( enableDown ); m_ui->topButton->setEnabled( enableUp ); } ProjectBuildSetWidget::~ProjectBuildSetWidget() { delete m_ui; } //TODO test whether this could be replaced by projecttreeview.cpp::popupContextMenu_appendActions void showContextMenu_appendActions(QMenu& menu, const QList& actions) { menu.addSeparator(); foreach( QAction* act, actions ) { menu.addAction(act); } } void ProjectBuildSetWidget::showContextMenu( const QPoint& p ) { if( m_ui->itemView->selectionModel()->selectedRows().isEmpty() ) return; QList itemlist; if(m_ui->itemView->selectionModel()->selectedRows().count() == 1) { KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); int row = m_ui->itemView->selectionModel()->selectedRows()[0].row(); if(row < buildSet->items().size()) { KDevelop::ProjectBaseItem* item = buildSet->items().at(row).findItem(); if(item) itemlist << item; } } QMenu m; m.setTitle( i18n("Build Set") ); m.addAction( QIcon::fromTheme(QStringLiteral("list-remove")), i18n( "Remove From Build Set" ), this, SLOT(removeItems()) ); if( !itemlist.isEmpty() ) { KDevelop::ProjectItemContextImpl context(itemlist); QList extensions = KDevelop::ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); QList buildActions; QList vcsActions; QList extActions; QList projectActions; QList fileActions; QList runActions; foreach( const KDevelop::ContextMenuExtension& ext, extensions ) { buildActions += ext.actions(KDevelop::ContextMenuExtension::BuildGroup); fileActions += ext.actions(KDevelop::ContextMenuExtension::FileGroup); projectActions += ext.actions(KDevelop::ContextMenuExtension::ProjectGroup); vcsActions += ext.actions(KDevelop::ContextMenuExtension::VcsGroup); extActions += ext.actions(KDevelop::ContextMenuExtension::ExtensionGroup); runActions += ext.actions(KDevelop::ContextMenuExtension::RunGroup); } showContextMenu_appendActions(m, buildActions); showContextMenu_appendActions(m, runActions); showContextMenu_appendActions(m, fileActions); showContextMenu_appendActions(m, vcsActions); showContextMenu_appendActions(m, extActions); showContextMenu_appendActions(m, projectActions); } m.exec( m_ui->itemView->viewport()->mapToGlobal( p ) ); } void ProjectBuildSetWidget::addItems() { foreach( KDevelop::ProjectBaseItem* item, m_view->selectedItems() ) { KDevelop::ICore::self()->projectController()->buildSetModel()->addProjectItem( item ); } } void ProjectBuildSetWidget::removeItems() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "removing:" << range.top() << range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->removeRows( range.top(), range.height() ); top = qMin( top, buildSet->rowCount() - 1 ); QModelIndex sidx = buildSet->index( top, 0 ); QModelIndex eidx = buildSet->index( top, buildSet->columnCount() - 1 ); m_ui->itemView->selectionModel()->select( QItemSelection( sidx, eidx ), QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( sidx, QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveDown() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsDown( top, height ); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( top + 1, 0 ), buildSet->index( top + height, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveToBottom() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsToBottom( top, height ); int rowCount = buildSet->rowCount(); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( rowCount - height, 0 ), buildSet->index( rowCount - 1, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveUp() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsUp( top, height ); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( top - 1, 0 ), buildSet->index( top - 2 + height, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveToTop() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsToTop( top, height ); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( 0, 0 ), buildSet->index( height - 1, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } diff --git a/plugins/projectmanagerview/projectmanagerview.cpp b/plugins/projectmanagerview/projectmanagerview.cpp index f08fe23997..9a367bffd1 100644 --- a/plugins/projectmanagerview/projectmanagerview.cpp +++ b/plugins/projectmanagerview/projectmanagerview.cpp @@ -1,283 +1,283 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectmanagerview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" #include #include #include "projectmanagerviewplugin.h" #include "vcsoverlayproxymodel.h" #include "ui_projectmanagerview.h" #include "debug.h" using namespace KDevelop; ProjectManagerViewItemContext::ProjectManagerViewItemContext(const QList< ProjectBaseItem* >& items, ProjectManagerView* view) : ProjectItemContextImpl(items), m_view(view) { } ProjectManagerView *ProjectManagerViewItemContext::view() const { return m_view; } static const char sessionConfigGroup[] = "ProjectManagerView"; static const char splitterStateConfigKey[] = "splitterState"; static const char targetsVisibleConfigKey[] = "targetsVisible"; static const int projectTreeViewStrechFactor = 75; // % static const int projectBuildSetStrechFactor = 25; // % ProjectManagerView::ProjectManagerView( ProjectManagerViewPlugin* plugin, QWidget *parent ) : QWidget( parent ), m_ui(new Ui::ProjectManagerView), m_plugin(plugin) { m_ui->setupUi( this ); m_ui->projectTreeView->installEventFilter(this); setWindowIcon( QIcon::fromTheme( QStringLiteral("project-development"), windowIcon() ) ); KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); if (pmviewConfig.hasKey(splitterStateConfigKey)) { QByteArray geometry = pmviewConfig.readEntry(splitterStateConfigKey, QByteArray()); m_ui->splitter->restoreState(geometry); } else { m_ui->splitter->setStretchFactor(0, projectTreeViewStrechFactor); m_ui->splitter->setStretchFactor(1, projectBuildSetStrechFactor); } m_syncAction = plugin->actionCollection()->action(QStringLiteral("locate_document")); Q_ASSERT(m_syncAction); m_syncAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_syncAction->setText(i18n("Locate Current Document")); m_syncAction->setToolTip(i18n("Locates the current document in the project tree and selects it.")); m_syncAction->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); m_syncAction->setShortcut(Qt::ControlModifier + Qt::Key_Less); connect(m_syncAction, &QAction::triggered, this, &ProjectManagerView::locateCurrentDocument); addAction(m_syncAction); updateSyncAction(); m_toggleTargetsAction = new QAction(i18n("Show Targets"), this); m_toggleTargetsAction->setCheckable(true); m_toggleTargetsAction->setChecked(pmviewConfig.readEntry(targetsVisibleConfigKey, true)); m_toggleTargetsAction->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); connect(m_toggleTargetsAction, &QAction::triggered, this, &ProjectManagerView::toggleHideTargets); addAction(m_toggleTargetsAction); addAction(plugin->actionCollection()->action(QStringLiteral("project_build"))); addAction(plugin->actionCollection()->action(QStringLiteral("project_install"))); addAction(plugin->actionCollection()->action(QStringLiteral("project_clean"))); connect(m_ui->projectTreeView, &ProjectTreeView::activate, this, &ProjectManagerView::open); m_ui->buildSetView->setProjectView( this ); m_modelFilter = new ProjectProxyModel( this ); m_modelFilter->showTargets(m_toggleTargetsAction->isChecked()); m_modelFilter->setSourceModel(ICore::self()->projectController()->projectModel()); m_overlayProxy = new VcsOverlayProxyModel( this ); m_overlayProxy->setSourceModel(m_modelFilter); m_ui->projectTreeView->setModel( m_overlayProxy ); connect( m_ui->projectTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectManagerView::selectionChanged ); connect( KDevelop::ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProjectManagerView::updateSyncAction); connect( KDevelop::ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProjectManagerView::updateSyncAction); connect( qobject_cast(KDevelop::ICore::self()->uiController()->activeMainWindow()), &Sublime::MainWindow::areaChanged, this, &ProjectManagerView::updateSyncAction); selectionChanged(); //Update the "sync" button after the initialization has completed, to see whether there already is some open documents QMetaObject::invokeMethod(this, "updateSyncAction", Qt::QueuedConnection); // Need to set this to get horizontal scrollbar. Also needs to be done after // the setModel call m_ui->projectTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents ); } bool ProjectManagerView::eventFilter(QObject* obj, QEvent* event) { if (obj == m_ui->projectTreeView) { if (event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Delete && keyEvent->modifiers() == Qt::NoModifier) { m_plugin->removeItems(selectedItems()); return true; } else if (keyEvent->key() == Qt::Key_F2 && keyEvent->modifiers() == Qt::NoModifier) { m_plugin->renameItems(selectedItems()); return true; } else if (keyEvent->key() == Qt::Key_C && keyEvent->modifiers() == Qt::ControlModifier) { m_plugin->copyFromContextMenu(); return true; } else if (keyEvent->key() == Qt::Key_V && keyEvent->modifiers() == Qt::ControlModifier) { m_plugin->pasteFromContextMenu(); return true; } } } return QObject::eventFilter(obj, event); } void ProjectManagerView::selectionChanged() { m_ui->buildSetView->selectionChanged(); QList selected; foreach( const QModelIndex& idx, m_ui->projectTreeView->selectionModel()->selectedRows() ) { selected << ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView( idx )); } - selected.removeAll(0); + selected.removeAll(nullptr); KDevelop::ICore::self()->selectionController()->updateSelection( new ProjectManagerViewItemContext( selected, this ) ); } void ProjectManagerView::updateSyncAction() { m_syncAction->setEnabled( KDevelop::ICore::self()->documentController()->activeDocument() ); } ProjectManagerView::~ProjectManagerView() { KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); pmviewConfig.writeEntry(splitterStateConfigKey, m_ui->splitter->saveState()); pmviewConfig.sync(); delete m_ui; } QList ProjectManagerView::selectedItems() const { QList items; foreach( const QModelIndex &idx, m_ui->projectTreeView->selectionModel()->selectedIndexes() ) { KDevelop::ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView(idx)); if( item ) items << item; else qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "adding an unknown item"; } return items; } void ProjectManagerView::selectItems(const QList< ProjectBaseItem* >& items) { QItemSelection selection; foreach (ProjectBaseItem *item, items) { QModelIndex indx = indexToView(item->index()); selection.append(QItemSelectionRange(indx, indx)); m_ui->projectTreeView->setCurrentIndex(indx); } m_ui->projectTreeView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } void ProjectManagerView::expandItem(ProjectBaseItem* item) { m_ui->projectTreeView->expand( indexToView(item->index())); } void ProjectManagerView::toggleHideTargets(bool visible) { KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); pmviewConfig.writeEntry(targetsVisibleConfigKey, visible); m_modelFilter->showTargets(visible); } void ProjectManagerView::locateCurrentDocument() { ICore::self()->uiController()->raiseToolView(this); KDevelop::IDocument *doc = ICore::self()->documentController()->activeDocument(); if (!doc) { // in theory we should never get a null pointer as the action is only enabled // when there is an active document. // but: in practice it can happen that you close the last document and press // the shortcut to locate a doc or vice versa... so just do the failsafe thing here... return; } QModelIndex bestMatch; foreach (IProject* proj, ICore::self()->projectController()->projects()) { foreach (KDevelop::ProjectFileItem* item, proj->filesForPath(IndexedString(doc->url()))) { QModelIndex index = indexToView(item->index()); if (index.isValid()) { if (!bestMatch.isValid()) { bestMatch = index; } else if (KDevelop::ProjectBaseItem* parent = item->parent()) { // prefer files in their real folders over the 'copies' in the target folders if (!parent->target()) { bestMatch = index; break; } } } } } if (bestMatch.isValid()) { m_ui->projectTreeView->clearSelection(); m_ui->projectTreeView->setCurrentIndex(bestMatch); m_ui->projectTreeView->expand(bestMatch); m_ui->projectTreeView->scrollTo(bestMatch); } } void ProjectManagerView::open( const Path& path ) { IOpenWith::openFiles(QList() << path.toUrl()); } QModelIndex ProjectManagerView::indexFromView(const QModelIndex& index) const { return m_modelFilter->mapToSource( m_overlayProxy->mapToSource(index) ); } QModelIndex ProjectManagerView::indexToView(const QModelIndex& index) const { return m_overlayProxy->mapFromSource( m_modelFilter->mapFromSource(index) ); } diff --git a/plugins/projectmanagerview/projectmanagerviewplugin.cpp b/plugins/projectmanagerview/projectmanagerviewplugin.cpp index add17c6295..32c5aace8b 100644 --- a/plugins/projectmanagerview/projectmanagerviewplugin.cpp +++ b/plugins/projectmanagerview/projectmanagerviewplugin.cpp @@ -1,715 +1,715 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectmanagerviewplugin.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 "projectmanagerview.h" #include "debug.h" using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_PROJECTMANAGERVIEW, "kdevplatform.plugins.projectmanagerview") K_PLUGIN_FACTORY_WITH_JSON(ProjectManagerFactory, "kdevprojectmanagerview.json", registerPlugin();) class KDevProjectManagerViewFactory: public KDevelop::IToolViewFactory { public: KDevProjectManagerViewFactory( ProjectManagerViewPlugin *plugin ): mplugin( plugin ) {} - QWidget* create( QWidget *parent = 0 ) override + QWidget* create( QWidget *parent = nullptr ) override { return new ProjectManagerView( mplugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ProjectsView"); } private: ProjectManagerViewPlugin *mplugin; }; class ProjectManagerViewPluginPrivate { public: ProjectManagerViewPluginPrivate() {} KDevProjectManagerViewFactory *factory; QList ctxProjectItemList; QAction* m_buildAll; QAction* m_build; QAction* m_install; QAction* m_clean; QAction* m_configure; QAction* m_prune; }; static QList itemsFromIndexes(const QList& indexes) { QList items; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach(const QModelIndex& index, indexes) { items += model->itemFromIndex(index); } return items; } ProjectManagerViewPlugin::ProjectManagerViewPlugin( QObject *parent, const QVariantList& ) : IPlugin( QStringLiteral("kdevprojectmanagerview"), parent ), d(new ProjectManagerViewPluginPrivate) { d->m_buildAll = new QAction( i18n("Build all Projects"), this ); d->m_buildAll->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); connect( d->m_buildAll, &QAction::triggered, this, &ProjectManagerViewPlugin::buildAllProjects ); actionCollection()->addAction( QStringLiteral("project_buildall"), d->m_buildAll ); d->m_build = new QAction( i18n("Build Selection"), this ); d->m_build->setIconText( i18n("Build") ); actionCollection()->setDefaultShortcut( d->m_build, Qt::Key_F8 ); d->m_build->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); d->m_build->setEnabled( false ); connect( d->m_build, &QAction::triggered, this, &ProjectManagerViewPlugin::buildProjectItems ); actionCollection()->addAction( QStringLiteral("project_build"), d->m_build ); d->m_install = new QAction( i18n("Install Selection"), this ); d->m_install->setIconText( i18n("Install") ); d->m_install->setIcon(QIcon::fromTheme(QStringLiteral("run-build-install"))); actionCollection()->setDefaultShortcut( d->m_install, Qt::SHIFT + Qt::Key_F8 ); d->m_install->setEnabled( false ); connect( d->m_install, &QAction::triggered, this, &ProjectManagerViewPlugin::installProjectItems ); actionCollection()->addAction( QStringLiteral("project_install"), d->m_install ); d->m_clean = new QAction( i18n("Clean Selection"), this ); d->m_clean->setIconText( i18n("Clean") ); d->m_clean->setIcon(QIcon::fromTheme(QStringLiteral("run-build-clean"))); d->m_clean->setEnabled( false ); connect( d->m_clean, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanProjectItems ); actionCollection()->addAction( QStringLiteral("project_clean"), d->m_clean ); d->m_configure = new QAction( i18n("Configure Selection"), this ); d->m_configure->setMenuRole( QAction::NoRole ); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item d->m_configure->setIconText( i18n("Configure") ); d->m_configure->setIcon(QIcon::fromTheme(QStringLiteral("run-build-configure"))); d->m_configure->setEnabled( false ); connect( d->m_configure, &QAction::triggered, this, &ProjectManagerViewPlugin::configureProjectItems ); actionCollection()->addAction( QStringLiteral("project_configure"), d->m_configure ); d->m_prune = new QAction( i18n("Prune Selection"), this ); d->m_prune->setIconText( i18n("Prune") ); d->m_prune->setIcon(QIcon::fromTheme(QStringLiteral("run-build-prune"))); d->m_prune->setEnabled( false ); connect( d->m_prune, &QAction::triggered, this, &ProjectManagerViewPlugin::pruneProjectItems ); actionCollection()->addAction( QStringLiteral("project_prune"), d->m_prune ); // only add the action so that its known in the actionCollection // and so that it's shortcut etc. pp. is restored // apparently that is not possible to be done in the view itself *sigh* actionCollection()->addAction( QStringLiteral("locate_document") ); setXMLFile( QStringLiteral("kdevprojectmanagerview.rc") ); d->factory = new KDevProjectManagerViewFactory( this ); core()->uiController()->addToolView( i18n("Projects"), d->factory ); connect(core()->selectionController(), &ISelectionController::selectionChanged, this, &ProjectManagerViewPlugin::updateActionState); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsInserted, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsRemoved, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::modelReset, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); } void ProjectManagerViewPlugin::updateFromBuildSetChange() { updateActionState( core()->selectionController()->currentSelection() ); } void ProjectManagerViewPlugin::updateActionState( KDevelop::Context* ctx ) { bool isEmpty = ICore::self()->projectController()->buildSetModel()->items().isEmpty(); if( isEmpty ) { isEmpty = !ctx || ctx->type() != Context::ProjectItemContext || dynamic_cast( ctx )->items().isEmpty(); } d->m_build->setEnabled( !isEmpty ); d->m_install->setEnabled( !isEmpty ); d->m_clean->setEnabled( !isEmpty ); d->m_configure->setEnabled( !isEmpty ); d->m_prune->setEnabled( !isEmpty ); } ProjectManagerViewPlugin::~ProjectManagerViewPlugin() { delete d; } void ProjectManagerViewPlugin::unload() { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "unloading manager view"; core()->uiController()->removeToolView(d->factory); } ContextMenuExtension ProjectManagerViewPlugin::contextMenuExtension( KDevelop::Context* context ) { if( context->type() != KDevelop::Context::ProjectItemContext ) return IPlugin::contextMenuExtension( context ); KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); d->ctxProjectItemList.clear(); if( items.isEmpty() ) return IPlugin::contextMenuExtension( context ); //TODO: also needs: removeTarget, removeFileFromTarget, runTargetsFromContextMenu ContextMenuExtension menuExt; bool needsCreateFile = true; bool needsCreateFolder = true; bool needsCloseProjects = true; bool needsBuildItems = true; bool needsFolderItems = true; bool needsRemoveAndRename = true; bool needsRemoveTargetFiles = true; bool needsPaste = true; //needsCreateFile if there is one item and it's a folder or target needsCreateFile &= (items.count() == 1) && (items.first()->folder() || items.first()->target()); //needsCreateFolder if there is one item and it's a folder needsCreateFolder &= (items.count() == 1) && (items.first()->folder()); needsPaste = needsCreateFolder; foreach( ProjectBaseItem* item, items ) { d->ctxProjectItemList << item->index(); //needsBuildItems if items are limited to targets and buildfolders needsBuildItems &= item->target() || item->type() == ProjectBaseItem::BuildFolder; //needsCloseProjects if items are limited to top level folders (Project Folders) needsCloseProjects &= item->folder() && !item->folder()->parent(); //needsFolderItems if items are limited to folders needsFolderItems &= (bool)item->folder(); //needsRemove if items are limited to non-top-level folders or files that don't belong to targets needsRemoveAndRename &= (item->folder() && item->parent()) || (item->file() && !item->parent()->target()); //needsRemoveTargets if items are limited to file items with target parents needsRemoveTargetFiles &= (item->file() && item->parent()->target()); } if ( needsCreateFile ) { QAction* action = new QAction( i18n( "Create File..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFileFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsCreateFolder ) { QAction* action = new QAction( i18n( "Create Folder..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFolderFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsBuildItems ) { QAction* action = new QAction( i18nc( "@action", "Build" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::buildItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Install" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("run-install"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::installItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Clean" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("run-clean"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18n( "Add to Build Set" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); } if ( needsCloseProjects ) { QAction* close = new QAction( i18np( "Close Project", "Close Projects", items.count() ), this ); close->setIcon(QIcon::fromTheme(QStringLiteral("project-development-close"))); connect( close, &QAction::triggered, this, &ProjectManagerViewPlugin::closeProjects ); menuExt.addAction( ContextMenuExtension::ProjectGroup, close ); } if ( needsFolderItems ) { QAction* action = new QAction( i18n( "Reload" ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::reloadFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsRemoveAndRename ) { QAction* remove = new QAction( i18n( "Remove" ), this ); remove->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); QAction* rename = new QAction( i18n( "Rename..." ), this ); rename->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); connect( rename, &QAction::triggered, this, &ProjectManagerViewPlugin::renameItemFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, rename ); } if ( needsRemoveTargetFiles ) { QAction* remove = new QAction( i18n( "Remove From Target" ), this ); remove->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeTargetFilesFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); } { QAction* copy = KStandardAction::copy(this, SLOT(copyFromContextMenu()), this); copy->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, copy ); } if (needsPaste) { QAction* paste = KStandardAction::paste(this, SLOT(pasteFromContextMenu()), this); paste->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, paste ); } return menuExt; } void ProjectManagerViewPlugin::closeProjects() { QList projectsToClose; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach( const QModelIndex& index, d->ctxProjectItemList ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex(index); if( !projectsToClose.contains( item->project() ) ) { projectsToClose << item->project(); } } d->ctxProjectItemList.clear(); foreach( KDevelop::IProject* proj, projectsToClose ) { core()->projectController()->closeProject( proj ); } } void ProjectManagerViewPlugin::installItemsFromContextMenu() { runBuilderJob( BuilderJob::Install, itemsFromIndexes(d->ctxProjectItemList) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::cleanItemsFromContextMenu() { runBuilderJob( BuilderJob::Clean, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::buildItemsFromContextMenu() { runBuilderJob( BuilderJob::Build, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } QList ProjectManagerViewPlugin::collectAllProjects() { QList items; foreach( KDevelop::IProject* project, core()->projectController()->projects() ) { items << project->projectItem(); } return items; } void ProjectManagerViewPlugin::buildAllProjects() { runBuilderJob( BuilderJob::Build, collectAllProjects() ); } QList ProjectManagerViewPlugin::collectItems() { QList items; QList buildItems = ICore::self()->projectController()->buildSetModel()->items(); if( !buildItems.isEmpty() ) { foreach( const BuildItem& buildItem, buildItems ) { if( ProjectBaseItem* item = buildItem.findItem() ) { items << item; } } } else { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); items = ctx->items(); } return items; } void ProjectManagerViewPlugin::runBuilderJob( BuilderJob::BuildType type, QList items ) { BuilderJob* builder = new BuilderJob; builder->addItems( type, items ); builder->updateJobName(); ICore::self()->uiController()->registerStatus(new JobStatus(builder)); ICore::self()->runController()->registerJob( builder ); } void ProjectManagerViewPlugin::installProjectItems() { runBuilderJob( KDevelop::BuilderJob::Install, collectItems() ); } void ProjectManagerViewPlugin::pruneProjectItems() { runBuilderJob( KDevelop::BuilderJob::Prune, collectItems() ); } void ProjectManagerViewPlugin::configureProjectItems() { runBuilderJob( KDevelop::BuilderJob::Configure, collectItems() ); } void ProjectManagerViewPlugin::cleanProjectItems() { runBuilderJob( KDevelop::BuilderJob::Clean, collectItems() ); } void ProjectManagerViewPlugin::buildProjectItems() { runBuilderJob( KDevelop::BuilderJob::Build, collectItems() ); } void ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { ICore::self()->projectController()->buildSetModel()->addProjectItem( item ); } } void ProjectManagerViewPlugin::runTargetsFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { KDevelop::ProjectExecutableTargetItem* t=item->executable(); if(t) { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "Running target: " << t->text() << t->builtUrl(); } } } void ProjectManagerViewPlugin::projectConfiguration( ) { if( !d->ctxProjectItemList.isEmpty() ) { ProjectModel* model = ICore::self()->projectController()->projectModel(); core()->projectController()->configureProject( model->itemFromIndex(d->ctxProjectItemList.at( 0 ))->project() ); } } void ProjectManagerViewPlugin::reloadFromContextMenu( ) { QList< KDevelop::ProjectFolderItem* > folders; foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { // since reloading should be recursive, only pass the upper-most items bool found = false; foreach ( KDevelop::ProjectFolderItem* existing, folders ) { if ( existing->path().isParentOf(item->folder()->path()) ) { // simply skip this child found = true; break; } else if ( item->folder()->path().isParentOf(existing->path()) ) { // remove the child in the list and add the current item instead folders.removeOne(existing); // continue since there could be more than one existing child } } if ( !found ) { folders << item->folder(); } } } foreach( KDevelop::ProjectFolderItem* folder, folders ) { folder->project()->projectFileManager()->reload(folder); } } void ProjectManagerViewPlugin::createFolderFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { if ( item->folder() ) { QWidget* window(ICore::self()->uiController()->activeMainWindow()->window()); QString name = QInputDialog::getText ( window, i18n ( "Create Folder in %1", item->folder()->path().pathOrUrl() ), i18n ( "Folder Name" ) ); if (!name.isEmpty()) { item->project()->projectFileManager()->addFolder( Path(item->path(), name), item->folder() ); } } } } void ProjectManagerViewPlugin::removeFromContextMenu() { removeItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::removeItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } //copy the list of selected items and sort it to guarantee parents will come before children QList sortedItems = items; std::sort(sortedItems.begin(), sortedItems.end(), ProjectBaseItem::pathLessThan); Path lastFolder; QHash< IProjectFileManager*, QList > filteredItems; QStringList itemPaths; foreach( KDevelop::ProjectBaseItem* item, sortedItems ) { if (item->isProjectRoot()) { continue; } else if (item->folder() || item->file()) { //make sure no children of folders that will be deleted are listed if (lastFolder.isParentOf(item->path())) { continue; } else if (item->folder()) { lastFolder = item->path(); } IProjectFileManager* manager = item->project()->projectFileManager(); if (manager) { filteredItems[manager] << item; itemPaths << item->path().pathOrUrl(); } } } if (filteredItems.isEmpty()) { return; } if (KMessageBox::warningYesNoList( QApplication::activeWindow(), i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", itemPaths.size()), itemPaths, i18n("Delete Files"), KStandardGuiItem::del(), KStandardGuiItem::cancel() ) == KMessageBox::No) { return; } //Go though projectmanagers, have them remove the files and folders that they own QHash< IProjectFileManager*, QList >::iterator it; for (it = filteredItems.begin(); it != filteredItems.end(); ++it) { Q_ASSERT(it.key()); it.key()->removeFilesAndFolders(it.value()); } } void ProjectManagerViewPlugin::removeTargetFilesFromContextMenu() { QList items = itemsFromIndexes( d->ctxProjectItemList ); QHash< IBuildSystemManager*, QList > itemsByBuildSystem; foreach(ProjectBaseItem *item, items) itemsByBuildSystem[item->project()->buildSystemManager()].append(item->file()); QHash< IBuildSystemManager*, QList >::iterator it; for (it = itemsByBuildSystem.begin(); it != itemsByBuildSystem.end(); ++it) it.key()->removeFilesFromTargets(it.value()); } void ProjectManagerViewPlugin::renameItemFromContextMenu() { renameItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::renameItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); foreach( KDevelop::ProjectBaseItem* item, items ) { if ((item->type()!=ProjectBaseItem::BuildFolder && item->type()!=ProjectBaseItem::Folder && item->type()!=ProjectBaseItem::File) || !item->parent()) { continue; } const QString src = item->text(); //Change QInputDialog->KFileSaveDialog? QString name = QInputDialog::getText( window, i18n("Rename..."), i18n("New name for '%1':", item->text()), QLineEdit::Normal, item->text() ); if (!name.isEmpty() && name != src) { ProjectBaseItem::RenameStatus status = item->rename( name ); switch(status) { case ProjectBaseItem::RenameOk: break; case ProjectBaseItem::ExistingItemSameName: KMessageBox::error(window, i18n("There is already a file named '%1'", name)); break; case ProjectBaseItem::ProjectManagerRenameFailed: KMessageBox::error(window, i18n("Could not rename '%1'", name)); break; case ProjectBaseItem::InvalidNewName: KMessageBox::error(window, i18n("'%1' is not a valid file name", name)); break; } } } } ProjectFileItem* createFile(const ProjectFolderItem* item) { QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); QString name = QInputDialog::getText(window, i18n("Create File in %1", item->path().pathOrUrl()), i18n("File name:")); if(name.isEmpty()) - return 0; + return nullptr; ProjectFileItem* ret = item->project()->projectFileManager()->addFile( Path(item->path(), name), item->folder() ); if (ret) { ICore::self()->documentController()->openDocument( ret->path().toUrl() ); } return ret; } void ProjectManagerViewPlugin::createFileFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { createFile(item->folder()); } else if ( item->target() ) { ProjectFolderItem* folder=dynamic_cast(item->parent()); if(folder) { ProjectFileItem* f=createFile(folder); if(f) item->project()->buildSystemManager()->addFilesToTarget(QList() << f, item->target()); } } } } void ProjectManagerViewPlugin::copyFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); QList urls; foreach (ProjectBaseItem* item, ctx->items()) { if (item->folder() || item->file()) { urls << item->path().toUrl(); } } qCDebug(PLUGIN_PROJECTMANAGERVIEW) << urls; if (!urls.isEmpty()) { QMimeData* data = new QMimeData; data->setUrls(urls); qApp->clipboard()->setMimeData(data); } } void ProjectManagerViewPlugin::pasteFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (ctx->items().count() != 1) return; //do nothing if multiple or none items are selected ProjectBaseItem* destItem = ctx->items().at(0); if (!destItem->folder()) return; //do nothing if the target is not a directory const QMimeData* data = qApp->clipboard()->mimeData(); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << data->urls(); const Path::List paths = toPathList(data->urls()); bool success = destItem->project()->projectFileManager()->copyFilesAndFolders(paths, destItem->folder()); if (success) { ProjectManagerViewItemContext* viewCtx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (viewCtx) { //expand target folder viewCtx->view()->expandItem(destItem); //and select new items QList newItems; foreach (const Path &path, paths) { const Path targetPath(destItem->path(), path.lastPathSegment()); foreach (ProjectBaseItem *item, destItem->children()) { if (item->path() == targetPath) { newItems << item; } } } viewCtx->view()->selectItems(newItems); } } } #include "projectmanagerviewplugin.moc" diff --git a/plugins/projectmanagerview/projectmodelsaver.cpp b/plugins/projectmanagerview/projectmodelsaver.cpp index 509c7daf85..e2f2ea51d7 100644 --- a/plugins/projectmanagerview/projectmodelsaver.cpp +++ b/plugins/projectmanagerview/projectmodelsaver.cpp @@ -1,73 +1,73 @@ /* This file is part of KDevelop Copyright 2012 Andrew Fuller 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 "projectmodelsaver.h" #include "projecttreeview.h" #include "project/projectmodel.h" #include #include #include #include namespace KDevelop { ProjectModelSaver::ProjectModelSaver() -: m_project(0) +: m_project(nullptr) { } void ProjectModelSaver::setProject(IProject* project) { m_project = project; } QModelIndex ProjectModelSaver::indexFromConfigString(const QAbstractItemModel *viewModel, const QString &key) const { const KDevelop::ProjectModel *projectModel = KDevelop::ICore::self()->projectController()->projectModel(); const QModelIndex sourceIndex = projectModel->pathToIndex(key.split('/')); if ( m_project && sourceIndex.isValid() ) { ProjectBaseItem* item = projectModel->itemFromIndex(sourceIndex); if ( item && item->project() == m_project ) { return ProjectTreeView::mapFromSource(qobject_cast(viewModel), sourceIndex); } } return QModelIndex(); } QString ProjectModelSaver::indexToConfigString(const QModelIndex& index) const { if( !index.isValid() || !m_project ) { return QString(); } ProjectBaseItem* item = index.data(ProjectModel::ProjectItemRole).value(); if ( !item || item->project() != m_project ) { return QString(); } return ICore::self()->projectController()->projectModel()->pathFromIndex( item->index() ).join(QLatin1Char('/')); } } diff --git a/plugins/projectmanagerview/projecttreeview.cpp b/plugins/projectmanagerview/projecttreeview.cpp index 8d128ad984..d0e2ea969a 100644 --- a/plugins/projectmanagerview/projecttreeview.cpp +++ b/plugins/projectmanagerview/projecttreeview.cpp @@ -1,473 +1,473 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2009 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projecttreeview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "projectmanagerviewplugin.h" #include "projectmodelsaver.h" #include "projectmodelitemdelegate.h" #include "debug.h" #include #include #include #include #include using namespace KDevelop; namespace { const char settingsConfigGroup[] = "ProjectTreeView"; QList fileItemsWithin(const QList& items) { QList fileItems; fileItems.reserve(items.size()); foreach(ProjectBaseItem* item, items) { if (ProjectFileItem *file = item->file()) fileItems.append(file); else if (item->folder()) fileItems.append(fileItemsWithin(item->children())); } return fileItems; } QList topLevelItemsWithin(QList items) { std::sort(items.begin(), items.end(), ProjectBaseItem::pathLessThan); Path lastFolder; for (int i = items.size() - 1; i >= 0; --i) { if (lastFolder.isParentOf(items[i]->path())) items.removeAt(i); else if (items[i]->folder()) lastFolder = items[i]->path(); } return items; } template void filterDroppedItems(QList &items, ProjectBaseItem* dest) { for (int i = items.size() - 1; i >= 0; --i) { //No drag and drop from and to same location if (items[i]->parent() == dest) items.removeAt(i); //No moving between projects (technically feasible if the projectmanager is the same though...) else if (items[i]->project() != dest->project()) items.removeAt(i); } } //TODO test whether this could be replaced by projectbuildsetwidget.cpp::showContextMenu_appendActions void popupContextMenu_appendActions(QMenu& menu, const QList& actions) { menu.addActions(actions); menu.addSeparator(); } } ProjectTreeView::ProjectTreeView( QWidget *parent ) - : QTreeView( parent ), m_ctxProject( 0 ) + : QTreeView( parent ), m_ctxProject( nullptr ) { header()->hide(); setEditTriggers( QAbstractItemView::EditKeyPressed ); setContextMenuPolicy( Qt::CustomContextMenu ); setSelectionMode( QAbstractItemView::ExtendedSelection ); setIndentation(10); setDragEnabled(true); setDragDropMode(QAbstractItemView::InternalMove); setAutoScroll(true); setAutoExpandDelay(300); setItemDelegate(new ProjectModelItemDelegate(this)); connect( this, &ProjectTreeView::customContextMenuRequested, this, &ProjectTreeView::popupContextMenu ); connect( this, &ProjectTreeView::activated, this, &ProjectTreeView::slotActivated ); connect( ICore::self(), &ICore::aboutToShutdown, this, &ProjectTreeView::aboutToShutdown); connect( ICore::self()->projectController(), &IProjectController::projectOpened, this, &ProjectTreeView::restoreState ); connect( ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectTreeView::saveState ); restoreState(); } ProjectTreeView::~ProjectTreeView() { } ProjectBaseItem* ProjectTreeView::itemAtPos(QPoint pos) { return indexAt(pos).data(ProjectModel::ProjectItemRole).value(); } void ProjectTreeView::dropEvent(QDropEvent* event) { ProjectItemContext* selectionCtxt = static_cast(KDevelop::ICore::self()->selectionController()->currentSelection()); ProjectBaseItem* destItem = itemAtPos(event->pos()); if (destItem && (dropIndicatorPosition() == AboveItem || dropIndicatorPosition() == BelowItem)) destItem = destItem->parent(); if (selectionCtxt && destItem) { if (ProjectFolderItem *folder = destItem->folder()) { QMenu dropMenu(this); QString seq = QKeySequence( Qt::ShiftModifier ).toString(); seq.chop(1); // chop superfluous '+' QAction* move = new QAction(i18n( "&Move Here" ) + '\t' + seq, &dropMenu); move->setIcon(QIcon::fromTheme(QStringLiteral("go-jump"))); dropMenu.addAction(move); seq = QKeySequence( Qt::ControlModifier ).toString(); seq.chop(1); QAction* copy = new QAction(i18n( "&Copy Here" ) + '\t' + seq, &dropMenu); copy->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); dropMenu.addAction(copy); dropMenu.addSeparator(); QAction* cancel = new QAction(i18n( "C&ancel" ) + '\t' + QKeySequence( Qt::Key_Escape ).toString(), &dropMenu); cancel->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); dropMenu.addAction(cancel); - QAction *executedAction = 0; + QAction *executedAction = nullptr; Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (modifiers == Qt::ControlModifier) { executedAction = copy; } else if (modifiers == Qt::ShiftModifier) { executedAction = move; } else { executedAction = dropMenu.exec(this->mapToGlobal(event->pos())); } QList usefulItems = topLevelItemsWithin(selectionCtxt->items()); filterDroppedItems(usefulItems, destItem); Path::List paths; foreach (ProjectBaseItem* i, usefulItems) { paths << i->path(); } bool success = false; if (executedAction == copy) { success = destItem->project()->projectFileManager()->copyFilesAndFolders(paths, folder); } else if (executedAction == move) { success = destItem->project()->projectFileManager()->moveFilesAndFolders(usefulItems, folder); } if (success) { //expand target folder expand( mapFromItem(folder)); //and select new items QItemSelection selection; foreach (const Path &path, paths) { const Path targetPath(folder->path(), path.lastPathSegment()); foreach (ProjectBaseItem *item, folder->children()) { if (item->path() == targetPath) { QModelIndex indx = mapFromItem( item ); selection.append(QItemSelectionRange(indx, indx)); setCurrentIndex(indx); } } } selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } } else if (destItem->target() && destItem->project()->buildSystemManager()) { QMenu dropMenu(this); QString seq = QKeySequence( Qt::ControlModifier ).toString(); seq.chop(1); QAction* addToTarget = new QAction(i18n( "&Add to Target" ) + '\t' + seq, &dropMenu); addToTarget->setIcon(QIcon::fromTheme(QStringLiteral("edit-link"))); dropMenu.addAction(addToTarget); dropMenu.addSeparator(); QAction* cancel = new QAction(i18n( "C&ancel" ) + '\t' + QKeySequence( Qt::Key_Escape ).toString(), &dropMenu); cancel->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); dropMenu.addAction(cancel); - QAction *executedAction = 0; + QAction *executedAction = nullptr; Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (modifiers == Qt::ControlModifier) { executedAction = addToTarget; } else { executedAction = dropMenu.exec(this->mapToGlobal(event->pos())); } if (executedAction == addToTarget) { QList usefulItems = fileItemsWithin(selectionCtxt->items()); filterDroppedItems(usefulItems, destItem); destItem->project()->buildSystemManager()->addFilesToTarget(usefulItems, destItem->target()); } } } event->accept(); } QModelIndex ProjectTreeView::mapFromSource(const QAbstractProxyModel* proxy, const QModelIndex& sourceIdx) { const QAbstractItemModel* next = proxy->sourceModel(); Q_ASSERT(next == sourceIdx.model() || qobject_cast(next)); if(next == sourceIdx.model()) return proxy->mapFromSource(sourceIdx); else { const QAbstractProxyModel* nextProxy = qobject_cast(next); QModelIndex idx = mapFromSource(nextProxy, sourceIdx); Q_ASSERT(idx.model() == nextProxy); return proxy->mapFromSource(idx); } } QModelIndex ProjectTreeView::mapFromItem(const ProjectBaseItem* item) { QModelIndex ret = mapFromSource(qobject_cast(model()), item->index()); Q_ASSERT(ret.model() == model()); return ret; } void ProjectTreeView::slotActivated( const QModelIndex &index ) { if ( QApplication::keyboardModifiers() & Qt::CTRL || QApplication::keyboardModifiers() & Qt::SHIFT ) { // Do not open file when Ctrl or Shift is pressed; that's for selection return; } KDevelop::ProjectBaseItem *item = index.data(ProjectModel::ProjectItemRole).value(); if ( item && item->file() ) { emit activate( item->file()->path() ); } } void ProjectTreeView::popupContextMenu( const QPoint &pos ) { QList itemlist; if ( indexAt(pos).isValid() ) { QModelIndexList indexes = selectionModel()->selectedRows(); foreach( const QModelIndex& index, indexes ) { if ( KDevelop::ProjectBaseItem *item = index.data(ProjectModel::ProjectItemRole).value() ) itemlist << item; } } if( !itemlist.isEmpty() ) { m_ctxProject = itemlist.at(0)->project(); } else { - m_ctxProject = 0; + m_ctxProject = nullptr; } QMenu menu( this ); KDevelop::ProjectItemContextImpl context(itemlist); QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); QList buildActions; QList vcsActions; QList analyzeActions; QList extActions; QList projectActions; QList fileActions; QList runActions; foreach( const ContextMenuExtension& ext, extensions ) { buildActions += ext.actions(ContextMenuExtension::BuildGroup); fileActions += ext.actions(ContextMenuExtension::FileGroup); projectActions += ext.actions(ContextMenuExtension::ProjectGroup); vcsActions += ext.actions(ContextMenuExtension::VcsGroup); analyzeActions += ext.actions(ContextMenuExtension::AnalyzeGroup); extActions += ext.actions(ContextMenuExtension::ExtensionGroup); runActions += ext.actions(ContextMenuExtension::RunGroup); } if ( analyzeActions.count() ) { QMenu* analyzeMenu = new QMenu( i18n("Analyze With"), this ); analyzeMenu->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok"))); foreach( QAction* act, analyzeActions ) { analyzeMenu->addAction( act ); } analyzeActions = {analyzeMenu->menuAction()}; } popupContextMenu_appendActions(menu, buildActions); popupContextMenu_appendActions(menu, runActions ); popupContextMenu_appendActions(menu, fileActions); popupContextMenu_appendActions(menu, vcsActions); popupContextMenu_appendActions(menu, analyzeActions); popupContextMenu_appendActions(menu, extActions); if ( !itemlist.isEmpty() && itemlist.size() == 1 && itemlist[0]->folder() && !itemlist[0]->folder()->parent() ) { QAction* projectConfig = new QAction(i18n("Open Configuration..."), this); projectConfig->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect( projectConfig, &QAction::triggered, this, &ProjectTreeView::openProjectConfig ); projectActions << projectConfig; } popupContextMenu_appendActions(menu, projectActions); if(!itemlist.isEmpty()) KDevelop::populateParentItemsMenu(itemlist.front(), &menu); if ( !menu.isEmpty() ) { menu.exec( mapToGlobal( pos ) ); } } void ProjectTreeView::openProjectConfig() { if( m_ctxProject ) { IProjectController* ip = ICore::self()->projectController(); ip->configureProject( m_ctxProject ); } } void ProjectTreeView::saveState() { KConfigGroup configGroup( ICore::self()->activeSession()->config(), settingsConfigGroup ); ProjectModelSaver saver; saver.setView( this ); saver.saveState( configGroup ); } void ProjectTreeView::restoreState(IProject* project) { KConfigGroup configGroup( ICore::self()->activeSession()->config(), settingsConfigGroup ); ProjectModelSaver saver; saver.setProject( project ); saver.setView( this ); saver.restoreState( configGroup ); } void ProjectTreeView::aboutToShutdown() { // save all projects, not just the last one that is closed disconnect( ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectTreeView::saveState ); saveState(); } bool ProjectTreeView::event(QEvent* event) { if(event->type()==QEvent::ToolTip) { QPoint p = mapFromGlobal(QCursor::pos()); QModelIndex idxView = indexAt(p); ProjectBaseItem* it = idxView.data(ProjectModel::ProjectItemRole).value(); QModelIndex idx; if(it) idx = it->index(); if((m_idx!=idx || !m_tooltip) && it && it->file()) { m_idx=idx; ProjectFileItem* file=it->file(); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); TopDUContext* top= DUChainUtils::standardContextForUrl(file->path().toUrl()); if(m_tooltip) m_tooltip->close(); if(top) { QWidget* navigationWidget = top->createNavigationWidget(); if( navigationWidget ) { m_tooltip = new KDevelop::NavigationToolTip(this, mapToGlobal(p) + QPoint(40, 0), navigationWidget); m_tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "tooltip size" << m_tooltip->size(); ActiveToolTip::showToolTip(m_tooltip); return true; } } } } return QAbstractItemView::event(event); } void ProjectTreeView::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Return && currentIndex().isValid() && state()!=QAbstractItemView::EditingState) { event->accept(); slotActivated(currentIndex()); } else QTreeView::keyPressEvent(event); } void ProjectTreeView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const { if (WidgetColorizer::colorizeByProject()) { const auto projectPath = index.data(ProjectModel::ProjectRole).value()->path(); const QColor color = WidgetColorizer::colorForId(qHash(projectPath), palette(), true); WidgetColorizer::drawBranches(this, painter, rect, index, color); } QTreeView::drawBranches(painter, rect, index); } diff --git a/plugins/quickopen/duchainitemquickopen.cpp b/plugins/quickopen/duchainitemquickopen.cpp index 139104bd35..bbf8408e4b 100644 --- a/plugins/quickopen/duchainitemquickopen.cpp +++ b/plugins/quickopen/duchainitemquickopen.cpp @@ -1,252 +1,252 @@ /* This file is part of the KDE libraries Copyright (C) 2007 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 "projectitemquickopen.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; DUChainItemData::DUChainItemData( const DUChainItem& file, bool openDefinition ) : m_item(file) , m_openDefinition(openDefinition) { } QString DUChainItemData::text() const { DUChainReadLocker lock;; Declaration* decl = m_item.m_item.data(); if(!decl) return i18n("Not available any more: %1", m_item.m_text); if(FunctionDefinition* def = dynamic_cast(decl)) { if(def->declaration()) { decl = def->declaration(); } } QString text = decl->qualifiedIdentifier().toString(); if(!decl->abstractType()) { //With simplified representation, still mark functions as such by adding parens if(dynamic_cast(decl)) { text += QLatin1String("(...)"); } }else if(TypePtr function = decl->type()) { text += function->partToString( FunctionType::SignatureArguments ); } return text; } QList DUChainItemData::highlighting() const { DUChainReadLocker lock;; Declaration* decl = m_item.m_item.data(); if(!decl) { return QList(); } if(FunctionDefinition* def = dynamic_cast(decl)) { if(def->declaration()) { decl = def->declaration(); } } QTextCharFormat boldFormat; boldFormat.setFontWeight(QFont::Bold); QTextCharFormat normalFormat; int prefixLength = 0; QString signature; TypePtr function = decl->type(); if(function) { signature = function->partToString( FunctionType::SignatureArguments ); } //Only highlight the last part of the qualified identifier, so the scope doesn't distract too much QualifiedIdentifier id = decl->qualifiedIdentifier(); QString fullId = id.toString(); QString lastId; if(!id.isEmpty()) { lastId = id.last().toString(); } prefixLength += fullId.length() - lastId.length(); QList ret; ret << 0; ret << prefixLength; ret << QVariant(normalFormat); ret << prefixLength; ret << lastId.length(); ret << QVariant(boldFormat); if(!signature.isEmpty()) { ret << prefixLength + lastId.length(); ret << signature.length(); ret << QVariant(normalFormat); } return ret; } QString DUChainItemData::htmlDescription() const { if(m_item.m_noHtmlDestription) { return QString(); } DUChainReadLocker lock;; Declaration* decl = m_item.m_item.data(); if(!decl) { return i18n("Not available any more"); } TypePtr function = decl->type(); QString text; if( function && function->returnType() ) { text = i18nc("%1: function signature", "Return: %1", function->partToString(FunctionType::SignatureReturn)) + QLatin1String(" "); } text += i18nc("%1: file path", "File: %1", ICore::self()->projectController()->prettyFileName(decl->url().toUrl())); QString ret = "" + text + ""; return ret; } bool DUChainItemData::execute( QString& /*filterText*/ ) { DUChainReadLocker lock;; Declaration* decl = m_item.m_item.data(); if(!decl) { return false; } if(m_openDefinition && FunctionDefinition::definition(decl)) { decl = FunctionDefinition::definition(decl); } QUrl url = decl->url().toUrl(); KTextEditor::Cursor cursor = decl->rangeInCurrentRevision().start(); DUContext* internal = decl->internalContext(); if(internal && (internal->type() == DUContext::Other || internal->type() == DUContext::Class)) { //Move into the body if(internal->range().end.line > internal->range().start.line) { cursor = KTextEditor::Cursor(internal->range().start.line+1, 0); //Move into the body } } lock.unlock(); ICore::self()->documentController()->openDocument( url, cursor ); return true; } bool DUChainItemData::isExpandable() const { return true; } QWidget* DUChainItemData::expandingWidget() const { DUChainReadLocker lock;; Declaration* decl = dynamic_cast(m_item.m_item.data()); if( !decl || !decl->context() ) { - return 0; + return nullptr; } return decl->context()->createNavigationWidget( decl, decl->topContext(), QString(), QString(), AbstractNavigationWidget::EmbeddableWidget); } QIcon DUChainItemData::icon() const { return QIcon(); } Path DUChainItemData::projectPath() const { return m_item.m_projectPath; } DUChainItemDataProvider::DUChainItemDataProvider( IQuickOpen* quickopen, bool openDefinitions ) : m_quickopen(quickopen) , m_openDefinitions(openDefinitions) { reset(); } void DUChainItemDataProvider::setFilterText( const QString& text ) { Base::setFilter( text ); } uint DUChainItemDataProvider::itemCount() const { return Base::filteredItems().count(); } uint DUChainItemDataProvider::unfilteredItemCount() const { return Base::items().count(); } QuickOpenDataPointer DUChainItemDataProvider::data( uint row ) const { return KDevelop::QuickOpenDataPointer( createData( Base::filteredItems()[row] ) ); } DUChainItemData* DUChainItemDataProvider::createData( const DUChainItem& item ) const { return new DUChainItemData( item, m_openDefinitions ); } QString DUChainItemDataProvider::itemText( const DUChainItem& data ) const { return data.m_text; } void DUChainItemDataProvider::reset() { } diff --git a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp index a0e9a0d7d6..4eaa3d2420 100644 --- a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp +++ b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp @@ -1,529 +1,529 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "expandingwidgetmodel.h" #include #include #include #include #include #include #include #include "expandingdelegate.h" #include #include "../debug.h" QIcon ExpandingWidgetModel::m_expandedIcon; QIcon ExpandingWidgetModel::m_collapsedIcon; using namespace KTextEditor; inline QModelIndex firstColumn( const QModelIndex& index ) { return index.sibling(index.row(), 0); } ExpandingWidgetModel::ExpandingWidgetModel( QWidget* parent ) : QAbstractTableModel(parent) { } ExpandingWidgetModel::~ExpandingWidgetModel() { clearExpanding(); } static QColor doAlternate(QColor color) { QColor background = QApplication::palette().background().color(); return KColorUtils::mix(color, background, 0.15); } uint ExpandingWidgetModel::matchColor(const QModelIndex& index) const { int matchQuality = contextMatchQuality( index.sibling(index.row(), 0) ); if( matchQuality > 0 ) { bool alternate = index.row() & 1; QColor badMatchColor(0xff00aa44); //Blue-ish green QColor goodMatchColor(0xff00ff00); //Green QColor background = treeView()->palette().light().color(); QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, ((float)matchQuality)/10.0); if(alternate) totalColor = doAlternate(totalColor); const float dynamicTint = 0.2f; const float minimumTint = 0.2f; double tintStrength = (dynamicTint*matchQuality)/10; if(tintStrength) tintStrength += minimumTint; //Some minimum tinting strength, else it's not visible any more return KColorUtils::tint(background, totalColor, tintStrength ).rgb(); }else{ return 0; } } QVariant ExpandingWidgetModel::data( const QModelIndex & index, int role ) const { switch( role ) { case Qt::BackgroundRole: { if( index.column() == 0 ) { //Highlight by match-quality uint color = matchColor(index); if( color ) return QBrush( color ); } //Use a special background-color for expanded items if( isExpanded(index) ) { if( index.row() & 1 ) { return doAlternate(treeView()->palette().toolTipBase().color()); } else { return treeView()->palette().toolTipBase(); } } } } return QVariant(); } void ExpandingWidgetModel::clearMatchQualities() { m_contextMatchQualities.clear(); } QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const { if( m_partiallyExpanded.isEmpty() ) return QModelIndex(); else return m_partiallyExpanded.constBegin().key(); } void ExpandingWidgetModel::clearExpanding() { clearMatchQualities(); QMap oldExpandState = m_expandState; foreach( QPointer widget, m_expandingWidgets ) delete widget; m_expandingWidgets.clear(); m_expandState.clear(); m_partiallyExpanded.clear(); for( QMap::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it ) if(it.value() == Expanded) emit dataChanged(it.key(), it.key()); } ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex& index) const { if( m_partiallyExpanded.contains(firstColumn(index)) ) return m_partiallyExpanded[firstColumn(index)]; else return NotExpanded; } void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex& idx_) { QModelIndex index( firstColumn(idx_) ); m_partiallyExpanded.remove(index); m_partiallyExpanded.remove(idx_); } int ExpandingWidgetModel::partiallyExpandWidgetHeight() const { return 60; ///@todo use font-metrics text-height*2 for 2 lines } void ExpandingWidgetModel::rowSelected(const QModelIndex& idx_) { QModelIndex idx( firstColumn(idx_) ); if( !m_partiallyExpanded.contains( idx ) ) { QModelIndex oldIndex = partiallyExpandedRow(); //Unexpand the previous partially expanded row if( !m_partiallyExpanded.isEmpty() ) { ///@todo allow multiple partially expanded rows while( !m_partiallyExpanded.isEmpty() ) m_partiallyExpanded.erase(m_partiallyExpanded.begin()); //partiallyUnExpand( m_partiallyExpanded.begin().key() ); } //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget. if( !idx.isValid() ) { //All items have been unselected if( oldIndex.isValid() ) emit dataChanged(oldIndex, oldIndex); } else { QVariant variant = data(idx, CodeCompletionModel::ItemSelected); if( !isExpanded(idx) && variant.type() == QVariant::String) { //Either expand upwards or downwards, choose in a way that //the visible fields of the new selected entry are not moved. if( oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()) ) ) m_partiallyExpanded.insert(idx, ExpandUpwards); else m_partiallyExpanded.insert(idx, ExpandDownwards); //Say that one row above until one row below has changed, so no items will need to be moved(the space that is taken from one item is given to the other) if( oldIndex.isValid() && oldIndex < idx ) { emit dataChanged(oldIndex, idx); if( treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem ) { //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible, //so we do the scrolling by hand. QRect selectedRect = treeView()->visualRect(idx); QRect frameRect = treeView()->frameRect(); if( selectedRect.bottom() > frameRect.bottom() ) { int diff = selectedRect.bottom() - frameRect.bottom(); //We need to scroll down QModelIndex newTopIndex = idx; QModelIndex nextTopIndex = idx; QRect nextRect = treeView()->visualRect(nextTopIndex); while( nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff ) { newTopIndex = nextTopIndex; nextTopIndex = treeView()->indexAbove(nextTopIndex); if( nextTopIndex.isValid() ) nextRect = treeView()->visualRect(nextTopIndex); } treeView()->scrollTo( newTopIndex, QAbstractItemView::PositionAtTop ); } } //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible. //But we must make sure that it isn't too expensive. //We need to make sure that scrolling is efficient, and the whole content is not repainted. //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature. //Since this also doesn't work smoothly, leave it for now //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); } else if( oldIndex.isValid() && idx < oldIndex ) { emit dataChanged(idx, oldIndex); //For consistency with the down-scrolling, we keep one additional line visible above the current visible. //Since this also doesn't work smoothly, leave it for now /* QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column()); if( prevLine.isValid() ) treeView()->scrollTo( prevLine );*/ } else emit dataChanged(idx, idx); } else if( oldIndex.isValid() ) { //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded. emit dataChanged(oldIndex, oldIndex); } } }else{ qCDebug( PLUGIN_QUICKOPEN ) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded"; } } QString ExpandingWidgetModel::partialExpandText(const QModelIndex& idx) const { if( !idx.isValid() ) return QString(); return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); } QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if( !idx.isValid() ) return QRect(); ExpansionType expansion = ExpandDownwards; if( m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd() ) expansion = m_partiallyExpanded[idx]; //Get the whole rectangle of the row: QModelIndex rightMostIndex = idx; QModelIndex tempIndex = idx; while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() ) rightMostIndex = tempIndex; QRect rect = treeView()->visualRect(idx); QRect rightMostRect = treeView()->visualRect(rightMostIndex); rect.setLeft( rect.left() + 20 ); rect.setRight( rightMostRect.right() - 5 ); //These offsets must match exactly those used in ExpandingDelegate::sizeHint() int top = rect.top() + 5; int bottom = rightMostRect.bottom() - 5 ; if( expansion == ExpandDownwards ) top += basicRowHeight(idx); else bottom -= basicRowHeight(idx); rect.setTop( top ); rect.setBottom( bottom ); return rect; } bool ExpandingWidgetModel::isExpandable(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if( !m_expandState.contains(idx) ) { m_expandState.insert(idx, NotExpandable); QVariant v = data(idx, CodeCompletionModel::IsExpandable); if( v.canConvert() && v.toBool() ) m_expandState[idx] = Expandable; } return m_expandState[idx] != NotExpandable; } bool ExpandingWidgetModel::isExpanded(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); return m_expandState.contains(idx) && m_expandState[idx] == Expanded; } void ExpandingWidgetModel::setExpanded(QModelIndex idx_, bool expanded) { QModelIndex idx(firstColumn(idx_)); //qCDebug( PLUGIN_QUICKOPEN ) << "Setting expand-state of row " << idx.row() << " to " << expanded; if( !idx.isValid() ) return; if( isExpandable(idx) ) { if( !expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx] ) { m_expandingWidgets[idx]->hide(); } m_expandState[idx] = expanded ? Expanded : Expandable; if( expanded ) partiallyUnExpand(idx); if( expanded && !m_expandingWidgets.contains(idx) ) { QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); if( v.canConvert() ) { m_expandingWidgets[idx] = v.value(); } else if( v.canConvert() ) { //Create a html widget that shows the given string KTextEdit* edit = new KTextEdit( v.toString() ); edit->setReadOnly(true); edit->resize(200, 50); //Make the widget small so it embeds nicely. m_expandingWidgets[idx] = edit; } else { - m_expandingWidgets[idx] = 0; + m_expandingWidgets[idx] = nullptr; } } //Eventually partially expand the row if( !expanded && firstColumn(treeView()->currentIndex()) == idx && !isPartiallyExpanded(idx) ) rowSelected(idx); //Partially expand the row. emit dataChanged(idx, idx); if(treeView()) treeView()->scrollTo(idx); } } int ExpandingWidgetModel::basicRowHeight( const QModelIndex& idx_ ) const { QModelIndex idx(firstColumn(idx_)); ExpandingDelegate* delegate = dynamic_cast( treeView()->itemDelegate(idx) ); if( !delegate || !idx.isValid() ) { qCDebug( PLUGIN_QUICKOPEN ) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; return 15; } return delegate->basicSizeHint( idx ).height(); } void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex& idx_) { QModelIndex idx(firstColumn(idx_)); - QWidget* w = 0; + QWidget* w = nullptr; if( m_expandingWidgets.contains(idx) ) w = m_expandingWidgets[idx]; if( w && isExpanded(idx) ) { if( !idx.isValid() ) return; QRect rect = treeView()->visualRect(idx); if( !rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height() ) { //The item is currently not visible w->hide(); return; } QModelIndex rightMostIndex = idx; QModelIndex tempIndex = idx; while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() ) rightMostIndex = tempIndex; QRect rightMostRect = treeView()->visualRect(rightMostIndex); //Find out the basic height of the row rect.setLeft( rect.left() + 20 ); rect.setRight( rightMostRect.right() - 5 ); //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() rect.setTop( rect.top() + basicRowHeight(idx) + 5 ); rect.setHeight( w->height() ); if( w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible() ) { w->setParent( treeView()->viewport() ); w->setGeometry(rect); w->show(); } } } void ExpandingWidgetModel::placeExpandingWidgets() { for( QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) { placeExpandingWidget(it.key()); } } int ExpandingWidgetModel::expandingWidgetsHeight() const { int sum = 0; for( QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) { if( isExpanded(it.key() ) && (*it) ) sum += (*it)->height(); } return sum; } QWidget* ExpandingWidgetModel::expandingWidget(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if( m_expandingWidgets.contains(idx) ) return m_expandingWidgets[idx]; else - return 0; + return nullptr; } void ExpandingWidgetModel::cacheIcons() const { if( m_expandedIcon.isNull() ) m_expandedIcon = QIcon::fromTheme(QStringLiteral("arrow-down")); if( m_collapsedIcon.isNull() ) m_collapsedIcon = QIcon::fromTheme(QStringLiteral("arrow-right")); } QList mergeCustomHighlighting( int leftSize, const QList& left, int rightSize, const QList& right ) { QList ret = left; if( left.isEmpty() ) { ret << QVariant(0); ret << QVariant(leftSize); ret << QTextFormat(QTextFormat::CharFormat); } if( right.isEmpty() ) { ret << QVariant(leftSize); ret << QVariant(rightSize); ret << QTextFormat(QTextFormat::CharFormat); } else { QList::const_iterator it = right.constBegin(); while( it != right.constEnd() ) { { QList::const_iterator testIt = it; for(int a = 0; a < 2; a++) { ++testIt; if(testIt == right.constEnd()) { qWarning() << "Length of input is not multiple of 3"; break; } } } ret << QVariant( (*it).toInt() + leftSize ); ++it; ret << QVariant( (*it).toInt() ); ++it; ret << *it; if(!(*it).value().isValid()) qCDebug( PLUGIN_QUICKOPEN ) << "Text-format is invalid"; ++it; } } return ret; } //It is assumed that between each two strings, one space is inserted QList mergeCustomHighlighting( QStringList strings, QList highlights, int grapBetweenStrings ) { if(strings.isEmpty()) { qWarning() << "List of strings is empty"; return QList(); } if(highlights.isEmpty()) { qWarning() << "List of highlightings is empty"; return QList(); } if(strings.count() != highlights.count()) { qWarning() << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; return QList(); } //Merge them together QString totalString = strings[0]; QVariantList totalHighlighting = highlights[0]; strings.pop_front(); highlights.pop_front(); while( !strings.isEmpty() ) { totalHighlighting = mergeCustomHighlighting( totalString.length(), totalHighlighting, strings[0].length(), highlights[0] ); totalString += strings[0]; for(int a = 0; a < grapBetweenStrings; a++) totalString += ' '; strings.pop_front(); highlights.pop_front(); } //Combine the custom-highlightings return totalHighlighting; } diff --git a/plugins/quickopen/projectfilequickopen.cpp b/plugins/quickopen/projectfilequickopen.cpp index 3725e64aec..f7b14b170d 100644 --- a/plugins/quickopen/projectfilequickopen.cpp +++ b/plugins/quickopen/projectfilequickopen.cpp @@ -1,368 +1,368 @@ /* This file is part of the KDE libraries Copyright (C) 2007 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 "projectfilequickopen.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" using namespace KDevelop; namespace { QSet openFiles() { QSet openFiles; const QList& docs = ICore::self()->documentController()->openDocuments(); openFiles.reserve(docs.size()); foreach( IDocument* doc, docs ) { openFiles << IndexedString(doc->url()); } return openFiles; } QString iconNameForUrl(const IndexedString& url) { if (url.isEmpty()) { return QStringLiteral("tab-duplicate"); } ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemForPath(url); if (item) { return item->iconName(); } return QStringLiteral("unknown"); } } ProjectFileData::ProjectFileData( const ProjectFile& file ) : m_file(file) { } QString ProjectFileData::text() const { return m_file.projectPath.relativePath(m_file.path); } QString ProjectFileData::htmlDescription() const { return "" + i18nc("%1: project name", "Project %1", project()) + ""; } bool ProjectFileData::execute( QString& filterText ) { const QUrl url = m_file.path.toUrl(); IOpenWith::openFiles(QList() << url); auto cursor = KTextEditorHelpers::extractCursor(filterText); if (cursor.isValid()) { IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (doc) { doc->setCursorPosition(cursor); } } return true; } bool ProjectFileData::isExpandable() const { return true; } QList ProjectFileData::highlighting() const { QTextCharFormat boldFormat; boldFormat.setFontWeight(QFont::Bold); QTextCharFormat normalFormat; QString txt = text(); QList ret; int fileNameLength = m_file.path.lastPathSegment().length(); ret << 0; ret << txt.length() - fileNameLength; ret << QVariant(normalFormat); ret << txt.length() - fileNameLength; ret << fileNameLength; ret << QVariant(boldFormat); return ret; } QWidget* ProjectFileData::expandingWidget() const { const QUrl url = m_file.path.toUrl(); DUChainReadLocker lock; ///Find a du-chain for the document QList contexts = DUChain::self()->chainsForDocument(url); ///Pick a non-proxy context - TopDUContext* chosen = 0; + TopDUContext* chosen = nullptr; foreach( TopDUContext* ctx, contexts ) { if( !(ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->isProxyContext()) ) { chosen = ctx; } } if( chosen ) { - return chosen->createNavigationWidget(0, 0, + return chosen->createNavigationWidget(nullptr, nullptr, "" + i18nc("%1: project name", "Project %1", project()) + "
"); } else { QTextBrowser* ret = new QTextBrowser(); ret->resize(400, 100); ret->setText( "" + i18nc("%1: project name", "Project %1", project()) + "
" + i18n("Not parsed yet") + "
"); return ret; } - return 0; + return nullptr; } QIcon ProjectFileData::icon() const { const QString& iconName = iconNameForUrl(m_file.indexedPath); /** * FIXME: Move this cache into a more central place and reuse it elsewhere. * The project model e.g. could reuse this as well. * * Note: We cache here since otherwise displaying and esp. scrolling * in a large list of quickopen items becomes very slow. */ static QHash iconCache; QHash< QString, QPixmap >::const_iterator it = iconCache.constFind(iconName); if (it != iconCache.constEnd()) { return it.value(); } const QPixmap& pixmap = KIconLoader::global()->loadIcon(iconName, KIconLoader::Small); iconCache.insert(iconName, pixmap); return pixmap; } QString ProjectFileData::project() const { const IProject* project = ICore::self()->projectController()->findProjectForUrl(m_file.path.toUrl()); if (project) { return project->name(); } else { return i18n("none"); } } Path ProjectFileData::projectPath() const { return m_file.projectPath; } BaseFileDataProvider::BaseFileDataProvider() { } void BaseFileDataProvider::setFilterText( const QString& text ) { int pathLength; KTextEditorHelpers::extractCursor(text, &pathLength); QString path(text.mid(0, pathLength)); if ( path.startsWith(QLatin1String("./")) || path.startsWith(QLatin1String("../")) ) { // assume we want to filter relative to active document's url IDocument* doc = ICore::self()->documentController()->activeDocument(); if (doc) { path = Path(Path(doc->url()).parent(), path).pathOrUrl(); } } setFilter( path.split('/', QString::SkipEmptyParts) ); } uint BaseFileDataProvider::itemCount() const { return filteredItems().count(); } uint BaseFileDataProvider::unfilteredItemCount() const { return items().count(); } QuickOpenDataPointer BaseFileDataProvider::data(uint row) const { return QuickOpenDataPointer(new ProjectFileData( filteredItems().at(row) )); } ProjectFileDataProvider::ProjectFileDataProvider() { auto projectController = ICore::self()->projectController(); connect(projectController, &IProjectController::projectClosing, this, &ProjectFileDataProvider::projectClosing); connect(projectController, &IProjectController::projectOpened, this, &ProjectFileDataProvider::projectOpened); foreach (const auto project, projectController->projects()) { projectOpened(project); } } void ProjectFileDataProvider::projectClosing( IProject* project ) { foreach(ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) { fileRemovedFromSet(file); } } void ProjectFileDataProvider::projectOpened( IProject* project ) { const int processAfter = 1000; int processed = 0; foreach(ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) { fileAddedToSet(file); if (++processed == processAfter) { // prevent UI-lockup when a huge project was imported QApplication::processEvents(); processed = 0; } } connect(project, &IProject::fileAddedToSet, this, &ProjectFileDataProvider::fileAddedToSet); connect(project, &IProject::fileRemovedFromSet, this, &ProjectFileDataProvider::fileRemovedFromSet); } void ProjectFileDataProvider::fileAddedToSet( ProjectFileItem* file ) { ProjectFile f; f.projectPath = file->project()->path(); f.path = file->path(); f.indexedPath = file->indexedPath(); f.outsideOfProject = !f.projectPath.isParentOf(f.path); auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), f); if (it == m_projectFiles.end() || it->path != f.path) { m_projectFiles.insert(it, f); } } void ProjectFileDataProvider::fileRemovedFromSet( ProjectFileItem* file ) { ProjectFile item; item.path = file->path(); // fast-path for non-generated files // NOTE: figuring out whether something is generated is expensive... and since // generated files are rare we apply this two-step algorithm here auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item); if (it != m_projectFiles.end() && !(item < *it)) { m_projectFiles.erase(it); return; } // last try: maybe it was generated item.outsideOfProject = true; it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item); if (it != m_projectFiles.end() && !(item < *it)) { m_projectFiles.erase(it); return; } } void ProjectFileDataProvider::reset() { clearFilter(); QList projectFiles = m_projectFiles; const auto& open = openFiles(); for(QList::iterator it = projectFiles.begin(); it != projectFiles.end();) { if (open.contains(it->indexedPath)) { it = projectFiles.erase(it); } else { ++it; } } setItems(projectFiles); } QSet ProjectFileDataProvider::files() const { QSet ret; foreach( IProject* project, ICore::self()->projectController()->projects() ) ret += project->fileSet(); return ret - openFiles(); } void OpenFilesDataProvider::reset() { clearFilter(); IProjectController* projCtrl = ICore::self()->projectController(); IDocumentController* docCtrl = ICore::self()->documentController(); const QList& docs = docCtrl->openDocuments(); QList currentFiles; currentFiles.reserve(docs.size()); foreach( IDocument* doc, docs ) { ProjectFile f; f.path = Path(doc->url()); IProject* project = projCtrl->findProjectForUrl(doc->url()); if (project) { f.projectPath = project->path(); } currentFiles << f; } std::sort(currentFiles.begin(), currentFiles.end()); setItems(currentFiles); } QSet OpenFilesDataProvider::files() const { return openFiles(); } diff --git a/plugins/quickopen/projectitemquickopen.cpp b/plugins/quickopen/projectitemquickopen.cpp index 95ae99cb9b..805480d467 100644 --- a/plugins/quickopen/projectitemquickopen.cpp +++ b/plugins/quickopen/projectitemquickopen.cpp @@ -1,376 +1,376 @@ /* This file is part of the KDE libraries Copyright (C) 2007 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 "projectitemquickopen.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { struct SubstringCache { SubstringCache( const QString& string = QString() ) : substring(string) { } inline int containedIn( const Identifier& id ) const { int index = id.index(); QHash::const_iterator it = cache.constFind(index); if(it != cache.constEnd()) { return *it; } const QString idStr = id.identifier().str(); int result = idStr.lastIndexOf(substring, -1, Qt::CaseInsensitive); if (result < 0 && !idStr.isEmpty() && !substring.isEmpty()) { // no match; try abbreviations result = matchesAbbreviation(idStr.midRef(0), substring) ? 0 : -1; } //here we shift the values if the matched string is bigger than the substring, //so closer matches will appear first if (result >= 0) { result = result + (idStr.size() - substring.size()); } cache[index] = result; return result; } QString substring; mutable QHash cache; }; struct ClosestMatchToText { ClosestMatchToText( const QHash& _cache ) : cache(_cache) { } /** @brief Calculates the distance to two pre-filtered match items * * @param a The CodeModelView witch represents the first item to be tested * @param b The CodeModelView witch represents the second item to be tested * * @b */ inline bool operator()( const CodeModelViewItem& a, const CodeModelViewItem& b ) const { const int height_a = cache.value(a.m_id.index(), -1); const int height_b = cache.value(b.m_id.index(), -1); Q_ASSERT(height_a != -1); Q_ASSERT(height_b != -1); if (height_a == height_b) { // stable sorting for equal items based on index // TODO: fast string-based sorting in such cases? return a.m_id.index() < b.m_id.index(); } return height_a < height_b; } private: const QHash& cache; }; Path findProjectForForPath(const IndexedString& path) { const auto model = ICore::self()->projectController()->projectModel(); const auto item = model->itemForPath(path); return item ? item->project()->path() : Path(); } } ProjectItemDataProvider::ProjectItemDataProvider( KDevelop::IQuickOpen* quickopen ) : m_quickopen(quickopen) { } void ProjectItemDataProvider::setFilterText( const QString& text ) { m_addedItems.clear(); QStringList search(text.split(QStringLiteral("::"), QString::SkipEmptyParts)); for(int a = 0; a < search.count(); ++a) { if(search[a].endsWith(':')) { //Don't get confused while the :: is being typed search[a] = search[a].left(search[a].length()-1); } } if(!search.isEmpty() && search.back().endsWith('(')) { search.back().chop(1); } if(text.isEmpty() || search.isEmpty()) { m_filteredItems = m_currentItems; return; } KDevVarLengthArray cache; foreach(const QString& searchPart, search) { cache.append(SubstringCache(searchPart)); } if(!text.startsWith(m_currentFilter)) { m_filteredItems = m_currentItems; } m_currentFilter = text; QVector oldFiltered = m_filteredItems; QHash heights; m_filteredItems.clear(); foreach(const CodeModelViewItem& item, oldFiltered) { const QualifiedIdentifier& currentId = item.m_id; int last_pos = currentId.count() - 1; int current_height = 0; int distance = 0; //iter over each search item from last to first //this makes easier to calculate the distance based on where we hit the result or nothing //Iterating from the last item to the first is more efficient, as we want to match the //class/function name, which is the last item on the search fields and on the identifier. for(int b = search.count() - 1; b >= 0; --b) { //iter over each id for the current identifier, from last to first for(; last_pos >= 0; --last_pos, distance++) { // the more distant we are from the class definition, the less priority it will have current_height += distance * 10000; int result; //if the current search item is contained on the current identifier if((result = cache[b].containedIn( currentId.at(last_pos) )) >= 0) { //when we find a hit, whe add the distance to the searched word. //so the closest item will be displayed first current_height += result; if(b == 0) { heights[currentId.index()] = current_height; m_filteredItems << item; } break; } } } } //then, for the last part, we use the already built cache to sort the items according with their distance std::sort(m_filteredItems.begin(), m_filteredItems.end(), ClosestMatchToText(heights)); } QList ProjectItemDataProvider::data( uint start, uint end ) const { QList ret; for(uint a = start; a < end; ++a) { ret << data(a); } return ret; } KDevelop::QuickOpenDataPointer ProjectItemDataProvider::data( uint pos ) const { //Check whether this position falls into an appended item-list, else apply the offset uint filteredItemOffset = 0; for(AddedItems::const_iterator it = m_addedItems.constBegin(); it != m_addedItems.constEnd(); ++it) { int offsetInAppended = pos - (it.key()+1); if(offsetInAppended >= 0 && offsetInAppended < it.value().count()) { return it.value()[offsetInAppended]; } if(it.key() >= pos) { break; } filteredItemOffset += it.value().count(); } const uint a = pos - filteredItemOffset; if(a > (uint)m_filteredItems.size()) { return KDevelop::QuickOpenDataPointer(); } const auto& filteredItem = m_filteredItems[a]; QList ret; KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* ctx = DUChainUtils::standardContextForUrl(filteredItem.m_file.toUrl()); if(ctx) { - QList decls = ctx->findDeclarations(filteredItem.m_id, CursorInRevision::invalid(), AbstractType::Ptr(), 0, DUContext::DirectQualifiedLookup); + QList decls = ctx->findDeclarations(filteredItem.m_id, CursorInRevision::invalid(), AbstractType::Ptr(), nullptr, DUContext::DirectQualifiedLookup); //Filter out forward-declarations or duplicate imported declarations foreach(Declaration* decl, decls) { bool filter = false; if (decls.size() > 1 && decl->isForwardDeclaration()) { filter = true; } else if (decl->url() != filteredItem.m_file && m_files.contains(decl->url())) { filter = true; } if (filter) { decls.removeOne(decl); } } foreach(Declaration* decl, decls) { DUChainItem item; item.m_item = decl; item.m_text = decl->qualifiedIdentifier().toString(); item.m_projectPath = findProjectForForPath(filteredItem.m_file); ret << QuickOpenDataPointer(new DUChainItemData(item)); } if(decls.isEmpty()) { DUChainItem item; item.m_text = filteredItem.m_id.toString(); item.m_projectPath = findProjectForForPath(filteredItem.m_file); ret << QuickOpenDataPointer(new DUChainItemData(item)); } } else { qCDebug(PLUGIN_QUICKOPEN) << "Could not find standard-context"; } if(!ret.isEmpty()) { QList append = ret.mid(1); if(!append.isEmpty()) { AddedItems addMap; for(AddedItems::iterator it = m_addedItems.begin(); it != m_addedItems.end();) { if(it.key() == pos) { //There already is appended data stored, nothing to do return ret.first(); } else if(it.key() > pos) { addMap[it.key() + append.count()] = it.value(); it = m_addedItems.erase(it); } else { ++it; } } m_addedItems.insert(pos, append); for(AddedItems::const_iterator it = addMap.constBegin(); it != addMap.constEnd(); ++it) { m_addedItems.insert(it.key(), it.value()); } } return ret.first(); } else { return KDevelop::QuickOpenDataPointer(); } } void ProjectItemDataProvider::reset() { m_files = m_quickopen->fileSet(); m_currentItems.clear(); m_addedItems.clear(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); foreach( const IndexedString& u, m_files ) { uint count; const KDevelop::CodeModelItem* items; CodeModel::self().items( u, count, items ); for(uint a = 0; a < count; ++a) { if(!items[a].id.isValid() || items[a].kind & CodeModelItem::ForwardDeclaration) { continue; } if(((m_itemTypes & Classes) && (items[a].kind & CodeModelItem::Class)) || ((m_itemTypes & Functions) && (items[a].kind & CodeModelItem::Function))) { QualifiedIdentifier id = items[a].id.identifier(); if (id.isEmpty() || id.at(0).identifier().isEmpty()) { // id.isEmpty() not always hit when .toString() is actually empty... // anyhow, this makes sure that we don't show duchain items without // any name that could be searched for. This happens e.g. in the c++ // plugin for anonymous structs or sometimes for declarations in macro // expressions continue; } m_currentItems << CodeModelViewItem(u, id); } } } m_filteredItems = m_currentItems; m_currentFilter.clear(); } uint addedItems(const AddedItems& items) { uint add = 0; for(AddedItems::const_iterator it = items.constBegin(); it != items.constEnd(); ++it) { add += it.value().count(); } return add; } uint ProjectItemDataProvider::itemCount() const { return m_filteredItems.count() + addedItems(m_addedItems); } uint ProjectItemDataProvider::unfilteredItemCount() const { return m_currentItems.count() + addedItems(m_addedItems); } QStringList ProjectItemDataProvider::supportedItemTypes() { QStringList ret; ret << i18n("Classes"); ret << i18n("Functions"); return ret; } void ProjectItemDataProvider::enableData( const QStringList& items, const QStringList& scopes ) { //FIXME: property support different scopes if(scopes.contains(i18n("Project"))) { m_itemTypes = NoItems; if(items.contains(i18n("Classes"))) { m_itemTypes = (ItemTypes)(m_itemTypes | Classes); } if( items.contains(i18n("Functions"))) { m_itemTypes = (ItemTypes)(m_itemTypes | Functions); } } else { m_itemTypes = NoItems; } } diff --git a/plugins/quickopen/quickopenmodel.cpp b/plugins/quickopen/quickopenmodel.cpp index 0c46d3da47..8d5a17db56 100644 --- a/plugins/quickopen/quickopenmodel.cpp +++ b/plugins/quickopen/quickopenmodel.cpp @@ -1,450 +1,450 @@ /* This file is part of the KDE libraries Copyright (C) 2007 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 "quickopenmodel.h" #include "debug.h" #include #include #include #include #include #include "expandingtree/expandingtree.h" #include "projectfilequickopen.h" #include "duchainitemquickopen.h" #define QUICKOPEN_USE_ITEM_CACHING using namespace KDevelop; -QuickOpenModel::QuickOpenModel( QWidget* parent ) : ExpandingWidgetModel( parent ), m_treeView(0), m_expandingWidgetHeightIncrease(0), m_resetBehindRow(0) +QuickOpenModel::QuickOpenModel( QWidget* parent ) : ExpandingWidgetModel( parent ), m_treeView(nullptr), m_expandingWidgetHeightIncrease(0), m_resetBehindRow(0) { m_resetTimer = new QTimer(this); m_resetTimer->setSingleShot(true); connect(m_resetTimer, &QTimer::timeout, this, &QuickOpenModel::resetTimer); } void QuickOpenModel::setExpandingWidgetHeightIncrease(int pixels) { m_expandingWidgetHeightIncrease = pixels; } QStringList QuickOpenModel::allScopes() const { QStringList scopes; foreach( const ProviderEntry& provider, m_providers ) foreach( const QString& scope, provider.scopes ) if( !scopes.contains( scope ) ) scopes << scope; return scopes; } QStringList QuickOpenModel::allTypes() const { QSet types; foreach( const ProviderEntry& provider, m_providers ) types += provider.types; return types.toList(); } void QuickOpenModel::registerProvider( const QStringList& scopes, const QStringList& types, KDevelop::QuickOpenDataProviderBase* provider ) { ProviderEntry e; e.scopes = QSet::fromList(scopes); e.types = QSet::fromList(types); e.provider = provider; m_providers << e; //.insert( types, e ); connect( provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed ); restart(true); } bool QuickOpenModel::removeProvider( KDevelop::QuickOpenDataProviderBase* provider ) { bool ret = false; for( ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it ) { if( (*it).provider == provider ) { m_providers.erase( it ); disconnect( provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed ); ret = true; break; } } restart(true); return ret; } void QuickOpenModel::enableProviders( const QStringList& _items, const QStringList& _scopes ) { QSet items = QSet::fromList( _items ); QSet scopes = QSet::fromList( _scopes ); if (m_enabledItems == items && m_enabledScopes == scopes && !items.isEmpty() && !scopes.isEmpty()) { return; } m_enabledItems = items; m_enabledScopes = scopes; qCDebug(PLUGIN_QUICKOPEN) << "params " << items << " " << scopes; //We use 2 iterations here: In the first iteration, all providers that implement QuickOpenFileSetInterface are initialized, then the other ones. //The reason is that the second group can refer to the first one. for( ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it ) { if( !dynamic_cast((*it).provider) ) continue; qCDebug(PLUGIN_QUICKOPEN) << "comparing" << (*it).scopes << (*it).types; if( ( scopes.isEmpty() || !( scopes & (*it).scopes ).isEmpty() ) && ( !( items & (*it).types ).isEmpty() || items.isEmpty() ) ) { qCDebug(PLUGIN_QUICKOPEN) << "enabling " << (*it).types << " " << (*it).scopes; (*it).enabled = true; (*it).provider->enableData( _items, _scopes ); } else { qCDebug(PLUGIN_QUICKOPEN) << "disabling " << (*it).types << " " << (*it).scopes; (*it).enabled = false; if( ( scopes.isEmpty() || !( scopes & (*it).scopes ).isEmpty() ) ) (*it).provider->enableData( _items, _scopes ); //The provider may still provide files } } for( ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it ) { if( dynamic_cast((*it).provider) ) continue; qCDebug(PLUGIN_QUICKOPEN) << "comparing" << (*it).scopes << (*it).types; if( ( scopes.isEmpty() || !( scopes & (*it).scopes ).isEmpty() ) && ( !( items & (*it).types ).isEmpty() || items.isEmpty() ) ) { qCDebug(PLUGIN_QUICKOPEN) << "enabling " << (*it).types << " " << (*it).scopes; (*it).enabled = true; (*it).provider->enableData( _items, _scopes ); } else { qCDebug(PLUGIN_QUICKOPEN) << "disabling " << (*it).types << " " << (*it).scopes; (*it).enabled = false; } } restart(true); } void QuickOpenModel::textChanged( const QString& str ) { if( m_filterText == str ) return; beginResetModel(); m_filterText = str; foreach( const ProviderEntry& provider, m_providers ) if( provider.enabled ) provider.provider->setFilterText( str ); m_cachedData.clear(); clearExpanding(); //Get the 50 first items, so the data-providers notice changes without ui-glitches due to resetting for(int a = 0; a < 50 && a < rowCount(QModelIndex()) ; ++a) getItem(a, true); endResetModel(); } void QuickOpenModel::restart(bool keepFilterText) { // make sure we do not restart recursivly which could lead to // recursive loading of provider plugins e.g. (happened for the cpp plugin) QMetaObject::invokeMethod(this, "restart_internal", Qt::QueuedConnection, Q_ARG(bool, keepFilterText)); } void QuickOpenModel::restart_internal(bool keepFilterText) { if(!keepFilterText) m_filterText.clear(); bool anyEnabled = false; foreach( const ProviderEntry& e, m_providers ) anyEnabled |= e.enabled; if( !anyEnabled ) return; foreach( const ProviderEntry& provider, m_providers ) { if( !dynamic_cast(provider.provider) ) continue; ///Always reset providers that implement QuickOpenFileSetInterface and have some matchign scopes, because they may be needed by other providers. if( m_enabledScopes.isEmpty() || !( m_enabledScopes & provider.scopes ).isEmpty() ) provider.provider->reset(); } foreach( const ProviderEntry& provider, m_providers ) { if( dynamic_cast(provider.provider) ) continue; if( provider.enabled && provider.provider ) provider.provider->reset(); } if(keepFilterText) { textChanged(m_filterText); }else{ beginResetModel(); m_cachedData.clear(); clearExpanding(); endResetModel(); } } void QuickOpenModel::destroyed( QObject* obj ) { removeProvider( static_cast(obj) ); } QModelIndex QuickOpenModel::index( int row, int column, const QModelIndex& /*parent*/) const { if( column >= columnCount() || row >= rowCount(QModelIndex()) ) return QModelIndex(); if (row < 0 || column < 0) return QModelIndex(); return createIndex( row, column ); } QModelIndex QuickOpenModel::parent( const QModelIndex& ) const { return QModelIndex(); } int QuickOpenModel::rowCount( const QModelIndex& i ) const { if( i.isValid() ) return 0; int count = 0; foreach( const ProviderEntry& provider, m_providers ) if( provider.enabled ) count += provider.provider->itemCount(); return count; } int QuickOpenModel::unfilteredRowCount() const { int count = 0; foreach( const ProviderEntry& provider, m_providers ) if( provider.enabled ) count += provider.provider->unfilteredItemCount(); return count; } int QuickOpenModel::columnCount() const { return 2; } int QuickOpenModel::columnCount( const QModelIndex& index ) const { if( index.parent().isValid() ) return 0; else { return columnCount(); } } QVariant QuickOpenModel::data( const QModelIndex& index, int role ) const { QuickOpenDataPointer d = getItem( index.row() ); if( !d ) return QVariant(); switch( role ) { case KTextEditor::CodeCompletionModel::ItemSelected: { QString desc = d->htmlDescription(); if(desc.isEmpty()) return QVariant(); else return desc; } case KTextEditor::CodeCompletionModel::IsExpandable: return d->isExpandable(); case KTextEditor::CodeCompletionModel::ExpandingWidget: { QVariant v; QWidget* w = d->expandingWidget(); if(w && m_expandingWidgetHeightIncrease) w->resize(w->width(), w->height() + m_expandingWidgetHeightIncrease); v.setValue(w); return v; } case ExpandingTree::ProjectPathRole: // TODO: put this into the QuickOpenDataBase API // we cannot do this in 5.0, cannot change ABI if (auto projectFile = dynamic_cast(d.constData())) { return QVariant::fromValue(projectFile->projectPath()); } else if (auto duchainItem = dynamic_cast(d.constData())) { return QVariant::fromValue(duchainItem->projectPath()); } } if( index.column() == 1 ) { //This column contains the actual content switch( role ) { case Qt::DecorationRole: return d->icon(); case Qt::DisplayRole: return d->text(); case KTextEditor::CodeCompletionModel::HighlightingMethod: return KTextEditor::CodeCompletionModel::CustomHighlighting; case KTextEditor::CodeCompletionModel::CustomHighlight: return d->highlighting(); } } else if( index.column() == 0 ) { //This column only contains the expanded/not expanded icon switch( role ) { case Qt::DecorationRole: { if( isExpandable(index) ) { //Show the expanded/unexpanded handles cacheIcons(); if( isExpanded(index) ) { return m_expandedIcon; } else { return m_collapsedIcon; } } } } } return ExpandingWidgetModel::data( index, role ); } void QuickOpenModel::resetTimer() { int currentRow = treeView() ? treeView()->currentIndex().row() : -1; beginResetModel(); //Remove all cached data behind row m_resetBehindRow for(DataList::iterator it = m_cachedData.begin(); it != m_cachedData.end(); ) { if(it.key() > m_resetBehindRow) it = m_cachedData.erase(it); else ++it; } endResetModel(); if (currentRow != -1) { treeView()->setCurrentIndex(index(currentRow, 0, QModelIndex())); //Preserve the current index } m_resetBehindRow = 0; } QuickOpenDataPointer QuickOpenModel::getItem( int row, bool noReset ) const { ///@todo mix all the models alphabetically here. For now, they are simply ordered. ///@todo Deal with unexpected item-counts, like for example in the case of overloaded function-declarations #ifdef QUICKOPEN_USE_ITEM_CACHING if( m_cachedData.contains( row ) ) return m_cachedData[row]; #endif int rowOffset = 0; Q_ASSERT(row < rowCount(QModelIndex())); foreach( const ProviderEntry& provider, m_providers ) { if( !provider.enabled ) continue; uint itemCount = provider.provider->itemCount(); if( (uint)row < itemCount ) { QuickOpenDataPointer item = provider.provider->data( row ); if(!noReset && provider.provider->itemCount() != itemCount) { qCDebug(PLUGIN_QUICKOPEN) << "item-count in provider has changed, resetting model"; m_resetTimer->start(0); m_resetBehindRow = rowOffset + row; //Don't reset everything, only everything behind this position } #ifdef QUICKOPEN_USE_ITEM_CACHING m_cachedData[row+rowOffset] = item; #endif return item; } else { row -= provider.provider->itemCount(); rowOffset += provider.provider->itemCount(); } } // qWarning() << "No item for row " << row; return QuickOpenDataPointer(); } QSet QuickOpenModel::fileSet() const { QSet merged; foreach( const ProviderEntry& provider, m_providers ) { if( m_enabledScopes.isEmpty() || !( m_enabledScopes & provider.scopes ).isEmpty() ) { if( QuickOpenFileSetInterface* iface = dynamic_cast(provider.provider) ) { QSet ifiles = iface->files(); //qCDebug(PLUGIN_QUICKOPEN) << "got file-list with" << ifiles.count() << "entries from data-provider" << typeid(*iface).name(); merged += ifiles; } } } return merged; } QTreeView* QuickOpenModel::treeView() const { return m_treeView; } bool QuickOpenModel::indexIsItem(const QModelIndex& /*index*/) const { return true; } void QuickOpenModel::setTreeView( QTreeView* view ) { m_treeView = view; } int QuickOpenModel::contextMatchQuality(const QModelIndex & /*index*/) const { return -1; } bool QuickOpenModel::execute( const QModelIndex& index, QString& filterText ) { qCDebug(PLUGIN_QUICKOPEN) << "executing model"; if( !index.isValid() ) { qWarning() << "Invalid index executed"; return false; } QuickOpenDataPointer item = getItem( index.row() ); if( item ) { return item->execute( filterText ); }else{ qWarning() << "Got no item for row " << index.row() << " "; } return false; } diff --git a/plugins/quickopen/quickopenplugin.cpp b/plugins/quickopen/quickopenplugin.cpp index 9e45003f61..ed7e4f2354 100644 --- a/plugins/quickopen/quickopenplugin.cpp +++ b/plugins/quickopen/quickopenplugin.cpp @@ -1,1108 +1,1108 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopenplugin.h" #include "quickopenwidget.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 #include #include #include #include #include #include #include #include #include "quickopenmodel.h" #include "projectfilequickopen.h" #include "projectitemquickopen.h" #include "declarationlistquickopen.h" #include "documentationquickopenprovider.h" #include "actionsquickopenprovider.h" #include "debug.h" #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_QUICKOPEN, "kdevplatform.plugins.quickopen") using namespace KDevelop; const bool noHtmlDestriptionInOutline = true; class QuickOpenWidgetCreator { public: virtual ~QuickOpenWidgetCreator() { } virtual QuickOpenWidget* createWidget() = 0; virtual QString objectNameForLine() = 0; virtual void widgetShown() { } }; class StandardQuickOpenWidgetCreator : public QuickOpenWidgetCreator { public: StandardQuickOpenWidgetCreator(const QStringList& items, const QStringList& scopes) : m_items(items) , m_scopes(scopes) { } QString objectNameForLine() override { return QStringLiteral("Quickopen"); } void setItems(const QStringList& scopes, const QStringList& items) { m_scopes = scopes; m_items = items; } QuickOpenWidget* createWidget() override { QStringList useItems = m_items; if(useItems.isEmpty()) useItems = QuickOpenPlugin::self()->lastUsedItems; QStringList useScopes = m_scopes; if(useScopes.isEmpty()) useScopes = QuickOpenPlugin::self()->lastUsedScopes; return new QuickOpenWidget( i18n("Quick Open"), QuickOpenPlugin::self()->m_model, QuickOpenPlugin::self()->lastUsedItems, useScopes, false, true ); } QStringList m_items; QStringList m_scopes; }; class OutlineFilter : public DUChainUtils::DUChainItemFilter { public: enum OutlineMode { Functions, FunctionsAndClasses }; OutlineFilter(QList& _items, OutlineMode _mode = FunctionsAndClasses) : items(_items), mode(_mode) { } bool accept(Declaration* decl) override { if(decl->range().isEmpty()) return false; bool collectable = mode == Functions ? decl->isFunctionDeclaration() : (decl->isFunctionDeclaration() || (decl->internalContext() && decl->internalContext()->type() == DUContext::Class)); if (collectable) { DUChainItem item; item.m_item = IndexedDeclaration(decl); item.m_text = decl->toString(); items << item; return true; } else return false; } bool accept(DUContext* ctx) override { if(ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper ) return true; else return false; } QList& items; OutlineMode mode; }; K_PLUGIN_FACTORY_WITH_JSON(KDevQuickOpenFactory, "kdevquickopen.json", registerPlugin();) Declaration* cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if(!view) - return 0; + return nullptr; KDevelop::DUChainReadLocker lock( DUChain::lock() ); return DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor( view->document()->url(), KTextEditor::Cursor(view->cursorPosition()) ).declaration ); } ///The first definition that belongs to a context that surrounds the current cursor Declaration* cursorContextDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if(!view) - return 0; + return nullptr; KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url()); if(!ctx) - return 0; + return nullptr; KTextEditor::Cursor cursor(view->cursorPosition()); DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor)); while(subCtx && !subCtx->owner()) subCtx = subCtx->parentContext(); - Declaration* definition = 0; + Declaration* definition = nullptr; if(!subCtx || !subCtx->owner()) definition = DUChainUtils::declarationInLine(cursor, ctx); else definition = subCtx->owner(); if(!definition) - return 0; + return nullptr; return definition; } //Returns only the name, no template-parameters or scope QString cursorItemText() { KDevelop::DUChainReadLocker lock( DUChain::lock() ); Declaration* decl = cursorDeclaration(); if(!decl) return QString(); IDocument* doc = ICore::self()->documentController()->activeDocument(); if(!doc) return QString(); TopDUContext* context = DUChainUtils::standardContextForUrl( doc->url() ); if( !context ) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return QString(); } AbstractType::Ptr t = decl->abstractType(); IdentifiedType* idType = dynamic_cast(t.data()); if( idType && idType->declaration(context) ) decl = idType->declaration(context); if(!decl->qualifiedIdentifier().isEmpty()) return decl->qualifiedIdentifier().last().identifier().str(); return QString(); } QuickOpenLineEdit* QuickOpenPlugin::createQuickOpenLineWidget() { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(QStringList(), QStringList())); } QuickOpenLineEdit* QuickOpenPlugin::quickOpenLine(QString name) { QList< QuickOpenLineEdit* > lines = ICore::self()->uiController()->activeMainWindow()->findChildren(name); foreach(QuickOpenLineEdit* line, lines) { if(line->isVisible()) { return line; } } - return 0; + return nullptr; } -static QuickOpenPlugin* staticQuickOpenPlugin = 0; +static QuickOpenPlugin* staticQuickOpenPlugin = nullptr; QuickOpenPlugin* QuickOpenPlugin::self() { return staticQuickOpenPlugin; } void QuickOpenPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevquickopen.rc"); QAction* quickOpen = actions.addAction(QStringLiteral("quick_open")); quickOpen->setText( i18n("&Quick Open") ); quickOpen->setIcon( QIcon::fromTheme(QStringLiteral("quickopen")) ); actions.setDefaultShortcut( quickOpen, Qt::CTRL | Qt::ALT | Qt::Key_Q ); connect(quickOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpen); QAction* quickOpenFile = actions.addAction(QStringLiteral("quick_open_file")); quickOpenFile->setText( i18n("Quick Open &File") ); quickOpenFile->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-file")) ); actions.setDefaultShortcut( quickOpenFile, Qt::CTRL | Qt::ALT | Qt::Key_O ); connect(quickOpenFile, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFile); QAction* quickOpenClass = actions.addAction(QStringLiteral("quick_open_class")); quickOpenClass->setText( i18n("Quick Open &Class") ); quickOpenClass->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-class")) ); actions.setDefaultShortcut( quickOpenClass, Qt::CTRL | Qt::ALT | Qt::Key_C ); connect(quickOpenClass, &QAction::triggered, this, &QuickOpenPlugin::quickOpenClass); QAction* quickOpenFunction = actions.addAction(QStringLiteral("quick_open_function")); quickOpenFunction->setText( i18n("Quick Open &Function") ); quickOpenFunction->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-function")) ); actions.setDefaultShortcut( quickOpenFunction, Qt::CTRL | Qt::ALT | Qt::Key_M ); connect(quickOpenFunction, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFunction); QAction* quickOpenAlreadyOpen = actions.addAction(QStringLiteral("quick_open_already_open")); quickOpenAlreadyOpen->setText( i18n("Quick Open &Already Open File") ); quickOpenAlreadyOpen->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-file")) ); connect(quickOpenAlreadyOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpenOpenFile); QAction* quickOpenDocumentation = actions.addAction(QStringLiteral("quick_open_documentation")); quickOpenDocumentation->setText( i18n("Quick Open &Documentation") ); quickOpenDocumentation->setIcon( QIcon::fromTheme(QStringLiteral("quickopen-documentation")) ); actions.setDefaultShortcut( quickOpenDocumentation, Qt::CTRL | Qt::ALT | Qt::Key_D ); connect(quickOpenDocumentation, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDocumentation); QAction* quickOpenActions = actions.addAction(QStringLiteral("quick_open_actions")); quickOpenActions->setText( i18n("Quick Open &Actions") ); actions.setDefaultShortcut( quickOpenActions, Qt::CTRL | Qt::ALT | Qt::Key_A); connect(quickOpenActions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenActions); m_quickOpenDeclaration = actions.addAction(QStringLiteral("quick_open_jump_declaration")); m_quickOpenDeclaration->setText( i18n("Jump to Declaration") ); m_quickOpenDeclaration->setIcon( QIcon::fromTheme(QStringLiteral("go-jump-declaration") ) ); actions.setDefaultShortcut( m_quickOpenDeclaration, Qt::CTRL | Qt::Key_Period ); connect(m_quickOpenDeclaration, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDeclaration, Qt::QueuedConnection); m_quickOpenDefinition = actions.addAction(QStringLiteral("quick_open_jump_definition")); m_quickOpenDefinition->setText( i18n("Jump to Definition") ); m_quickOpenDefinition->setIcon( QIcon::fromTheme(QStringLiteral("go-jump-definition") ) ); actions.setDefaultShortcut( m_quickOpenDefinition, Qt::CTRL | Qt::Key_Comma ); connect(m_quickOpenDefinition, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDefinition, Qt::QueuedConnection); QWidgetAction* quickOpenLine = new QWidgetAction(this); quickOpenLine->setText( i18n("Embedded Quick Open") ); // actions.setDefaultShortcut( quickOpenLine, Qt::CTRL | Qt::ALT | Qt::Key_E ); // connect(quickOpenLine, SIGNAL(triggered(bool)), this, SLOT(quickOpenLine(bool))); quickOpenLine->setDefaultWidget(createQuickOpenLineWidget()); actions.addAction(QStringLiteral("quick_open_line"), quickOpenLine); QAction* quickOpenNextFunction = actions.addAction(QStringLiteral("quick_open_next_function")); quickOpenNextFunction->setText( i18n("Next Function") ); actions.setDefaultShortcut( quickOpenNextFunction, Qt::CTRL| Qt::ALT | Qt::Key_PageDown ); connect(quickOpenNextFunction, &QAction::triggered, this, &QuickOpenPlugin::nextFunction); QAction* quickOpenPrevFunction = actions.addAction(QStringLiteral("quick_open_prev_function")); quickOpenPrevFunction->setText( i18n("Previous Function") ); actions.setDefaultShortcut( quickOpenPrevFunction, Qt::CTRL| Qt::ALT | Qt::Key_PageUp ); connect(quickOpenPrevFunction, &QAction::triggered, this, &QuickOpenPlugin::previousFunction); QAction* quickOpenNavigateFunctions = actions.addAction(QStringLiteral("quick_open_outline")); quickOpenNavigateFunctions->setText( i18n("Outline") ); actions.setDefaultShortcut( quickOpenNavigateFunctions, Qt::CTRL| Qt::ALT | Qt::Key_N ); connect(quickOpenNavigateFunctions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenNavigateFunctions); } QuickOpenPlugin::QuickOpenPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevquickopen"), parent) { staticQuickOpenPlugin = this; KDEV_USE_EXTENSION_INTERFACE( KDevelop::IQuickOpen ) - m_model = new QuickOpenModel( 0 ); + m_model = new QuickOpenModel( nullptr ); KConfigGroup quickopengrp = KSharedConfig::openConfig()->group("QuickOpen"); lastUsedScopes = quickopengrp.readEntry("SelectedScopes", QStringList() << i18n("Project") << i18n("Includes") << i18n("Includers") << i18n("Currently Open") ); lastUsedItems = quickopengrp.readEntry("SelectedItems", QStringList() ); { m_openFilesData = new OpenFilesDataProvider(); QStringList scopes, items; scopes << i18n("Currently Open"); items << i18n("Files"); m_model->registerProvider( scopes, items, m_openFilesData ); } { m_projectFileData = new ProjectFileDataProvider(); QStringList scopes, items; scopes << i18n("Project"); items << i18n("Files"); m_model->registerProvider( scopes, items, m_projectFileData ); } { m_projectItemData = new ProjectItemDataProvider(this); QStringList scopes, items; scopes << i18n("Project"); items << ProjectItemDataProvider::supportedItemTypes(); m_model->registerProvider( scopes, items, m_projectItemData ); } { m_documentationItemData = new DocumentationQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Documentation"); m_model->registerProvider( scopes, items, m_documentationItemData ); } { m_actionsItemData = new ActionsQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Actions"); m_model->registerProvider( scopes, items, m_actionsItemData ); } } QuickOpenPlugin::~QuickOpenPlugin() { freeModel(); delete m_model; delete m_projectFileData; delete m_projectItemData; delete m_openFilesData; delete m_documentationItemData; delete m_actionsItemData; } void QuickOpenPlugin::unload() { } ContextMenuExtension QuickOpenPlugin::contextMenuExtension(Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker readLock; Declaration* decl(codeContext->declaration().data()); if (decl) { const bool isDef = FunctionDefinition::definition(decl); if (codeContext->use().isValid() || !isDef) { menuExt.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, m_quickOpenDeclaration); } if(isDef) { menuExt.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, m_quickOpenDefinition); } } return menuExt; } void QuickOpenPlugin::showQuickOpen(const QStringList& items) { if(!freeModel()) return; QStringList initialItems = items; QStringList useScopes = lastUsedScopes; if (!useScopes.contains(i18n("Currently Open"))) useScopes << i18n("Currently Open"); showQuickOpenWidget(initialItems, useScopes, false); } void QuickOpenPlugin::showQuickOpen( ModelTypes modes ) { if(!freeModel()) return; QStringList initialItems; if( modes & Files || modes & OpenFiles ) initialItems << i18n("Files"); if( modes & Functions ) initialItems << i18n("Functions"); if( modes & Classes ) initialItems << i18n("Classes"); QStringList useScopes; if ( modes != OpenFiles ) useScopes = lastUsedScopes; if((modes & OpenFiles) && !useScopes.contains(i18n("Currently Open"))) useScopes << i18n("Currently Open"); bool preselectText = (!(modes & Files) || modes == QuickOpenPlugin::All); showQuickOpenWidget(initialItems, useScopes, preselectText); } void QuickOpenPlugin::showQuickOpenWidget(const QStringList& items, const QStringList& scopes, bool preselectText) { QuickOpenWidgetDialog* dialog = new QuickOpenWidgetDialog( i18n("Quick Open"), m_model, items, scopes ); m_currentWidgetHandler = dialog; if (preselectText) { KDevelop::IDocument *currentDoc = core()->documentController()->activeDocument(); if (currentDoc && currentDoc->isTextDocument()) { QString preselected = currentDoc->textSelection().isEmpty() ? currentDoc->textWord() : currentDoc->textDocument()->text(currentDoc->textSelection()); dialog->widget()->setPreselectedText(preselected); } } connect( dialog->widget(), &QuickOpenWidget::scopesChanged, this, &QuickOpenPlugin::storeScopes ); //Not connecting itemsChanged to storeItems, as showQuickOpen doesn't use lastUsedItems and so shouldn't store item changes //connect( dialog->widget(), SIGNAL(itemsChanged(QStringList)), this, SLOT(storeItems(QStringList)) ); dialog->widget()->ui.itemsButton->setEnabled(false); if(quickOpenLine()) { quickOpenLine()->showWithWidget(dialog->widget()); dialog->deleteLater(); }else{ dialog->run(); } } void QuickOpenPlugin::storeScopes( const QStringList& scopes ) { lastUsedScopes = scopes; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry( "SelectedScopes", scopes ); } void QuickOpenPlugin::storeItems( const QStringList& items ) { lastUsedItems = items; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry( "SelectedItems", items ); } void QuickOpenPlugin::quickOpen() { if(quickOpenLine()) //Same as clicking on Quick Open quickOpenLine()->setFocus(); else showQuickOpen( All ); } void QuickOpenPlugin::quickOpenFile() { showQuickOpen( (ModelTypes)(Files | OpenFiles) ); } void QuickOpenPlugin::quickOpenFunction() { showQuickOpen( Functions ); } void QuickOpenPlugin::quickOpenClass() { showQuickOpen( Classes ); } void QuickOpenPlugin::quickOpenOpenFile() { showQuickOpen( OpenFiles ); } void QuickOpenPlugin::quickOpenDocumentation() { showQuickOpenWidget(QStringList(i18n("Documentation")), QStringList(i18n("Includes")), true); } void QuickOpenPlugin::quickOpenActions() { showQuickOpenWidget(QStringList(i18n("Actions")), QStringList(i18n("Includes")), true); } QSet QuickOpenPlugin::fileSet() const { return m_model->fileSet(); } void QuickOpenPlugin::registerProvider( const QStringList& scope, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider ) { m_model->registerProvider( scope, type, provider ); } bool QuickOpenPlugin::removeProvider( KDevelop::QuickOpenDataProviderBase* provider ) { m_model->removeProvider( provider ); return true; } void QuickOpenPlugin::quickOpenDeclaration() { if(jumpToSpecialObject()) return; KDevelop::DUChainReadLocker lock( DUChain::lock() ); Declaration* decl = cursorDeclaration(); if(!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } decl->activateSpecialization(); IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if(u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } QWidget* QuickOpenPlugin::specialObjectNavigationWidget() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if( !view ) - return 0; + return nullptr; QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); foreach (const auto language, languages) { QWidget* w = language->specialLanguageObjectNavigationWidget(url, KTextEditor::Cursor(view->cursorPosition()) ); if(w) return w; } - return 0; + return nullptr; } QPair QuickOpenPlugin::specialObjectJumpPosition() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if( !view ) return qMakePair(QUrl(), KTextEditor::Cursor()); QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); foreach (const auto language, languages) { QPair pos = language->specialLanguageObjectJumpCursor(url, KTextEditor::Cursor(view->cursorPosition()) ); if(pos.second.isValid()) { return pos; } } return qMakePair(QUrl(), KTextEditor::Cursor::invalid()); } bool QuickOpenPlugin::jumpToSpecialObject() { QPair pos = specialObjectJumpPosition(); if(pos.second.isValid()) { if(pos.first.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for special language object"; return false; } ICore::self()->documentController()->openDocument(pos.first, pos.second); return true; } return false; } void QuickOpenPlugin::quickOpenDefinition() { if(jumpToSpecialObject()) return; KDevelop::DUChainReadLocker lock( DUChain::lock() ); Declaration* decl = cursorDeclaration(); if(!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if(FunctionDefinition* def = FunctionDefinition::definition(decl)) { def->activateSpecialization(); u = def->url(); c = def->rangeInCurrentRevision().start(); }else{ qCDebug(PLUGIN_QUICKOPEN) << "Found no definition for declaration"; decl->activateSpecialization(); } if(u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } bool QuickOpenPlugin::freeModel() { if(m_currentWidgetHandler) delete m_currentWidgetHandler; - m_currentWidgetHandler = 0; + m_currentWidgetHandler = nullptr; return true; } void QuickOpenPlugin::nextFunction() { jumpToNearestFunction(NextFunction); } void QuickOpenPlugin::previousFunction() { jumpToNearestFunction(PreviousFunction); } void QuickOpenPlugin::jumpToNearestFunction(QuickOpenPlugin::FunctionJumpDirection direction) { IDocument* doc = ICore::self()->documentController()->activeDocument(); if(!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* context = DUChainUtils::standardContextForUrl( doc->url() ); if( !context ) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } QList items; OutlineFilter filter(items, OutlineFilter::Functions); DUChainUtils::collectItems( context, filter ); CursorInRevision cursor = context->transformToLocalRevision(KTextEditor::Cursor(doc->cursorPosition())); if (!cursor.isValid()) return; - Declaration *nearestDeclBefore = 0; + Declaration *nearestDeclBefore = nullptr; int distanceBefore = INT_MIN; - Declaration *nearestDeclAfter = 0; + Declaration *nearestDeclAfter = nullptr; int distanceAfter = INT_MAX; for (int i = 0; i < items.count(); ++i) { Declaration *decl = items[i].m_item.data(); int distance = decl->range().start.line - cursor.line; if (distance < 0 && distance >= distanceBefore) { distanceBefore = distance; nearestDeclBefore = decl; } else if (distance > 0 && distance <= distanceAfter) { distanceAfter = distance; nearestDeclAfter = decl; } } CursorInRevision c = CursorInRevision::invalid(); if (direction == QuickOpenPlugin::NextFunction && nearestDeclAfter) c = nearestDeclAfter->range().start; else if (direction == QuickOpenPlugin::PreviousFunction && nearestDeclBefore) c = nearestDeclBefore->range().start; KTextEditor::Cursor textCursor = KTextEditor::Cursor::invalid(); if (c.isValid()) textCursor = context->transformFromLocalRevision(c); lock.unlock(); if (textCursor.isValid()) core()->documentController()->openDocument(doc->url(), textCursor); else qCDebug(PLUGIN_QUICKOPEN) << "No declaration to jump to"; } struct CreateOutlineDialog { - CreateOutlineDialog() : dialog(0), cursorDecl(0), model(0) { + CreateOutlineDialog() : dialog(nullptr), cursorDecl(nullptr), model(nullptr) { } void start() { if(!QuickOpenPlugin::self()->freeModel()) return; IDocument* doc = ICore::self()->documentController()->activeDocument(); if(!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock( DUChain::lock() ); TopDUContext* context = DUChainUtils::standardContextForUrl( doc->url() ); if( !context ) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } - model = new QuickOpenModel(0); + model = new QuickOpenModel(nullptr); OutlineFilter filter(items); DUChainUtils::collectItems( context, filter ); if(noHtmlDestriptionInOutline) { for(int a = 0; a < items.size(); ++a) items[a].m_noHtmlDestription = true; } cursorDecl = cursorContextDeclaration(); model->registerProvider( QStringList(), QStringList(), new DeclarationListDataProvider(QuickOpenPlugin::self(), items, true) ); dialog = new QuickOpenWidgetDialog( i18n("Outline"), model, QStringList(), QStringList(), true ); model->setParent(dialog->widget()); } void finish() { //Select the declaration that contains the cursor if(cursorDecl && dialog) { int num = 0; foreach(const DUChainItem& item, items) { if(item.m_item.data() == cursorDecl) { QModelIndex index(model->index(num,0,QModelIndex())); // Need to invoke the scrolling later. If we did it now, then it wouldn't have any effect, // apparently because the widget internals aren't initialized yet properly (although we've // already called 'widget->show()'. auto list = dialog->widget()->ui.list; QMetaObject::invokeMethod(list, "setCurrentIndex", Qt::QueuedConnection, Q_ARG(QModelIndex, index)); QMetaObject::invokeMethod(list, "scrollTo", Qt::QueuedConnection, Q_ARG(QModelIndex, index), Q_ARG(QAbstractItemView::ScrollHint, QAbstractItemView::PositionAtCenter)); } ++num; } } } QPointer dialog; Declaration* cursorDecl; QList items; QuickOpenModel* model; }; class OutlineQuickopenWidgetCreator : public QuickOpenWidgetCreator { public: - OutlineQuickopenWidgetCreator(QStringList /*scopes*/, QStringList /*items*/) : m_creator(0) { + OutlineQuickopenWidgetCreator(QStringList /*scopes*/, QStringList /*items*/) : m_creator(nullptr) { } ~OutlineQuickopenWidgetCreator() override { delete m_creator; } QuickOpenWidget* createWidget() override { delete m_creator; m_creator = new CreateOutlineDialog; m_creator->start(); if(!m_creator->dialog) - return 0; + return nullptr; m_creator->dialog->deleteLater(); return m_creator->dialog->widget(); } void widgetShown() override { if(m_creator) { m_creator->finish(); delete m_creator; - m_creator = 0; + m_creator = nullptr; } } QString objectNameForLine() override { return QStringLiteral("Outline"); } CreateOutlineDialog* m_creator; }; void QuickOpenPlugin::quickOpenNavigateFunctions() { CreateOutlineDialog create; create.start(); if(!create.dialog) return; m_currentWidgetHandler = create.dialog; QuickOpenLineEdit* line = quickOpenLine(QStringLiteral("Outline")); if(!line) line = quickOpenLine(); if(line) { line->showWithWidget(create.dialog->widget()); create.dialog->deleteLater(); }else create.dialog->run(); create.finish(); } -QuickOpenLineEdit::QuickOpenLineEdit(QuickOpenWidgetCreator* creator) : m_widget(0), m_forceUpdate(false), m_widgetCreator(creator) { +QuickOpenLineEdit::QuickOpenLineEdit(QuickOpenWidgetCreator* creator) : m_widget(nullptr), m_forceUpdate(false), m_widgetCreator(creator) { setMinimumWidth(200); setMaximumWidth(400); deactivate(); setDefaultText(i18n("Quick Open...")); setToolTip(i18n("Search for files, classes, functions and more," " allowing you to quickly navigate in your source code.")); setObjectName(m_widgetCreator->objectNameForLine()); setFocusPolicy(Qt::ClickFocus); } QuickOpenLineEdit::~QuickOpenLineEdit() { delete m_widget; delete m_widgetCreator; } bool QuickOpenLineEdit::insideThis(QObject* object) { while (object) { qCDebug(PLUGIN_QUICKOPEN) << object; if (object == this || object == m_widget) { return true; } object = object->parent(); } return false; } void QuickOpenLineEdit::widgetDestroyed(QObject* obj) { Q_UNUSED(obj); // need to use a queued connection here, because this function is called in ~QWidget! // => QuickOpenWidget instance is half-destructed => connections are not yet cleared // => clear() will trigger signals which will operate on the invalid QuickOpenWidget // So, just wait until properly destructed QMetaObject::invokeMethod(this, "deactivate", Qt::QueuedConnection); } void QuickOpenLineEdit::showWithWidget(QuickOpenWidget* widget) { connect(widget, &QuickOpenWidget::destroyed, this, &QuickOpenLineEdit::widgetDestroyed); qCDebug(PLUGIN_QUICKOPEN) << "storing widget" << widget; deactivate(); if(m_widget) { qCDebug(PLUGIN_QUICKOPEN) << "deleting" << m_widget; delete m_widget; } m_widget = widget; m_forceUpdate = true; setFocus(); } void QuickOpenLineEdit::focusInEvent(QFocusEvent* ev) { QLineEdit::focusInEvent(ev); // delete m_widget; qCDebug(PLUGIN_QUICKOPEN) << "got focus"; qCDebug(PLUGIN_QUICKOPEN) << "old widget" << m_widget << "force update:" << m_forceUpdate; if (m_widget && !m_forceUpdate) return; if (!m_forceUpdate && !QuickOpenPlugin::self()->freeModel()) { deactivate(); return; } m_forceUpdate = false; if(!m_widget) { m_widget = m_widgetCreator->createWidget(); if(!m_widget) { deactivate(); return; } } activate(); m_widget->showStandardButtons(false); m_widget->showSearchField(false); - m_widget->setParent(0, Qt::ToolTip); + m_widget->setParent(nullptr, Qt::ToolTip); m_widget->setFocusPolicy(Qt::NoFocus); m_widget->setAlternativeSearchField(this); QuickOpenPlugin::self()->m_currentWidgetHandler = m_widget; connect(m_widget.data(), &QuickOpenWidget::ready, this, &QuickOpenLineEdit::deactivate); connect( m_widget.data(), &QuickOpenWidget::scopesChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeScopes ); connect( m_widget.data(), &QuickOpenWidget::itemsChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeItems ); Q_ASSERT(m_widget->ui.searchLine == this); m_widget->prepareShow(); QRect widgetGeometry = QRect(mapToGlobal(QPoint(0, height())), mapToGlobal(QPoint(width(), height() + 400))); widgetGeometry.setWidth(700); ///@todo Waste less space QRect screenGeom = QApplication::desktop()->screenGeometry(this); if (widgetGeometry.right() > screenGeom.right()) { widgetGeometry.moveRight(screenGeom.right()); } if (widgetGeometry.bottom() > screenGeom.bottom()) { widgetGeometry.moveBottom(mapToGlobal(QPoint(0, 0)).y()); } m_widget->setGeometry(widgetGeometry); m_widget->show(); m_widgetCreator->widgetShown(); } void QuickOpenLineEdit::hideEvent(QHideEvent* ev) { QWidget::hideEvent(ev); if(m_widget) QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); // deactivate(); } bool QuickOpenLineEdit::eventFilter(QObject* obj, QEvent* e) { if (!m_widget) return false; switch (e->type()) { case QEvent::KeyPress: case QEvent::ShortcutOverride: if (static_cast(e)->key() == Qt::Key_Escape) { deactivate(); e->accept(); return true; } break; case QEvent::WindowActivate: case QEvent::WindowDeactivate: QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); break; // handle bug 260657 - "Outline menu doesn't follow main window on its move" case QEvent::Move: { if (QWidget* widget = qobject_cast(obj)) { // close the outline menu in case a parent widget moved if (widget->isAncestorOf(this)) { qCDebug(PLUGIN_QUICKOPEN) << "closing because of parent widget move"; deactivate(); } break; } } case QEvent::FocusIn: if (dynamic_cast(obj)) { QFocusEvent* focusEvent = dynamic_cast(e); Q_ASSERT(focusEvent); //Eat the focus event, keep the focus qCDebug(PLUGIN_QUICKOPEN) << "focus change" << "inside this: " << insideThis(obj) << "this" << this << "obj" << obj; if(obj == this) return false; qCDebug(PLUGIN_QUICKOPEN) << "reason" << focusEvent->reason(); if (focusEvent->reason() != Qt::MouseFocusReason && focusEvent->reason() != Qt::ActiveWindowFocusReason) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); return false; } if (!insideThis(obj)) deactivate(); } else if(obj != this) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } break; default: break; } return false; } void QuickOpenLineEdit::activate() { qCDebug(PLUGIN_QUICKOPEN) << "activating"; setText(QString()); setStyleSheet(QString()); qApp->installEventFilter(this); } void QuickOpenLineEdit::deactivate() { qCDebug(PLUGIN_QUICKOPEN) << "deactivating"; clear(); if(m_widget || hasFocus()) QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); if (m_widget) m_widget->deleteLater(); - m_widget = 0; + m_widget = nullptr; qApp->removeEventFilter(this); } void QuickOpenLineEdit::checkFocus() { qCDebug(PLUGIN_QUICKOPEN) << "checking focus" << m_widget; if(m_widget) { QWidget* focusWidget = QApplication::focusWidget(); bool focusWidgetInsideThis = focusWidget ? insideThis(focusWidget) : false; if(QApplication::focusWindow() && isVisible() && !isHidden() && (!focusWidget || (focusWidget && focusWidgetInsideThis))) { qCDebug(PLUGIN_QUICKOPEN) << "setting focus to line edit"; activateWindow(); setFocus(); } else { qCDebug(PLUGIN_QUICKOPEN) << "deactivating because check failed, focusWidget" << focusWidget << "insideThis" << focusWidgetInsideThis; deactivate(); } }else{ if (ICore::self()->documentController()->activeDocument()) ICore::self()->documentController()->activateDocument(ICore::self()->documentController()->activeDocument()); //Make sure the focus is somewehre else, even if there is no active document setEnabled(false); setEnabled(true); } } IQuickOpenLine* QuickOpenPlugin::createQuickOpenLine(const QStringList& scopes, const QStringList& type, IQuickOpen::QuickOpenType kind) { if(kind == Outline) return new QuickOpenLineEdit(new OutlineQuickopenWidgetCreator(scopes, type)); else return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(scopes, type)); } #include "quickopenplugin.moc" diff --git a/plugins/quickopen/quickopenwidget.cpp b/plugins/quickopen/quickopenwidget.cpp index 3cc9e9f085..bcc2dcb464 100644 --- a/plugins/quickopen/quickopenwidget.cpp +++ b/plugins/quickopen/quickopenwidget.cpp @@ -1,515 +1,515 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * Copyright 2016 Kevin Funk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopenwidget.h" #include #include "debug.h" #include "expandingtree/expandingdelegate.h" #include "quickopenmodel.h" #include #include #include #include #include #include #include using namespace KDevelop; class QuickOpenDelegate : public ExpandingDelegate { Q_OBJECT public: - QuickOpenDelegate(ExpandingWidgetModel* model, QObject* parent = 0L) : ExpandingDelegate(model, parent) { + QuickOpenDelegate(ExpandingWidgetModel* model, QObject* parent = nullptr) : ExpandingDelegate(model, parent) { } QList createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const override { QList highlighting = index.data(KTextEditor::CodeCompletionModel::CustomHighlight).toList(); if(!highlighting.isEmpty()) return highlightingFromVariantList(highlighting); return ExpandingDelegate::createHighlighting( index, option ); } }; QuickOpenWidget::QuickOpenWidget( QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly, bool noSearchField ) : m_model(model) , m_expandedTemporary(false) , m_hadNoCommandSinceAlt(true) { m_filterTimer.setSingleShot(true); connect(&m_filterTimer, &QTimer::timeout, this, &QuickOpenWidget::applyFilter); Q_UNUSED( title ); ui.setupUi( this ); ui.list->header()->hide(); ui.list->setRootIsDecorated( false ); ui.list->setVerticalScrollMode( QAbstractItemView::ScrollPerItem ); connect(ui.list->verticalScrollBar(), &QScrollBar::valueChanged, m_model, &QuickOpenModel::placeExpandingWidgets); ui.searchLine->setFocus(); ui.list->setItemDelegate( new QuickOpenDelegate( m_model, ui.list ) ); if(!listOnly) { QStringList allTypes = m_model->allTypes(); QStringList allScopes = m_model->allScopes(); QMenu* itemsMenu = new QMenu(this); foreach( const QString &type, allTypes ) { QAction* action = new QAction(type, itemsMenu); action->setCheckable(true); action->setChecked(initialItems.isEmpty() || initialItems.contains( type )); connect( action, &QAction::toggled, this, &QuickOpenWidget::updateProviders, Qt::QueuedConnection ); itemsMenu->addAction(action); } ui.itemsButton->setMenu(itemsMenu); QMenu* scopesMenu = new QMenu(this); foreach( const QString &scope, allScopes ) { QAction* action = new QAction(scope, scopesMenu); action->setCheckable(true); action->setChecked(initialScopes.isEmpty() || initialScopes.contains( scope ) ); connect( action, &QAction::toggled, this, &QuickOpenWidget::updateProviders, Qt::QueuedConnection ); scopesMenu->addAction(action); } ui.scopesButton->setMenu(scopesMenu); }else{ ui.list->setFocusPolicy(Qt::StrongFocus); ui.scopesButton->hide(); ui.itemsButton->hide(); ui.label->hide(); ui.label_2->hide(); } showSearchField(!noSearchField); ui.okButton->hide(); ui.cancelButton->hide(); ui.searchLine->installEventFilter( this ); ui.list->installEventFilter( this ); ui.list->setFocusPolicy(Qt::NoFocus); ui.scopesButton->setFocusPolicy(Qt::NoFocus); ui.itemsButton->setFocusPolicy(Qt::NoFocus); connect( ui.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged ); connect( ui.list, &ExpandingTree::doubleClicked, this, &QuickOpenWidget::doubleClicked ); connect(ui.okButton, &QPushButton::clicked, this, &QuickOpenWidget::accept); connect(ui.okButton, &QPushButton::clicked, this, &QuickOpenWidget::ready); connect(ui.cancelButton, &QPushButton::clicked, this, &QuickOpenWidget::ready); updateProviders(); updateTimerInterval(true); // no need to call this, it's done by updateProviders already // m_model->restart(); } void QuickOpenWidget::showStandardButtons(bool show) { if(show) { ui.okButton->show(); ui.cancelButton->show(); }else{ ui.okButton->hide(); ui.cancelButton->hide(); } } void QuickOpenWidget::updateTimerInterval(bool cheapFilterChange) { const int MAX_ITEMS = 10000; if ( cheapFilterChange && m_model->rowCount(QModelIndex()) < MAX_ITEMS ) { // cheap change and there are currently just a few items, // so apply filter instantly m_filterTimer.setInterval(0); } else if ( m_model->unfilteredRowCount() < MAX_ITEMS ) { // not a cheap change, but there are generally // just a few items in the list: apply filter instantly m_filterTimer.setInterval(0); } else { // otherwise use a timer to prevent sluggishness while typing m_filterTimer.setInterval(300); } } void QuickOpenWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); // The column width only has an effect _after_ the widget has been shown ui.list->setColumnWidth( 0, 20 ); } void QuickOpenWidget::setAlternativeSearchField(QLineEdit* alterantiveSearchField) { ui.searchLine = alterantiveSearchField; ui.searchLine->installEventFilter( this ); connect( ui.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged ); } void QuickOpenWidget::showSearchField(bool b) { if(b){ ui.searchLine->show(); ui.searchLabel->show(); }else{ ui.searchLine->hide(); ui.searchLabel->hide(); } } void QuickOpenWidget::prepareShow() { - ui.list->setModel( 0 ); + ui.list->setModel( nullptr ); ui.list->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_model->setTreeView( ui.list ); ui.list->setModel( m_model ); m_filterTimer.stop(); m_filter = QString(); if (!m_preselectedText.isEmpty()) { ui.searchLine->setText(m_preselectedText); ui.searchLine->selectAll(); } m_model->restart(false); connect( ui.list->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QuickOpenWidget::callRowSelected ); connect( ui.list->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QuickOpenWidget::callRowSelected ); } void QuickOpenWidgetDialog::run() { m_widget->prepareShow(); m_dialog->show(); } QuickOpenWidget::~QuickOpenWidget() { - m_model->setTreeView( 0 ); + m_model->setTreeView( nullptr ); } QuickOpenWidgetDialog::QuickOpenWidgetDialog(QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly, bool noSearchField) { m_widget = new QuickOpenWidget(title, model, initialItems, initialScopes, listOnly, noSearchField); // the QMenu might close on esc and we want to close the whole dialog then connect( m_widget, &QuickOpenWidget::aboutToHide, this, &QuickOpenWidgetDialog::deleteLater ); m_dialog = new QDialog( ICore::self()->uiController()->activeMainWindow() ); m_dialog->resize(QSize(800, 400)); m_dialog->setWindowTitle(title); QVBoxLayout* layout = new QVBoxLayout(m_dialog); layout->addWidget(m_widget); m_widget->showStandardButtons(true); connect(m_widget, &QuickOpenWidget::ready, m_dialog, &QDialog::close); connect( m_dialog, &QDialog::accepted, m_widget, &QuickOpenWidget::accept ); } QuickOpenWidgetDialog::~QuickOpenWidgetDialog() { delete m_dialog; } void QuickOpenWidget::setPreselectedText(const QString& text) { m_preselectedText = text; } void QuickOpenWidget::updateProviders() { if(QAction* action = (sender() ? qobject_cast(sender()) : nullptr)) { QMenu* menu = qobject_cast(action->parentWidget()); if(menu) { menu->show(); menu->setActiveAction(action); } } QStringList checkedItems; if(ui.itemsButton->menu()) { foreach( QObject* obj, ui.itemsButton->menu()->children() ) { QAction* box = qobject_cast( obj ); if( box ) { if( box->isChecked() ) checkedItems << box->text().remove('&'); } } ui.itemsButton->setText(checkedItems.join(QStringLiteral(", "))); } QStringList checkedScopes; if(ui.scopesButton->menu()) { foreach( QObject* obj, ui.scopesButton->menu()->children() ) { QAction* box = qobject_cast( obj ); if( box ) { if( box->isChecked() ) checkedScopes << box->text().remove('&'); } } ui.scopesButton->setText(checkedScopes.join(QStringLiteral(", "))); } emit itemsChanged( checkedItems ); emit scopesChanged( checkedScopes ); m_model->enableProviders( checkedItems, checkedScopes ); } void QuickOpenWidget::textChanged( const QString& str ) { // "cheap" when something was just appended to the current filter updateTimerInterval(str.startsWith(m_filter)); m_filter = str; m_filterTimer.start(); } void QuickOpenWidget::applyFilter() { m_model->textChanged( m_filter ); QModelIndex currentIndex = m_model->index(0, 0, QModelIndex()); ui.list->selectionModel()->setCurrentIndex( currentIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current ); callRowSelected(); } void QuickOpenWidget::callRowSelected() { QModelIndex currentIndex = ui.list->selectionModel()->currentIndex(); if( currentIndex.isValid() ) m_model->rowSelected( currentIndex ); else qCDebug(PLUGIN_QUICKOPEN) << "current index is not valid"; } void QuickOpenWidget::accept() { QString filterText = ui.searchLine->text(); m_model->execute( ui.list->currentIndex(), filterText ); } void QuickOpenWidget::doubleClicked ( const QModelIndex & index ) { // crash guard: https://bugs.kde.org/show_bug.cgi?id=297178 ui.list->setCurrentIndex(index); QMetaObject::invokeMethod(this, "accept", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "ready", Qt::QueuedConnection); } void QuickOpenWidget::avoidMenuAltFocus() { // send an invalid key event to the main menu bar. The menu bar will // stop listening when observing another key than ALT between the press // and the release. QKeyEvent event1(QEvent::KeyPress, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event1); QKeyEvent event2(QEvent::KeyRelease, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event2); } bool QuickOpenWidget::eventFilter ( QObject * watched, QEvent * event ) { QKeyEvent *keyEvent = dynamic_cast(event); if( event->type() == QEvent::KeyRelease ) { if(keyEvent->key() == Qt::Key_Alt) { if((m_expandedTemporary && m_altDownTime.msecsTo( QTime::currentTime() ) > 300) || (!m_expandedTemporary && m_altDownTime.msecsTo( QTime::currentTime() ) < 300 && m_hadNoCommandSinceAlt)) { //Unexpand the item QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); if(m_model->isExpanded( row )) m_model->setExpanded( row, false ); } } m_expandedTemporary = false; } } if( event->type() == QEvent::KeyPress ) { m_hadNoCommandSinceAlt = false; if(keyEvent->key() == Qt::Key_Alt) { avoidMenuAltFocus(); m_hadNoCommandSinceAlt = true; //Expand QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); m_altDownTime = QTime::currentTime(); if(!m_model->isExpanded( row )) { m_expandedTemporary = true; m_model->setExpanded( row, true ); } } } switch( keyEvent->key() ) { case Qt::Key_Tab: if ( keyEvent->modifiers() == Qt::NoModifier ) { // Tab should work just like Down QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Down, Qt::NoModifier)); return true; } break; case Qt::Key_Backtab: if ( keyEvent->modifiers() == Qt::ShiftModifier ) { // Shift + Tab should work just like Up QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier)); QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Up, Qt::NoModifier)); return true; } break; case Qt::Key_Down: case Qt::Key_Up: { if( keyEvent->modifiers() == Qt::AltModifier ) { QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ if( keyEvent->key() == Qt::Key_Down ) interface->down(); else interface->up(); return true; } return false; } } case Qt::Key_PageUp: case Qt::Key_PageDown: if(watched == ui.list ) return false; QApplication::sendEvent( ui.list, event ); //callRowSelected(); return true; case Qt::Key_Left: { //Expand/unexpand if( keyEvent->modifiers() == Qt::AltModifier ) { //Eventually Send action to the widget QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->previous(); return true; } } else { QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); if( m_model->isExpanded( row ) ) { m_model->setExpanded( row, false ); return true; } } } return false; } case Qt::Key_Right: { //Expand/unexpand if( keyEvent->modifiers() == Qt::AltModifier ) { //Eventually Send action to the widget QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->next(); return true; } } else { QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); if( !m_model->isExpanded( row ) ) { m_model->setExpanded( row, true ); return true; } } } return false; } case Qt::Key_Return: case Qt::Key_Enter: { if (m_filterTimer.isActive()) { m_filterTimer.stop(); applyFilter(); } if( keyEvent->modifiers() == Qt::AltModifier ) { //Eventually Send action to the widget QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->accept(); return true; } } else { QString filterText = ui.searchLine->text(); //Safety: Track whether this object is deleted. When execute() is called, a dialog may be opened, //which kills the quickopen widget. QPointer stillExists(this); if( m_model->execute( ui.list->currentIndex(), filterText ) ) { if(!stillExists) return true; if(!(keyEvent->modifiers() & Qt::ShiftModifier)) emit ready(); } else { //Maybe the filter-text was changed: if( filterText != ui.searchLine->text() ) { ui.searchLine->setText( filterText ); } } } return true; } } } return false; } #include "quickopenwidget.moc" diff --git a/plugins/quickopen/tests/quickopentestbase.cpp b/plugins/quickopen/tests/quickopentestbase.cpp index e60ae84bec..c9c35364da 100644 --- a/plugins/quickopen/tests/quickopentestbase.cpp +++ b/plugins/quickopen/tests/quickopentestbase.cpp @@ -1,82 +1,82 @@ /* * Copyright Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "quickopentestbase.h" #include #include #include #include #include #include using namespace KDevelop; QuickOpenTestBase::QuickOpenTestBase(Core::Setup setup_, QObject* parent) : QObject(parent) -, core(0) +, core(nullptr) , setup(setup_) -, projectController(0) +, projectController(nullptr) { qRegisterMetaType("QModelIndex"); } void QuickOpenTestBase::initTestCase() { AutoTestShell::init(); core = new TestCore; TestPluginController* pluginController = new TestPluginController(core); core->setPluginController(pluginController); TestCore::initialize(setup); projectController = new TestProjectController(core); delete core->projectController(); core->setProjectController(projectController); } void QuickOpenTestBase::cleanupTestCase() { TestCore::shutdown(); } void QuickOpenTestBase::cleanup() { projectController->closeAllProjects(); core->documentController()->closeAllDocuments(); } TestProject* getProjectWithFiles(int files) { TestProject* project = new TestProject; ProjectFolderItem* foo = createChild(project->projectItem(), QStringLiteral("foo")); ProjectFolderItem* bar = createChild(foo, QStringLiteral("bar")); for(int i = 0; i < files; ++i) { createChild(bar, QString::number(i) + QLatin1String(".txt")); } return project; } QStringList items(const ProjectFileDataProvider& provider) { QStringList list; for(uint i = 0; i < provider.itemCount(); ++i) { list << provider.data(i)->text(); } return list; } diff --git a/plugins/standardoutputview/outputwidget.cpp b/plugins/standardoutputview/outputwidget.cpp index 390ea3cd90..dc140dc508 100644 --- a/plugins/standardoutputview/outputwidget.cpp +++ b/plugins/standardoutputview/outputwidget.cpp @@ -1,675 +1,675 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * 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. */ #include "outputwidget.h" #include "standardoutputview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "toolviewdata.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_STANDARDOUTPUTVIEW, "kdevplatform.plugins.standardoutputview") Q_DECLARE_METATYPE(QTreeView*) OutputWidget::OutputWidget(QWidget* parent, const ToolViewData* tvdata) : QWidget( parent ) - , tabwidget(0) - , stackwidget(0) + , tabwidget(nullptr) + , stackwidget(nullptr) , data(tvdata) - , m_closeButton(0) - , m_closeOthersAction(0) - , nextAction(0) - , previousAction(0) - , activateOnSelect(0) - , focusOnSelect(0) - , filterInput(0) - , filterAction(0) + , m_closeButton(nullptr) + , m_closeOthersAction(nullptr) + , nextAction(nullptr) + , previousAction(nullptr) + , activateOnSelect(nullptr) + , focusOnSelect(nullptr) + , filterInput(nullptr) + , filterAction(nullptr) { setWindowTitle(i18n("Output View")); setWindowIcon(tvdata->icon); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget = new QTabWidget(this); layout->addWidget( tabwidget ); m_closeButton = new QToolButton( this ); connect( m_closeButton, &QToolButton::clicked, this, &OutputWidget::closeActiveView ); m_closeButton->setIcon( QIcon::fromTheme( QStringLiteral( "tab-close") ) ); m_closeButton->setToolTip( i18n( "Close the currently active output view") ); m_closeButton->setAutoRaise(true); m_closeOthersAction = new QAction( this ); connect(m_closeOthersAction, &QAction::triggered, this, &OutputWidget::closeOtherViews); m_closeOthersAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); m_closeOthersAction->setToolTip( i18n( "Close all other output views" ) ); m_closeOthersAction->setText( m_closeOthersAction->toolTip() ); addAction(m_closeOthersAction); tabwidget->setCornerWidget(m_closeButton, Qt::TopRightCorner); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { stackwidget = new QStackedWidget( this ); layout->addWidget( stackwidget ); previousAction = new QAction( QIcon::fromTheme( QStringLiteral( "arrow-left" ) ), i18n("Previous Output"), this ); connect(previousAction, &QAction::triggered, this, &OutputWidget::previousOutput); addAction(previousAction); nextAction = new QAction( QIcon::fromTheme( QStringLiteral( "arrow-right" ) ), i18n("Next Output"), this ); connect(nextAction, &QAction::triggered, this, &OutputWidget::nextOutput); addAction(nextAction); } addAction(dynamic_cast(data->plugin->actionCollection()->action(QStringLiteral("prev_error")))); addAction(dynamic_cast(data->plugin->actionCollection()->action(QStringLiteral("next_error")))); activateOnSelect = new KToggleAction( QIcon(), i18n("Select activated Item"), this ); activateOnSelect->setChecked( true ); focusOnSelect = new KToggleAction( QIcon(), i18n("Focus when selecting Item"), this ); focusOnSelect->setChecked( false ); if( data->option & KDevelop::IOutputView::ShowItemsButton ) { addAction(activateOnSelect); addAction(focusOnSelect); } QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); QAction* action; action = new QAction(QIcon::fromTheme(QStringLiteral("go-first")), i18n("First Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectFirstItem); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Previous Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectPreviousItem); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Next Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectNextItem); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("go-last")), i18n("Last Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectLastItem); addAction(action); QAction* selectAllAction = KStandardAction::selectAll(this, SLOT(selectAll()), this); selectAllAction->setShortcut(QKeySequence()); //FIXME: why does CTRL-A conflict with Katepart (while CTRL-Cbelow doesn't) ? selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(selectAllAction); QAction* copyAction = KStandardAction::copy(this, SLOT(copySelection()), this); copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(copyAction); if( data->option & KDevelop::IOutputView::AddFilterAction ) { QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); filterInput = new QLineEdit(); filterInput->setMaximumWidth(150); filterInput->setMinimumWidth(100); filterInput->setPlaceholderText(i18n("Search...")); filterInput->setClearButtonEnabled(true); filterInput->setToolTip(i18n("Enter a wild card string to filter the output view")); filterAction = new QWidgetAction(this); filterAction->setDefaultWidget(filterInput); addAction(filterAction); connect(filterInput, &QLineEdit::textEdited, this, &OutputWidget::outputFilter ); if( data->type & KDevelop::IOutputView::MultipleView ) { connect(tabwidget, &QTabWidget::currentChanged, this, &OutputWidget::updateFilter); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { connect(stackwidget, &QStackedWidget::currentChanged, this, &OutputWidget::updateFilter); } } addActions(data->actionList); connect( data, &ToolViewData::outputAdded, this, &OutputWidget::addOutput ); connect( this, &OutputWidget::outputRemoved, data->plugin, &StandardOutputView::outputRemoved ); foreach( int id, data->outputdata.keys() ) { changeModel( id ); changeDelegate( id ); } enableActions(); } void OutputWidget::addOutput( int id ) { QTreeView* listview = createListView(id); setCurrentWidget( listview ); connect( data->outputdata.value(id), &OutputData::modelChanged, this, &OutputWidget::changeModel); connect( data->outputdata.value(id), &OutputData::delegateChanged, this, &OutputWidget::changeDelegate); enableActions(); } void OutputWidget::setCurrentWidget( QTreeView* view ) { if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget->setCurrentWidget( view ); } else if( data->type & KDevelop::IOutputView::HistoryView ) { stackwidget->setCurrentWidget( view ); } } void OutputWidget::changeDelegate( int id ) { if( data->outputdata.contains( id ) && views.contains( id ) ) { views.value(id)->setItemDelegate(data->outputdata.value(id)->delegate); } else { addOutput(id); } } void OutputWidget::changeModel( int id ) { if( data->outputdata.contains( id ) && views.contains( id ) ) { OutputData* od = data->outputdata.value(id); views.value( id )->setModel(od->model); } else { addOutput( id ); } } void OutputWidget::removeOutput( int id ) { if( data->outputdata.contains( id ) && views.contains( id ) ) { QTreeView* view = views.value(id); if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = tabwidget->indexOf( view ); if( idx != -1 ) { tabwidget->removeTab( idx ); if( proxyModels.contains( idx ) ) { delete proxyModels.take( idx ); filters.remove( idx ); } } } else { int idx = stackwidget->indexOf( view ); if( idx != -1 && proxyModels.contains( idx ) ) { delete proxyModels.take( idx ); filters.remove( idx ); } stackwidget->removeWidget( view ); } delete view; } else { - views.value( id )->setModel( 0 ); - views.value( id )->setItemDelegate( 0 ); + views.value( id )->setModel( nullptr ); + views.value( id )->setItemDelegate( nullptr ); if( proxyModels.contains( 0 ) ) { delete proxyModels.take( 0 ); filters.remove( 0 ); } } views.remove( id ); emit outputRemoved( data->toolViewId, id ); } enableActions(); } void OutputWidget::closeActiveView() { QWidget* widget = tabwidget->currentWidget(); if( !widget ) return; foreach( int id, views.keys() ) { if( views.value(id) == widget ) { OutputData* od = data->outputdata.value(id); if( od->behaviour & KDevelop::IOutputView::AllowUserClose ) { data->plugin->removeOutput( id ); } } } enableActions(); } void OutputWidget::closeOtherViews() { QWidget* widget = tabwidget->currentWidget(); if (!widget) return; foreach (int id, views.keys()) { if (views.value(id) == widget) { continue; // leave the active view open } OutputData* od = data->outputdata.value(id); if (od->behaviour & KDevelop::IOutputView::AllowUserClose) { data->plugin->removeOutput( id ); } } enableActions(); } QWidget* OutputWidget::currentWidget() const { QWidget* widget; if( data->type & KDevelop::IOutputView::MultipleView ) { widget = tabwidget->currentWidget(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { widget = stackwidget->currentWidget(); } else { widget = views.begin().value(); } return widget; } KDevelop::IOutputViewModel *OutputWidget::outputViewModel() const { QWidget* widget = currentWidget(); if( !widget || !widget->isVisible() ) return nullptr; auto view = qobject_cast(widget); if( !view ) return nullptr; QAbstractItemModel *absmodel = view->model(); KDevelop::IOutputViewModel *iface = dynamic_cast(absmodel); if ( ! iface ) { // try if it's a proxy model? if ( QAbstractProxyModel* proxy = qobject_cast(absmodel) ) { iface = dynamic_cast(proxy->sourceModel()); } } return iface; } void OutputWidget::eventuallyDoFocus() { QWidget* widget = currentWidget(); if( focusOnSelect->isChecked() && !widget->hasFocus() ) { widget->setFocus( Qt::OtherFocusReason ); } } QAbstractItemView *OutputWidget::outputView() const { auto widget = currentWidget(); return qobject_cast(widget); } void OutputWidget::activateIndex(const QModelIndex &index, QAbstractItemView *view, KDevelop::IOutputViewModel *iface) { if( ! index.isValid() ) return; int tabIndex = currentOutputIndex(); QModelIndex sourceIndex = index; QModelIndex viewIndex = index; if( QAbstractProxyModel* proxy = proxyModels.value(tabIndex) ) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source sourceIndex = proxy->mapToSource(index); } else if (proxy == view->model()) { // index is from the source, map it to the proxy viewIndex = proxy->mapFromSource(index); } } view->setCurrentIndex( viewIndex ); view->scrollTo( viewIndex ); if( activateOnSelect->isChecked() ) { iface->activate( sourceIndex ); } } void OutputWidget::selectFirstItem() { selectItem(First); } void OutputWidget::selectNextItem() { selectItem(Next); } void OutputWidget::selectPreviousItem() { selectItem(Previous); } void OutputWidget::selectLastItem() { selectItem(Last); } void OutputWidget::selectItem(SelectionMode selectionMode) { auto view = outputView(); auto iface = outputViewModel(); if ( ! view || ! iface ) return; eventuallyDoFocus(); auto index = view->currentIndex(); if (QAbstractProxyModel* proxy = proxyModels.value(currentOutputIndex())) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source index = proxy->mapToSource(index); } } QModelIndex newIndex; switch (selectionMode) { case First: newIndex = iface->firstHighlightIndex(); break; case Next: newIndex = iface->nextHighlightIndex( index ); break; case Previous: newIndex = iface->previousHighlightIndex( index ); break; case Last: newIndex = iface->lastHighlightIndex(); break; } qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "old:" << index << "- new:" << newIndex; activateIndex(newIndex, view, iface); } void OutputWidget::activate(const QModelIndex& index) { auto iface = outputViewModel(); auto view = outputView(); if( ! view || ! iface ) return; activateIndex(index, view, iface); } QTreeView* OutputWidget::createListView(int id) { auto createHelper = [&]() -> QTreeView* { KDevelop::FocusedTreeView* listview = new KDevelop::FocusedTreeView(this); listview->setEditTriggers( QAbstractItemView::NoEditTriggers ); listview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); //Always enable the scrollbar, so it doesn't flash around listview->setHeaderHidden(true); listview->setUniformRowHeights(true); listview->setRootIsDecorated(false); listview->setSelectionMode( QAbstractItemView::ContiguousSelection ); if (data->outputdata.value(id)->behaviour & KDevelop::IOutputView::AutoScroll) { listview->setAutoScrollAtEnd(true); } connect(listview, &QTreeView::activated, this, &OutputWidget::activate); connect(listview, &QTreeView::clicked, this, &OutputWidget::activate); return listview; }; - QTreeView* listview = 0; + QTreeView* listview = nullptr; if( !views.contains(id) ) { bool newView = true; if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "creating listview"; listview = createHelper(); if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget->addTab( listview, data->outputdata.value(id)->title ); } else { stackwidget->addWidget( listview ); stackwidget->setCurrentWidget( listview ); } } else { if( views.isEmpty() ) { listview = createHelper(); layout()->addWidget( listview ); } else { listview = views.begin().value(); newView = false; } } views[id] = listview; changeModel( id ); changeDelegate( id ); if (newView) listview->scrollToBottom(); } else { listview = views.value(id); } enableActions(); return listview; } void OutputWidget::raiseOutput(int id) { if( views.contains(id) ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = tabwidget->indexOf( views.value(id) ); if( idx >= 0 ) { tabwidget->setCurrentIndex( idx ); } } else if( data->type & KDevelop::IOutputView::HistoryView ) { int idx = stackwidget->indexOf( views.value(id) ); if( idx >= 0 ) { stackwidget->setCurrentIndex( idx ); } } } enableActions(); } void OutputWidget::nextOutput() { if( stackwidget && stackwidget->currentIndex() < stackwidget->count()-1 ) { stackwidget->setCurrentIndex( stackwidget->currentIndex()+1 ); } enableActions(); } void OutputWidget::previousOutput() { if( stackwidget && stackwidget->currentIndex() > 0 ) { stackwidget->setCurrentIndex( stackwidget->currentIndex()-1 ); } enableActions(); } void OutputWidget::enableActions() { if( data->type == KDevelop::IOutputView::HistoryView ) { Q_ASSERT(stackwidget); Q_ASSERT(nextAction); Q_ASSERT(previousAction); previousAction->setEnabled( ( stackwidget->currentIndex() > 0 ) ); nextAction->setEnabled( ( stackwidget->currentIndex() < stackwidget->count() - 1 ) ); } } void OutputWidget::scrollToIndex( const QModelIndex& idx ) { QWidget* w = currentWidget(); if( !w ) return; QAbstractItemView *view = dynamic_cast(w); view->scrollTo( idx ); } void OutputWidget::copySelection() { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; QClipboard *cb = QApplication::clipboard(); QModelIndexList indexes = view->selectionModel()->selectedRows(); QString content; Q_FOREACH( const QModelIndex& index, indexes) { content += index.data().toString() + '\n'; } cb->setText(content); } void OutputWidget::selectAll() { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; view->selectAll(); } int OutputWidget::currentOutputIndex() { int index = 0; if( data->type & KDevelop::IOutputView::MultipleView ) { index = tabwidget->currentIndex(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { index = stackwidget->currentIndex(); } return index; } void OutputWidget::outputFilter(const QString& filter) { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; int index = currentOutputIndex(); auto proxyModel = dynamic_cast(view->model()); if( !proxyModel ) { proxyModel = new QSortFilterProxyModel(view->model()); proxyModel->setDynamicSortFilter(true); proxyModel->setSourceModel(view->model()); proxyModels.insert(index, proxyModel); view->setModel(proxyModel); } QRegExp regExp(filter, Qt::CaseInsensitive); proxyModel->setFilterRegExp(regExp); filters[index] = filter; } void OutputWidget::updateFilter(int index) { if(filters.contains(index)) { filterInput->setText(filters[index]); } else { filterInput->clear(); } } void OutputWidget::setTitle(int outputId, const QString& title) { if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget->setTabText(outputId - 1, title); } } diff --git a/plugins/standardoutputview/standardoutputview.cpp b/plugins/standardoutputview/standardoutputview.cpp index 27b96f9f13..a54d0cc1c8 100644 --- a/plugins/standardoutputview/standardoutputview.cpp +++ b/plugins/standardoutputview/standardoutputview.cpp @@ -1,315 +1,315 @@ /* KDevelop Standard OutputView * * Copyright 2006-2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * 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. */ #include "standardoutputview.h" #include "outputwidget.h" #include "toolviewdata.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include class OutputViewFactory : public KDevelop::IToolViewFactory{ public: OutputViewFactory(const ToolViewData* data): m_data(data) {} - QWidget* create(QWidget *parent = 0) override + QWidget* create(QWidget *parent = nullptr) override { return new OutputWidget( parent, m_data ); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } void viewCreated( Sublime::View* view ) override { m_data->views << view; } QString id() const override { //NOTE: id must be unique, see e.g. https://bugs.kde.org/show_bug.cgi?id=287093 return "org.kdevelop.OutputView." + QString::number(m_data->toolViewId); } private: const ToolViewData *m_data; }; StandardOutputView::StandardOutputView(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevstandardoutputview"), parent) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IOutputView ) setXMLFile(QStringLiteral("kdevstandardoutputview.rc")); connect(KDevelop::ICore::self()->uiController()->controller(), &Sublime::Controller::aboutToRemoveView, this, &StandardOutputView::removeSublimeView); } void StandardOutputView::removeSublimeView( Sublime::View* v ) { foreach( ToolViewData* d, toolviews ) { if( d->views.contains(v) ) { if( d->views.count() == 1 ) { toolviews.remove( d->toolViewId ); ids.removeAll( d->toolViewId ); delete d; } else { d->views.removeAll(v); } } } } StandardOutputView::~StandardOutputView() { } int StandardOutputView::standardToolView( KDevelop::IOutputView::StandardToolView view ) { if( standardViews.contains( view ) ) { return standardViews.value( view ); } int ret = -1; switch( view ) { case KDevelop::IOutputView::BuildView: { ret = registerToolView( i18nc("@title:window", "Build"), KDevelop::IOutputView::HistoryView, QIcon::fromTheme(QStringLiteral("run-build")), KDevelop::IOutputView::AddFilterAction ); break; } case KDevelop::IOutputView::RunView: { ret = registerToolView( i18nc("@title:window", "Run"), KDevelop::IOutputView::MultipleView, QIcon::fromTheme(QStringLiteral("system-run")), KDevelop::IOutputView::AddFilterAction ); break; } case KDevelop::IOutputView::DebugView: { ret = registerToolView( i18nc("@title:window", "Debug"), KDevelop::IOutputView::MultipleView, QIcon::fromTheme(QStringLiteral("debug-step-into")), KDevelop::IOutputView::AddFilterAction ); break; } case KDevelop::IOutputView::TestView: { ret = registerToolView( i18nc("@title:window", "Test"), KDevelop::IOutputView::HistoryView, QIcon::fromTheme(QStringLiteral("system-run"))); break; } case KDevelop::IOutputView::VcsView: { ret = registerToolView( i18nc("@title:window", "Version Control"), KDevelop::IOutputView::HistoryView, QIcon::fromTheme(QStringLiteral("system-run"))); break; } } Q_ASSERT(ret != -1); standardViews[view] = ret; return ret; } int StandardOutputView::registerToolView( const QString& title, KDevelop::IOutputView::ViewType type, const QIcon& icon, Options option, const QList& actionList ) { // try to reuse existing toolview foreach( ToolViewData* d, toolviews ) { if ( d->type == type && d->title == title ) { return d->toolViewId; } } // register new tool view const int newid = ids.isEmpty() ? 0 : (ids.last() + 1); qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "Registering view" << title << "with type:" << type << "id:" << newid; ToolViewData* tvdata = new ToolViewData( this ); tvdata->toolViewId = newid; tvdata->type = type; tvdata->title = title; tvdata->icon = icon; tvdata->plugin = this; tvdata->option = option; tvdata->actionList = actionList; core()->uiController()->addToolView( title, new OutputViewFactory( tvdata ) ); ids << newid; toolviews[newid] = tvdata; return newid; } int StandardOutputView::registerOutputInToolView( int toolViewId, const QString& title, KDevelop::IOutputView::Behaviours behaviour ) { if( !toolviews.contains( toolViewId ) ) return -1; int newid; if( ids.isEmpty() ) { newid = 0; } else { newid = ids.last()+1; } ids << newid; toolviews.value( toolViewId )->addOutput( newid, title, behaviour ); return newid; } void StandardOutputView::raiseOutput(int outputId) { foreach( int _id, toolviews.keys() ) { if( toolviews.value( _id )->outputdata.contains( outputId ) ) { foreach( Sublime::View* v, toolviews.value( _id )->views ) { if( v->hasWidget() ) { OutputWidget* w = qobject_cast( v->widget() ); w->raiseOutput( outputId ); v->requestRaise(); } } } } } void StandardOutputView::setModel( int outputId, QAbstractItemModel* model ) { int tvid = -1; foreach( int _id, toolviews.keys() ) { if( toolviews.value( _id )->outputdata.contains( outputId ) ) { tvid = _id; break; } } if( tvid == -1 ) qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "Trying to set model on unknown view-id:" << outputId; else { toolviews.value( tvid )->outputdata.value( outputId )->setModel( model ); } } void StandardOutputView::setDelegate( int outputId, QAbstractItemDelegate* delegate ) { int tvid = -1; foreach( int _id, toolviews.keys() ) { if( toolviews.value( _id )->outputdata.contains( outputId ) ) { tvid = _id; break; } } if( tvid == -1 ) qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "Trying to set model on unknown view-id:" << outputId; else { toolviews.value( tvid )->outputdata.value( outputId )->setDelegate( delegate ); } } void StandardOutputView::removeToolView( int toolviewId ) { if( toolviews.contains(toolviewId) ) { ToolViewData* td = toolviews.value(toolviewId); foreach( Sublime::View* view, td->views ) { if( view->hasWidget() ) { OutputWidget* outputWidget = qobject_cast( view->widget() ); foreach( int outid, td->outputdata.keys() ) { outputWidget->removeOutput( outid ); } } foreach( Sublime::Area* area, KDevelop::ICore::self()->uiController()->controller()->allAreas() ) { area->removeToolView( view ); } } delete td; toolviews.remove(toolviewId); emit toolViewRemoved(toolviewId); } } OutputWidget* StandardOutputView::outputWidgetForId( int outputId ) const { foreach( ToolViewData* td, toolviews ) { if( td->outputdata.contains( outputId ) ) { foreach( Sublime::View* view, td->views ) { if( view->hasWidget() ) return qobject_cast( view->widget() ); } } } - return 0; + return nullptr; } void StandardOutputView::scrollOutputTo( int outputId, const QModelIndex& idx ) { OutputWidget* widget = outputWidgetForId( outputId ); if( widget ) widget->scrollToIndex( idx ); } void StandardOutputView::removeOutput( int outputId ) { foreach( ToolViewData* td, toolviews ) { if( td->outputdata.contains( outputId ) ) { foreach( Sublime::View* view, td->views ) { if( view->hasWidget() ) qobject_cast( view->widget() )->removeOutput( outputId ); } td->outputdata.remove( outputId ); } } } void StandardOutputView::setTitle(int outputId, const QString& title) { outputWidgetForId(outputId)->setTitle(outputId, title); } diff --git a/plugins/standardoutputview/tests/test_standardoutputview.cpp b/plugins/standardoutputview/tests/test_standardoutputview.cpp index c10c80fadd..b59f408420 100644 --- a/plugins/standardoutputview/tests/test_standardoutputview.cpp +++ b/plugins/standardoutputview/tests/test_standardoutputview.cpp @@ -1,234 +1,234 @@ /* Copyright (C) 2011 Silvère Lestang 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test_standardoutputview.h" #include "../outputwidget.h" #include "../toolviewdata.h" namespace KDevelop { class IUiController; } class QAbstractItemDelegate; class QStandardItemModel; QTEST_MAIN(StandardOutputViewTest) const QString StandardOutputViewTest::toolviewTitle = QStringLiteral("my_toolview"); void StandardOutputViewTest::initTestCase() { KDevelop::AutoTestShell::init(); m_testCore = new KDevelop::TestCore(); m_testCore->initialize(KDevelop::Core::Default); m_controller = m_testCore->uiControllerInternal(); QTest::qWait(500); // makes sure that everything is loaded (don't know if it's required) - m_stdOutputView = 0; + m_stdOutputView = nullptr; KDevelop::IPluginController* plugin_controller = m_testCore->pluginController(); QList plugins = plugin_controller->loadedPlugins(); // make sure KDevStandardOutputView is loaded KDevelop::IPlugin* plugin = plugin_controller->loadPlugin(QStringLiteral("KDevStandardOutputView")); QVERIFY(plugin); m_stdOutputView = dynamic_cast(plugin); QVERIFY(m_stdOutputView); } void StandardOutputViewTest::cleanupTestCase() { m_testCore->cleanup(); delete m_testCore; } OutputWidget* StandardOutputViewTest::toolviewPointer(QString toolviewTitle) { QList< Sublime::View* > views = m_controller->activeArea()->toolViews(); foreach(Sublime::View* view, views) { Sublime::ToolDocument *doc = dynamic_cast(view->document()); if(doc) { if(doc->title() == toolviewTitle && view->hasWidget()) { return dynamic_cast(view->widget()); } } } - return 0; + return nullptr; } void StandardOutputViewTest::testRegisterAndRemoveToolView() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView); QVERIFY(toolviewPointer(toolviewTitle)); // re-registering should return the same tool view instead of creating a new one QCOMPARE(toolviewId, m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView)); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); } void StandardOutputViewTest::testActions() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::MultipleView, QIcon()); OutputWidget* outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); QList actions = outputWidget->actions(); QCOMPARE(actions.size(), 10); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); QList addedActions; - addedActions.append(new QAction(QStringLiteral("Action1"), 0)); - addedActions.append(new QAction(QStringLiteral("Action2"), 0)); + addedActions.append(new QAction(QStringLiteral("Action1"), nullptr)); + addedActions.append(new QAction(QStringLiteral("Action2"), nullptr)); toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView, QIcon(), KDevelop::IOutputView::ShowItemsButton | KDevelop::IOutputView::AddFilterAction, addedActions); outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); actions = outputWidget->actions(); QCOMPARE(actions.size(), 15); QCOMPARE(actions[actions.size()-2]->text(), addedActions[0]->text()); QCOMPARE(actions[actions.size()-1]->text(), addedActions[1]->text()); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); } void StandardOutputViewTest::testRegisterAndRemoveOutput() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::MultipleView, QIcon()); OutputWidget* outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); for(int i = 0; i < 5; i++) { outputId[i] = m_stdOutputView->registerOutputInToolView(toolviewId, QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { QCOMPARE(outputWidget->data->outputdata.value(outputId[i])->title, QStringLiteral("output%1").arg(i)); QCOMPARE(outputWidget->tabwidget->tabText(i), QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { m_stdOutputView->removeOutput(outputId[i]); QVERIFY(!outputWidget->data->outputdata.contains(outputId[i])); } QCOMPARE(outputWidget->tabwidget->count(), 0); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView, QIcon(), KDevelop::IOutputView::ShowItemsButton | KDevelop::IOutputView::AddFilterAction); outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); for(int i = 0; i < 5; i++) { outputId[i] = m_stdOutputView->registerOutputInToolView(toolviewId, QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { QCOMPARE(outputWidget->data->outputdata.value(outputId[i])->title, QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { m_stdOutputView->removeOutput(outputId[i]); QVERIFY(!outputWidget->data->outputdata.contains(outputId[i])); } QCOMPARE(outputWidget->stackwidget->count(), 0); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); } void StandardOutputViewTest::testSetModelAndDelegate() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::MultipleView, QIcon()); OutputWidget* outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); QAbstractItemModel* model = new QStandardItemModel; QPointer checkModel(model); QAbstractItemDelegate* delegate = new QItemDelegate; QPointer checkDelegate(delegate); outputId[0] = m_stdOutputView->registerOutputInToolView(toolviewId, QStringLiteral("output")); m_stdOutputView->setModel(outputId[0], model); m_stdOutputView->setDelegate(outputId[0], delegate); QCOMPARE(outputWidget->views.value(outputId[0])->model(), model); QCOMPARE(outputWidget->views.value(outputId[0])->itemDelegate(), delegate); QVERIFY(model->parent()); // they have a parent (the outputdata), so parent() != 0x0 QVERIFY(delegate->parent()); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); // view deleted, hence model + delegate deleted QVERIFY(!checkModel.data()); QVERIFY(!checkDelegate.data()); } void StandardOutputViewTest::testStandardToolViews() { QFETCH(KDevelop::IOutputView::StandardToolView, view); int id = m_stdOutputView->standardToolView(view); QVERIFY(id); QCOMPARE(id, m_stdOutputView->standardToolView(view)); } void StandardOutputViewTest::testStandardToolViews_data() { QTest::addColumn("view"); QTest::newRow("build") << KDevelop::IOutputView::BuildView; QTest::newRow("run") << KDevelop::IOutputView::RunView; QTest::newRow("debug") << KDevelop::IOutputView::DebugView; QTest::newRow("test") << KDevelop::IOutputView::TestView; QTest::newRow("vcs") << KDevelop::IOutputView::VcsView; } diff --git a/plugins/standardoutputview/toolviewdata.cpp b/plugins/standardoutputview/toolviewdata.cpp index 1947d33627..932c5a9973 100644 --- a/plugins/standardoutputview/toolviewdata.cpp +++ b/plugins/standardoutputview/toolviewdata.cpp @@ -1,79 +1,79 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "toolviewdata.h" #include #include OutputData::OutputData( ToolViewData* tv ) : QObject( tv ) -, delegate(0) -, model(0) +, delegate(nullptr) +, model(nullptr) , toolView(tv) , id(-1) { } void OutputData::setModel( QAbstractItemModel* model_ ) { model = model_; if (model) { model->setParent(this); } emit modelChanged( id ); } void OutputData::setDelegate( QAbstractItemDelegate* del ) { delegate = del; if (delegate) { delegate->setParent(this); } emit delegateChanged( id ); } ToolViewData::ToolViewData( QObject* parent ) - : QObject( parent ), plugin(0), toolViewId(-1) + : QObject( parent ), plugin(nullptr), toolViewId(-1) { } ToolViewData::~ToolViewData() { } OutputData* ToolViewData::addOutput( int id, const QString& title, KDevelop::IOutputView::Behaviours behave ) { OutputData* d = new OutputData( this ); d->id = id; d->title = title; d->behaviour = behave; d->toolView = this; outputdata.insert( id, d ); emit outputAdded( id ); return d; } diff --git a/plugins/subversion/kdevsvncpp/client_annotate.cpp b/plugins/subversion/kdevsvncpp/client_annotate.cpp index fa6ca5738e..7e2e5f6193 100644 --- a/plugins/subversion/kdevsvncpp/client_annotate.cpp +++ b/plugins/subversion/kdevsvncpp/client_annotate.cpp @@ -1,81 +1,81 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // Subversion api #include "svn_client.h" // svncpp #include "kdevsvncpp/client.hpp" namespace svn { static svn_error_t * annotateReceiver(void *baton, apr_int64_t line_no, svn_revnum_t revision, const char *author, const char *date, const char *line, apr_pool_t * /*pool*/) { AnnotatedFile * entries = (AnnotatedFile *) baton; entries->push_back( AnnotateLine(line_no, revision, author?author:"unknown", date?date:"unknown date", line?line:"???")); - return NULL; + return nullptr; } AnnotatedFile * Client::annotate(const Path & path, const Revision & revisionStart, const Revision & revisionEnd) throw(ClientException) { Pool pool; AnnotatedFile * entries = new AnnotatedFile; svn_error_t *error; error = svn_client_blame( path.c_str(), revisionStart.revision(), revisionEnd.revision(), annotateReceiver, entries, *m_context, // client ctx pool); - if (error != NULL) + if (error != nullptr) { delete entries; throw ClientException(error); } return entries; } } diff --git a/plugins/subversion/kdevsvncpp/client_cat.cpp b/plugins/subversion/kdevsvncpp/client_cat.cpp index f5d465cec8..08ca6e487f 100644 --- a/plugins/subversion/kdevsvncpp/client_cat.cpp +++ b/plugins/subversion/kdevsvncpp/client_cat.cpp @@ -1,178 +1,178 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // stl #include // Subversion api #include "svn_client.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/status.hpp" #include "m_check.hpp" namespace svn { std::string Client::cat(const Path & path, const Revision & revision, const Revision & peg_revision) throw(ClientException) { Pool pool; svn_stringbuf_t * stringbuf = svn_stringbuf_create("", pool); svn_stream_t * stream = svn_stream_from_stringbuf(stringbuf, pool); svn_error_t * error; error = svn_client_cat2(stream, path.c_str(), peg_revision.revision(), revision.revision(), *m_context, pool); - if (error != 0) + if (error != nullptr) throw ClientException(error); return std::string(stringbuf->data, stringbuf->len); } /** * Create a new temporary file in @a dstPath. If @a dstPath * is empty (""), then construct the temporary filename * from the temporary directory and the filename component * of @a path. The file-extension of @a path will be transformed * to @a dstPath and @a dstPath will be a unique filename * * @param dstPath path to temporary file. Will be constructed * from @a path and temporary dir (and unique elements) * if empty string * @param path existing filename. Necessary only for construction * of @a dstPath * @param pool pool to use * @return open file */ static apr_file_t * openTempFile(Path & dstPath, const Path & path, const Revision & revision, Pool & pool) throw(ClientException) { - apr_file_t * file = 0; + apr_file_t * file = nullptr; if (dstPath.length() > 0) { apr_status_t status = apr_file_open(&file, dstPath.c_str(), APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY, APR_OS_DEFAULT, pool); if (status != 0) throw ClientException(status); } else { // split the path into its components std::string dir, filename, ext; path.split(dir, filename, ext); // add the revision number to the filename char revstring[20]; if (revision.kind() == revision.HEAD) strcpy(revstring, "HEAD"); else sprintf(revstring, "%" SVN_REVNUM_T_FMT, revision.revnum()); filename += '-'; filename += revstring; // replace the dir component with tempdir Path tempPath = Path::getTempDir(); tempPath.addComponent(filename); const char * unique_name; svn_error_t * error = svn_io_open_unique_file( &file, &unique_name, tempPath.c_str(), // path ext.c_str(), // suffix 0, // dont delete on close pool); - if (error != 0) + if (error != nullptr) throw ClientException(error); dstPath = unique_name; } return file; } void Client::get(Path & dstPath, const Path & path, const Revision & revision, const Revision & peg_revision) throw(ClientException) { Pool pool; // create a new file and suppose we only want // this users to be able to read and write the file apr_file_t * file = openTempFile(dstPath, path, revision, pool); // now create a stream and let svn_client_cat write to the // stream svn_stream_t * stream = svn_stream_from_aprfile(file, pool); - if (stream != 0) + if (stream != nullptr) { svn_error_t * error = svn_client_cat2( stream, path.c_str(), peg_revision.revision() , revision.revision(), *m_context, pool); - if (error != 0) + if (error != nullptr) throw ClientException(error); svn_stream_close(stream); } // finalize stuff apr_file_close(file); } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/client_diff.cpp b/plugins/subversion/kdevsvncpp/client_diff.cpp index 2c064cc3c8..ed21fb4c16 100644 --- a/plugins/subversion/kdevsvncpp/client_diff.cpp +++ b/plugins/subversion/kdevsvncpp/client_diff.cpp @@ -1,340 +1,340 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // Subversion api #include "svn_client.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/status.hpp" namespace svn { /** * a quick way to create error messages */ static void fail(apr_pool_t *pool, apr_status_t status, const char *fmt, ...) { va_list ap; char *msg; svn_error_t * error; va_start(ap, fmt); msg = apr_pvsprintf(pool, fmt, ap); va_end(ap); - error = svn_error_create(status, NULL, msg); + error = svn_error_create(status, nullptr, msg); throw ClientException(error); } /** * closes and deletes temporary files that diff has been using */ static void diffCleanup(apr_file_t * outfile, const char * outfileName, apr_file_t * errfile, const char * errfileName, apr_pool_t *pool) { - if (outfile != NULL) + if (outfile != nullptr) apr_file_close(outfile); - if (errfile != NULL) + if (errfile != nullptr) apr_file_close(errfile); - if (outfileName != NULL) + if (outfileName != nullptr) svn_error_clear(svn_io_remove_file(outfileName, pool)); - if (errfileName != NULL) + if (errfileName != nullptr) svn_error_clear(svn_io_remove_file(errfileName, pool)); } std::string Client::diff(const Path & tmpPath, const Path & path, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted) throw(ClientException) { Pool pool; svn_error_t * error; apr_status_t status; - apr_file_t * outfile = NULL; - const char * outfileName = NULL; - apr_file_t * errfile = NULL; - const char * errfileName = NULL; + apr_file_t * outfile = nullptr; + const char * outfileName = nullptr; + apr_file_t * errfile = nullptr; + const char * errfileName = nullptr; apr_array_header_t * options; svn_stringbuf_t * stringbuf; // svn_client_diff needs an options array, even if it is empty options = apr_array_make(pool, 0, 0); // svn_client_diff needs a temporary file to write diff output to error = svn_io_open_unique_file(&outfile, &outfileName, tmpPath.c_str(), ".tmp", false, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // and another one to write errors to error = svn_io_open_unique_file(&errfile, &errfileName, tmpPath.c_str(), ".tmp", false, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // run diff error = svn_client_diff(options, path.c_str(), revision1.revision(), path.c_str(), revision2.revision(), recurse, ignoreAncestry, noDiffDeleted, outfile, errfile, *m_context, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // then we reopen outfile for reading status = apr_file_close(outfile); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to close '%s'", outfileName); } status = apr_file_open(&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to open '%s'", outfileName); } // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile(&stringbuf, outfile, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } diffCleanup(outfile, outfileName, errfile, errfileName, pool); return stringbuf->data; } std::string Client::diff(const Path & tmpPath, const Path & path1, const Path & path2, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted) throw(ClientException) { Pool pool; svn_error_t * error; apr_status_t status; - apr_file_t * outfile = NULL; - const char * outfileName = NULL; - apr_file_t * errfile = NULL; - const char * errfileName = NULL; + apr_file_t * outfile = nullptr; + const char * outfileName = nullptr; + apr_file_t * errfile = nullptr; + const char * errfileName = nullptr; apr_array_header_t * options; svn_stringbuf_t * stringbuf; // svn_client_diff needs an options array, even if it is empty options = apr_array_make(pool, 0, 0); // svn_client_diff needs a temporary file to write diff output to error = svn_io_open_unique_file(&outfile, &outfileName, tmpPath.c_str(), ".tmp", false, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // and another one to write errors to error = svn_io_open_unique_file(&errfile, &errfileName, tmpPath.c_str(), ".tmp", false, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // run diff error = svn_client_diff(options, path1.c_str(), revision1.revision(), path2.c_str(), revision2.revision(), recurse, ignoreAncestry, noDiffDeleted, outfile, errfile, *m_context, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // then we reopen outfile for reading status = apr_file_close(outfile); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to close '%s'", outfileName); } status = apr_file_open(&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to open '%s'", outfileName); } // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile(&stringbuf, outfile, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } diffCleanup(outfile, outfileName, errfile, errfileName, pool); return stringbuf->data; } std::string Client::diff(const Path & tmpPath, const Path & path, const Revision & pegRevision, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted) throw(ClientException) { Pool pool; svn_error_t * error; apr_status_t status; - apr_file_t * outfile = NULL; - const char * outfileName = NULL; - apr_file_t * errfile = NULL; - const char * errfileName = NULL; + apr_file_t * outfile = nullptr; + const char * outfileName = nullptr; + apr_file_t * errfile = nullptr; + const char * errfileName = nullptr; apr_array_header_t * options; svn_stringbuf_t * stringbuf; // svn_client_diff needs an options array, even if it is empty options = apr_array_make(pool, 0, 0); // svn_client_diff needs a temporary file to write diff output to error = svn_io_open_unique_file(&outfile, &outfileName, tmpPath.c_str(), ".tmp", false, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // and another one to write errors to error = svn_io_open_unique_file(&errfile, &errfileName, tmpPath.c_str(), ".tmp", false, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // run diff error = svn_client_diff_peg(options, path.c_str(), pegRevision.revision(), revision1.revision(), revision2.revision(), recurse, ignoreAncestry, noDiffDeleted, outfile, errfile, *m_context, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } // then we reopen outfile for reading status = apr_file_close(outfile); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to close '%s'", outfileName); } status = apr_file_open(&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (status) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); fail(pool, status, "failed to open '%s'", outfileName); } // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile(&stringbuf, outfile, pool); - if (error != NULL) + if (error != nullptr) { diffCleanup(outfile, outfileName, errfile, errfileName, pool); throw ClientException(error); } diffCleanup(outfile, outfileName, errfile, errfileName, pool); return stringbuf->data; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/client_modify.cpp b/plugins/subversion/kdevsvncpp/client_modify.cpp index b644d964b5..c125d4be46 100644 --- a/plugins/subversion/kdevsvncpp/client_modify.cpp +++ b/plugins/subversion/kdevsvncpp/client_modify.cpp @@ -1,554 +1,554 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // subversion api #include "svn_client.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/targets.hpp" #include "m_check.hpp" namespace svn { svn_revnum_t Client::checkout(const char * url, const Path & destPath, const Revision & revision, bool recurse, bool ignore_externals, const Revision & peg_revision) throw(ClientException) { Pool subPool; apr_pool_t * apr_pool = subPool.pool(); svn_revnum_t revnum = 0; svn_error_t * error = svn_client_checkout2(&revnum, url, destPath.c_str(), peg_revision.revision(), // peg_revision revision.revision(), // revision recurse, ignore_externals, *m_context, apr_pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); return revnum; } void Client::remove(const Path & path, bool force) throw(ClientException) { Pool pool; Targets targets(path.c_str()); - svn_client_commit_info_t *commit_info = NULL; + svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_delete(&commit_info, const_cast(targets.array(pool)), force, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::remove(const Targets & targets, bool force) throw(ClientException) { Pool pool; - svn_client_commit_info_t *commit_info = NULL; + svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_delete(&commit_info, const_cast(targets.array(pool)), force, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::lock(const Targets & targets, bool force, const char * comment) throw(ClientException) { Pool pool; svn_error_t * error = svn_client_lock(const_cast(targets.array(pool)), comment, force, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::unlock(const Targets & targets, bool force) throw(ClientException) { Pool pool; svn_error_t * error = svn_client_unlock(const_cast(targets.array(pool)), force, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::revert(const Targets & targets, bool recurse) throw(ClientException) { Pool pool; svn_error_t * error = svn_client_revert((targets.array(pool)), recurse, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::add(const Path & path, bool recurse) throw(ClientException) { Pool pool; // we do not need the newer version of this // function "svn_client_add2" or "svn_client_add3" // since RapidSVN doesnt even have a dialog // for adding false svn_error_t * error = svn_client_add(path.c_str(), recurse, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } std::vector Client::update(const Targets & targets, const Revision & revision, bool recurse, bool ignore_externals) throw(ClientException) { Pool pool; apr_array_header_t * result_revs; svn_error_t * error = svn_client_update2(&result_revs, const_cast(targets.array(pool)), revision.revision(), recurse, ignore_externals, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); std::vector revnums; int i; for (i = 0; i < result_revs->nelts; i++) { svn_revnum_t revnum= APR_ARRAY_IDX(result_revs, i, svn_revnum_t); revnums.push_back(revnum); } return revnums; } svn_revnum_t Client::update(const Path & path, const Revision & revision, bool recurse, bool ignore_externals) throw(ClientException) { Targets targets(path.c_str()); return update(targets, revision, recurse, ignore_externals)[0]; } svn_revnum_t Client::commit(const Targets & targets, const char * message, bool recurse, bool keep_locks) throw(ClientException) { Pool pool; m_context->setLogMessage(message); - svn_client_commit_info_t *commit_info = NULL; + svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_commit2(&commit_info, targets.array(pool), recurse, keep_locks, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); if (commit_info && SVN_IS_VALID_REVNUM(commit_info->revision)) return commit_info->revision; return -1; } void Client::copy(const Path & srcPath, const Revision & srcRevision, const Path & destPath) throw(ClientException) { Pool pool; - svn_client_commit_info_t *commit_info = NULL; + svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_copy(&commit_info, srcPath.c_str(), srcRevision.revision(), destPath.c_str(), *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::move(const Path & srcPath, const Revision & /*srcRevision*/, const Path & destPath, bool force) throw(ClientException) { Pool pool; - svn_client_commit_info_t *commit_info = NULL; + svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_move2(&commit_info, srcPath.c_str(), destPath.c_str(), force, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::mkdir(const Path & path) throw(ClientException) { Pool pool; Targets targets(path.c_str()); - svn_client_commit_info_t *commit_info = NULL; + svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_mkdir(&commit_info, const_cast (targets.array(pool)), *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::mkdir(const Targets & targets) throw(ClientException) { Pool pool; - svn_client_commit_info_t *commit_info = NULL; + svn_client_commit_info_t *commit_info = nullptr; svn_error_t * error = svn_client_mkdir(&commit_info, const_cast (targets.array(pool)), *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::cleanup(const Path & path) throw(ClientException) { Pool subPool; apr_pool_t * apr_pool = subPool.pool(); svn_error_t * error = svn_client_cleanup(path.c_str(), *m_context, apr_pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::resolved(const Path & path, bool recurse) throw(ClientException) { Pool pool; svn_error_t * error = svn_client_resolved(path.c_str(), recurse, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::doExport(const Path & from_path, const Path & to_path, const Revision & revision, bool overwrite, const Revision & peg_revision, bool ignore_externals, bool recurse, const char * native_eol) throw(ClientException) { Pool pool; svn_revnum_t revnum = 0; svn_error_t * error = svn_client_export3(&revnum, from_path.c_str(), to_path.c_str(), peg_revision.revision(), revision.revision(), overwrite, ignore_externals, recurse, native_eol, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } svn_revnum_t Client::doSwitch(const Path & path, const char * url, const Revision & revision, bool recurse) throw(ClientException) { Pool pool; svn_revnum_t revnum = 0; svn_error_t * error = svn_client_switch(&revnum, path.c_str(), url, revision.revision(), recurse, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); return revnum; } void Client::import(const Path & path, const char * url, const char * message, bool recurse) throw(ClientException) { Pool pool; - svn_client_commit_info_t *commit_info = NULL; + svn_client_commit_info_t *commit_info = nullptr; m_context->setLogMessage(message); svn_error_t * error = svn_client_import(&commit_info, path.c_str(), url, !recurse, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::import(const Path & path, const Path & url, const char * message, bool recurse) throw(ClientException) { import(path, url.c_str(), message, recurse); } void Client::merge(const Path & path1, const Revision & revision1, const Path & path2, const Revision & revision2, const Path & localPath, bool force, bool recurse, bool notice_ancestry, bool dry_run) throw(ClientException) { Pool pool; svn_error_t * error = svn_client_merge(path1.c_str(), revision1.revision(), path2.c_str(), revision2.revision(), localPath.c_str(), recurse, !notice_ancestry, force, dry_run, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::relocate(const Path & path, const char * from_url, const char * to_url, bool recurse) throw(ClientException) { Pool pool; svn_error_t * error = svn_client_relocate(path.c_str(), from_url, to_url, recurse, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::ignore(const Path & path) throw(ClientException) { static const char s_svnIgnore[] = "svn:ignore"; Pool pool; std::string dirpath, basename; path.split(dirpath, basename); Revision revision; apr_hash_t *props; svn_error_t * error = svn_client_propget(&props, s_svnIgnore, dirpath.c_str(), Revision::UNSPECIFIED.revision(), false, // recursive *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); PathPropertiesMapList path_prop_map_list; apr_hash_index_t *hi; for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) { PropertiesMap prop_map; const void *key; void *val; - apr_hash_this(hi, &key, NULL, &val); + apr_hash_this(hi, &key, nullptr, &val); prop_map [std::string(s_svnIgnore)] = std::string(((const svn_string_t *)val)->data); path_prop_map_list.push_back(PathPropertiesMapEntry((const char *)key, prop_map)); } std::string str = basename; for (PathPropertiesMapList::const_iterator i=path_prop_map_list.begin(), ei=path_prop_map_list.end();i!=ei;++i) { if (dirpath != i->first) continue; for (PropertiesMap::const_iterator j=i->second.begin(), ej=i->second.end(); j != ej; ++j) { if (s_svnIgnore != j->first) continue; str += '\n'+j->second; } } const svn_string_t * propval = svn_string_create(str.c_str(), pool); error = svn_client_propset2(s_svnIgnore, propval, dirpath.c_str(), false, false, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Client::ignore(const Targets & targets) throw(ClientException) { // it's slow, but simple for (std::vector::const_iterator i=targets.targets().begin(), e=targets.targets().end();i!=e;++i) { ignore(*i); } } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/client_property.cpp b/plugins/subversion/kdevsvncpp/client_property.cpp index de29de6bef..6b85b19bd6 100644 --- a/plugins/subversion/kdevsvncpp/client_property.cpp +++ b/plugins/subversion/kdevsvncpp/client_property.cpp @@ -1,389 +1,389 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // subversion api #include "svn_client.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/revision.hpp" #include "m_check.hpp" namespace svn { /** * lists properties in @a path no matter whether local or * repository * * @param path * @param revision * @param recurse * @return PropertiesList */ PathPropertiesMapList Client::proplist(const Path & path, const Revision & revision, bool recurse) { Pool pool; apr_array_header_t * props; svn_error_t * error = svn_client_proplist(&props, path.c_str(), revision.revision(), recurse, *m_context, pool); - if (error != NULL) + if (error != nullptr) { throw ClientException(error); } PathPropertiesMapList path_prop_map_list; for (int j = 0; j < props->nelts; ++j) { svn_client_proplist_item_t *item = ((svn_client_proplist_item_t **)props->elts)[j]; PropertiesMap prop_map; apr_hash_index_t *hi; for (hi = apr_hash_first(pool, item->prop_hash); hi; hi = apr_hash_next(hi)) { const void *key; void *val; - apr_hash_this(hi, &key, NULL, &val); + apr_hash_this(hi, &key, nullptr, &val); prop_map [std::string((const char *)key)] = std::string(((const svn_string_t *)val)->data); } path_prop_map_list.push_back(PathPropertiesMapEntry(item->node_name->data, prop_map)); } return path_prop_map_list; } /** * lists properties in @a path no matter whether local or * repository * * @param path * @param revision * @param recurse * @return PropertiesList */ PathPropertiesMapList Client::propget(const char *propName, const Path &path, const Revision &revision, bool recurse) { Pool pool; apr_hash_t *props; svn_error_t * error = svn_client_propget(&props, propName, path.c_str(), revision.revision(), recurse, *m_context, pool); - if (error != NULL) + if (error != nullptr) { throw ClientException(error); } PathPropertiesMapList path_prop_map_list; apr_hash_index_t *hi; for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) { PropertiesMap prop_map; const void *key; void *val; - apr_hash_this(hi, &key, NULL, &val); + apr_hash_this(hi, &key, nullptr, &val); prop_map [std::string(propName)] = std::string(((const svn_string_t *)val)->data); path_prop_map_list.push_back(PathPropertiesMapEntry((const char *)key, prop_map)); } return path_prop_map_list; } /** * set property in @a path no matter whether local or * repository * * @param path * @param revision * @param propName * @param propValue * @param recurse * @return PropertiesList */ void Client::propset(const char * propName, const char * propValue, const Path & path, const Revision & /*revision*/, bool recurse, bool skip_checks) { Pool pool; const svn_string_t * propval = svn_string_create((const char *) propValue, pool); svn_error_t * error = svn_client_propset2(propName, propval, path.c_str(), recurse, skip_checks, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } /** * delete property in @a path no matter whether local or * repository * * @param path * @param revision * @param propName * @param propValue * @param recurse * @return PropertiesList */ void Client::propdel(const char *propName, const Path &path, const Revision & /*revision*/, bool recurse) { Pool pool; svn_error_t * error = svn_client_propset(propName, - NULL, // value = NULL + nullptr, // value = NULL path.c_str(), recurse, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } //-------------------------------------------------------------------------------- // // revprop functions // //-------------------------------------------------------------------------------- /** * lists revision properties in @a path no matter whether local or * repository * * @param path * @param revision * @param recurse * @return PropertiesList */ std::pair Client::revproplist(const Path &path, const Revision &revision) { Pool pool; apr_hash_t * props; svn_revnum_t revnum; svn_error_t * error = svn_client_revprop_list(&props, path.c_str(), revision.revision(), &revnum, *m_context, pool); - if (error != NULL) + if (error != nullptr) { throw ClientException(error); } PropertiesMap prop_map; apr_hash_index_t *hi; for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) { const void *key; void *val; - apr_hash_this(hi, &key, NULL, &val); + apr_hash_this(hi, &key, nullptr, &val); prop_map [std::string((const char *)key)] = std::string(((const svn_string_t *)val)->data); } return std::pair (revnum, prop_map); } /** * lists one revision property in @a path no matter whether local or * repository * * @param path * @param revision * @param recurse * @return PropertiesList */ std::pair Client::revpropget(const char *propName, const Path &path, const Revision &revision) { Pool pool; svn_string_t *propval; svn_revnum_t revnum; svn_error_t * error = svn_client_revprop_get(propName, &propval, path.c_str(), revision.revision(), &revnum, *m_context, pool); - if (error != NULL) + if (error != nullptr) { throw ClientException(error); } // if the property does not exist NULL is returned - if (propval == NULL) + if (propval == nullptr) return std::pair (0, std::string()); return std::pair (revnum, std::string(propval->data)); } /** * set property in @a path no matter whether local or * repository * * @param path * @param revision * @param propName * @param propValue * @param recurse * @param revprop * @return PropertiesList */ svn_revnum_t Client::revpropset(const char *propName, const char *propValue, const Path &path, const Revision &revision, bool force) { Pool pool; const svn_string_t * propval = svn_string_create((const char *) propValue, pool); svn_revnum_t revnum; svn_error_t * error = svn_client_revprop_set(propName, propval, path.c_str(), revision.revision(), &revnum, force, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); return revnum; } /** * delete property in @a path no matter whether local or * repository * * @param path * @param revision * @param propName * @param propValue * @param recurse * @param revprop * @return PropertiesList */ svn_revnum_t Client::revpropdel(const char *propName, const Path &path, const Revision &revision, bool force) { Pool pool; svn_revnum_t revnum; svn_error_t * error = svn_client_revprop_set(propName, - NULL, // value = NULL + nullptr, // value = NULL path.c_str(), revision.revision(), &revnum, force, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); return revnum; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/client_status.cpp b/plugins/subversion/kdevsvncpp/client_status.cpp index 5ed2718b34..538b61889e 100644 --- a/plugins/subversion/kdevsvncpp/client_status.cpp +++ b/plugins/subversion/kdevsvncpp/client_status.cpp @@ -1,418 +1,418 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #if defined( _MSC_VER) && _MSC_VER <= 1200 #pragma warning( disable: 4786 )// debug symbol truncated #endif // Stdlib (for strcmp) #include "string.h" // Subversion api #include "svn_client.h" #include "svn_sorts.h" //#include "svn_utf.h" // svncpp #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/dirent.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/info.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/status.hpp" #include "kdevsvncpp/targets.hpp" #include "kdevsvncpp/url.hpp" namespace svn { static svn_error_t * logReceiver(void *baton, apr_hash_t * changedPaths, svn_revnum_t rev, const char *author, const char *date, const char *msg, apr_pool_t * pool) { LogEntries * entries = (LogEntries *) baton; entries->insert(entries->begin(), LogEntry(rev, author, date, msg)); - if (changedPaths != NULL) + if (changedPaths != nullptr) { LogEntry &entry = entries->front(); for (apr_hash_index_t *hi = apr_hash_first(pool, changedPaths); - hi != NULL; + hi != nullptr; hi = apr_hash_next(hi)) { char *path; void *val; - apr_hash_this(hi, (const void **)&path, NULL, &val); + apr_hash_this(hi, (const void **)&path, nullptr, &val); svn_log_changed_path_t *log_item = reinterpret_cast(val); entry.changedPaths.push_back( LogChangePathEntry(path, log_item->action, log_item->copyfrom_path, log_item->copyfrom_rev)); } } - return NULL; + return nullptr; } static void statusEntriesFunc(void *baton, const char *path, svn_wc_status2_t *status) { StatusEntries * entries = static_cast(baton); entries->push_back(Status(path, status)); } static StatusEntries localStatus(const char * path, const bool descend, const bool get_all, const bool update, const bool no_ignore, Context * context, const bool ignore_externals) { svn_error_t *error; StatusEntries entries; svn_revnum_t revnum; Revision rev(Revision::HEAD); Pool pool; error = svn_client_status2( &revnum, // revnum path, // path rev, // revision statusEntriesFunc, // status func &entries, // status baton descend, // recurse get_all, update, // need 'update' to be true to get repository lock info no_ignore, ignore_externals, // ignore_externals *context, // client ctx pool); - if (error!=NULL) + if (error!=nullptr) throw ClientException(error); return entries; } static Status dirEntryToStatus(const char * path, const DirEntry & dirEntry) { Pool pool; svn_wc_entry_t * e = static_cast( apr_pcalloc(pool, sizeof(svn_wc_entry_t))); std::string url(path); url += '/'; url += dirEntry.name(); e->name = dirEntry.name(); e->revision = dirEntry.createdRev(); e->url = url.c_str(); e->kind = dirEntry.kind(); e->schedule = svn_wc_schedule_normal; e->text_time = dirEntry.time(); e->prop_time = dirEntry.time(); e->cmt_rev = dirEntry.createdRev(); e->cmt_date = dirEntry.time(); e->cmt_author = dirEntry.lastAuthor(); svn_wc_status2_t * s = static_cast( apr_pcalloc(pool, sizeof(svn_wc_status2_t))); s->entry = e; s->text_status = svn_wc_status_normal; s->prop_status = svn_wc_status_normal; s->locked = 0; s->switched = 0; s->repos_text_status = svn_wc_status_normal; s->repos_prop_status = svn_wc_status_normal; return Status(url.c_str(), s); } static svn_revnum_t remoteStatus(Client * client, const char * path, const bool descend, StatusEntries & entries, Context * /*context*/) { Revision rev(Revision::HEAD); DirEntries dirEntries = client->list(path, rev, descend); DirEntries::const_iterator it; svn_revnum_t revnum = 0; for (it = dirEntries.begin(); it != dirEntries.end(); it++) { const DirEntry & dirEntry = *it; entries.push_back(dirEntryToStatus(path, dirEntry)); } if (dirEntries.size() > 0) revnum = dirEntries[0].createdRev(); return revnum; } StatusEntries Client::status(const char * path, const bool descend, const bool get_all, const bool update, const bool no_ignore, const bool ignore_externals) throw(ClientException) { if (Url::isValid(path)) { StatusEntries entries; remoteStatus(this, path, descend, entries, m_context); return entries; } else return localStatus(path, descend, get_all, update, no_ignore, m_context, ignore_externals); } struct StatusFilter; struct StatusBaton { public: const StatusFilter & filter; StatusEntries & entries; StatusBaton(const StatusFilter & filter_, StatusEntries & entries_) : filter(filter_), entries(entries_) { } }; static void filteredStatusFunc(void *baton_, const char *path, svn_wc_status2_t *status) { StatusBaton * baton = static_cast(baton_); // now we have to decide whether to return the entry or not - if (0 == status) + if (nullptr == status) return; bool useStatus = false; - bool isUnversioned = 0 == status->entry; + bool isUnversioned = nullptr == status->entry; if (isUnversioned) { // unversioned if (baton->filter.showUnversioned) useStatus = true; } else { bool isUnmodified = ((svn_wc_status_normal == status->text_status) && (svn_wc_status_normal == status->prop_status)); if (isUnmodified) { if (baton->filter.showUnmodified) useStatus = true; } else { // so here we know its modified. // what are we interested in? if (baton->filter.showModified) useStatus = true; else if (baton->filter.showConflicted) { if (svn_wc_status_conflicted == status->text_status) useStatus = true; } } } if (useStatus) baton->entries.push_back(Status(path, status)); } static svn_revnum_t localFilteredStatus(const char * path, const StatusFilter & filter, const bool descend, const bool update, StatusEntries & entries, Context * context) { svn_error_t *error; svn_revnum_t revnum; Revision rev(Revision::HEAD); Pool pool; StatusBaton baton(filter, entries); error = svn_client_status2( &revnum, // revnum path, // path rev, // revision filteredStatusFunc, // status func &baton, // status baton descend, // recurse filter.showUnmodified, update, // need 'update' to be true to get repository lock info filter.showIgnored, // no_ignores !filter.showExternals, // ignore_externals *context, // client ctx pool); - if (error!=NULL) + if (error!=nullptr) throw ClientException(error); return revnum; } svn_revnum_t Client::status(const char * path, const StatusFilter & filter, const bool descend, const bool update, StatusEntries & entries) throw(ClientException) { entries.clear(); if (Url::isValid(path)) return remoteStatus(this, path, descend, entries, m_context); else { // remote URLs only need a subset of the filters: // we dont expect any modified, conflicting, unknown, // ignored entries. And externals arent visible there anyhow return localFilteredStatus( path, filter, descend, update, entries, m_context); } } const LogEntries * Client::log(const char * path, const Revision & revisionStart, const Revision & revisionEnd, bool discoverChangedPaths, bool strictNodeHistory) throw(ClientException) { Pool pool; Targets target(path); LogEntries * entries = new LogEntries(); svn_error_t *error; int limit = 0; error = svn_client_log2( target.array(pool), revisionStart.revision(), revisionEnd.revision(), limit, discoverChangedPaths ? 1 : 0, strictNodeHistory ? 1 : 0, logReceiver, entries, *m_context, // client ctx pool); - if (error != NULL) + if (error != nullptr) { delete entries; throw ClientException(error); } return entries; } /** * callback function for Client::info, will be * called for every entry svn_client_info wants to * return */ static svn_error_t * infoReceiverFunc(void * baton, const char * path, const svn_info_t * info, apr_pool_t * /*pool*/) { InfoVector * infoVector = static_cast(baton); infoVector->push_back(Info(path, info)); - return 0; + return nullptr; } InfoVector Client::info(const Path & pathOrUrl, bool recurse, const Revision & revision, const Revision & pegRevision) throw(ClientException) { Pool pool; InfoVector infoVector; svn_error_t * error = svn_client_info(pathOrUrl.c_str(), pegRevision.revision(), revision.revision(), infoReceiverFunc, &infoVector, recurse, *m_context, pool); - if (error != 0) + if (error != nullptr) throw ClientException(error); return infoVector; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/context.cpp b/plugins/subversion/kdevsvncpp/context.cpp index 37b4002d8a..44094fab7e 100644 --- a/plugins/subversion/kdevsvncpp/context.cpp +++ b/plugins/subversion/kdevsvncpp/context.cpp @@ -1,717 +1,717 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ /** * @todo implement 1.3 SVN api: * * SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN * svn_client_add3 * svn_client_copy2 * svn_client_commit3 * svn_client_delete2 * svn_client_move3 * svn_client_mkdir2 * svn_client_import2 * svn_client_info */ // Apache Portable Runtime #include "apr_xlate.h" // Subversion api #include "svn_auth.h" #include "svn_config.h" #include "svn_subst.h" //#include "svn_utf.h" // svncpp #include "kdevsvncpp/apr.hpp" #include "kdevsvncpp/context.hpp" #include "kdevsvncpp/context_listener.hpp" namespace svn { struct Context::Data { public: /** The usage of Apr makes sure Apr is initialized * before any use of apr functions. */ Apr apr; ContextListener * listener; bool logIsSet; int promptCounter; Pool pool; svn_client_ctx_t * ctx; std::string username; std::string password; std::string logMessage; std::string configDir; /** * translate native c-string to utf8 */ static svn_error_t * translateString(const char * str, const char ** newStr, apr_pool_t * /*pool*/) { // due to problems with apr_xlate we dont perform // any conversion at this place. YOU will have to make // sure any strings passed are UTF 8 strings // svn_string_t *string = svn_string_create ("", pool); // // string->data = str; // string->len = strlen (str); // // const char * encoding = APR_LOCALE_CHARSET; // // SVN_ERR (svn_subst_translate_string (&string, string, // encoding, pool)); // // *newStr = string->data; *newStr = str; return SVN_NO_ERROR; } /** * the @a baton is interpreted as Data * * Several checks are performed on the baton: * - baton == 0? * - baton->Data * - listener set? * * @param baton * @param data returned data if everything is OK * @retval SVN_NO_ERROR if everything is fine * @retval SVN_ERR_CANCELLED on invalid values */ static svn_error_t * getData(void * baton, Data ** data) { - if (baton == NULL) - return svn_error_create(SVN_ERR_CANCELLED, NULL, + if (baton == nullptr) + return svn_error_create(SVN_ERR_CANCELLED, nullptr, "invalid baton"); Data * data_ = static_cast (baton); - if (data_->listener == 0) - return svn_error_create(SVN_ERR_CANCELLED, NULL, + if (data_->listener == nullptr) + return svn_error_create(SVN_ERR_CANCELLED, nullptr, "invalid listener"); *data = data_; return SVN_NO_ERROR; } Data(const std::string & configDir_) - : listener(0), logIsSet(false), + : listener(nullptr), logIsSet(false), promptCounter(0), configDir(configDir_) { - const char * c_configDir = 0; + const char * c_configDir = nullptr; if (configDir.length() > 0) c_configDir = configDir.c_str(); // make sure the configuration directory exists svn_config_ensure(c_configDir, pool); // initialize authentication providers // * simple // * username // * simple prompt // * ssl server trust file // * ssl server trust prompt // * ssl client cert pw file // * ssl client cert pw prompt // * ssl client cert file // =================== // 8 providers apr_array_header_t *providers = apr_array_make(pool, 8, sizeof(svn_auth_provider_object_t *)); svn_auth_provider_object_t *provider; svn_client_get_simple_provider( &provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_username_provider( &provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_simple_prompt_provider( &provider, onSimplePrompt, this, 100000000, // not very nice. should be infinite... pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; // add ssl providers // file first then prompt providers svn_client_get_ssl_server_trust_file_provider(&provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_ssl_client_cert_file_provider(&provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_ssl_client_cert_pw_file_provider(&provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_ssl_server_trust_prompt_provider( &provider, onSslServerTrustPrompt, this, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; // plugged in 3 as the retry limit - what is a good limit? svn_client_get_ssl_client_cert_pw_prompt_provider( &provider, onSslClientCertPwPrompt, this, 3, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_auth_baton_t *ab; svn_auth_open(&ab, providers, pool); // initialize ctx structure svn_client_create_context(&ctx, pool); // get the config based on the configDir passed in svn_config_get_config(&ctx->config, c_configDir, pool); // disable external diff and diff3 commands svn_config_t *config = (svn_config_t *)apr_hash_get( ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING); svn_config_set(config, SVN_CONFIG_SECTION_HELPERS, - SVN_CONFIG_OPTION_DIFF_CMD, NULL); + SVN_CONFIG_OPTION_DIFF_CMD, nullptr); svn_config_set(config, SVN_CONFIG_SECTION_HELPERS, - SVN_CONFIG_OPTION_DIFF3_CMD, NULL); + SVN_CONFIG_OPTION_DIFF3_CMD, nullptr); // tell the auth functions where the config is svn_auth_set_parameter(ab, SVN_AUTH_PARAM_CONFIG_DIR, c_configDir); ctx->auth_baton = ab; ctx->log_msg_func = onLogMsg; ctx->log_msg_baton = this; ctx->notify_func = onNotify; ctx->notify_baton = this; ctx->cancel_func = onCancel; ctx->cancel_baton = this; ctx->notify_func2 = onNotify2; ctx->notify_baton2 = this; } void setAuthCache(bool value) { - void *param = 0; + void *param = nullptr; if (!value) param = (void *)"1"; svn_auth_set_parameter(ctx->auth_baton, SVN_AUTH_PARAM_NO_AUTH_CACHE, param); } /** @see Context::setLogin */ void setLogin(const char * usr, const char * pwd) { username = usr; password = pwd; svn_auth_baton_t * ab = ctx->auth_baton; svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_USERNAME, username.c_str()); svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD, password.c_str()); } /** @see Context::setLogMessage */ void setLogMessage(const char * msg) { logMessage = msg; logIsSet = true; } /** * this function gets called by the subversion api function * when a log message is needed. This is the case on a commit * for example */ static svn_error_t * onLogMsg(const char **log_msg, const char **tmp_file, apr_array_header_t *, //UNUSED commit_items void *baton, apr_pool_t * pool) { - Data * data = NULL; + Data * data = nullptr; SVN_ERR(getData(baton, &data)); std::string msg; if (data->logIsSet) msg = data->getLogMessage(); else { if (!data->retrieveLogMessage(msg)) - return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); + return svn_error_create(SVN_ERR_CANCELLED, nullptr, ""); } *log_msg = apr_pstrdup(pool, msg.c_str()); - *tmp_file = NULL; + *tmp_file = nullptr; return SVN_NO_ERROR; } /** * this is the callback function for the subversion * api functions to signal the progress of an action */ static void onNotify(void * baton, const char *path, svn_wc_notify_action_t action, svn_node_kind_t kind, const char *mime_type, svn_wc_notify_state_t content_state, svn_wc_notify_state_t prop_state, svn_revnum_t revision) { - if (baton == 0) + if (baton == nullptr) return; Data * data = static_cast (baton); data->notify(path, action, kind, mime_type, content_state, prop_state, revision); } /** * this is the callback function for the subversion 1.2 * api functions to signal the progress of an action * * @todo right now we forward only to @a onNotify, * but maybe we should a notify2 to the listener * @since subversion 1.2 */ static void onNotify2(void*baton,const svn_wc_notify_t *action,apr_pool_t *) { if (!baton) return; // for now forward the call to @a onNotify onNotify(baton, action->path, action->action, action->kind, action->mime_type, action->content_state, action->prop_state, action->revision); } /** * this is the callback function for the subversion * api functions to signal the progress of an action */ static svn_error_t * onCancel(void * baton) { - if (baton == 0) + if (baton == nullptr) return SVN_NO_ERROR; Data * data = static_cast (baton); if (data->cancel()) - return svn_error_create(SVN_ERR_CANCELLED, NULL, "cancelled by user"); + return svn_error_create(SVN_ERR_CANCELLED, nullptr, "cancelled by user"); else return SVN_NO_ERROR; } /** * @see svn_auth_simple_prompt_func_t */ static svn_error_t * onSimplePrompt(svn_auth_cred_simple_t **cred, void *baton, const char *realm, const char *username, svn_boolean_t _may_save, apr_pool_t *pool) { - Data * data = NULL; + Data * data = nullptr; SVN_ERR(getData(baton, &data)); bool may_save = _may_save != 0; if (!data->retrieveLogin(username, realm, may_save)) - return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); + return svn_error_create(SVN_ERR_CANCELLED, nullptr, ""); svn_auth_cred_simple_t* lcred = (svn_auth_cred_simple_t*) apr_palloc(pool, sizeof(svn_auth_cred_simple_t)); /* SVN_ERR (svn_utf_cstring_to_utf8 ( &lcred->password, data->getPassword (), pool)); SVN_ERR (svn_utf_cstring_to_utf8 ( &lcred->username, data->getUsername (), pool)); */ lcred->password = data->getPassword(); lcred->username = data->getUsername(); // tell svn if the credentials need to be saved lcred->may_save = may_save; *cred = lcred; return SVN_NO_ERROR; } /** * @see svn_auth_ssl_server_trust_prompt_func_t */ static svn_error_t * onSslServerTrustPrompt(svn_auth_cred_ssl_server_trust_t **cred, void *baton, const char *realm, apr_uint32_t failures, const svn_auth_ssl_server_cert_info_t *info, svn_boolean_t may_save, apr_pool_t *pool) { - Data * data = NULL; + Data * data = nullptr; SVN_ERR(getData(baton, &data)); ContextListener::SslServerTrustData trustData(failures); - if (realm != NULL) + if (realm != nullptr) trustData.realm = realm; trustData.hostname = info->hostname; trustData.fingerprint = info->fingerprint; trustData.validFrom = info->valid_from; trustData.validUntil = info->valid_until; trustData.issuerDName = info->issuer_dname; trustData.maySave = may_save != 0; apr_uint32_t acceptedFailures; ContextListener::SslServerTrustAnswer answer = data->listener->contextSslServerTrustPrompt( trustData, acceptedFailures); if (answer == ContextListener::DONT_ACCEPT) - *cred = NULL; + *cred = nullptr; else { svn_auth_cred_ssl_server_trust_t *cred_ = (svn_auth_cred_ssl_server_trust_t*) apr_palloc(pool, sizeof(svn_auth_cred_ssl_server_trust_t)); if (answer == ContextListener::ACCEPT_PERMANENTLY) { cred_->may_save = 1; cred_->accepted_failures = acceptedFailures; } *cred = cred_; } return SVN_NO_ERROR; } /** * @see svn_auth_ssl_client_cert_prompt_func_t */ static svn_error_t * onSslClientCertPrompt(svn_auth_cred_ssl_client_cert_t **cred, void *baton, apr_pool_t *pool) { - Data * data = NULL; + Data * data = nullptr; SVN_ERR(getData(baton, &data)); std::string certFile; if (!data->listener->contextSslClientCertPrompt(certFile)) - return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); + return svn_error_create(SVN_ERR_CANCELLED, nullptr, ""); svn_auth_cred_ssl_client_cert_t *cred_ = (svn_auth_cred_ssl_client_cert_t*) apr_palloc(pool, sizeof(svn_auth_cred_ssl_client_cert_t)); /* SVN_ERR (svn_utf_cstring_to_utf8 ( &cred_->cert_file, certFile.c_str (), pool)); */ cred_->cert_file = certFile.c_str(); *cred = cred_; return SVN_NO_ERROR; } /** * @see svn_auth_ssl_client_cert_pw_prompt_func_t */ static svn_error_t * onSslClientCertPwPrompt( svn_auth_cred_ssl_client_cert_pw_t **cred, void *baton, const char *realm, svn_boolean_t maySave, apr_pool_t *pool) { - Data * data = NULL; + Data * data = nullptr; SVN_ERR(getData(baton, &data)); std::string password; bool may_save = maySave != 0; if (!data->listener->contextSslClientCertPwPrompt(password, realm, may_save)) - return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); + return svn_error_create(SVN_ERR_CANCELLED, nullptr, ""); svn_auth_cred_ssl_client_cert_pw_t *cred_ = (svn_auth_cred_ssl_client_cert_pw_t *) apr_palloc(pool, sizeof(svn_auth_cred_ssl_client_cert_pw_t)); /* SVN_ERR (svn_utf_cstring_to_utf8 ( &cred_->password, password.c_str (), pool)); */ cred_->password = password.c_str(); cred_->may_save = may_save; *cred = cred_; return SVN_NO_ERROR; } const char * getUsername() const { return username.c_str(); } const char * getPassword() const { return password.c_str(); } const char * getLogMessage() const { return logMessage.c_str(); } /** * if the @a listener is set, use it to retrieve the log * message using ContextListener::contextGetLogMessage. * This return values is given back, then. * * if the @a listener is not set the its checked whether * the log message has been set using @a setLogMessage * yet. If not, return false otherwise true * * @param msg log message * @retval false cancel */ bool retrieveLogMessage(std::string & msg) { bool ok; - if (listener == 0) + if (listener == nullptr) return false; ok = listener->contextGetLogMessage(logMessage); if (ok) msg = logMessage; else logIsSet = false; return ok; } /** * if the @a listener is set and no password has been * set yet, use it to retrieve login and password using * ContextListener::contextGetLogin. * * if the @a listener is not set, check if setLogin * has been called yet. * * @return continue? * @retval false cancel */ bool retrieveLogin(const char * username_, const char * realm, bool &may_save) { bool ok; - if (listener == 0) + if (listener == nullptr) return false; - if (username_ == NULL) + if (username_ == nullptr) username = ""; else username = username_; ok = listener->contextGetLogin(realm, username, password, may_save); return ok; } /** * if the @a listener is set call the method * @a contextNotify */ void notify(const char *path, svn_wc_notify_action_t action, svn_node_kind_t kind, const char *mime_type, svn_wc_notify_state_t content_state, svn_wc_notify_state_t prop_state, svn_revnum_t revision) { - if (listener != 0) + if (listener != nullptr) { listener->contextNotify(path, action, kind, mime_type, content_state, prop_state, revision); } } /** * if the @a listener is set call the method * @a contextCancel */ bool cancel() { - if (listener != 0) + if (listener != nullptr) { return listener->contextCancel(); } else { // don't cancel if no listener return false; } } }; Context::Context(const std::string &configDir) { m = new Data(configDir); } Context::Context(const Context & src) { m = new Data(src.m->configDir); setLogin(src.getUsername(), src.getPassword()); } Context::~Context() { delete m; } void Context::setAuthCache(bool value) { m->setAuthCache(value); } void Context::setLogin(const char * username, const char * password) { m->setLogin(username, password); } Context::operator svn_client_ctx_t * () { return m->ctx; } svn_client_ctx_t * Context::ctx() { return m->ctx; } void Context::setLogMessage(const char * msg) { m->setLogMessage(msg); } const char * Context::getUsername() const { return m->getUsername(); } const char * Context::getPassword() const { return m->getPassword(); } const char * Context::getLogMessage() const { return m->getLogMessage(); } void Context::setListener(ContextListener * listener) { m->listener = listener; } ContextListener * Context::getListener() const { return m->listener; } void Context::reset() { m->promptCounter = 0; m->logIsSet = false; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/dirent.cpp b/plugins/subversion/kdevsvncpp/dirent.cpp index 75dd29cbb0..73c2dde4e2 100644 --- a/plugins/subversion/kdevsvncpp/dirent.cpp +++ b/plugins/subversion/kdevsvncpp/dirent.cpp @@ -1,153 +1,153 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // stl #include "kdevsvncpp/string_wrapper.hpp" // svncpp #include "kdevsvncpp/dirent.hpp" namespace svn { struct DirEntry::Data { public: std::string name; svn_node_kind_t kind; svn_filesize_t size; bool hasProps; svn_revnum_t createdRev; apr_time_t time; std::string lastAuthor; Data() : kind(svn_node_unknown), size(0), hasProps(false), createdRev(0), time(0) { } Data(const char * _name, const svn_dirent_t * dirEntry) : name(_name), kind(dirEntry->kind), size(dirEntry->size), hasProps(dirEntry->has_props != 0), createdRev(dirEntry->created_rev), time(dirEntry->time) { - lastAuthor = dirEntry->last_author == 0 ? "" : dirEntry->last_author; + lastAuthor = dirEntry->last_author == nullptr ? "" : dirEntry->last_author; } Data(const DirEntry & src) { init(src); } void init(const DirEntry & src) { name = src.name(); kind = src.kind(); size = src.size(); hasProps = src.hasProps(); createdRev = src.createdRev(); time = src.time(); lastAuthor = src.lastAuthor(); } }; DirEntry::DirEntry() : m(new Data()) { } DirEntry::DirEntry(const char * name, const svn_dirent_t * DirEntry) : m(new Data(name, DirEntry)) { } DirEntry::DirEntry(const DirEntry & src) : m(new Data(src)) { } DirEntry::~DirEntry() { delete m; } svn_node_kind_t DirEntry::kind() const { return m->kind; } svn_filesize_t DirEntry::size() const { return m->size; } bool DirEntry::hasProps() const { return m->hasProps; } svn_revnum_t DirEntry::createdRev() const { return m->createdRev; } apr_time_t DirEntry::time() const { return m->time; } const char * DirEntry::lastAuthor() const { return m->lastAuthor.c_str(); } const char * DirEntry::name() const { return m->name.c_str(); } DirEntry & DirEntry::operator= (const DirEntry & dirEntry) { if (this == &dirEntry) return *this; m->init(dirEntry); return *this; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/entry.cpp b/plugins/subversion/kdevsvncpp/entry.cpp index 0d9a2adf63..de47848734 100644 --- a/plugins/subversion/kdevsvncpp/entry.cpp +++ b/plugins/subversion/kdevsvncpp/entry.cpp @@ -1,82 +1,82 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // svncpp #include "kdevsvncpp/entry.hpp" #include "m_check.hpp" namespace svn { Entry::Entry(const svn_wc_entry_t * src) - : m_entry(0), m_pool(0), m_valid(false) + : m_entry(nullptr), m_pool(nullptr), m_valid(false) { init(src); } Entry::Entry(const Entry & src) - : m_entry(0), m_pool(0), m_valid(false) + : m_entry(nullptr), m_pool(nullptr), m_valid(false) { init(src); } Entry::~Entry() { // no need to explicitly de-allocate m_entry // since this will be handled by m_pool } void Entry::init(const svn_wc_entry_t * src) { if (src) { // copy the contents of src m_entry = svn_wc_entry_dup(src, m_pool); m_valid = true; } else { // create an empty entry m_entry = (svn_wc_entry_t*) apr_pcalloc(m_pool, sizeof(svn_wc_entry_t)); } } Entry & Entry::operator = (const Entry & src) { if (this == &src) return *this; init(src); return *this; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/exception.cpp b/plugins/subversion/kdevsvncpp/exception.cpp index 4db3471dde..5a4d87fa39 100644 --- a/plugins/subversion/kdevsvncpp/exception.cpp +++ b/plugins/subversion/kdevsvncpp/exception.cpp @@ -1,136 +1,136 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // stl #include "kdevsvncpp/string_wrapper.hpp" #include // svncpp #include "kdevsvncpp/exception.hpp" namespace svn { struct Exception::Data { public: std::string message; apr_status_t apr_err; Data(const char * msg) : message(msg) { } Data(const Data& other) : message(other.message), apr_err(other.apr_err) { } }; Exception::Exception(const char * message) throw() { m = new Data(message); } Exception::Exception(const Exception & other) throw() { m = new Data(*other.m); } Exception::~Exception() throw() { delete m; } apr_status_t Exception::apr_err() const { return m->apr_err; } const char * Exception::message() const { return m->message.c_str(); } ClientException::ClientException(svn_error_t * error) throw() : Exception("") { - if (error == 0) + if (error == nullptr) return; m->apr_err = error->apr_err; svn_error_t * next = error->child; /// @todo send rapidsvn an hint that error->message may sometimes NULL! std::string & message = m->message; if (error->message) message = error->message; else { message = "Unknown error!\n"; if (error->file) { message += "In file "; message += error->file; std::stringstream num; num << " Line " << error->line; message += num.str(); } } - while (next != NULL && next->message != NULL) + while (next != nullptr && next->message != nullptr) { message = message + '\n' + next->message; next = next->child; } svn_error_clear(error); } ClientException::ClientException(apr_status_t status) throw() : Exception("") { m->apr_err = status; } ClientException::~ClientException() throw() { } ClientException::ClientException(const ClientException & src) throw() : Exception(src.message()) { } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/info.cpp b/plugins/subversion/kdevsvncpp/info.cpp index 4ac5f9cd7e..fbcd6d330a 100644 --- a/plugins/subversion/kdevsvncpp/info.cpp +++ b/plugins/subversion/kdevsvncpp/info.cpp @@ -1,254 +1,254 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * Copyright (c) 2008 Andreas Pakulat * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // svncpp #include "kdevsvncpp/info.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/pool.hpp" #include namespace svn { struct Info::Data { svn_info_t * info; Path path; Pool pool; /** constructor (because of optional param */ - Data(const Path & path_, const svn_info_t * info_ = 0) - : info(0), path(path_) + Data(const Path & path_, const svn_info_t * info_ = nullptr) + : info(nullptr), path(path_) { - if (info_ != 0) + if (info_ != nullptr) info = svn_info_dup(info_, pool); } /** copy constructor */ Data(const Data * src) - : info(0), path(src->path) + : info(nullptr), path(src->path) { - if (src->info != 0) + if (src->info != nullptr) info = svn_info_dup(src->info, pool); } }; Info::Info(const Path & path, const svn_info_t * info) : m(new Data(path, info)) { } Info::Info(const Info & src) : m(new Data(src.m)) { } Info::~Info() { delete m; } Info & Info::operator = (const Info & src) { if (this != &src) { delete m; m = new Data(src.m); } return *this; } svn_node_kind_t Info::kind() const { if (isValid()) return svn_node_none; else return m->info->kind; } bool Info::isValid() const { - return m->info != 0; + return m->info != nullptr; } const char * Info::url() const { if (isValid()) - return 0; + return nullptr; else return m->info->URL; } const Path & Info::path () const { return m->path; } svn_revnum_t Info::revision () const { if (isValid()) return 0; else return m->info->rev; } const char * Info::repos () const { if (isValid()) - return 0; + return nullptr; else return m->info->repos_root_URL; } const char * Info::uuid () const { if (isValid()) - return 0; + return nullptr; else return m->info->repos_UUID; } svn_revnum_t Info::lastChangedRevision () const { if (isValid()) return 0; else return m->info->last_changed_rev; } apr_time_t Info::lastChangedDate () const { if (isValid()) return 0; else return m->info->last_changed_date; } const char * Info::lastChangedAuthor () const { if (isValid()) - return 0; + return nullptr; else return m->info->last_changed_author; } bool Info::hasWcInfo () const { if (isValid()) return 0; else return m->info->has_wc_info; } svn_wc_schedule_t Info::schedule () const { assert(m->info); return m->info->schedule; } const char* Info::copyFromUrl () const { if (isValid()) - return 0; + return nullptr; else return m->info->copyfrom_url; } svn_revnum_t Info::copyFromRevision () const { if (isValid()) return 0; else return m->info->copyfrom_rev; } apr_time_t Info::textTime () const { if (isValid()) return 0; else return m->info->text_time; } apr_time_t Info::propertyTime () const { if (isValid()) return 0; else return m->info->prop_time; } const char* Info::oldConflictFile () const { if (isValid()) - return 0; + return nullptr; else return m->info->conflict_old; } const char* Info::newConflictFile () const { if (isValid()) - return 0; + return nullptr; else return m->info->conflict_new; } const char* Info::workingConflictFile () const { if (isValid()) - return 0; + return nullptr; else return m->info->conflict_wrk; } const char* Info::propertyRejectFile () const { if (isValid()) - return 0; + return nullptr; else return m->info->prejfile; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/log_entry.cpp b/plugins/subversion/kdevsvncpp/log_entry.cpp index f41725058c..bd74915383 100644 --- a/plugins/subversion/kdevsvncpp/log_entry.cpp +++ b/plugins/subversion/kdevsvncpp/log_entry.cpp @@ -1,81 +1,81 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // stl #include "kdevsvncpp/string_wrapper.hpp" // svncpp #include "kdevsvncpp/log_entry.hpp" #include "kdevsvncpp/pool.hpp" // subversion api #include "svn_time.h" namespace svn { LogChangePathEntry::LogChangePathEntry( const char *path_, char action_, const char *copyFromPath_, const svn_revnum_t copyFromRevision_) : path(path_), action(action_), - copyFromPath(copyFromPath_ != NULL ? copyFromPath_ : ""), + copyFromPath(copyFromPath_ != nullptr ? copyFromPath_ : ""), copyFromRevision(copyFromRevision_) { } LogEntry::LogEntry() { } LogEntry::LogEntry( const svn_revnum_t revision_, const char * author_, const char * date_, const char * message_) { date = 0; - if (date_ != 0) + if (date_ != nullptr) { Pool pool; - if (svn_time_from_cstring(&date, date_, pool) != 0) + if (svn_time_from_cstring(&date, date_, pool) != nullptr) date = 0; } revision = revision_; - author = author_ == 0 ? "" : author_; - message = message_ == 0 ? "" : message_; + author = author_ == nullptr ? "" : author_; + message = message_ == nullptr ? "" : message_; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/path.cpp b/plugins/subversion/kdevsvncpp/path.cpp index 4dced08647..33fc2c8892 100644 --- a/plugins/subversion/kdevsvncpp/path.cpp +++ b/plugins/subversion/kdevsvncpp/path.cpp @@ -1,406 +1,406 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // subversion api #include "svn_path.h" #include "svn_dirent_uri.h" // apr api #include "apr_file_io.h" // svncpp #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/url.hpp" namespace svn { const PathVector EmptyPathVector; Path::Path(const char * path) { init(path); } Path::Path(const std::string & path) { init(path.c_str()); } Path::Path(const Path & path) { init(path.c_str()); } void Path::init(const char * path) { Pool pool; m_pathIsUrl = false; - if (path == 0) + if (path == nullptr) m_path = ""; else { const char * int_path = svn_dirent_canonicalize(path, pool); m_path = int_path; if (svn::Url::isValid(int_path)) m_pathIsUrl = true; } } const std::string & Path::path() const { return m_path; } const char * Path::c_str() const { return m_path.c_str(); } Path& Path::operator= (const Path & path) { if (this == &path) return *this; init(path.c_str()); return *this; } bool Path::operator== (const Path& path) const { if (path.path() == this->path()) return true; return false; } bool Path::isSet() const { return m_path.length() > 0; } bool Path::isUrl() const { return m_pathIsUrl; } static bool isAbsolute(const char * path) { - if (0 == path) + if (nullptr == path) return false; std::string p(path); if (0 == p.length()) return false; // a path that begins with "/" is absolute if ('/' == p [0]) return true; // a path with a ":" like "http://xxx" or // "c:/foo" is absolute too if (p.find(":", 0) != std::string::npos) return true; // Well it's relative return false; } void Path::addComponent(const char * component) { Pool pool; - if (0 == component) + if (nullptr == component) return; // in case of an empty string, return if (*component == 0) return; // if the @a component is absolute, simply // use it if (isAbsolute(component)) { m_path = component; return; } if (Url::isValid(m_path.c_str())) { const char * newPath = svn_path_url_add_component(m_path.c_str(), component, pool); m_path = newPath; } else { svn_stringbuf_t * pathStringbuf = svn_stringbuf_create(m_path.c_str(), pool); svn_path_add_component(pathStringbuf, component); m_path = pathStringbuf->data; } } void Path::addComponent(const std::string & component) { addComponent(component.c_str()); } void Path::split(std::string & dirpath, std::string & basename) const { Pool pool; const char * cdirpath; const char * cbasename; svn_path_split(m_path.c_str(), &cdirpath, &cbasename, pool); dirpath = cdirpath; basename = cbasename; } void Path::split(std::string & dir, std::string & filename, std::string & ext) const { std::string basename; // first split path into dir and filename+ext split(dir, basename); // next search for last . size_t pos = basename.find_last_of("."); if (pos == std::string::npos) { filename = basename; ext = ""; } else { filename = basename.substr(0, pos); ext = basename.substr(pos); } } std::string Path::basename() const { std::string dir; std::string filename; split(dir, filename); return filename; } std::string Path::dirpath() const { std::string dir; std::string filename; split(dir, filename); return dir; } std::string Path::substr(const size_t count) const { if (m_path.length() > count) return m_path.substr(count); else return ""; } std::string Path::unescape() const { return svn::Url::unescape(m_path.c_str()); } /* =================================================================== * The next two Fixed_* functions are copies of the APR * apr_temp_dir_get functionality with a fix applied. * This should turn up in APR release 0.9.5 or 1.0, but * for now is reproduced here. * * TODO: Remove this section! */ #include "apr_env.h" #define test_tempdir Fixed_test_tempdir #define apr_temp_dir_get Fixed_apr_temp_dir_get static char global_temp_dir[APR_PATH_MAX+1] = { 0 }; /* Try to open a temporary file in the temporary dir, write to it, and then close it. */ static int Fixed_test_tempdir(const char *temp_dir, apr_pool_t *p) { apr_file_t *dummy_file; // This is the only actual fix - adding the ".XXXXXX"! const char *path = apr_pstrcat(p, temp_dir, "/apr-tmp.XXXXXX", NULL); if (apr_file_mktemp(&dummy_file, (char *)path, 0, p) == APR_SUCCESS) { if (apr_file_putc('!', dummy_file) == APR_SUCCESS) { if (apr_file_close(dummy_file) == APR_SUCCESS) { apr_file_remove(path, p); return 1; } } } return 0; } static apr_status_t Fixed_apr_temp_dir_get(const char **temp_dir, apr_pool_t *p) { apr_status_t apr_err; const char *try_dirs[] = { "/tmp", "/usr/tmp", "/var/tmp" }; const char *try_envs[] = { "TMP", "TEMP", "TMPDIR" }; char *cwd; size_t i; /* Our goal is to find a temporary directory suitable for writing into. We'll only pay the price once if we're successful -- we cache our successful find. Here's the order in which we'll try various paths: $TMP $TEMP $TMPDIR "/tmp" "/var/tmp" "/usr/tmp" `pwd` NOTE: This algorithm is basically the same one used by Python 2.2's tempfile.py module. */ /* Try the environment first. */ for (i = 0; i < (sizeof(try_envs) / sizeof(const char *)); i++) { char *value; apr_err = apr_env_get(&value, try_envs[i], p); if ((apr_err == APR_SUCCESS) && value) { apr_size_t len = strlen(value); if (len && (len < APR_PATH_MAX) && test_tempdir(value, p)) { memcpy(global_temp_dir, value, len + 1); goto end; } } } /* Next, try a set of hard-coded paths. */ for (i = 0; i < (sizeof(try_dirs) / sizeof(const char *)); i++) { if (test_tempdir(try_dirs[i], p)) { memcpy(global_temp_dir, try_dirs[i], strlen(try_dirs[i]) + 1); goto end; } } /* Finally, try the current working directory. */ if (APR_SUCCESS == apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, p)) { if (test_tempdir(cwd, p)) { memcpy(global_temp_dir, cwd, strlen(cwd) + 1); goto end; } } end: if (global_temp_dir[0]) { *temp_dir = apr_pstrdup(p, global_temp_dir); return APR_SUCCESS; } return APR_EGENERAL; } /* =================================================================== * End of inserted fixed APR code */ Path Path::getTempDir() { - const char * tempdir = NULL; + const char * tempdir = nullptr; Pool pool; if (apr_temp_dir_get(&tempdir, pool) != APR_SUCCESS) { - tempdir = NULL; + tempdir = nullptr; } return tempdir; } size_t Path::length() const { return m_path.length(); } std::string Path::native() const { if (m_pathIsUrl) { // this converts something like // http://foo/my%20location // to // http://foo/my location return Url::unescape(m_path.c_str()); } else { // On Windows, p://foo/bar will be converted to p:\foo\bar Pool pool; return svn_path_local_style(m_path.c_str(), pool); } } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/property.cpp b/plugins/subversion/kdevsvncpp/property.cpp index 7602b4ce38..dbd9107ada 100644 --- a/plugins/subversion/kdevsvncpp/property.cpp +++ b/plugins/subversion/kdevsvncpp/property.cpp @@ -1,171 +1,171 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // subversion api #include "svn_client.h" //#include "svn_utf.h" // svncpp #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/property.hpp" #include "kdevsvncpp/revision.hpp" #include "m_check.hpp" namespace svn { PropertyEntry::PropertyEntry(const char * name, const char * value) { this->name = name; this->value = value; } Property::Property(Context * context, const Path & path) : m_context(context), m_path(path) { list(); } Property::~Property() { } void Property::list() { Pool pool; Revision revision; m_entries.clear(); apr_array_header_t * props; svn_error_t * error = svn_client_proplist(&props, m_path.c_str(), revision, false, /* recurse */ *m_context, pool); - if (error != NULL) + if (error != nullptr) { throw ClientException(error); } for (int j = 0; j < props->nelts; ++j) { svn_client_proplist_item_t *item = ((svn_client_proplist_item_t **)props->elts)[j]; apr_hash_index_t *hi; for (hi = apr_hash_first(pool, item->prop_hash); hi; hi = apr_hash_next(hi)) { const void *key; void *val; - apr_hash_this(hi, &key, NULL, &val); + apr_hash_this(hi, &key, nullptr, &val); m_entries.push_back(PropertyEntry( (const char *)key, getValue((const char *)key).c_str())); } } } std::string Property::getValue(const char * name) { Pool pool; Revision revision; apr_hash_t *props; svn_client_propget(&props, name, m_path.c_str(), revision, false, // recurse *m_context, pool); apr_hash_index_t *hi; hi = apr_hash_first(pool, props); if (!hi) { return ""; } const void *key; void *val; const svn_string_t *propval; - apr_hash_this(hi, &key, NULL, &val); + apr_hash_this(hi, &key, nullptr, &val); propval = (const svn_string_t *)val; return propval->data; } void Property::set(const char * name, const char * value) { Pool pool; const svn_string_t * propval = svn_string_create((const char *) value, pool); bool recurse = false; bool skip_checks = false; svn_error_t * error = svn_client_propset2(name, propval, m_path.c_str(), recurse, skip_checks, *m_context, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Property::remove(const char * name) { Pool pool; svn_error_t * error = svn_client_propset(name, - NULL, // value = NULL + nullptr, // value = NULL m_path.c_str(), false, //dont recurse pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/status.cpp b/plugins/subversion/kdevsvncpp/status.cpp index 5deac84da9..5038f52921 100644 --- a/plugins/subversion/kdevsvncpp/status.cpp +++ b/plugins/subversion/kdevsvncpp/status.cpp @@ -1,238 +1,238 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // stl #include "kdevsvncpp/string_wrapper.hpp" // svncpp #include "kdevsvncpp/status.hpp" namespace svn { struct Status::Data { svn_wc_status2_t *status; std::string path; Pool pool; bool isVersioned; Data(const char * path_, const svn_wc_status2_t * status_) - : status(0), path("") + : status(nullptr), path("") { - if (path_ != 0) + if (path_ != nullptr) path = path_; - if (status_ != 0) + if (status_ != nullptr) { status = svn_wc_dup_status2( const_cast(status_), pool); isVersioned = status_->text_status > svn_wc_status_unversioned; } } Data(const Data * src) - : status(0), path(src->path) + : status(nullptr), path(src->path) { - if (src->status != 0) + if (src->status != nullptr) { status = svn_wc_dup_status2(src->status, pool); switch (status->text_status) { case svn_wc_status_none: case svn_wc_status_unversioned: case svn_wc_status_ignored: case svn_wc_status_obstructed: isVersioned = false; break; default: isVersioned = true; } } } }; Status::Status(const char * path, const svn_wc_status2_t * status) : m(new Data(path, status)) { } Status::Status(const Status & src) : m(new Data(src.m)) { } Status::~Status() { delete m; } const char * Status::path() const { return m->path.c_str(); } const Entry Status::entry() const { - if (0 == m->status) + if (nullptr == m->status) return Entry(); return Entry(m->status->entry); } svn_wc_status_kind Status::textStatus() const { return m->status->text_status; } svn_wc_status_kind Status::propStatus() const { return m->status->prop_status; } bool Status::isVersioned() const { return m->isVersioned; } bool Status::isCopied() const { return m->status->copied != 0; } bool Status::isSwitched() const { return m->status->switched != 0; } svn_wc_status_kind Status::reposTextStatus() const { return m->status->repos_text_status; } svn_wc_status_kind Status::reposPropStatus() const { return m->status->repos_prop_status; } bool Status::isLocked() const { - if (m->status->repos_lock && (m->status->repos_lock->token != 0)) + if (m->status->repos_lock && (m->status->repos_lock->token != nullptr)) return true; else if (m->status->entry) - return m->status->entry->lock_token != 0; + return m->status->entry->lock_token != nullptr; else return false; } bool Status::isRepLock() const { - if (m->status->entry && (m->status->entry->lock_token != 0)) + if (m->status->entry && (m->status->entry->lock_token != nullptr)) return false; - else if (m->status->repos_lock && (m->status->repos_lock->token != 0)) + else if (m->status->repos_lock && (m->status->repos_lock->token != nullptr)) return true; else return false; } const char * Status::lockToken() const { - if (m->status->repos_lock && m->status->repos_lock->token != 0) + if (m->status->repos_lock && m->status->repos_lock->token != nullptr) return m->status->repos_lock->token; else if (m->status->entry) return m->status->entry->lock_token; else return ""; } const char * Status::lockOwner() const { - if (m->status->repos_lock && m->status->repos_lock->token != 0) + if (m->status->repos_lock && m->status->repos_lock->token != nullptr) return m->status->repos_lock->owner; else if (m->status->entry) return m->status->entry->lock_owner; else return ""; } const char * Status::lockComment() const { - if (m->status->repos_lock && m->status->repos_lock->token != 0) + if (m->status->repos_lock && m->status->repos_lock->token != nullptr) return m->status->repos_lock->comment; else if (m->status->entry) return m->status->entry->lock_comment; else return ""; } apr_time_t Status::lockCreationDate() const { - if (m->status->repos_lock && m->status->repos_lock->token != 0) + if (m->status->repos_lock && m->status->repos_lock->token != nullptr) return m->status->repos_lock->creation_date; else if (m->status->entry) return m->status->entry->lock_creation_date; else return 0; } Status & Status::operator=(const Status & src) { if (this != &src) { delete m; m = new Data(src.m); } return *this; } bool Status::isSet() const { return m->path.length() > 0; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/targets.cpp b/plugins/subversion/kdevsvncpp/targets.cpp index 02b512ef5b..52c58c9a09 100644 --- a/plugins/subversion/kdevsvncpp/targets.cpp +++ b/plugins/subversion/kdevsvncpp/targets.cpp @@ -1,148 +1,148 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // subversion api #include "svn_types.h" // apr api #include "apr_pools.h" #include "apr_strings.h" // svncpp #include "kdevsvncpp/targets.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/pool.hpp" namespace svn { Targets::Targets(const PathVector & targets) { m_targets = targets; } Targets::Targets(const apr_array_header_t * apr_targets) { int i; m_targets.clear(); m_targets.reserve(apr_targets->nelts); for (i = 0; i < apr_targets->nelts; i++) { const char ** target = &APR_ARRAY_IDX(apr_targets, i, const char *); m_targets.push_back(Path(*target)); } } Targets::Targets(const Targets & targets) { m_targets = targets.targets(); } Targets::Targets(const char * target) { - if (target != 0) + if (target != nullptr) { m_targets.push_back(target); } } Targets::~Targets() { } const apr_array_header_t * Targets::array(const Pool & pool) const { PathVector::const_iterator it; apr_pool_t *apr_pool = pool.pool(); apr_array_header_t *apr_targets = apr_array_make(apr_pool, m_targets.size(), sizeof(const char *)); for (it = m_targets.begin(); it != m_targets.end(); it++) { const Path &path = *it; const char * target = apr_pstrdup(apr_pool, path.c_str()); (*((const char **) apr_array_push(apr_targets))) = target; } return apr_targets; } const PathVector & Targets::targets() const { return m_targets; } size_t Targets::size() const { return m_targets.size(); } const Path Targets::target() const { if (m_targets.size() > 0) { return m_targets[0]; } else { return ""; } } void Targets::push_back(const Path & path) { m_targets.push_back(path); } void Targets::clear() { m_targets.clear(); } void Targets::reserve(size_t size) { m_targets.reserve(size); } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/wc.cpp b/plugins/subversion/kdevsvncpp/wc.cpp index b580b131ae..11b0a24bde 100644 --- a/plugins/subversion/kdevsvncpp/wc.cpp +++ b/plugins/subversion/kdevsvncpp/wc.cpp @@ -1,106 +1,106 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // subversion api #include "svn_wc.h" // svncpp #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/pool.hpp" #include "kdevsvncpp/wc.hpp" namespace svn { bool Wc::checkWc(const char * dir) { Path path(dir); return Wc::checkWc(path); } bool Wc::checkWc(const Path & dir) { Pool pool; int wc; svn_error_t * error = svn_wc_check_wc(dir.c_str(), &wc, pool); - if ((error != NULL) || (wc == 0)) + if ((error != nullptr) || (wc == 0)) { return false; } return true; } void Wc::ensureAdm(const char * dir, const char *uuid, const char * url, const Revision & revision) { Pool pool; Path dirPath(dir); Path urlPath(url); svn_error_t * error = svn_wc_ensure_adm(dirPath.c_str(), // path uuid, // UUID urlPath.c_str(), // url revision.revnum(), // revision pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } void Wc::setAdmDir(const char * dir) { Pool pool; svn_error_t * error = svn_wc_set_adm_dir(dir, pool); - if (error != NULL) + if (error != nullptr) throw ClientException(error); } bool Wc::isAdmDir(const char * name) { Pool pool; return 0 != svn_wc_is_adm_dir(name, pool); } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvnplugin.cpp b/plugins/subversion/kdevsvnplugin.cpp index 9d4cc39525..00d201d24b 100644 --- a/plugins/subversion/kdevsvnplugin.cpp +++ b/plugins/subversion/kdevsvnplugin.cpp @@ -1,531 +1,531 @@ /*************************************************************************** * Copyright 2007 Dukju Ahn * * Copyright 2008 Andreas Pakulat * * * * 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. * * * ***************************************************************************/ #include "kdevsvnplugin.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 #include "kdevsvncpp/apr.hpp" #include "svncommitjob.h" #include "svnstatusjob.h" #include "svnaddjob.h" #include "svnrevertjob.h" #include "svnremovejob.h" #include "svnupdatejob.h" #include "svninfojob.h" #include "svndiffjob.h" #include "svncopyjob.h" #include "svnmovejob.h" #include "svnlogjob.h" #include "svnblamejob.h" #include "svnimportjob.h" #include "svncheckoutjob.h" #include "svnimportmetadatawidget.h" #include "svncheckoutmetadatawidget.h" #include #include #include "svnlocationwidget.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_SVN, "kdevplatform.plugins.svn") K_PLUGIN_FACTORY_WITH_JSON(KDevSvnFactory, "kdevsubversion.json", registerPlugin();) KDevSvnPlugin::KDevSvnPlugin(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevsubversion"), parent) , m_common(new KDevelop::VcsPluginHelper(this, this)) - , copy_action( 0 ) - , move_action( 0 ) + , copy_action( nullptr ) + , move_action( nullptr ) , m_jobQueue(new ThreadWeaver::Queue(this)) { KDEV_USE_EXTENSION_INTERFACE(KDevelop::IBasicVersionControl) KDEV_USE_EXTENSION_INTERFACE(KDevelop::ICentralizedVersionControl) qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } KDevSvnPlugin::~KDevSvnPlugin() { } bool KDevSvnPlugin::isVersionControlled(const QUrl &localLocation) { ///TODO: also check this in the other functions? if (!localLocation.isValid()) { return false; } SvnInfoJob* job = new SvnInfoJob(this); job->setLocation(localLocation); if (job->exec()) { QVariant result = job->fetchResults(); if (result.isValid()) { SvnInfoHolder h = result.value(); return !h.name.isEmpty(); } } else { qCDebug(PLUGIN_SVN) << "Couldn't execute job"; } return false; } KDevelop::VcsJob* KDevSvnPlugin::repositoryLocation(const QUrl &localLocation) { SvnInfoJob* job = new SvnInfoJob(this); job->setLocation(localLocation); job->setProvideInformation(SvnInfoJob::RepoUrlOnly); return job; } KDevelop::VcsJob* KDevSvnPlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode mode) { SvnStatusJob* job = new SvnStatusJob(this); job->setLocations(localLocations); job->setRecursive((mode == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnAddJob* job = new SvnAddJob(this); job->setLocations(localLocations); job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::remove(const QList& localLocations) { SvnRemoveJob* job = new SvnRemoveJob(this); job->setLocations(localLocations); return job; } KDevelop::VcsJob* KDevSvnPlugin::edit(const QUrl& /*localLocation*/) { - return 0; + return nullptr; } KDevelop::VcsJob* KDevSvnPlugin::unedit(const QUrl& /*localLocation*/) { - return 0; + return nullptr; } KDevelop::VcsJob* KDevSvnPlugin::localRevision(const QUrl &localLocation, KDevelop::VcsRevision::RevisionType type) { SvnInfoJob* job = new SvnInfoJob(this); job->setLocation(localLocation); job->setProvideInformation(SvnInfoJob::RevisionOnly); job->setProvideRevisionType(type); return job; } KDevelop::VcsJob* KDevSvnPlugin::copy(const QUrl &localLocationSrc, const QUrl& localLocationDstn) { SvnCopyJob* job = new SvnCopyJob(this); job->setSourceLocation(localLocationSrc); job->setDestinationLocation(localLocationDstn); return job; } KDevelop::VcsJob* KDevSvnPlugin::move(const QUrl &localLocationSrc, const QUrl& localLocationDst) { SvnMoveJob* job = new SvnMoveJob(this); job->setSourceLocation(localLocationSrc); job->setDestinationLocation(localLocationDst); return job; } KDevelop::VcsJob* KDevSvnPlugin::revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnRevertJob* job = new SvnRevertJob(this); job->setLocations(localLocations); job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::update(const QList& localLocations, const KDevelop::VcsRevision& rev, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnUpdateJob* job = new SvnUpdateJob(this); job->setLocations(localLocations); job->setRevision(rev); job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnCommitJob* job = new SvnCommitJob(this); qCDebug(PLUGIN_SVN) << "Committing locations:" << localLocations << endl; job->setUrls(localLocations); job->setCommitMessage(message) ; job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::diff(const QUrl &fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type diffType, KDevelop::IBasicVersionControl::RecursionMode recurse) { KDevelop::VcsLocation loc(fileOrDirectory); return diff2(loc, loc, srcRevision, dstRevision, diffType, recurse); } KDevelop::VcsJob* KDevSvnPlugin::diff2(const KDevelop::VcsLocation& src, const KDevelop::VcsLocation& dst, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type diffType, KDevelop::IBasicVersionControl::RecursionMode recurse) { SvnDiffJob* job = new SvnDiffJob(this); job->setSource(src); job->setDestination(dst); job->setSrcRevision(srcRevision); job->setDstRevision(dstRevision); job->setDiffType(diffType); job->setRecursive((recurse == KDevelop::IBasicVersionControl::Recursive)); return job; } KDevelop::VcsJob* KDevSvnPlugin::log(const QUrl &localLocation, const KDevelop::VcsRevision& rev, unsigned long limit) { SvnLogJob* job = new SvnLogJob(this); job->setLocation(localLocation); job->setStartRevision(rev); job->setLimit(limit); return job; } KDevelop::VcsJob* KDevSvnPlugin::log(const QUrl &localLocation, const KDevelop::VcsRevision& startRev, const KDevelop::VcsRevision& endRev) { SvnLogJob* job = new SvnLogJob(this); job->setLocation(localLocation); job->setStartRevision(startRev); job->setEndRevision(endRev); return job; } KDevelop::VcsJob* KDevSvnPlugin::annotate(const QUrl &localLocation, const KDevelop::VcsRevision& rev) { SvnBlameJob* job = new SvnBlameJob(this); job->setLocation(localLocation); job->setEndRevision(rev); return job; } KDevelop::VcsJob* KDevSvnPlugin::merge(const KDevelop::VcsLocation& localOrRepoLocationSrc, const KDevelop::VcsLocation& localOrRepoLocationDst, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, const QUrl &localLocation) { // TODO implement merge Q_UNUSED(localOrRepoLocationSrc) Q_UNUSED(localOrRepoLocationDst) Q_UNUSED(srcRevision) Q_UNUSED(dstRevision) Q_UNUSED(localLocation) - return 0; + return nullptr; } KDevelop::VcsJob* KDevSvnPlugin::resolve(const QList& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { - return 0; + return nullptr; } KDevelop::VcsJob* KDevSvnPlugin::import(const QString & commitMessage, const QUrl &sourceDirectory, const KDevelop::VcsLocation & destinationRepository) { SvnImportJob* job = new SvnImportJob(this); job->setMapping(sourceDirectory, destinationRepository); job->setMessage(commitMessage); return job; } KDevelop::VcsJob* KDevSvnPlugin::createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl &destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion) { SvnCheckoutJob* job = new SvnCheckoutJob(this); job->setMapping(sourceRepository, destinationDirectory, recursion); return job; } KDevelop::ContextMenuExtension KDevSvnPlugin::contextMenuExtension(KDevelop::Context* context) { m_common->setupFromContext(context); const QList & ctxUrlList = m_common->contextUrlList(); bool hasVersionControlledEntries = false; foreach(const QUrl &url, ctxUrlList) { if (isVersionControlled(url) || isVersionControlled(KIO::upUrl(url))) { hasVersionControlledEntries = true; break; } } qCDebug(PLUGIN_SVN) << "version controlled?" << hasVersionControlledEntries; if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context); QMenu* svnmenu= m_common->commonActions(); svnmenu->addSeparator(); if( !copy_action ) { copy_action = new QAction(i18n("Copy..."), this); connect(copy_action, SIGNAL(triggered()), this, SLOT(ctxCopy())); } svnmenu->addAction(copy_action); if( !move_action ) { move_action = new QAction(i18n("Move..."), this); connect(move_action, SIGNAL(triggered()), this, SLOT(ctxMove())); } svnmenu->addAction(move_action); KDevelop::ContextMenuExtension menuExt; menuExt.addAction(KDevelop::ContextMenuExtension::VcsGroup, svnmenu->menuAction()); return menuExt; } void KDevSvnPlugin::ctxInfo() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { - KMessageBox::error(0, i18n("Please select only one item for this operation")); + KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } } void KDevSvnPlugin::ctxStatus() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() > 1) { - KMessageBox::error(0, i18n("Please select only one item for this operation")); + KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } } void KDevSvnPlugin::ctxCopy() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() > 1) { - KMessageBox::error(0, i18n("Please select only one item for this operation")); + KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } QUrl source = ctxUrlList.first(); if (source.isLocalFile()) { QUrl dir = source; bool isFile = QFileInfo(source.toLocalFile()).isFile(); if (isFile) { dir = dir.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash); } - KUrlRequesterDialog dlg(dir, i18n("Destination file/directory"), 0); + KUrlRequesterDialog dlg(dir, i18n("Destination file/directory"), nullptr); if (isFile) { dlg.urlRequester()->setMode(KFile::File | KFile::Directory | KFile::LocalOnly); } else { dlg.urlRequester()->setMode(KFile::Directory | KFile::LocalOnly); } if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(copy(source, dlg.selectedUrl())); } } else { - KMessageBox::error(0, i18n("Copying only works on local files")); + KMessageBox::error(nullptr, i18n("Copying only works on local files")); return; } } void KDevSvnPlugin::ctxMove() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { - KMessageBox::error(0, i18n("Please select only one item for this operation")); + KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } QUrl source = ctxUrlList.first(); if (source.isLocalFile()) { QUrl dir = source; bool isFile = QFileInfo(source.toLocalFile()).isFile(); if (isFile) { dir = source.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash); } - KUrlRequesterDialog dlg(dir, i18n("Destination file/directory"), 0); + KUrlRequesterDialog dlg(dir, i18n("Destination file/directory"), nullptr); if (isFile) { dlg.urlRequester()->setMode(KFile::File | KFile::Directory | KFile::LocalOnly); } else { dlg.urlRequester()->setMode(KFile::Directory | KFile::LocalOnly); } if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(move(source, dlg.selectedUrl())); } } else { - KMessageBox::error(0, i18n("Moving only works on local files/dirs")); + KMessageBox::error(nullptr, i18n("Moving only works on local files/dirs")); return; } } void KDevSvnPlugin::ctxCat() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { - KMessageBox::error(0, i18n("Please select only one item for this operation")); + KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } } QString KDevSvnPlugin::name() const { return i18n("Subversion"); } KDevelop::VcsImportMetadataWidget* KDevSvnPlugin::createImportMetadataWidget(QWidget* parent) { return new SvnImportMetadataWidget(parent); } void KDevSvnPlugin::ctxImport() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { - KMessageBox::error(0, i18n("Please select only one item for this operation")); + KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } QDialog dlg; dlg.setWindowTitle(i18n("Import into Subversion repository")); SvnImportMetadataWidget* widget = new SvnImportMetadataWidget(&dlg); widget->setSourceLocation(KDevelop::VcsLocation(ctxUrlList.first())); widget->setSourceLocationEditable(false); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto layout = new QVBoxLayout(); dlg.setLayout(layout); layout->addWidget(widget); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(import(widget->message(), widget->source(), widget->destination())); } } void KDevSvnPlugin::ctxCheckout() { QList const & ctxUrlList = m_common->contextUrlList(); if (ctxUrlList.count() != 1) { - KMessageBox::error(0, i18n("Please select only one item for this operation")); + KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return; } QDialog dlg; dlg.setWindowTitle(i18n("Checkout from Subversion repository")); SvnCheckoutMetadataWidget* widget = new SvnCheckoutMetadataWidget(&dlg); QUrl tmp = KIO::upUrl(ctxUrlList.first()); widget->setDestinationLocation(tmp); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto layout = new QVBoxLayout(); dlg.setLayout(layout); layout->addWidget(widget); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); if (dlg.exec() == QDialog::Accepted) { KDevelop::ICore::self()->runController()->registerJob(createWorkingCopy(widget->source(), widget->destination(), widget->recursionMode())); } } KDevelop::VcsLocationWidget* KDevSvnPlugin::vcsLocation(QWidget* parent) const { return new SvnLocationWidget(parent); } ThreadWeaver::Queue* KDevSvnPlugin::jobQueue() const { return m_jobQueue; } #include "kdevsvnplugin.moc" diff --git a/plugins/subversion/svnclient.cpp b/plugins/subversion/svnclient.cpp index 1deba6d64e..5889678d4f 100644 --- a/plugins/subversion/svnclient.cpp +++ b/plugins/subversion/svnclient.cpp @@ -1,335 +1,335 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * Parts of the file are copied from the RapidSvn C++ library * * Copyright (c) 2002-2006 The RapidSvn Group. All rights reserved. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svnclient.h" #include #include #include extern "C" { #include #include } #include "kdevsvncpp/targets.hpp" #include "kdevsvncpp/pool.hpp" #include #include void fail (apr_pool_t *pool, apr_status_t status, const char *fmt, ...) { va_list ap; char *msg; svn_error_t * error; va_start (ap, fmt); msg = apr_pvsprintf (pool, fmt, ap); va_end (ap); - error = svn_error_create (status, NULL, msg); + error = svn_error_create (status, nullptr, msg); throw svn::ClientException (error); } void cleanup( apr_file_t* outfile, const char* outfileName, apr_file_t* errfile, const char* errfileName, const svn::Pool& pool ) { - if( outfile != 0 ) + if( outfile != nullptr ) { apr_file_close( outfile ); } - if( errfile != 0 ) + if( errfile != nullptr ) { apr_file_close( outfile ); } - if( outfileName != 0 ) + if( outfileName != nullptr ) { svn_error_clear( svn_io_remove_file ( outfileName, pool ) ); } - if( errfileName != 0 ) + if( errfileName != nullptr ) { svn_error_clear( svn_io_remove_file ( errfileName, pool ) ); } } SvnClient::SvnClient( svn::Context* ctx ) - : QObject(0), svn::Client( ctx ), m_ctxt( ctx ) + : QObject(nullptr), svn::Client( ctx ), m_ctxt( ctx ) { } QString SvnClient::diff( const svn::Path& src, const svn::Revision& srcRev, const svn::Path& dst, const svn::Revision& dstRev, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted, const bool ignoreContentType ) throw (svn::ClientException) { svn::Pool pool; // null options apr_array_header_t *options = svn_cstring_split( "", "\t\r\n", false, pool ); svn_error_t* error; - const char* outfileName = 0; - apr_file_t* outfile = 0; - const char* errfileName = 0; - apr_file_t* errfile = 0; + const char* outfileName = nullptr; + apr_file_t* outfile = nullptr; + const char* errfileName = nullptr; + apr_file_t* errfile = nullptr; QByteArray ba = QString(QStandardPaths::writableLocation(QStandardPaths::TempLocation)+"/kdevelop_svn_diff" ).toUtf8(); error = svn_io_open_unique_file( &outfile, &outfileName, ba.data(), ".tmp", false, pool ); - if( error != 0 ) + if( error != nullptr ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_io_open_unique_file( &errfile, &errfileName, ba.data(), ".tmp", false, pool ); - if( error != 0 ) + if( error != nullptr ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_client_diff3( options, src.c_str(), srcRev.revision(), dst.c_str(), dstRev.revision(), recurse, ignoreAncestry, noDiffDeleted, ignoreContentType, "UTF-8", outfile, errfile, m_ctxt->ctx(), pool ); if ( error ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException(error); } // then we reopen outfile for reading apr_status_t aprstatus = apr_file_close (outfile); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to close '%s'", outfileName); } aprstatus = apr_file_open (&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to open '%s'", outfileName); } svn_stringbuf_t* stringbuf; // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile (&stringbuf, outfile, pool); - if (error != NULL) + if (error != nullptr) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); throw svn::ClientException (error); } ::cleanup (outfile, outfileName, errfile, errfileName, pool); return QString::fromUtf8( stringbuf->data ); } QString SvnClient::diff( const svn::Path& src, const svn::Revision& pegRev, const svn::Revision& srcRev, const svn::Revision& dstRev, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted, const bool ignoreContentType ) throw (svn::ClientException) { svn::Pool pool; // null options apr_array_header_t *options = svn_cstring_split( "", "\t\r\n", false, pool ); svn_error_t* error; - const char* outfileName = 0; - apr_file_t* outfile = 0; - const char* errfileName = 0; - apr_file_t* errfile = 0; + const char* outfileName = nullptr; + apr_file_t* outfile = nullptr; + const char* errfileName = nullptr; + apr_file_t* errfile = nullptr; QByteArray ba = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toUtf8(); error = svn_io_open_unique_file( &outfile, &outfileName, ba.data(), ".tmp", false, pool ); - if( error != 0 ) + if( error != nullptr ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_io_open_unique_file( &errfile, &errfileName, ba.data(), ".tmp", false, pool ); - if( error != 0 ) + if( error != nullptr ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException( error ); } error = svn_client_diff_peg3( options, src.c_str(), pegRev.revision(), srcRev.revision(), dstRev.revision(), recurse, ignoreAncestry, noDiffDeleted, ignoreContentType, "UTF-8", outfile, errfile, m_ctxt->ctx(), pool ); if ( error ) { ::cleanup( outfile, outfileName, errfile, errfileName, pool ); throw svn::ClientException(error); } // then we reopen outfile for reading apr_status_t aprstatus = apr_file_close (outfile); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to close '%s'", outfileName); } aprstatus = apr_file_open (&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool); if (aprstatus) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); ::fail (pool, aprstatus, "failed to open '%s'", outfileName); } svn_stringbuf_t* stringbuf; // now we can read the diff output from outfile and return that error = svn_stringbuf_from_aprfile (&stringbuf, outfile, pool); - if (error != NULL) + if (error != nullptr) { ::cleanup (outfile, outfileName, errfile, errfileName, pool); throw svn::ClientException(error); } ::cleanup (outfile, outfileName, errfile, errfileName, pool); return QString::fromUtf8( stringbuf->data ); } static svn_error_t * kdev_logReceiver (void *baton, apr_hash_t * changedPaths, svn_revnum_t rev, const char *author, const char *date, const char *msg, apr_pool_t * pool) { SvnClient* client = (SvnClient *) baton; KDevelop::VcsEvent ev; ev.setAuthor( QString::fromUtf8( author ) ); ev.setDate( QDateTime::fromString( QString::fromUtf8( date ), Qt::ISODate ) ); ev.setMessage( QString::fromUtf8( msg ) ); KDevelop::VcsRevision vcsrev; vcsrev.setRevisionValue( QVariant( qlonglong( rev ) ), KDevelop::VcsRevision::GlobalNumber ); ev.setRevision( vcsrev ); - if (changedPaths != NULL) + if (changedPaths != nullptr) { for (apr_hash_index_t *hi = apr_hash_first (pool, changedPaths); - hi != NULL; + hi != nullptr; hi = apr_hash_next (hi)) { char *path; void *val; - apr_hash_this (hi, (const void **)&path, NULL, &val); + apr_hash_this (hi, (const void **)&path, nullptr, &val); svn_log_changed_path_t *log_item = reinterpret_cast (val); KDevelop::VcsItemEvent iev; iev.setRepositoryLocation( QString::fromUtf8( path ) ); iev.setRepositoryCopySourceLocation( QString::fromUtf8( log_item->copyfrom_path ) ); KDevelop::VcsRevision irev; irev.setRevisionValue( QVariant( qlonglong( log_item->copyfrom_rev ) ), KDevelop::VcsRevision::GlobalNumber ); iev.setRepositoryCopySourceRevision( irev ); switch( log_item->action ) { case 'A': iev.setActions( KDevelop::VcsItemEvent::Added ); break; case 'M': iev.setActions( KDevelop::VcsItemEvent::Modified ); break; case 'D': iev.setActions( KDevelop::VcsItemEvent::Deleted ); break; case 'R': iev.setActions( KDevelop::VcsItemEvent::Replaced ); break; } auto items = ev.items(); items.append( iev ); ev.setItems( items ); } } client->emitLogEventReceived( ev ); - return NULL; + return nullptr; } void SvnClient::log( const char* path, const svn::Revision& start, const svn::Revision& end, int limit, bool discoverChangedPaths, bool strictNodeHistory ) throw (svn::ClientException) { svn::Pool pool; svn::Targets target(path); svn_error_t *error; error = svn_client_log2 ( target.array(pool), start.revision(), end.revision(), limit, discoverChangedPaths ? 1 : 0, strictNodeHistory ? 1 : 0, kdev_logReceiver, this, m_ctxt->ctx(), // client ctx pool); - if (error != NULL) + if (error != nullptr) { throw svn::ClientException (error); } } void SvnClient::emitLogEventReceived( const KDevelop::VcsEvent& ev ) { emit logEventReceived( ev ); } diff --git a/plugins/subversion/svninternaljobbase.cpp b/plugins/subversion/svninternaljobbase.cpp index 6c59837c90..65af2fc1a4 100644 --- a/plugins/subversion/svninternaljobbase.cpp +++ b/plugins/subversion/svninternaljobbase.cpp @@ -1,388 +1,388 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2007 Dukju Ahn * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svninternaljobbase.h" #include #include #include #include #include extern "C" { #include } #include #include #include "kdevsvncpp/context.hpp" #include "kdevsvncpp/apr.hpp" #include "kdevsvncpp/revision.hpp" SvnInternalJobBase::SvnInternalJobBase( SvnJobBase* parent ) : QObject(parent) , m_ctxt( new svn::Context() ) , m_guiSemaphore( 0 ) , m_mutex() , m_killMutex() , m_success( true ) , sendFirstDelta( false ) , killed( false ) { m_ctxt->setListener(this); } SvnInternalJobBase::~SvnInternalJobBase() { - m_ctxt->setListener(0); + m_ctxt->setListener(nullptr); delete m_ctxt; - m_ctxt = 0; + m_ctxt = nullptr; } void SvnInternalJobBase::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { emit started(); ThreadWeaver::Job::defaultBegin(self, thread); } void SvnInternalJobBase::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { ThreadWeaver::Job::defaultEnd(self, thread); if (!self->success()) { emit failed(); } emit done(); } bool SvnInternalJobBase::contextGetLogin( const std::string& realm, std::string& username, std::string& password, bool& maySave ) { emit needLogin( QString::fromUtf8( realm.c_str() ) ); m_guiSemaphore.acquire( 1 ); QMutexLocker l(&m_mutex); if( m_login_username.isEmpty() || m_login_password.isEmpty() ) return false; username = std::string( m_login_username.toUtf8() ); password = std::string( m_login_password.toUtf8() ); maySave = this->m_maySave; return true; } void SvnInternalJobBase::contextNotify( const char* path, svn_wc_notify_action_t action, svn_node_kind_t kind, const char* mimetype, svn_wc_notify_state_t contentState, svn_wc_notify_state_t propState, svn_revnum_t rev ) { QString notifyString; switch( action ){ case svn_wc_notify_add: notifyString = i18nc( "A file was marked to be added to svn", "Added %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_delete: notifyString = i18nc( "A file was marked for deletion from svn", "Deleted %1", QString::fromUtf8( path ) ); break; // various update notifications case svn_wc_notify_update_delete: notifyString = i18nc( "A file was deleted during an svn update operation", "Deleted %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_update_add: notifyString = i18nc( "A file was added during an svn update operation", "Added %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_update_update: /* If this is an inoperative dir change, do no notification. An inoperative dir change is when a directory gets closed without any props having been changed. */ if (! ((kind == svn_node_dir) && ((propState == svn_wc_notify_state_inapplicable) || (propState == svn_wc_notify_state_unknown) || (propState == svn_wc_notify_state_unchanged)))) { if (kind == svn_node_file) { if (contentState == svn_wc_notify_state_conflicted) notifyString = QStringLiteral("Conflict On File"); else if (contentState == svn_wc_notify_state_merged) notifyString = QStringLiteral("File Merged"); else if (contentState == svn_wc_notify_state_changed) notifyString = QStringLiteral("File Updated"); } if (propState == svn_wc_notify_state_conflicted) notifyString += QLatin1String(" Conflict On Property"); else if (propState == svn_wc_notify_state_merged) notifyString += QLatin1String(" Properties Merged"); else if (propState == svn_wc_notify_state_changed) notifyString += QLatin1String(" Properties Updated"); else notifyString += ' '; if (! ((contentState == svn_wc_notify_state_unchanged || contentState == svn_wc_notify_state_unknown) && (propState == svn_wc_notify_state_unchanged || propState == svn_wc_notify_state_unknown))) notifyString += QStringLiteral( " " ) + QString::fromUtf8( path ); } break; case svn_wc_notify_update_completed: // The last notification in an update (including updates of externals). notifyString = i18n("Revision %1", rev ); break; case svn_wc_notify_update_external: notifyString = i18n("Updating externals: %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_status_completed: break; case svn_wc_notify_status_external: break; // various commit notifications case svn_wc_notify_commit_modified: notifyString = i18n( "Sending %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_commit_added: if( mimetype ){ notifyString = i18n( "Adding %1 using mimetype %2.", QString::fromUtf8( path ), mimetype ); } else { notifyString = i18n( "Adding %1.", QString::fromUtf8( path ) ); } break; case svn_wc_notify_commit_deleted: notifyString = i18n( "Deleting %1.", QString::fromUtf8( path ) ); break; case svn_wc_notify_commit_replaced: notifyString = i18n( "Replacing %1.", QString::fromUtf8( path ) ); break; case svn_wc_notify_commit_postfix_txdelta: if ( sendFirstDelta ) { sendFirstDelta = false; notifyString=i18n("Transmitting file data "); } else { notifyString='.'; } break; case svn_wc_notify_blame_revision: notifyString = i18n( "Blame finished for revision %1, path %2", rev, QString::fromUtf8( path ) ); break; case svn_wc_notify_revert: notifyString = i18n( "Reverted working copy %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_failed_revert: notifyString = i18n( "Reverting failed on working copy %1", QString::fromUtf8( path ) ); break; case svn_wc_notify_copy: notifyString = i18n( "Copied %1", QString::fromUtf8( path ) ); break; default: break; } emit showNotification( QString::fromUtf8( path ), notifyString ); } bool SvnInternalJobBase::contextCancel() { QMutexLocker lock( &m_killMutex ); return killed; } bool SvnInternalJobBase::contextGetLogMessage( std::string& msg ) { emit needCommitMessage(); m_guiSemaphore.acquire( 1 ); QMutexLocker l( &m_mutex ); QByteArray ba = m_commitMessage.toUtf8(); msg = std::string( ba.data() ); return true; } void SvnInternalJobBase::initBeforeRun() { connect( this, SIGNAL(needCommitMessage()), parent(), SLOT(askForCommitMessage()), Qt::QueuedConnection ); connect( this, SIGNAL(needLogin(QString)), parent(), SLOT(askForLogin(QString)), Qt::QueuedConnection ); connect( this, SIGNAL( needSslServerTrust( const QStringList&, const QString&, const QString&, const QString&, const QString&, const QString&, const QString& ) ), parent(), SLOT( askForSslServerTrust( const QStringList&, const QString&, const QString&, const QString&, const QString&, const QString&, const QString& ) ), Qt::QueuedConnection ); connect( this, SIGNAL(showNotification(QString,QString)), parent(), SLOT(showNotification(QString,QString)), Qt::QueuedConnection ); connect( this, SIGNAL(needSslClientCert(QString)), parent(), SLOT(askForSslClientCert(QString)), Qt::QueuedConnection ); connect( this, SIGNAL(needSslClientCertPassword(QString)), parent(), SLOT(askForSslClientCertPassword(QString)), Qt::QueuedConnection ); } svn::ContextListener::SslServerTrustAnswer SvnInternalJobBase::contextSslServerTrustPrompt( const svn::ContextListener::SslServerTrustData& data, apr_uint32_t& acceptedFailures ) { std::string host = data.hostname; std::string print = data.fingerprint; std::string from = data.validFrom; std::string until = data.validUntil; std::string issue = data.issuerDName; std::string realm = data.realm; acceptedFailures = data.failures; QStringList failures; if( data.failures & SVN_AUTH_SSL_NOTYETVALID ) { failures << i18n("Certificate is not yet valid."); } if( data.failures & SVN_AUTH_SSL_EXPIRED ) { failures << i18n("Certificate has expired."); } if( data.failures & SVN_AUTH_SSL_CNMISMATCH ) { failures << i18n("Certificate's CN (hostname) doesn't match the remote hostname."); } if( data.failures & SVN_AUTH_SSL_UNKNOWNCA ) { failures << i18n("Certificate authority is unknown."); } if( data.failures & SVN_AUTH_SSL_NOTYETVALID ) { failures << i18n("Other unknown error."); } emit needSslServerTrust( failures, QString::fromUtf8( host.c_str() ), QString::fromUtf8( print.c_str() ), QString::fromUtf8( from.c_str() ), QString::fromUtf8( until.c_str() ), QString::fromUtf8( issue.c_str() ), QString::fromUtf8( realm.c_str() ) ); m_guiSemaphore.acquire(1); QMutexLocker l(&m_mutex); return m_trustAnswer; } bool SvnInternalJobBase::contextSslClientCertPrompt( std::string& cert ) { emit needSslClientCert( QString::fromUtf8( cert.c_str() ) ); m_guiSemaphore.acquire( 1 ); return true; } bool SvnInternalJobBase::contextSslClientCertPwPrompt( std::string& pw, const std::string& realm, bool& maySave ) { Q_UNUSED(pw); Q_UNUSED(maySave); emit needSslClientCertPassword( QString::fromUtf8( realm.c_str() ) ); m_guiSemaphore.acquire( 1 ); return false; } bool SvnInternalJobBase::success() const { return m_success; } svn::Revision SvnInternalJobBase::createSvnCppRevisionFromVcsRevision( const KDevelop::VcsRevision& revision ) { svn::Revision rev; QVariant value = revision.revisionValue(); switch( revision.revisionType() ) { case KDevelop::VcsRevision::Special: { if( value.canConvert() ) { KDevelop::VcsRevision::RevisionSpecialType specialtype = value.value(); switch( specialtype ) { case KDevelop::VcsRevision::Head: rev = svn::Revision( svn::Revision::HEAD ); break; case KDevelop::VcsRevision::Working: rev = svn::Revision( svn::Revision::WORKING ); break; case KDevelop::VcsRevision::Base: rev = svn::Revision( svn::Revision::BASE ); break; case KDevelop::VcsRevision::Previous: rev = svn::Revision( svn_opt_revision_previous ); break; case KDevelop::VcsRevision::Start: rev = svn::Revision( svn::Revision::START ); break; default: break; } } break; } case KDevelop::VcsRevision::GlobalNumber: case KDevelop::VcsRevision::FileNumber: { bool ok; qlonglong number = value.toLongLong(&ok); if( ok ) { rev = svn::Revision( number ); } break; } case KDevelop::VcsRevision::Date: { QDateTime dt = value.toDateTime(); if( dt.isValid() ) { rev = svn::Revision( dt.toTime_t() ); } break; } default: break; } return rev; } void SvnInternalJobBase::setErrorMessage( const QString& msg ) { QMutexLocker lock( &m_mutex ); m_errorMessage = msg; } QString SvnInternalJobBase::errorMessage() const { QMutexLocker lock( &m_mutex ); return m_errorMessage; } void SvnInternalJobBase::kill() { QMutexLocker lock( &m_killMutex ); killed = true; } diff --git a/plugins/subversion/svnjobbase.cpp b/plugins/subversion/svnjobbase.cpp index cd2eb60df4..670b856115 100644 --- a/plugins/subversion/svnjobbase.cpp +++ b/plugins/subversion/svnjobbase.cpp @@ -1,212 +1,212 @@ /*************************************************************************** * Copyright 2007 Dukju Ahn * * * * 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. * * * ***************************************************************************/ #include "svnjobbase.h" #include #include #include #include #include #include #include #include #include #include #include "svninternaljobbase.h" #include "svnssldialog.h" SvnJobBase::SvnJobBase( KDevSvnPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity ) : VcsJob( parent, verbosity ), m_part( parent ), m_status( KDevelop::VcsJob::JobNotStarted ) { setCapabilities( KJob::Killable ); setTitle( QStringLiteral("Subversion") ); } SvnJobBase::~SvnJobBase() { } void SvnJobBase::startInternalJob() { auto job = internalJob(); connect( job, &SvnInternalJobBase::failed, this, &SvnJobBase::internalJobFailed, Qt::QueuedConnection ); connect( job, &SvnInternalJobBase::done, this, &SvnJobBase::internalJobDone, Qt::QueuedConnection ); connect( job, &SvnInternalJobBase::started, this, &SvnJobBase::internalJobStarted, Qt::QueuedConnection ); m_part->jobQueue()->stream() << ThreadWeaver::make_job_raw(job); } bool SvnJobBase::doKill() { internalJob()->kill(); m_status = VcsJob::JobCanceled; return true; } KDevelop::VcsJob::JobStatus SvnJobBase::status() const { return m_status; } void SvnJobBase::askForLogin( const QString& realm ) { qCDebug(PLUGIN_SVN) << "login"; - KPasswordDialog dlg( 0, KPasswordDialog::ShowUsernameLine | KPasswordDialog::ShowKeepPassword ); + KPasswordDialog dlg( nullptr, KPasswordDialog::ShowUsernameLine | KPasswordDialog::ShowKeepPassword ); dlg.setPrompt( i18n("Enter Login for: %1", realm ) ); dlg.exec(); internalJob()->m_login_username = dlg.username(); internalJob()->m_login_password = dlg.password(); internalJob()->m_maySave = dlg.keepPassword(); internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::showNotification( const QString& path, const QString& msg ) { Q_UNUSED(path); outputMessage(msg); } void SvnJobBase::askForCommitMessage() { qCDebug(PLUGIN_SVN) << "commit msg"; internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::askForSslServerTrust( const QStringList& failures, const QString& host, const QString& print, const QString& from, const QString& until, const QString& issuer, const QString& realm ) { qCDebug(PLUGIN_SVN) << "servertrust"; SvnSSLTrustDialog dlg; dlg.setCertInfos( host, print, from, until, issuer, realm, failures ); if( dlg.exec() == QDialog::Accepted ) { qCDebug(PLUGIN_SVN) << "accepted with:" << dlg.useTemporarily(); if( dlg.useTemporarily() ) { internalJob()->m_trustAnswer = svn::ContextListener::ACCEPT_TEMPORARILY; }else { internalJob()->m_trustAnswer = svn::ContextListener::ACCEPT_PERMANENTLY; } }else { qCDebug(PLUGIN_SVN) << "didn't accept"; internalJob()->m_trustAnswer = svn::ContextListener::DONT_ACCEPT; } internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::askForSslClientCert( const QString& realm ) { - KMessageBox::information( 0, realm ); + KMessageBox::information( nullptr, realm ); qCDebug(PLUGIN_SVN) << "clientrust"; internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::askForSslClientCertPassword( const QString& ) { qCDebug(PLUGIN_SVN) << "clientpw"; internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::internalJobStarted() { qCDebug(PLUGIN_SVN) << "job started" << static_cast(internalJob()); m_status = KDevelop::VcsJob::JobRunning; } void SvnJobBase::internalJobDone() { qCDebug(PLUGIN_SVN) << "job done" << internalJob(); if ( m_status == VcsJob::JobFailed ) { // see: https://bugs.kde.org/show_bug.cgi?id=273759 // this gets also called when the internal job failed // then the emit result in internalJobFailed might trigger // a nested event loop (i.e. error dialog) // during that the internalJobDone gets called and triggers // deleteLater and eventually deletes this job // => havoc // // catching this state here works but I don't like it personally... return; } outputMessage(i18n("Completed")); if( m_status != VcsJob::JobCanceled ) { m_status = KDevelop::VcsJob::JobSucceeded; } emitResult(); if( m_status == VcsJob::JobCanceled ) { deleteLater(); } } void SvnJobBase::internalJobFailed() { qCDebug(PLUGIN_SVN) << "job failed" << internalJob(); setError( 255 ); QString msg = internalJob()->errorMessage(); if( !msg.isEmpty() ) setErrorText( i18n( "Error executing Job:\n%1", msg ) ); outputMessage(errorText()); qCDebug(PLUGIN_SVN) << "Job failed"; if( m_status != VcsJob::JobCanceled ) { m_status = KDevelop::VcsJob::JobFailed; } emitResult(); if( m_status == VcsJob::JobCanceled ) { deleteLater(); } } KDevelop::IPlugin* SvnJobBase::vcsPlugin() const { return m_part; } void SvnJobBase::outputMessage(const QString& message) { if (!model()) return; if (verbosity() == KDevelop::OutputJob::Silent) return; QStandardItemModel *m = qobject_cast(model()); QStandardItem *previous = m->item(m->rowCount()-1); if (message == QLatin1String(".") && previous && previous->text().contains(QRegExp("\\.+"))) previous->setText(previous->text() + message); else m->appendRow(new QStandardItem(message)); KDevelop::IPlugin* i = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IOutputView")); if( i ) { KDevelop::IOutputView* view = i->extension(); if( view ) { view->raiseOutput( outputId() ); } } } diff --git a/plugins/subversion/tests/svnrecursiveadd.cpp b/plugins/subversion/tests/svnrecursiveadd.cpp index fb681609ec..c63c8f672f 100644 --- a/plugins/subversion/tests/svnrecursiveadd.cpp +++ b/plugins/subversion/tests/svnrecursiveadd.cpp @@ -1,160 +1,160 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Fabian Wiesel * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "svnrecursiveadd.h" #include #include #include #include #include #include #include #include #include #define PATHETIC // A little motivator to make things work right :) #if defined(PATHETIC) inline QString vcsTestDir0() { return QStringLiteral("testdir0"); } inline QString vcsTestDir1() { return QStringLiteral("testdir1"); } inline QString vcsTest_FileName0() { return QStringLiteral("foo"); } inline QString vcsTest_FileName1() { return QStringLiteral("bar"); } inline QString keywordText() { return QStringLiteral("text"); } #else inline QString vcsTestDir0() { return QStringLiteral("dvcs\t testdir"); } // Directory containing whitespaces inline QString vcsTestDir1() { return QStringLiteral("--help"); } // Starting with hyphen for command-line tools inline QString vcsTest_FileName0() { return QStringLiteral("foo\t bar"); } inline QString vcsTest_FileName1() { return QStringLiteral("--help"); } inline QString keywordText() { return QStringLiteral("Author:\nDate:\nCommit:\n------------------------------------------------------------------------\nr999999 | ehrman | 1989-11-09 18:53:00 +0100 (Thu, 09 Nov 1989) | 1 lines\nthe line\n"); } // Text containing keywords of the various vcs-programs #endif inline QString simpleText() { return QStringLiteral("It's foo!\n"); } inline QString simpleAltText() { return QStringLiteral("No, foo()! It's bar()!\n"); } #define VERBOSE #if defined(VERBOSE) #define TRACE(X) qDebug() << X #else #define TRACE(X) { line = line; } #endif using namespace KDevelop; void validatingExecJob(VcsJob* j, VcsJob::JobStatus status = VcsJob::JobSucceeded) { QVERIFY(j); // Print the commands in full, for easier bug location #if 0 if (QLatin1String(j->metaObject()->className()) == "DVcsJob") { qDebug() << "Command: \"" << ((DVcsJob*)j)->getChildproc()->program() << ((DVcsJob*)j)->getChildproc()->workingDirectory(); qDebug() << "Output: \"" << ((DVcsJob*)j)->output(); } #endif if (!j->exec()) { qDebug() << "ooops, no exec"; qDebug() << j->errorString(); // On error, wait for key in order to allow manual state inspection #if 0 char c; std::cin.read(&c, 1); #endif } QCOMPARE(j->status(), status); } void verifiedWrite(QUrl const & url, QString const & contents) { QFile f(url.path()); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream filecontents(&f); filecontents << contents; filecontents.flush(); f.flush(); } void fillWorkingDirectory(QString const & dirname) { QDir dir(dirname); //we start it after repoInit, so we still have empty dvcs repo QVERIFY(dir.mkdir(vcsTestDir0())); QVERIFY(dir.cd(vcsTestDir0())); QUrl file0 = QUrl::fromLocalFile(dir.absoluteFilePath(vcsTest_FileName0())); QVERIFY(dir.mkdir(vcsTestDir1())); QVERIFY(dir.cd(vcsTestDir1())); QUrl file1 = QUrl::fromLocalFile(dir.absoluteFilePath(vcsTest_FileName1())); verifiedWrite(file0, simpleText()); verifiedWrite(file1, keywordText()); } void SvnRecursiveAdd::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void SvnRecursiveAdd::cleanupTestCase() { TestCore::shutdown(); } void SvnRecursiveAdd::test() { QTemporaryDir reposDir; KProcess cmd; cmd.setWorkingDirectory(reposDir.path()); cmd << QStringLiteral("svnadmin") << QStringLiteral("create") << reposDir.path(); QCOMPARE(cmd.execute(10000), 0); QList plugins = Core::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IBasicVersionControl")); - IBasicVersionControl* vcs = NULL; + IBasicVersionControl* vcs = nullptr; foreach(IPlugin* p, plugins) { qDebug() << "checking plugin" << p; ICentralizedVersionControl* icentr = p->extension(); if (!icentr) continue; if (icentr->name() == QLatin1String("Subversion")) { vcs = icentr; break; } } qDebug() << "ok, got vcs" << vcs; QVERIFY(vcs); VcsLocation reposLoc; reposLoc.setRepositoryServer("file://" + reposDir.path()); QTemporaryDir checkoutDir; QUrl checkoutLoc = QUrl::fromLocalFile(checkoutDir.path()); qDebug() << "Checking out from " << reposLoc.repositoryServer() << " to " << checkoutLoc; qDebug() << "creating job"; VcsJob* job = vcs->createWorkingCopy( reposLoc, checkoutLoc ); validatingExecJob(job); qDebug() << "filling wc"; fillWorkingDirectory(checkoutDir.path()); QUrl addUrl = QUrl::fromLocalFile( checkoutDir.path() + '/' + vcsTestDir0() ); qDebug() << "Recursively adding files at " << addUrl; validatingExecJob(vcs->add({addUrl}, IBasicVersionControl::Recursive)); qDebug() << "Recursively reverting changes at " << addUrl; validatingExecJob(vcs->revert({addUrl}, IBasicVersionControl::Recursive)); } QTEST_MAIN(SvnRecursiveAdd) diff --git a/plugins/switchtobuddy/switchtobuddyplugin.cpp b/plugins/switchtobuddy/switchtobuddyplugin.cpp index 211a299f42..ab415b7251 100644 --- a/plugins/switchtobuddy/switchtobuddyplugin.cpp +++ b/plugins/switchtobuddy/switchtobuddyplugin.cpp @@ -1,319 +1,319 @@ /* * This file is part of KDevelop * Copyright 2012 André Stein * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "switchtobuddyplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_SWITCHTOBUDDY, "kdevplatform.plugins.switchtobuddy") using namespace KDevelop; namespace { KTextEditor::Cursor normalizeCursor(KTextEditor::Cursor c) { c.setColumn(0); return c; } ///Tries to find a definition for the declaration at given cursor-position and document-url. DUChain must be locked. Declaration* definitionForCursorDeclaration(const KTextEditor::Cursor& cursor, const QUrl& url) { QList topContexts = DUChain::self()->chainsForDocument(url); foreach (TopDUContext* ctx, topContexts) { Declaration* decl = DUChainUtils::declarationInLine(cursor, ctx); if (!decl) { continue; } if (auto definition = FunctionDefinition::definition(decl)) { return definition; } } - return 0; + return nullptr; } QString findSwitchCandidate(const QUrl& docUrl) { QMimeDatabase db; IBuddyDocumentFinder* finder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(docUrl).name()); if (finder) { // get the first entry that exists, use that as candidate foreach(const QUrl& buddyUrl, finder->getPotentialBuddies(docUrl)) { if (!QFile::exists(buddyUrl.toLocalFile())) { continue; } return buddyUrl.toLocalFile(); } } return QString(); } } K_PLUGIN_FACTORY_WITH_JSON(SwitchToBuddyPluginFactory, "kdevswitchtobuddy.json", registerPlugin(); ) SwitchToBuddyPlugin::SwitchToBuddyPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( QStringLiteral("kdevswitchtobuddy"), parent ) - , m_signalMapper(0) + , m_signalMapper(nullptr) { setXMLFile(QStringLiteral("kdevswitchtobuddy.rc")); } SwitchToBuddyPlugin::~SwitchToBuddyPlugin() { } ContextMenuExtension SwitchToBuddyPlugin::contextMenuExtension(Context* context) { EditorContext* ctx = dynamic_cast(context); if (!ctx) { return ContextMenuExtension(); } QUrl currentUrl = ctx->url(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(QMimeDatabase().mimeTypeForUrl(currentUrl).name()); if (!buddyFinder) return ContextMenuExtension(); // Get all potential buddies for the current document and add a switch-to action // for each buddy who really exists in the file system. Note: if no buddies could be calculated // no extension actions are generated. const QVector& potentialBuddies = buddyFinder->getPotentialBuddies(currentUrl); ContextMenuExtension extension; if (m_signalMapper) { delete m_signalMapper; } m_signalMapper = new QSignalMapper(this); foreach(const QUrl& url, potentialBuddies) { if (!QFile::exists(url.toLocalFile())) { continue; } QAction* action = new QAction(i18n("Switch to '%1'", url.fileName()), this); connect(action, &QAction::triggered, m_signalMapper, static_cast(&QSignalMapper::map), Qt::QueuedConnection); m_signalMapper->setMapping(action, url.toLocalFile()); connect(m_signalMapper, static_cast(&QSignalMapper::mapped), this, &SwitchToBuddyPlugin::switchToBuddy, Qt::QueuedConnection); extension.addAction(ContextMenuExtension::ExtensionGroup, action); } return extension; } void SwitchToBuddyPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = this->xmlFile(); QAction* switchDefinitionDeclaration = actions.addAction(QStringLiteral("switch_definition_declaration")); switchDefinitionDeclaration->setText(i18n("&Switch Definition/Declaration")); actions.setDefaultShortcut(switchDefinitionDeclaration, Qt::CTRL | Qt::SHIFT | Qt::Key_C); connect(switchDefinitionDeclaration, &QAction::triggered, this, &SwitchToBuddyPlugin::switchDefinitionDeclaration); QAction* switchHeaderSource = actions.addAction(QStringLiteral("switch_header_source")); switchHeaderSource->setText(i18n("Switch Header/Source")); actions.setDefaultShortcut(switchHeaderSource, Qt::CTRL | Qt::Key_Slash); connect(switchHeaderSource, &QAction::triggered, this, &SwitchToBuddyPlugin::switchHeaderSource); } void SwitchToBuddyPlugin::switchHeaderSource() { qCDebug(PLUGIN_SWITCHTOBUDDY) << "switching header/source"; auto doc = ICore::self()->documentController()->activeDocument(); if (!doc) return; QString buddyUrl = findSwitchCandidate(doc->url()); if (!buddyUrl.isEmpty()) switchToBuddy(buddyUrl); } void SwitchToBuddyPlugin::switchToBuddy(const QString& url) { KDevelop::ICore::self()->documentController()->openDocument(QUrl::fromLocalFile(url)); } void SwitchToBuddyPlugin::switchDefinitionDeclaration() { qCDebug(PLUGIN_SWITCHTOBUDDY) << "switching definition/declaration"; QUrl docUrl; KTextEditor::Cursor cursor; ///Step 1: Find the current top-level context of type DUContext::Other(the highest code-context). ///-- If it belongs to a function-declaration or definition, it can be retrieved through owner(), and we are in a definition. ///-- If no such context could be found, search for a declaration on the same line as the cursor, and switch to the according definition { auto view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "No active document"; return; } docUrl = view->document()->url(); cursor = view->cursorPosition(); } QString switchCandidate = findSwitchCandidate(docUrl); if(!switchCandidate.isEmpty()) { DUChainReadLocker lock; //If the file has not been parsed yet, update it TopDUContext* ctx = DUChainUtils::standardContextForUrl(docUrl); //At least 'VisibleDeclarationsAndContexts' is required so we can do a switch if (!ctx || (ctx->parsingEnvironmentFile() && !ctx->parsingEnvironmentFile()->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses))) { lock.unlock(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "Parsing switch-candidate before switching" << switchCandidate; ReferencedTopDUContext updatedContext = DUChain::self()->waitForUpdate(IndexedString(switchCandidate), TopDUContext::AllDeclarationsContextsAndUses); if (!updatedContext) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Failed to update document:" << switchCandidate; return; } } } qCDebug(PLUGIN_SWITCHTOBUDDY) << "Document:" << docUrl; DUChainReadLocker lock; TopDUContext* standardCtx = DUChainUtils::standardContextForUrl(docUrl); bool wasSignal = false; if (standardCtx) { - Declaration* definition = 0; + Declaration* definition = nullptr; DUContext* ctx = standardCtx->findContext(standardCtx->transformToLocalRevision(cursor)); if (!ctx) { ctx = standardCtx; } while (ctx && ctx->parentContext() && (ctx->parentContext()->type() == DUContext::Other || ctx->parentContext()->type() == DUContext::Function)) { ctx = ctx->parentContext(); } if (ctx && ctx->owner() && (ctx->type() == DUContext::Other || ctx->type() == DUContext::Function) && ctx->owner()->isDefinition()) { definition = ctx->owner(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition while traversing:" << definition->toString(); } if (!definition && ctx) { definition = DUChainUtils::declarationInLine(cursor, ctx); } if (ClassFunctionDeclaration* cDef = dynamic_cast(definition)) { if (cDef->isSignal()) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition is a signal, not switching to .moc implementation"; - definition = 0; + definition = nullptr; wasSignal = true; } } FunctionDefinition* def = dynamic_cast(definition); if (def && def->declaration()) { Declaration* declaration = def->declaration(); KTextEditor::Range targetRange = declaration->rangeInCurrentRevision(); const auto url = declaration->url().toUrl(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition that has declaration: " << definition->toString() << "range" << targetRange << "url" << url; lock.unlock(); auto view = ICore::self()->documentController()->activeTextDocumentView(); if (view && !targetRange.contains(view->cursorPosition())) { const auto pos = normalizeCursor(targetRange.start()); ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); } else { ICore::self()->documentController()->openDocument(url); } return; } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Definition has no assigned declaration"; } qCDebug(PLUGIN_SWITCHTOBUDDY) << "Could not get definition/declaration from context"; } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Got no context for the current document"; } - Declaration* def = 0; + Declaration* def = nullptr; if (!wasSignal) { def = definitionForCursorDeclaration(cursor, docUrl); } if (def) { const auto url = def->url().toUrl(); KTextEditor::Range targetRange = def->rangeInCurrentRevision(); if (def->internalContext()) { targetRange.end() = def->internalContext()->rangeInCurrentRevision().end(); } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Declaration does not have internal context"; } lock.unlock(); auto view = ICore::self()->documentController()->activeTextDocumentView(); if (view && !targetRange.contains(view->cursorPosition())) { KTextEditor::Cursor pos(normalizeCursor(targetRange.start())); ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); } else { //The cursor is already in the target range, only open the document ICore::self()->documentController()->openDocument(url); } return; } else if (!wasSignal) { qWarning() << "Found no definition assigned to cursor position"; } lock.unlock(); ///- If no definition/declaration could be found to switch to, just switch the document using normal header/source heuristic by file-extension if (!switchCandidate.isEmpty()) { ICore::self()->documentController()->openDocument(QUrl::fromUserInput(switchCandidate)); } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Found no source/header candidate to switch"; } } #include "switchtobuddyplugin.moc" diff --git a/plugins/testview/testview.cpp b/plugins/testview/testview.cpp index 38139ef459..3ef0cf3d83 100644 --- a/plugins/testview/testview.cpp +++ b/plugins/testview/testview.cpp @@ -1,410 +1,410 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testview.h" #include "testviewplugin.h" #include "testviewdebug.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 using namespace KDevelop; enum CustomRoles { ProjectRole = Qt::UserRole + 1, SuiteRole, CaseRole }; TestView::TestView(TestViewPlugin* plugin, QWidget* parent) : QWidget(parent) , m_plugin(plugin) , m_tree(new QTreeView(this)) , m_filter(new KRecursiveFilterProxyModel(this)) { setWindowIcon(QIcon::fromTheme(QStringLiteral("preflight-verifier"), windowIcon())); setWindowTitle(i18n("Unit Tests")); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); layout->addWidget(m_tree); m_tree->setSortingEnabled(true); m_tree->header()->hide(); m_tree->setIndentation(10); m_tree->setEditTriggers(QTreeView::NoEditTriggers); m_tree->setSelectionBehavior(QTreeView::SelectRows); m_tree->setSelectionMode(QTreeView::SingleSelection); m_tree->setExpandsOnDoubleClick(false); m_tree->sortByColumn(0, Qt::AscendingOrder); connect(m_tree, &QTreeView::activated, this, &TestView::doubleClicked); m_model = new QStandardItemModel(this); m_filter->setSourceModel(m_model); m_tree->setModel(m_filter); QAction* showSource = new QAction( QIcon::fromTheme(QStringLiteral("code-context")), i18n("Show Source"), this ); connect (showSource, &QAction::triggered, this, &TestView::showSource); m_contextMenuActions << showSource; addAction(plugin->actionCollection()->action(QStringLiteral("run_all_tests"))); addAction(plugin->actionCollection()->action(QStringLiteral("stop_running_tests"))); QAction* runSelected = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Run Selected Tests"), this ); connect (runSelected, &QAction::triggered, this, &TestView::runSelectedTests); addAction(runSelected); QLineEdit* edit = new QLineEdit(parent); edit->setPlaceholderText(i18n("Filter...")); edit->setClearButtonEnabled(true); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(edit); connect(edit, &QLineEdit::textChanged, this, &TestView::changeFilter); addAction(widgetAction); IProjectController* pc = ICore::self()->projectController(); connect (pc, &IProjectController::projectClosed, this, &TestView::removeProject); ITestController* tc = ICore::self()->testController(); connect (tc, &ITestController::testSuiteAdded, this, &TestView::addTestSuite); connect (tc, &ITestController::testSuiteRemoved, this, &TestView::removeTestSuite); connect (tc, &ITestController::testRunFinished, this, &TestView::updateTestSuite); connect (tc, &ITestController::testRunStarted, this, &TestView::notifyTestCaseStarted); foreach (ITestSuite* suite, tc->testSuites()) { addTestSuite(suite); } } TestView::~TestView() { } void TestView::updateTestSuite(ITestSuite* suite, const TestResult& result) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Updating test suite" << suite->name(); item->setIcon(iconForTestResult(result.suiteResult)); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (result.testCaseResults.contains(caseItem->text())) { TestResult::TestCaseResult caseResult = result.testCaseResults.value(caseItem->text(), TestResult::NotRun); caseItem->setIcon(iconForTestResult(caseResult)); } } } void TestView::changeFilter(const QString &newFilter) { m_filter->setFilterWildcard(newFilter); if (newFilter.isEmpty()) { m_tree->collapseAll(); } else { m_tree->expandAll(); } } void TestView::notifyTestCaseStarted(ITestSuite* suite, const QStringList& test_cases) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Notify a test of the suite " << suite->name() << " has started"; // Global test suite icon item->setIcon(QIcon::fromTheme(QStringLiteral("process-idle"))); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (test_cases.contains(caseItem->text())) { // Each test case icon caseItem->setIcon(QIcon::fromTheme(QStringLiteral("process-idle"))); } } } QIcon TestView::iconForTestResult(TestResult::TestCaseResult result) { switch (result) { case TestResult::NotRun: return QIcon::fromTheme(QStringLiteral("code-function")); case TestResult::Skipped: return QIcon::fromTheme(QStringLiteral("task-delegate")); case TestResult::Passed: return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); case TestResult::UnexpectedPass: // This is a very rare occurrence, so the icon should stand out return QIcon::fromTheme(QStringLiteral("dialog-warning")); case TestResult::Failed: return QIcon::fromTheme(QStringLiteral("edit-delete")); case TestResult::ExpectedFail: return QIcon::fromTheme(QStringLiteral("dialog-ok")); case TestResult::Error: return QIcon::fromTheme(QStringLiteral("dialog-cancel")); default: return QIcon::fromTheme(QLatin1String("")); } } QStandardItem* TestView::itemForSuite(ITestSuite* suite) { foreach (QStandardItem* item, m_model->findItems(suite->name(), Qt::MatchRecursive)) { if (item->parent() && item->parent()->text() == suite->project()->name() && !item->parent()->parent()) { return item; } } - return 0; + return nullptr; } QStandardItem* TestView::itemForProject(IProject* project) { foreach (QStandardItem* item, m_model->findItems(project->name())) { return item; } return addProject(project); } void TestView::runSelectedTests() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { //if there's no selection we'll run all of them (or only the filtered) //in case there's a filter. const int rc = m_filter->rowCount(); for(int i=0; iindex(i, 0); } } QList jobs; ITestController* tc = ICore::self()->testController(); /* * NOTE: If a test suite or a single test case was selected, * the job is launched in Verbose mode with raised output window. * If a project is selected, it is launched silently. * * This is the somewhat-intuitive approach. Maybe a configuration should be offered. */ foreach (const QModelIndex& idx, indexes) { QModelIndex index = m_filter->mapToSource(idx); if (index.parent().isValid() && indexes.contains(index.parent())) { continue; } QStandardItem* item = m_model->itemFromIndex(index); - if (item->parent() == 0) + if (item->parent() == nullptr) { // A project was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->data(ProjectRole).toString()); foreach (ITestSuite* suite, tc->testSuitesForProject(project)) { jobs << suite->launchAllCases(ITestSuite::Silent); } } - else if (item->parent()->parent() == 0) + else if (item->parent()->parent() == nullptr) { // A suite was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); jobs << suite->launchAllCases(ITestSuite::Verbose); } else { // This was a single test case IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); const QString testCase = item->data(CaseRole).toString(); jobs << suite->launchCase(testCase, ITestSuite::Verbose); } } if (!jobs.isEmpty()) { KDevelop::ExecuteCompositeJob* compositeJob = new KDevelop::ExecuteCompositeJob(this, jobs); compositeJob->setObjectName(i18np("Run 1 test", "Run %1 tests", jobs.size())); compositeJob->setProperty("test_job", true); ICore::self()->runController()->registerJob(compositeJob); } } void TestView::showSource() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return; } IndexedDeclaration declaration; ITestController* tc = ICore::self()->testController(); QModelIndex index = m_filter->mapToSource(indexes.first()); QStandardItem* item = m_model->itemFromIndex(index); - if (item->parent() == 0) + if (item->parent() == nullptr) { // No sense in finding source code for projects. return; } - else if (item->parent()->parent() == 0) + else if (item->parent()->parent() == nullptr) { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); declaration = suite->declaration(); } else { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); declaration = suite->caseDeclaration(item->data(CaseRole).toString()); } DUChainReadLocker locker; Declaration* d = declaration.data(); if (!d) { return; } QUrl url = d->url().toUrl(); KTextEditor::Cursor cursor = d->rangeInCurrentRevision().start(); locker.unlock(); IDocumentController* dc = ICore::self()->documentController(); qCDebug(PLUGIN_TESTVIEW) << "Activating declaration in" << url; dc->openDocument(url, cursor); } void TestView::addTestSuite(ITestSuite* suite) { QStandardItem* projectItem = itemForProject(suite->project()); Q_ASSERT(projectItem); QStandardItem* suiteItem = new QStandardItem(QIcon::fromTheme(QStringLiteral("view-list-tree")), suite->name()); suiteItem->setData(suite->name(), SuiteRole); foreach (const QString& caseName, suite->cases()) { QStandardItem* caseItem = new QStandardItem(iconForTestResult(TestResult::NotRun), caseName); caseItem->setData(caseName, CaseRole); suiteItem->appendRow(caseItem); } projectItem->appendRow(suiteItem); } void TestView::removeTestSuite(ITestSuite* suite) { QStandardItem* item = itemForSuite(suite); item->parent()->removeRow(item->row()); } QStandardItem* TestView::addProject(IProject* project) { QStandardItem* projectItem = new QStandardItem(QIcon::fromTheme(QStringLiteral("project-development")), project->name()); projectItem->setData(project->name(), ProjectRole); m_model->appendRow(projectItem); return projectItem; } void TestView::removeProject(IProject* project) { QStandardItem* projectItem = itemForProject(project); m_model->removeRow(projectItem->row()); } void TestView::doubleClicked(const QModelIndex& index) { m_tree->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); runSelectedTests(); } QList< QAction* > TestView::contextMenuActions() { return m_contextMenuActions; } diff --git a/plugins/testview/testviewplugin.cpp b/plugins/testview/testviewplugin.cpp index a0d4de12c7..a341dc8ec4 100644 --- a/plugins/testview/testviewplugin.cpp +++ b/plugins/testview/testviewplugin.cpp @@ -1,155 +1,155 @@ /* This file is part of KDevelop Copyright 2012 Miha ?an?ula 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testviewplugin.h" #include "testview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(TestViewFactory, "kdevtestview.json", registerPlugin();) using namespace KDevelop; class TestToolViewFactory: public KDevelop::IToolViewFactory { public: TestToolViewFactory( TestViewPlugin *plugin ): mplugin( plugin ) {} - QWidget* create( QWidget *parent = 0 ) override + QWidget* create( QWidget *parent = nullptr ) override { return new TestView( mplugin, parent ); } Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.TestView"); } QList< QAction* > contextMenuActions(QWidget* viewWidget) const override { return qobject_cast(viewWidget)->contextMenuActions(); } private: TestViewPlugin *mplugin; }; TestViewPlugin::TestViewPlugin(QObject* parent, const QVariantList& args) : IPlugin(QStringLiteral("kdevtestview"), parent) { Q_UNUSED(args) QAction* runAll = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Run All Tests"), this ); connect(runAll, &QAction::triggered, this, &TestViewPlugin::runAllTests); actionCollection()->addAction(QStringLiteral("run_all_tests"), runAll); QAction* stopTest = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop Running Tests"), this ); connect(stopTest, &QAction::triggered, this, &TestViewPlugin::stopRunningTests); actionCollection()->addAction(QStringLiteral("stop_running_tests"), stopTest); setXMLFile(QStringLiteral("kdevtestview.rc")); m_viewFactory = new TestToolViewFactory(this); core()->uiController()->addToolView(i18n("Unit Tests"), m_viewFactory); connect(core()->runController(),&IRunController::jobRegistered, this, &TestViewPlugin::jobStateChanged); connect(core()->runController(),&IRunController::jobUnregistered, this, &TestViewPlugin::jobStateChanged); jobStateChanged(); } TestViewPlugin::~TestViewPlugin() { } void TestViewPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } void TestViewPlugin::runAllTests() { ITestController* tc = core()->testController(); foreach (IProject* project, core()->projectController()->projects()) { QList jobs; foreach (ITestSuite* suite, tc->testSuitesForProject(project)) { if (KJob* job = suite->launchAllCases(ITestSuite::Silent)) { jobs << job; } } if (!jobs.isEmpty()) { KDevelop::ExecuteCompositeJob* compositeJob = new KDevelop::ExecuteCompositeJob(this, jobs); compositeJob->setObjectName(i18np("Run 1 test in %2", "Run %1 tests in %2", jobs.size(), project->name())); compositeJob->setProperty("test_job", true); core()->runController()->registerJob(compositeJob); } } } void TestViewPlugin::stopRunningTests() { foreach (KJob* job, core()->runController()->currentJobs()) { if (job->property("test_job").toBool()) { job->kill(); } } } void TestViewPlugin::jobStateChanged() { bool found = false; foreach (KJob* job, core()->runController()->currentJobs()) { if (job->property("test_job").toBool()) { found = true; break; } } actionCollection()->action(QStringLiteral("run_all_tests"))->setEnabled(!found); actionCollection()->action(QStringLiteral("stop_running_tests"))->setEnabled(found); } #include "testviewplugin.moc" diff --git a/plugins/vcschangesview/vcschangesviewplugin.cpp b/plugins/vcschangesview/vcschangesviewplugin.cpp index efba9be669..9744e630de 100644 --- a/plugins/vcschangesview/vcschangesviewplugin.cpp +++ b/plugins/vcschangesview/vcschangesviewplugin.cpp @@ -1,107 +1,107 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "vcschangesviewplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vcschangesview.h" K_PLUGIN_FACTORY_WITH_JSON(VcsProjectIntegrationFactory, "kdevvcschangesview.json", registerPlugin();) using namespace KDevelop; class VCSProjectToolViewFactory : public KDevelop::IToolViewFactory { public: VCSProjectToolViewFactory(VcsProjectIntegrationPlugin *plugin): m_plugin(plugin) {} - QWidget* create(QWidget *parent = 0) override + QWidget* create(QWidget *parent = nullptr) override { VcsChangesView* modif = new VcsChangesView(m_plugin, parent); modif->setModel(m_plugin->model()); QObject::connect(modif, static_cast&)>(&VcsChangesView::reload), m_plugin->model(), static_cast&)>(&ProjectChangesModel::reload)); QObject::connect(modif, static_cast&)>(&VcsChangesView::reload), m_plugin->model(), static_cast&)>(&ProjectChangesModel::reload)); QObject::connect(modif, &VcsChangesView::activated, m_plugin, &VcsProjectIntegrationPlugin::activated); return modif; } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.VCSProject"); } private: VcsProjectIntegrationPlugin *m_plugin; }; VcsProjectIntegrationPlugin::VcsProjectIntegrationPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevvcsprojectintegration"), parent) - , m_model(0) + , m_model(nullptr) { ICore::self()->uiController()->addToolView(i18n("Project Changes"), new VCSProjectToolViewFactory(this)); QAction* synaction = actionCollection()->addAction(QStringLiteral("locate_document")); synaction->setText(i18n("Locate Current Document")); synaction->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); synaction->setToolTip(i18n("Locates the current document and selects it.")); QAction* reloadaction = actionCollection()->addAction(QStringLiteral("reload_view")); reloadaction->setText(i18n("Reload View")); reloadaction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); reloadaction->setToolTip(i18n("Refreshes the view for all projects, in case anything changed.")); } void VcsProjectIntegrationPlugin::activated(const QModelIndex& /*idx*/) { } ProjectChangesModel* VcsProjectIntegrationPlugin::model() { if(!m_model) { m_model = ICore::self()->projectController()->changesModel(); connect(actionCollection()->action(QStringLiteral("reload_view")), &QAction::triggered, m_model, &ProjectChangesModel::reloadAll); } return m_model; } #include "vcschangesviewplugin.moc" diff --git a/plugins/welcomepage/uihelper.cpp b/plugins/welcomepage/uihelper.cpp index f1c61c5194..54f85bca3f 100644 --- a/plugins/welcomepage/uihelper.cpp +++ b/plugins/welcomepage/uihelper.cpp @@ -1,86 +1,86 @@ /* 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 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 "uihelper.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; UiHelper::UiHelper(QObject* parent): QObject(parent) {} QAction* findActionRec(const QStringList& path, const QList& actions) { QStringList newPath = path; QString current = newPath.takeFirst(); foreach(QAction* a, actions) { if(a->objectName() == current) { if(newPath.isEmpty()) return a; else if(a->menu()) return findActionRec(newPath, a->menu()->actions()); else qDebug() << "shouldn't get here:" << path; } } qWarning() << "error: action path not found: " << path; - return 0; + return nullptr; } QAction* UiHelper::retrieveMenuAction(const QString& menuPath) { QMenuBar* m = ICore::self()->uiController()->activeMainWindow()->menuBar(); QAction* a=findActionRec(menuPath.split('/'), m->actions()); return a; } void UiHelper::setArea(const QString& name) { ICore::self()->uiController()->switchToArea(name, IUiController::ThisWindow); } void UiHelper::raiseToolView(const QString& id) { QList< Sublime::View* > views = ICore::self()->uiController()->activeArea()->toolViews(); foreach(Sublime::View* v, views) { QWidget* w=v->widget(); if(w && id==w->objectName()) ICore::self()->uiController()->raiseToolView(w); } } void UiHelper::showMenu(const QString& name) { QAction* action = retrieveMenuAction(name); Q_ASSERT(action); Q_ASSERT(action->menu()); action->menu()->popup(QCursor::pos()); } diff --git a/project/abstractfilemanagerplugin.cpp b/project/abstractfilemanagerplugin.cpp index 52f65bea7a..9eaf1fed05 100644 --- a/project/abstractfilemanagerplugin.cpp +++ b/project/abstractfilemanagerplugin.cpp @@ -1,659 +1,659 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2010-2012 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "abstractfilemanagerplugin.h" #include "filemanagerlistjob.h" #include "projectmodel.h" #include "helper.h" #include #include #include #include #include #include #include #include #include #include #include "projectfiltermanager.h" #include "debug.h" #define ifDebug(x) using namespace KDevelop; //BEGIN Helper namespace { /** * Returns the parent folder item for a given item or the project root item if there is no parent. */ ProjectFolderItem* getParentFolder(ProjectBaseItem* item) { if ( item->parent() ) { return static_cast(item->parent()); } else { return item->project()->projectItem(); } } } //END Helper //BEGIN Private struct AbstractFileManagerPlugin::Private { explicit Private(AbstractFileManagerPlugin* qq) : q(qq) { } AbstractFileManagerPlugin* q; /** * The just returned must be started in one way or another for this method * to have any affect. The job will then auto-delete itself upon completion. */ KIO::Job* eventuallyReadFolder( ProjectFolderItem* item ) Q_REQUIRED_RESULT; void addJobItems(FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries); void deleted(const QString &path); void created(const QString &path); void projectClosing(IProject* project); void jobFinished(KJob* job); /// Stops watching the given folder for changes, only useful for local files. void stopWatcher(ProjectFolderItem* folder); /// Continues watching the given folder for changes. void continueWatcher(ProjectFolderItem* folder); /// Common renaming function. bool rename(ProjectBaseItem* item, const Path& newPath); void removeFolder(ProjectFolderItem* folder); QHash m_watchers; QHash > m_projectJobs; QVector m_stoppedFolders; ProjectFilterManager m_filters; }; void AbstractFileManagerPlugin::Private::projectClosing(IProject* project) { if ( m_projectJobs.contains(project) ) { // make sure the import job does not live longer than the project // see also addLotsOfFiles test foreach( FileManagerListJob* job, m_projectJobs[project] ) { qCDebug(FILEMANAGER) << "killing project job:" << job; job->abort(); } m_projectJobs.remove(project); } delete m_watchers.take(project); m_filters.remove(project); } KIO::Job* AbstractFileManagerPlugin::Private::eventuallyReadFolder( ProjectFolderItem* item ) { FileManagerListJob* listJob = new FileManagerListJob( item ); m_projectJobs[ item->project() ] << listJob; qCDebug(FILEMANAGER) << "adding job" << listJob << item << item->path() << "for project" << item->project(); q->connect( listJob, &FileManagerListJob::finished, q, [&] (KJob* job) { jobFinished(job); } ); q->connect( listJob, &FileManagerListJob::entries, q, [&] (FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries) { addJobItems(job, baseItem, entries); } ); return listJob; } void AbstractFileManagerPlugin::Private::jobFinished(KJob* job) { FileManagerListJob* gmlJob = qobject_cast(job); if (gmlJob) { ifDebug(qCDebug(FILEMANAGER) << job << gmlJob << gmlJob->item();) m_projectJobs[ gmlJob->item()->project() ].removeOne( gmlJob ); } else { // job emitted its finished signal from its destructor // ensure we don't keep a dangling point in our list foreach (auto jobs, m_projectJobs) { if (jobs.removeOne(reinterpret_cast(job))) { break; } } } } void AbstractFileManagerPlugin::Private::addJobItems(FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries) { if ( entries.empty() ) { return; } qCDebug(FILEMANAGER) << "reading entries of" << baseItem->path(); // build lists of valid files and folders with paths relative to the project folder Path::List files; Path::List folders; foreach ( const KIO::UDSEntry& entry, entries ) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if (name == QLatin1String(".") || name == QLatin1String("..")) { continue; } Path path(baseItem->path(), name); if ( !q->isValid( path, entry.isDir(), baseItem->project() ) ) { continue; } else { if ( entry.isDir() ) { if( entry.isLink() ) { const Path linkedPath = baseItem->path().cd(entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST )); // make sure we don't end in an infinite loop if( linkedPath.isParentOf( baseItem->project()->path() ) || baseItem->project()->path().isParentOf( linkedPath ) || linkedPath == baseItem->project()->path() ) { continue; } } folders << path; } else { files << path; } } } ifDebug(qCDebug(FILEMANAGER) << "valid folders:" << folders;) ifDebug(qCDebug(FILEMANAGER) << "valid files:" << files;) // remove obsolete rows for ( int j = 0; j < baseItem->rowCount(); ++j ) { if ( ProjectFolderItem* f = baseItem->child(j)->folder() ) { // check if this is still a valid folder int index = folders.indexOf( f->path() ); if ( index == -1 ) { // folder got removed or is now invalid removeFolder(f); --j; } else { // this folder already exists in the view folders.remove( index ); // no need to add this item, but we still want to recurse into it job->addSubDir( f ); emit q->reloadedFolderItem( f ); } } else if ( ProjectFileItem* f = baseItem->child(j)->file() ) { // check if this is still a valid file int index = files.indexOf( f->path() ); if ( index == -1 ) { // file got removed or is now invalid ifDebug(qCDebug(FILEMANAGER) << "removing file:" << f << f->path();) baseItem->removeRow( j ); --j; } else { // this file already exists in the view files.remove( index ); emit q->reloadedFileItem( f ); } } } // add new rows foreach ( const Path& path, files ) { ProjectFileItem* file = q->createFileItem( baseItem->project(), path, baseItem ); if (file) { emit q->fileAdded( file ); } } foreach ( const Path& path, folders ) { ProjectFolderItem* folder = q->createFolderItem( baseItem->project(), path, baseItem ); if (folder) { emit q->folderAdded( folder ); job->addSubDir( folder ); } } } void AbstractFileManagerPlugin::Private::created(const QString &path_) { qCDebug(FILEMANAGER) << "created:" << path_; QFileInfo info(path_); ///FIXME: share memory with parent const Path path(path_); const IndexedString indexedPath(path.pathOrUrl()); const IndexedString indexedParent(path.parent().pathOrUrl()); foreach ( IProject* p, m_watchers.keys() ) { if ( !p->projectItem()->model() ) { // not yet finished with loading // FIXME: how should this be handled? see unit test continue; } if ( !q->isValid(path, info.isDir(), p) ) { continue; } if ( info.isDir() ) { bool found = false; foreach ( ProjectFolderItem* folder, p->foldersForPath(indexedPath) ) { // exists already in this project, happens e.g. when we restart the dirwatcher // or if we delete and remove folders consecutively https://bugs.kde.org/show_bug.cgi?id=260741 qCDebug(FILEMANAGER) << "force reload of" << path << folder; auto job = eventuallyReadFolder( folder ); job->start(); found = true; } if ( found ) { continue; } } else if (!p->filesForPath(indexedPath).isEmpty()) { // also gets triggered for kate's backup files continue; } foreach ( ProjectFolderItem* parentItem, p->foldersForPath(indexedParent) ) { if ( info.isDir() ) { ProjectFolderItem* folder = q->createFolderItem( p, path, parentItem ); if (folder) { emit q->folderAdded( folder ); auto job = eventuallyReadFolder( folder ); job->start(); } } else { ProjectFileItem* file = q->createFileItem( p, path, parentItem ); if (file) { emit q->fileAdded( file ); } } } } } void AbstractFileManagerPlugin::Private::deleted(const QString &path_) { if ( QFile::exists(path_) ) { // stopDirScan... return; } // ensure that the path is not inside a stopped folder foreach(const QString& folder, m_stoppedFolders) { if (path_.startsWith(folder)) { return; } } qCDebug(FILEMANAGER) << "deleted:" << path_; const Path path(path_); const IndexedString indexed(path.pathOrUrl()); foreach ( IProject* p, m_watchers.keys() ) { if (path == p->path()) { KMessageBox::error(qApp->activeWindow(), i18n("The base folder of project %1" " got deleted or moved outside of KDevelop.\n" "The project has to be closed.", p->name()), i18n("Project Folder Deleted") ); ICore::self()->projectController()->closeProject(p); continue; } if ( !p->projectItem()->model() ) { // not yet finished with loading // FIXME: how should this be handled? see unit test continue; } foreach ( ProjectFolderItem* item, p->foldersForPath(indexed) ) { removeFolder(item); } foreach ( ProjectFileItem* item, p->filesForPath(indexed) ) { emit q->fileRemoved(item); ifDebug(qCDebug(FILEMANAGER) << "removing file" << item;) item->parent()->removeRow(item->row()); } } } bool AbstractFileManagerPlugin::Private::rename(ProjectBaseItem* item, const Path& newPath) { if ( !q->isValid(newPath, true, item->project()) ) { int cancel = KMessageBox::warningContinueCancel( qApp->activeWindow(), i18n("You tried to rename '%1' to '%2', but the latter is filtered and will be hidden.\n" "Do you want to continue?", item->text(), newPath.lastPathSegment()), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("GenericManagerRenameToFiltered") ); if ( cancel == KMessageBox::Cancel ) { return false; } } foreach ( ProjectFolderItem* parent, item->project()->foldersForPath(IndexedString(newPath.parent().pathOrUrl())) ) { if ( parent->folder() ) { stopWatcher(parent); const Path source = item->path(); bool success = renameUrl( item->project(), source.toUrl(), newPath.toUrl() ); if ( success ) { item->setPath( newPath ); item->parent()->takeRow( item->row() ); parent->appendRow( item ); if (item->file()) { emit q->fileRenamed(source, item->file()); } else { Q_ASSERT(item->folder()); emit q->folderRenamed(source, item->folder()); } } continueWatcher(parent); return success; } } return false; } void AbstractFileManagerPlugin::Private::stopWatcher(ProjectFolderItem* folder) { if ( !folder->path().isLocalFile() ) { return; } Q_ASSERT(m_watchers.contains(folder->project())); const QString path = folder->path().toLocalFile(); m_watchers[folder->project()]->stopDirScan(path); m_stoppedFolders.append(path); } void AbstractFileManagerPlugin::Private::continueWatcher(ProjectFolderItem* folder) { if ( !folder->path().isLocalFile() ) { return; } Q_ASSERT(m_watchers.contains(folder->project())); const QString path = folder->path().toLocalFile(); m_watchers[folder->project()]->restartDirScan(path); const int idx = m_stoppedFolders.indexOf(path); if (idx != -1) { m_stoppedFolders.remove(idx); } } bool isChildItem(ProjectBaseItem* parent, ProjectBaseItem* child) { do { if (child == parent) { return true; } child = child->parent(); } while(child); return false; } void AbstractFileManagerPlugin::Private::removeFolder(ProjectFolderItem* folder) { ifDebug(qCDebug(FILEMANAGER) << "removing folder:" << folder << folder->path();) foreach(FileManagerListJob* job, m_projectJobs[folder->project()]) { if (isChildItem(folder, job->item())) { qCDebug(FILEMANAGER) << "killing list job for removed folder" << job << folder->path(); job->abort(); Q_ASSERT(!m_projectJobs.value(folder->project()).contains(job)); } else { job->removeSubDir(folder); } } folder->parent()->removeRow( folder->row() ); } //END Private //BEGIN Plugin AbstractFileManagerPlugin::AbstractFileManagerPlugin( const QString& componentName, QObject *parent, const QVariantList & /*args*/ ) : IProjectFileManager(), IPlugin( componentName, parent ), d(new Private(this)) { KDEV_USE_EXTENSION_INTERFACE( IProjectFileManager ) connect(core()->projectController(), &IProjectController::projectClosing, this, [&] (IProject* project) { d->projectClosing(project); }); } AbstractFileManagerPlugin::~AbstractFileManagerPlugin() { delete d; } IProjectFileManager::Features AbstractFileManagerPlugin::features() const { return Features( Folders | Files ); } QList AbstractFileManagerPlugin::parse( ProjectFolderItem *item ) { // we are async, can't return anything here qCDebug(FILEMANAGER) << "note: parse will always return an empty list"; Q_UNUSED(item); return QList(); } ProjectFolderItem *AbstractFileManagerPlugin::import( IProject *project ) { - ProjectFolderItem *projectRoot = createFolderItem( project, project->path(), 0 ); + ProjectFolderItem *projectRoot = createFolderItem( project, project->path(), nullptr ); emit folderAdded( projectRoot ); qCDebug(FILEMANAGER) << "imported new project" << project->name() << "at" << projectRoot->path(); ///TODO: check if this works for remote files when something gets changed through another KDE app if ( project->path().isLocalFile() ) { d->m_watchers[project] = new KDirWatch( project ); connect(d->m_watchers[project], &KDirWatch::created, this, [&] (const QString& path_) { d->created(path_); }); connect(d->m_watchers[project], &KDirWatch::deleted, this, [&] (const QString& path_) { d->deleted(path_); }); d->m_watchers[project]->addDir(project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles ); } d->m_filters.add(project); return projectRoot; } KJob* AbstractFileManagerPlugin::createImportJob(ProjectFolderItem* item) { return d->eventuallyReadFolder(item); } bool AbstractFileManagerPlugin::reload( ProjectFolderItem* item ) { qCDebug(FILEMANAGER) << "reloading item" << item->path(); auto job = d->eventuallyReadFolder( item->folder() ); job->start(); return true; } ProjectFolderItem* AbstractFileManagerPlugin::addFolder( const Path& folder, ProjectFolderItem * parent ) { qCDebug(FILEMANAGER) << "adding folder" << folder << "to" << parent->path(); - ProjectFolderItem* created = 0; + ProjectFolderItem* created = nullptr; d->stopWatcher(parent); if ( createFolder(folder.toUrl()) ) { created = createFolderItem( parent->project(), folder, parent ); if (created) { emit folderAdded(created); } } d->continueWatcher(parent); return created; } ProjectFileItem* AbstractFileManagerPlugin::addFile( const Path& file, ProjectFolderItem * parent ) { qCDebug(FILEMANAGER) << "adding file" << file << "to" << parent->path(); - ProjectFileItem* created = 0; + ProjectFileItem* created = nullptr; d->stopWatcher(parent); if ( createFile(file.toUrl()) ) { created = createFileItem( parent->project(), file, parent ); if (created) { emit fileAdded(created); } } d->continueWatcher(parent); return created; } bool AbstractFileManagerPlugin::renameFolder(ProjectFolderItem* folder, const Path& newPath) { qCDebug(FILEMANAGER) << "trying to rename a folder:" << folder->path() << newPath; return d->rename(folder, newPath); } bool AbstractFileManagerPlugin::renameFile(ProjectFileItem* file, const Path& newPath) { qCDebug(FILEMANAGER) << "trying to rename a file:" << file->path() << newPath; return d->rename(file, newPath); } bool AbstractFileManagerPlugin::removeFilesAndFolders(const QList &items) { bool success = true; foreach(ProjectBaseItem* item, items) { Q_ASSERT(item->folder() || item->file()); ProjectFolderItem* parent = getParentFolder(item); d->stopWatcher(parent); success &= removeUrl(parent->project(), item->path().toUrl(), true); if ( success ) { if (item->file()) { emit fileRemoved(item->file()); } else { Q_ASSERT(item->folder()); emit folderRemoved(item->folder()); } item->parent()->removeRow( item->row() ); } d->continueWatcher(parent); if ( !success ) break; } return success; } bool AbstractFileManagerPlugin::moveFilesAndFolders(const QList< ProjectBaseItem* >& items, ProjectFolderItem* newParent) { bool success = true; foreach(ProjectBaseItem* item, items) { Q_ASSERT(item->folder() || item->file()); ProjectFolderItem* oldParent = getParentFolder(item); d->stopWatcher(oldParent); d->stopWatcher(newParent); const Path oldPath = item->path(); const Path newPath(newParent->path(), item->baseName()); success &= renameUrl(oldParent->project(), oldPath.toUrl(), newPath. toUrl()); if ( success ) { if (item->file()) { emit fileRemoved(item->file()); } else { emit folderRemoved(item->folder()); } oldParent->removeRow( item->row() ); KIO::Job *readJob = d->eventuallyReadFolder(newParent); // reload first level synchronously, deeper levels will run async // this is required for code that expects the new item to exist after // this method finished readJob->exec(); } d->continueWatcher(oldParent); d->continueWatcher(newParent); if ( !success ) break; } return success; } bool AbstractFileManagerPlugin::copyFilesAndFolders(const Path::List& items, ProjectFolderItem* newParent) { bool success = true; foreach(const Path& item, items) { d->stopWatcher(newParent); success &= copyUrl(newParent->project(), item.toUrl(), newParent->path().toUrl()); if ( success ) { KIO::Job *readJob = d->eventuallyReadFolder(newParent); // reload first level synchronously, deeper levels will run async // this is required for code that expects the new item to exist after // this method finished readJob->exec(); } d->continueWatcher(newParent); if ( !success ) break; } return success; } bool AbstractFileManagerPlugin::isValid( const Path& path, const bool isFolder, IProject* project ) const { return d->m_filters.isValid( path, isFolder, project ); } ProjectFileItem* AbstractFileManagerPlugin::createFileItem( IProject* project, const Path& path, ProjectBaseItem* parent ) { return new ProjectFileItem( project, path, parent ); } ProjectFolderItem* AbstractFileManagerPlugin::createFolderItem( IProject* project, const Path& path, ProjectBaseItem* parent ) { return new ProjectFolderItem( project, path, parent ); } KDirWatch* AbstractFileManagerPlugin::projectWatcher( IProject* project ) const { - return d->m_watchers.value( project, 0 ); + return d->m_watchers.value( project, nullptr ); } //END Plugin #include "moc_abstractfilemanagerplugin.cpp" diff --git a/project/builderjob.cpp b/project/builderjob.cpp index 08f311aa98..77eb1698ce 100644 --- a/project/builderjob.cpp +++ b/project/builderjob.cpp @@ -1,271 +1,271 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "builderjob.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; struct SubJobData { BuilderJob::BuildType type; KJob* job; ProjectBaseItem* item; }; Q_DECLARE_TYPEINFO(SubJobData, Q_MOVABLE_TYPE); namespace KDevelop { class BuilderJobPrivate { public: BuilderJobPrivate( BuilderJob* job ) : q(job) , failOnFirstError(true) { } BuilderJob* q; void addJob( BuilderJob::BuildType, ProjectBaseItem* ); bool failOnFirstError; QString buildTypeToString( BuilderJob::BuildType type ) const; bool hasJobForProject( BuilderJob::BuildType type, IProject* project ) const { foreach(const SubJobData& data, m_metadata) { if (data.type == type && data.item->project() == project) { return true; } } return false; } /** * a structure to keep metadata of all registered jobs */ QVector m_metadata; /** * get the subjob list and clear this composite job */ QVector takeJobList(); }; } QString BuilderJobPrivate::buildTypeToString(BuilderJob::BuildType type) const { switch( type ) { case BuilderJob::Build: return i18nc( "@info:status", "build" ); case BuilderJob::Clean: return i18nc( "@info:status", "clean" ); case BuilderJob::Configure: return i18nc( "@info:status", "configure" ); case BuilderJob::Install: return i18nc( "@info:status", "install" ); case BuilderJob::Prune: return i18nc( "@info:status", "prune" ); default: return QString(); } } void BuilderJobPrivate::addJob( BuilderJob::BuildType t, ProjectBaseItem* item ) { Q_ASSERT(item); qCDebug(PROJECT) << "adding build job for item:" << item->text(); Q_ASSERT(item->project()); qCDebug(PROJECT) << "project for item:" << item->project()->name(); Q_ASSERT(item->project()->projectItem()); qCDebug(PROJECT) << "project item for the project:" << item->project()->projectItem()->text(); if( !item->project()->buildSystemManager() ) { qWarning() << "no buildsystem manager for:" << item->text() << item->project()->name(); return; } qCDebug(PROJECT) << "got build system manager"; Q_ASSERT(item->project()->buildSystemManager()->builder()); - KJob* j = 0; + KJob* j = nullptr; switch( t ) { case BuilderJob::Build: j = item->project()->buildSystemManager()->builder()->build( item ); break; case BuilderJob::Clean: j = item->project()->buildSystemManager()->builder()->clean( item ); break; case BuilderJob::Install: j = item->project()->buildSystemManager()->builder()->install( item ); break; case BuilderJob::Prune: if (!hasJobForProject(t, item->project())) { j = item->project()->buildSystemManager()->builder()->prune( item->project() ); } break; case BuilderJob::Configure: if (!hasJobForProject(t, item->project())) { j = item->project()->buildSystemManager()->builder()->configure( item->project() ); } break; default: break; } if( j ) { q->addCustomJob( t, j, item ); } } BuilderJob::BuilderJob() : d( new BuilderJobPrivate( this ) ) { } BuilderJob::~BuilderJob() { delete d; } void BuilderJob::addItems( BuildType t, const QList& items ) { foreach( ProjectBaseItem* item, items ) { d->addJob( t, item ); } } void BuilderJob::addProjects( BuildType t, const QList& projects ) { foreach( IProject* project, projects ) { d->addJob( t, project->projectItem() ); } } void BuilderJob::addItem( BuildType t, ProjectBaseItem* item ) { d->addJob( t, item ); } void BuilderJob::addCustomJob( BuilderJob::BuildType type, KJob* job, ProjectBaseItem* item ) { if( BuilderJob* builderJob = dynamic_cast( job ) ) { // If a subjob is a builder job itself, re-own its job list to avoid having recursive composite jobs. QVector subjobs = builderJob->d->takeJobList(); builderJob->deleteLater(); foreach( const SubJobData& subjob, subjobs ) { subjob.job->setParent(this); addSubjob( subjob.job ); } d->m_metadata << subjobs; } else { job->setParent(this); addSubjob( job ); SubJobData data; data.type = type; data.job = job; data.item = item; d->m_metadata << data; } } QVector< SubJobData > BuilderJobPrivate::takeJobList() { QVector< SubJobData > ret = m_metadata; m_metadata.clear(); q->clearSubjobs(); q->setObjectName( QString() ); return ret; } void BuilderJob::updateJobName() { // Which items are mentioned in the set // Make it a list to preserve ordering; search overhead (n^2) isn't too big QList< ProjectBaseItem* > registeredItems; // Which build types are mentioned in the set // (Same rationale applies) QList< BuildType > buildTypes; // Whether there are jobs without any specific item bool hasNullItems = false; foreach( const SubJobData& subjob, d->m_metadata ) { if( subjob.item ) { if( !registeredItems.contains( subjob.item ) ) { registeredItems.append( subjob.item ); } if( !buildTypes.contains( subjob.type ) ) { buildTypes.append( subjob.type ); } } else { hasNullItems = true; } } QString itemNames; if( !hasNullItems ) { QStringList itemNamesList; foreach( ProjectBaseItem* item, registeredItems ) { itemNamesList << item->text(); } itemNames = itemNamesList.join(QStringLiteral(", ")); } else { itemNames = i18nc( "Unspecified set of build items (e. g. projects, targets)", "Various items" ); } QString methodNames; QStringList methodNamesList; foreach( BuildType type, buildTypes ) { methodNamesList << d->buildTypeToString( type ); } methodNames = methodNamesList.join( QStringLiteral( ", " ) ); QString jobName = QStringLiteral( "%1: %2" ).arg( itemNames, methodNames ); setObjectName( jobName ); } void BuilderJob::start() { // Automatically save all documents before starting to build // might need an option to turn off at some point // Also should be moved into the builder and there try to find target(s) for the given item and then just save the documents of that target -> list?? if( ICore::self()->activeSession()->config()->group("Project Manager").readEntry( "Save All Documents Before Building", true ) ) { ICore::self()->documentController()->saveAllDocuments( IDocument::Silent ); } ExecuteCompositeJob::start(); } diff --git a/project/importprojectjob.cpp b/project/importprojectjob.cpp index 090d319d87..acc13d9c51 100644 --- a/project/importprojectjob.cpp +++ b/project/importprojectjob.cpp @@ -1,116 +1,116 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "importprojectjob.h" #include "projectmodel.h" #include #include #include #include #include #include #include #include #include namespace KDevelop { class ImportProjectJobPrivate { public: ImportProjectJobPrivate() : cancel(false) {} ProjectFolderItem *m_folder; IProjectFileManager *m_importer; QFutureWatcher *m_watcher; QPointer m_project; bool cancel; void import(ProjectFolderItem* folder) { foreach(ProjectFolderItem* sub, m_importer->parse(folder)) { if(!cancel) { import(sub); } } } }; ImportProjectJob::ImportProjectJob(ProjectFolderItem *folder, IProjectFileManager *importer) - : KJob(0), d(new ImportProjectJobPrivate ) + : KJob(nullptr), d(new ImportProjectJobPrivate ) { d->m_importer = importer; d->m_folder = folder; d->m_project = folder->project(); setObjectName(i18n("Project Import: %1", d->m_project->name())); connect(ICore::self(), &ICore::aboutToShutdown, this, &ImportProjectJob::aboutToShutdown); } ImportProjectJob::~ImportProjectJob() { delete d; } void ImportProjectJob::start() { d->m_watcher = new QFutureWatcher(); connect(d->m_watcher, &QFutureWatcher::finished, this, &ImportProjectJob::importDone); connect(d->m_watcher, &QFutureWatcher::canceled, this, &ImportProjectJob::importCanceled); QFuture f = QtConcurrent::run(d, &ImportProjectJobPrivate::import, d->m_folder); d->m_watcher->setFuture(f); } void ImportProjectJob::importDone() { d->m_watcher->deleteLater(); /* Goodbye to the QFutureWatcher */ emitResult(); } bool ImportProjectJob::doKill() { d->m_watcher->cancel(); d->cancel=true; setError(1); setErrorText(i18n("Project import canceled.")); d->m_watcher->waitForFinished(); return true; } void ImportProjectJob::aboutToShutdown() { kill(); } void ImportProjectJob::importCanceled() { d->m_watcher->deleteLater(); } } diff --git a/project/interfaces/iprojectbuilder.cpp b/project/interfaces/iprojectbuilder.cpp index 8fb0317c32..a14218579e 100644 --- a/project/interfaces/iprojectbuilder.cpp +++ b/project/interfaces/iprojectbuilder.cpp @@ -1,46 +1,46 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "iprojectbuilder.h" namespace KDevelop { IProjectBuilder::~IProjectBuilder() { } KJob* IProjectBuilder::configure(IProject*) { - return 0; + return nullptr; } KJob* IProjectBuilder::prune(IProject*) { - return 0; + return nullptr; } QList< IProjectBuilder* > IProjectBuilder::additionalBuilderPlugins( IProject* project ) const { Q_UNUSED( project ) return QList< IProjectBuilder* >(); } } diff --git a/project/projectchangesmodel.cpp b/project/projectchangesmodel.cpp index f8a358be9c..0091d192f1 100644 --- a/project/projectchangesmodel.cpp +++ b/project/projectchangesmodel.cpp @@ -1,280 +1,280 @@ /* 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 #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 0; + 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() : 0; + 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); 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; iitemFromIndex(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/project/projectitemlineedit.cpp b/project/projectitemlineedit.cpp index d3dcd140fd..4a3853c784 100644 --- a/project/projectitemlineedit.cpp +++ b/project/projectitemlineedit.cpp @@ -1,254 +1,254 @@ /*************************************************************************** * Copyright 2008 Aleix Pol * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "projectitemlineedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "projectproxymodel.h" static const QChar sep = '/'; static const QChar escape = '\\'; class ProjectItemCompleter : public QCompleter { Q_OBJECT public: - ProjectItemCompleter(QObject* parent=0); + ProjectItemCompleter(QObject* parent=nullptr); QString separator() const { return sep; } QStringList splitPath(const QString &path) const override; QString pathFromIndex(const QModelIndex& index) const override; void setBaseItem( KDevelop::ProjectBaseItem* item ) { mBase = item; } private: KDevelop::ProjectModel* mModel; KDevelop::ProjectBaseItem* mBase; }; class ProjectItemValidator : public QValidator { Q_OBJECT public: - ProjectItemValidator(QObject* parent = 0 ); + ProjectItemValidator(QObject* parent = nullptr ); QValidator::State validate( QString& input, int& pos ) const override; void setBaseItem( KDevelop::ProjectBaseItem* item ) { mBase = item; } private: KDevelop::ProjectBaseItem* mBase; }; ProjectItemCompleter::ProjectItemCompleter(QObject* parent) : QCompleter(parent) , mModel(KDevelop::ICore::self()->projectController()->projectModel()) - , mBase( 0 ) + , mBase( nullptr ) { setModel(mModel); setCaseSensitivity( Qt::CaseInsensitive ); } QStringList ProjectItemCompleter::splitPath(const QString& path) const { return joinProjectBasePath( KDevelop::splitWithEscaping( path, sep, escape ), mBase ); } QString ProjectItemCompleter::pathFromIndex(const QModelIndex& index) const { QString postfix; if(mModel->itemFromIndex(index)->folder()) postfix=sep; return KDevelop::joinWithEscaping(removeProjectBasePath( mModel->pathFromIndex(index), mBase ), sep, escape)+postfix; } -ProjectItemValidator::ProjectItemValidator(QObject* parent): QValidator(parent), mBase(0) +ProjectItemValidator::ProjectItemValidator(QObject* parent): QValidator(parent), mBase(nullptr) { } QValidator::State ProjectItemValidator::validate(QString& input, int& pos) const { Q_UNUSED( pos ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QStringList path = joinProjectBasePath( KDevelop::splitWithEscaping( input, sep, escape ), mBase ); QModelIndex idx = model->pathToIndex( path ); QValidator::State state = input.isEmpty() ? QValidator::Intermediate : QValidator::Invalid; if( idx.isValid() ) { state = QValidator::Acceptable; } else if( path.count() > 1 ) { // Check beginning of path and if that is ok, then try to find a child QString end = path.takeLast(); idx = model->pathToIndex( path ); if( idx.isValid() ) { for( int i = 0; i < model->rowCount( idx ); i++ ) { if( model->data( model->index( i, 0, idx ) ).toString().startsWith( end, Qt::CaseInsensitive ) ) { state = QValidator::Intermediate; break; } } } } else if( path.count() == 1 ) { // Check for a project whose name beings with the input QString first = path.first(); foreach( KDevelop::IProject* project, KDevelop::ICore::self()->projectController()->projects() ) { if( project->name().startsWith( first, Qt::CaseInsensitive ) ) { state = QValidator::Intermediate; break; } } } return state; } ProjectItemLineEdit::ProjectItemLineEdit(QWidget* parent) : QLineEdit(parent), - m_base(0), + m_base(nullptr), m_completer( new ProjectItemCompleter( this ) ), m_validator( new ProjectItemValidator( this ) ), - m_suggestion( 0 ) + m_suggestion( nullptr ) { setCompleter( m_completer ); setValidator( m_validator ); setPlaceholderText( i18n("Enter the path to an item from the projects tree" ) ); QAction* selectItemAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-document")), i18n("Select..."), this); connect(selectItemAction, &QAction::triggered, this, &ProjectItemLineEdit::selectItemDialog); addAction(selectItemAction); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &ProjectItemLineEdit::customContextMenuRequested, this, &ProjectItemLineEdit::showCtxMenu); } void ProjectItemLineEdit::showCtxMenu(const QPoint& p) { QScopedPointer menu(createStandardContextMenu()); menu->addActions(actions()); menu->exec(mapToGlobal(p)); } bool ProjectItemLineEdit::selectItemDialog() { KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel(); QWidget* w=new QWidget; w->setLayout(new QVBoxLayout(w)); QTreeView* view = new QTreeView(w); ProjectProxyModel* proxymodel = new ProjectProxyModel(view); proxymodel->setSourceModel(model); view->header()->hide(); view->setModel(proxymodel); view->setSelectionMode(QAbstractItemView::SingleSelection); w->layout()->addWidget(new QLabel(i18n("Select the item you want to get the path from."))); w->layout()->addWidget(view); QDialog dialog; dialog.setWindowTitle(i18n("Select an item...")); QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(w); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); mainLayout->addWidget(buttonBox); if (m_suggestion) { const QModelIndex idx = proxymodel->proxyIndexFromItem(m_suggestion->projectItem()); view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); } int res = dialog.exec(); if(res==QDialog::Accepted && view->selectionModel()->hasSelection()) { QModelIndex idx=proxymodel->mapToSource(view->selectionModel()->selectedIndexes().first()); setText(KDevelop::joinWithEscaping(model->pathFromIndex(idx), sep, escape)); selectAll(); return true; } return false; } void ProjectItemLineEdit::setItemPath(const QStringList& list) { setText( KDevelop::joinWithEscaping( removeProjectBasePath( list, m_base ), sep, escape ) ); } QStringList ProjectItemLineEdit::itemPath() const { return joinProjectBasePath( KDevelop::splitWithEscaping( text(), sep, escape ), m_base ); } void ProjectItemLineEdit::setBaseItem(KDevelop::ProjectBaseItem* item) { m_base = item; m_validator->setBaseItem( m_base ); m_completer->setBaseItem( m_base ); } KDevelop::ProjectBaseItem* ProjectItemLineEdit::baseItem() const { return m_base; } KDevelop::ProjectBaseItem* ProjectItemLineEdit::currentItem() const { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); return model->itemFromIndex(model->pathToIndex(KDevelop::splitWithEscaping(text(),'/', '\\'))); } void ProjectItemLineEdit::setSuggestion(KDevelop::IProject* project) { m_suggestion = project; } #include "projectitemlineedit.moc" #include "moc_projectitemlineedit.cpp" diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp index 70bcaba3b5..bdf907ce44 100644 --- a/project/projectmodel.cpp +++ b/project/projectmodel.cpp @@ -1,1170 +1,1170 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2007 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectmodel.h" #include #include #include #include #include #include #include #include #include #include "interfaces/iprojectfilemanager.h" #include #include "path.h" namespace KDevelop { QStringList removeProjectBasePath( const QStringList& fullpath, KDevelop::ProjectBaseItem* item ) { QStringList result = fullpath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QStringList basePath = model->pathFromIndex( model->indexFromItem( item ) ); if( basePath.count() >= fullpath.count() ) { return QStringList(); } for( int i = 0; i < basePath.count(); i++ ) { result.takeFirst(); } } return result; } QStringList joinProjectBasePath( const QStringList& partialpath, KDevelop::ProjectBaseItem* item ) { QStringList basePath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); basePath = model->pathFromIndex( model->indexFromItem( item ) ); } return basePath + partialpath; } inline uint indexForPath( const Path& path ) { return IndexedString::indexForString(path.pathOrUrl()); } class ProjectModelPrivate { public: ProjectModelPrivate( ProjectModel* model ): model( model ) { } ProjectBaseItem* rootItem; ProjectModel* model; ProjectBaseItem* itemFromIndex( const QModelIndex& idx ) { if( !idx.isValid() ) { return rootItem; } if( idx.model() != model ) { - return 0; + return nullptr; } return model->itemFromIndex( idx ); } // a hash of IndexedString::indexForString(path) <-> ProjectBaseItem for fast lookup QMultiHash pathLookupTable; }; class ProjectBaseItemPrivate { public: - ProjectBaseItemPrivate() : project(0), parent(0), row(-1), model(0), m_pathIndex(0) {} + ProjectBaseItemPrivate() : project(nullptr), parent(nullptr), row(-1), model(nullptr), m_pathIndex(0) {} IProject* project; ProjectBaseItem* parent; int row; QList children; QString text; ProjectBaseItem::ProjectItemType type; Qt::ItemFlags flags; ProjectModel* model; Path m_path; uint m_pathIndex; QString iconName; ProjectBaseItem::RenameStatus renameBaseItem(ProjectBaseItem* item, const QString& newName) { if (item->parent()) { foreach(ProjectBaseItem* sibling, item->parent()->children()) { if (sibling->text() == newName) { return ProjectBaseItem::ExistingItemSameName; } } } item->setText( newName ); return ProjectBaseItem::RenameOk; } ProjectBaseItem::RenameStatus renameFileOrFolder(ProjectBaseItem* item, const QString& newName) { Q_ASSERT(item->file() || item->folder()); if (newName.contains('/')) { return ProjectBaseItem::InvalidNewName; } if (item->text() == newName) { return ProjectBaseItem::RenameOk; } Path newPath = item->path(); newPath.setLastPathSegment(newName); auto job = KIO::stat(newPath.toUrl(), KIO::StatJob::SourceSide, 0); if (job->exec()) { // file/folder exists already return ProjectBaseItem::ExistingItemSameName; } if( !item->project() || !item->project()->projectFileManager() ) { return renameBaseItem(item, newName); } else if( item->folder() && item->project()->projectFileManager()->renameFolder(item->folder(), newPath) ) { return ProjectBaseItem::RenameOk; } else if ( item->file() && item->project()->projectFileManager()->renameFile(item->file(), newPath) ) { return ProjectBaseItem::RenameOk; } else { return ProjectBaseItem::ProjectManagerRenameFailed; } } }; ProjectBaseItem::ProjectBaseItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : d_ptr(new ProjectBaseItemPrivate) { Q_ASSERT(!name.isEmpty() || !parent); Q_D(ProjectBaseItem); d->project = project; d->text = name; d->flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if( parent ) { parent->appendRow( this ); } } ProjectBaseItem::~ProjectBaseItem() { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } if( parent() ) { parent()->takeRow( d->row ); } else if( model() ) { model()->takeRow( d->row ); } removeRows(0, d->children.size()); delete d; } ProjectBaseItem* ProjectBaseItem::child( int row ) const { Q_D(const ProjectBaseItem); if( row < 0 || row >= d->children.length() ) { - return 0; + return nullptr; } return d->children.at( row ); } QList< ProjectBaseItem* > ProjectBaseItem::children() const { Q_D(const ProjectBaseItem); return d->children; } ProjectBaseItem* ProjectBaseItem::takeRow(int row) { Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row < d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row); } ProjectBaseItem* olditem = d->children.takeAt( row ); - olditem->d_func()->parent = 0; + olditem->d_func()->parent = nullptr; olditem->d_func()->row = -1; - olditem->setModel( 0 ); + olditem->setModel( nullptr ); for(int i=row; id_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } if( model() ) { model()->endRemoveRows(); } return olditem; } void ProjectBaseItem::removeRow( int row ) { delete takeRow( row ); } void ProjectBaseItem::removeRows(int row, int count) { if (!count) { return; } Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row + count <= d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row + count - 1); } //NOTE: we unset parent, row and model manually to speed up the deletion if (row == 0 && count == d->children.size()) { // optimize if we want to delete all foreach(ProjectBaseItem* item, d->children) { - item->d_func()->parent = 0; + item->d_func()->parent = nullptr; item->d_func()->row = -1; - item->setModel( 0 ); + item->setModel( nullptr ); delete item; } d->children.clear(); } else { for (int i = row; i < count; ++i) { ProjectBaseItem* item = d->children.at(i); - item->d_func()->parent = 0; + item->d_func()->parent = nullptr; item->d_func()->row = -1; - item->setModel( 0 ); + item->setModel( nullptr ); delete d->children.takeAt( row ); } for(int i = row; i < d->children.size(); ++i) { d->children.at(i)->d_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } } if( model() ) { model()->endRemoveRows(); } } QModelIndex ProjectBaseItem::index() const { if( model() ) { return model()->indexFromItem( this ); } return QModelIndex(); } int ProjectBaseItem::rowCount() const { Q_D(const ProjectBaseItem); return d->children.count(); } int ProjectBaseItem::type() const { return ProjectBaseItem::BaseItem; } ProjectModel* ProjectBaseItem::model() const { Q_D(const ProjectBaseItem); return d->model; } ProjectBaseItem* ProjectBaseItem::parent() const { Q_D(const ProjectBaseItem); if( model() && model()->d->rootItem == d->parent ) { - return 0; + return nullptr; } return d->parent; } int ProjectBaseItem::row() const { Q_D(const ProjectBaseItem); return d->row; } QString ProjectBaseItem::text() const { Q_D(const ProjectBaseItem); if( project() && !parent() ) { return project()->name(); } else { return d->text; } } void ProjectBaseItem::setModel( ProjectModel* model ) { Q_D(ProjectBaseItem); if (model == d->model) { return; } if (d->model && d->m_pathIndex) { d->model->d->pathLookupTable.remove(d->m_pathIndex, this); } d->model = model; if (model && d->m_pathIndex) { model->d->pathLookupTable.insert(d->m_pathIndex, this); } foreach( ProjectBaseItem* item, d->children ) { item->setModel( model ); } } void ProjectBaseItem::setRow( int row ) { Q_D(ProjectBaseItem); d->row = row; } void ProjectBaseItem::setText( const QString& text ) { Q_ASSERT(!text.isEmpty() || !parent()); Q_D(ProjectBaseItem); d->text = text; if( d->model ) { QModelIndex idx = index(); emit d->model->dataChanged(idx, idx); } } ProjectBaseItem::RenameStatus ProjectBaseItem::rename(const QString& newName) { Q_D(ProjectBaseItem); return d->renameBaseItem(this, newName); } KDevelop::ProjectBaseItem::ProjectItemType baseType( int type ) { if( type == KDevelop::ProjectBaseItem::Folder || type == KDevelop::ProjectBaseItem::BuildFolder ) return KDevelop::ProjectBaseItem::Folder; if( type == KDevelop::ProjectBaseItem::Target || type == KDevelop::ProjectBaseItem::ExecutableTarget || type == KDevelop::ProjectBaseItem::LibraryTarget) return KDevelop::ProjectBaseItem::Target; return static_cast( type ); } bool ProjectBaseItem::lessThan( const KDevelop::ProjectBaseItem* item ) const { if(item->type() >= KDevelop::ProjectBaseItem::CustomProjectItemType ) { // For custom types we want to make sure that if they override lessThan, then we // prefer their lessThan implementation return !item->lessThan( this ); } KDevelop::ProjectBaseItem::ProjectItemType leftType=baseType(type()), rightType=baseType(item->type()); if(leftType==rightType) { if(leftType==KDevelop::ProjectBaseItem::File) { return file()->fileName().compare(item->file()->fileName(), Qt::CaseInsensitive) < 0; } return this->text()text(); } else { return leftTypepath() < item2->path(); } IProject* ProjectBaseItem::project() const { Q_D(const ProjectBaseItem); return d->project; } void ProjectBaseItem::appendRow( ProjectBaseItem* item ) { Q_D(ProjectBaseItem); if( !item ) { return; } if( item->parent() ) { // Proper way is to first removeRow() on the original parent, then appendRow on this one qWarning() << "Ignoring double insertion of item" << item; return; } // this is too slow... O(n) and thankfully not a problem anyways // Q_ASSERT(!d->children.contains(item)); int startrow,endrow; if( model() ) { startrow = endrow = d->children.count(); model()->beginInsertRows(index(), startrow, endrow); } d->children.append( item ); item->setRow( d->children.count() - 1 ); item->d_func()->parent = this; item->setModel( model() ); if( model() ) { model()->endInsertRows(); } } Path ProjectBaseItem::path() const { Q_D(const ProjectBaseItem); return d->m_path; } QString ProjectBaseItem::baseName() const { return text(); } void ProjectBaseItem::setPath( const Path& path) { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } d->m_path = path; d->m_pathIndex = indexForPath(path); setText( path.lastPathSegment() ); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.insert(d->m_pathIndex, this); } } Qt::ItemFlags ProjectBaseItem::flags() { Q_D(ProjectBaseItem); return d->flags; } Qt::DropActions ProjectModel::supportedDropActions() const { return (Qt::DropActions)(Qt::MoveAction); } void ProjectBaseItem::setFlags(Qt::ItemFlags flags) { Q_D(ProjectBaseItem); d->flags = flags; if(d->model) d->model->dataChanged(index(), index()); } QString ProjectBaseItem::iconName() const { return QString(); } ProjectFolderItem *ProjectBaseItem::folder() const { - return 0; + return nullptr; } ProjectTargetItem *ProjectBaseItem::target() const { - return 0; + return nullptr; } ProjectExecutableTargetItem *ProjectBaseItem::executable() const { - return 0; + return nullptr; } ProjectFileItem *ProjectBaseItem::file() const { - return 0; + return nullptr; } QList ProjectBaseItem::folderList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Folder || item->type() == BuildFolder ) { ProjectFolderItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::targetList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Target || item->type() == LibraryTarget || item->type() == ExecutableTarget ) { ProjectTargetItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::fileList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); Q_ASSERT(item); if ( item && item->type() == File ) { ProjectFileItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } void ProjectModel::clear() { d->rootItem->removeRows(0, d->rootItem->rowCount()); } ProjectFolderItem::ProjectFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setPath( path ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project && project->path() != path) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::ProjectFolderItem( const QString & name, ProjectBaseItem * parent ) : ProjectBaseItem( parent->project(), name, parent ) { setPath( Path(parent->path(), name) ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project() && project()->path() != path()) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::~ProjectFolderItem() { } void ProjectFolderItem::setPath( const Path& path ) { ProjectBaseItem::setPath(path); propagateRename(path); } ProjectFolderItem *ProjectFolderItem::folder() const { return const_cast(this); } int ProjectFolderItem::type() const { return ProjectBaseItem::Folder; } QString ProjectFolderItem::folderName() const { return baseName(); } void ProjectFolderItem::propagateRename( const Path& newBase ) const { Path path = newBase; path.addPath(QStringLiteral("dummy")); foreach( KDevelop::ProjectBaseItem* child, children() ) { path.setLastPathSegment( child->text() ); child->setPath( path ); const ProjectFolderItem* folder = child->folder(); if ( folder ) { folder->propagateRename( path ); } } } ProjectBaseItem::RenameStatus ProjectFolderItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } bool ProjectFolderItem::hasFileOrFolder(const QString& name) const { foreach ( ProjectBaseItem* item, children() ) { if ( (item->type() == Folder || item->type() == File || item->type() == BuildFolder) && name == item->baseName() ) { return true; } } return false; } bool ProjectBaseItem::isProjectRoot() const { - return parent()==0; + return parent()==nullptr; } ProjectBuildFolderItem::ProjectBuildFolderItem(IProject* project, const Path& path, ProjectBaseItem *parent) : ProjectFolderItem( project, path, parent ) { } ProjectBuildFolderItem::ProjectBuildFolderItem( const QString& name, ProjectBaseItem* parent ) : ProjectFolderItem( name, parent ) { } QString ProjectFolderItem::iconName() const { return QStringLiteral("folder"); } int ProjectBuildFolderItem::type() const { return ProjectBaseItem::BuildFolder; } QString ProjectBuildFolderItem::iconName() const { return QStringLiteral("folder-development"); } ProjectFileItem::ProjectFileItem( IProject* project, const Path& path, ProjectBaseItem* parent ) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( path ); } ProjectFileItem::ProjectFileItem( const QString& name, ProjectBaseItem* parent ) : ProjectBaseItem( parent->project(), name, parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( Path(parent->path(), name) ); } ProjectFileItem::~ProjectFileItem() { if( project() && d_ptr->m_pathIndex ) { project()->removeFromFileSet( this ); } } IndexedString ProjectFileItem::indexedPath() const { return IndexedString::fromIndex( d_ptr->m_pathIndex ); } ProjectBaseItem::RenameStatus ProjectFileItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } QString ProjectFileItem::fileName() const { return baseName(); } // Maximum length of a string to still consider it as a file extension which we cache // This has to be a slow value, so that we don't fill our file extension cache with crap static const int maximumCacheExtensionLength = 3; bool isNumeric(const QStringRef& str) { int len = str.length(); if(len == 0) return false; for(int a = 0; a < len; ++a) if(!str.at(a).isNumber()) return false; return true; } class IconNameCache { public: QString iconNameForPath(const Path& path, const QString& fileName) { // find icon name based on file extension, if possible QString extension; int extensionStart = fileName.lastIndexOf(QLatin1Char('.')); if( extensionStart != -1 && fileName.length() - extensionStart - 1 <= maximumCacheExtensionLength ) { QStringRef extRef = fileName.midRef(extensionStart + 1); if( isNumeric(extRef) ) { // don't cache numeric extensions extRef.clear(); } if( !extRef.isEmpty() ) { extension = extRef.toString(); QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = fileExtensionToIcon.constFind( extension ); if( it != fileExtensionToIcon.constEnd() ) { return *it; } } } QMimeType mime = QMimeDatabase().mimeTypeForFile(path.lastPathSegment(), QMimeDatabase::MatchExtension); // no I/O QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = mimeToIcon.constFind(mime.name()); QString iconName; if ( it == mimeToIcon.constEnd() ) { iconName = mime.iconName(); if (iconName.isEmpty()) { iconName = QStringLiteral("none"); } mimeToIcon.insert(mime.name(), iconName); } else { iconName = *it; } if ( !extension.isEmpty() ) { fileExtensionToIcon.insert(extension, iconName); } return iconName; } QMutex mutex; QHash mimeToIcon; QHash fileExtensionToIcon; }; Q_GLOBAL_STATIC(IconNameCache, s_cache); QString ProjectFileItem::iconName() const { // think of d_ptr->iconName as mutable, possible since d_ptr is not const if (d_ptr->iconName.isEmpty()) { // lazy load implementation of icon lookup d_ptr->iconName = s_cache->iconNameForPath( d_ptr->m_path, d_ptr->text ); // we should always get *some* icon name back Q_ASSERT(!d_ptr->iconName.isEmpty()); } return d_ptr->iconName; } void ProjectFileItem::setPath( const Path& path ) { if (path == d_ptr->m_path) { return; } if( project() && d_ptr->m_pathIndex ) { // remove from fileset if we are in there project()->removeFromFileSet( this ); } ProjectBaseItem::setPath( path ); if( project() && d_ptr->m_pathIndex ) { // add to fileset with new path project()->addToFileSet( this ); } // invalidate icon name for future lazy-loaded updated d_ptr->iconName.clear(); } int ProjectFileItem::type() const { return ProjectBaseItem::File; } ProjectFileItem *ProjectFileItem::file() const { return const_cast( this ); } ProjectTargetItem::ProjectTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectBaseItem( project, name, parent ) { setFlags(flags() | Qt::ItemIsDropEnabled); } QString ProjectTargetItem::iconName() const { return QStringLiteral("system-run"); } void ProjectTargetItem::setPath( const Path& path ) { // don't call base class, it calls setText with the new path's filename // which we do not want for target items d_ptr->m_path = path; } int ProjectTargetItem::type() const { return ProjectBaseItem::Target; } ProjectTargetItem *ProjectTargetItem::target() const { return const_cast( this ); } ProjectExecutableTargetItem::ProjectExecutableTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) { } ProjectExecutableTargetItem *ProjectExecutableTargetItem::executable() const { return const_cast( this ); } int ProjectExecutableTargetItem::type() const { return ProjectBaseItem::ExecutableTarget; } ProjectLibraryTargetItem::ProjectLibraryTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) {} int ProjectLibraryTargetItem::type() const { return ProjectBaseItem::LibraryTarget; } QModelIndex ProjectModel::pathToIndex(const QStringList& tofetch_) const { if(tofetch_.isEmpty()) return QModelIndex(); QStringList tofetch(tofetch_); if(tofetch.last().isEmpty()) tofetch.takeLast(); QModelIndex current=index(0,0, QModelIndex()); QModelIndex ret; for(int a = 0; a < tofetch.size(); ++a) { const QString& currentName = tofetch[a]; bool matched = false; QModelIndexList l = match(current, Qt::DisplayRole, currentName, -1, Qt::MatchExactly); foreach(const QModelIndex& idx, l) { //If this is not the last item, only match folders, as there may be targets and folders with the same name if(a == tofetch.size()-1 || itemFromIndex(idx)->folder()) { ret = idx; current = index(0,0, ret); matched = true; break; } } if(!matched) { ret = QModelIndex(); break; } } Q_ASSERT(!ret.isValid() || data(ret).toString()==tofetch.last()); return ret; } QStringList ProjectModel::pathFromIndex(const QModelIndex& index) const { if (!index.isValid()) return QStringList(); QModelIndex idx = index; QStringList list; do { QString t = data(idx, Qt::DisplayRole).toString(); list.prepend(t); QModelIndex parent = idx.parent(); idx = parent.sibling(parent.row(), index.column()); } while (idx.isValid()); return list; } int ProjectModel::columnCount( const QModelIndex& ) const { return 1; } int ProjectModel::rowCount( const QModelIndex& parent ) const { ProjectBaseItem* item = d->itemFromIndex( parent ); return item ? item->rowCount() : 0; } QModelIndex ProjectModel::parent( const QModelIndex& child ) const { if( child.isValid() ) { ProjectBaseItem* item = static_cast( child.internalPointer() ); return indexFromItem( item ); } return QModelIndex(); } QModelIndex ProjectModel::indexFromItem( const ProjectBaseItem* item ) const { if( item && item->d_func()->parent ) { return createIndex( item->row(), 0, item->d_func()->parent ); } return QModelIndex(); } ProjectBaseItem* ProjectModel::itemFromIndex( const QModelIndex& index ) const { if( index.row() >= 0 && index.column() == 0 && index.model() == this ) { ProjectBaseItem* parent = static_cast( index.internalPointer() ); if( parent ) { return parent->child( index.row() ); } } - return 0; + return nullptr; } QVariant ProjectModel::data( const QModelIndex& index, int role ) const { static const QSet allowedRoles = { Qt::DisplayRole, Qt::ToolTipRole, Qt::DecorationRole, ProjectItemRole, ProjectRole, UrlRole }; if( allowedRoles.contains(role) && index.isValid() ) { ProjectBaseItem* item = itemFromIndex( index ); if( item ) { switch(role) { case Qt::DecorationRole: return QIcon::fromTheme(item->iconName()); case Qt::ToolTipRole: return item->path().pathOrUrl(); case Qt::DisplayRole: return item->text(); case ProjectItemRole: return QVariant::fromValue(item); case UrlRole: return item->path().toUrl(); case ProjectRole: return QVariant::fromValue(item->project()); } } } return QVariant(); } ProjectModel::ProjectModel( QObject *parent ) : QAbstractItemModel( parent ), d( new ProjectModelPrivate( this ) ) { - d->rootItem = new ProjectBaseItem( 0, QString(), 0 ); + d->rootItem = new ProjectBaseItem( nullptr, QString(), nullptr ); d->rootItem->setModel( this ); } ProjectModel::~ProjectModel() { d->rootItem->setModel(nullptr); delete d->rootItem; delete d; } ProjectVisitor::ProjectVisitor() { } QModelIndex ProjectModel::index( int row, int column, const QModelIndex& parent ) const { ProjectBaseItem* parentItem = d->itemFromIndex( parent ); if( parentItem && row >= 0 && row < parentItem->rowCount() && column == 0 ) { return createIndex( row, column, parentItem ); } return QModelIndex(); } void ProjectModel::appendRow( ProjectBaseItem* item ) { d->rootItem->appendRow( item ); } void ProjectModel::removeRow( int row ) { d->rootItem->removeRow( row ); } ProjectBaseItem* ProjectModel::takeRow( int row ) { return d->rootItem->takeRow( row ); } ProjectBaseItem* ProjectModel::itemAt(int row) const { return d->rootItem->child(row); } QList< ProjectBaseItem* > ProjectModel::topItems() const { return d->rootItem->children(); } Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const { ProjectBaseItem* item = itemFromIndex( index ); if(item) return item->flags(); else - return 0; + return nullptr; } bool ProjectModel::insertColumns(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::insertRows(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::setData(const QModelIndex&, const QVariant&, int) { // Not supported return false; } QList ProjectModel::itemsForPath(const IndexedString& path) const { return d->pathLookupTable.values(path.index()); } ProjectBaseItem* ProjectModel::itemForPath(const IndexedString& path) const { return d->pathLookupTable.value(path.index()); } void ProjectVisitor::visit( ProjectModel* model ) { foreach( ProjectBaseItem* item, model->topItems() ) { visit( item->project() ); } } void ProjectVisitor::visit ( IProject* prj ) { visit( prj->projectItem() ); } void ProjectVisitor::visit ( ProjectBuildFolderItem* folder ) { visit(static_cast(folder)); } void ProjectVisitor::visit ( ProjectExecutableTargetItem* exec ) { foreach( ProjectFileItem* item, exec->fileList() ) { visit( item ); } } void ProjectVisitor::visit ( ProjectFolderItem* folder ) { foreach( ProjectFileItem* item, folder->fileList() ) { visit( item ); } foreach( ProjectTargetItem* item, folder->targetList() ) { if( item->type() == ProjectBaseItem::LibraryTarget ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::ExecutableTarget ) { visit( dynamic_cast( item ) ); } } foreach( ProjectFolderItem* item, folder->folderList() ) { if( item->type() == ProjectBaseItem::BuildFolder ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::Folder ) { visit( dynamic_cast( item ) ); } } } void ProjectVisitor::visit ( ProjectFileItem* ) { } void ProjectVisitor::visit ( ProjectLibraryTargetItem* lib ) { foreach( ProjectFileItem* item, lib->fileList() ) { visit( item ); } } ProjectVisitor::~ProjectVisitor() { } } diff --git a/project/tests/projectmodelperformancetest.cpp b/project/tests/projectmodelperformancetest.cpp index 7bc26f5a68..b535acc1c1 100644 --- a/project/tests/projectmodelperformancetest.cpp +++ b/project/tests/projectmodelperformancetest.cpp @@ -1,207 +1,207 @@ /*************************************************************************** * Copyright 2010 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "projectmodelperformancetest.h" #include #include #include #include #include #include #include #include #include #include #include #include // Knobs to increase/decrease the amount of items being generated #define SMALL_DEPTH 2 #define SMALL_WIDTH 10 #define BIG_DEPTH 3 #define BIG_WIDTH 10 #define INIT_WIDTH 10 #define INIT_DEPTH 3 #include using KDevelop::ProjectModel; using KDevelop::ProjectFolderItem; using KDevelop::ProjectBaseItem; using KDevelop::ProjectFileItem; using KDevelop::Path; void generateChilds( ProjectBaseItem* parent, int count, int depth ) { for( int i = 0; i < 10; i++ ) { if( depth > 0 ) { ProjectFolderItem* item = new ProjectFolderItem( QStringLiteral( "f%1" ).arg( i ), parent ); generateChilds( item, count, depth - 1 ); } else { new ProjectFileItem( QStringLiteral( "f%1" ).arg( i ), parent ); } } } ProjectModelPerformanceTest::ProjectModelPerformanceTest(QWidget* parent ) : QWidget(parent) { QGridLayout * l = new QGridLayout( this ); setLayout( l ); view = new QTreeView( this ); // This is used so the treeview layout performance is not influencing the test view->setUniformRowHeights( true ); QPushButton* b = new QPushButton( QStringLiteral("Expand All"), this ); connect( b, &QPushButton::clicked, view, &QTreeView::expandAll ); l->addWidget( b, 0, 0 ); b = new QPushButton( QStringLiteral("Collapse All"), this ); connect( b, &QPushButton::clicked, view, &QTreeView::collapseAll ); l->addWidget( b, 0, 1 ); b = new QPushButton( QStringLiteral("Add Small Subtree"), this ); connect( b, &QPushButton::clicked, this, &ProjectModelPerformanceTest::addSmallTree ); l->addWidget( b, 0, 2 ); b = new QPushButton( QStringLiteral("Add Big Subtree"), this ); connect( b, &QPushButton::clicked, this, &ProjectModelPerformanceTest::addBigTree ); l->addWidget( b, 0, 3 ); b = new QPushButton( QStringLiteral("Add Big Subtree in Chunks"), this ); connect( b, &QPushButton::clicked, this, &ProjectModelPerformanceTest::addBigTreeDelayed ); l->addWidget( b, 0, 4 ); l->addWidget( view, 1, 0, 1, 6 ); } void ProjectModelPerformanceTest::init() { QElapsedTimer timer; timer.start(); KDevelop::AutoTestShell::init(); KDevelop::TestCore* core = new KDevelop::TestCore; core->setPluginController(new KDevelop::TestPluginController(core)); core->initialize(); qDebug() << "init core" << timer.elapsed(); timer.start(); model = new KDevelop::ProjectModel( this ); qDebug() << "create model" << timer.elapsed(); timer.start(); for( int i = 0; i < INIT_WIDTH; i++ ) { - ProjectFolderItem* item = new ProjectFolderItem( 0, Path( QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) ) ) ); + ProjectFolderItem* item = new ProjectFolderItem( nullptr, Path( QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) ) ) ); generateChilds( item, INIT_WIDTH, INIT_DEPTH ); model->appendRow( item ); } qDebug() << "init model" << timer.elapsed(); timer.start(); view->setModel( model ); qDebug() << "set model" << timer.elapsed(); timer.start(); } ProjectModelPerformanceTest::~ProjectModelPerformanceTest() { KDevelop::TestCore::shutdown(); QApplication::quit(); } void ProjectModelPerformanceTest::addBigTree() { QElapsedTimer timer; timer.start(); for( int i = 0; i < BIG_WIDTH; i++ ) { - ProjectFolderItem* item = new ProjectFolderItem( 0, Path( QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) ) ) ); + ProjectFolderItem* item = new ProjectFolderItem( nullptr, Path( QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) ) ) ); generateChilds( item, BIG_WIDTH, BIG_DEPTH ); model->appendRow( item ); } qDebug() << "addBigTree" << timer.elapsed(); } void ProjectModelPerformanceTest::addBigTreeDelayed() { originalWidth = model->rowCount(); QTimer::singleShot( 0, this, SLOT(addItemDelayed()) ); } void ProjectModelPerformanceTest::addItemDelayed() { QElapsedTimer timer; timer.start(); - ProjectBaseItem* parent = 0; + ProjectBaseItem* parent = nullptr; Path path; if( !currentParent.isEmpty() ) { parent = currentParent.top(); path = Path(parent->path(), QStringLiteral("f%1").arg(parent->rowCount())); } else { path = Path(QUrl::fromLocalFile(QStringLiteral("/f%1").arg(model->rowCount()))); } - ProjectBaseItem* item = 0; + ProjectBaseItem* item = nullptr; if( currentParent.size() < BIG_DEPTH ) { - item = new ProjectFolderItem(0, path, parent); + item = new ProjectFolderItem(nullptr, path, parent); } else { - item = new ProjectFileItem( 0, path, parent ); + item = new ProjectFileItem( nullptr, path, parent ); } if( currentParent.isEmpty() ) { model->appendRow( item ); } // Abort/Continue conditions are: // Go one level deeper (by pushing item on stack) as long as we haven't reached the max depth or the max width // else if we've reached the max width then pop, i.e go one level up // else the next run will add a sibling to the just-generated item if( currentParent.size() < BIG_DEPTH && ( currentParent.isEmpty() || currentParent.top()->rowCount() < BIG_WIDTH ) ) { currentParent.push( item ); } else if( !currentParent.isEmpty() && currentParent.top()->rowCount() >= BIG_WIDTH ) { currentParent.pop(); } if( ( currentParent.isEmpty() && ( model->rowCount() - originalWidth ) < BIG_WIDTH ) || !currentParent.isEmpty() ) { QTimer::singleShot( 0, this, SLOT(addItemDelayed()) ); } qDebug() << "addBigTreeDelayed" << timer.elapsed(); } void ProjectModelPerformanceTest::addSmallTree() { QElapsedTimer timer; timer.start(); for( int i = 0; i < SMALL_WIDTH; i++ ) { - ProjectFolderItem* item = new ProjectFolderItem( 0, Path(QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) )) ); + ProjectFolderItem* item = new ProjectFolderItem( nullptr, Path(QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) )) ); generateChilds( item, SMALL_WIDTH, SMALL_DEPTH ); model->appendRow( item ); } qDebug() << "addSmallTree" << timer.elapsed(); } int main( int argc, char** argv ) { QApplication a( argc, argv ); ProjectModelPerformanceTest* w = new ProjectModelPerformanceTest; w->show(); w->setAttribute(Qt::WA_DeleteOnClose); QMetaObject::invokeMethod(w, "init"); return a.exec(); } diff --git a/project/tests/test_projectmodel.cpp b/project/tests/test_projectmodel.cpp index ea4e3907d9..a7b48f814c 100644 --- a/project/tests/test_projectmodel.cpp +++ b/project/tests/test_projectmodel.cpp @@ -1,546 +1,546 @@ /*************************************************************************** * Copyright 2010 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_projectmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; void debugItemModel(QAbstractItemModel* m, const QModelIndex& parent=QModelIndex(), int depth=0) { Q_ASSERT(m); qDebug() << QByteArray(depth*2, '-') << m->data(parent).toString(); for(int i=0; irowCount(parent); i++) { debugItemModel(m, m->index(i, 0, parent), depth+1); } } void TestProjectModel::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); qRegisterMetaType("QModelIndex"); model = ICore::self()->projectController()->projectModel(); new ModelTest( model, this ); proxy = new ProjectProxyModel( model ); new ModelTest(proxy, proxy); proxy->setSourceModel(model); } void TestProjectModel::init() { model->clear(); } void TestProjectModel::cleanupTestCase() { TestCore::shutdown(); } void TestProjectModel::testCreateFileSystemItems() { QFETCH( int, itemType ); QFETCH( Path, itemPath ); QFETCH( Path, expectedItemPath ); QFETCH( QString, expectedItemText ); QFETCH( QStringList, expectedRelativeItemPath ); QFETCH( int, expectedItemRow ); - ProjectBaseItem* newitem = 0; + ProjectBaseItem* newitem = nullptr; switch( itemType ) { case ProjectBaseItem::Folder: - newitem = new ProjectFolderItem( 0, itemPath ); + newitem = new ProjectFolderItem( nullptr, itemPath ); break; case ProjectBaseItem::BuildFolder: - newitem = new ProjectBuildFolderItem( 0, itemPath ); + newitem = new ProjectBuildFolderItem( nullptr, itemPath ); break; case ProjectBaseItem::File: - newitem = new ProjectFileItem( 0, itemPath ); + newitem = new ProjectFileItem( nullptr, itemPath ); break; } int origRowCount = model->rowCount(); model->appendRow( newitem ); QCOMPARE( model->rowCount(), origRowCount+1 ); QCOMPARE( newitem->row(), expectedItemRow ); QModelIndex idx = model->index( expectedItemRow, 0, QModelIndex() ); QVERIFY( model->itemFromIndex( idx ) ); QCOMPARE( model->itemFromIndex( idx ), newitem ); QCOMPARE( newitem->text(), expectedItemText ); QCOMPARE( newitem->path(), expectedItemPath ); if( itemType == ProjectBaseItem::File ) { QCOMPARE( dynamic_cast( newitem )->fileName(), expectedItemText ); } if( itemType == ProjectBaseItem::Folder || itemType == ProjectBaseItem::BuildFolder ) { QCOMPARE( dynamic_cast( newitem )->folderName(), expectedItemText ); } QCOMPARE( newitem->type(), itemType ); QCOMPARE( model->data( idx ).toString(), expectedItemText ); QCOMPARE( model->indexFromItem( newitem ), idx ); QCOMPARE( model->pathFromIndex( idx ), expectedRelativeItemPath ); QCOMPARE( model->pathToIndex( expectedRelativeItemPath ), idx ); } void TestProjectModel::testCreateFileSystemItems_data() { QTest::addColumn( "itemType" ); QTest::addColumn( "itemPath" ); QTest::addColumn( "expectedItemPath" ); QTest::addColumn( "expectedItemText" ); QTest::addColumn( "expectedRelativeItemPath" ); QTest::addColumn( "expectedItemRow" ); QTest::newRow("RootFolder") << (int)ProjectBaseItem::Folder << Path(QUrl::fromLocalFile(QStringLiteral("/rootdir"))) << Path(QUrl::fromLocalFile(QStringLiteral("/rootdir/"))) << QStringLiteral("rootdir") << ( QStringList() << QStringLiteral("rootdir") ) << 0; QTest::newRow("RootBuildFolder") << (int)ProjectBaseItem::BuildFolder << Path(QUrl::fromLocalFile(QStringLiteral("/rootdir"))) << Path(QUrl::fromLocalFile(QStringLiteral("/rootdir/"))) << QStringLiteral("rootdir") << ( QStringList() << QStringLiteral("rootdir") ) << 0; QTest::newRow("RootFile") << (int)ProjectBaseItem::File << Path(QUrl::fromLocalFile(QStringLiteral("/rootfile"))) << Path(QUrl::fromLocalFile(QStringLiteral("/rootfile"))) << QStringLiteral("rootfile") << ( QStringList() << QStringLiteral("rootfile") ) << 0; } void TestProjectModel::testCreateTargetItems() { QFETCH( int, itemType ); QFETCH( QString, itemText ); QFETCH( QString, expectedItemText ); QFETCH( QStringList, expectedItemPath ); QFETCH( int, expectedItemRow ); - ProjectBaseItem* newitem = 0; + ProjectBaseItem* newitem = nullptr; switch( itemType ) { case ProjectBaseItem::Target: - newitem = new ProjectTargetItem( 0, itemText ); + newitem = new ProjectTargetItem( nullptr, itemText ); break; case ProjectBaseItem::LibraryTarget: - newitem = new ProjectLibraryTargetItem( 0, itemText ); + newitem = new ProjectLibraryTargetItem( nullptr, itemText ); break; } int origRowCount = model->rowCount(); model->appendRow( newitem ); QCOMPARE( model->rowCount(), origRowCount+1 ); QCOMPARE( newitem->row(), expectedItemRow ); QModelIndex idx = model->index( expectedItemRow, 0, QModelIndex() ); QVERIFY( model->itemFromIndex( idx ) ); QCOMPARE( model->itemFromIndex( idx ), newitem ); QCOMPARE( newitem->text(), expectedItemText ); QCOMPARE( newitem->type(), itemType ); QCOMPARE( model->data( idx ).toString(), expectedItemText ); QCOMPARE( model->indexFromItem( newitem ), idx ); QCOMPARE( model->pathFromIndex( idx ), expectedItemPath ); QCOMPARE( model->pathToIndex( expectedItemPath ), idx ); } void TestProjectModel::testCreateTargetItems_data() { QTest::addColumn( "itemType" ); QTest::addColumn( "itemText" ); QTest::addColumn( "expectedItemText" ); QTest::addColumn( "expectedItemPath" ); QTest::addColumn( "expectedItemRow" ); QTest::newRow("RootTarget") << (int)ProjectBaseItem::Target << "target" << QStringLiteral("target") << ( QStringList() << QStringLiteral("target") ) << 0; QTest::newRow("RootLibraryTarget") << (int)ProjectBaseItem::LibraryTarget << "libtarget" << QStringLiteral("libtarget") << ( QStringList() << QStringLiteral("libtarget") ) << 0; } void TestProjectModel::testChangeWithProxyModel() { QSortFilterProxyModel* proxy = new QSortFilterProxyModel( this ); proxy->setSourceModel( model ); - ProjectFolderItem* root = new ProjectFolderItem( 0, Path(QUrl::fromLocalFile(QStringLiteral("/folder1"))) ); - root->appendRow( new ProjectFileItem( 0, Path(QUrl::fromLocalFile(QStringLiteral("/folder1/file1"))) ) ); + ProjectFolderItem* root = new ProjectFolderItem( nullptr, Path(QUrl::fromLocalFile(QStringLiteral("/folder1"))) ); + root->appendRow( new ProjectFileItem( nullptr, Path(QUrl::fromLocalFile(QStringLiteral("/folder1/file1"))) ) ); model->appendRow( root ); QCOMPARE( model->rowCount(), 1 ); QCOMPARE( proxy->rowCount(), 1 ); model->removeRow( 0 ); QCOMPARE( model->rowCount(), 0 ); QCOMPARE( proxy->rowCount(), 0 ); } void TestProjectModel::testCreateSimpleHierarchy() { QString folderName = QStringLiteral("rootfolder"); QString fileName = QStringLiteral("file"); QString targetName = QStringLiteral("testtarged"); QString cppFileName = QStringLiteral("file.cpp"); - ProjectFolderItem* rootFolder = new ProjectFolderItem( 0, Path(QUrl::fromLocalFile("/"+folderName)) ); + ProjectFolderItem* rootFolder = new ProjectFolderItem( nullptr, Path(QUrl::fromLocalFile("/"+folderName)) ); QCOMPARE(rootFolder->baseName(), folderName); ProjectFileItem* file = new ProjectFileItem( fileName, rootFolder ); QCOMPARE(file->baseName(), fileName); - ProjectTargetItem* target = new ProjectTargetItem( 0, targetName ); + ProjectTargetItem* target = new ProjectTargetItem( nullptr, targetName ); rootFolder->appendRow( target ); - ProjectFileItem* targetfile = new ProjectFileItem( 0, Path(rootFolder->path(), cppFileName), target ); + ProjectFileItem* targetfile = new ProjectFileItem( nullptr, Path(rootFolder->path(), cppFileName), target ); model->appendRow( rootFolder ); QCOMPARE( model->rowCount(), 1 ); QModelIndex folderIdx = model->index( 0, 0, QModelIndex() ); QCOMPARE( model->data( folderIdx ).toString(), folderName ); QCOMPARE( model->rowCount( folderIdx ), 2 ); QCOMPARE( model->itemFromIndex( folderIdx ), rootFolder ); QVERIFY( rootFolder->hasFileOrFolder( fileName ) ); QModelIndex fileIdx = model->index( 0, 0, folderIdx ); QCOMPARE( model->data( fileIdx ).toString(), fileName ); QCOMPARE( model->rowCount( fileIdx ), 0 ); QCOMPARE( model->itemFromIndex( fileIdx ), file ); QModelIndex targetIdx = model->index( 1, 0, folderIdx ); QCOMPARE( model->data( targetIdx ).toString(), targetName ); QCOMPARE( model->rowCount( targetIdx ), 1 ); QCOMPARE( model->itemFromIndex( targetIdx ), target ); QModelIndex targetFileIdx = model->index( 0, 0, targetIdx ); QCOMPARE( model->data( targetFileIdx ).toString(), cppFileName ); QCOMPARE( model->rowCount( targetFileIdx ), 0 ); QCOMPARE( model->itemFromIndex( targetFileIdx ), targetfile ); rootFolder->removeRow( 1 ); QCOMPARE( model->rowCount( folderIdx ), 1 ); delete file; - file = 0; + file = nullptr; // Check that we also find a folder with the fileName new ProjectFolderItem( fileName, rootFolder ); QVERIFY( rootFolder->hasFileOrFolder( fileName ) ); delete rootFolder; QCOMPARE( model->rowCount(), 0 ); } void TestProjectModel::testItemSanity() { - ProjectBaseItem* parent = new ProjectBaseItem( 0, QStringLiteral("test") ); - ProjectBaseItem* child = new ProjectBaseItem( 0, QStringLiteral("test"), parent ); - ProjectBaseItem* child2 = new ProjectBaseItem( 0, QStringLiteral("ztest"), parent ); - ProjectFileItem* child3 = new ProjectFileItem( 0, Path(QUrl(QStringLiteral("file:///bcd"))), parent ); - ProjectFileItem* child4 = new ProjectFileItem( 0, Path(QUrl(QStringLiteral("file:///abcd"))), parent ); + ProjectBaseItem* parent = new ProjectBaseItem( nullptr, QStringLiteral("test") ); + ProjectBaseItem* child = new ProjectBaseItem( nullptr, QStringLiteral("test"), parent ); + ProjectBaseItem* child2 = new ProjectBaseItem( nullptr, QStringLiteral("ztest"), parent ); + ProjectFileItem* child3 = new ProjectFileItem( nullptr, Path(QUrl(QStringLiteral("file:///bcd"))), parent ); + ProjectFileItem* child4 = new ProjectFileItem( nullptr, Path(QUrl(QStringLiteral("file:///abcd"))), parent ); // Just some basic santiy checks on the API QCOMPARE( parent->child( 0 ), child ); QCOMPARE( parent->row(), -1 ); QVERIFY( !parent->child( -1 ) ); QVERIFY( !parent->file() ); QVERIFY( !parent->folder() ); QVERIFY( !parent->project() ); QVERIFY( !parent->child( parent->rowCount() ) ); QCOMPARE( parent->iconName(), QString() ); QCOMPARE( parent->index(), QModelIndex() ); QCOMPARE( child->type(), (int)ProjectBaseItem::BaseItem ); QCOMPARE( child->lessThan( child2 ), true ); QCOMPARE( child3->lessThan( child4 ), false ); // Check that model is properly emitting data-changes model->appendRow( parent ); QCOMPARE( parent->index(), model->index(0, 0, QModelIndex()) ); QSignalSpy s( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)) ); parent->setPath( Path(QStringLiteral("/newtest")) ); QCOMPARE( s.count(), 1 ); QCOMPARE( model->data( parent->index() ).toString(), QStringLiteral("newtest") ); parent->removeRow( child->row() ); } void TestProjectModel::testTakeRow() { - ProjectBaseItem* parent = new ProjectBaseItem( 0, QStringLiteral("test") ); - ProjectBaseItem* child = new ProjectBaseItem( 0, QStringLiteral("test"), parent ); - ProjectBaseItem* subchild = new ProjectBaseItem( 0, QStringLiteral("subtest"), child ); + ProjectBaseItem* parent = new ProjectBaseItem( nullptr, QStringLiteral("test") ); + ProjectBaseItem* child = new ProjectBaseItem( nullptr, QStringLiteral("test"), parent ); + ProjectBaseItem* subchild = new ProjectBaseItem( nullptr, QStringLiteral("subtest"), child ); model->appendRow( parent ); QCOMPARE( parent->model(), model ); QCOMPARE( child->model(), model ); QCOMPARE( subchild->model(), model ); parent->takeRow( child->row() ); - QCOMPARE( child->model(), static_cast(0) ); - QCOMPARE( subchild->model(), static_cast(0) ); + QCOMPARE( child->model(), static_cast(nullptr) ); + QCOMPARE( subchild->model(), static_cast(nullptr) ); } void TestProjectModel::testRename() { QFETCH( int, itemType ); QFETCH( QString, itemText ); QFETCH( QString, newName ); QFETCH( bool, datachangesignal ); QFETCH( QString, expectedItemText ); QFETCH( int, expectedRenameCode ); const Path projectFolder = Path(QUrl::fromLocalFile(QStringLiteral("/dummyprojectfolder"))); TestProject* proj = new TestProject; - ProjectFolderItem* rootItem = new ProjectFolderItem( proj, projectFolder, 0); + ProjectFolderItem* rootItem = new ProjectFolderItem( proj, projectFolder, nullptr); proj->setProjectItem( rootItem ); new ProjectFileItem(QStringLiteral("existing"), rootItem); - ProjectBaseItem* item = 0; + ProjectBaseItem* item = nullptr; if( itemType == ProjectBaseItem::Target ) { item = new ProjectTargetItem( proj, itemText, rootItem ); } else if( itemType == ProjectBaseItem::File ) { item = new ProjectFileItem( itemText, rootItem ); } else if( itemType == ProjectBaseItem::Folder ) { item = new ProjectFolderItem( itemText, rootItem ); } else if( itemType == ProjectBaseItem::BuildFolder ) { item = new ProjectBuildFolderItem( itemText, rootItem ); } Q_ASSERT( item ); QCOMPARE(item->model(), model); QSignalSpy s( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)) ); ProjectBaseItem::RenameStatus stat = item->rename( newName ); QCOMPARE( (int)stat, expectedRenameCode ); if( datachangesignal ) { QCOMPARE( s.count(), 1 ); QCOMPARE( qvariant_cast( s.takeFirst().at(0) ), item->index() ); } else { QCOMPARE( s.count(), 0 ); } QCOMPARE( item->text(), expectedItemText ); } void TestProjectModel::testRename_data() { QTest::addColumn( "itemType" ); QTest::addColumn( "itemText" ); QTest::addColumn( "newName" ); QTest::addColumn( "datachangesignal" ); QTest::addColumn( "expectedItemText" ); QTest::addColumn( "expectedRenameCode" ); QTest::newRow("RenameableTarget") << (int)ProjectBaseItem::Target << QStringLiteral("target") << QStringLiteral("othertarget") << true << QStringLiteral("othertarget") << (int)ProjectBaseItem::RenameOk; QTest::newRow("RenameableFile") << (int)ProjectBaseItem::File << QStringLiteral("newfile.cpp") << QStringLiteral("otherfile.cpp") << true << QStringLiteral("otherfile.cpp") << (int)ProjectBaseItem::RenameOk; QTest::newRow("SourceAndDestinationFileEqual") << (int)ProjectBaseItem::File << QStringLiteral("newfile.cpp") << QStringLiteral("newfile.cpp") << false << QStringLiteral("newfile.cpp") << (int)ProjectBaseItem::RenameOk; QTest::newRow("RenameableFolder") << (int)ProjectBaseItem::Folder << QStringLiteral("newfolder") << QStringLiteral("otherfolder") << true << QStringLiteral("otherfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("SourceAndDestinationFolderEqual") << (int)ProjectBaseItem::Folder << QStringLiteral("newfolder") << QStringLiteral("newfolder") << false << QStringLiteral("newfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("RenameableBuildFolder") << (int)ProjectBaseItem::BuildFolder << QStringLiteral("newbfolder") << QStringLiteral("otherbfolder") << true << QStringLiteral("otherbfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("SourceAndDestinationBuildFolderEqual") << (int)ProjectBaseItem::BuildFolder << QStringLiteral("newbfolder") << QStringLiteral("newbfolder") << false << QStringLiteral("newbfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("ExistingFileError") << (int)ProjectBaseItem::Folder << QStringLiteral("mynew") << QStringLiteral("existing") << false << QStringLiteral("mynew") << (int)ProjectBaseItem::ExistingItemSameName; QTest::newRow("InvalidNameError") << (int)ProjectBaseItem::File << QStringLiteral("mynew") << QStringLiteral("other/bash") << false << QStringLiteral("mynew") << (int)ProjectBaseItem::InvalidNewName; } void TestProjectModel::testWithProject() { TestProject* proj = new TestProject(); - ProjectFolderItem* rootItem = new ProjectFolderItem( proj, Path(QUrl::fromLocalFile(QStringLiteral("/dummyprojectfolder"))), 0); + ProjectFolderItem* rootItem = new ProjectFolderItem( proj, Path(QUrl::fromLocalFile(QStringLiteral("/dummyprojectfolder"))), nullptr); proj->setProjectItem( rootItem ); ProjectBaseItem* item = model->itemFromIndex( model->index( 0, 0 ) ); QCOMPARE( item, rootItem ); QCOMPARE( item->text(), proj->name() ); QCOMPARE( item->path(), proj->path() ); } void TestProjectModel::testItemsForPath() { QFETCH(Path, path); QFETCH(ProjectBaseItem*, root); QFETCH(int, matches); model->appendRow(root); auto items = model->itemsForPath(IndexedString(path.pathOrUrl())); QCOMPARE(items.size(), matches); foreach(ProjectBaseItem* item, items) { QVERIFY(item->path() == path); } model->clear(); } void TestProjectModel::testItemsForPath_data() { QTest::addColumn("path"); QTest::addColumn("root"); QTest::addColumn("matches"); { - ProjectFolderItem* root = new ProjectFolderItem(0, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); + ProjectFolderItem* root = new ProjectFolderItem(nullptr, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); ProjectFileItem* file = new ProjectFileItem(QStringLiteral("a"), root); QTest::newRow("find one") << file->path() << static_cast(root) << 1; } { - ProjectFolderItem* root = new ProjectFolderItem(0, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); + ProjectFolderItem* root = new ProjectFolderItem(nullptr, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); ProjectFolderItem* folder = new ProjectFolderItem(QStringLiteral("a"), root); ProjectFileItem* file = new ProjectFileItem(QStringLiteral("foo"), folder); - ProjectTargetItem* target = new ProjectTargetItem(0, QStringLiteral("b"), root); - ProjectFileItem* file2 = new ProjectFileItem(0, file->path(), target); + ProjectTargetItem* target = new ProjectTargetItem(nullptr, QStringLiteral("b"), root); + ProjectFileItem* file2 = new ProjectFileItem(nullptr, file->path(), target); Q_UNUSED(file2); QTest::newRow("find two") << file->path() << static_cast(root) << 2; } } void TestProjectModel::testProjectProxyModel() { - ProjectFolderItem* root = new ProjectFolderItem(0, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); + ProjectFolderItem* root = new ProjectFolderItem(nullptr, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); new ProjectFileItem(QStringLiteral("b1"), root); new ProjectFileItem(QStringLiteral("a1"), root); new ProjectFileItem(QStringLiteral("d1"), root); new ProjectFileItem(QStringLiteral("c1"), root); model->appendRow(root); QModelIndex proxyRoot = proxy->mapFromSource(root->index()); QCOMPARE(model->rowCount(root->index()), 4); QCOMPARE(proxy->rowCount(proxyRoot), 4); QCOMPARE(proxy->index(0, 0, proxy->index(0, 0)).data().toString(), QStringLiteral("a1")); QCOMPARE(proxy->index(1, 0, proxy->index(0, 0)).data().toString(), QStringLiteral("b1")); QCOMPARE(proxy->index(2, 0, proxy->index(0, 0)).data().toString(), QStringLiteral("c1")); QCOMPARE(proxy->index(3, 0, proxy->index(0, 0)).data().toString(), QStringLiteral("d1")); model->clear(); } void TestProjectModel::testProjectFileSet() { TestProject* project = new TestProject; QVERIFY(project->fileSet().isEmpty()); Path path(QUrl::fromLocalFile(QStringLiteral("/tmp/a"))); ProjectFileItem* item = new ProjectFileItem(project, path, project->projectItem()); QCOMPARE(project->fileSet().size(), 1); qDebug() << path << project->fileSet().toList().at(0).toUrl(); QCOMPARE(Path(project->fileSet().toList().at(0).toUrl()), path); delete item; QVERIFY(project->fileSet().isEmpty()); } void TestProjectModel::testProjectFileIcon() { QMimeDatabase db; - ProjectFileItem* item = new ProjectFileItem(0, Path(QStringLiteral("/tmp/foo.txt"))); + ProjectFileItem* item = new ProjectFileItem(nullptr, Path(QStringLiteral("/tmp/foo.txt"))); const QString txtIcon = db.mimeTypeForUrl(item->path().toUrl()).iconName(); QCOMPARE(item->iconName(), txtIcon); item->setPath(Path(QStringLiteral("/tmp/bar.cpp"))); QCOMPARE(item->iconName(), db.mimeTypeForUrl(item->path().toUrl()).iconName()); QVERIFY(item->iconName() != txtIcon); } QTEST_MAIN( TestProjectModel) diff --git a/serialization/abstractitemrepository.cpp b/serialization/abstractitemrepository.cpp index 27e18a643f..178a6e69c7 100644 --- a/serialization/abstractitemrepository.cpp +++ b/serialization/abstractitemrepository.cpp @@ -1,48 +1,48 @@ /* 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 "abstractitemrepository.h" #include "config-kdevplatform.h" namespace KDevelop { uint staticItemRepositoryVersion() { return KDEV_ITEMREPOSITORY_VERSION; } AbstractItemRepository::~AbstractItemRepository() { } -AbstractRepositoryManager::AbstractRepositoryManager() : m_repository(0) +AbstractRepositoryManager::AbstractRepositoryManager() : m_repository(nullptr) { } AbstractRepositoryManager::~AbstractRepositoryManager() { } void AbstractRepositoryManager::deleteRepository() { delete m_repository; - m_repository = 0; + m_repository = nullptr; } } diff --git a/serialization/indexedstring.cpp b/serialization/indexedstring.cpp index 3910248578..91584dfd50 100644 --- a/serialization/indexedstring.cpp +++ b/serialization/indexedstring.cpp @@ -1,407 +1,407 @@ /* This file is part of KDevelop Copyright 2008 David Nolden Copyright 2016 Milian Wolff 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, see . */ #include "indexedstring.h" #include "serialization/stringrepository.h" #include "referencecounting.h" using namespace KDevelop; namespace { struct IndexedStringData { unsigned short length; uint refCount; uint itemSize() const { return sizeof(IndexedStringData) + length; } uint hash() const { IndexedString::RunningHash running; const char* str = ((const char*)this) + sizeof(IndexedStringData); for (int a = length - 1; a >= 0; --a) { running.append(*str); ++str; } return running.hash; } }; inline void increase(uint& val) { ++val; } inline void decrease(uint& val) { --val; } struct IndexedStringRepositoryItemRequest { //The text is supposed to be utf8 encoded IndexedStringRepositoryItemRequest(const char* text, uint hash, unsigned short length) : m_hash(hash) , m_length(length) , m_text(text) { } enum { AverageSize = 10 //This should be the approximate average size of an Item }; typedef uint HashType; //Should return the hash-value associated with this request(For example the hash of a string) HashType hash() const { return m_hash; } //Should return the size of an item created with createItem uint itemSize() const { return sizeof(IndexedStringData) + m_length; } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(IndexedStringData* item) const { item->length = m_length; item->refCount = 0; ++item; memcpy(item, m_text, m_length); } static void destroy(IndexedStringData* item, AbstractItemRepository&) { Q_UNUSED(item); //Nothing to do here (The object is not intelligent) } static bool persistent(const IndexedStringData* item) { return (bool)item->refCount; } //Should return whether the here requested item equals the given item bool equals(const IndexedStringData* item) const { return item->length == m_length && (memcmp(++item, m_text, m_length) == 0); } uint m_hash; unsigned short m_length; const char* m_text; }; inline const char* c_strFromItem(const IndexedStringData* item) { return reinterpret_cast(item + 1); } ///@param item must be valid(nonzero) inline QString stringFromItem(const IndexedStringData* item) { return QString::fromUtf8(c_strFromItem(item), item->length); } inline QByteArray arrayFromItem(const IndexedStringData* item) { return QByteArray(c_strFromItem(item), item->length); } inline bool isSingleCharIndex(uint index) { return (index & 0xffff0000) == 0xffff0000; } inline uint charToIndex(char c) { return 0xffff0000 | c; } inline char indexToChar(uint index) { Q_ASSERT(isSingleCharIndex(index)); return static_cast(index & 0xff); } using IndexedStringRepository = ItemRepository; using IndexedStringRepositoryManagerBase = RepositoryManager; class IndexedStringRepositoryManager : public IndexedStringRepositoryManagerBase { public: IndexedStringRepositoryManager() : IndexedStringRepositoryManagerBase(QStringLiteral("String Index")) { repository()->setMutex(&m_mutex); } private: // non-recursive mutex to increase speed QMutex m_mutex; }; IndexedStringRepository* globalIndexedStringRepository() { static IndexedStringRepositoryManager manager; return manager.repository(); } template auto readRepo(ReadAction action) -> decltype(action(globalIndexedStringRepository())) { const auto* repo = globalIndexedStringRepository(); QMutexLocker lock(repo->mutex()); return action(repo); } template auto editRepo(EditAction action) -> decltype(action(globalIndexedStringRepository())) { auto* repo = globalIndexedStringRepository(); QMutexLocker lock(repo->mutex()); return action(repo); } inline void ref(IndexedString* string) { const uint index = string->index(); if (index && !isSingleCharIndex(index)) { if (shouldDoDUChainReferenceCounting(string)) { editRepo([index] (IndexedStringRepository* repo) { increase(repo->dynamicItemFromIndexSimple(index)->refCount); }); } } } inline void deref(IndexedString* string) { const uint index = string->index(); if (index && !isSingleCharIndex(index)) { if (shouldDoDUChainReferenceCounting(string)) { editRepo([index] (IndexedStringRepository* repo) { decrease(repo->dynamicItemFromIndexSimple(index)->refCount); }); } } } } IndexedString::IndexedString() : m_index(0) { } ///@param str must be a utf8 encoded string, does not need to be 0-terminated. ///@param length must be its length in bytes. IndexedString::IndexedString(const char* str, unsigned short length, uint hash) { if (!length) { m_index = 0; } else if (length == 1) { m_index = charToIndex(str[0]); } else { const auto request = IndexedStringRepositoryItemRequest(str, hash ? hash : hashString(str, length), length); bool refcount = shouldDoDUChainReferenceCounting(this); m_index = editRepo([request, refcount] (IndexedStringRepository* repo) { auto index = repo->index(request); if (refcount) { increase(repo->dynamicItemFromIndexSimple(index)->refCount); } return index; }); } } IndexedString::IndexedString(char c) : m_index(charToIndex(c)) {} IndexedString::IndexedString(const QUrl& url) : IndexedString(url.isLocalFile() ? url.toLocalFile() : url.toString()) { Q_ASSERT(url.isEmpty() || !url.isRelative()); #if !defined(QT_NO_DEBUG) if (url != url.adjusted(QUrl::NormalizePathSegments)) { qWarning() << "wrong url" << url << url.adjusted(QUrl::NormalizePathSegments); } #endif Q_ASSERT(url == url.adjusted(QUrl::NormalizePathSegments)); } IndexedString::IndexedString(const QString& string) : IndexedString(string.toUtf8()) {} IndexedString::IndexedString(const char* str) : IndexedString(str, str ? strlen(str) : 0) {} IndexedString::IndexedString(const QByteArray& str) : IndexedString(str.constData(), str.length()) {} IndexedString::~IndexedString() { deref(this); } IndexedString::IndexedString(const IndexedString& rhs) : m_index(rhs.m_index) { ref(this); } IndexedString& IndexedString::operator=(const IndexedString& rhs) { if (m_index == rhs.m_index) { return *this; } deref(this); m_index = rhs.m_index; ref(this); return *this; } QUrl IndexedString::toUrl() const { if (isEmpty()) { return {}; } QUrl ret = QUrl::fromUserInput(str()); Q_ASSERT(!ret.isRelative()); return ret; } QString IndexedString::str() const { if (!m_index) { return QString(); } else if (isSingleCharIndex(m_index)) { return QString(QLatin1Char(indexToChar(m_index))); } else { const uint index = m_index; return readRepo([index] (const IndexedStringRepository* repo) { return stringFromItem(repo->itemFromIndex(index)); }); } } int IndexedString::length() const { return lengthFromIndex(m_index); } int IndexedString::lengthFromIndex(uint index) { if (!index) { return 0; } else if (isSingleCharIndex(index)) { return 1; } else { return readRepo([index] (const IndexedStringRepository* repo) { return repo->itemFromIndex(index)->length; }); } } const char* IndexedString::c_str() const { if (!m_index) { - return 0; + return nullptr; } else if (isSingleCharIndex(m_index)) { #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN const uint offset = 0; #else const uint offset = 3; #endif return reinterpret_cast(&m_index) + offset; } else { const uint index = m_index; return readRepo([index] (const IndexedStringRepository* repo) { return c_strFromItem(repo->itemFromIndex(index)); }); } } QByteArray IndexedString::byteArray() const { if (!m_index) { return QByteArray(); } else if (isSingleCharIndex(m_index)) { return QByteArray(1, indexToChar(m_index)); } else { const uint index = m_index; return readRepo([index] (const IndexedStringRepository* repo) { return arrayFromItem(repo->itemFromIndex(index)); }); } } uint IndexedString::hashString(const char* str, unsigned short length) { RunningHash running; for (int a = length - 1; a >= 0; --a) { running.append(*str); ++str; } return running.hash; } uint IndexedString::indexForString(const char* str, short unsigned length, uint hash) { if (!length) { return 0; } else if (length == 1) { return charToIndex(str[0]); } else { const auto request = IndexedStringRepositoryItemRequest(str, hash ? hash : hashString(str, length), length); return editRepo([request] (IndexedStringRepository* repo) { return repo->index(request); }); } } uint IndexedString::indexForString(const QString& str, uint hash) { const QByteArray array(str.toUtf8()); return indexForString(array.constBegin(), array.size(), hash); } QDebug operator<<(QDebug s, const IndexedString& string) { s.nospace() << string.str(); return s.space(); } diff --git a/serialization/itemrepositoryregistry.cpp b/serialization/itemrepositoryregistry.cpp index b86876c7ea..57b9c7bd06 100644 --- a/serialization/itemrepositoryregistry.cpp +++ b/serialization/itemrepositoryregistry.cpp @@ -1,410 +1,410 @@ /* 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 "itemrepositoryregistry.h" #include #include #include #include #include #include #include #include #include "abstractitemrepository.h" #include "debug.h" using namespace KDevelop; namespace { //If KDevelop crashed this many times consicutively, clean up the repository const int crashesBeforeCleanup = 1; void setCrashCounter(QFile& crashesFile, int count) { crashesFile.close(); crashesFile.open(QIODevice::WriteOnly | QIODevice::Truncate); QDataStream writeStream(&crashesFile); writeStream << count; } QString repositoryPathForSession(const KDevelop::ISessionLock::Ptr& session) { QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); cacheDir += QStringLiteral("/kdevduchain"); QString baseDir = QProcessEnvironment::systemEnvironment().value(QStringLiteral("KDEV_DUCHAIN_DIR"), cacheDir); baseDir += QStringLiteral("/%1-%2").arg(qApp->applicationName(), session->id()); return baseDir; } bool shouldClear(const QString& path) { QDir dir(path); if (!dir.exists()) { return false; } if (getenv("CLEAR_DUCHAIN_DIR")) { qCDebug(SERIALIZATION) << "clearing duchain directory because CLEAR_DUCHAIN_DIR is set"; return true; } if (dir.exists(QStringLiteral("is_writing"))) { qCWarning(SERIALIZATION) << "repository" << path << "was write-locked, it probably is inconsistent"; return true; } if (!dir.exists(QStringLiteral("version_%1").arg(staticItemRepositoryVersion()))) { qCWarning(SERIALIZATION) << "version-hint not found, seems to be an old version"; return true; } QFile crashesFile(dir.filePath(QStringLiteral("crash_counter"))); if (crashesFile.open(QIODevice::ReadOnly)) { int count; QDataStream stream(&crashesFile); stream >> count; qCDebug(SERIALIZATION) << "current count of crashes: " << count; if (count >= crashesBeforeCleanup && !getenv("DONT_CLEAR_DUCHAIN_DIR")) { bool userAnswer = askUser(i18np("The previous session crashed", "Session crashed %1 times in a row", count), i18nc("@action", "Clear cache"), i18nc("@title", "Session crashed"), i18n("The crash may be caused by a corruption of cached data.\n\n" "Press OK if you want KDevelop to clear the cache, otherwise press Cancel if you are sure the crash has another origin.")); if (userAnswer) { qCDebug(SERIALIZATION) << "User chose to clean repository"; return true; } else { setCrashCounter(crashesFile, 1); qCDebug(SERIALIZATION) << "User chose to reset crash counter"; } } else { ///Increase the crash-count. It will be reset if kdevelop is shut down cleanly. setCrashCounter(crashesFile, ++count); } } else { setCrashCounter(crashesFile, 1); } return false; } } namespace KDevelop { struct ItemRepositoryRegistryPrivate { ItemRepositoryRegistry* m_owner; bool m_shallDelete; QString m_path; ISessionLock::Ptr m_sessionLock; QMap m_repositories; QMap m_customCounters; mutable QMutex m_mutex; ItemRepositoryRegistryPrivate(ItemRepositoryRegistry* owner) : m_owner(owner) , m_shallDelete(false) , m_mutex(QMutex::Recursive) { } void lockForWriting(); void unlockForWriting(); void deleteDataDirectory(const QString& path, bool recreate = true); /// @param path A shared directory-path that the item-repositories are to be loaded from. /// @returns Whether the repository registry has been opened successfully. /// If @c false, then all registered repositories should have been deleted. /// @note Currently the given path must reference a hidden directory, just to make sure we're /// not accidentally deleting something important. bool open(const QString& path); /// Close all contained repositories. /// @warning The current state is not stored to disk. void close(); }; //The global item-reposity registry -ItemRepositoryRegistry* ItemRepositoryRegistry::m_self = 0; +ItemRepositoryRegistry* ItemRepositoryRegistry::m_self = nullptr; ItemRepositoryRegistry::ItemRepositoryRegistry(const ISessionLock::Ptr& session) : d(new ItemRepositoryRegistryPrivate(this)) { Q_ASSERT(session); d->open(repositoryPathForSession(session)); } void ItemRepositoryRegistry::initialize(const ISessionLock::Ptr& session) { if (!m_self) { ///We intentionally leak the registry, to prevent problems in the destruction order, where ///the actual repositories might get deleted later than the repository registry. m_self = new ItemRepositoryRegistry(session); } } ItemRepositoryRegistry* ItemRepositoryRegistry::self() { Q_ASSERT(m_self); return m_self; } void ItemRepositoryRegistry::deleteRepositoryFromDisk(const ISessionLock::Ptr& session) { // Now, as we have only the global item-repository registry, assume that if and only if // the given session is ours, its cache path is used by the said global item-repository registry. const QString repositoryPath = repositoryPathForSession(session); if(m_self && m_self->d->m_path == repositoryPath) { // remove later m_self->d->m_shallDelete = true; } else { // Otherwise, given session is not ours. // remove its item-repository directory directly. QDir(repositoryPath).removeRecursively(); } } QMutex& ItemRepositoryRegistry::mutex() { return d->m_mutex; } QAtomicInt& ItemRepositoryRegistry::getCustomCounter(const QString& identity, int initialValue) { if(!d->m_customCounters.contains(identity)) d->m_customCounters.insert(identity, new QAtomicInt(initialValue)); return *d->m_customCounters[identity]; } ///The global item-repository registry that is used by default ItemRepositoryRegistry& globalItemRepositoryRegistry() { return *ItemRepositoryRegistry::self(); } void ItemRepositoryRegistry::registerRepository(AbstractItemRepository* repository, AbstractRepositoryManager* manager) { QMutexLocker lock(&d->m_mutex); d->m_repositories.insert(repository, manager); if(!d->m_path.isEmpty()) { if(!repository->open(d->m_path)) { d->deleteDataDirectory(d->m_path); qCritical() << "failed to open a repository"; abort(); } } } QString ItemRepositoryRegistry::path() const { //We cannot lock the mutex here, since this may be called with one of the repositories locked, //and that may lead to a deadlock when at the same time a storing is requested return d->m_path; } void ItemRepositoryRegistryPrivate::lockForWriting() { QMutexLocker lock(&m_mutex); //Create is_writing QFile f(m_path + "/is_writing"); f.open(QIODevice::WriteOnly); f.close(); } void ItemRepositoryRegistry::lockForWriting() { d->lockForWriting(); } void ItemRepositoryRegistryPrivate::unlockForWriting() { QMutexLocker lock(&m_mutex); //Delete is_writing QFile::remove(m_path + "/is_writing"); } void ItemRepositoryRegistry::unlockForWriting() { d->unlockForWriting(); } void ItemRepositoryRegistry::unRegisterRepository(AbstractItemRepository* repository) { QMutexLocker lock(&d->m_mutex); Q_ASSERT(d->m_repositories.contains(repository)); repository->close(); d->m_repositories.remove(repository); } //After calling this, the data-directory may be a new one void ItemRepositoryRegistryPrivate::deleteDataDirectory(const QString& path, bool recreate) { QMutexLocker lock(&m_mutex); //lockForWriting creates a file, that prevents any other KDevelop instance from using the directory as it is. //Instead, the other instance will try to delete the directory as well. lockForWriting(); bool result = QDir(path).removeRecursively(); Q_ASSERT(result); Q_UNUSED(result); // Just recreate the directory then; leave old path (as it is dependent on appname and session only). if(recreate) { QDir().mkpath(path); } } bool ItemRepositoryRegistryPrivate::open(const QString& path) { QMutexLocker mlock(&m_mutex); if(m_path == path) { return true; } // Check if the repository shall be cleared if (shouldClear(path)) { qCWarning(SERIALIZATION) << QStringLiteral("The data-repository at %1 has to be cleared.").arg(path); deleteDataDirectory(path); } QDir().mkpath(path); foreach(AbstractItemRepository* repository, m_repositories.keys()) { if(!repository->open(path)) { deleteDataDirectory(path); qCritical() << "failed to open a repository"; abort(); } } QFile f(path + "/Counters"); if(f.open(QIODevice::ReadOnly)) { QDataStream stream(&f); while(!stream.atEnd()) { //Read in all custom counter values QString counterName; stream >> counterName; int counterValue; stream >> counterValue; m_owner->getCustomCounter(counterName, 0) = counterValue; } } m_path = path; return true; } void ItemRepositoryRegistry::store() { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { repository->store(); } QFile versionFile(d->m_path + QStringLiteral("/version_%1").arg(staticItemRepositoryVersion())); if(versionFile.open(QIODevice::WriteOnly)) { versionFile.close(); } else { qCWarning(SERIALIZATION) << "Could not open version file for writing"; } //Store all custom counter values QFile f(d->m_path + "/Counters"); if(f.open(QIODevice::WriteOnly)) { f.resize(0); QDataStream stream(&f); for(QMap::const_iterator it = d->m_customCounters.constBegin(); it != d->m_customCounters.constEnd(); ++it) { stream << it.key(); stream << it.value()->fetchAndAddRelaxed(0); } } else { qCWarning(SERIALIZATION) << "Could not open counter file for writing"; } } void ItemRepositoryRegistry::printAllStatistics() const { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { qCDebug(SERIALIZATION) << "statistics in" << repository->repositoryName() << ":"; qCDebug(SERIALIZATION) << repository->printStatistics(); } } int ItemRepositoryRegistry::finalCleanup() { QMutexLocker lock(&d->m_mutex); int changed = false; foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { int added = repository->finalCleanup(); changed += added; qCDebug(SERIALIZATION) << "cleaned in" << repository->repositoryName() << ":" << added; } return changed; } void ItemRepositoryRegistryPrivate::close() { QMutexLocker lock(&m_mutex); foreach(AbstractItemRepository* repository, m_repositories.keys()) { repository->close(); } m_path.clear(); } ItemRepositoryRegistry::~ItemRepositoryRegistry() { QMutexLocker lock(&d->m_mutex); d->close(); foreach(QAtomicInt * counter, d->m_customCounters) { delete counter; } delete d; } void ItemRepositoryRegistry::shutdown() { QMutexLocker lock(&d->m_mutex); QString path = d->m_path; // FIXME: we don't close since this can trigger crashes at shutdown // since some items are still referenced, e.g. in static variables // d->close(); if(d->m_shallDelete) { d->deleteDataDirectory(path, false); } else { QFile::remove(path + QLatin1String("/crash_counter")); } } } diff --git a/serialization/referencecounting.cpp b/serialization/referencecounting.cpp index 30c84f2dfe..85c0eb75c2 100644 --- a/serialization/referencecounting.cpp +++ b/serialization/referencecounting.cpp @@ -1,260 +1,260 @@ /* * This file is part of KDevelop * * Copyright 2009 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "referencecounting.h" #include #include #include #include "serialization/itemrepository.h" namespace KDevelop { bool doReferenceCounting = false; //Protects the reference-counting data through a spin-lock QMutex refCountingLock; QMap >* refCountingRanges = new QMap >(); //ptr, , leaked intentionally! bool refCountingHasAdditionalRanges = false; //Whether 'refCountingRanges' is non-empty //Speedup: In most cases there is only exactly one reference-counted range active, //so the first reference-counting range can be marked here. - void* refCountingFirstRangeStart = 0; + void* refCountingFirstRangeStart = nullptr; QPair refCountingFirstRangeExtent = qMakePair(0u, 0u); } void KDevelop::disableDUChainReferenceCounting(void* start) { QMutexLocker lock(&refCountingLock); if(refCountingFirstRangeStart && ((char*)refCountingFirstRangeStart) <= (char*)start && (char*)start < ((char*)refCountingFirstRangeStart) + refCountingFirstRangeExtent.first) { Q_ASSERT(refCountingFirstRangeExtent.second > 0); --refCountingFirstRangeExtent.second; if(refCountingFirstRangeExtent.second == 0) { refCountingFirstRangeExtent = qMakePair(0, 0); - refCountingFirstRangeStart = 0; + refCountingFirstRangeStart = nullptr; } } else if(refCountingHasAdditionalRanges) { QMap< void*, QPair >::iterator it = refCountingRanges->upperBound(start); if(it != refCountingRanges->begin()) { --it; if(((char*)it.key()) <= (char*)start && (char*)start < ((char*)it.key()) + it.value().first) { //Contained }else{ Q_ASSERT(0); } } Q_ASSERT(it.value().second > 0); --it.value().second; if(it.value().second == 0) refCountingRanges->erase(it); refCountingHasAdditionalRanges = !refCountingRanges->isEmpty(); }else{ Q_ASSERT(0); } if(!refCountingFirstRangeStart && !refCountingHasAdditionalRanges) doReferenceCounting = false; } void KDevelop::enableDUChainReferenceCounting(void* start, unsigned int size) { QMutexLocker lock(&refCountingLock); doReferenceCounting = true; if(refCountingFirstRangeStart && ((char*)refCountingFirstRangeStart) <= (char*)start && (char*)start < ((char*)refCountingFirstRangeStart) + refCountingFirstRangeExtent.first) { //Increase the count for the first range ++refCountingFirstRangeExtent.second; }else if(refCountingHasAdditionalRanges || refCountingFirstRangeStart) { //There is additional ranges in the ranges-structure. Add any new ranges there as well. QMap< void*, QPair >::iterator it = refCountingRanges->upperBound(start); if(it != refCountingRanges->begin()) { --it; if(((char*)it.key()) <= (char*)start && (char*)start < ((char*)it.key()) + it.value().first) { //Contained, count up }else{ it = refCountingRanges->end(); //Insert own item } }else if(it != refCountingRanges->end() && it.key() > start) { //The item is behind it = refCountingRanges->end(); } if(it == refCountingRanges->end()) { QMap< void*, QPair >::iterator inserted = refCountingRanges->insert(start, qMakePair(size, 1u)); //Merge following ranges QMap< void*, QPair >::iterator it = inserted; ++it; while(it != refCountingRanges->end() && it.key() < ((char*)start) + size) { inserted.value().second += it.value().second; //Accumulate count if(((char*)start) + size < ((char*)inserted.key()) + it.value().first) { //Update end position inserted.value().first = (((char*)inserted.key()) + it.value().first) - ((char*)start); } it = refCountingRanges->erase(it); } }else{ ++it.value().second; if(it.value().first < size) it.value().first = size; } refCountingHasAdditionalRanges = true; }else{ refCountingFirstRangeStart = start; refCountingFirstRangeExtent.first = size; refCountingFirstRangeExtent.second = 1; } Q_ASSERT(refCountingHasAdditionalRanges == (refCountingRanges && !refCountingRanges->isEmpty())); #ifdef TEST_REFERENCE_COUNTING Q_ASSERT(shouldDoDUChainReferenceCounting(start)); Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)start + (size-1)))); #endif } #ifdef TEST_REFERENCE_COUNTING QAtomicInt& id() { static QAtomicInt& ret(KDevelop::globalItemRepositoryRegistry().getCustomCounter("referencer ids", 1)); return ret; } namespace KDevelop { ReferenceCountManager::ReferenceCountManager() : m_id(id().fetchAndAddRelaxed(1)) { } struct ReferenceCountItem { ///Item entries: ReferenceCountItem(uint id, uint target) : m_id(id), m_targetId(target) { } //Every item has to implement this function, and return a valid hash. //Must be exactly the same hash value as ReferenceCountItemRequest::hash() has returned while creating the item. unsigned int hash() const { return KDevHash() << m_id << m_targetId; } //Every item has to implement this function, and return the complete size this item takes in memory. //Must be exactly the same value as ReferenceCountItemRequest::itemSize() has returned while creating the item. unsigned short int itemSize() const { return sizeof(ReferenceCountItem); } uint m_id; uint m_targetId; ///Request entries: enum { AverageSize = 8 }; void createItem(ReferenceCountItem* item) const { *item = *this; } static void destroy(ReferenceCountItem* /*item*/, AbstractItemRepository&) { } static bool persistent(const ReferenceCountItem*) { return true; } bool equals(const ReferenceCountItem* item) const { return m_id == item->m_id && m_targetId == item->m_targetId; } }; static RepositoryManager< ItemRepository, false>& references() { static RepositoryManager< ItemRepository, false> referencesObject("Reference Count Debugging"); return referencesObject; } static RepositoryManager< ItemRepository, false>& oldReferences() { static RepositoryManager< ItemRepository, false> oldReferencesObject("Old Reference Count Debugging"); return oldReferencesObject; } void KDevelop::initReferenceCounting() { references(); oldReferences(); } ReferenceCountManager::~ReferenceCountManager() { //Make sure everything is cleaned up when the object is destroyed // Q_ASSERT(!references().contains(m_id)); } ReferenceCountManager::ReferenceCountManager(const ReferenceCountManager& rhs) : m_id(id().fetchAndAddRelaxed(1)) { //New id } ReferenceCountManager& ReferenceCountManager::ReferenceCountManager::operator=(const ReferenceCountManager& rhs) { //Keep id return *this; } // bool ReferenceCountManager::hasReferenceCount() const { // return references->findIndex(ReferenceCountItem); // } void ReferenceCountManager::increase(uint& ref, uint targetId) { Q_ASSERT(shouldDoDUChainReferenceCounting(this)); Q_ASSERT(!references->findIndex(ReferenceCountItem(m_id, targetId))); ++ref; { int oldIndex = oldReferences->findIndex(ReferenceCountItem(m_id, targetId)); if(oldIndex) oldReferences->deleteItem(oldIndex); } Q_ASSERT(references->index(ReferenceCountItem(m_id, targetId))); } void ReferenceCountManager::decrease(uint& ref, uint targetId) { Q_ASSERT(ref > 0); Q_ASSERT(shouldDoDUChainReferenceCounting(this)); Q_ASSERT(!oldReferences->findIndex(ReferenceCountItem(m_id, targetId))); uint refIndex = references->findIndex(ReferenceCountItem(m_id, targetId)); Q_ASSERT(refIndex); --ref; references->deleteItem(refIndex); oldReferences->index(ReferenceCountItem(m_id, targetId)); } } #else namespace KDevelop { void initReferenceCounting() { } } #endif diff --git a/serialization/tests/test_itemrepository.cpp b/serialization/tests/test_itemrepository.cpp index 57d61dd410..c512cbcf91 100644 --- a/serialization/tests/test_itemrepository.cpp +++ b/serialization/tests/test_itemrepository.cpp @@ -1,358 +1,358 @@ #include #include #include #include #include #include #include #include struct TestItem { TestItem(uint hash, uint dataSize) : m_hash(hash), m_dataSize(dataSize) { } //Every item has to implement this function, and return a valid hash. //Must be exactly the same hash value as ExampleItemRequest::hash() has returned while creating the item. unsigned int hash() const { return m_hash; } //Every item has to implement this function, and return the complete size this item takes in memory. //Must be exactly the same value as ExampleItemRequest::itemSize() has returned while creating the item. unsigned int itemSize() const { return sizeof(TestItem) + m_dataSize; } bool equals(const TestItem* rhs) const { return rhs->m_hash == m_hash && itemSize() == rhs->itemSize() && memcmp((char*)this, rhs, itemSize()) == 0; } uint m_hash; uint m_dataSize; }; struct TestItemRequest { TestItem& m_item; bool m_compareData; TestItemRequest(TestItem& item, bool compareData = false) : m_item(item) , m_compareData(compareData) { } enum { AverageSize = 700 //This should be the approximate average size of an Item }; uint hash() const { return m_item.hash(); } //Should return the size of an item created with createItem size_t itemSize() const { return m_item.itemSize(); } void createItem(TestItem* item) const { memcpy(item, &m_item, m_item.itemSize()); } static void destroy(TestItem* /*item*/, KDevelop::AbstractItemRepository&) { //Nothing to do } static bool persistent(const TestItem* /*item*/) { return true; } //Should return whether the here requested item equals the given item bool equals(const TestItem* item) const { return hash() == item->hash() && (!m_compareData || m_item.equals(item)); } }; uint smallItemsFraction = 20; //Fraction of items betwen 0 and 1 kb uint largeItemsFraction = 1; //Fraction of items between 0 and 200 kb uint cycles = 100000; uint deletionProbability = 1; //Percentual probability that a checked item is deleted. Per-cycle probability must be multiplied with checksPerCycle. uint checksPerCycle = 10; TestItem* createItem(uint id, uint size) { TestItem* ret; char* data = new char[size]; uint dataSize = size - sizeof(TestItem); ret = new (data) TestItem(id, dataSize); //Fill in same random pattern for(uint a = 0; a < dataSize; ++a) data[sizeof(TestItem) + a] = (char)(a + id); return ret; } ///@todo Add a test where the complete content is deleted again, and make sure the result has a nice structure ///@todo More consistency and lost-space tests, especially about monster-buckets. Make sure their space is re-claimed class TestItemRepository : public QObject { Q_OBJECT private slots: void initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore* core = new KDevelop::TestCore(); core->initialize(KDevelop::Core::NoUi); } void cleanupTestCase() { KDevelop::TestCore::shutdown(); } void testItemRepository() { KDevelop::ItemRepository repository(QStringLiteral("TestItemRepository")); uint itemId = 0; QHash realItemsByIndex; QHash realItemsById; uint totalInsertions = 0, totalDeletions = 0; uint maxSize = 0; uint totalSize = 0; - srand(time(NULL)); + srand(time(nullptr)); uint highestSeenIndex = 0; for(uint a = 0; a < cycles; ++a) { { //Insert an item uint itemDecision = rand() % (smallItemsFraction + largeItemsFraction); uint itemSize; if(itemDecision < largeItemsFraction) { //Create a large item: Up to 200kb itemSize = (rand() % 200000) + sizeof(TestItem); } else itemSize = (rand() % 1000) + sizeof(TestItem); TestItem* item = createItem(++itemId, itemSize); Q_ASSERT(item->hash() == itemId); QVERIFY(item->equals(item)); uint index = repository.index(TestItemRequest(*item)); if(index > highestSeenIndex) highestSeenIndex = index; Q_ASSERT(index); realItemsByIndex.insert(index, item); realItemsById.insert(itemId, item); ++totalInsertions; totalSize += itemSize; if(itemSize > maxSize) maxSize = itemSize; } for(uint a = 0; a < checksPerCycle; ++a) { //Check an item uint pick = rand() % itemId; if(realItemsById.contains(pick)) { uint index = repository.findIndex(*realItemsById[pick]); QVERIFY(index); QVERIFY(realItemsByIndex.contains(index)); QVERIFY(realItemsByIndex[index]->equals(repository.itemFromIndex(index))); if((uint) (rand() % 100) < deletionProbability) { ++totalDeletions; //Delete the item repository.deleteItem(index); QVERIFY(!repository.findIndex(*realItemsById[pick])); uint newIndex = repository.index(*realItemsById[pick]); QVERIFY(newIndex); QVERIFY(realItemsByIndex[index]->equals(repository.itemFromIndex(newIndex))); #ifdef POSITION_TEST //Since we have previously deleted the item, there must be enough space if(!((newIndex >> 16) <= (highestSeenIndex >> 16))) { qDebug() << "size:" << realItemsById[pick]->itemSize(); qDebug() << "previous highest seen bucket:" << (highestSeenIndex >> 16); qDebug() << "new bucket:" << (newIndex >> 16); } QVERIFY((newIndex >> 16) <= (highestSeenIndex >> 16)); #endif repository.deleteItem(newIndex); QVERIFY(!repository.findIndex(*realItemsById[pick])); delete[] realItemsById[pick]; realItemsById.remove(pick); realItemsByIndex.remove(index); } } } } qDebug() << "total insertions:" << totalInsertions << "total deletions:" << totalDeletions << "average item size:" << (totalSize / totalInsertions) << "biggest item size:" << maxSize; KDevelop::ItemRepository::Statistics stats = repository.statistics(); qDebug() << stats; QVERIFY(stats.freeUnreachableSpace < stats.freeSpaceInBuckets/100); // < 1% of the free space is unreachable QVERIFY(stats.freeSpaceInBuckets < stats.usedSpaceForBuckets); // < 20% free space } void testLeaks() { KDevelop::ItemRepository repository(QStringLiteral("TestItemRepository")); QList items; for(int i = 0; i < 10000; ++i) { TestItem* item = createItem(i, (rand() % 1000) + sizeof(TestItem)); items << item; repository.index(TestItemRequest(*item)); } foreach(auto item, items) { delete[] item; } } void testStringSharing() { QString qString; qString.fill('.', 1000); KDevelop::IndexedString indexedString(qString); const int repeat = 10000; QVector strings; strings.resize(repeat); for(int i = 0; i < repeat; ++i) { strings[i] = indexedString.str(); QCOMPARE(qString, strings[i]); } } void deleteClashingMonsterBucket() { KDevelop::ItemRepository repository(QStringLiteral("TestItemRepository")); const uint hash = 1235; QScopedArrayPointer monsterItem(createItem(hash, KDevelop::ItemRepositoryBucketSize + 10)); QScopedArrayPointer smallItem(createItem(hash, 20)); QVERIFY(!monsterItem->equals(smallItem.data())); uint smallIndex = repository.index(TestItemRequest(*smallItem, true)); uint monsterIndex = repository.index(TestItemRequest(*monsterItem, true)); QVERIFY(monsterIndex != smallIndex); repository.deleteItem(smallIndex); QVERIFY(!repository.findIndex(TestItemRequest(*smallItem, true))); QCOMPARE(monsterIndex, repository.findIndex(TestItemRequest(*monsterItem, true))); repository.deleteItem(monsterIndex); // now in reverse order, with different data see: https://bugs.kde.org/show_bug.cgi?id=272408 monsterItem.reset(createItem(hash + 1, KDevelop::ItemRepositoryBucketSize + 10)); smallItem.reset(createItem(hash + 1, 20)); QVERIFY(!monsterItem->equals(smallItem.data())); monsterIndex = repository.index(TestItemRequest(*monsterItem, true)); smallIndex = repository.index(TestItemRequest(*smallItem, true)); repository.deleteItem(monsterIndex); QCOMPARE(smallIndex, repository.findIndex(TestItemRequest(*smallItem, true))); QVERIFY(!repository.findIndex(TestItemRequest(*monsterItem, true))); repository.deleteItem(smallIndex); } void usePermissiveModuloWhenRemovingClashLinks() { KDevelop::ItemRepository repository(QStringLiteral("PermissiveModulo")); const uint bucketHashSize = decltype(repository)::bucketHashSize; const uint nextBucketHashSize = decltype(repository)::MyBucket::NextBucketHashSize; auto bucketNumberForIndex = [](const uint index) { return index >> 16; }; const uint clashValue = 2; // Choose sizes that ensure that the items fit in the desired buckets const uint bigItemSize = KDevelop::ItemRepositoryBucketSize * 0.55 - 1; const uint smallItemSize = KDevelop::ItemRepositoryBucketSize * 0.25 - 1; // Will get placed in bucket 1 (bucket zero is invalid), so the root bucket table at position 'clashValue' will be '1' const QScopedArrayPointer firstChainFirstLink(createItem(clashValue, bigItemSize)); const uint firstChainFirstLinkIndex = repository.index(*firstChainFirstLink); QCOMPARE(bucketNumberForIndex(firstChainFirstLinkIndex), 1u); // Will also get placed in bucket 1, so root bucket table at position 'nextBucketHashSize + clashValue' will be '1' const QScopedArrayPointer secondChainFirstLink(createItem(nextBucketHashSize + clashValue, smallItemSize)); const uint secondChainFirstLinkIndex = repository.index(*secondChainFirstLink); QCOMPARE(bucketNumberForIndex(secondChainFirstLinkIndex), 1u); // Will get placed in bucket 2, so bucket 1's next hash table at position 'clashValue' will be '2' const QScopedArrayPointer firstChainSecondLink(createItem(bucketHashSize + clashValue, bigItemSize)); const uint firstChainSecondLinkIndex = repository.index(*firstChainSecondLink); QCOMPARE(bucketNumberForIndex(firstChainSecondLinkIndex), 2u); // Will also get placed in bucket 2, reachable since bucket 1's next hash table at position 'clashValue' is '2' const QScopedArrayPointer secondChainSecondLink(createItem(bucketHashSize + nextBucketHashSize + clashValue, smallItemSize)); const uint secondChainSecondLinkIndex = repository.index(*secondChainSecondLink); QCOMPARE(bucketNumberForIndex(secondChainSecondLinkIndex), 2u); /* * At this point we have two chains in the repository, rooted at 'clashValue' and 'nextBucketHashSize + clashValue' * Both of the chains start in bucket 1 and end in bucket 2, but both chains share the same link to bucket 2 * This is because two of the hashes clash the other two when % bucketHashSize, but all of them clash % nextBucketHashSize */ repository.deleteItem(firstChainSecondLinkIndex); /* * Now we've deleted the second item in the first chain, this means the first chain no longer requires a link to the * second bucket where that item was... but the link must remain, since it's shared (clashed) by the second chain. * * When cutting a link out of the middle of the chain, we need to check if its items clash using the "permissive" * modulus (the size of the /next/ buckets map), which is always a factor of the "stricter" modulus (the size of the * /root/ buckets map). * * This behavior implies that there will sometimes be useless buckets in the bucket chain for a given hash, so when * cutting out the root link, it's safe to skip over them to the first clash with the 'stricter' modulus. */ // The second item of the second chain must still be reachable QCOMPARE(repository.findIndex(*secondChainSecondLink), secondChainSecondLinkIndex); /* * As a memo to anyone who's still reading this, this also means the following situation can exist: * * bucketHashSize == 8 * nextBucketHashSize == 4 * U is a link table * B is a bucket * [...] are the hashes of the contained items * * U * U * U -> B1 * U * U * U * U -> B2 * U * * B0 (Invalid) * B1 -> [2, 6] * U * U * U -> B3 * U * B2 -> [14] * U * U * U -> B1 * U * B3 -> [10] * U * U * U * U * * The chain for hash 6 is: * Root[6] -> B2[2] -> B1[2] -> B3 * * If you remove the item with hash 6, 6 and 2 will clash with mod 4 (permissive) * * So the useless link `B2[2] -> B1` will be preserved, even though its useless * as the item with hash 2 is reachable directly from the root. * * So TODO: Don't preserve links to items accessible from root buckets. This cannot * be done correctly using only Bucket::hasClashingItem as of now. */ } }; #include "test_itemrepository.moc" QTEST_MAIN(TestItemRepository) diff --git a/shell/core.cpp b/shell/core.cpp index a21ddc6f0a..084e074be3 100644 --- a/shell/core.cpp +++ b/shell/core.cpp @@ -1,620 +1,620 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2007 Kris Wong * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "core.h" #include "core_p.h" #include #include #include #include #include #include "mainwindow.h" #include "sessioncontroller.h" #include "uicontroller.h" #include "plugincontroller.h" #include "projectcontroller.h" #include "partcontroller.h" #include "languagecontroller.h" #include "documentcontroller.h" #include "runcontroller.h" #include "session.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "progresswidget/progressmanager.h" #include "selectioncontroller.h" #include "debugcontroller.h" #include "kdevplatform_version.h" #include "workingsetcontroller.h" #include "testcontroller.h" #include "debug.h" #include #include #include #include namespace { void shutdownGracefully(int sig) { static volatile std::sig_atomic_t handlingSignal = 0; if ( !handlingSignal ) { handlingSignal = 1; qCDebug(SHELL) << "signal " << sig << " received, shutting down gracefully"; QCoreApplication* app = QCoreApplication::instance(); if (QApplication* guiApp = qobject_cast(app)) { guiApp->closeAllWindows(); } app->quit(); return; } // re-raise signal with default handler and trigger program termination std::signal(sig, SIG_DFL); std::raise(sig); } void installSignalHandler() { #ifdef SIGHUP std::signal(SIGHUP, shutdownGracefully); #endif #ifdef SIGINT std::signal(SIGINT, shutdownGracefully); #endif #ifdef SIGTERM std::signal(SIGTERM, shutdownGracefully); #endif } } namespace KDevelop { -Core *Core::m_self = 0; +Core *Core::m_self = nullptr; KAboutData createAboutData() { KAboutData aboutData( QStringLiteral("kdevplatform"), i18n("KDevelop Platform"), QStringLiteral(KDEVPLATFORM_VERSION_STRING), i18n("Development Platform for IDE-like Applications"), KAboutLicense::LGPL_V2, i18n( "Copyright 2004-2016, The KDevelop developers" ), QString(), QStringLiteral("http://www.kdevelop.org") ); aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), QStringLiteral("apaku@gmx.de") ); aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), QStringLiteral("adymo@kdevelop.org") ); aboutData.addAuthor( i18n("David Nolden"), i18n( "Definition-Use Chain, C++ Support" ), QStringLiteral("david.nolden.kdevelop@art-master.de") ); aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), QStringLiteral("aleixpol@kde.org") ); aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), QStringLiteral("ghost@cs.msu.su") ); aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), QStringLiteral("rodda@kde.org") ); aboutData.addCredit( i18n("Matt Rogers"), QString(), QStringLiteral("mattr@kde.org")); aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), QStringLiteral("cedric.pasteur@free.fr") ); aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), QStringLiteral("powerfox@kde.ru") ); //Veritas is outside in playground currently. //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integraton"), "mbr.nxi@gmail.com" ); aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), QStringLiteral("rgruber@users.sourceforge.net") ); aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), QStringLiteral("dukjuahn@gmail.com") ); aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), QStringLiteral("niko.sams@gmail.com") ); aboutData.addAuthor( i18n("Milian Wolff"), i18n( "Generic manager, Webdevelopment Plugins, Snippets, Performance" ), QStringLiteral("mail@milianw.de") ); aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support, Performance, Website" ), QStringLiteral("kfunk@kde.org") ); aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), QStringLiteral("svenbrauch@gmx.de") ); return aboutData; } CorePrivate::CorePrivate(Core *core): m_aboutData( createAboutData() ), m_core(core), m_cleanedUp(false), m_shuttingDown(false) { } bool CorePrivate::initialize(Core::Setup mode, QString session ) { m_mode=mode; qCDebug(SHELL) << "Creating controllers"; emit m_core->startupProgress(0); if( !sessionController ) { sessionController = new SessionController(m_core); } if( !workingSetController && !(mode & Core::NoUi) ) { workingSetController = new WorkingSetController(); } qCDebug(SHELL) << "Creating ui controller"; if( !uiController ) { uiController = new UiController(m_core); } emit m_core->startupProgress(10); qCDebug(SHELL) << "Creating plugin controller"; if( !pluginController ) { pluginController = new PluginController(m_core); const auto pluginInfos = pluginController->allPluginInfos(); if (pluginInfos.isEmpty()) { QMessageBox::critical(nullptr, i18n("Could not find any plugins"), i18n("

Could not find any plugins during startup.
" "Please make sure QT_PLUGIN_PATH is set correctly.

" "Refer to this article for more information."), QMessageBox::Abort, QMessageBox::Abort); qWarning() << "Could not find any plugins, aborting"; return false; } } if( !partController && !(mode & Core::NoUi)) { partController = new PartController(m_core, uiController.data()->defaultMainWindow()); } emit m_core->startupProgress(20); if( !projectController ) { projectController = new ProjectController(m_core); } if( !documentController ) { documentController = new DocumentController(m_core); } if( !languageController ) { // Must be initialized after documentController, because the background parser depends // on the document controller. languageController = new LanguageController(m_core); } emit m_core->startupProgress(25); if( !runController ) { runController = new RunController(m_core); } if( !sourceFormatterController ) { sourceFormatterController = new SourceFormatterController(m_core); } emit m_core->startupProgress(30); if ( !progressController) { progressController = ProgressManager::instance(); } if( !selectionController ) { selectionController = new SelectionController(m_core); } emit m_core->startupProgress(35); if( !documentationController && !(mode & Core::NoUi) ) { documentationController = new DocumentationController(m_core); } if( !debugController ) { debugController = new DebugController(m_core); } emit m_core->startupProgress(40); if( !testController ) { testController = new TestController(m_core); } emit m_core->startupProgress(47); qCDebug(SHELL) << "Done creating controllers"; qCDebug(SHELL) << "Initializing controllers"; sessionController.data()->initialize( session ); if( !sessionController.data()->activeSessionLock() ) { return false; } emit m_core->startupProgress(55); // TODO: Is this early enough, or should we put the loading of the session into // the controller construct DUChain::initialize(); if (!(mode & Core::NoUi)) { uiController.data()->initialize(); } languageController.data()->initialize(); if (partController) { partController.data()->initialize(); } projectController.data()->initialize(); documentController.data()->initialize(); emit m_core->startupProgress(63); /* This is somewhat messy. We want to load the areas before loading the plugins, so that when each plugin is loaded we know if an area wants some of the tool view from that plugin. OTOH, loading of areas creates documents, and some documents might require that a plugin is already loaded. Probably, the best approach would be to plugins to just add tool views to a list of available tool view, and then grab those tool views when loading an area. */ qCDebug(SHELL) << "Initializing plugin controller (loading session plugins)"; pluginController.data()->initialize(); emit m_core->startupProgress(78); qCDebug(SHELL) << "Initializing working set controller"; if(!(mode & Core::NoUi)) { workingSetController.data()->initialize(); /* Need to do this after everything else is loaded. It's too hard to restore position of views, and toolbars, and whatever that are not created yet. */ uiController.data()->loadAllAreas(KSharedConfig::openConfig()); uiController.data()->defaultMainWindow()->show(); } emit m_core->startupProgress(90); qCDebug(SHELL) << "Initializing remaining controllers"; runController.data()->initialize(); sourceFormatterController.data()->initialize(); selectionController.data()->initialize(); if (documentationController) { documentationController.data()->initialize(); } emit m_core->startupProgress(95); debugController.data()->initialize(); testController.data()->initialize(); installSignalHandler(); qCDebug(SHELL) << "Done initializing controllers"; if (partController) { // check features of kate and report to user if it does not fit KTextEditor::Document* doc = partController.data()->createTextPart(); if ( !qobject_cast< KTextEditor::MovingInterface* >(doc) ) { KMessageBox::error(QApplication::activeWindow(), i18n("The installed Kate version does not support the MovingInterface which is crucial for " "KDevelop starting from version 4.2.\n\n" "To use KDevelop with KDE SC prior to 4.6, where the SmartInterface is used instead " "of the MovingInterface, you need KDevelop 4.1 or lower.")); delete doc; return false; } delete doc; } emit m_core->startupProgress(100); return true; } CorePrivate::~CorePrivate() { delete selectionController.data(); delete projectController.data(); delete languageController.data(); delete pluginController.data(); delete uiController.data(); delete partController.data(); delete documentController.data(); delete runController.data(); delete sessionController.data(); delete sourceFormatterController.data(); delete documentationController.data(); delete debugController.data(); delete workingSetController.data(); delete testController.data(); selectionController.clear(); projectController.clear(); languageController.clear(); pluginController.clear(); uiController.clear(); partController.clear(); documentController.clear(); runController.clear(); sessionController.clear(); sourceFormatterController.clear(); documentationController.clear(); debugController.clear(); workingSetController.clear(); testController.clear(); } bool Core::initialize(QObject* splash, Setup mode, const QString& session ) { if (m_self) return true; m_self = new Core(); if (splash) { // can't use new signal/slot syntax here, we don't know the class that splash has at runtime connect(m_self, SIGNAL(startupProgress(int)), splash, SLOT(progress(int))); } bool ret = m_self->d->initialize(mode, session); if( splash ) { QTimer::singleShot( 200, splash, SLOT(deleteLater()) ); } if(ret) emit m_self->initializationCompleted(); return ret; } Core *KDevelop::Core::self() { return m_self; } Core::Core(QObject *parent) : ICore(parent) { d = new CorePrivate(this); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown); } Core::Core(CorePrivate* dd, QObject* parent) : ICore(parent), d(dd) { connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown); } Core::~Core() { qCDebug(SHELL); //Cleanup already called before mass destruction of GUI delete d; - m_self = 0; + m_self = nullptr; } Core::Setup Core::setupFlags() const { return d->m_mode; } void Core::shutdown() { qCDebug(SHELL); if (!d->m_shuttingDown) { cleanup(); deleteLater(); } qCDebug(SHELL) << "Shutdown done"; } bool Core::shuttingDown() const { return d->m_shuttingDown; } void Core::cleanup() { qCDebug(SHELL); d->m_shuttingDown = true; emit aboutToShutdown(); if (!d->m_cleanedUp) { // first of all: stop background jobs d->languageController->backgroundParser()->abortAllJobs(); d->languageController->backgroundParser()->suspend(); d->debugController.data()->cleanup(); d->selectionController.data()->cleanup(); // Save the layout of the ui here, so run it first d->uiController.data()->cleanup(); if (d->workingSetController) d->workingSetController.data()->cleanup(); /* Must be called before projectController.data()->cleanup(). */ // Closes all documents (discards, as already saved if the user wished earlier) d->documentController.data()->cleanup(); d->runController.data()->cleanup(); if (d->partController) { d->partController->cleanup(); } d->projectController.data()->cleanup(); d->sourceFormatterController.data()->cleanup(); d->pluginController.data()->cleanup(); d->sessionController.data()->cleanup(); d->testController.data()->cleanup(); //Disable the functionality of the language controller d->languageController.data()->cleanup(); DUChain::self()->shutdown(); } d->m_cleanedUp = true; emit shutdownCompleted(); } KAboutData Core::aboutData() const { return d->m_aboutData; } IUiController *Core::uiController() { return d->uiController.data(); } ISession* Core::activeSession() { return sessionController()->activeSession(); } ISessionLock::Ptr Core::activeSessionLock() { return sessionController()->activeSessionLock(); } SessionController *Core::sessionController() { return d->sessionController.data(); } UiController *Core::uiControllerInternal() { return d->uiController.data(); } IPluginController *Core::pluginController() { return d->pluginController.data(); } PluginController *Core::pluginControllerInternal() { return d->pluginController.data(); } IProjectController *Core::projectController() { return d->projectController.data(); } ProjectController *Core::projectControllerInternal() { return d->projectController.data(); } IPartController *Core::partController() { return d->partController.data(); } PartController *Core::partControllerInternal() { return d->partController.data(); } ILanguageController *Core::languageController() { return d->languageController.data(); } LanguageController *Core::languageControllerInternal() { return d->languageController.data(); } IDocumentController *Core::documentController() { return d->documentController.data(); } DocumentController *Core::documentControllerInternal() { return d->documentController.data(); } IRunController *Core::runController() { return d->runController.data(); } RunController *Core::runControllerInternal() { return d->runController.data(); } ISourceFormatterController* Core::sourceFormatterController() { return d->sourceFormatterController.data(); } SourceFormatterController* Core::sourceFormatterControllerInternal() { return d->sourceFormatterController.data(); } ProgressManager *Core::progressController() { return d->progressController.data(); } ISelectionController* Core::selectionController() { return d->selectionController.data(); } IDocumentationController* Core::documentationController() { return d->documentationController.data(); } DocumentationController* Core::documentationControllerInternal() { return d->documentationController.data(); } IDebugController* Core::debugController() { return d->debugController.data(); } DebugController* Core::debugControllerInternal() { return d->debugController.data(); } ITestController* Core::testController() { return d->testController.data(); } TestController* Core::testControllerInternal() { return d->testController.data(); } WorkingSetController* Core::workingSetControllerInternal() { return d->workingSetController.data(); } QString Core::version() { return QStringLiteral(KDEVPLATFORM_VERSION_STRING); } } diff --git a/shell/debugcontroller.cpp b/shell/debugcontroller.cpp index 45bd4186f4..b08bf7c3f5 100644 --- a/shell/debugcontroller.cpp +++ b/shell/debugcontroller.cpp @@ -1,534 +1,534 @@ /* This file is part of KDevelop * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * 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. */ #include "debugcontroller.h" #include #include #include #include #include #include #include "../interfaces/idocument.h" #include "../interfaces/icore.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/ipartcontroller.h" #include "../interfaces/contextmenuextension.h" #include "../interfaces/context.h" #include "../language/interfaces/editorcontext.h" #include "../sublime/view.h" #include "../sublime/mainwindow.h" #include "../sublime/area.h" #include "../debugger/breakpoint/breakpointmodel.h" #include "../debugger/breakpoint/breakpointwidget.h" #include "../debugger/variable/variablewidget.h" #include "../debugger/framestack/framestackmodel.h" #include "../debugger/framestack/framestackwidget.h" #include "core.h" #include "debug.h" #include "uicontroller.h" namespace KDevelop { template class DebuggerToolFactory : public KDevelop::IToolViewFactory { public: DebuggerToolFactory(DebugController* controller, const QString &id, Qt::DockWidgetArea defaultArea) : m_controller(controller), m_id(id), m_defaultArea(defaultArea) {} - QWidget* create(QWidget *parent = 0) override + QWidget* create(QWidget *parent = nullptr) override { return new T(m_controller, parent); } QString id() const override { return m_id; } Qt::DockWidgetArea defaultPosition() override { return m_defaultArea; } void viewCreated(Sublime::View* view) override { if (view->widget()->metaObject()->indexOfSignal("requestRaise()") != -1) QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise())); } /* At present, some debugger widgets (e.g. breakpoint) contain actions so that shortcuts work, but they don't need any toolbar. So, suppress toolbar action. */ QList toolBarActions( QWidget* viewWidget ) const override { Q_UNUSED(viewWidget); return QList(); } private: DebugController* m_controller; QString m_id; Qt::DockWidgetArea m_defaultArea; }; DebugController::DebugController(QObject *parent) : IDebugController(parent), KXMLGUIClient(), - m_continueDebugger(0), m_stopDebugger(0), - m_interruptDebugger(0), m_runToCursor(0), - m_jumpToCursor(0), m_stepOver(0), - m_stepIntoInstruction(0), m_stepInto(0), - m_stepOverInstruction(0), m_stepOut(0), - m_toggleBreakpoint(0), + m_continueDebugger(nullptr), m_stopDebugger(nullptr), + m_interruptDebugger(nullptr), m_runToCursor(nullptr), + m_jumpToCursor(nullptr), m_stepOver(nullptr), + m_stepIntoInstruction(nullptr), m_stepInto(nullptr), + m_stepOverInstruction(nullptr), m_stepOut(nullptr), + m_toggleBreakpoint(nullptr), m_breakpointModel(new BreakpointModel(this)), m_variableCollection(new VariableCollection(this)), m_uiInitialized(false) { setComponentName(QStringLiteral("kdevdebugger"), QStringLiteral("kdevdebugger")); setXMLFile(QStringLiteral("kdevdebuggershellui.rc")); } void DebugController::initialize() { m_breakpointModel->load(); } void DebugController::initializeUi() { if (m_uiInitialized) return; m_uiInitialized = true; if((Core::self()->setupFlags() & Core::NoUi)) return; setupActions(); ICore::self()->uiController()->addToolView( i18n("Frame Stack"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.StackView"), Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Breakpoints"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.BreakpointsView"), Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Variables"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.VariablesView"), Qt::LeftDockWidgetArea)); foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts()) partAdded(p); connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &DebugController::partAdded); ICore::self()->uiController()->activeMainWindow()->guiFactory()->addClient(this); stateChanged(QStringLiteral("ended")); } void DebugController::cleanup() { if (m_currentSession) m_currentSession.data()->stopDebugger(); } DebugController::~DebugController() { } BreakpointModel* DebugController::breakpointModel() { return m_breakpointModel; } VariableCollection* DebugController::variableCollection() { return m_variableCollection; } void DebugController::partAdded(KParts::Part* part) { if (KTextEditor::Document* doc = dynamic_cast(part)) { KTextEditor::MarkInterface *iface = dynamic_cast(doc); if( !iface ) return; iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, *executionPointPixmap()); } } IDebugSession* DebugController::currentSession() { return m_currentSession.data(); } void DebugController::setupActions() { KActionCollection* ac = actionCollection(); QAction* action = m_continueDebugger = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("&Continue"), this); action->setToolTip( i18n("Continue application execution") ); action->setWhatsThis( i18n("Continues the execution of your application in the " "debugger. This only takes effect when the application " "has been halted by the debugger (i.e. a breakpoint has " "been activated or the interrupt was pressed).") ); ac->addAction(QStringLiteral("debug_continue"), action); connect(action, &QAction::triggered, this, &DebugController::run); #if 0 m_restartDebugger = action = new QAction(QIcon::fromTheme("media-seek-backward"), i18n("&Restart"), this); action->setToolTip( i18n("Restart program") ); action->setWhatsThis( i18n("Restarts applications from the beginning.") ); action->setEnabled(false); connect(action, SIGNAL(triggered(bool)), this, SLOT(restartDebugger())); ac->addAction("debug_restart", action); #endif m_interruptDebugger = action = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-pause")), i18n("Interrupt"), this); action->setToolTip( i18n("Interrupt application") ); action->setWhatsThis(i18n("Interrupts the debugged process or current debugger command.")); connect(action, &QAction::triggered, this, &DebugController::interruptDebugger); ac->addAction(QStringLiteral("debug_pause"), action); m_runToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-run-cursor")), i18n("Run to &Cursor"), this); action->setToolTip( i18n("Run to cursor") ); action->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); connect(action, &QAction::triggered, this, &DebugController::runToCursor); ac->addAction(QStringLiteral("debug_runtocursor"), action); m_jumpToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-execute-to-cursor")), i18n("Set E&xecution Position to Cursor"), this); action->setToolTip( i18n("Jump to cursor") ); action->setWhatsThis(i18n("Continue execution from the current cursor position.")); connect(action, &QAction::triggered, this, &DebugController::jumpToCursor); ac->addAction(QStringLiteral("debug_jumptocursor"), action); m_stepOver = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-over")), i18n("Step &Over"), this); ac->setDefaultShortcut( action, Qt::Key_F10); action->setToolTip( i18n("Step over the next line") ); action->setWhatsThis( i18n("Executes one line of source in the current source file. " "If the source line is a call to a function the whole " "function is executed and the app will stop at the line " "following the function call.") ); connect(action, &QAction::triggered, this, &DebugController::stepOver); ac->addAction(QStringLiteral("debug_stepover"), action); m_stepOverInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-instruction")), i18n("Step over Ins&truction"), this); action->setToolTip( i18n("Step over instruction") ); action->setWhatsThis(i18n("Steps over the next assembly instruction.")); connect(action, &QAction::triggered, this, &DebugController::stepOverInstruction); ac->addAction(QStringLiteral("debug_stepoverinst"), action); m_stepInto = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-into")), i18n("Step &Into"), this); ac->setDefaultShortcut( action, Qt::Key_F11); action->setToolTip( i18n("Step into the next statement") ); action->setWhatsThis( i18n("Executes exactly one line of source. If the source line " "is a call to a function then execution will stop after " "the function has been entered.") ); connect(action, &QAction::triggered, this, &DebugController::stepInto); ac->addAction(QStringLiteral("debug_stepinto"), action); m_stepIntoInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-into-instruction")), i18n("Step into I&nstruction"), this); action->setToolTip( i18n("Step into instruction") ); action->setWhatsThis(i18n("Steps into the next assembly instruction.")); connect(action, &QAction::triggered, this, &DebugController::stepIntoInstruction); ac->addAction(QStringLiteral("debug_stepintoinst"), action); m_stepOut = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-out")), i18n("Step O&ut"), this); ac->setDefaultShortcut( action, Qt::Key_F12); action->setToolTip( i18n("Step out of the current function") ); action->setWhatsThis( i18n("Executes the application until the currently executing " "function is completed. The debugger will then display " "the line after the original call to that function. If " "program execution is in the outermost frame (i.e. in " "main()) then this operation has no effect.") ); connect(action, &QAction::triggered, this, &DebugController::stepOut); ac->addAction(QStringLiteral("debug_stepout"), action); m_toggleBreakpoint = action = new QAction(QIcon::fromTheme(QStringLiteral("breakpoint")), i18n("Toggle Breakpoint"), this); ac->setDefaultShortcut( action, i18n("Ctrl+Alt+B") ); action->setToolTip(i18n("Toggle breakpoint")); action->setWhatsThis(i18n("Toggles the breakpoint at the current line in editor.")); connect(action, &QAction::triggered, this, &DebugController::toggleBreakpoint); ac->addAction(QStringLiteral("debug_toggle_breakpoint"), action); } void DebugController::addSession(IDebugSession* session) { qCDebug(SHELL) << session; Q_ASSERT(session->variableController()); Q_ASSERT(session->breakpointController()); Q_ASSERT(session->frameStackModel()); //TODO support multiple sessions if (m_currentSession) { m_currentSession.data()->stopDebugger(); } m_currentSession = session; connect(session, &IDebugSession::stateChanged, this, &DebugController::debuggerStateChanged); connect(session, &IDebugSession::showStepInSource, this, &DebugController::showStepInSource); connect(session, &IDebugSession::clearExecutionPoint, this, &DebugController::clearExecutionPoint); connect(session, &IDebugSession::raiseFramestackViews, this, &DebugController::raiseFramestackViews); updateDebuggerState(session->state(), session); emit currentSessionChanged(session); if((Core::self()->setupFlags() & Core::NoUi)) return; Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow->area()->objectName() != QLatin1String("debug")) { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea(QStringLiteral("debug"), IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); connect(mainWindow, &Sublime::MainWindow::areaChanged, this, &DebugController::areaChanged); } } void DebugController::clearExecutionPoint() { qCDebug(SHELL); foreach (KDevelop::IDocument* document, KDevelop::ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *iface = dynamic_cast(document->textDocument()); if (!iface) continue; QHashIterator it = iface->marks(); while (it.hasNext()) { KTextEditor::Mark* mark = it.next().value(); if( mark->type & KTextEditor::MarkInterface::Execution ) iface->removeMark( mark->line, KTextEditor::MarkInterface::Execution ); } } } void DebugController::showStepInSource(const QUrl &url, int lineNum) { if((Core::self()->setupFlags() & Core::NoUi)) return; clearExecutionPoint(); qCDebug(SHELL) << url << lineNum; Q_ASSERT(dynamic_cast(sender())); QPair openUrl = static_cast(sender())->convertToLocalUrl(qMakePair( url, lineNum )); KDevelop::IDocument* document = KDevelop::ICore::self() ->documentController() ->openDocument(openUrl.first, KTextEditor::Cursor(openUrl.second, 0), IDocumentController::DoNotFocus); if( !document ) return; KTextEditor::MarkInterface *iface = dynamic_cast(document->textDocument()); if( !iface ) return; document->textDocument()->blockSignals(true); iface->addMark( lineNum, KTextEditor::MarkInterface::Execution ); document->textDocument()->blockSignals(false); } void DebugController::debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state) { Q_ASSERT(dynamic_cast(sender())); IDebugSession* session = static_cast(sender()); qCDebug(SHELL) << session << state << "current" << m_currentSession.data(); if (session == m_currentSession.data()) { updateDebuggerState(state, session); } if (state == IDebugSession::EndedState) { if (session == m_currentSession.data()) { m_currentSession.clear(); - emit currentSessionChanged(0); + emit currentSessionChanged(nullptr); if (!Core::self()->shuttingDown()) { Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow && mainWindow->area()->objectName() != QLatin1String("code")) { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea(QStringLiteral("code"), IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); } - ICore::self()->uiController()->findToolView(i18n("Debug"), 0, IUiController::Raise); + ICore::self()->uiController()->findToolView(i18n("Debug"), nullptr, IUiController::Raise); } } session->deleteLater(); } } void DebugController::updateDebuggerState(IDebugSession::DebuggerState state, IDebugSession *session) { Q_UNUSED(session); if((Core::self()->setupFlags() & Core::NoUi)) return; qCDebug(SHELL) << state; switch (state) { case IDebugSession::StoppedState: case IDebugSession::NotStartedState: case IDebugSession::StoppingState: qCDebug(SHELL) << "new state: stopped"; stateChanged(QStringLiteral("stopped")); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::StartingState: case IDebugSession::PausedState: qCDebug(SHELL) << "new state: paused"; stateChanged(QStringLiteral("paused")); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::ActiveState: qCDebug(SHELL) << "new state: active"; stateChanged(QStringLiteral("active")); //m_restartDebugger->setEnabled(false); break; case IDebugSession::EndedState: qCDebug(SHELL) << "new state: ended"; stateChanged(QStringLiteral("ended")); //m_restartDebugger->setEnabled(false); break; } if (state == IDebugSession::PausedState && ICore::self()->uiController()->activeMainWindow()) { ICore::self()->uiController()->activeMainWindow()->activateWindow(); } } ContextMenuExtension DebugController::contextMenuExtension( Context* context ) { ContextMenuExtension menuExt; if( context->type() != Context::EditorContext ) return menuExt; KDevelop::EditorContext *econtext = dynamic_cast(context); if (!econtext) return menuExt; if (m_currentSession && m_currentSession.data()->isRunning()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_runToCursor); } if (econtext->url().isLocalFile()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_toggleBreakpoint); } return menuExt; } #if 0 void DebugController::restartDebugger() { if (m_currentSession) { m_currentSession.data()->restartDebugger(); } } #endif void DebugController::stopDebugger() { if (m_currentSession) { m_currentSession.data()->stopDebugger(); } } void DebugController::interruptDebugger() { if (m_currentSession) { m_currentSession.data()->interruptDebugger(); } } void DebugController::run() { if (m_currentSession) { m_currentSession.data()->run(); } } void DebugController::runToCursor() { if (m_currentSession) { m_currentSession.data()->runToCursor(); } } void DebugController::jumpToCursor() { if (m_currentSession) { m_currentSession.data()->jumpToCursor(); } } void DebugController::stepOver() { if (m_currentSession) { m_currentSession.data()->stepOver(); } } void DebugController::stepIntoInstruction() { if (m_currentSession) { m_currentSession.data()->stepIntoInstruction(); } } void DebugController::stepInto() { if (m_currentSession) { m_currentSession.data()->stepInto(); } } void DebugController::stepOverInstruction() { if (m_currentSession) { m_currentSession.data()->stepOverInstruction(); } } void DebugController::stepOut() { if (m_currentSession) { m_currentSession.data()->stepOut(); } } void DebugController::areaChanged(Sublime::Area* newArea) { if (newArea->objectName()!=QLatin1String("debug")) { stopDebugger(); } } void DebugController::toggleBreakpoint() { if (KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = document->cursorPosition(); if (!cursor.isValid()) return; breakpointModel()->toggleBreakpoint(document->url(), cursor); } } const QPixmap* DebugController::executionPointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("go-next")).pixmap(QSize(22,22), QIcon::Normal, QIcon::Off); return &pixmap; } } diff --git a/shell/documentationcontroller.cpp b/shell/documentationcontroller.cpp index aff176d277..9f7f923b8a 100644 --- a/shell/documentationcontroller.cpp +++ b/shell/documentationcontroller.cpp @@ -1,233 +1,233 @@ /* Copyright 2009 Aleix Pol Gonzalez Copyright 2010 Benjamin Port 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 "documentationcontroller.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { /** * Return a "more useful" declaration that documentation providers can look-up * * @code * QPoint point; * ^-- cursor here * @endcode * * In this case, this method returns a Declaration pointer to the *type* * instead of a pointer to the instance, which is more useful when looking for help * * @return A more appropriate Declaration pointer or the given parameter @p decl */ Declaration* usefulDeclaration(Declaration* decl) { if (!decl) return nullptr; // First: Attempt to find the declaration of a definition decl = DUChainUtils::declarationForDefinition(decl); // Convenience feature: Retrieve the type declaration of instances, // it makes no sense to pass the declaration pointer of instances of types if (decl->kind() == Declaration::Instance) { AbstractType::Ptr type = TypeUtils::targetTypeKeepAliases(decl->abstractType(), decl->topContext()); IdentifiedType* idType = dynamic_cast(type.data()); - Declaration* idDecl = idType ? idType->declaration(decl->topContext()) : 0; + Declaration* idDecl = idType ? idType->declaration(decl->topContext()) : nullptr; if (idDecl) { decl = idDecl; } } return decl; } } class DocumentationViewFactory: public KDevelop::IToolViewFactory { public: DocumentationViewFactory() {} QWidget* create(QWidget *parent = nullptr) override { if (!m_providersModel) { m_providersModel.reset(new ProvidersModel); } return new DocumentationView(parent, m_providersModel.data()); } Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.DocumentationView"); } private: QScopedPointer m_providersModel; }; DocumentationController::DocumentationController(Core* core) : m_factory(new DocumentationViewFactory) { m_showDocumentation = core->uiController()->activeMainWindow()->actionCollection()->addAction(QStringLiteral("showDocumentation")); m_showDocumentation->setText(i18n("Show Documentation")); m_showDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("documentation"))); connect(m_showDocumentation, &QAction::triggered, this, &DocumentationController::doShowDocumentation); } DocumentationController::~DocumentationController() { } void DocumentationController::initialize() { if(!documentationProviders().isEmpty() && !(Core::self()->setupFlags() & Core::NoUi)) { Core::self()->uiController()->addToolView(i18n("Documentation"), m_factory); } } void KDevelop::DocumentationController::doShowDocumentation() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if(!view) return; KDevelop::DUChainReadLocker lock( DUChain::lock() ); Declaration* decl = usefulDeclaration(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); auto documentation = documentationForDeclaration(decl); if (documentation) { showDocumentation(documentation); } } KDevelop::ContextMenuExtension KDevelop::DocumentationController::contextMenuExtension ( Context* context ) { ContextMenuExtension menuExt; DeclarationContext* ctx = dynamic_cast(context); if(ctx) { DUChainReadLocker lock(DUChain::lock()); if(!ctx->declaration().data()) return menuExt; auto doc = documentationForDeclaration(ctx->declaration().data()); if (doc) { menuExt.addAction(ContextMenuExtension::ExtensionGroup, m_showDocumentation);; } } return menuExt; } IDocumentation::Ptr DocumentationController::documentationForDeclaration(Declaration* decl) { if (!decl) return {}; foreach (IDocumentationProvider* doc, documentationProviders()) { qCDebug(SHELL) << "Documentation provider found:" << doc; auto ret = doc->documentationForDeclaration(decl); qCDebug(SHELL) << "Documentation proposed: " << ret.data(); if (ret) { return ret; } } return {}; } QList< IDocumentationProvider* > DocumentationController::documentationProviders() const { QList plugins=ICore::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IDocumentationProvider")); QList pluginsProvider=ICore::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IDocumentationProviderProvider")); QList ret; foreach(IPlugin* p, pluginsProvider) { IDocumentationProviderProvider *docProvider=p->extension(); if (!docProvider) { qWarning() << "plugin" << p << "does not implement ProviderProvider extension, rerun kbuildsycoca5"; continue; } ret.append(docProvider->providers()); } foreach(IPlugin* p, plugins) { IDocumentationProvider *doc=p->extension(); if (!doc) { qWarning() << "plugin" << p << "does not implement Provider extension, rerun kbuildsycoca5"; continue; } ret.append(doc); } return ret; } void KDevelop::DocumentationController::showDocumentation(const IDocumentation::Ptr& doc) { QWidget* w = ICore::self()->uiController()->findToolView(i18n("Documentation"), m_factory, KDevelop::IUiController::CreateAndRaise); if(!w) { qWarning() << "Could not add documentation toolview"; return; } DocumentationView* view = dynamic_cast(w); if( !view ) { qWarning() << "Could not cast toolview" << w << "to DocumentationView class!"; return; } view->showDocumentation(doc); } void DocumentationController::changedDocumentationProviders() { emit providersChanged(); } diff --git a/shell/documentcontroller.cpp b/shell/documentcontroller.cpp index c1e9c4c4f5..0df5366d61 100644 --- a/shell/documentcontroller.cpp +++ b/shell/documentcontroller.cpp @@ -1,1243 +1,1243 @@ /* This file is part of the KDE project Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Bernd Gehrmann Copyright 2003 Roberto Raggi Copyright 2003-2008 Hamish Rodda Copyright 2003 Harald Fernengel Copyright 2003 Jens Dagerbo Copyright 2005 Adam Treat Copyright 2004-2007 Alexander Dymo Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentcontroller.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 "core.h" #include "mainwindow.h" #include "textdocument.h" #include "uicontroller.h" #include "partcontroller.h" #include "savedialog.h" #include "debug.h" #include #include #include #include #define EMPTY_DOCUMENT_URL i18n("Untitled") namespace KDevelop { struct DocumentControllerPrivate { struct OpenFileResult { QList urls; QString encoding; }; DocumentControllerPrivate(DocumentController* c) : controller(c) - , fileOpenRecent(0) - , globalTextEditorInstance(0) + , fileOpenRecent(nullptr) + , globalTextEditorInstance(nullptr) { } ~DocumentControllerPrivate() { //delete temporary files so they are removed from disk foreach (QTemporaryFile *temp, tempFiles) delete temp; } // used to map urls to open docs QHash< QUrl, IDocument* > documents; QHash< QString, IDocumentFactory* > factories; QList tempFiles; struct HistoryEntry { HistoryEntry() {} HistoryEntry( const QUrl & u, const KTextEditor::Cursor& cursor ); QUrl url; KTextEditor::Cursor cursor; int id; }; void removeDocument(Sublime::Document *doc) { QList urlsForDoc = documents.keys(dynamic_cast(doc)); foreach (const QUrl &url, urlsForDoc) { qCDebug(SHELL) << "destroying document" << doc; documents.remove(url); } } OpenFileResult showOpenFile() const { QUrl dir; if ( controller->activeDocument() ) { dir = controller->activeDocument()->url().adjusted(QUrl::RemoveFilename); } else { const auto cfg = KSharedConfig::openConfig()->group("Open File"); dir = cfg.readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() ); } const auto caption = i18n("Open File"); const auto filter = i18n("*|Text File\n"); auto parent = Core::self()->uiControllerInternal()->defaultMainWindow(); // use special dialogs in a KDE session, native dialogs elsewhere if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { const auto result = KEncodingFileDialog::getOpenUrlsAndEncoding(QString(), dir, filter, parent, caption); return {result.URLs, result.encoding}; } // note: can't just filter on text files using the native dialog, just display all files // see https://phabricator.kde.org/D622#11679 const auto urls = QFileDialog::getOpenFileUrls(parent, caption, dir); return {urls, QString()}; } void chooseDocument() { const auto res = showOpenFile(); if( !res.urls.isEmpty() ) { QString encoding = res.encoding; foreach( const QUrl& u, res.urls ) { openDocumentInternal(u, QString(), KTextEditor::Range::invalid(), encoding ); } } } void changeDocumentUrl(KDevelop::IDocument* document) { QMutableHashIterator it = documents; while (it.hasNext()) { if (it.next().value() == document) { if (documents.contains(document->url())) { // Weird situation (saving as a file that is aready open) IDocument* origDoc = documents[document->url()]; if (origDoc->state() & IDocument::Modified) { // given that the file has been saved, close the saved file as the other instance will become conflicted on disk document->close(); controller->activateDocument( origDoc ); break; } // Otherwise close the original document origDoc->close(); } else { // Remove the original document it.remove(); } documents.insert(document->url(), document); if (!controller->isEmptyDocumentUrl(document->url())) { fileOpenRecent->addUrl(document->url()); } break; } } } KDevelop::IDocument* findBuddyDocument(const QUrl &url, IBuddyDocumentFinder* finder) { QList allDocs = controller->openDocuments(); foreach( KDevelop::IDocument* doc, allDocs ) { if(finder->areBuddies(url, doc->url())) { return doc; } } - return 0; + return nullptr; } static bool fileExists(const QUrl& url) { if (url.isLocalFile()) { return QFile::exists(url.toLocalFile()); } else { auto job = KIO::stat(url, KIO::StatJob::SourceSide, 0); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); return job->exec(); } }; IDocument* openDocumentInternal( const QUrl & inputUrl, const QString& prefName = QString(), const KTextEditor::Range& range = KTextEditor::Range::invalid(), const QString& encoding = QString(), - DocumentController::DocumentActivationParams activationParams = 0, - IDocument* buddy = 0) + DocumentController::DocumentActivationParams activationParams = nullptr, + IDocument* buddy = nullptr) { Q_ASSERT(!inputUrl.isRelative()); Q_ASSERT(!inputUrl.fileName().isEmpty()); QString _encoding = encoding; QUrl url = inputUrl; if ( url.isEmpty() && (!activationParams.testFlag(IDocumentController::DoNotCreateView)) ) { const auto res = showOpenFile(); if( !res.urls.isEmpty() ) url = res.urls.first(); _encoding = res.encoding; if ( url.isEmpty() ) //still no url - return 0; + return nullptr; } KSharedConfig::openConfig()->group("Open File").writeEntry( "Last Open File Directory", url.adjusted(QUrl::RemoveFilename) ); // clean it and resolve possible symlink url = url.adjusted( QUrl::NormalizePathSegments ); if ( url.isLocalFile() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url = QUrl::fromLocalFile( path ); } //get a part document IDocument* doc = documents.value(url); if (!doc) { QMimeType mimeType; if (DocumentController::isEmptyDocumentUrl(url)) { mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } else if (!url.isValid()) { // Exit if the url is invalid (should not happen) // If the url is valid and the file does not already exist, // kate creates the file and gives a message saying so qCDebug(SHELL) << "invalid URL:" << url.url(); - return 0; + return nullptr; } else if (KProtocolInfo::isKnownProtocol(url.scheme()) && !fileExists(url)) { //Don't create a new file if we are not in the code mode. if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("code")) { - return 0; + return nullptr; } // enfore text mime type in order to create a kate part editor which then can be used to create the file // otherwise we could end up opening e.g. okteta which then crashes, see: https://bugs.kde.org/id=326434 mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } else { mimeType = QMimeDatabase().mimeTypeForUrl(url); if(!url.isLocalFile() && mimeType.isDefault()) { // fall back to text/plain, for remote files without extension, i.e. COPYING, LICENSE, ... // using a synchronous KIO::MimetypeJob is hazardous and may lead to repeated calls to // this function without it having returned in the first place // and this function is *not* reentrant, see assert below: // Q_ASSERT(!documents.contains(url) || documents[url]==doc); mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } } // is the URL pointing to a directory? if (mimeType.inherits(QStringLiteral("inode/directory"))) { qCDebug(SHELL) << "cannot open directory:" << url.url(); - return 0; + return nullptr; } if( prefName.isEmpty() ) { // Try to find a plugin that handles this mimetype QVariantMap constraints; constraints.insert(QStringLiteral("X-KDevelop-SupportedMimeTypes"), mimeType.name()); Core::self()->pluginController()->pluginForExtension(QString(), QString(), constraints); } if( IDocumentFactory* factory = factories.value(mimeType.name())) { doc = factory->create(url, Core::self()); } if(!doc) { if( !prefName.isEmpty() ) { doc = new PartDocument(url, Core::self(), prefName); } else if ( Core::self()->partControllerInternal()->isTextType(mimeType)) { doc = new TextDocument(url, Core::self(), _encoding); } else if( Core::self()->partControllerInternal()->canCreatePart(url) ) { doc = new PartDocument(url, Core::self()); } else { - int openAsText = KMessageBox::questionYesNo(0, i18n("KDevelop could not find the editor for file '%1' of type %2.\nDo you want to open it as plain text?", url.fileName(), mimeType.name()), i18nc("@title:window", "Could Not Find Editor"), + int openAsText = KMessageBox::questionYesNo(nullptr, i18n("KDevelop could not find the editor for file '%1' of type %2.\nDo you want to open it as plain text?", url.fileName(), mimeType.name()), i18nc("@title:window", "Could Not Find Editor"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("AskOpenWithTextEditor")); if (openAsText == KMessageBox::Yes) doc = new TextDocument(url, Core::self(), _encoding); else - return 0; + return nullptr; } } } // The url in the document must equal the current url, else the housekeeping will get broken Q_ASSERT(!doc || doc->url() == url); if(doc && openDocumentInternal(doc, range, activationParams, buddy)) return doc; else - return 0; + return nullptr; } bool openDocumentInternal(IDocument* doc, const KTextEditor::Range& range, DocumentController::DocumentActivationParams activationParams, - IDocument* buddy = 0) + IDocument* buddy = nullptr) { IDocument* previousActiveDocument = controller->activeDocument(); KTextEditor::View* previousActiveTextView = ICore::self()->documentController()->activeTextDocumentView(); KTextEditor::Cursor previousActivePosition; if(previousActiveTextView) previousActivePosition = previousActiveTextView->cursorPosition(); QUrl url=doc->url(); UiController *uiController = Core::self()->uiControllerInternal(); Sublime::Area *area = uiController->activeArea(); //We can't have the same url in many documents //so we check it's already the same if it exists //contains=>it's the same Q_ASSERT(!documents.contains(url) || documents[url]==doc); Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { documents.remove(url); delete doc; return false; } //react on document deletion - we need to cleanup controller structures QObject::connect(sdoc, &Sublime::Document::aboutToDelete, controller, &DocumentController::notifyDocumentClosed); //We check if it was already opened before bool emitOpened = !documents.contains(url); if(emitOpened) documents[url]=doc; if (!activationParams.testFlag(IDocumentController::DoNotCreateView)) { //find a view if there's one already opened in this area - Sublime::View *partView = 0; + Sublime::View *partView = nullptr; Sublime::AreaIndex* activeViewIdx = area->indexOf(uiController->activeSublimeWindow()->activeView()); foreach (Sublime::View *view, sdoc->views()) { Sublime::AreaIndex* areaIdx = area->indexOf(view); if (areaIdx && areaIdx == activeViewIdx) { partView = view; break; } } bool addView = false; if (!partView) { //no view currently shown for this url partView = sdoc->createView(); addView = true; } if(addView) { // This code is never executed when restoring session on startup, // only when opening a file manually - Sublime::View* buddyView = 0; + Sublime::View* buddyView = nullptr; bool placeAfterBuddy = true; if(Core::self()->uiControllerInternal()->arrangeBuddies() && !buddy && doc->mimeType().isValid()) { // If buddy is not set, look for a (usually) plugin which handles this URL's mimetype // and use its IBuddyDocumentFinder, if available, to find a buddy document QString mime = doc->mimeType().name(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); if(buddyFinder) { buddy = findBuddyDocument(url, buddyFinder); if(buddy) { placeAfterBuddy = buddyFinder->buddyOrder(buddy->url(), doc->url()); } } } if(buddy) { Sublime::Document* sublimeDocBuddy = dynamic_cast(buddy); if(sublimeDocBuddy) { Sublime::AreaIndex *pActiveViewIndex = area->indexOf(uiController->activeSublimeWindow()->activeView()); if(pActiveViewIndex) { // try to find existing View of buddy document in current active view's tab foreach (Sublime::View *pView, pActiveViewIndex->views()) { if(sublimeDocBuddy->views().contains(pView)) { buddyView = pView; break; } } } } } // add view to the area if(buddyView && area->indexOf(buddyView)) { if(placeAfterBuddy) { // Adding new view after buddy view, simple case area->addView(partView, area->indexOf(buddyView), buddyView); } else { // First new view, then buddy view area->addView(partView, area->indexOf(buddyView), buddyView); // move buddyView tab after the new document area->removeView(buddyView); area->addView(buddyView, area->indexOf(partView), partView); } } else { // no buddy found for new document / plugin does not support buddies / buddy feature disabled Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); - Sublime::UrlDocument *activeDoc = 0; - IBuddyDocumentFinder *buddyFinder = 0; + Sublime::UrlDocument *activeDoc = nullptr; + IBuddyDocumentFinder *buddyFinder = nullptr; if(activeView) activeDoc = dynamic_cast(activeView->document()); if(activeDoc && Core::self()->uiControllerInternal()->arrangeBuddies()) { QString mime = QMimeDatabase().mimeTypeForUrl(activeDoc->url()).name(); buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); } if(Core::self()->uiControllerInternal()->openAfterCurrent() && Core::self()->uiControllerInternal()->arrangeBuddies() && buddyFinder) { // Check if active document's buddy is directly next to it. // For example, we have the already-open tabs | *foo.h* | foo.cpp | , foo.h is active. // When we open a new document here (and the buddy feature is enabled), // we do not want to separate foo.h and foo.cpp, so we take care and avoid this. Sublime::AreaIndex *activeAreaIndex = area->indexOf(activeView); int pos = activeAreaIndex->views().indexOf(activeView); - Sublime::View *afterActiveView = activeAreaIndex->views().value(pos+1, 0); + Sublime::View *afterActiveView = activeAreaIndex->views().value(pos+1, nullptr); - Sublime::UrlDocument *activeDoc = 0, *afterActiveDoc = 0; + Sublime::UrlDocument *activeDoc = nullptr, *afterActiveDoc = nullptr; if(activeView && afterActiveView) { activeDoc = dynamic_cast(activeView->document()); afterActiveDoc = dynamic_cast(afterActiveView->document()); } if(activeDoc && afterActiveDoc && buddyFinder->areBuddies(activeDoc->url(), afterActiveDoc->url())) { // don't insert in between of two buddies, but after them area->addView(partView, activeAreaIndex, afterActiveView); } else { // The active document's buddy is not directly after it // => no ploblem, insert after active document area->addView(partView, activeView); } } else { // Opening as last tab won't disturb our buddies // Same, if buddies are disabled, we needn't care about them. // this method places the tab according to openAfterCurrent() area->addView(partView, activeView); } } } if (!activationParams.testFlag(IDocumentController::DoNotActivate)) { uiController->activeSublimeWindow()->activateView( partView, !activationParams.testFlag(IDocumentController::DoNotFocus)); } if (!controller->isEmptyDocumentUrl(url)) { fileOpenRecent->addUrl( url ); } if( range.isValid() ) { if (range.isEmpty()) doc->setCursorPosition( range.start() ); else doc->setTextSelection( range ); } } // Deferred signals, wait until it's all ready first if( emitOpened ) { emit controller->documentOpened( doc ); } if (!activationParams.testFlag(IDocumentController::DoNotActivate) && doc != controller->activeDocument()) emit controller->documentActivated( doc ); saveAll->setEnabled(true); revertAll->setEnabled(true); close->setEnabled(true); closeAll->setEnabled(true); closeAllOthers->setEnabled(true); KTextEditor::Cursor activePosition; if(range.isValid()) activePosition = range.start(); else if(KTextEditor::View* v = doc->activeTextView()) activePosition = v->cursorPosition(); if (doc != previousActiveDocument || activePosition != previousActivePosition) emit controller->documentJumpPerformed(doc, activePosition, previousActiveDocument, previousActivePosition); return true; } DocumentController* controller; QList backHistory; QList forwardHistory; bool isJumping; QPointer saveAll; QPointer revertAll; QPointer close; QPointer closeAll; QPointer closeAllOthers; KRecentFilesAction* fileOpenRecent; KTextEditor::Document* globalTextEditorInstance; }; DocumentController::DocumentController( QObject *parent ) : IDocumentController( parent ) { setObjectName(QStringLiteral("DocumentController")); d = new DocumentControllerPrivate(this); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/DocumentController"), this, QDBusConnection::ExportScriptableSlots ); connect(this, &DocumentController::documentUrlChanged, this, [&] (IDocument* document) { d->changeDocumentUrl(document); }); if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } void DocumentController::initialize() { } void DocumentController::cleanup() { if (d->fileOpenRecent) d->fileOpenRecent->saveEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); // Close all documents without checking if they should be saved. // This is because the user gets a chance to save them during MainWindow::queryClose. foreach (IDocument* doc, openDocuments()) doc->close(IDocument::Discard); } DocumentController::~DocumentController() { delete d; } void DocumentController::setupActions() { KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = ac->addAction( QStringLiteral("file_open") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_O ); action->setText(i18n( "&Open..." ) ); connect( action, &QAction::triggered, this, [&] { d->chooseDocument(); } ); action->setToolTip( i18n( "Open file" ) ); action->setWhatsThis( i18n( "Opens a file for editing." ) ); d->fileOpenRecent = KStandardAction::openRecent(this, SLOT(slotOpenDocument(QUrl)), ac); d->fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); d->fileOpenRecent->loadEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); action = d->saveAll = ac->addAction( QStringLiteral("file_save_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); action->setText(i18n( "Save Al&l" ) ); connect( action, &QAction::triggered, this, &DocumentController::slotSaveAllDocuments ); action->setToolTip( i18n( "Save all open documents" ) ); action->setWhatsThis( i18n( "Save all open documents, prompting for additional information when necessary." ) ); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L) ); action->setEnabled(false); action = d->revertAll = ac->addAction( QStringLiteral("file_revert_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-revert"))); action->setText(i18n( "Reload All" ) ); connect( action, &QAction::triggered, this, &DocumentController::reloadAllDocuments ); action->setToolTip( i18n( "Revert all open documents" ) ); action->setWhatsThis( i18n( "Revert all open documents, returning to the previously saved state." ) ); action->setEnabled(false); action = d->close = ac->addAction( QStringLiteral("file_close") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_W ); action->setText( i18n( "&Close" ) ); connect( action, &QAction::triggered, this, &DocumentController::fileClose ); action->setToolTip( i18n( "Close file" ) ); action->setWhatsThis( i18n( "Closes current file." ) ); action->setEnabled(false); action = d->closeAll = ac->addAction( QStringLiteral("file_close_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); action->setText(i18n( "Clos&e All" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllDocuments ); action->setToolTip( i18n( "Close all open documents" ) ); action->setWhatsThis( i18n( "Close all open documents, prompting for additional information when necessary." ) ); action->setEnabled(false); action = d->closeAllOthers = ac->addAction( QStringLiteral("file_closeother") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_W ); action->setText(i18n( "Close All Ot&hers" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllOtherDocuments ); action->setToolTip( i18n( "Close all other documents" ) ); action->setWhatsThis( i18n( "Close all open documents, with the exception of the currently active document." ) ); action->setEnabled(false); action = ac->addAction( QStringLiteral("vcsannotate_current_document") ); connect( action, &QAction::triggered, this, &DocumentController::vcsAnnotateCurrentDocument ); action->setText( i18n( "Show Annotate on current document") ); action->setIconText( i18n( "Annotate" ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("user-properties")) ); } void DocumentController::slotOpenDocument(const QUrl &url) { openDocument(url); } IDocument* DocumentController::openDocumentFromText( const QString& data ) { IDocument* d = openDocument(nextEmptyDocumentUrl()); Q_ASSERT(d->textDocument()); d->textDocument()->setText( data ); return d; } bool DocumentController::openDocumentFromTextSimple( QString text ) { return (bool)openDocumentFromText( text ); } bool DocumentController::openDocumentSimple( QString url, int line, int column ) { return (bool)openDocument( QUrl::fromUserInput(url), KTextEditor::Cursor( line, column ) ); } IDocument* DocumentController::openDocument( const QUrl& inputUrl, const QString& prefName ) { return d->openDocumentInternal( inputUrl, prefName ); } IDocument* DocumentController::openDocument( const QUrl & inputUrl, const KTextEditor::Range& range, DocumentActivationParams activationParams, const QString& encoding, IDocument* buddy) { return d->openDocumentInternal( inputUrl, QLatin1String(""), range, encoding, activationParams, buddy); } bool DocumentController::openDocument(IDocument* doc, const KTextEditor::Range& range, DocumentActivationParams activationParams, IDocument* buddy) { return d->openDocumentInternal( doc, range, activationParams, buddy); } void DocumentController::fileClose() { IDocument *activeDoc = activeDocument(); if (activeDoc) { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); uiController->activeArea()->closeView(activeView); } } bool DocumentController::closeDocument( const QUrl &url ) { if( !d->documents.contains(url) ) return false; //this will remove all views and after the last view is removed, the //document will be self-destructed and removeDocument() slot will catch that //and clean up internal data structures d->documents[url]->close(); return true; } void DocumentController::notifyDocumentClosed(Sublime::Document* doc_) { IDocument* doc = dynamic_cast(doc_); Q_ASSERT(doc); d->removeDocument(doc_); if (d->documents.isEmpty()) { if (d->saveAll) d->saveAll->setEnabled(false); if (d->revertAll) d->revertAll->setEnabled(false); if (d->close) d->close->setEnabled(false); if (d->closeAll) d->closeAll->setEnabled(false); if (d->closeAllOthers) d->closeAllOthers->setEnabled(false); } emit documentClosed(doc); } IDocument * DocumentController::documentForUrl( const QUrl & dirtyUrl ) const { if (dirtyUrl.isEmpty()) { return nullptr; } Q_ASSERT(!dirtyUrl.isRelative()); Q_ASSERT(!dirtyUrl.fileName().isEmpty()); //Fix urls that might not be normalized - return d->documents.value( dirtyUrl.adjusted( QUrl::NormalizePathSegments ), 0 ); + return d->documents.value( dirtyUrl.adjusted( QUrl::NormalizePathSegments ), nullptr ); } QList DocumentController::openDocuments() const { QList opened; foreach (IDocument *doc, d->documents) { Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { continue; } if (!sdoc->views().isEmpty()) opened << doc; } return opened; } void DocumentController::activateDocument( IDocument * document, const KTextEditor::Range& range ) { // TODO avoid some code in openDocument? Q_ASSERT(document); openDocument(document->url(), range); } void DocumentController::slotSaveAllDocuments() { saveAllDocuments(IDocument::Silent); } bool DocumentController::saveAllDocuments(IDocument::DocumentSaveMode mode) { return saveSomeDocuments(openDocuments(), mode); } bool KDevelop::DocumentController::saveSomeDocuments(const QList< IDocument * > & list, IDocument::DocumentSaveMode mode) { if (mode & IDocument::Silent) { foreach (IDocument* doc, modifiedDocuments(list)) { if( !DocumentController::isEmptyDocumentUrl(doc->url()) && !doc->save(mode) ) { if( doc ) qWarning() << "!! Could not save document:" << doc->url(); else qWarning() << "!! Could not save document as its NULL"; } // TODO if (!ret) showErrorDialog() ? } } else { // Ask the user which documents to save QList checkSave = modifiedDocuments(list); if (!checkSave.isEmpty()) { KSaveSelectDialog dialog(checkSave, qApp->activeWindow()); if (dialog.exec() == QDialog::Rejected) return false; } } return true; } QList< IDocument * > KDevelop::DocumentController::visibleDocumentsInWindow(MainWindow * mw) const { // Gather a list of all documents which do have a view in the given main window // Does not find documents which are open in inactive areas QList list; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { foreach (Sublime::View* view, sdoc->views()) { if (view->hasWidget() && view->widget()->window() == mw) { list.append(doc); break; } } } } return list; } QList< IDocument * > KDevelop::DocumentController::documentsExclusivelyInWindow(MainWindow * mw, bool currentAreaOnly) const { // Gather a list of all documents which have views only in the given main window QList checkSave; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { bool inOtherWindow = false; foreach (Sublime::View* view, sdoc->views()) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) if(window->containsView(view) && (window != mw || (currentAreaOnly && window == mw && !mw->area()->views().contains(view)))) inOtherWindow = true; } if (!inOtherWindow) checkSave.append(doc); } } return checkSave; } QList< IDocument * > KDevelop::DocumentController::modifiedDocuments(const QList< IDocument * > & list) const { QList< IDocument * > ret; foreach (IDocument* doc, list) if (doc->state() == IDocument::Modified || doc->state() == IDocument::DirtyAndModified) ret.append(doc); return ret; } bool DocumentController::saveAllDocumentsForWindow(KParts::MainWindow* mw, KDevelop::IDocument::DocumentSaveMode mode, bool currentAreaOnly) { QList checkSave = documentsExclusivelyInWindow(dynamic_cast(mw), currentAreaOnly); return saveSomeDocuments(checkSave, mode); } void DocumentController::reloadAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; foreach (IDocument* doc, views) if(!isEmptyDocumentUrl(doc->url())) doc->reload(); } } bool DocumentController::closeAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return false; foreach (IDocument* doc, views) doc->close(IDocument::Discard); } return true; } void DocumentController::closeAllOtherDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { Sublime::View* activeView = mw->activeView(); if (!activeView) { qWarning() << "Shouldn't there always be an active view when this function is called?"; return; } // Deal with saving unsaved solo views QList soloViews = documentsExclusivelyInWindow(dynamic_cast(mw)); soloViews.removeAll(dynamic_cast(activeView->document())); if (!saveSomeDocuments(soloViews, IDocument::Default)) // User cancelled or other error return; foreach (Sublime::View* view, mw->area()->views()) { if (view != activeView) mw->area()->closeView(view); } activeView->widget()->setFocus(); } } IDocument* DocumentController::activeDocument() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); - if( !mw || !mw->activeView() ) return 0; + if( !mw || !mw->activeView() ) return nullptr; return dynamic_cast(mw->activeView()->document()); } KTextEditor::View* DocumentController::activeTextDocumentView() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); if( !mw || !mw->activeView() ) - return 0; + return nullptr; TextView* view = qobject_cast(mw->activeView()); if(!view) - return 0; + return nullptr; return view->textView(); } QString DocumentController::activeDocumentPath( QString target ) const { if(!target.isEmpty()) { foreach(IProject* project, Core::self()->projectController()->projects()) { if(project->name().startsWith(target, Qt::CaseInsensitive)) { return project->path().pathOrUrl() + "/."; } } } IDocument* doc = activeDocument(); if(!doc || target == QStringLiteral("[selection]")) { Context* selection = ICore::self()->selectionController()->currentSelection(); if(selection && selection->type() == Context::ProjectItemContext && !static_cast(selection)->items().isEmpty()) { QString ret = static_cast(selection)->items().at(0)->path().pathOrUrl(); if(static_cast(selection)->items().at(0)->folder()) ret += QStringLiteral("/."); return ret; } return QString(); } return doc->url().toString(); } QStringList DocumentController::activeDocumentPaths() const { UiController *uiController = Core::self()->uiControllerInternal(); if( !uiController->activeSublimeWindow() ) return QStringList(); QSet documents; foreach(Sublime::View* view, uiController->activeSublimeWindow()->area()->views()) documents.insert(view->document()->documentSpecifier()); return documents.toList(); } void DocumentController::registerDocumentForMimetype( const QString& mimetype, KDevelop::IDocumentFactory* factory ) { if( !d->factories.contains( mimetype ) ) d->factories[mimetype] = factory; } QStringList DocumentController::documentTypes() const { return QStringList() << QStringLiteral("Text"); } static const QRegularExpression& emptyDocumentPattern() { static const QRegularExpression pattern(QStringLiteral("^/%1(?:\\s\\((\\d+)\\))?$").arg(EMPTY_DOCUMENT_URL)); return pattern; } bool DocumentController::isEmptyDocumentUrl(const QUrl &url) { return emptyDocumentPattern().match(url.toDisplayString(QUrl::PreferLocalFile)).hasMatch(); } QUrl DocumentController::nextEmptyDocumentUrl() { int nextEmptyDocNumber = 0; const auto& pattern = emptyDocumentPattern(); foreach (IDocument *doc, Core::self()->documentControllerInternal()->openDocuments()) { if (DocumentController::isEmptyDocumentUrl(doc->url())) { const auto match = pattern.match(doc->url().toDisplayString(QUrl::PreferLocalFile)); if (match.hasMatch()) { const int num = match.captured(1).toInt(); nextEmptyDocNumber = qMax(nextEmptyDocNumber, num + 1); } else { nextEmptyDocNumber = qMax(nextEmptyDocNumber, 1); } } } QUrl url; if (nextEmptyDocNumber > 0) url = QUrl::fromLocalFile(QStringLiteral("/%1 (%2)").arg(EMPTY_DOCUMENT_URL).arg(nextEmptyDocNumber)); else url = QUrl::fromLocalFile('/' + EMPTY_DOCUMENT_URL); return url; } IDocumentFactory* DocumentController::factory(const QString& mime) const { return d->factories.value(mime); } KTextEditor::Document* DocumentController::globalTextEditorInstance() { if(!d->globalTextEditorInstance) d->globalTextEditorInstance = Core::self()->partControllerInternal()->createTextPart(); return d->globalTextEditorInstance; } bool DocumentController::openDocumentsSimple( QStringList urls ) { Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); Sublime::AreaIndex* areaIndex = area->rootIndex(); QList topViews = static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->getTopViews(); if(Sublime::View* activeView = Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()) areaIndex = area->indexOf(activeView); qCDebug(SHELL) << "opening " << urls << " to area " << area << " index " << areaIndex << " with children " << areaIndex->first() << " " << areaIndex->second(); bool isFirstView = true; bool ret = openDocumentsWithSplitSeparators( areaIndex, urls, isFirstView ); qCDebug(SHELL) << "area arch. after opening: " << areaIndex->print(); // Required because sublime sometimes doesn't update correctly when the area-index contents has been changed // (especially when views have been moved to other indices, through unsplit, split, etc.) static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->reconstructViews(topViews); return ret; } bool DocumentController::openDocumentsWithSplitSeparators( Sublime::AreaIndex* index, QStringList urlsWithSeparators, bool& isFirstView ) { qCDebug(SHELL) << "opening " << urlsWithSeparators << " index " << index << " with children " << index->first() << " " << index->second() << " view-count " << index->viewCount(); if(urlsWithSeparators.isEmpty()) return true; Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); QList topLevelSeparators; // Indices of the top-level separators (with groups skipped) QStringList separators = QStringList() << QStringLiteral("/") << QStringLiteral("-"); QList groups; bool ret = true; { int parenDepth = 0; int groupStart = 0; for(int pos = 0; pos < urlsWithSeparators.size(); ++pos) { QString item = urlsWithSeparators[pos]; if(separators.contains(item)) { if(parenDepth == 0) topLevelSeparators << pos; }else if(item == QLatin1String("[")) { if(parenDepth == 0) groupStart = pos+1; ++parenDepth; } else if(item == QLatin1String("]")) { if(parenDepth > 0) { --parenDepth; if(parenDepth == 0) groups << urlsWithSeparators.mid(groupStart, pos-groupStart); } else{ qCDebug(SHELL) << "syntax error in " << urlsWithSeparators << ": parens do not match"; ret = false; } }else if(parenDepth == 0) { groups << (QStringList() << item); } } } if(topLevelSeparators.isEmpty()) { if(urlsWithSeparators.size() > 1) { foreach(const QStringList& group, groups) ret &= openDocumentsWithSplitSeparators( index, group, isFirstView ); }else{ while(index->isSplit()) index = index->first(); // Simply open the document into the area index IDocument* doc = Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(urlsWithSeparators.front()), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *sublimeDoc = dynamic_cast(doc); if (sublimeDoc) { Sublime::View* view = sublimeDoc->createView(); area->addView(view, index); if(isFirstView) { static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->activateView(view); isFirstView = false; } }else{ ret = false; } } return ret; } // Pick a separator in the middle int pickSeparator = topLevelSeparators[topLevelSeparators.size()/2]; bool activeViewToSecondChild = false; if(pickSeparator == urlsWithSeparators.size()-1) { // There is no right child group, so the right side should be filled with the currently active views activeViewToSecondChild = true; }else{ QStringList separatorsAndParens = separators; separatorsAndParens << QStringLiteral("[") << QStringLiteral("]"); // Check if the second child-set contains an unterminated separator, which means that the active views should end up there for(int pos = pickSeparator+1; pos < urlsWithSeparators.size(); ++pos) if( separators.contains(urlsWithSeparators[pos]) && (pos == urlsWithSeparators.size()-1 || separatorsAndParens.contains(urlsWithSeparators[pos-1]) || separatorsAndParens.contains(urlsWithSeparators[pos-1])) ) activeViewToSecondChild = true; } Qt::Orientation orientation = urlsWithSeparators[pickSeparator] == QLatin1String("/") ? Qt::Horizontal : Qt::Vertical; if(!index->isSplit()) { qCDebug(SHELL) << "splitting " << index << "orientation" << orientation << "to second" << activeViewToSecondChild; index->split(orientation, activeViewToSecondChild); }else{ index->setOrientation(orientation); qCDebug(SHELL) << "WARNING: Area is already split (shouldn't be)" << urlsWithSeparators; } openDocumentsWithSplitSeparators( index->first(), urlsWithSeparators.mid(0, pickSeparator) , isFirstView ); if(pickSeparator != urlsWithSeparators.size() - 1) openDocumentsWithSplitSeparators( index->second(), urlsWithSeparators.mid(pickSeparator+1, urlsWithSeparators.size() - (pickSeparator+1) ), isFirstView ); // Clean up the child-indices, because document-loading may fail if(!index->first()->viewCount() && !index->first()->isSplit()) { qCDebug(SHELL) << "unsplitting first"; index->unsplit(index->first()); } else if(!index->second()->viewCount() && !index->second()->isSplit()) { qCDebug(SHELL) << "unsplitting second"; index->unsplit(index->second()); } return ret; } void DocumentController::vcsAnnotateCurrentDocument() { IDocument* doc = activeDocument(); QUrl url = doc->url(); IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { - IBasicVersionControl* iface = 0; + IBasicVersionControl* iface = nullptr; iface = project->versionControlPlugin()->extension(); auto helper = new VcsPluginHelper(project->versionControlPlugin(), iface); connect(doc->textDocument(), &KTextEditor::Document::aboutToClose, helper, static_cast(&VcsPluginHelper::disposeEventually)); Q_ASSERT(qobject_cast(doc->activeTextView())); // can't use new signal slot syntax here, AnnotationViewInterface is not a QObject connect(doc->activeTextView(), SIGNAL(annotationBorderVisibilityChanged(View*,bool)), helper, SLOT(disposeEventually(View*, bool))); helper->addContextDocument(url); helper->annotation(); } else { - KMessageBox::error(0, i18n("Could not annotate the document because it is not " + KMessageBox::error(nullptr, i18n("Could not annotate the document because it is not " "part of a version-controlled project.")); } } } #include "moc_documentcontroller.cpp" diff --git a/shell/environmentconfigurebutton.cpp b/shell/environmentconfigurebutton.cpp index 369cfe4daf..1ae375cc8d 100644 --- a/shell/environmentconfigurebutton.cpp +++ b/shell/environmentconfigurebutton.cpp @@ -1,108 +1,108 @@ /* This file is part of KDevelop Copyright 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentconfigurebutton.h" #include #include #include "settings/environmentpreferences.h" #include #include #include #include #include #include namespace KDevelop { class EnvironmentConfigureButtonPrivate { public: EnvironmentConfigureButtonPrivate(EnvironmentConfigureButton* _q) - : q(_q), selectionWidget(0) + : q(_q), selectionWidget(nullptr) { } void showDialog() { QDialog dlg(qApp->activeWindow()); QString selected; if (selectionWidget) { selected = selectionWidget->effectiveProfileName(); } EnvironmentPreferences prefs(selected, q); // TODO: This should be implicit when constructing EnvironmentPreferences prefs.initConfigManager(); prefs.reset(); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); auto layout = new QVBoxLayout; layout->addWidget(&prefs); layout->addWidget(buttonBox); dlg.setLayout(layout); dlg.setWindowTitle(prefs.fullName()); dlg.setWindowIcon(prefs.icon()); dlg.resize(480, 320); if (dlg.exec() == QDialog::Accepted) { prefs.apply(); emit q->environmentConfigured(); } } EnvironmentConfigureButton *q; EnvironmentSelectionWidget *selectionWidget; }; EnvironmentConfigureButton::EnvironmentConfigureButton(QWidget* parent) : QPushButton(parent), d(new EnvironmentConfigureButtonPrivate(this)) { setText(QString()); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); setIcon(QIcon::fromTheme(QStringLiteral("configure"))); setToolTip(i18n("Configure environment variables")); connect(this, &EnvironmentConfigureButton::clicked, this, [&] { d->showDialog(); }); } EnvironmentConfigureButton::~EnvironmentConfigureButton() { delete d; } void EnvironmentConfigureButton::setSelectionWidget(EnvironmentSelectionWidget* widget) { connect(this, &EnvironmentConfigureButton::environmentConfigured, widget, &EnvironmentSelectionWidget::reconfigure); d->selectionWidget = widget; } } #include "moc_environmentconfigurebutton.cpp" diff --git a/shell/languagecontroller.cpp b/shell/languagecontroller.cpp index b471d1c50e..0ac5c009cc 100644 --- a/shell/languagecontroller.cpp +++ b/shell/languagecontroller.cpp @@ -1,367 +1,367 @@ /*************************************************************************** * Copyright 2006 Adam Treat * * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "languagecontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "problemmodelset.h" #include "core.h" #include "settings/languagepreferences.h" #include "completionsettings.h" #include "debug.h" namespace { // Maximum length of a string to still consider it as a file extension which we cache // This has to be a slow value, so that we don't fill our file extension cache with crap const int maximumCacheExtensionLength = 3; QString KEY_SupportedMimeTypes() { return QStringLiteral("X-KDevelop-SupportedMimeTypes"); } QString KEY_ILanguageSupport() { return QStringLiteral("ILanguageSupport"); } } #if QT_VERSION < 0x050600 inline uint qHash(const QMimeType& mime, uint seed = 0) { return qHash(mime.name(), seed); } #endif namespace KDevelop { typedef QHash LanguageHash; typedef QHash > LanguageCache; struct LanguageControllerPrivate { LanguageControllerPrivate(LanguageController *controller) : dataMutex(QMutex::Recursive) , backgroundParser(new BackgroundParser(controller)) , staticAssistantsManager(nullptr) , m_cleanedUp(false) , problemModelSet(new ProblemModelSet(controller)) , m_controller(controller) {} void documentActivated(KDevelop::IDocument *document) { QUrl url = document->url(); if (!url.isValid()) { return; } activeLanguages.clear(); QList languages = m_controller->languagesForUrl(url); foreach (const auto lang, languages) { activeLanguages << lang; } } QList activeLanguages; mutable QMutex dataMutex; LanguageHash languages; //Maps language-names to languages LanguageCache languageCache; //Maps mimetype-names to languages typedef QMultiHash MimeTypeCache; MimeTypeCache mimeTypeCache; //Maps mimetypes to languages BackgroundParser *backgroundParser; StaticAssistantsManager* staticAssistantsManager; bool m_cleanedUp; void addLanguageSupport(ILanguageSupport* support, const QStringList& mimetypes); void addLanguageSupport(ILanguageSupport* support); ProblemModelSet *problemModelSet; private: LanguageController *m_controller; }; void LanguageControllerPrivate::addLanguageSupport(ILanguageSupport* languageSupport, const QStringList& mimetypes) { Q_ASSERT(!languages.contains(languageSupport->name())); languages.insert(languageSupport->name(), languageSupport); foreach(const QString& mimeTypeName, mimetypes) { qCDebug(SHELL) << "adding supported mimetype:" << mimeTypeName << "language:" << languageSupport->name(); languageCache[mimeTypeName] << languageSupport; QMimeType mime = QMimeDatabase().mimeTypeForName(mimeTypeName); if (mime.isValid()) { mimeTypeCache.insert(mime, languageSupport); } else { qWarning() << "could not create mime-type" << mimeTypeName; } } } void LanguageControllerPrivate::addLanguageSupport(KDevelop::ILanguageSupport* languageSupport) { if (languages.contains(languageSupport->name())) return; Q_ASSERT(dynamic_cast(languageSupport)); KPluginMetaData info = Core::self()->pluginController()->pluginInfo(dynamic_cast(languageSupport)); QStringList mimetypes = KPluginMetaData::readStringList(info.rawData(), KEY_SupportedMimeTypes()); addLanguageSupport(languageSupport, mimetypes); } LanguageController::LanguageController(QObject *parent) : ILanguageController(parent) { setObjectName(QStringLiteral("LanguageController")); d = new LanguageControllerPrivate(this); } LanguageController::~LanguageController() { delete d; } void LanguageController::initialize() { d->backgroundParser->loadSettings(); d->staticAssistantsManager = new StaticAssistantsManager(this); // make sure the DUChain is setup before we try to access it from different threads at the same time DUChain::self(); connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, [&] (IDocument* document) { d->documentActivated(document); }); } void LanguageController::cleanup() { QMutexLocker lock(&d->dataMutex); d->m_cleanedUp = true; } QList LanguageController::activeLanguages() { QMutexLocker lock(&d->dataMutex); return d->activeLanguages; } StaticAssistantsManager* LanguageController::staticAssistantsManager() const { return d->staticAssistantsManager; } ICompletionSettings *LanguageController::completionSettings() const { return &CompletionSettings::self(); } ProblemModelSet* LanguageController::problemModelSet() const { return d->problemModelSet; } QList LanguageController::loadedLanguages() const { QMutexLocker lock(&d->dataMutex); QList ret; if(d->m_cleanedUp) return ret; foreach(ILanguageSupport* lang, d->languages) ret << lang; return ret; } ILanguageSupport* LanguageController::language(const QString &name) const { QMutexLocker lock(&d->dataMutex); if(d->m_cleanedUp) - return 0; + return nullptr; if(d->languages.contains(name)) return d->languages[name]; QVariantMap constraints; constraints.insert(QStringLiteral("X-KDevelop-Language"), name); QList supports = Core::self()->pluginController()->allPluginsForExtension(KEY_ILanguageSupport(), constraints); if(!supports.isEmpty()) { ILanguageSupport *languageSupport = supports[0]->extension(); if(languageSupport) { d->addLanguageSupport(languageSupport); return languageSupport; } } return nullptr; } bool isNumeric(const QString& str) { int len = str.length(); if(len == 0) return false; for(int a = 0; a < len; ++a) if(!str[a].isNumber()) return false; return true; } QList LanguageController::languagesForUrl(const QUrl &url) { QMutexLocker lock(&d->dataMutex); QList languages; if(d->m_cleanedUp) return languages; const QString fileName = url.fileName(); ///TODO: cache regexp or simple string pattern for endsWith matching QRegExp exp("", Qt::CaseInsensitive, QRegExp::Wildcard); ///non-crashy part: Use the mime-types of known languages for(LanguageControllerPrivate::MimeTypeCache::const_iterator it = d->mimeTypeCache.constBegin(); it != d->mimeTypeCache.constEnd(); ++it) { foreach(const QString& pattern, it.key().globPatterns()) { if(pattern.startsWith('*')) { const QStringRef subPattern = pattern.midRef(1); if (!subPattern.contains('*')) { //optimize: we can skip the expensive QRegExp in this case //and do a simple string compare (much faster) if (fileName.endsWith(subPattern)) { languages << *it; } continue; } } exp.setPattern(pattern); if(int position = exp.indexIn(fileName)) { if(position != -1 && exp.matchedLength() + position == fileName.length()) languages << *it; } } } if(!languages.isEmpty()) return languages; //Never use findByUrl from within a background thread, and never load a language support //from within the backgruond thread. Both is unsafe, and can lead to crashes if(!languages.isEmpty() || QThread::currentThread() != thread()) return languages; QMimeType mimeType; if (url.isLocalFile()) { mimeType = QMimeDatabase().mimeTypeForFile(url.toLocalFile()); } else { // remote file, only look at the extension mimeType = QMimeDatabase().mimeTypeForUrl(url); } if (mimeType.isDefault()) { // ask the document controller about a more concrete mimetype IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (doc) { mimeType = doc->mimeType(); } } languages = languagesForMimetype(mimeType.name()); return languages; } QList LanguageController::languagesForMimetype(const QString& mimetype) { QMutexLocker lock(&d->dataMutex); QList languages; LanguageCache::ConstIterator it = d->languageCache.constFind(mimetype); if (it != d->languageCache.constEnd()) { languages = it.value(); } else { QVariantMap constraints; constraints.insert(KEY_SupportedMimeTypes(), mimetype); QList supports = Core::self()->pluginController()->allPluginsForExtension(KEY_ILanguageSupport(), constraints); if (supports.isEmpty()) { qCDebug(SHELL) << "no languages for mimetype:" << mimetype; d->languageCache.insert(mimetype, QList()); } else { foreach (IPlugin *support, supports) { ILanguageSupport* languageSupport = support->extension(); qCDebug(SHELL) << "language-support:" << languageSupport; if(languageSupport) { d->addLanguageSupport(languageSupport); languages << languageSupport; } } } } return languages; } QList LanguageController::mimetypesForLanguageName(const QString& languageName) { QMutexLocker lock(&d->dataMutex); QList mimetypes; for (LanguageCache::ConstIterator iter = d->languageCache.constBegin(); iter != d->languageCache.constEnd(); ++iter) { foreach (ILanguageSupport* language, iter.value()) { if (language->name() == languageName) { mimetypes << iter.key(); break; } } } return mimetypes; } BackgroundParser *LanguageController::backgroundParser() const { return d->backgroundParser; } void LanguageController::addLanguageSupport(ILanguageSupport* languageSupport, const QStringList& mimetypes) { d->addLanguageSupport(languageSupport, mimetypes); } } #include "moc_languagecontroller.cpp" diff --git a/shell/launchconfigurationdialog.cpp b/shell/launchconfigurationdialog.cpp index 1c31441e9e..99bb5ffcba 100644 --- a/shell/launchconfigurationdialog.cpp +++ b/shell/launchconfigurationdialog.cpp @@ -1,1024 +1,1024 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "launchconfigurationdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "runcontroller.h" #include "launchconfiguration.h" #include "debug.h" #include #include #include namespace KDevelop { bool launchConfigGreaterThan(KDevelop::LaunchConfigurationType* a, KDevelop::LaunchConfigurationType* b) { return a->name()>b->name(); } //TODO: Maybe use KPageDialog instead, might make the model stuff easier and the default-size stuff as well LaunchConfigurationDialog::LaunchConfigurationDialog(QWidget* parent) : QDialog(parent) , currentPageChanged(false) { setWindowTitle( i18n( "Launch Configurations" ) ); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); setupUi(mainWidget); mainLayout->setContentsMargins( 0, 0, 0, 0 ); splitter->setSizes(QList() << 260 << 620); splitter->setCollapsible(0, false); addConfig->setIcon( QIcon::fromTheme(QStringLiteral("list-add")) ); addConfig->setToolTip(i18nc("@info:tooltip", "Add a new launch configuration.")); deleteConfig->setIcon( QIcon::fromTheme(QStringLiteral("list-remove")) ); deleteConfig->setEnabled( false ); deleteConfig->setToolTip(i18nc("@info:tooltip", "Delete selected launch configuration.")); model = new LaunchConfigurationsModel( tree ); tree->setModel( model ); tree->setExpandsOnDoubleClick( true ); tree->setSelectionBehavior( QAbstractItemView::SelectRows ); tree->setSelectionMode( QAbstractItemView::SingleSelection ); tree->setUniformRowHeights( true ); tree->setItemDelegate( new LaunchConfigurationModelDelegate(this) ); tree->setColumnHidden(1, true); for(int row=0; rowrowCount(); row++) { tree->setExpanded(model->index(row, 0), true); } tree->setContextMenuPolicy(Qt::CustomContextMenu); connect( tree, &QTreeView::customContextMenuRequested, this, &LaunchConfigurationDialog::doTreeContextMenu ); connect( deleteConfig, &QToolButton::clicked, this, &LaunchConfigurationDialog::deleteConfiguration); connect( model, &LaunchConfigurationsModel::dataChanged, this, &LaunchConfigurationDialog::modelChanged ); connect( tree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &LaunchConfigurationDialog::selectionChanged); QModelIndex idx = model->indexForConfig( Core::self()->runControllerInternal()->defaultLaunch() ); qCDebug(SHELL) << "selecting index:" << idx; if( !idx.isValid() ) { for( int i = 0; i < model->rowCount(); i++ ) { if( model->rowCount( model->index( i, 0, QModelIndex() ) ) > 0 ) { idx = model->index( 1, 0, model->index( i, 0, QModelIndex() ) ); break; } } if( !idx.isValid() ) { idx = model->index( 0, 0, QModelIndex() ); } } tree->selectionModel()->select( QItemSelection( idx, idx ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); // Unfortunately tree->resizeColumnToContents() only looks at the top-level // items, instead of all open ones. Hence we're calculating it ourselves like // this: // Take the selected index, check if it has childs, if so take the first child // Then count the level by going up, then let the tree calculate the width // for the selected or its first child index and add indentation*level // // If Qt Software ever fixes resizeColumnToContents, the following line // can be enabled and the rest be removed // tree->resizeColumnToContents( 0 ); int level = 0; QModelIndex widthidx = idx; if( model->rowCount( idx ) > 0 ) { widthidx = idx.child( 0, 0 ); } QModelIndex parentidx = widthidx.parent(); while( parentidx.isValid() ) { level++; parentidx = parentidx.parent(); } // make sure the base column width is honored, e.g. when no launch configs exist tree->resizeColumnToContents(0); int width = tree->columnWidth( 0 ); while ( widthidx.isValid() ) { width = qMax( width, level*tree->indentation() + tree->indentation() + tree->sizeHintForIndex( widthidx ).width() ); widthidx = widthidx.parent(); } tree->setColumnWidth( 0, width ); QMenu* m = new QMenu(this); QList types = Core::self()->runController()->launchConfigurationTypes(); std::sort(types.begin(), types.end(), launchConfigGreaterThan); //we want it in reverse order foreach(LaunchConfigurationType* type, types) { connect(type, &LaunchConfigurationType::signalAddLaunchConfiguration, this, &LaunchConfigurationDialog::addConfiguration); QMenu* suggestionsMenu = type->launcherSuggestions(); if(suggestionsMenu) { m->addMenu(suggestionsMenu); } } // Simplify menu structure to get rid of 1-entry levels while (m->actions().count() == 1) { QMenu* subMenu = m->actions().at(0)->menu(); if (subMenu && subMenu->isEnabled() && subMenu->actions().count()<5) { m = subMenu; } else { break; } } if(!m->isEmpty()) { QAction* separator = new QAction(m); separator->setSeparator(true); m->insertAction(m->actions().at(0), separator); } foreach(LaunchConfigurationType* type, types) { QAction* action = new QAction(type->icon(), type->name(), m); action->setProperty("configtype", qVariantFromValue(type)); connect(action, &QAction::triggered, this, &LaunchConfigurationDialog::createEmptyLauncher); if(!m->actions().isEmpty()) m->insertAction(m->actions().at(0), action); else m->addAction(action); } addConfig->setMenu(m); addConfig->setEnabled( !m->isEmpty() ); messageWidget->setCloseButtonVisible( false ); messageWidget->setMessageType( KMessageWidget::Warning ); messageWidget->setText( i18n("No launch configurations available. (Is any of the Execute plugins loaded?)") ); messageWidget->setVisible( m->isEmpty() ); connect(debugger, static_cast(&QComboBox::currentIndexChanged), this, &LaunchConfigurationDialog::launchModeChanged); connect(buttonBox, &QDialogButtonBox::accepted, this, &LaunchConfigurationDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LaunchConfigurationDialog::reject); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, static_cast(&LaunchConfigurationDialog::saveConfig) ); connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, static_cast(&LaunchConfigurationDialog::saveConfig) ); mainLayout->addWidget(buttonBox); resize( QSize(qMax(700, sizeHint().width()), qMax(500, sizeHint().height())) ); } void LaunchConfigurationDialog::doTreeContextMenu(QPoint point) { if ( ! tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex selected = tree->selectionModel()->selectedRows().first(); if ( selected.parent().isValid() && ! selected.parent().parent().isValid() ) { // only display the menu if a launch config is clicked QMenu menu; QAction* rename = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename configuration"), &menu); QAction* delete_ = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete configuration"), &menu); connect(rename, &QAction::triggered, this, &LaunchConfigurationDialog::renameSelected); connect(delete_, &QAction::triggered, this, &LaunchConfigurationDialog::deleteConfiguration); menu.addAction(rename); menu.addAction(delete_); menu.exec(tree->mapToGlobal(point)); } } } void LaunchConfigurationDialog::renameSelected() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex parent = tree->selectionModel()->selectedRows().first(); if( parent.parent().isValid() ) { parent = parent.parent(); } QModelIndex index = model->index(tree->selectionModel()->selectedRows().first().row(), 0, parent); tree->edit( index ); } } QSize LaunchConfigurationDialog::sizeHint() const { QSize s = QDialog::sizeHint(); return s.expandedTo(QSize(880, 520)); } void LaunchConfigurationDialog::createEmptyLauncher() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); LaunchConfigurationType* type = qobject_cast(action->property("configtype").value()); Q_ASSERT(type); IProject* p = model->projectForIndex(tree->currentIndex()); QPair< QString, QString > launcher( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() ); ILaunchConfiguration* l = ICore::self()->runController()->createLaunchConfiguration(type, launcher, p); addConfiguration(l); } void LaunchConfigurationDialog::selectionChanged(QItemSelection selected, QItemSelection deselected ) { if( !deselected.indexes().isEmpty() ) { LaunchConfiguration* l = model->configForIndex( deselected.indexes().first() ); if( l ) { disconnect(l, &LaunchConfiguration::nameChanged, this, &LaunchConfigurationDialog::updateNameLabel); if( currentPageChanged ) { if( KMessageBox::questionYesNo( this, i18n("Selected Launch Configuration has unsaved changes. Do you want to save it?"), i18n("Unsaved Changes") ) == KMessageBox::Yes ) { saveConfig( deselected.indexes().first() ); } else { LaunchConfigPagesContainer* tab = qobject_cast( stack->currentWidget() ); tab->setLaunchConfiguration( l ); buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false ); currentPageChanged = false; } } } } - updateNameLabel(0); + updateNameLabel(nullptr); for( int i = 1; i < stack->count(); i++ ) { QWidget* w = stack->widget(i); stack->removeWidget(w); delete w; } debugger->clear(); if( !selected.indexes().isEmpty() ) { QModelIndex idx = selected.indexes().first(); LaunchConfiguration* l = model->configForIndex( idx ); ILaunchMode* lm = model->modeForIndex( idx ); if( l ) { updateNameLabel( l ); tree->expand( model->indexForConfig( l ) ); connect( l, &LaunchConfiguration::nameChanged, this, &LaunchConfigurationDialog::updateNameLabel ); if( lm ) { bool b = debugger->blockSignals(true); QList launchers = l->type()->launchers(); for( QList::const_iterator it = launchers.constBegin(); it != launchers.constEnd(); it++ ) { if( ((*it)->supportedModes().contains( lm->id() ) ) ) { debugger->addItem( (*it)->name(), (*it)->id() ); } } debugger->blockSignals(b); debugger->setVisible(debugger->count()>0); debugLabel->setVisible(debugger->count()>0); QVariant currentLaunchMode = idx.sibling(idx.row(), 1).data(Qt::EditRole); debugger->setCurrentIndex(debugger->findData(currentLaunchMode)); ILauncher* launcher = l->type()->launcherForId( currentLaunchMode.toString() ); if( launcher ) { LaunchConfigPagesContainer* tab = launcherWidgets.value( launcher ); if(!tab) { QList pages = launcher->configPages(); if(!pages.isEmpty()) { tab = new LaunchConfigPagesContainer( launcher->configPages(), stack ); connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged ); stack->addWidget( tab ); } } if(tab) { tab->setLaunchConfiguration( l ); stack->setCurrentWidget( tab ); } else { QLabel* label = new QLabel(i18nc("%1 is a launcher name", "No configuration is needed for '%1'", launcher->name()), stack); label->setAlignment(Qt::AlignCenter); QFont font = label->font(); font.setItalic(true); label->setFont(font); stack->addWidget(label); stack->setCurrentWidget(label); } updateNameLabel( l ); addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); } else { addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); } } else { //TODO: enable removal button LaunchConfigurationType* type = l->type(); LaunchConfigPagesContainer* tab = typeWidgets.value( type ); if( !tab ) { tab = new LaunchConfigPagesContainer( type->configPages(), stack ); connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged ); stack->addWidget( tab ); } qCDebug(SHELL) << "created pages, setting config up"; tab->setLaunchConfiguration( l ); stack->setCurrentWidget( tab ); addConfig->setEnabled( addConfig->menu() && !addConfig->menu()->isEmpty() ); deleteConfig->setEnabled( true ); debugger->setVisible( false ); debugLabel->setVisible( false ); } } else { addConfig->setEnabled( addConfig->menu() && !addConfig->menu()->isEmpty() ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); QLabel* l = new QLabel(i18n("Select a configuration to edit from the left,
" "or click the \"Add New\" button to add a new one.
"), stack); l->setAlignment(Qt::AlignCenter); stack->addWidget(l); stack->setCurrentWidget(l); debugger->setVisible( false ); debugLabel->setVisible( false ); } } else { debugger->setVisible( false ); debugLabel->setVisible( false ); addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); } } void LaunchConfigurationDialog::saveConfig( const QModelIndex& idx ) { Q_UNUSED( idx ); LaunchConfigPagesContainer* tab = qobject_cast( stack->currentWidget() ); if( tab ) { tab->save(); buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false ); currentPageChanged = false; } } void LaunchConfigurationDialog::saveConfig() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { saveConfig( tree->selectionModel()->selectedRows().first() ); } } void LaunchConfigurationDialog::pageChanged() { currentPageChanged = true; buttonBox->button(QDialogButtonBox::Apply)->setEnabled( true ); } void LaunchConfigurationDialog::modelChanged(QModelIndex topLeft, QModelIndex bottomRight) { if (tree->selectionModel()) { QModelIndex index = tree->selectionModel()->selectedRows().first(); if (index.row() >= topLeft.row() && index.row() <= bottomRight.row() && bottomRight.column() == 1) selectionChanged(tree->selectionModel()->selection(), tree->selectionModel()->selection()); } } void LaunchConfigurationDialog::deleteConfiguration() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { model->deleteConfiguration( tree->selectionModel()->selectedRows().first() ); tree->resizeColumnToContents( 0 ); } } void LaunchConfigurationDialog::updateNameLabel( LaunchConfiguration* l ) { if( l ) { configName->setText( i18n("Editing %2: %1", l->name(), l->type()->name() ) ); } else { configName->clear(); } } void LaunchConfigurationDialog::createConfiguration() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex idx = tree->selectionModel()->selectedRows().first(); if( idx.parent().isValid() ) { idx = idx.parent(); } model->createConfiguration( idx ); QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx ); tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->edit( newindex ); tree->resizeColumnToContents( 0 ); } } void LaunchConfigurationDialog::addConfiguration(ILaunchConfiguration* _launch) { LaunchConfiguration* launch = dynamic_cast(_launch); Q_ASSERT(launch); int row = launch->project() ? model->findItemForProject(launch->project())->row : 0; QModelIndex idx = model->index(row, 0); model->addConfiguration(launch, idx); QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx ); tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->edit( newindex ); tree->resizeColumnToContents( 0 ); } LaunchConfigurationsModel::LaunchConfigurationsModel(QObject* parent): QAbstractItemModel(parent) { GenericPageItem* global = new GenericPageItem; global->text = i18n("Global"); global->row = 0; topItems << global; foreach( IProject* p, Core::self()->projectController()->projects() ) { ProjectItem* t = new ProjectItem; t->project = p; t->row = topItems.count(); topItems << t; } foreach( LaunchConfiguration* l, Core::self()->runControllerInternal()->launchConfigurationsInternal() ) { addItemForLaunchConfig( l ); } } void LaunchConfigurationsModel::addItemForLaunchConfig( LaunchConfiguration* l ) { LaunchItem* t = new LaunchItem; t->launch = l; TreeItem* parent; if( l->project() ) { parent = findItemForProject( l->project() ); } else { parent = topItems.at(0); } t->parent = parent; t->row = parent->children.count(); parent->children.append( t ); addLaunchModeItemsForLaunchConfig ( t ); } void LaunchConfigurationsModel::addLaunchModeItemsForLaunchConfig ( LaunchItem* t ) { QList items; QSet modes; foreach( ILauncher* launcher, t->launch->type()->launchers() ) { foreach( const QString& mode, launcher->supportedModes() ) { if( !modes.contains( mode ) && launcher->configPages().count() > 0 ) { modes.insert( mode ); LaunchModeItem* lmi = new LaunchModeItem; lmi->mode = Core::self()->runController()->launchModeForId( mode ); lmi->parent = t; lmi->row = t->children.count(); items.append( lmi ); } } } if( !items.isEmpty() ) { QModelIndex p = indexForConfig( t->launch ); beginInsertRows( p, t->children.count(), t->children.count() + items.count() - 1 ); t->children.append( items ); endInsertRows(); } } LaunchConfigurationsModel::ProjectItem* LaunchConfigurationsModel::findItemForProject( IProject* p ) { foreach( TreeItem* t, topItems ) { ProjectItem* pi = dynamic_cast( t ); if( pi && pi->project == p ) { return pi; } } Q_ASSERT(false); - return 0; + return nullptr; } int LaunchConfigurationsModel::columnCount(const QModelIndex& parent) const { Q_UNUSED( parent ); return 2; } QVariant LaunchConfigurationsModel::data(const QModelIndex& index, int role) const { if( index.isValid() && index.column() >= 0 && index.column() < 2 ) { TreeItem* t = static_cast( index.internalPointer() ); switch( role ) { case Qt::DisplayRole: { LaunchItem* li = dynamic_cast( t ); if( li ) { if( index.column() == 0 ) { return li->launch->name(); } else if( index.column() == 1 ) { return li->launch->type()->name(); } } ProjectItem* pi = dynamic_cast( t ); if( pi && index.column() == 0 ) { return pi->project->name(); } GenericPageItem* gpi = dynamic_cast( t ); if( gpi && index.column() == 0 ) { return gpi->text; } LaunchModeItem* lmi = dynamic_cast( t ); if( lmi ) { if( index.column() == 0 ) { return lmi->mode->name(); } else if( index.column() == 1 ) { LaunchConfiguration* l = configForIndex( index ); return l->type()->launcherForId( l->launcherForMode( lmi->mode->id() ) )->name(); } } break; } case Qt::DecorationRole: { LaunchItem* li = dynamic_cast( t ); if( index.column() == 0 && li ) { return li->launch->type()->icon(); } LaunchModeItem* lmi = dynamic_cast( t ); if( lmi && index.column() == 0 ) { return lmi->mode->icon(); } if ( index.column() == 0 && !index.parent().isValid() ) { if (index.row() == 0) { // global item return QIcon::fromTheme(QStringLiteral("folder")); } else { // project item return QIcon::fromTheme(QStringLiteral("folder-development")); } } } case Qt::EditRole: { LaunchItem* li = dynamic_cast( t ); if( li ) { if( index.column() == 0 ) { return li->launch->name(); } else if ( index.column() == 1 ) { return li->launch->type()->id(); } } LaunchModeItem* lmi = dynamic_cast( t ); if( lmi && index.column() == 1 ) { return configForIndex( index )->launcherForMode( lmi->mode->id() ); } break; } default: break; } } return QVariant(); } QModelIndex LaunchConfigurationsModel::index(int row, int column, const QModelIndex& parent) const { if( !hasIndex( row, column, parent ) ) return QModelIndex(); TreeItem* tree; if( !parent.isValid() ) { tree = topItems.at( row ); } else { TreeItem* t = static_cast( parent.internalPointer() ); tree = t->children.at( row ); } if( tree ) { return createIndex( row, column, tree ); } return QModelIndex(); } QModelIndex LaunchConfigurationsModel::parent(const QModelIndex& child) const { if( child.isValid() ) { TreeItem* t = static_cast( child.internalPointer() ); if( t->parent ) { return createIndex( t->parent->row, 0, t->parent ); } } return QModelIndex(); } int LaunchConfigurationsModel::rowCount(const QModelIndex& parent) const { if( parent.column() > 0 ) return 0; if( parent.isValid() ) { TreeItem* t = static_cast( parent.internalPointer() ); return t->children.count(); } else { return topItems.count(); } return 0; } QVariant LaunchConfigurationsModel::headerData(int section, Qt::Orientation orientation, int role) const { if( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { if( section == 0 ) { return i18nc("Name of the Launch Configurations", "Name"); } else if( section == 1 ) { return i18nc("The type of the Launch Configurations (i.e. Python Application, C++ Application)", "Type"); } } return QVariant(); } Qt::ItemFlags LaunchConfigurationsModel::flags(const QModelIndex& index) const { if( index.isValid() && index.column() >= 0 && index.column() < columnCount( QModelIndex() ) ) { TreeItem* t = static_cast( index.internalPointer() ); if( t && ( dynamic_cast( t ) || ( dynamic_cast( t ) && index.column() == 1 ) ) ) { return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable ); } else if( t ) { return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); } } return Qt::NoItemFlags; } bool LaunchConfigurationsModel::setData(const QModelIndex& index, const QVariant& value, int role) { if( index.isValid() && index.parent().isValid() && role == Qt::EditRole ) { if( index.row() >= 0 && index.row() < rowCount( index.parent() ) ) { LaunchItem* t = dynamic_cast( static_cast( index.internalPointer() ) ); if( t ) { if( index.column() == 0 ) { t->launch->setName( value.toString() ); } else if( index.column() == 1 ) { if (t->launch->type()->id() != value.toString()) { t->launch->setType( value.toString() ); QModelIndex p = indexForConfig(t->launch); qCDebug(SHELL) << data(p); beginRemoveRows( p, 0, t->children.count() ); qDeleteAll( t->children ); t->children.clear(); endRemoveRows(); addLaunchModeItemsForLaunchConfig( t ); } } emit dataChanged(index, index); return true; } LaunchModeItem* lmi = dynamic_cast( static_cast( index.internalPointer() ) ); if( lmi ) { if( index.column() == 1 && index.data(Qt::EditRole)!=value) { LaunchConfiguration* l = configForIndex( index ); l->setLauncherForMode( lmi->mode->id(), value.toString() ); emit dataChanged(index, index); return true; } } } } return false; } ILaunchMode* LaunchConfigurationsModel::modeForIndex( const QModelIndex& idx ) const { if( idx.isValid() ) { LaunchModeItem* item = dynamic_cast( static_cast( idx.internalPointer() ) ); if( item ) { return item->mode; } } - return 0; + return nullptr; } LaunchConfiguration* LaunchConfigurationsModel::configForIndex(const QModelIndex& idx ) const { if( idx.isValid() ) { LaunchItem* item = dynamic_cast( static_cast( idx.internalPointer() ) ); if( item ) { return item->launch; } LaunchModeItem* lmitem = dynamic_cast( static_cast( idx.internalPointer() ) ); if( lmitem ) { return dynamic_cast( lmitem->parent )->launch; } } - return 0; + return nullptr; } QModelIndex LaunchConfigurationsModel::indexForConfig( LaunchConfiguration* l ) const { if( l ) { TreeItem* tparent = topItems.at( 0 ); if( l->project() ) { foreach( TreeItem* t, topItems ) { ProjectItem* pi = dynamic_cast( t ); if( pi && pi->project == l->project() ) { tparent = t; break; } } } if( tparent ) { foreach( TreeItem* c, tparent->children ) { LaunchItem* li = dynamic_cast( c ); if( li->launch && li->launch == l ) { return index( c->row, 0, index( tparent->row, 0, QModelIndex() ) ); } } } } return QModelIndex(); } void LaunchConfigurationsModel::deleteConfiguration( const QModelIndex& index ) { LaunchItem* t = dynamic_cast( static_cast( index.internalPointer() ) ); if( !t ) return; beginRemoveRows( parent( index ), index.row(), index.row() ); t->parent->children.removeAll( t ); Core::self()->runControllerInternal()->removeLaunchConfiguration( t->launch ); endRemoveRows(); } void LaunchConfigurationsModel::createConfiguration(const QModelIndex& parent ) { if(!Core::self()->runController()->launchConfigurationTypes().isEmpty()) { TreeItem* t = static_cast( parent.internalPointer() ); ProjectItem* ti = dynamic_cast( t ); LaunchConfigurationType* type = Core::self()->runController()->launchConfigurationTypes().at(0); QPair launcher = qMakePair( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() ); - IProject* p = ( ti ? ti->project : 0 ); + IProject* p = ( ti ? ti->project : nullptr ); ILaunchConfiguration* l = Core::self()->runController()->createLaunchConfiguration( type, launcher, p ); addConfiguration(l, parent); } } void LaunchConfigurationsModel::addConfiguration(ILaunchConfiguration* l, const QModelIndex& parent) { if( parent.isValid() ) { beginInsertRows( parent, rowCount( parent ), rowCount( parent ) ); addItemForLaunchConfig( dynamic_cast( l ) ); endInsertRows(); } else { delete l; Q_ASSERT(false && "could not add the configuration"); } } IProject* LaunchConfigurationsModel::projectForIndex(const QModelIndex& idx) { if(idx.parent().isValid()) { return projectForIndex(idx.parent()); } else { const ProjectItem* item = dynamic_cast(topItems[idx.row()]); - return item ? item->project : 0; + return item ? item->project : nullptr; } } LaunchConfigPagesContainer::LaunchConfigPagesContainer( const QList& factories, QWidget* parent ) : QWidget(parent) { setLayout( new QVBoxLayout( this ) ); layout()->setContentsMargins( 0, 0, 0, 0 ); QWidget* parentwidget = this; - QTabWidget* tab = 0; + QTabWidget* tab = nullptr; if( factories.count() > 1 ) { tab = new QTabWidget( this ); parentwidget = tab; layout()->addWidget( tab ); } foreach( LaunchConfigurationPageFactory* fac, factories ) { LaunchConfigurationPage* page = fac->createWidget( parentwidget ); if ( page->layout() ) { page->layout()->setContentsMargins( 0, 0, 0, 0 ); } pages.append( page ); connect( page, &LaunchConfigurationPage::changed, this, &LaunchConfigPagesContainer::changed ); if( tab ) { tab->addTab( page, page->icon(), page->title() ); } else { layout()->addWidget( page ); } } } void LaunchConfigPagesContainer::setLaunchConfiguration( KDevelop::LaunchConfiguration* l ) { config = l; foreach( LaunchConfigurationPage* p, pages ) { p->loadFromConfiguration( config->config(), config->project() ); } } void LaunchConfigPagesContainer::save() { foreach( LaunchConfigurationPage* p, pages ) { p->saveToConfiguration( config->config() ); } config->config().sync(); } QWidget* LaunchConfigurationModelDelegate::createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const { const LaunchConfigurationsModel* model = dynamic_cast( index.model() ); ILaunchMode* mode = model->modeForIndex( index ); LaunchConfiguration* config = model->configForIndex( index ); if( index.column() == 1 && mode && config ) { KComboBox* box = new KComboBox( parent ); QList launchers = config->type()->launchers(); for( QList::const_iterator it = launchers.constBegin(); it != launchers.constEnd(); it++ ) { if( ((*it)->supportedModes().contains( mode->id() ) ) ) { box->addItem( (*it)->name(), (*it)->id() ); } } return box; } else if( !mode && config && index.column() == 1 ) { KComboBox* box = new KComboBox( parent ); const QList types = Core::self()->runController()->launchConfigurationTypes(); for( QList::const_iterator it = types.begin(); it != types.end(); it++ ) { box->addItem( (*it)->name(), (*it)->id() ); } return box; } return QStyledItemDelegate::createEditor ( parent, option, index ); } void LaunchConfigurationModelDelegate::setEditorData ( QWidget* editor, const QModelIndex& index ) const { const LaunchConfigurationsModel* model = dynamic_cast( index.model() ); LaunchConfiguration* config = model->configForIndex( index ); if( index.column() == 1 && config ) { KComboBox* box = qobject_cast( editor ); box->setCurrentIndex( box->findData( index.data( Qt::EditRole ) ) ); } else { QStyledItemDelegate::setEditorData ( editor, index ); } } void LaunchConfigurationModelDelegate::setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const { LaunchConfigurationsModel* lmodel = dynamic_cast( model ); LaunchConfiguration* config = lmodel->configForIndex( index ); if( index.column() == 1 && config ) { KComboBox* box = qobject_cast( editor ); lmodel->setData( index, box->itemData( box->currentIndex() ) ); } else { QStyledItemDelegate::setModelData ( editor, model, index ); } } void LaunchConfigurationDialog::launchModeChanged(int item) { QModelIndex index = tree->currentIndex(); if(debugger->isVisible() && item>=0) tree->model()->setData(index.sibling(index.row(), 1), debugger->itemData(item), Qt::EditRole); } } diff --git a/shell/loadedpluginsdialog.cpp b/shell/loadedpluginsdialog.cpp index b8f3a3bb51..574c9f57e0 100644 --- a/shell/loadedpluginsdialog.cpp +++ b/shell/loadedpluginsdialog.cpp @@ -1,310 +1,310 @@ /************************************************************************** * Copyright 2009 Andreas Pakulat * * Copyright 2010 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "loadedpluginsdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #define MARGIN 5 namespace { KPluginMetaData pluginInfo(KDevelop::IPlugin* plugin) { return KDevelop::Core::self()->pluginControllerInternal()->pluginInfo(plugin); }; QString displayName(KDevelop::IPlugin* plugin) { const auto name = pluginInfo(plugin).name(); return !name.isEmpty() ? name : plugin->componentName(); } bool sortPlugins(KDevelop::IPlugin* l, KDevelop::IPlugin* r) { return displayName(l) < displayName(r); } } class PluginsModel : public QAbstractListModel { Q_OBJECT public: enum ExtraRoles { DescriptionRole = Qt::UserRole+1 }; - PluginsModel(QObject* parent = 0) + PluginsModel(QObject* parent = nullptr) : QAbstractListModel(parent) { m_plugins = KDevelop::Core::self()->pluginControllerInternal()->loadedPlugins(); std::sort(m_plugins.begin(), m_plugins.end(), sortPlugins); } KDevelop::IPlugin *pluginForIndex(const QModelIndex& index) const { - if (!index.isValid()) return 0; - if (index.parent().isValid()) return 0; - if (index.column() != 0) return 0; - if (index.row() >= m_plugins.count()) return 0; + if (!index.isValid()) return nullptr; + if (index.parent().isValid()) return nullptr; + if (index.column() != 0) return nullptr; + if (index.row() >= m_plugins.count()) return nullptr; return m_plugins[index.row()]; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { KDevelop::IPlugin* plugin = pluginForIndex(index); if (!plugin) return QVariant(); switch (role) { case Qt::DisplayRole: return displayName(plugin); case DescriptionRole: return pluginInfo(plugin).description(); case Qt::DecorationRole: return pluginInfo(plugin).iconName(); default: return QVariant(); }; } int rowCount(const QModelIndex& parent = QModelIndex()) const override { if (!parent.isValid()) { return m_plugins.count(); } return 0; } private: QList m_plugins; }; class LoadedPluginsDelegate : public KWidgetItemDelegate { Q_OBJECT public: - LoadedPluginsDelegate(QAbstractItemView *itemView, QObject *parent = 0) + LoadedPluginsDelegate(QAbstractItemView *itemView, QObject *parent = nullptr) : KWidgetItemDelegate(itemView, parent) , pushButton(new QPushButton) { pushButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); // only for getting size matters } ~LoadedPluginsDelegate() override { delete pushButton; } QList createItemWidgets(const QModelIndex &/*index*/) const override { return QList(); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { int i = 5; int j = 1; QFont font = titleFont(option.font); QFontMetrics fmTitle(font); return QSize(qMax(fmTitle.width(index.model()->data(index, Qt::DisplayRole).toString()), option.fontMetrics.width(index.model()->data(index, PluginsModel::DescriptionRole).toString())) + KIconLoader::SizeMedium + MARGIN * i + pushButton->sizeHint().width() * j, qMax(KIconLoader::SizeMedium + MARGIN * 2, fmTitle.height() + option.fontMetrics.height() + MARGIN * 2)); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid()) { return; } painter->save(); - QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0); + QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); int iconSize = option.rect.height() - MARGIN * 2; QIcon icon = QIcon::fromTheme(index.model()->data(index, Qt::DecorationRole).toString()); icon.paint(painter, QRect(dependantLayoutValue(MARGIN + option.rect.left(), iconSize, option.rect.width()), MARGIN + option.rect.top(), iconSize, iconSize)); QRect contentsRect(dependantLayoutValue(MARGIN * 2 + iconSize + option.rect.left(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.width()), MARGIN + option.rect.top(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.height() - MARGIN * 2); int lessHorizontalSpace = MARGIN * 2 + pushButton->sizeHint().width(); contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.highlightedText().color()); } if (itemView()->layoutDirection() == Qt::RightToLeft) { contentsRect.translate(lessHorizontalSpace, 0); } painter->save(); painter->save(); QFont font = titleFont(option.font); QFontMetrics fmTitle(font); painter->setFont(font); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignTop, fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignBottom, option.fontMetrics.elidedText(index.model()->data(index, PluginsModel::DescriptionRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->restore(); } QList createItemWidgets() const { QPushButton *button = new QPushButton(); button->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); setBlockedEventTypes(button, QList() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick); connect(button, &QPushButton::clicked, this, &LoadedPluginsDelegate::info); return QList() << button; } void updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override { Q_UNUSED(index); if (widgets.isEmpty()) { return; } QPushButton *aboutPushButton = static_cast(widgets[0]); QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint(); aboutPushButton->resize(aboutPushButtonSizeHint); aboutPushButton->move(dependantLayoutValue(option.rect.width() - MARGIN - aboutPushButtonSizeHint.width(), aboutPushButtonSizeHint.width(), option.rect.width()), option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2); } int dependantLayoutValue(int value, int width, int totalWidth) const { if (itemView()->layoutDirection() == Qt::LeftToRight) { return value; } return totalWidth - width - value; } QFont titleFont(const QFont &baseFont) const { QFont retFont(baseFont); retFont.setBold(true); return retFont; } private Q_SLOTS: void info() { PluginsModel *m = static_cast(itemView()->model()); KDevelop::IPlugin *p = m->pluginForIndex(focusedIndex()); if (p) { // TODO KF5: Port // const K4AboutData *aboutData = p->componentData().aboutData(); // if (!aboutData->programName().isEmpty()) { // Be sure the about data is not completely empty // KAboutApplicationDialog aboutPlugin(aboutData, itemView()); // aboutPlugin.exec(); // return; // } } } private: QPushButton *pushButton; }; class PluginsView : public QListView { Q_OBJECT public: - PluginsView(QWidget* parent = 0) + PluginsView(QWidget* parent = nullptr) :QListView(parent) { setModel(new PluginsModel()); setItemDelegate(new LoadedPluginsDelegate(this)); setVerticalScrollMode(QListView::ScrollPerPixel); } ~PluginsView() override { // explicitly delete the delegate here since otherwise // we get spammed by warnings that the QPushButton we return // in createItemWidgets is deleted before the delegate // *sigh* - even dfaure says KWidgetItemDelegate is a crude hack delete itemDelegate(); } QSize sizeHint() const override { QSize ret = QListView::sizeHint(); ret.setWidth(qMax(ret.width(), sizeHintForColumn(0) + 30)); return ret; } }; LoadedPluginsDialog::LoadedPluginsDialog( QWidget* parent ) : QDialog( parent ) { setWindowTitle(i18n("Loaded Plugins")); QVBoxLayout* vbox = new QVBoxLayout(this); KTitleWidget* title = new KTitleWidget(this); title->setPixmap(QIcon::fromTheme(KAboutData::applicationData().programIconName()), KTitleWidget::ImageLeft); title->setText(i18n("Plugins loaded for %1", KAboutData::applicationData().displayName())); vbox->addWidget(title); vbox->addWidget(new PluginsView()); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &LoadedPluginsDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LoadedPluginsDialog::reject); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); vbox->addWidget(buttonBox); } #include "moc_loadedpluginsdialog.cpp" #include "loadedpluginsdialog.moc" diff --git a/shell/mainwindow.cpp b/shell/mainwindow.cpp index cae664db88..4abc2a3323 100644 --- a/shell/mainwindow.cpp +++ b/shell/mainwindow.cpp @@ -1,473 +1,473 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow.h" #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shellextension.h" #include "partcontroller.h" #include "plugincontroller.h" #include "projectcontroller.h" #include "uicontroller.h" #include "documentcontroller.h" #include "debugcontroller.h" #include "workingsetcontroller.h" #include "sessioncontroller.h" #include "sourceformattercontroller.h" #include "areadisplay.h" #include "project.h" #include "debug.h" #include "uiconfig.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QColor defaultColor(const QPalette& palette) { return palette.foreground().color(); } QColor colorForDocument(const QUrl& url, const QPalette& palette, const QColor& defaultColor) { auto project = Core::self()->projectController()->findProjectForUrl(url); if (!project) return defaultColor; return WidgetColorizer::colorForId(qHash(project->path()), palette); } } void MainWindow::applyMainWindowSettings(const KConfigGroup& config) { if(!d->changingActiveView()) KXmlGuiWindow::applyMainWindowSettings(config); } void MainWindow::createGUI(KParts::Part* part) { //TODO remove if-clause once KF5 >= 5.24 is required #if KPARTS_VERSION_MINOR >= 24 Sublime::MainWindow::setWindowTitleHandling(false); Sublime::MainWindow::createGUI(part); #else Sublime::MainWindow::createGUI(part); if (part) { // Don't let the Part control the main window caption -- we take care of that disconnect(part, SIGNAL(setWindowCaption(QString)), this, SLOT(setCaption(QString))); } #endif } void MainWindow::initializeCorners() { const KConfigGroup cg = KSharedConfig::openConfig()->group( "UiSettings" ); const int bottomleft = cg.readEntry( "BottomLeftCornerOwner", 0 ); const int bottomright = cg.readEntry( "BottomRightCornerOwner", 0 ); qCDebug(SHELL) << "Bottom Left:" << bottomleft; qCDebug(SHELL) << "Bottom Right:" << bottomright; // 0 means vertical dock (left, right), 1 means horizontal dock( top, bottom ) if( bottomleft == 0 ) setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea ); else if( bottomleft == 1 ) setCorner( Qt::BottomLeftCorner, Qt::BottomDockWidgetArea ); if( bottomright == 0 ) setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea ); else if( bottomright == 1 ) setCorner( Qt::BottomRightCorner, Qt::BottomDockWidgetArea ); } MainWindow::MainWindow( Sublime::Controller *parent, Qt::WindowFlags flags ) : Sublime::MainWindow( parent, flags ) { QDBusConnection::sessionBus().registerObject( QStringLiteral("/kdevelop/MainWindow"), this, QDBusConnection::ExportScriptableSlots ); setAcceptDrops( true ); initializeCorners(); setObjectName( QStringLiteral("MainWindow") ); d = new MainWindowPrivate(this); setStandardToolBarMenuEnabled( true ); d->setupActions(); if( !ShellExtension::getInstance()->xmlFile().isEmpty() ) { setXMLFile( ShellExtension::getInstance() ->xmlFile() ); } menuBar()->setCornerWidget(new AreaDisplay(this), Qt::TopRightCorner); } MainWindow::~ MainWindow() { if (memberList().count() == 1) { // We're closing down... Core::self()->shutdown(); } delete d; } KTextEditorIntegration::MainWindow *MainWindow::kateWrapper() const { return d->kateWrapper(); } void MainWindow::ensureVisible() { if (isMinimized()) { if (isMaximized()) { showMaximized(); } else { showNormal(); } } KWindowSystem::forceActiveWindow(winId()); } QAction* MainWindow::createCustomElement(QWidget* parent, int index, const QDomElement& element) { - QAction* before = 0L; + QAction* before = nullptr; if (index > 0 && index < parent->actions().count()) before = parent->actions().at(index); //KDevelop needs to ensure that separators defined as //are always shown in the menubar. For those, we create special disabled actions //instead of calling QMenuBar::addSeparator() because menubar separators are ignored if (element.tagName().toLower() == QLatin1String("separator") && element.attribute(QStringLiteral("style")) == QLatin1String("visible")) { if ( QMenuBar* bar = qobject_cast( parent ) ) { QAction *separatorAction = new QAction(QStringLiteral("|"), this); bar->insertAction( before, separatorAction ); separatorAction->setDisabled(true); return separatorAction; } } return KXMLGUIBuilder::createCustomElement(parent, index, element); } void MainWindow::dragEnterEvent( QDragEnterEvent* ev ) { if( ev->mimeData()->hasFormat( QStringLiteral("text/uri-list") ) && ev->mimeData()->hasUrls() ) { ev->acceptProposedAction(); } } void MainWindow::dropEvent( QDropEvent* ev ) { Sublime::View* dropToView = viewForPosition(mapToGlobal(ev->pos())); if(dropToView) activateView(dropToView); foreach( const QUrl& u, ev->mimeData()->urls() ) { Core::self()->documentController()->openDocument( u ); } ev->acceptProposedAction(); } void MainWindow::loadSettings() { qCDebug(SHELL) << "Loading Settings"; initializeCorners(); updateAllTabColors(); Sublime::MainWindow::loadSettings(); } void MainWindow::configureShortcuts() { ///Workaround for a problem with the actions: Always start the shortcut-configuration in the first mainwindow, then propagate the updated ///settings into the other windows // We need to bring up the shortcut dialog ourself instead of // Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->configureShortcuts(); // so we can connect to the saved() signal to propagate changes in the editor shortcuts KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); foreach (KXMLGUIClient *client, Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients()) { if(client && !client->xmlFile().isEmpty()) dlg.addCollection( client->actionCollection() ); } connect(&dlg, &KShortcutsDialog::saved, this, &MainWindow::shortcutsChanged); dlg.configure(true); QMap shortcuts; foreach(KXMLGUIClient* client, Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients()) { foreach(QAction* action, client->actionCollection()->actions()) { if(!action->objectName().isEmpty()) { shortcuts[action->objectName()] = action->shortcut(); } } } for(int a = 1; a < Core::self()->uiControllerInternal()->mainWindows().size(); ++a) { foreach(KXMLGUIClient* client, Core::self()->uiControllerInternal()->mainWindows()[a]->guiFactory()->clients()) { foreach(QAction* action, client->actionCollection()->actions()) { qCDebug(SHELL) << "transferring setting shortcut for" << action->objectName(); if(shortcuts.contains(action->objectName())) { action->setShortcut(shortcuts[action->objectName()]); } } } } } void MainWindow::shortcutsChanged() { KTextEditor::View *activeClient = Core::self()->documentController()->activeTextDocumentView(); if(!activeClient) return; foreach(IDocument * doc, Core::self()->documentController()->openDocuments()) { KTextEditor::Document *textDocument = doc->textDocument(); if (textDocument) { foreach(KTextEditor::View *client, textDocument->views()) { if (client != activeClient) { client->reloadXML(); } } } } } void MainWindow::initialize() { KStandardAction::keyBindings(this, SLOT(configureShortcuts()), actionCollection()); setupGUI( KXmlGuiWindow::ToolBar | KXmlGuiWindow::Create | KXmlGuiWindow::Save ); Core::self()->partController()->addManagedTopLevelWidget(this); qCDebug(SHELL) << "Adding plugin-added connection"; connect( Core::self()->pluginController(), &IPluginController::pluginLoaded, d, &MainWindowPrivate::addPlugin); connect( Core::self()->pluginController(), &IPluginController::pluginUnloaded, d, &MainWindowPrivate::removePlugin); connect( Core::self()->partController(), &IPartController::activePartChanged, d, &MainWindowPrivate::activePartChanged); connect( this, &MainWindow::activeViewChanged, d, &MainWindowPrivate::changeActiveView); foreach(IPlugin* plugin, Core::self()->pluginController()->loadedPlugins()) d->addPlugin(plugin); guiFactory()->addClient(Core::self()->sessionController()); guiFactory()->addClient(Core::self()->sourceFormatterControllerInternal()); // Needed to re-plug the actions from the sessioncontroller as xmlguiclients don't // seem to remember which actions where plugged in. Core::self()->sessionController()->updateXmlGuiActionList(); d->setupGui(); //Queued so we process it with some delay, to make sure the rest of the UI has already adapted connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &MainWindow::updateActiveDocumentConnection, Qt::QueuedConnection); connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->sessionController()->activeSession(), &ISession::sessionUpdated, this, &MainWindow::updateCaption); connect(Core::self()->documentController(), &IDocumentController::documentOpened, this, &MainWindow::updateTabColor, Qt::QueuedConnection); connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateTabColor, Qt::QueuedConnection); connect(this, &Sublime::MainWindow::viewAdded, this, &MainWindow::updateAllTabColors); connect(Core::self()->projectController(), &ProjectController::projectOpened, this, &MainWindow::updateAllTabColors, Qt::QueuedConnection); updateCaption(); } void MainWindow::cleanup() { } void MainWindow::setVisible( bool visible ) { KXmlGuiWindow::setVisible( visible ); emit finishedLoading(); } bool MainWindow::queryClose() { if (!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(this, IDocument::Default)) return false; return Sublime::MainWindow::queryClose(); } void MainWindow::updateActiveDocumentConnection(IDocument* document) { disconnect(d->activeDocumentReadWriteConnection); if (auto textDocument = document->textDocument()) { d->activeDocumentReadWriteConnection = connect(textDocument, &KTextEditor::Document::readWriteChanged, this, &MainWindow::updateCaption); } } void MainWindow::updateCaption() { const auto activeSession = Core::self()->sessionController()->activeSession(); QString title = activeSession ? activeSession->description() : QString(); if(area()->activeView()) { if(!title.isEmpty()) title += QLatin1String(" - [ "); Sublime::Document* doc = area()->activeView()->document(); Sublime::UrlDocument* urlDoc = dynamic_cast(doc); if(urlDoc) title += Core::self()->projectController()->prettyFileName(urlDoc->url(), KDevelop::IProjectController::FormatPlain); else title += doc->title(); auto activeDocument = Core::self()->documentController()->activeDocument(); if (activeDocument && activeDocument->textDocument() && !activeDocument->textDocument()->isReadWrite()) title += i18n(" (read only)"); title += QLatin1String(" ]"); } setCaption(title); } void MainWindow::updateAllTabColors() { auto documentController = Core::self()->documentController(); if (!documentController) return; const auto defaultColor = ::defaultColor(palette()); if (UiConfig::colorizeByProject()) { QHash viewColors; foreach (auto container, containers()) { auto views = container->views(); viewColors.reserve(views.size()); viewColors.clear(); foreach (auto view, views) { const auto urlDoc = qobject_cast(view->document()); if (urlDoc) { viewColors[view] = colorForDocument(urlDoc->url(), palette(), defaultColor); } } container->setTabColors(viewColors); } } else { foreach (auto container, containers()) { container->resetTabColors(defaultColor); } } } void MainWindow::updateTabColor(IDocument* doc) { if (!UiConfig::self()->colorizeByProject()) return; const auto color = colorForDocument(doc->url(), palette(), defaultColor(palette())); foreach (auto container, containers()) { foreach (auto view, container->views()) { const auto urlDoc = qobject_cast(view->document()); if (urlDoc && urlDoc->url() == doc->url()) { container->setTabColor(view, color); } } } } void MainWindow::registerStatus(QObject* status) { d->registerStatus(status); } void MainWindow::initializeStatusBar() { d->setupStatusBar(); } void MainWindow::showErrorMessage(const QString& message, int timeout) { d->showErrorMessage(message, timeout); } void MainWindow::tabContextMenuRequested(Sublime::View* view, QMenu* menu) { Sublime::MainWindow::tabContextMenuRequested(view, menu); d->tabContextMenuRequested(view, menu); } void MainWindow::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) { d->tabToolTipRequested(view, container, tab); } void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) { d->dockBarContextMenuRequested(area, position); } void MainWindow::newTabRequested() { Sublime::MainWindow::newTabRequested(); d->fileNew(); } diff --git a/shell/mainwindow_p.cpp b/shell/mainwindow_p.cpp index e995d00efc..8c85833ed8 100644 --- a/shell/mainwindow_p.cpp +++ b/shell/mainwindow_p.cpp @@ -1,457 +1,457 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "partdocument.h" #include "partcontroller.h" #include "uicontroller.h" #include "statusbar.h" #include "mainwindow.h" #include "textdocument.h" #include "sessioncontroller.h" #include "debug.h" #include "ktexteditorpluginintegration.h" #include "colorschemechooser.h" #include #include #include #include #include namespace KDevelop { MainWindowPrivate::MainWindowPrivate(MainWindow *mainWindow) : QObject(mainWindow) , m_mainWindow(mainWindow) - , m_statusBar(0) - , lastXMLGUIClientView(0) + , m_statusBar(nullptr) + , lastXMLGUIClientView(nullptr) , m_changingActiveView(false) , m_kateWrapper(new KTextEditorIntegration::MainWindow(mainWindow)) { } void MainWindowPrivate::setupGui() { m_statusBar = new KDevelop::StatusBar(m_mainWindow); setupStatusBar(); } void MainWindowPrivate::setupStatusBar() { QWidget *location = m_mainWindow->statusBarLocation(); if (m_statusBar) location->layout()->addWidget(m_statusBar); } void MainWindowPrivate::addPlugin( IPlugin *plugin ) { qCDebug(SHELL) << "add plugin" << plugin << plugin->componentName(); Q_ASSERT( plugin ); //The direct plugin client can only be added to the first mainwindow if(m_mainWindow == Core::self()->uiControllerInternal()->mainWindows()[0]) m_mainWindow->guiFactory()->addClient( plugin ); Q_ASSERT(!m_pluginCustomClients.contains(plugin)); KXMLGUIClient* ownClient = plugin->createGUIForMainWindow(m_mainWindow); if(ownClient) { m_pluginCustomClients[plugin] = ownClient; connect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); m_mainWindow->guiFactory()->addClient(ownClient); } } void MainWindowPrivate::pluginDestroyed(QObject* pluginObj) { IPlugin* plugin = static_cast(pluginObj); KXMLGUIClient* p = m_pluginCustomClients.take(plugin); m_mainWindow->guiFactory()->removeClient( p ); delete p; } MainWindowPrivate::~MainWindowPrivate() { qDeleteAll(m_pluginCustomClients); } void MainWindowPrivate::removePlugin( IPlugin *plugin ) { Q_ASSERT( plugin ); pluginDestroyed(plugin); disconnect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); m_mainWindow->guiFactory()->removeClient( plugin ); } void MainWindowPrivate::activePartChanged(KParts::Part *part) { if ( Core::self()->uiController()->activeMainWindow() == m_mainWindow) m_mainWindow->createGUI(part); } void MainWindowPrivate::changeActiveView(Sublime::View *view) { //disable updates on a window to avoid toolbar flickering on xmlgui client change Sublime::HoldUpdates s(m_mainWindow); mergeView(view); if(!view) return; IDocument *doc = dynamic_cast(view->document()); if (doc) { doc->activate(view, m_mainWindow); } else { //activated view is not a part document so we need to remove active part gui ///@todo adymo: only this window needs to remove GUI // KParts::Part *activePart = Core::self()->partController()->activePart(); // if (activePart) // guiFactory()->removeClient(activePart); } } void MainWindowPrivate::mergeView(Sublime::View* view) { PushPositiveValue block(m_changingActiveView, true); // If the previous view was KXMLGUIClient, remove its actions // In the case that that view was removed, lastActiveView // will auto-reset, and xmlguifactory will disconnect that // client, I think. if (lastXMLGUIClientView) { qCDebug(SHELL) << "clearing last XML GUI client" << lastXMLGUIClientView; m_mainWindow->guiFactory()->removeClient(dynamic_cast(lastXMLGUIClientView)); - disconnect (lastXMLGUIClientView, &QWidget::destroyed, this, 0); + disconnect (lastXMLGUIClientView, &QWidget::destroyed, this, nullptr); - lastXMLGUIClientView = NULL; + lastXMLGUIClientView = nullptr; } if (!view) return; QWidget* viewWidget = view->widget(); Q_ASSERT(viewWidget); qCDebug(SHELL) << "changing active view to" << view << "doc" << view->document() << "mw" << m_mainWindow; // If the new view is KXMLGUIClient, add it. if (KXMLGUIClient* c = dynamic_cast(viewWidget)) { qCDebug(SHELL) << "setting new XMLGUI client" << viewWidget; lastXMLGUIClientView = viewWidget; m_mainWindow->guiFactory()->addClient(c); connect(viewWidget, &QWidget::destroyed, this, &MainWindowPrivate::xmlguiclientDestroyed); } } void MainWindowPrivate::xmlguiclientDestroyed(QObject* obj) { /* We're informed the QWidget for the active view that is also KXMLGUIclient is dying. KXMLGUIFactory will not like deleted clients, really. Unfortunately, there's nothing we can do at this point. For example, KateView derives from QWidget and KXMLGUIClient. The destroyed() signal is emitted by ~QWidget. At this point, event attempt to cross-cast to KXMLGUIClient is undefined behaviour. We hope to catch view deletion a bit later, but if we fail, we better report it now, rather than get a weird crash a bit later. */ Q_ASSERT(obj == lastXMLGUIClientView); Q_ASSERT(false && "xmlgui clients management is messed up"); Q_UNUSED(obj); } void MainWindowPrivate::setupActions() { connect(Core::self()->sessionController(), &SessionController::quitSession, this, &MainWindowPrivate::quitAll); QAction* action; const QString app = qApp->applicationName(); action = KStandardAction::preferences( this, SLOT(settingsDialog()), actionCollection()); action->setToolTip( i18nc( "%1 = application name", "Configure %1", app ) ); action->setWhatsThis( i18n( "Lets you customize %1.", app ) ); action = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); action->setText( i18n("Configure Notifications...") ); action->setToolTip( i18nc("@info:tooltip", "Configure notifications") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog that lets you configure notifications." ) ); action = actionCollection()->addAction( QStringLiteral("about_platform"), this, SLOT(showAboutPlatform()) ); action->setText( i18n("About KDevelop Platform") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'About KDevelop' action->setStatusTip( i18n("Show Information about KDevelop Platform") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about KDevelop Platform." ) ); action = actionCollection()->addAction( QStringLiteral("loaded_plugins"), this, SLOT(showLoadedPlugins()) ); action->setText( i18n("Loaded Plugins") ); action->setStatusTip( i18n("Show a list of all loaded plugins") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about all loaded plugins." ) ); action = actionCollection()->addAction( QStringLiteral("view_next_window") ); action->setText( i18n( "&Next Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Right ); action->setToolTip( i18nc( "@info:tooltip", "Next window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next window." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); action = actionCollection()->addAction( QStringLiteral("view_previous_window") ); action->setText( i18n( "&Previous Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Left ); action->setToolTip( i18nc( "@info:tooltip", "Previous window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous window." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); action = actionCollection()->addAction(QStringLiteral("next_error")); action->setText(i18n("Jump to Next Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::Key_F4) ); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextItem); action = actionCollection()->addAction(QStringLiteral("prev_error")); action->setText(i18n("Jump to Previous Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::SHIFT | Qt::Key_F4) ); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPrevItem); action = actionCollection()->addAction( QStringLiteral("split_horizontal") ); action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-top-bottom") )); action->setText( i18n( "Split View &Top/Bottom" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_T ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitHorizontal ); action->setToolTip( i18nc( "@info:tooltip", "Split horizontal" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view horizontally." ) ); action = actionCollection()->addAction( QStringLiteral("split_vertical") ); action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-left-right") )); action->setText( i18n( "Split View &Left/Right" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_L ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitVertical ); action->setToolTip( i18nc( "@info:tooltip", "Split vertical" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view vertically." ) ); action = actionCollection()->addAction( QStringLiteral("view_next_split") ); action->setText( i18n( "&Next Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_N ); action->setToolTip( i18nc( "@info:tooltip", "Next split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next split view." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); action = actionCollection()->addAction( QStringLiteral("view_previous_split") ); action->setText( i18n( "&Previous Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_P ); action->setToolTip( i18nc( "@info:tooltip", "Previous split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous split view." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); action = KStandardAction::fullScreen( this, SLOT(toggleFullScreen(bool)), m_mainWindow, actionCollection() ); action = actionCollection()->addAction( QStringLiteral("file_new") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_N ); action->setText( i18n( "&New" ) ); action->setIconText( i18nc( "Shorter Text for 'New File' shown in the toolbar", "New") ); connect( action, &QAction::triggered, this, &MainWindowPrivate::fileNew ); action->setToolTip( i18nc( "@info:tooltip", "New file" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Creates an empty file." ) ); action = actionCollection()->addAction( QStringLiteral("add_toolview") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_V ); action->setText( i18n( "&Add Tool View..." ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::viewAddNewToolView ); action->setToolTip( i18nc( "@info:tooltip", "Add tool view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Adds a new tool view to this window." ) ); //Load themes actionCollection()->addAction(QStringLiteral("colorscheme_menu"), new ColorSchemeChooser(actionCollection())); } void MainWindowPrivate::toggleArea(bool b) { if (!b) return; QAction* action = qobject_cast(sender()); if (!action) return; m_mainWindow->controller()->showArea(action->data().toString(), m_mainWindow); } KActionCollection * MainWindowPrivate::actionCollection() { return m_mainWindow->actionCollection(); } void MainWindowPrivate::registerStatus(QObject* status) { m_statusBar->registerStatus(status); } void MainWindowPrivate::showErrorMessage(QString message, int timeout) { m_statusBar->showErrorMessage(message, timeout); } void MainWindowPrivate::tabContextMenuRequested(Sublime::View* view, QMenu* menu) { m_tabView = view; QAction* action; action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18n("Split View Top/Bottom")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitHorizontal); action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18n("Split View Left/Right")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitVertical); menu->addSeparator(); action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("New File")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuFileNew); if (view) { if (TextDocument* doc = dynamic_cast(view->document())) { action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload")); connect(action, &QAction::triggered, doc, &TextDocument::reload); action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload All")); connect(action, &QAction::triggered, this, &MainWindowPrivate::reloadAll); } } } void MainWindowPrivate::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) { if (m_tabTooltip.second) { if (m_tabTooltip.first == view) { // tooltip already shown, don't do anything. prevents flicker when moving mouse over same tab return; } else { m_tabTooltip.second.data()->close(); } } Sublime::UrlDocument* urlDoc = dynamic_cast(view->document()); if (urlDoc) { DUChainReadLocker lock; TopDUContext* top = DUChainUtils::standardContextForUrl(urlDoc->url()); if (top) { if ( QWidget* navigationWidget = top->createNavigationWidget() ) { NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(m_mainWindow, QCursor::pos() + QPoint(20, 20), navigationWidget); tooltip->resize(navigationWidget->sizeHint() + QSize(10, 10)); tooltip->setHandleRect(container->tabRect(tab)); m_tabTooltip.first = view; m_tabTooltip.second = tooltip; ActiveToolTip::showToolTip(m_tabTooltip.second.data()); } } } } void MainWindowPrivate::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) { QMenu menu; menu.addSection(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Add Tool View")); QHash factories = Core::self()->uiControllerInternal()->factoryDocuments(); QHash actionToFactory; if ( !factories.isEmpty() ) { // sorted actions QMap actionMap; for (QHash::const_iterator it = factories.constBegin(); it != factories.constEnd(); ++it) { QAction* action = new QAction(it.value()->statusIcon(), it.value()->title(), &menu); action->setIcon(it.value()->statusIcon()); if (!it.key()->allowMultiple() && Core::self()->uiControllerInternal()->toolViewPresent(it.value(), m_mainWindow->area())) { action->setDisabled(true); } actionToFactory.insert(action, it.key()); actionMap[action->text()] = action; } menu.addActions(actionMap.values()); } QAction* triggered = menu.exec(position); if ( !triggered ) { return; } Core::self()->uiControllerInternal()->addToolViewToDockArea( actionToFactory[triggered], area ); } bool MainWindowPrivate::changingActiveView() const { return m_changingActiveView; } KTextEditorIntegration::MainWindow *MainWindowPrivate::kateWrapper() const { return m_kateWrapper; } } #include "mainwindow_actions.cpp" diff --git a/shell/openprojectdialog.cpp b/shell/openprojectdialog.cpp index 29d7aa3794..202febb7ef 100644 --- a/shell/openprojectdialog.cpp +++ b/shell/openprojectdialog.cpp @@ -1,343 +1,343 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "shellextension.h" #include "projectsourcepage.h" #include namespace { struct URLInfo { bool isValid; bool isDir; QString extension; }; URLInfo getUrlInfo(const QUrl& url) { URLInfo ret; ret.isValid = false; if (url.isLocalFile()) { QFileInfo info(url.toLocalFile()); ret.isValid = info.exists(); if (ret.isValid) { ret.isDir = info.isDir(); ret.extension = info.suffix(); } } else if (url.isValid()) { KIO::StatJob* statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, KDevelop::Core::self()->uiControllerInternal()->defaultMainWindow()); ret.isValid = statJob->exec(); // TODO: do this asynchronously so that the user isn't blocked while typing every letter of the hostname in sftp://hostname if (ret.isValid) { KIO::UDSEntry entry = statJob->statResult(); ret.isDir = entry.isDir(); ret.extension = QFileInfo(entry.stringValue(KIO::UDSEntry::UDS_NAME)).suffix(); } } return ret; } } namespace KDevelop { OpenProjectDialog::OpenProjectDialog( bool fetch, const QUrl& startUrl, QWidget* parent ) : KAssistantDialog( parent ) , sourcePage(nullptr) , openPage(nullptr) , projectInfoPage(nullptr) { resize(QSize(700, 500)); const bool useKdeFileDialog = qEnvironmentVariableIsSet("KDE_FULL_SESSION"); QStringList filters, allEntry; QString filterFormat = useKdeFileDialog ? QStringLiteral("%1|%2 (%1)") : QStringLiteral("%2 (%1)"); allEntry << "*." + ShellExtension::getInstance()->projectFileExtension(); filters << filterFormat.arg("*." + ShellExtension::getInstance()->projectFileExtension(), ShellExtension::getInstance()->projectFileDescription()); QVector plugins = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); foreach(const KPluginMetaData& info, plugins) { QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); QString desc = info.value(QStringLiteral("X-KDevelop-ProjectFilesFilterDescription")); m_projectFilters.insert(info.name(), filter); m_projectPlugins.insert(info.name(), info); allEntry += filter; filters << filterFormat.arg(filter.join(QStringLiteral(" ")), desc); } if (useKdeFileDialog) filters.prepend(i18n("%1|All Project Files (%1)", allEntry.join(QStringLiteral(" ")))); QUrl start = startUrl.isValid() ? startUrl : Core::self()->projectController()->projectsBaseDirectory(); start = start.adjusted(QUrl::NormalizePathSegments); - KPageWidgetItem* currentPage = 0; + KPageWidgetItem* currentPage = nullptr; if( fetch ) { sourcePageWidget = new ProjectSourcePage( start, this ); connect( sourcePageWidget, &ProjectSourcePage::isCorrect, this, &OpenProjectDialog::validateSourcePage ); sourcePage = addPage( sourcePageWidget, i18n("Select Source") ); currentPage = sourcePage; } if (useKdeFileDialog) { openPageWidget = new OpenProjectPage( start, filters, this ); connect( openPageWidget, &OpenProjectPage::urlSelected, this, &OpenProjectDialog::validateOpenUrl ); connect( openPageWidget, &OpenProjectPage::accepted, this, &OpenProjectDialog::openPageAccepted ); openPage = addPage( openPageWidget, i18n("Select a build system setup file, existing KDevelop project, " "or any folder to open as a project") ); if (!currentPage) { currentPage = openPage; } } else { nativeDialog = new QFileDialog(this, i18n("Open Project")); nativeDialog->setDirectoryUrl(start); nativeDialog->setFileMode(QFileDialog::Directory); } ProjectInfoPage* page = new ProjectInfoPage( this ); connect( page, &ProjectInfoPage::projectNameChanged, this, &OpenProjectDialog::validateProjectName ); connect( page, &ProjectInfoPage::projectManagerChanged, this, &OpenProjectDialog::validateProjectManager ); projectInfoPage = addPage( page, i18n("Project Information") ); if (!currentPage) { currentPage = projectInfoPage; } setValid( sourcePage, false ); setValid( openPage, false ); setValid( projectInfoPage, false); setAppropriate( projectInfoPage, false ); setCurrentPage( currentPage ); setWindowTitle(i18n("Open Project")); } bool OpenProjectDialog::execNativeDialog() { while (true) { if (nativeDialog->exec()) { QUrl selectedUrl = nativeDialog->selectedUrls().at(0); if (getUrlInfo(selectedUrl).isValid) { // validate directory first to populate m_projectName and m_projectManager validateOpenUrl(selectedUrl.adjusted(QUrl::RemoveFilename)); validateOpenUrl(selectedUrl); return true; } } else { return false; } } } int OpenProjectDialog::exec() { if (nativeDialog && !execNativeDialog()) { reject(); return QDialog::Rejected; } return KAssistantDialog::exec(); } void OpenProjectDialog::validateSourcePage(bool valid) { setValid(sourcePage, valid); if (!nativeDialog) { openPageWidget->setUrl(sourcePageWidget->workingDir()); } } void OpenProjectDialog::validateOpenUrl( const QUrl& url_ ) { URLInfo urlInfo = getUrlInfo(url_); const QUrl url = url_.adjusted(QUrl::StripTrailingSlash); // openPage is used only in KDE if (openPage) { if ( urlInfo.isValid ) { // reset header openPage->setHeader(i18n("Open \"%1\" as project", url.fileName())); } else { // report error KColorScheme scheme(palette().currentColorGroup()); const QString errorMsg = i18n("Selected URL is invalid"); openPage->setHeader(QStringLiteral("%2") .arg(scheme.foreground(KColorScheme::NegativeText).color().name(), errorMsg) ); setAppropriate( projectInfoPage, false ); setAppropriate( openPage, true ); setValid( openPage, false ); return; } } m_selected = url; if( urlInfo.isDir || urlInfo.extension != ShellExtension::getInstance()->projectFileExtension() ) { setAppropriate( projectInfoPage, true ); m_url = url; if( !urlInfo.isDir ) { m_url = m_url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename); } ProjectInfoPage* page = qobject_cast( projectInfoPage->widget() ); if( page ) { page->setProjectName( m_url.fileName() ); // clear the filelist m_fileList.clear(); if( urlInfo.isDir ) { // If a dir was selected fetch all files in it KIO::ListJob* job = KIO::listDir( m_url ); connect( job, &KIO::ListJob::entries, this, &OpenProjectDialog::storeFileList); KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); job->exec(); } else { // Else we'll just take the given file m_fileList << url.fileName(); } // Now find a manager for the file(s) in our filelist. QVector choices; Q_FOREACH ( const auto& file, m_fileList ) { auto plugins = projectManagerForFile(file); if ( plugins.contains("") ) { plugins.removeAll(""); choices.append({i18n("Open existing file \"%1\"", file), "", QString()}); } Q_FOREACH ( const auto& plugin, plugins ) { auto meta = m_projectPlugins.value(plugin); choices.append({file + QString(" (%1)").arg(plugin), meta.pluginId(), meta.iconName()}); } } Q_FOREACH ( const auto& plugin, m_projectFilters.keys() ) { qDebug() << plugin << m_projectFilters.value(plugin); if ( m_projectFilters.value(plugin).isEmpty() ) { // that works in any case auto meta = m_projectPlugins.value(plugin); choices.append({plugin, meta.pluginId(), meta.iconName()}); } } page->populateProjectFileCombo(choices); } m_url.setPath( m_url.path() + '/' + m_url.fileName() + '.' + ShellExtension::getInstance()->projectFileExtension() ); } else { setAppropriate( projectInfoPage, false ); m_url = url; } validateProjectInfo(); setValid( openPage, true ); } QStringList OpenProjectDialog::projectManagerForFile(const QString& file) const { QStringList ret; foreach( const QString& manager, m_projectFilters.keys() ) { foreach( const QString& filterexp, m_projectFilters.value(manager) ) { QRegExp exp( filterexp, Qt::CaseSensitive, QRegExp::Wildcard ); if ( exp.exactMatch(file) ) { ret.append(manager); } } } if ( file.endsWith(ShellExtension::getInstance()->projectFileExtension()) ) { ret.append(""); } return ret; } void OpenProjectDialog::openPageAccepted() { if ( isValid( openPage ) ) { next(); } } void OpenProjectDialog::validateProjectName( const QString& name ) { m_projectName = name; validateProjectInfo(); } void OpenProjectDialog::validateProjectInfo() { setValid( projectInfoPage, (!projectName().isEmpty() && !projectManager().isEmpty()) ); } void OpenProjectDialog::validateProjectManager( const QString& manager ) { m_projectManager = manager; validateProjectInfo(); } QUrl OpenProjectDialog::projectFileUrl() const { return m_url; } QUrl OpenProjectDialog::selectedUrl() const { return m_selected; } QString OpenProjectDialog::projectName() const { return m_projectName; } QString OpenProjectDialog::projectManager() const { return m_projectManager; } void OpenProjectDialog::storeFileList(KIO::Job*, const KIO::UDSEntryList& list) { foreach( const KIO::UDSEntry& entry, list ) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if( name != QLatin1String(".") && name != QLatin1String("..") && !entry.isDir() ) { m_fileList << name; } } } } diff --git a/shell/partcontroller.cpp b/shell/partcontroller.cpp index 5763936094..fef0d89dca 100644 --- a/shell/partcontroller.cpp +++ b/shell/partcontroller.cpp @@ -1,327 +1,327 @@ /*************************************************************************** * Copyright 2006 Adam Treat * * Copyright 2007 Alexander Dymo * * Copyright 2015 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "partcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "textdocument.h" #include "debug.h" #include "uicontroller.h" #include "mainwindow.h" #include #include #include #include namespace KDevelop { class PartControllerPrivate { public: PartControllerPrivate() {} bool m_showTextEditorStatusBar = false; QString m_editor; QStringList m_textTypes; Core *m_core; }; PartController::PartController(Core *core, QWidget *toplevel) : IPartController( toplevel ), d(new PartControllerPrivate) { setObjectName(QStringLiteral("PartController")); d->m_core = core; //Cache this as it is too expensive when creating parts // KConfig * config = Config::standard(); // config->setGroup( "General" ); // // d->m_textTypes = config->readEntry( "TextTypes", QStringList() ); // // config ->setGroup( "Editor" ); // d->m_editor = config->readPathEntry( "EmbeddedKTextEditor", QString() ); // required early because some actions are checkable and need to be initialized loadSettings(false); if (!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } PartController::~PartController() { delete d; } bool PartController::showTextEditorStatusBar() const { return d->m_showTextEditorStatusBar; } void PartController::setShowTextEditorStatusBar(bool show) { if (d->m_showTextEditorStatusBar == show) return; d->m_showTextEditorStatusBar = show; // update foreach (Sublime::Area* area, Core::self()->uiControllerInternal()->allAreas()) { foreach (Sublime::View* view, area->views()) { if (!view->hasWidget()) continue; auto textView = qobject_cast(view->widget()); if (textView) { textView->setStatusBarEnabled(show); } } } // also notify active view that it should update the "view status" TextView* textView = qobject_cast(Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()); if (textView) { emit textView->statusChanged(textView); } } //MOVE BACK TO DOCUMENTCONTROLLER OR MULTIBUFFER EVENTUALLY bool PartController::isTextType(const QMimeType& mimeType) { bool isTextType = false; if (d->m_textTypes.contains(mimeType.name())) { isTextType = true; } // is this regular text - open in editor return ( isTextType || mimeType.inherits(QStringLiteral("text/plain")) || mimeType.inherits(QStringLiteral("text/html")) || mimeType.inherits(QStringLiteral("application/x-zerosize"))); } KTextEditor::Editor* PartController::editorPart() const { return KTextEditor::Editor::instance(); } KTextEditor::Document* PartController::createTextPart(const QString &encoding) { KTextEditor::Document* doc = editorPart()->createDocument(this); if ( !encoding.isNull() ) { KParts::OpenUrlArguments args = doc->arguments(); args.setMimeType( QLatin1String( "text/plain;" ) + encoding ); doc->setArguments( args ); } return doc; } KParts::Part* PartController::createPart( const QString & mimeType, const QString & partType, const QString & className, const QString & preferredName ) { KPluginFactory * editorFactory = findPartFactory( mimeType, partType, preferredName ); if ( !className.isEmpty() && editorFactory ) { return editorFactory->create( - 0, + nullptr, this, className.toLatin1() ); } - return 0; + return nullptr; } bool PartController::canCreatePart(const QUrl& url) { if (!url.isValid()) return false; QString mimeType; if ( url.isEmpty() ) mimeType = QStringLiteral("text/plain"); else mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); KService::List offers = KMimeTypeTrader::self()->query( mimeType, QStringLiteral("KParts/ReadOnlyPart") ); return offers.count() > 0; } KParts::Part* PartController::createPart( const QUrl & url, const QString& preferredPart ) { qCDebug(SHELL) << "creating part with url" << url << "and pref part:" << preferredPart; QString mimeType; if ( url.isEmpty() ) //create a part for empty text file mimeType = QStringLiteral("text/plain"); else if ( !url.isValid() ) - return 0; + return nullptr; else mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); KParts::Part* part = createPart( mimeType, preferredPart ); if( part ) { readOnly( part ) ->openUrl( url ); return part; } - return 0; + return nullptr; } KParts::ReadOnlyPart* PartController::activeReadOnly() const { return readOnly( activePart() ); } KParts::ReadWritePart* PartController::activeReadWrite() const { return readWrite( activePart() ); } KParts::ReadOnlyPart* PartController::readOnly( KParts::Part * part ) const { return qobject_cast( part ); } KParts::ReadWritePart* PartController::readWrite( KParts::Part * part ) const { return qobject_cast( part ); } void PartController::loadSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); d->m_showTextEditorStatusBar = cg.readEntry("ShowTextEditorStatusBar", false); } void PartController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); cg.writeEntry("ShowTextEditorStatusBar", d->m_showTextEditorStatusBar); } void PartController::initialize() { } void PartController::cleanup() { saveSettings(false); } void PartController::setupActions() { KActionCollection* actionCollection = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = KStandardAction::showStatusbar(this, SLOT(setShowTextEditorStatusBar(bool)), actionCollection); action->setWhatsThis(i18n("Use this command to show or hide the view's statusbar")); action->setChecked(showTextEditorStatusBar()); } //BEGIN KTextEditor::MdiContainer void PartController::setActiveView(KTextEditor::View *view) { Q_UNUSED(view) // NOTE: not implemented } KTextEditor::View *PartController::activeView() { TextView* textView = dynamic_cast(Core::self()->uiController()->activeArea()->activeView()); if (textView) { return textView->textView(); } - return 0; + return nullptr; } KTextEditor::Document *PartController::createDocument() { // NOTE: not implemented qWarning() << "WARNING: interface call not implemented"; - return 0; + return nullptr; } bool PartController::closeDocument(KTextEditor::Document *doc) { Q_UNUSED(doc) // NOTE: not implemented qWarning() << "WARNING: interface call not implemented"; return false; } KTextEditor::View *PartController::createView(KTextEditor::Document *doc) { Q_UNUSED(doc) // NOTE: not implemented qWarning() << "WARNING: interface call not implemented"; - return 0; + return nullptr; } bool PartController::closeView(KTextEditor::View *view) { Q_UNUSED(view) // NOTE: not implemented qWarning() << "WARNING: interface call not implemented"; return false; } //END KTextEditor::MdiContainer } diff --git a/shell/partdocument.cpp b/shell/partdocument.cpp index 5528079122..ea485cbaf9 100644 --- a/shell/partdocument.cpp +++ b/shell/partdocument.cpp @@ -1,227 +1,227 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "partdocument.h" #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "partcontroller.h" #include "documentcontroller.h" namespace KDevelop { class PartDocumentPrivate { public: QMap partForView; QString preferredPart; }; PartDocument::PartDocument(const QUrl& url, KDevelop::ICore* core, const QString& preferredPart) : Sublime::UrlDocument(core->uiController()->controller(), url), KDevelop::IDocument(core), d(new PartDocumentPrivate) { d->preferredPart = preferredPart; } PartDocument::~PartDocument() { delete d; } QWidget *PartDocument::createViewWidget(QWidget* /*parent*/) { KParts::Part *part = Core::self()->partControllerInternal()->createPart(url(), d->preferredPart); if( part ) { Core::self()->partController()->addPart(part); QWidget *w = part->widget(); d->partForView[w] = part; return w; } - return 0; + return nullptr; } KParts::Part *PartDocument::partForView(QWidget *view) const { return d->partForView[view]; } //KDevelop::IDocument implementation QMimeType PartDocument::mimeType() const { return QMimeDatabase().mimeTypeForUrl(url()); } KTextEditor::Document *PartDocument::textDocument() const { - return 0; + return nullptr; } bool PartDocument::isActive() const { return Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()->document() == this; } bool PartDocument::save(DocumentSaveMode /*mode*/) { //part document is read-only so do nothing here return true; } bool PartDocument::askForCloseFeedback() { int code = -1; if (state() == IDocument::Modified) { code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The document \"%1\" has unsaved changes. Would you like to save them?", url().toLocalFile()), i18n("Close Document")); /// @todo Is this behavior right? } else if (state() == IDocument::DirtyAndModified) { code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The document \"%1\" has unsaved changes and was modified by an external process.\n" "Do you want to override the external changes?", url().toLocalFile()), i18n("Close Document")); } if (code >= 0) { if (code == KMessageBox::Yes) { if (!save(Default)) return false; } else if (code == KMessageBox::Cancel) { return false; } } return true; } bool PartDocument::close(DocumentSaveMode mode) { if (!(mode & Discard)) { if (mode & Silent) { if (!save(mode)) return false; } else { if( !askForCloseFeedback() ) return false; } } //close all views and then delete ourself closeViews(); foreach (KParts::Part* part, d->partForView) part->deleteLater(); // The document will be deleted automatically if there are no views left return true; } bool PartDocument::closeDocument(bool silent) { return close(silent ? Silent : Default); } void PartDocument::reload() { //part document is read-only so do nothing here } IDocument::DocumentState PartDocument::state() const { return Clean; } void PartDocument::activate(Sublime::View *activeView, KParts::MainWindow *mainWindow) { Q_UNUSED(mainWindow); KParts::Part *part = partForView(activeView->widget()); if (Core::self()->partController()->activePart() != part) Core::self()->partController()->setActivePart(part); notifyActivated(); } KTextEditor::Cursor KDevelop::PartDocument::cursorPosition() const { return KTextEditor::Cursor::invalid(); } void PartDocument::setCursorPosition(const KTextEditor::Cursor &cursor) { //do nothing here Q_UNUSED(cursor); } void PartDocument::setTextSelection(const KTextEditor::Range &range) { Q_UNUSED(range); } QUrl PartDocument::url() const { return Sublime::UrlDocument::url(); } void PartDocument::setUrl(const QUrl& newUrl) { Sublime::UrlDocument::setUrl(newUrl); if(!prettyName().isEmpty()) setTitle(prettyName()); notifyUrlChanged(); } void PartDocument::setPrettyName(QString name) { KDevelop::IDocument::setPrettyName(name); // Re-set the url, to trigger the whole chain if(!name.isEmpty()) setTitle(name); else setTitle(Core::self()->projectController()->prettyFileName(url(), KDevelop::IProjectController::FormatPlain)); } QMap PartDocument::partForView() const { return d->partForView; } void PartDocument::addPartForView(QWidget* w, KParts::Part* p) { d->partForView[w]=p; } } diff --git a/shell/plugincontroller.cpp b/shell/plugincontroller.cpp index 6934008b94..62e1d27be8 100644 --- a/shell/plugincontroller.cpp +++ b/shell/plugincontroller.cpp @@ -1,792 +1,792 @@ /* This file is part of the KDE project Copyright 2004, 2007 Alexander Dymo Copyright 2006 Matt Rogers Based on code from Kopete Copyright (c) 2002-2003 Martijn Klingens 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 "plugincontroller.h" #include #include #include #include #include #include // TODO: remove once we no longer support old style plugins #include #include #include #include #include #include #include #include "core.h" #include "shellextension.h" #include "runcontroller.h" #include "debugcontroller.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "projectcontroller.h" #include "ktexteditorpluginintegration.h" #include "debug.h" namespace { inline QString KEY_Plugins() { return QStringLiteral("Plugins"); } inline QString KEY_Suffix_Enabled() { return QStringLiteral("Enabled"); } inline QString KEY_LoadMode() { return QStringLiteral("X-KDevelop-LoadMode"); } inline QString KEY_Category() { return QStringLiteral("X-KDevelop-Category"); } inline QString KEY_Mode() { return QStringLiteral("X-KDevelop-Mode"); } inline QString KEY_Version() { return QStringLiteral("X-KDevelop-Version"); } inline QString KEY_Interfaces() { return QStringLiteral("X-KDevelop-Interfaces"); } inline QString KEY_Required() { return QStringLiteral("X-KDevelop-IRequired"); } inline QString KEY_Optional() { return QStringLiteral("X-KDevelop-IOptional"); } inline QString KEY_Global() { return QStringLiteral("Global"); } inline QString KEY_Project() { return QStringLiteral("Project"); } inline QString KEY_Gui() { return QStringLiteral("GUI"); } inline QString KEY_AlwaysOn() { return QStringLiteral("AlwaysOn"); } inline QString KEY_UserSelectable() { return QStringLiteral("UserSelectable"); } bool isUserSelectable( const KPluginMetaData& info ) { QString loadMode = info.value(KEY_LoadMode()); return loadMode.isEmpty() || loadMode == KEY_UserSelectable(); } bool isGlobalPlugin( const KPluginMetaData& info ) { return info.value(KEY_Category()) == KEY_Global(); } bool hasMandatoryProperties( const KPluginMetaData& info ) { QString mode = info.value(KEY_Mode()); if (mode.isEmpty()) { return false; } // when the plugin is installed into the versioned plugin path, it's good to go if (info.fileName().contains(QLatin1String("/kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION) "/"))) { return true; } // the version property is only required when the plugin is not installed into the right directory QVariant version = info.rawData().value(KEY_Version()).toVariant(); if (version.isValid() && version.value() == KDEVELOP_PLUGIN_VERSION) { return true; } return false; } bool constraintsMatch( const KPluginMetaData& info, const QVariantMap& constraints) { for (auto it = constraints.begin(); it != constraints.end(); ++it) { const auto property = info.rawData().value(it.key()).toVariant(); if (!property.isValid()) { return false; } else if (property.canConvert()) { QSet values = property.toStringList().toSet(); QSet expected = it.value().toStringList().toSet(); if (!values.contains(expected)) { return false; } } else if (it.value() != property) { return false; } } return true; } struct Dependency { Dependency(const QString &dependency) : interface(dependency) { if (dependency.contains('@')) { const auto list = dependency.split('@', QString::SkipEmptyParts); if (list.size() == 2) { interface = list.at(0); pluginName = list.at(1); } } } QString interface; QString pluginName; }; } namespace KDevelop { class PluginControllerPrivate { public: QVector plugins; //map plugin infos to currently loaded plugins typedef QHash InfoToPluginMap; InfoToPluginMap loadedPlugins; // The plugin manager's mode. The mode is StartingUp until loadAllPlugins() // has finished loading the plugins, after which it is set to Running. // ShuttingDown and DoneShutdown are used during shutdown by the // async unloading of plugins. enum CleanupMode { Running /**< the plugin manager is running */, CleaningUp /**< the plugin manager is cleaning up for shutdown */, CleanupDone /**< the plugin manager has finished cleaning up */ }; CleanupMode cleanupMode; bool canUnload(const KPluginMetaData& plugin) { qCDebug(SHELL) << "checking can unload for:" << plugin.name() << plugin.value(KEY_LoadMode()); if (plugin.value(KEY_LoadMode()) == KEY_AlwaysOn()) { return false; } const QStringList interfaces = KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces()); qCDebug(SHELL) << "checking dependencies:" << interfaces; foreach (const KPluginMetaData& info, loadedPlugins.keys()) { if (info.pluginId() != plugin.pluginId()) { QStringList dependencies = KPluginMetaData::readStringList(plugin.rawData(), KEY_Required()); dependencies += KPluginMetaData::readStringList(plugin.rawData(), KEY_Optional()); foreach (const QString& dep, dependencies) { Dependency dependency(dep); if (!dependency.pluginName.isEmpty() && dependency.pluginName != plugin.pluginId()) { continue; } if (interfaces.contains(dependency.interface) && !canUnload(info)) { return false; } } } } return true; } KPluginMetaData infoForId( const QString& id ) const { foreach (const KPluginMetaData& info, plugins) { if (info.pluginId() == id) { return info; } } return KPluginMetaData(); } /** * Iterate over all cached plugin infos, and call the functor for every enabled plugin. * * If an extension and/or pluginName is given, the functor will only be called for * those plugins matching this information. * * The functor should return false when the iteration can be stopped, and true if it * should be continued. */ template void foreachEnabledPlugin(F func, const QString &extension = {}, const QVariantMap& constraints = QVariantMap(), const QString &pluginName = {}) { foreach (const auto& info, plugins) { if ((pluginName.isEmpty() || info.pluginId() == pluginName) && (extension.isEmpty() || KPluginMetaData::readStringList(info.rawData(), KEY_Interfaces()).contains(extension)) && constraintsMatch(info, constraints) && isEnabled(info)) { if (!func(info)) { break; } } } } /** * Decide whether a plugin is enabled */ bool isEnabled(const KPluginMetaData& info) const { static const QStringList disabledPlugins = QString::fromLatin1(qgetenv("KDEV_DISABLE_PLUGINS")).split(';'); if (disabledPlugins.contains(info.pluginId())) { return false; } if (!isUserSelectable( info )) return true; // in case there's a user preference, prefer that const KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); const QString pluginEnabledKey = info.pluginId() + KEY_Suffix_Enabled(); if (grp.hasKey(pluginEnabledKey)) { return grp.readEntry(pluginEnabledKey, true); } // in all other cases: figure out if we want to load that plugin by default const auto defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); const bool isDefaultPlugin = defaultPlugins.isEmpty() || defaultPlugins.contains(info.pluginId()); if (isDefaultPlugin) { return true; } if (!isGlobalPlugin( info )) { QJsonValue enabledByDefault = info.rawData()[QStringLiteral("KPlugin")].toObject()[QStringLiteral("EnabledByDefault")]; return enabledByDefault.isNull() || enabledByDefault.toBool(); //We consider plugins enabled until specified otherwise } return false; } Core *core; }; PluginController::PluginController(Core *core) : IPluginController(), d(new PluginControllerPrivate) { setObjectName(QStringLiteral("PluginController")); d->core = core; QSet foundPlugins; auto newPlugins = KPluginLoader::findPlugins(QStringLiteral("kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION)), [&](const KPluginMetaData& meta) { if (meta.serviceTypes().contains(QStringLiteral("KDevelop/Plugin"))) { foundPlugins.insert(meta.pluginId()); return true; } else { qWarning() << "Plugin" << meta.fileName() << "is installed into the kdevplatform plugin directory, but does not have" " \"KDevelop/Plugin\" set as the service type. This plugin will not be loaded."; return false; } }); qCDebug(SHELL) << "Found" << newPlugins.size() << " plugins:" << foundPlugins; d->plugins = newPlugins; KTextEditorIntegration::initialize(); const QVector ktePlugins = KPluginLoader::findPlugins(QStringLiteral("ktexteditor"), [](const KPluginMetaData & md) { return md.serviceTypes().contains(QStringLiteral("KTextEditor/Plugin")) && md.serviceTypes().contains(QStringLiteral("KDevelop/Plugin")); }); foundPlugins.clear(); std::for_each(ktePlugins.cbegin(), ktePlugins.cend(), [&foundPlugins](const KPluginMetaData& data) { foundPlugins << data.pluginId(); }); qCDebug(SHELL) << "Found" << ktePlugins.size() << " KTextEditor plugins:" << foundPlugins; foreach (const auto& info, ktePlugins) { auto data = info.rawData(); // add some KDevelop specific JSON data data[KEY_Category()] = KEY_Global(); data[KEY_Mode()] = KEY_Gui(); data[KEY_Version()] = KDEVELOP_PLUGIN_VERSION; d->plugins.append({data, info.fileName(), info.metaDataFileName()}); } d->cleanupMode = PluginControllerPrivate::Running; // Register the KDevelop::IPlugin* metatype so we can properly unload it qRegisterMetaType( "KDevelop::IPlugin*" ); } PluginController::~PluginController() { if ( d->cleanupMode != PluginControllerPrivate::CleanupDone ) { qCWarning(SHELL) << "Destructing plugin controller without going through the shutdown process!"; } delete d; } KPluginMetaData PluginController::pluginInfo( const IPlugin* plugin ) const { return d->loadedPlugins.key(const_cast(plugin)); } void PluginController::cleanup() { if(d->cleanupMode != PluginControllerPrivate::Running) { //qCDebug(SHELL) << "called when not running. state =" << d->cleanupMode; return; } d->cleanupMode = PluginControllerPrivate::CleaningUp; // Ask all plugins to unload while ( !d->loadedPlugins.isEmpty() ) { //Let the plugin do some stuff before unloading unloadPlugin(d->loadedPlugins.begin().value(), Now); } d->cleanupMode = PluginControllerPrivate::CleanupDone; } IPlugin* PluginController::loadPlugin( const QString& pluginName ) { return loadPluginInternal( pluginName ); } bool PluginController::isEnabled( const KPluginMetaData& info ) const { return d->isEnabled(info); } void PluginController::initialize() { QElapsedTimer timer; timer.start(); QMap pluginMap; if( ShellExtension::getInstance()->defaultPlugins().isEmpty() ) { foreach( const KPluginMetaData& pi, d->plugins ) { pluginMap.insert( pi.pluginId(), true ); } } else { // Get the default from the ShellExtension foreach( const QString& s, ShellExtension::getInstance()->defaultPlugins() ) { pluginMap.insert( s, true ); } } KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); QMap entries = grp.entryMap(); QMap::Iterator it; for ( it = entries.begin(); it != entries.end(); ++it ) { const QString key = it.key(); if (key.endsWith(KEY_Suffix_Enabled())) { bool enabled = false; const QString pluginid = key.left(key.length() - 7); const bool defValue = pluginMap.value( pluginid, false ); enabled = grp.readEntry(key, defValue); pluginMap.insert( pluginid, enabled ); } } foreach( const KPluginMetaData& pi, d->plugins ) { if( isGlobalPlugin( pi ) ) { QMap::const_iterator it = pluginMap.constFind( pi.pluginId() ); if( it != pluginMap.constEnd() && ( it.value() || !isUserSelectable( pi ) ) ) { // Plugin is mentioned in pluginmap and the value is true, so try to load it loadPluginInternal( pi.pluginId() ); if(!grp.hasKey(pi.pluginId() + KEY_Suffix_Enabled())) { if( isUserSelectable( pi ) ) { // If plugin isn't listed yet, add it with true now grp.writeEntry(pi.pluginId() + KEY_Suffix_Enabled(), true); } } else if( grp.hasKey( pi.pluginId() + "Disabled" ) && !isUserSelectable( pi ) ) { // Remove now-obsolete entries grp.deleteEntry( pi.pluginId() + "Disabled" ); } } } } // Synchronize so we're writing out to the file. grp.sync(); qCDebug(SHELL) << "Done loading plugins - took:" << timer.elapsed() << "ms"; } QList PluginController::loadedPlugins() const { return d->loadedPlugins.values(); } bool PluginController::unloadPlugin( const QString & pluginId ) { IPlugin *thePlugin = plugin( pluginId ); bool canUnload = d->canUnload( d->infoForId( pluginId ) ); qCDebug(SHELL) << "Unloading plugin:" << pluginId << "?" << thePlugin << canUnload; if( thePlugin && canUnload ) { return unloadPlugin(thePlugin, Later); } return (canUnload && thePlugin); } bool PluginController::unloadPlugin(IPlugin* plugin, PluginDeletion deletion) { qCDebug(SHELL) << "unloading plugin:" << plugin << pluginInfo( plugin ).name(); emit unloadingPlugin(plugin); plugin->unload(); emit pluginUnloaded(plugin); //Remove the plugin from our list of plugins so we create a new //instance when we're asked for it again. //This is important to do right here, not later when the plugin really //vanishes. For example project re-opening might try to reload the plugin //and then would get the "old" pointer which will be deleted in the next //event loop run and thus causing crashes. for ( PluginControllerPrivate::InfoToPluginMap::Iterator it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it ) { if ( it.value() == plugin ) { d->loadedPlugins.erase( it ); break; } } if (deletion == Later) plugin->deleteLater(); else delete plugin; return true; } KPluginMetaData PluginController::infoForPluginId( const QString &pluginId ) const { foreach (const KPluginMetaData& info, d->plugins) { if (info.pluginId() == pluginId) { return info; } } return KPluginMetaData(); } IPlugin *PluginController::loadPluginInternal( const QString &pluginId ) { QElapsedTimer timer; timer.start(); KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) { qCWarning(SHELL) << "Unable to find a plugin named '" << pluginId << "'!" ; return nullptr; } if ( IPlugin* plugin = d->loadedPlugins.value( info ) ) { return plugin; } if ( !isEnabled( info ) ) { // Do not load disabled plugins qWarning() << "Not loading plugin named" << pluginId << "because it has been disabled!"; return nullptr; } if ( !hasMandatoryProperties( info ) ) { qWarning() << "Unable to load plugin named" << pluginId << "because not all mandatory properties are set."; return nullptr; } if ( info.value(KEY_Mode()) == KEY_Gui() && Core::self()->setupFlags() == Core::NoUi ) { qCDebug(SHELL) << "Not loading plugin named" << pluginId << "- Running in No-Ui mode, but the plugin says it needs a GUI"; return nullptr; } qCDebug(SHELL) << "Attempting to load" << pluginId << "- name:" << info.name(); emit loadingPlugin( info.pluginId() ); // first, ensure all dependencies are available and not disabled // this is unrelated to whether they are loaded already or not. // when we depend on e.g. A and B, but B cannot be found, then we // do not want to load A first and then fail on B and leave A loaded. // this would happen if we'd skip this step here and directly loadDependencies. QStringList missingInterfaces; if ( !hasUnresolvedDependencies( info, missingInterfaces ) ) { qWarning() << "Can't load plugin" << pluginId << "some of its required dependencies could not be fulfilled:" << missingInterfaces.join(QStringLiteral(",")); return nullptr; } // now ensure all dependencies are loaded QString failedDependency; if( !loadDependencies( info, failedDependency ) ) { qWarning() << "Can't load plugin" << pluginId << "because a required dependency could not be loaded:" << failedDependency; return nullptr; } // same for optional dependencies, but don't error out if anything fails loadOptionalDependencies( info ); // now we can finally load the plugin itself KPluginLoader loader(info.fileName()); auto factory = loader.factory(); if (!factory) { qWarning() << "Can't load plugin" << pluginId << "because a factory to load the plugin could not be obtained:" << loader.errorString(); return nullptr; } // now create it auto plugin = factory->create(d->core); if (!plugin) { if (auto katePlugin = factory->create(d->core, QVariantList() << info.pluginId())) { plugin = new KTextEditorIntegration::Plugin(katePlugin, d->core); } else { qWarning() << "Creating plugin" << pluginId << "failed."; return nullptr; } } KConfigGroup group = Core::self()->activeSession()->config()->group(KEY_Plugins()); // runtime errors such as missing executables on the system or such get checked now if (plugin->hasError()) { qWarning() << "Could not load plugin" << pluginId << ", it reported the error:" << plugin->errorDescription() << "Disabling the plugin now."; group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), false); // do the same as KPluginInfo did group.sync(); unloadPlugin(pluginId); return nullptr; } // yay, it all worked - the plugin is loaded d->loadedPlugins.insert(info, plugin); group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), true); // do the same as KPluginInfo did group.sync(); qCDebug(SHELL) << "Successfully loaded plugin" << pluginId << "from" << loader.fileName() << "- took:" << timer.elapsed() << "ms"; emit pluginLoaded( plugin ); return plugin; } IPlugin* PluginController::plugin( const QString& pluginId ) { KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) - return 0L; + return nullptr; return d->loadedPlugins.value( info ); } bool PluginController::hasUnresolvedDependencies( const KPluginMetaData& info, QStringList& missing ) const { QSet required = KPluginMetaData::readStringList(info.rawData(), KEY_Required()).toSet(); if (!required.isEmpty()) { d->foreachEnabledPlugin([&required] (const KPluginMetaData& plugin) -> bool { foreach (const QString& iface, KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces())) { required.remove(iface); required.remove(iface + '@' + plugin.pluginId()); } return !required.isEmpty(); }); } // if we found all dependencies required should be empty now if (!required.isEmpty()) { missing = required.toList(); return false; } return true; } void PluginController::loadOptionalDependencies( const KPluginMetaData& info ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Optional()); foreach (const QString& dep, dependencies) { Dependency dependency(dep); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { qCDebug(SHELL) << "Couldn't load optional dependency:" << dep << info.pluginId(); } } } bool PluginController::loadDependencies( const KPluginMetaData& info, QString& failedDependency ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Required()); foreach (const QString& value, dependencies) { Dependency dependency(value); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { failedDependency = value; return false; } } return true; } IPlugin *PluginController::pluginForExtension(const QString &extension, const QString &pluginName, const QVariantMap& constraints) { IPlugin* plugin = nullptr; d->foreachEnabledPlugin([this, &plugin] (const KPluginMetaData& info) -> bool { plugin = d->loadedPlugins.value( info ); if( !plugin ) { plugin = loadPluginInternal( info.pluginId() ); } return !plugin; }, extension, constraints, pluginName); return plugin; } QList PluginController::allPluginsForExtension(const QString &extension, const QVariantMap& constraints) { //qCDebug(SHELL) << "Finding all Plugins for Extension:" << extension << "|" << constraints; QList plugins; d->foreachEnabledPlugin([this, &plugins] (const KPluginMetaData& info) -> bool { IPlugin* plugin = d->loadedPlugins.value( info ); if( !plugin) { plugin = loadPluginInternal( info.pluginId() ); } if (plugin && !plugins.contains(plugin)) { plugins << plugin; } return true; }, extension, constraints); return plugins; } QVector PluginController::queryExtensionPlugins(const QString& extension, const QVariantMap& constraints) const { QVector plugins; d->foreachEnabledPlugin([&plugins] (const KPluginMetaData& info) -> bool { plugins << info; return true; }, extension, constraints); return plugins; } QStringList PluginController::allPluginNames() { QStringList names; Q_FOREACH( const KPluginMetaData& info , d->plugins ) { names << info.pluginId(); } return names; } QList PluginController::queryPluginsForContextMenuExtensions(KDevelop::Context* context) const { // This fixes random order of extension menu items between different runs of KDevelop. // Without sorting we have random reordering of "Analyze With" submenu for example: // 1) "Cppcheck" actions, "Vera++" actions - first run // 2) "Vera++" actions, "Cppcheck" actions - some other run. QMultiMap sortedPlugins; for (auto it = d->loadedPlugins.constBegin(); it != d->loadedPlugins.constEnd(); ++it) { sortedPlugins.insert(it.key().name(), it.value()); } QList exts; foreach (IPlugin* plugin, sortedPlugins) { exts << plugin->contextMenuExtension(context); } exts << Core::self()->debugControllerInternal()->contextMenuExtension(context); exts << Core::self()->documentationControllerInternal()->contextMenuExtension(context); exts << Core::self()->sourceFormatterControllerInternal()->contextMenuExtension(context); exts << Core::self()->runControllerInternal()->contextMenuExtension(context); exts << Core::self()->projectControllerInternal()->contextMenuExtension(context); return exts; } QStringList PluginController::projectPlugins() { QStringList names; foreach (const KPluginMetaData& info, d->plugins) { if (info.value(KEY_Category()) == KEY_Project()) { names << info.pluginId(); } } return names; } void PluginController::loadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { loadPluginInternal( name ); } } void PluginController::unloadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { unloadPlugin( name ); } } QVector PluginController::allPluginInfos() const { return d->plugins; } void PluginController::updateLoadedPlugins() { QStringList defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); foreach( const KPluginMetaData& info, d->plugins ) { if( isGlobalPlugin( info ) ) { bool enabled = grp.readEntry(info.pluginId() + KEY_Suffix_Enabled(), ( defaultPlugins.isEmpty() || defaultPlugins.contains( info.pluginId() ) ) ) || !isUserSelectable( info ); bool loaded = d->loadedPlugins.contains( info ); if( loaded && !enabled ) { qCDebug(SHELL) << "unloading" << info.pluginId(); if( !unloadPlugin( info.pluginId() ) ) { grp.writeEntry( info.pluginId() + KEY_Suffix_Enabled(), false ); } } else if( !loaded && enabled ) { loadPluginInternal( info.pluginId() ); } } } } void PluginController::resetToDefaults() { KSharedConfigPtr cfg = Core::self()->activeSession()->config(); cfg->deleteGroup( KEY_Plugins() ); cfg->sync(); KConfigGroup grp = cfg->group( KEY_Plugins() ); QStringList plugins = ShellExtension::getInstance()->defaultPlugins(); if( plugins.isEmpty() ) { foreach( const KPluginMetaData& info, d->plugins ) { plugins << info.pluginId(); } } foreach( const QString& s, plugins ) { grp.writeEntry(s + KEY_Suffix_Enabled(), true); } grp.sync(); } } diff --git a/shell/progresswidget/overlaywidget.cpp b/shell/progresswidget/overlaywidget.cpp index f3eb998ec5..22dd5e9f7a 100644 --- a/shell/progresswidget/overlaywidget.cpp +++ b/shell/progresswidget/overlaywidget.cpp @@ -1,99 +1,99 @@ /** -*- c++ -*- * overlaywidget.h * * Copyright (c) 2004 David Faure * * 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; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "overlaywidget.h" #include #include #include #include using namespace KDevelop; OverlayWidget::OverlayWidget( QWidget* alignWidget, QWidget* parent, const char* name ) - : QWidget( parent, Qt::Window | Qt::FramelessWindowHint ), mAlignWidget( 0 ) + : QWidget( parent, Qt::Window | Qt::FramelessWindowHint ), mAlignWidget( nullptr ) { auto hboxHBoxLayout = new QHBoxLayout(this); hboxHBoxLayout->setMargin(0); setObjectName(name); setAlignWidget( alignWidget ); setWindowFlags(Qt::WindowDoesNotAcceptFocus | windowFlags()); qApp->installEventFilter(this); } OverlayWidget::~OverlayWidget() { } void OverlayWidget::reposition() { if ( !mAlignWidget ) return; // p is in the alignWidget's coordinates QPoint p; // We are always above the alignWidget, right-aligned with it. p.setX( mAlignWidget->width() - width() ); p.setY( -height() ); // Position in the global coordinates QPoint global = mAlignWidget->mapToGlobal( p ); // Move 'this' to that position. move( global ); } void OverlayWidget::setAlignWidget( QWidget * w ) { if (w == mAlignWidget) return; mAlignWidget = w; reposition(); } bool OverlayWidget::eventFilter( QObject* o, QEvent* e) { if (e->type() == QEvent::Move || e->type() == QEvent::Resize) { reposition(); } else if (e->type() == QEvent::Close) { close(); } return QWidget::eventFilter(o,e); } void OverlayWidget::resizeEvent( QResizeEvent* ev ) { reposition(); QWidget::resizeEvent( ev ); } diff --git a/shell/progresswidget/progressdialog.cpp b/shell/progresswidget/progressdialog.cpp index f6885b3ffd..7863208249 100644 --- a/shell/progresswidget/progressdialog.cpp +++ b/shell/progresswidget/progressdialog.cpp @@ -1,412 +1,412 @@ /** -*- c++ -*- * progressdialog.cpp * * Copyright (c) 2004 Till Adam , * David Faure * * 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; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "progressdialog.h" #include "progressmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KDevelop { static const int MAX_LABEL_WIDTH = 650; class TransactionItem; TransactionItemView::TransactionItemView( QWidget *parent, const char *name ) : QScrollArea( parent ) { setObjectName( name ); setFrameStyle( NoFrame ); mBigBox = new QWidget( this ); auto layout = new QVBoxLayout(mBigBox); layout->setMargin(0); setWidget( mBigBox ); setWidgetResizable( true ); setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ); } TransactionItem *TransactionItemView::addTransactionItem( ProgressItem *item, bool first ) { TransactionItem *ti = new TransactionItem( mBigBox, item, first ); mBigBox->layout()->addWidget( ti ); resize( mBigBox->width(), mBigBox->height() ); return ti; } void TransactionItemView::resizeEvent ( QResizeEvent *event ) { // Tell the layout in the parent (progressdialog) that our size changed updateGeometry(); QSize sz = parentWidget()->sizeHint(); int currentWidth = parentWidget()->width(); // Don't resize to sz.width() every time when it only reduces a little bit if ( currentWidth < sz.width() || currentWidth > sz.width() + 100 ) { currentWidth = sz.width(); } parentWidget()->resize( currentWidth, sz.height() ); QScrollArea::resizeEvent( event ); } QSize TransactionItemView::sizeHint() const { return minimumSizeHint(); } QSize TransactionItemView::minimumSizeHint() const { int f = 2 * frameWidth(); // Make room for a vertical scrollbar in all cases, to avoid a horizontal one int vsbExt = verticalScrollBar()->sizeHint().width(); QSize sz( mBigBox->minimumSizeHint() ); sz.setWidth( sz.width() + f + vsbExt ); sz.setHeight( sz.height() + f ); return sz; } void TransactionItemView::slotItemCompleted(TransactionItem* item) { // If completed item is the first, hide separator line for the one that will become first now if (mBigBox->layout()->indexOf(item) == 0) { auto *secondItem = mBigBox->layout()->itemAt(1); if (secondItem) { static_cast(secondItem->widget())->hideHLine(); } } mBigBox->layout()->removeWidget(item); delete item; //This slot is called whenever a TransactionItem is deleted, so this is a //good place to call updateGeometry(), so our parent takes the new size //into account and resizes. updateGeometry(); } // ---------------------------------------------------------------------------- TransactionItem::TransactionItem( QWidget *parent, ProgressItem *item, bool first ) - : QWidget( parent ), mCancelButton( 0 ), mItem( item ) + : QWidget( parent ), mCancelButton( nullptr ), mItem( item ) { auto vbox = new QVBoxLayout(this); vbox->setSpacing( 2 ); vbox->setMargin( 2 ); setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); mFrame = new QFrame( this ); mFrame->setFrameShape( QFrame::HLine ); mFrame->setFrameShadow( QFrame::Raised ); mFrame->show(); vbox->setStretchFactor( mFrame, 3 ); vbox->addWidget( mFrame ); QWidget *h = new QWidget( this ); auto hboxLayout = new QHBoxLayout(h); hboxLayout->setMargin(0); hboxLayout->setSpacing( 5 ); vbox->addWidget( h ); mItemLabel = new QLabel( fontMetrics().elidedText( item->label(), Qt::ElideRight, MAX_LABEL_WIDTH ), h ); h->layout()->addWidget( mItemLabel ); h->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); mProgress = new QProgressBar( h ); hboxLayout->addWidget(mProgress); mProgress->setMaximum( 100 ); mProgress->setValue( item->progress() ); h->layout()->addWidget( mProgress ); if ( item->canBeCanceled() ) { mCancelButton = new QPushButton( QIcon::fromTheme( QStringLiteral("dialog-cancel") ), QString(), h ); hboxLayout->addWidget(mCancelButton); mCancelButton->setToolTip( i18n( "Cancel this operation." ) ); connect ( mCancelButton, &QPushButton::clicked, this, &TransactionItem::slotItemCanceled); h->layout()->addWidget( mCancelButton ); } h = new QWidget( this ); hboxLayout = new QHBoxLayout(h); hboxLayout->setMargin(0); hboxLayout->setSpacing( 5 ); h->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); vbox->addWidget( h ); mItemStatus = new QLabel( h ); hboxLayout->addWidget(mItemStatus); mItemStatus->setTextFormat( Qt::RichText ); mItemStatus->setText( fontMetrics().elidedText( item->status(), Qt::ElideRight, MAX_LABEL_WIDTH ) ); h->layout()->addWidget( mItemStatus ); if ( first ) { hideHLine(); } } TransactionItem::~TransactionItem() { } void TransactionItem::hideHLine() { mFrame->hide(); } void TransactionItem::setProgress( int progress ) { mProgress->setValue( progress ); } void TransactionItem::setLabel( const QString &label ) { mItemLabel->setText( fontMetrics().elidedText( label, Qt::ElideRight, MAX_LABEL_WIDTH ) ); } void TransactionItem::setStatus( const QString &status ) { mItemStatus->setText( fontMetrics().elidedText( status, Qt::ElideRight, MAX_LABEL_WIDTH ) ); } void TransactionItem::setTotalSteps( int totalSteps ) { mProgress->setMaximum( totalSteps ); } void TransactionItem::slotItemCanceled() { if ( mItem ) { mItem->cancel(); } } void TransactionItem::addSubTransaction( ProgressItem *item ) { Q_UNUSED( item ); } // --------------------------------------------------------------------------- ProgressDialog::ProgressDialog( QWidget *alignWidget, QWidget *parent, const char *name ) : OverlayWidget( alignWidget, parent, name ), mWasLastShown( false ) { setAutoFillBackground( true ); mScrollView = new TransactionItemView( this, "ProgressScrollView" ); layout()->addWidget( mScrollView ); // No more close button for now, since there is no more autoshow /* QVBox* rightBox = new QVBox( this ); QToolButton* pbClose = new QToolButton( rightBox ); pbClose->setAutoRaise(true); pbClose->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) ); pbClose->setFixedSize( 16, 16 ); pbClose->setIcon( KIconLoader::global()->loadIconSet( "window-close", KIconLoader::Small, 14 ) ); pbClose->setToolTip( i18n( "Hide detailed progress window" ) ); connect(pbClose, SIGNAL(clicked()), this, SLOT(slotClose())); QWidget* spacer = new QWidget( rightBox ); // don't let the close button take up all the height rightBox->setStretchFactor( spacer, 100 ); */ /* * Get the singleton ProgressManager item which will inform us of * appearing and vanishing items. */ ProgressManager *pm = ProgressManager::instance(); connect ( pm, &ProgressManager::progressItemAdded, this, &ProgressDialog::slotTransactionAdded ); connect ( pm, &ProgressManager::progressItemCompleted, this, &ProgressDialog::slotTransactionCompleted ); connect ( pm, &ProgressManager::progressItemProgress, this, &ProgressDialog::slotTransactionProgress ); connect ( pm, &ProgressManager::progressItemStatus, this, &ProgressDialog::slotTransactionStatus ); connect ( pm, &ProgressManager::progressItemLabel, this, &ProgressDialog::slotTransactionLabel ); connect ( pm, &ProgressManager::progressItemUsesBusyIndicator, this, &ProgressDialog::slotTransactionUsesBusyIndicator ); connect ( pm, &ProgressManager::showProgressDialog, this, &ProgressDialog::slotShow ); } void ProgressDialog::closeEvent( QCloseEvent *e ) { e->accept(); hide(); } /* * Destructor */ ProgressDialog::~ProgressDialog() { // no need to delete child widgets. } void ProgressDialog::slotTransactionAdded( ProgressItem *item ) { if ( item->parent() ) { if ( mTransactionsToListviewItems.contains( item->parent() ) ) { TransactionItem * parent = mTransactionsToListviewItems[ item->parent() ]; parent->addSubTransaction( item ); } } else { const bool first = mTransactionsToListviewItems.empty(); TransactionItem *ti = mScrollView->addTransactionItem( item, first ); if ( ti ) { mTransactionsToListviewItems.insert( item, ti ); } if ( first && mWasLastShown ) { QTimer::singleShot( 1000, this, SLOT(slotShow()) ); } } } void ProgressDialog::slotTransactionCompleted( ProgressItem *item ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; mTransactionsToListviewItems.remove( item ); ti->setItemComplete(); QTimer::singleShot( 3000, mScrollView, [=] { mScrollView->slotItemCompleted(ti); } ); } // This was the last item, hide. if ( mTransactionsToListviewItems.empty() ) { QTimer::singleShot( 3000, this, SLOT(slotHide()) ); } } void ProgressDialog::slotTransactionCanceled( ProgressItem * ) { } void ProgressDialog::slotTransactionProgress( ProgressItem *item, unsigned int progress ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; ti->setProgress( progress ); } } void ProgressDialog::slotTransactionStatus( ProgressItem *item, const QString &status ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; ti->setStatus( status ); } } void ProgressDialog::slotTransactionLabel( ProgressItem *item, const QString &label ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; ti->setLabel( label ); } } void ProgressDialog::slotTransactionUsesBusyIndicator( KDevelop::ProgressItem *item, bool value ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; if ( value ) { ti->setTotalSteps( 0 ); } else { ti->setTotalSteps( 100 ); } } } void ProgressDialog::slotShow() { setVisible( true ); } void ProgressDialog::slotHide() { // check if a new item showed up since we started the timer. If not, hide if ( mTransactionsToListviewItems.isEmpty() ) { setVisible( false ); } mWasLastShown = false; } void ProgressDialog::slotClose() { mWasLastShown = false; setVisible( false ); } void ProgressDialog::setVisible( bool b ) { OverlayWidget::setVisible( b ); emit visibilityChanged( b ); } void ProgressDialog::slotToggleVisibility() { /* Since we are only hiding with a timeout, there is a short period of * time where the last item is still visible, but clicking on it in * the statusbarwidget should not display the dialog, because there * are no items to be shown anymore. Guard against that. */ mWasLastShown = isHidden(); if ( !isHidden() || !mTransactionsToListviewItems.isEmpty() ) { setVisible( isHidden() ); } } } diff --git a/shell/progresswidget/progressmanager.cpp b/shell/progresswidget/progressmanager.cpp index d0976baa8b..c6ae9924a5 100644 --- a/shell/progresswidget/progressmanager.cpp +++ b/shell/progresswidget/progressmanager.cpp @@ -1,269 +1,269 @@ /* progressmanager.cpp This file is part of libkdepim. Copyright (c) 2004 Till Adam 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 "progressmanager.h" #include "../debug.h" #include namespace KDevelop { unsigned int KDevelop::ProgressManager::uID = 42; ProgressItem::ProgressItem( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool canBeCanceled, bool usesCrypto ) : mId( id ), mLabel( label ), mStatus( status ), mParent( parent ), mCanBeCanceled( canBeCanceled ), mProgress( 0 ), mTotal( 0 ), mCompleted( 0 ), mWaitingForKids( false ), mCanceled( false ), mUsesCrypto( usesCrypto ), mUsesBusyIndicator( false ), mCompletedCalled( false ) { } ProgressItem::~ProgressItem() { } void ProgressItem::setComplete() { // qCDebug(SHELL) << label(); if ( mChildren.isEmpty() ) { if ( mCompletedCalled ) return; if ( !mCanceled ) { setProgress( 100 ); } mCompletedCalled = true; if ( parent() ) { parent()->removeChild( this ); } emit progressItemCompleted( this ); } else { mWaitingForKids = true; } } void ProgressItem::addChild( ProgressItem *kiddo ) { mChildren.insert( kiddo, true ); } void ProgressItem::removeChild( ProgressItem *kiddo ) { if ( mChildren.isEmpty() ) { mWaitingForKids = false; return; } if ( mChildren.remove( kiddo ) == 0 ) { // do nothing if the specified item is not in the map return; } // in case we were waiting for the last kid to go away, now is the time if ( mChildren.count() == 0 && mWaitingForKids ) { emit progressItemCompleted( this ); } } void ProgressItem::cancel() { if ( mCanceled || !mCanBeCanceled ) { return; } qCDebug(SHELL) << label(); mCanceled = true; // Cancel all children. QList kids = mChildren.keys(); QList::Iterator it( kids.begin() ); QList::Iterator end( kids.end() ); for ( ; it != end; it++ ) { ProgressItem *kid = *it; if ( kid->canBeCanceled() ) { kid->cancel(); } } setStatus( i18n( "Aborting..." ) ); emit progressItemCanceled( this ); } void ProgressItem::setProgress( unsigned int v ) { mProgress = v; // qCDebug(SHELL) << label() << " :" << v; emit progressItemProgress( this, mProgress ); } void ProgressItem::setLabel( const QString &v ) { mLabel = v; emit progressItemLabel( this, mLabel ); } void ProgressItem::setStatus( const QString &v ) { mStatus = v; emit progressItemStatus( this, mStatus ); } void ProgressItem::setUsesCrypto( bool v ) { mUsesCrypto = v; emit progressItemUsesCrypto( this, v ); } void ProgressItem::setUsesBusyIndicator( bool useBusyIndicator ) { mUsesBusyIndicator = useBusyIndicator; emit progressItemUsesBusyIndicator( this, useBusyIndicator ); } // ====================================== struct ProgressManagerPrivate { ProgressManager instance; }; Q_GLOBAL_STATIC( ProgressManagerPrivate, progressManagerPrivate ) ProgressManager::ProgressManager() : QObject() { } ProgressManager::~ProgressManager() {} ProgressManager *ProgressManager::instance() { - return progressManagerPrivate.isDestroyed() ? 0 : &progressManagerPrivate->instance ; + return progressManagerPrivate.isDestroyed() ? nullptr : &progressManagerPrivate->instance ; } ProgressItem *ProgressManager::createProgressItemImpl( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool cancellable, bool usesCrypto ) { - ProgressItem *t = 0; + ProgressItem *t = nullptr; if ( !mTransactions.value( id ) ) { t = new ProgressItem ( parent, id, label, status, cancellable, usesCrypto ); mTransactions.insert( id, t ); if ( parent ) { ProgressItem *p = mTransactions.value( parent->id() ); if ( p ) { p->addChild( t ); } } // connect all signals connect ( t, &ProgressItem::progressItemCompleted, this, &ProgressManager::slotTransactionCompleted ); connect ( t, &ProgressItem::progressItemProgress, this, &ProgressManager::progressItemProgress ); connect ( t, &ProgressItem::progressItemAdded, this, &ProgressManager::progressItemAdded ); connect ( t, &ProgressItem::progressItemCanceled, this, &ProgressManager::progressItemCanceled ); connect ( t, &ProgressItem::progressItemStatus, this, &ProgressManager::progressItemStatus ); connect ( t, &ProgressItem::progressItemLabel, this, &ProgressManager::progressItemLabel ); connect ( t, &ProgressItem::progressItemUsesCrypto, this, &ProgressManager::progressItemUsesCrypto ); connect ( t, &ProgressItem::progressItemUsesBusyIndicator, this, &ProgressManager::progressItemUsesBusyIndicator ); emit progressItemAdded( t ); } else { // Hm, is this what makes the most sense? t = mTransactions.value( id ); } return t; } ProgressItem *ProgressManager::createProgressItemImpl( const QString &parent, const QString &id, const QString &label, const QString &status, bool canBeCanceled, bool usesCrypto ) { ProgressItem *p = mTransactions.value( parent ); return createProgressItemImpl( p, id, label, status, canBeCanceled, usesCrypto ); } void ProgressManager::emitShowProgressDialogImpl() { emit showProgressDialog(); } // slots void ProgressManager::slotTransactionCompleted( ProgressItem *item ) { mTransactions.remove( item->id() ); emit progressItemCompleted( item ); } void ProgressManager::slotStandardCancelHandler( ProgressItem *item ) { item->setComplete(); } ProgressItem *ProgressManager::singleItem() const { - ProgressItem *item = 0; + ProgressItem *item = nullptr; QHash< QString, ProgressItem* >::const_iterator it = mTransactions.constBegin(); QHash< QString, ProgressItem* >::const_iterator end = mTransactions.constEnd(); while ( it != end ) { // No single item for progress possible, as one of them is a busy indicator one. if ( (*it)->usesBusyIndicator() ) - return 0; + return nullptr; if ( !(*it)->parent() ) { // if it's a top level one, only those count if ( item ) { - return 0; // we found more than one + return nullptr; // we found more than one } else { item = (*it); } } ++it; } return item; } void ProgressManager::slotAbortAll() { QHashIterator it(mTransactions); while (it.hasNext()) { it.next(); it.value()->cancel(); } } } // namespace diff --git a/shell/progresswidget/statusbarprogresswidget.cpp b/shell/progresswidget/statusbarprogresswidget.cpp index 42a58cfe0f..a15146cb9f 100644 --- a/shell/progresswidget/statusbarprogresswidget.cpp +++ b/shell/progresswidget/statusbarprogresswidget.cpp @@ -1,287 +1,287 @@ /* statusbarprogresswidget.cpp (C) 2004 Till Adam Don Sanders David Faure Copyright 2004 David Faure Includes StatusbarProgressWidget which is based on KIOLittleProgressDlg by Matt Koss KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2 or above, as published by the Free Software Foundation. KMail 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "statusbarprogresswidget.h" #include "progressdialog.h" #include "progressmanager.h" #ifdef Q_OS_OSX #include "../macdockprogressview.h" #endif #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; //----------------------------------------------------------------------------- StatusbarProgressWidget::StatusbarProgressWidget( ProgressDialog* progressDialog, QWidget* parent, bool button ) - : QFrame( parent ), mCurrentItem( 0 ), mProgressDialog( progressDialog ), - mDelayTimer( 0 ), mCleanTimer( 0 ) + : QFrame( parent ), mCurrentItem( nullptr ), mProgressDialog( progressDialog ), + mDelayTimer( nullptr ), mCleanTimer( nullptr ) { m_bShowButton = button; int w = fontMetrics().width( QStringLiteral(" 999.9 kB/s 00:00:01 ") ) + 8; box = new QHBoxLayout( this ); box->setMargin(0); box->setSpacing(0); m_pButton = new QToolButton( this ); m_pButton->setAttribute(Qt::WA_MacMiniSize); m_pButton->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) ); QIcon smallIcon = QIcon::fromTheme( QStringLiteral("go-up") ); m_pButton->setIcon( smallIcon ); box->addWidget( m_pButton ); stack = new QStackedWidget( this ); int maximumHeight = fontMetrics().height(); stack->setMaximumHeight( maximumHeight ); box->addWidget( stack ); m_pButton->setToolTip( i18n("Open detailed progress dialog") ); m_pButton->setAutoRaise(true); m_pProgressBar = new QProgressBar( this ); m_pProgressBar->installEventFilter( this ); m_pProgressBar->setMinimumWidth( w ); stack->insertWidget( 1,m_pProgressBar ); m_pLabel = new QLabel( QString(), this ); m_pLabel->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter ); m_pLabel->installEventFilter( this ); m_pLabel->setMinimumWidth( w ); stack->insertWidget( 2, m_pLabel ); #ifndef Q_OS_MAC // Currently on OSX this causes the button to be cut-off // It isn't necessary because on OSX the button's minimumSizeHint is small enough m_pButton->setMaximumHeight( maximumHeight ); #endif setMinimumWidth( minimumSizeHint().width() ); mode = None; setMode(); connect( m_pButton, &QPushButton::clicked, progressDialog, &ProgressDialog::slotToggleVisibility ); connect ( ProgressManager::instance(), &ProgressManager::progressItemAdded, this, &StatusbarProgressWidget::slotProgressItemAdded ); connect ( ProgressManager::instance(), &ProgressManager::progressItemCompleted, this, &StatusbarProgressWidget::slotProgressItemCompleted ); connect ( ProgressManager::instance(), &ProgressManager::progressItemUsesBusyIndicator, this, &StatusbarProgressWidget::updateBusyMode ); connect ( progressDialog, &ProgressDialog::visibilityChanged, this, &StatusbarProgressWidget::slotProgressDialogVisible ); mDelayTimer = new QTimer( this ); mDelayTimer->setSingleShot( true ); connect ( mDelayTimer, &QTimer::timeout, this, &StatusbarProgressWidget::slotShowItemDelayed ); mCleanTimer = new QTimer( this ); mCleanTimer->setSingleShot( true ); connect ( mCleanTimer, &QTimer::timeout, this, &StatusbarProgressWidget::slotClean ); } // There are three cases: no progressitem, one progressitem (connect to it directly), // or many progressitems (display busy indicator). Let's call them 0,1,N. // In slot..Added we can only end up in 1 or N. // In slot..Removed we can end up in 0, 1, or we can stay in N if we were already. void StatusbarProgressWidget::updateBusyMode() { connectSingleItem(); // if going to 1 item mDelayTimer->start( 1000 ); } void StatusbarProgressWidget::slotProgressItemAdded( ProgressItem *item ) { if ( item->parent() ) return; // we are only interested in top level items updateBusyMode(); } void StatusbarProgressWidget::slotProgressItemCompleted( ProgressItem *item ) { if ( item->parent() ) { item->deleteLater(); - item = 0; + item = nullptr; return; // we are only interested in top level items } item->deleteLater(); - item = 0; + item = nullptr; connectSingleItem(); // if going back to 1 item if ( ProgressManager::instance()->isEmpty() ) { // No item // Done. In 5s the progress-widget will close, then we can clean up the statusbar mCleanTimer->start( 5000 ); } else if ( mCurrentItem ) { // Exactly one item activateSingleItemMode(); } } void StatusbarProgressWidget::connectSingleItem() { if ( mCurrentItem ) { disconnect ( mCurrentItem, &ProgressItem::progressItemProgress, this, &StatusbarProgressWidget::slotProgressItemProgress ); - mCurrentItem = 0; + mCurrentItem = nullptr; } mCurrentItem = ProgressManager::instance()->singleItem(); if ( mCurrentItem ) { connect ( mCurrentItem, &ProgressItem::progressItemProgress, this, &StatusbarProgressWidget::slotProgressItemProgress ); } } void StatusbarProgressWidget::activateSingleItemMode() { m_pProgressBar->setMaximum( 100 ); m_pProgressBar->setValue( mCurrentItem->progress() ); m_pProgressBar->setTextVisible( true ); #ifdef Q_OS_OSX MacDockProgressView::setRange( 0, 100 ); MacDockProgressView::setProgress( mCurrentItem->progress() ); #endif } void StatusbarProgressWidget::slotShowItemDelayed() { bool noItems = ProgressManager::instance()->isEmpty(); if ( mCurrentItem ) { activateSingleItemMode(); } else if ( !noItems ) { // N items m_pProgressBar->setMaximum( 0 ); m_pProgressBar->setTextVisible( false ); #ifdef Q_OS_OSX MacDockProgressView::setRange( 0, 0 ); MacDockProgressView::setProgress( 0 ); #endif } if ( !noItems && mode == None ) { mode = Progress; setMode(); } } void StatusbarProgressWidget::slotProgressItemProgress( ProgressItem *item, unsigned int value ) { Q_ASSERT( item == mCurrentItem); // the only one we should be connected to Q_UNUSED( item ); m_pProgressBar->setValue( value ); #ifdef Q_OS_OSX MacDockProgressView::setProgress( value ); #endif } void StatusbarProgressWidget::setMode() { switch ( mode ) { case None: if ( m_bShowButton ) { m_pButton->hide(); } // show the empty label in order to make the status bar look better stack->show(); stack->setCurrentWidget( m_pLabel ); #ifdef Q_OS_OSX MacDockProgressView::setProgressVisible( false ); #endif break; case Progress: stack->show(); stack->setCurrentWidget( m_pProgressBar ); if ( m_bShowButton ) { m_pButton->show(); } #ifdef Q_OS_OSX MacDockProgressView::setProgressVisible( true ); #endif break; } } void StatusbarProgressWidget::slotClean() { // check if a new item showed up since we started the timer. If not, clear if ( ProgressManager::instance()->isEmpty() ) { m_pProgressBar->setValue( 0 ); //m_pLabel->clear(); mode = None; setMode(); } } bool StatusbarProgressWidget::eventFilter( QObject *, QEvent *ev ) { if ( ev->type() == QEvent::MouseButtonPress ) { QMouseEvent *e = (QMouseEvent*)ev; if ( e->button() == Qt::LeftButton && mode != None ) { // toggle view on left mouse button // Consensus seems to be that we should show/hide the fancy dialog when the user // clicks anywhere in the small one. mProgressDialog->slotToggleVisibility(); return true; } } return false; } void StatusbarProgressWidget::slotProgressDialogVisible( bool b ) { // Update the hide/show button when the detailed one is shown/hidden if ( b ) { m_pButton->setIcon( QIcon::fromTheme( QStringLiteral("go-down") ) ); m_pButton->setToolTip( i18n("Hide detailed progress window") ); setMode(); } else { m_pButton->setIcon( QIcon::fromTheme( QStringLiteral("go-up") ) ); m_pButton->setToolTip( i18n("Show detailed progress window") ); } } diff --git a/shell/project.cpp b/shell/project.cpp index 80eeb1c3fc..f8e22d1405 100644 --- a/shell/project.cpp +++ b/shell/project.cpp @@ -1,654 +1,654 @@ /* This file is part of the KDE project Copyright 2001 Matthias Hoelzer-Kluepfel Copyright 2002-2003 Roberto Raggi Copyright 2002 Simon Hausmann Copyright 2003 Jens Dagerbo Copyright 2003 Mario Scalas Copyright 2003-2004 Alexander Dymo Copyright 2006 Matt Rogers Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "project.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "mainwindow.h" #include "projectcontroller.h" #include "uicontroller.h" #include "debug.h" namespace KDevelop { class ProjectProgress : public QObject, public IStatus { Q_OBJECT Q_INTERFACES(KDevelop::IStatus) public: ProjectProgress(); ~ProjectProgress() override; QString statusName() const override; /*! Show indeterminate mode progress bar */ void setBuzzy(); /*! Hide progress bar */ void setDone(); QString projectName; private Q_SLOTS: void slotClean(); Q_SIGNALS: void clearMessage(KDevelop::IStatus*) override; void showMessage(KDevelop::IStatus*,const QString & message, int timeout = 0) override; void showErrorMessage(const QString & message, int timeout = 0) override; void hideProgress(KDevelop::IStatus*) override; void showProgress(KDevelop::IStatus*,int minimum, int maximum, int value) override; private: QTimer* m_timer; }; ProjectProgress::ProjectProgress() { m_timer = new QTimer(this); m_timer->setSingleShot( true ); m_timer->setInterval( 1000 ); connect(m_timer, &QTimer::timeout,this, &ProjectProgress::slotClean); } ProjectProgress::~ProjectProgress() { } QString ProjectProgress::statusName() const { return i18n("Loading Project %1", projectName); } void ProjectProgress::setBuzzy() { qCDebug(SHELL) << "showing busy progress" << statusName(); // show an indeterminate progressbar emit showProgress(this, 0,0,0); emit showMessage(this, i18nc("%1: Project name", "Loading %1", projectName)); } void ProjectProgress::setDone() { qCDebug(SHELL) << "showing done progress" << statusName(); // first show 100% bar for a second, then hide. emit showProgress(this, 0,1,1); m_timer->start(); } void ProjectProgress::slotClean() { emit hideProgress(this); emit clearMessage(this); } class ProjectPrivate { public: Path projectPath; Path projectFile; Path developerFile; QString developerTempFile; QTemporaryFile projectTempFile; IPlugin* manager; QPointer vcsPlugin; ProjectFolderItem* topItem; QString name; KSharedConfigPtr m_cfg; IProject *project; QSet fileSet; bool loading; bool fullReload; bool scheduleReload; ProjectProgress* progress; void reloadDone(KJob* job) { progress->setDone(); loading = false; ProjectController* projCtrl = Core::self()->projectControllerInternal(); if (job->error() == 0 && !Core::self()->shuttingDown()) { if(fullReload) projCtrl->projectModel()->appendRow(topItem); if (scheduleReload) { scheduleReload = false; project->reloadModel(); } } else { projCtrl->abortOpeningProject(project); } } QList itemsForPath( const IndexedString& path ) const { if ( path.isEmpty() ) { return QList(); } if (!topItem->model()) { // this gets hit when the project has not yet been added to the model // i.e. during import phase // TODO: should we handle this somehow? // possible idea: make the item<->path hash per-project return QList(); } Q_ASSERT(topItem->model()); QList items = topItem->model()->itemsForPath(path); QList::iterator it = items.begin(); while(it != items.end()) { if ((*it)->project() != project) { it = items.erase(it); } else { ++it; } } return items; } void importDone( KJob* job) { progress->setDone(); ProjectController* projCtrl = Core::self()->projectControllerInternal(); if(job->error() == 0 && !Core::self()->shuttingDown()) { loading=false; projCtrl->projectModel()->appendRow(topItem); projCtrl->projectImportingFinished( project ); } else { projCtrl->abortOpeningProject(project); } } void initProject(const Path& projectFile_) { // helper method for open() projectFile = projectFile_; } bool initProjectFiles() { KIO::StatJob* statJob = KIO::stat( projectFile.toUrl(), KIO::HideProgressInfo ); if ( !statJob->exec() ) //be sync for right now { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Unable to load the project file %1.
" "The project has been removed from the session.", projectFile.pathOrUrl() ) ); return false; } // developerfile == dirname(projectFileUrl) ."/.kdev4/". basename(projectfileUrl) developerFile = projectFile; developerFile.setLastPathSegment( QStringLiteral(".kdev4") ); developerFile.addPath( projectFile.lastPathSegment() ); statJob = KIO::stat( developerFile.toUrl(), KIO::HideProgressInfo ); if( !statJob->exec() ) { // the developerfile does not exist yet, check if its folder exists // the developerfile itself will get created below QUrl dir = developerFile.parent().toUrl(); statJob = KIO::stat( dir, KIO::HideProgressInfo ); if( !statJob->exec() ) { KIO::SimpleJob* mkdirJob = KIO::mkdir( dir ); if( !mkdirJob->exec() ) { KMessageBox::sorry( Core::self()->uiController()->activeMainWindow(), i18n("Unable to create hidden dir (%1) for developer file", dir.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } } } projectTempFile.open(); auto copyJob = KIO::file_copy(projectFile.toUrl(), QUrl::fromLocalFile(projectTempFile.fileName()), -1, KIO::Overwrite); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); if (!copyJob->exec()) { qCDebug(SHELL) << "Job failed:" << copyJob->errorString(); KMessageBox::sorry( Core::self()->uiController()->activeMainWindow(), i18n("Unable to get project file: %1", projectFile.pathOrUrl() ) ); return false; } if(developerFile.isLocalFile()) { developerTempFile = developerFile.toLocalFile(); } else { QTemporaryFile tmp; tmp.open(); developerTempFile = tmp.fileName(); auto job = KIO::file_copy(developerFile.toUrl(), QUrl::fromLocalFile(developerTempFile), -1, KIO::HideProgressInfo | KIO::Overwrite); KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); job->exec(); } return true; } KConfigGroup initKConfigObject() { // helper method for open() qCDebug(SHELL) << "Creating KConfig object for project files" << developerTempFile << projectTempFile.fileName(); m_cfg = KSharedConfig::openConfig( developerTempFile ); m_cfg->addConfigSources( QStringList() << projectTempFile.fileName() ); KConfigGroup projectGroup( m_cfg, "Project" ); return projectGroup; } bool projectNameUsed(const KConfigGroup& projectGroup) { // helper method for open() name = projectGroup.readEntry( "Name", projectFile.lastPathSegment() ); progress->projectName = name; if( Core::self()->projectController()->isProjectNameUsed( name ) ) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Could not load %1, a project with the same name '%2' is already open.", projectFile.pathOrUrl(), name ) ); qWarning() << "Trying to open a project with a name that is already used by another open project"; return true; } return false; } IProjectFileManager* fetchFileManager(const KConfigGroup& projectGroup) { if (manager) { - IProjectFileManager* iface = 0; + IProjectFileManager* iface = nullptr; iface = manager->extension(); Q_ASSERT(iface); return iface; } // helper method for open() QString managerSetting = projectGroup.readEntry( "Manager", "KDevGenericManager" ); //Get our importer IPluginController* pluginManager = Core::self()->pluginController(); manager = pluginManager->pluginForExtension( QStringLiteral("org.kdevelop.IProjectFileManager"), managerSetting ); - IProjectFileManager* iface = 0; + IProjectFileManager* iface = nullptr; if ( manager ) iface = manager->extension(); else { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Could not load project management plugin %1.
Check that the required programs are installed," " or see console output for more information.", managerSetting ) ); - manager = 0; - return 0; + manager = nullptr; + return nullptr; } - if (iface == 0) + if (iface == nullptr) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "project importing plugin (%1) does not support the IProjectFileManager interface.", managerSetting ) ); delete manager; - manager = 0; + manager = nullptr; } return iface; } void loadVersionControlPlugin(KConfigGroup& projectGroup) { // helper method for open() IPluginController* pluginManager = Core::self()->pluginController(); if( projectGroup.hasKey( "VersionControlSupport" ) ) { QString vcsPluginName = projectGroup.readEntry("VersionControlSupport", ""); if( !vcsPluginName.isEmpty() ) { vcsPlugin = pluginManager->pluginForExtension( QStringLiteral( "org.kdevelop.IBasicVersionControl" ), vcsPluginName ); } } else { QList plugins = pluginManager->allPluginsForExtension( QStringLiteral( "org.kdevelop.IBasicVersionControl" ) ); foreach( IPlugin* p, plugins ) { IBasicVersionControl* iface = p->extension(); if( iface && iface->isVersionControlled( topItem->path().toUrl() ) ) { vcsPlugin = p; projectGroup.writeEntry("VersionControlSupport", pluginManager->pluginInfo(p).pluginId()); projectGroup.sync(); } } } } bool importTopItem(IProjectFileManager* fileManager) { if (!fileManager) { return false; } topItem = fileManager->import( project ); if( !topItem ) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n("Could not open project") ); return false; } return true; } }; Project::Project( QObject *parent ) : IProject( parent ) , d( new ProjectPrivate ) { QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/Project"), this, QDBusConnection::ExportScriptableSlots ); d->project = this; - d->manager = 0; - d->topItem = 0; + d->manager = nullptr; + d->topItem = nullptr; d->loading = false; d->scheduleReload = false; d->progress = new ProjectProgress; Core::self()->uiController()->registerStatus( d->progress ); } Project::~Project() { delete d->progress; delete d; } QString Project::name() const { return d->name; } QString Project::developerTempFile() const { return d->developerTempFile; } QString Project::projectTempFile() const { return d->projectTempFile.fileName(); } KSharedConfigPtr Project::projectConfiguration() const { return d->m_cfg; } Path Project::path() const { return d->projectPath; } void Project::reloadModel() { if (d->loading) { d->scheduleReload = true; return; } d->loading = true; d->fileSet.clear(); // delete topItem and remove it from model ProjectModel* model = Core::self()->projectController()->projectModel(); model->removeRow( d->topItem->row() ); - d->topItem = 0; + d->topItem = nullptr; IProjectFileManager* iface = d->manager->extension(); if (!d->importTopItem(iface)) { d->loading = false; d->scheduleReload = false; return; } KJob* importJob = iface->createImportJob(d->topItem ); setReloadJob(importJob); d->fullReload = true; Core::self()->runController()->registerJob( importJob ); } void Project::setReloadJob(KJob* job) { d->loading = true; d->fullReload = false; d->progress->setBuzzy(); connect(job, &KJob::finished, this, [&] (KJob* job) { d->reloadDone(job); }); } bool Project::open( const Path& projectFile ) { d->initProject(projectFile); if (!d->initProjectFiles()) return false; KConfigGroup projectGroup = d->initKConfigObject(); if (d->projectNameUsed(projectGroup)) return false; d->projectPath = d->projectFile.parent(); IProjectFileManager* iface = d->fetchFileManager(projectGroup); if (!iface) return false; Q_ASSERT(d->manager); emit aboutToOpen(this); if (!d->importTopItem(iface) ) { return false; } d->loading=true; d->loadVersionControlPlugin(projectGroup); d->progress->setBuzzy(); KJob* importJob = iface->createImportJob(d->topItem ); connect( importJob, &KJob::result, this, [&] (KJob* job) { d->importDone(job); } ); Core::self()->runController()->registerJob( importJob ); return true; } void Project::close() { Q_ASSERT(d->topItem); if (d->topItem->row() == -1) { qWarning() << "Something went wrong. ProjectFolderItem detached. Project closed during reload?"; return; } Core::self()->projectController()->projectModel()->removeRow( d->topItem->row() ); if (!d->developerFile.isLocalFile()) { auto copyJob = KIO::file_copy(QUrl::fromLocalFile(d->developerTempFile), d->developerFile.toUrl()); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); if (!copyJob->exec()) { qCDebug(SHELL) << "Job failed:" << copyJob->errorString(); KMessageBox::sorry(Core::self()->uiController()->activeMainWindow(), i18n("Could not store developer specific project configuration.\n" "Attention: The project settings you changed will be lost.")); } } } bool Project::inProject( const IndexedString& path ) const { if (d->fileSet.contains( path )) { return true; } return !d->itemsForPath( path ).isEmpty(); } QList< ProjectBaseItem* > Project::itemsForPath(const IndexedString& path) const { return d->itemsForPath(path); } QList< ProjectFileItem* > Project::filesForPath(const IndexedString& file) const { QList items; foreach(ProjectBaseItem* item, d->itemsForPath( file ) ) { if( item->type() == ProjectBaseItem::File ) items << dynamic_cast( item ); } return items; } QList Project::foldersForPath(const IndexedString& folder) const { QList items; foreach(ProjectBaseItem* item, d->itemsForPath( folder ) ) { if( item->type() == ProjectBaseItem::Folder || item->type() == ProjectBaseItem::BuildFolder ) items << dynamic_cast( item ); } return items; } IProjectFileManager* Project::projectFileManager() const { return d->manager->extension(); } IBuildSystemManager* Project::buildSystemManager() const { return d->manager->extension(); } IPlugin* Project::managerPlugin() const { return d->manager; } void Project::setManagerPlugin( IPlugin* manager ) { d->manager = manager; } Path Project::projectFile() const { return d->projectFile; } Path Project::developerFile() const { return d->developerFile; } ProjectFolderItem* Project::projectItem() const { return d->topItem; } IPlugin* Project::versionControlPlugin() const { return d->vcsPlugin.data(); } void Project::addToFileSet( ProjectFileItem* file ) { if (d->fileSet.contains(file->indexedPath())) { return; } d->fileSet.insert( file->indexedPath() ); emit fileAddedToSet( file ); } void Project::removeFromFileSet( ProjectFileItem* file ) { QSet::iterator it = d->fileSet.find(file->indexedPath()); if (it == d->fileSet.end()) { return; } d->fileSet.erase( it ); emit fileRemovedFromSet( file ); } QSet Project::fileSet() const { return d->fileSet; } bool Project::isReady() const { return !d->loading; } } // namespace KDevelop #include "project.moc" #include "moc_project.cpp" diff --git a/shell/projectcontroller.cpp b/shell/projectcontroller.cpp index 1515397995..bea8851824 100644 --- a/shell/projectcontroller.cpp +++ b/shell/projectcontroller.cpp @@ -1,1204 +1,1204 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectcontroller.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" // TODO: Should get rid off this include (should depend on IProject only) #include "project.h" #include "mainwindow.h" #include "shellextension.h" #include "plugincontroller.h" #include "configdialog.h" #include "uicontroller.h" #include "documentcontroller.h" #include "openprojectdialog.h" #include "sessioncontroller.h" #include "session.h" #include "debug.h" namespace KDevelop { class ProjectControllerPrivate { public: QList m_projects; QMap< IProject*, QList > m_projectPlugins; QPointer m_recentAction; Core* m_core; // IProject* m_currentProject; ProjectModel* model; QItemSelectionModel* selectionModel; QPointer m_openProject; QPointer m_fetchProject; QPointer m_closeProject; QPointer m_openConfig; IProjectDialogProvider* dialog; QList m_currentlyOpening; // project-file urls that are being opened ProjectController* q; ProjectBuildSetModel* buildset; bool m_foundProjectFile; //Temporary flag used while searching the hierarchy for a project file bool m_cleaningUp; //Temporary flag enabled while destroying the project-controller QPointer m_changesModel; QHash< IProject*, QPointer > m_parseJobs; // parse jobs that add files from the project to the background parser. ProjectControllerPrivate( ProjectController* p ) - : m_core(0), model(0), selectionModel(0), dialog(0), q(p), buildset(0), m_foundProjectFile(false), m_cleaningUp(false) + : m_core(nullptr), model(nullptr), selectionModel(nullptr), dialog(nullptr), q(p), buildset(nullptr), m_foundProjectFile(false), m_cleaningUp(false) { } void unloadAllProjectPlugins() { if( m_projects.isEmpty() ) m_core->pluginControllerInternal()->unloadProjectPlugins(); } void projectConfig( QObject * obj ) { if( !obj ) return; Project* proj = qobject_cast(obj); if( !proj ) return; QVector configPages; auto mainWindow = m_core->uiController()->activeMainWindow(); ProjectConfigOptions options; options.developerFile = proj->developerFile(); options.developerTempFile = proj->developerTempFile(); options.projectTempFile = proj->projectTempFile(); options.project = proj; foreach (IPlugin* plugin, findPluginsForProject(proj)) { for (int i = 0; i < plugin->perProjectConfigPages(); ++i) { configPages.append(plugin->perProjectConfigPage(i, options, mainWindow)); } } std::sort(configPages.begin(), configPages.end(), [](const ConfigPage* a, const ConfigPage* b) { return a->name() < b->name(); }); auto cfgDlg = new KDevelop::ConfigDialog(configPages, mainWindow); cfgDlg->setAttribute(Qt::WA_DeleteOnClose); cfgDlg->setModal(true); QObject::connect(cfgDlg, &ConfigDialog::configSaved, cfgDlg, [this, proj](ConfigPage* page) { Q_UNUSED(page) Q_ASSERT_X(proj, Q_FUNC_INFO, "ConfigDialog signalled project config change, but no project set for configuring!"); emit q->projectConfigurationChanged(proj); }); cfgDlg->setWindowTitle(i18n("Configure Project %1", proj->name())); QObject::connect(cfgDlg, &KDevelop::ConfigDialog::finished, [this, proj]() { proj->projectConfiguration()->sync(); }); cfgDlg->show(); } void saveListOfOpenedProjects() { auto activeSession = Core::self()->activeSession(); if (!activeSession) { return; } QList openProjects; openProjects.reserve( m_projects.size() ); foreach( IProject* project, m_projects ) { openProjects.append(project->projectFile().toUrl()); } activeSession->setContainedProjects( openProjects ); } // Recursively collects builder dependencies for a project. static void collectBuilders( QList< IProjectBuilder* >& destination, IProjectBuilder* topBuilder, IProject* project ) { QList< IProjectBuilder* > auxBuilders = topBuilder->additionalBuilderPlugins( project ); destination.append( auxBuilders ); foreach( IProjectBuilder* auxBuilder, auxBuilders ) { collectBuilders( destination, auxBuilder, project ); } } QVector findPluginsForProject( IProject* project ) { QList plugins = m_core->pluginController()->loadedPlugins(); QVector projectPlugins; QList buildersForKcm; // Important to also include the "top" builder for the project, so // projects with only one such builder are kept working. Otherwise the project config // dialog is empty for such cases. if( IBuildSystemManager* buildSystemManager = project->buildSystemManager() ) { buildersForKcm << buildSystemManager->builder(); collectBuilders( buildersForKcm, buildSystemManager->builder(), project ); } foreach(auto plugin, plugins) { auto info = m_core->pluginController()->pluginInfo(plugin); IProjectFileManager* manager = plugin->extension(); if( manager && manager != project->projectFileManager() ) { // current plugin is a manager but does not apply to given project, skip continue; } IProjectBuilder* builder = plugin->extension(); if ( builder && !buildersForKcm.contains( builder ) ) { continue; } qCDebug(SHELL) << "Using plugin" << info.pluginId() << "for project" << project->name(); projectPlugins << plugin; } return projectPlugins; } void updateActionStates( Context* ctx ) { ProjectItemContext* itemctx = dynamic_cast(ctx); m_openConfig->setEnabled( itemctx && itemctx->items().count() == 1 ); m_closeProject->setEnabled( itemctx && itemctx->items().count() > 0 ); } void openProjectConfig() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() == 1 ) { q->configureProject( ctx->items().at(0)->project() ); } } void closeSelectedProjects() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() > 0 ) { QSet projects; foreach( ProjectBaseItem* item, ctx->items() ) { projects.insert( item->project() ); } foreach( IProject* project, projects ) { q->closeProject( project ); } } } void importProject(const QUrl& url_) { QUrl url(url_); if (url.isLocalFile()) { const QString path = QFileInfo(url.toLocalFile()).canonicalFilePath(); if (!path.isEmpty()) { url = QUrl::fromLocalFile(path); } } if ( !url.isValid() ) { KMessageBox::error(Core::self()->uiControllerInternal()->activeMainWindow(), i18n("Invalid Location: %1", url.toDisplayString(QUrl::PreferLocalFile))); return; } if ( m_currentlyOpening.contains(url)) { qCDebug(SHELL) << "Already opening " << url << ". Aborting."; KPassivePopup::message( i18n( "Project already being opened"), i18n( "Already opening %1, not opening again", url.toDisplayString(QUrl::PreferLocalFile) ), m_core->uiController()->activeMainWindow() ); return; } foreach( IProject* project, m_projects ) { if ( url == project->projectFile().toUrl() ) { if ( dialog->userWantsReopen() ) { // close first, then open again by falling through q->closeProject(project); } else { // abort return; } } } m_currentlyOpening += url; m_core->pluginControllerInternal()->loadProjectPlugins(); Project* project = new Project(); QObject::connect(project, &Project::aboutToOpen, q, &ProjectController::projectAboutToBeOpened); if ( !project->open( Path(url) ) ) { m_currentlyOpening.removeAll(url); q->abortOpeningProject(project); project->deleteLater(); } } void areaChanged(Sublime::Area* area) { KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); ac->action(QStringLiteral("commit_current_project"))->setEnabled(area->objectName() == QLatin1String("code")); ac->action(QStringLiteral("commit_current_project"))->setVisible(area->objectName() == QLatin1String("code")); } }; IProjectDialogProvider::IProjectDialogProvider() {} IProjectDialogProvider::~IProjectDialogProvider() {} ProjectDialogProvider::ProjectDialogProvider(ProjectControllerPrivate* const p) : d(p) {} ProjectDialogProvider::~ProjectDialogProvider() {} bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& createdFrom, const QString& manager ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( localConfigFile, KConfig::SimpleConfig ); if (!cfg->isConfigWritable(true)) { qCDebug(SHELL) << "can't write to configfile"; return false; } KConfigGroup grp = cfg->group( "Project" ); grp.writeEntry( "Name", name ); grp.writeEntry( "CreatedFrom", createdFrom ); grp.writeEntry( "Manager", manager ); cfg->sync(); return true; } bool writeProjectSettingsToConfigFile(const QUrl& projectFileUrl, OpenProjectDialog* dlg) { if ( !projectFileUrl.isLocalFile() ) { QTemporaryFile tmp; if ( !tmp.open() ) { return false; } if ( !writeNewProjectFile( tmp.fileName(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ) ) { return false; } // explicitly close file before uploading it, see also: https://bugs.kde.org/show_bug.cgi?id=254519 tmp.close(); auto uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmp.fileName()), projectFileUrl); KJobWidgets::setWindow(uploadJob, Core::self()->uiControllerInternal()->defaultMainWindow()); return uploadJob->exec(); } // Here and above we take .filename() part of the selectedUrl() to make it relative to the project root, // thus keeping .kdev file relocatable return writeNewProjectFile( projectFileUrl.toLocalFile(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ); } bool projectFileExists( const QUrl& u ) { if( u.isLocalFile() ) { return QFileInfo::exists( u.toLocalFile() ); } else { auto statJob = KIO::stat(u, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(statJob, Core::self()->uiControllerInternal()->activeMainWindow()); return statJob->exec(); } } bool equalProjectFile( const QString& configPath, OpenProjectDialog* dlg ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig ); KConfigGroup grp = cfg->group( "Project" ); QString defaultName = dlg->projectFileUrl().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).fileName(); return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) && grp.readEntry( "Manager", QString() ) == dlg->projectManager(); } QUrl ProjectDialogProvider::askProjectConfigLocation(bool fetch, const QUrl& startUrl) { Q_ASSERT(d); OpenProjectDialog dlg( fetch, startUrl, Core::self()->uiController()->activeMainWindow() ); if(dlg.exec() == QDialog::Rejected) return QUrl(); QUrl projectFileUrl = dlg.projectFileUrl(); qCDebug(SHELL) << "selected project:" << projectFileUrl << dlg.projectName() << dlg.projectManager(); if ( dlg.projectManager() == "" ) { return projectFileUrl; } // controls if existing project file should be saved bool writeProjectConfigToFile = true; if( projectFileExists( projectFileUrl ) ) { // check whether config is equal bool shouldAsk = true; if( projectFileUrl == dlg.selectedUrl() ) { if( projectFileUrl.isLocalFile() ) { shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), &dlg ); } else { shouldAsk = false; QTemporaryFile tmpFile; if (tmpFile.open()) { auto downloadJob = KIO::file_copy(projectFileUrl, QUrl::fromLocalFile(tmpFile.fileName())); KJobWidgets::setWindow(downloadJob, qApp->activeWindow()); if (downloadJob->exec()) { shouldAsk = !equalProjectFile(tmpFile.fileName(), &dlg); } } } } if ( shouldAsk ) { KGuiItem yes = KStandardGuiItem::yes(); yes.setText(i18n("Override")); yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration.")); yes.setIcon(QIcon()); KGuiItem no = KStandardGuiItem::no(); no.setText(i18n("Open Existing File")); no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration.")); no.setIcon(QIcon()); KGuiItem cancel = KStandardGuiItem::cancel(); cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project.")); int ret = KMessageBox::questionYesNoCancel(qApp->activeWindow(), i18n("There already exists a project configuration file at %1.\n" "Do you want to override it or open the existing file?", projectFileUrl.toDisplayString(QUrl::PreferLocalFile)), i18n("Override existing project configuration"), yes, no, cancel ); if ( ret == KMessageBox::No ) { writeProjectConfigToFile = false; } else if ( ret == KMessageBox::Cancel ) { return QUrl(); } // else fall through and write new file } else { writeProjectConfigToFile = false; } } if (writeProjectConfigToFile) { if (!writeProjectSettingsToConfigFile(projectFileUrl, &dlg)) { KMessageBox::error(d->m_core->uiControllerInternal()->defaultMainWindow(), i18n("Unable to create configuration file %1", projectFileUrl.url())); return QUrl(); } } return projectFileUrl; } bool ProjectDialogProvider::userWantsReopen() { Q_ASSERT(d); return (KMessageBox::questionYesNo( d->m_core->uiControllerInternal()->defaultMainWindow(), i18n( "Reopen the current project?" ) ) == KMessageBox::No) ? false : true; } void ProjectController::setDialogProvider(IProjectDialogProvider* dialog) { Q_ASSERT(d->dialog); delete d->dialog; d->dialog = dialog; } ProjectController::ProjectController( Core* core ) : IProjectController( core ), d( new ProjectControllerPrivate( this ) ) { qRegisterMetaType>(); setObjectName(QStringLiteral("ProjectController")); d->m_core = core; d->model = new ProjectModel(); //NOTE: this is required to be called here, such that the // actions are available when the UI controller gets // initialized *before* the project controller if (Core::self()->setupFlags() != Core::NoUi) { setupActions(); } } void ProjectController::setupActions() { KActionCollection * ac = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction*action; d->m_openProject = action = ac->addAction( QStringLiteral("project_open") ); action->setText(i18nc( "@action", "Open / Import Project..." ) ); action->setToolTip( i18nc( "@info:tooltip", "Open or import project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Open an existing KDevelop 4 project or import " "an existing Project into KDevelop 4. This entry " "allows one to select a KDevelop4 project file " "or an existing directory to open it in KDevelop. " "When opening an existing directory that does " "not yet have a KDevelop4 project file, the file " "will be created." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); connect(action, &QAction::triggered, this, [&] { openProject(); }); d->m_fetchProject = action = ac->addAction( QStringLiteral("project_fetch") ); action->setText(i18nc( "@action", "Fetch Project..." ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("edit-download") ) ); action->setToolTip( i18nc( "@info:tooltip", "Fetch project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Guides the user through the project fetch " "and then imports it into KDevelop 4." ) ); // action->setIcon(QIcon::fromTheme("project-open")); connect( action, &QAction::triggered, this, &ProjectController::fetchProject ); // action = ac->addAction( "project_close" ); // action->setText( i18n( "C&lose Project" ) ); // connect( action, SIGNAL(triggered(bool)), SLOT(closeProject()) ); // action->setToolTip( i18n( "Close project" ) ); // action->setWhatsThis( i18n( "Closes the current project." ) ); // action->setEnabled( false ); d->m_closeProject = action = ac->addAction( QStringLiteral("project_close") ); connect( action, &QAction::triggered, this, [&] { d->closeSelectedProjects(); } ); action->setText( i18nc( "@action", "Close Project(s)" ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("project-development-close") ) ); action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) ); action->setEnabled( false ); d->m_openConfig = action = ac->addAction( QStringLiteral("project_open_config") ); connect( action, &QAction::triggered, this, [&] { d->openProjectConfig(); } ); action->setText( i18n( "Open Configuration..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("configure")) ); action->setEnabled( false ); action = ac->addAction( QStringLiteral("commit_current_project") ); connect( action, &QAction::triggered, this, &ProjectController::commitCurrentProject ); action->setText( i18n( "Commit Current Project..." ) ); action->setIconText( i18n( "Commit..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("svn-commit")) ); connect(d->m_core->uiControllerInternal()->defaultMainWindow(), &MainWindow::areaChanged, this, [&] (Sublime::Area* area) { d->areaChanged(area); }); d->m_core->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(action); KSharedConfig * config = KSharedConfig::openConfig().data(); // KConfigGroup group = config->group( "General Options" ); d->m_recentAction = KStandardAction::openRecent(this, SLOT(openProject(QUrl)), this); ac->addAction( QStringLiteral("project_open_recent"), d->m_recentAction ); d->m_recentAction->setText( i18n( "Open Recent Project" ) ); d->m_recentAction->setWhatsThis( i18nc( "@info:whatsthis", "Opens recently opened project." ) ); d->m_recentAction->loadEntries( KConfigGroup(config, "RecentProjects") ); QAction* openProjectForFileAction = new QAction( this ); ac->addAction(QStringLiteral("project_open_for_file"), openProjectForFileAction); openProjectForFileAction->setText(i18n("Open Project for Current File")); connect( openProjectForFileAction, &QAction::triggered, this, &ProjectController::openProjectForUrlSlot); } ProjectController::~ProjectController() { delete d->model; delete d->dialog; delete d; } void ProjectController::cleanup() { if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } d->m_cleaningUp = true; if( buildSetModel() ) { buildSetModel()->storeToSession( Core::self()->activeSession() ); } closeAllProjects(); } void ProjectController::initialize() { d->buildset = new ProjectBuildSetModel( this ); buildSetModel()->loadFromSession( Core::self()->activeSession() ); connect( this, &ProjectController::projectOpened, d->buildset, &ProjectBuildSetModel::loadFromProject ); connect( this, &ProjectController::projectClosing, d->buildset, &ProjectBuildSetModel::saveToProject ); connect( this, &ProjectController::projectClosed, d->buildset, &ProjectBuildSetModel::projectClosed ); d->selectionModel = new QItemSelectionModel(d->model); loadSettings(false); d->dialog = new ProjectDialogProvider(d); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ProjectController"), this, QDBusConnection::ExportScriptableSlots ); KSharedConfigPtr config = Core::self()->activeSession()->config(); KConfigGroup group = config->group( "General Options" ); QList openProjects = group.readEntry( "Open Projects", QList() ); QMetaObject::invokeMethod(this, "openProjects", Qt::QueuedConnection, Q_ARG(QList, openProjects)); connect( Core::self()->selectionController(), &ISelectionController::selectionChanged, this, [&] (Context* ctx) { d->updateActionStates(ctx); } ); } void ProjectController::openProjects(const QList& projects) { foreach (const QUrl& url, projects) openProject(url); } void ProjectController::loadSettings( bool projectIsLoaded ) { Q_UNUSED(projectIsLoaded) } void ProjectController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); } int ProjectController::projectCount() const { return d->m_projects.count(); } IProject* ProjectController::projectAt( int num ) const { if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() ) return d->m_projects.at( num ); - return 0; + return nullptr; } QList ProjectController::projects() const { return d->m_projects; } void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, KIO::UDSEntryList entries ) { KIO::SimpleJob* job(dynamic_cast(_job)); Q_ASSERT(job); foreach(const KIO::UDSEntry& entry, entries) { if(d->m_foundProjectFile) break; if(!entry.isDir()) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if(name.endsWith(QLatin1String(".kdev4"))) { //We have found a project-file, open it openProject(Path(Path(job->url()), name).toUrl()); d->m_foundProjectFile = true; } } } } void ProjectController::openProjectForUrlSlot(bool) { if(ICore::self()->documentController()->activeDocument()) { QUrl url = ICore::self()->documentController()->activeDocument()->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(!project) { openProjectForUrl(url); }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("Project already open: %1", project->name())); } }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("No active document")); } } void ProjectController::openProjectForUrl(const QUrl& sourceUrl) { Q_ASSERT(!sourceUrl.isRelative()); QUrl dirUrl = sourceUrl.adjusted(QUrl::RemoveFilename); QUrl testAt = dirUrl; d->m_foundProjectFile = false; while(!testAt.path().isEmpty()) { KIO::ListJob* job = KIO::listDir(testAt); connect(job, &KIO::ListJob::entries, this, &ProjectController::eventuallyOpenProjectFile); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); job->exec(); if(d->m_foundProjectFile) { //Fine! We have directly opened the project d->m_foundProjectFile = false; return; } QUrl oldTest = testAt.adjusted(QUrl::RemoveFilename); if(oldTest == testAt) break; } QUrl askForOpen = d->dialog->askProjectConfigLocation(false, dirUrl); if(askForOpen.isValid()) openProject(askForOpen); } void ProjectController::openProject( const QUrl &projectFile ) { QUrl url = projectFile; if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); if ( url.isEmpty() ) { return; } } Q_ASSERT(!url.isRelative()); QList existingSessions; if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(url)) { foreach( const Session* session, Core::self()->sessionController()->sessions()) { if(session->containedProjects().contains(url)) { existingSessions << session; #if 0 ///@todo Think about this! Problem: The session might already contain files, the debugger might be active, etc. //If this session is empty, close it if(Core::self()->sessionController()->activeSession()->description().isEmpty()) { //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); } #endif } } } if ( ! existingSessions.isEmpty() ) { QDialog dialog(Core::self()->uiControllerInternal()->activeMainWindow()); dialog.setWindowTitle(i18n("Project Already Open")); auto mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(new QLabel(i18n("The project you're trying to open is already open in at least one " "other session.
What do you want to do?"))); QGroupBox sessions; sessions.setLayout(new QVBoxLayout); QRadioButton* newSession = new QRadioButton(i18n("Add project to current session")); sessions.layout()->addWidget(newSession); newSession->setChecked(true); foreach ( const Session* session, existingSessions ) { QRadioButton* button = new QRadioButton(i18n("Open session %1", session->description())); button->setProperty("sessionid", session->id().toString()); sessions.layout()->addWidget(button); } sessions.layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); mainLayout->addWidget(&sessions); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Abort); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); mainLayout->addWidget(buttonBox); bool success = dialog.exec(); if (!success) return; foreach ( const QObject* obj, sessions.children() ) { if ( const QRadioButton* button = qobject_cast(obj) ) { QString sessionid = button->property("sessionid").toString(); if ( button->isChecked() && ! sessionid.isEmpty() ) { Core::self()->sessionController()->loadSession(sessionid); return; } } } } if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); } if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::fetchProject() { QUrl url = d->dialog->askProjectConfigLocation(true); if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::projectImportingFinished( IProject* project ) { if( !project ) { qWarning() << "OOOPS: 0-pointer project"; return; } IPlugin *managerPlugin = project->managerPlugin(); QList pluglist; pluglist.append( managerPlugin ); d->m_projectPlugins.insert( project, pluglist ); d->m_projects.append( project ); if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } if (Core::self()->setupFlags() != Core::NoUi) { d->m_recentAction->addUrl( project->projectFile().toUrl() ); KSharedConfig * config = KSharedConfig::openConfig().data(); KConfigGroup recentGroup = config->group("RecentProjects"); d->m_recentAction->saveEntries( recentGroup ); config->sync(); } Q_ASSERT(d->m_currentlyOpening.contains(project->projectFile().toUrl())); d->m_currentlyOpening.removeAll(project->projectFile().toUrl()); emit projectOpened( project ); reparseProject(project); } // helper method for closeProject() void ProjectController::unloadUnusedProjectPlugins(IProject* proj) { QList pluginsForProj = d->m_projectPlugins.value( proj ); d->m_projectPlugins.remove( proj ); QList otherProjectPlugins; Q_FOREACH( const QList& _list, d->m_projectPlugins ) { otherProjectPlugins << _list; } QSet pluginsForProjSet = QSet::fromList( pluginsForProj ); QSet otherPrjPluginsSet = QSet::fromList( otherProjectPlugins ); // loaded - target = tobe unloaded. QSet tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet ); Q_FOREACH( IPlugin* _plugin, tobeRemoved ) { KPluginMetaData _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin ); if( _plugInfo.isValid() ) { QString _plugName = _plugInfo.pluginId(); qCDebug(SHELL) << "about to unloading :" << _plugName; Core::self()->pluginController()->unloadPlugin( _plugName ); } } } // helper method for closeProject() void ProjectController::closeAllOpenedFiles(IProject* proj) { foreach(IDocument* doc, Core::self()->documentController()->openDocuments()) { if (proj->inProject(IndexedString(doc->url()))) { doc->close(); } } } // helper method for closeProject() void ProjectController::initializePluginCleanup(IProject* proj) { // Unloading (and thus deleting) these plugins is not a good idea just yet // as we're being called by the view part and it gets deleted when we unload the plugin(s) // TODO: find a better place to unload connect(proj, &IProject::destroyed, this, [&] { d->unloadAllProjectPlugins(); }); } void ProjectController::takeProject(IProject* proj) { if (!proj) { return; } // loading might have failed d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); d->m_projects.removeAll(proj); emit projectClosing(proj); //Core::self()->saveSettings(); // The project file is being closed. // Now we can save settings for all of the Core // objects including this one!! unloadUnusedProjectPlugins(proj); closeAllOpenedFiles(proj); proj->close(); if (d->m_projects.isEmpty()) { initializePluginCleanup(proj); } if(!d->m_cleaningUp) d->saveListOfOpenedProjects(); emit projectClosed(proj); } void ProjectController::closeProject(IProject* proj) { takeProject(proj); proj->deleteLater(); // be safe when deleting } void ProjectController::closeAllProjects() { foreach (auto project, d->m_projects) { closeProject(project); } } void ProjectController::abortOpeningProject(IProject* proj) { d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); emit projectOpeningAborted(proj); } ProjectModel* ProjectController::projectModel() { return d->model; } IProject* ProjectController::findProjectForUrl( const QUrl& url ) const { if (d->m_projects.isEmpty()) { - return 0; + return nullptr; } ProjectBaseItem* item = d->model->itemForPath(IndexedString(url)); if (item) { return item->project(); } - return 0; + return nullptr; } IProject* ProjectController::findProjectByName( const QString& name ) { Q_FOREACH( IProject* proj, d->m_projects ) { if( proj->name() == name ) { return proj; } } - return 0; + return nullptr; } void ProjectController::configureProject( IProject* project ) { d->projectConfig( project ); } void ProjectController::addProject(IProject* project) { Q_ASSERT(project); if (d->m_projects.contains(project)) { qWarning() << "Project already tracked by this project controller:" << project; return; } // fake-emit signals so listeners are aware of a new project being added emit projectAboutToBeOpened(project); project->setParent(this); d->m_projects.append(project); emit projectOpened(project); } QItemSelectionModel* ProjectController::projectSelectionModel() { return d->selectionModel; } bool ProjectController::isProjectNameUsed( const QString& name ) const { foreach( IProject* p, projects() ) { if( p->name() == name ) { return true; } } return false; } QUrl ProjectController::projectsBaseDirectory() const { KConfigGroup group = ICore::self()->activeSession()->config()->group( "Project Manager" ); return group.readEntry( "Projects Base Directory", QUrl::fromLocalFile( QDir::homePath() + "/projects" ) ); } QString ProjectController::prettyFilePath(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(!project) { // Find a project with the correct base directory at least foreach(IProject* candidateProject, Core::self()->projectController()->projects()) { if(candidateProject->path().toUrl().isParentOf(url)) { project = candidateProject; break; } } } Path parent = Path(url).parent(); QString prefixText; if (project) { if (format == FormatHtml) { prefixText = "" + project->name() + "/"; } else { prefixText = project->name() + ':'; } QString relativePath = project->path().relativePath(parent); if(relativePath.startsWith(QLatin1String("./"))) { relativePath = relativePath.mid(2); } if (!relativePath.isEmpty()) { prefixText += relativePath + '/'; } } else { prefixText = parent.pathOrUrl() + '/'; } return prefixText; } QString ProjectController::prettyFileName(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(project && project->path() == Path(url)) { if (format == FormatHtml) { return "" + project->name() + ""; } else { return project->name(); } } QString prefixText = prettyFilePath( url, format ); if (format == FormatHtml) { return prefixText + "" + url.fileName() + ""; } else { return prefixText + url.fileName(); } } ContextMenuExtension ProjectController::contextMenuExtension ( Context* ctx ) { ContextMenuExtension ext; if ( ctx->type() != Context::ProjectItemContext || !static_cast(ctx)->items().isEmpty() ) { return ext; } ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentAction); return ext; } ProjectBuildSetModel* ProjectController::buildSetModel() { return d->buildset; } ProjectChangesModel* ProjectController::changesModel() { if(!d->m_changesModel) d->m_changesModel=new ProjectChangesModel(this); return d->m_changesModel; } void ProjectController::commitCurrentProject() { IDocument* doc=ICore::self()->documentController()->activeDocument(); if(!doc) return; QUrl url=doc->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { IPlugin* plugin = project->versionControlPlugin(); IBasicVersionControl* vcs=plugin->extension(); if(vcs) { ICore::self()->documentController()->saveAllDocuments(KDevelop::IDocument::Silent); const Path basePath = project->path(); VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(vcs, basePath.toUrl())); bool ret = showVcsDiff(patchSource); if(!ret) { VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } } } QString ProjectController::mapSourceBuild( const QString& path_, bool reverse, bool fallbackRoot ) const { Path path(path_); - IProject* sourceDirProject = 0, *buildDirProject = 0; + IProject* sourceDirProject = nullptr, *buildDirProject = nullptr; Q_FOREACH(IProject* proj, d->m_projects) { if(proj->path().isParentOf(path) || proj->path() == path) sourceDirProject = proj; if(proj->buildSystemManager()) { Path buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem()); if(buildDir.isValid() && (buildDir.isParentOf(path) || buildDir == path)) buildDirProject = proj; } } if(!reverse) { // Map-target is the build directory if(sourceDirProject && sourceDirProject->buildSystemManager()) { // We're in the source, map into the build directory QString relativePath = sourceDirProject->path().relativePath(path); Path build = sourceDirProject->buildSystemManager()->buildDirectory(sourceDirProject->projectItem()); build.addPath(relativePath); while(!QFile::exists(build.path())) build = build.parent(); return build.pathOrUrl(); }else if(buildDirProject && fallbackRoot) { // We're in the build directory, map to the build directory root return buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()).pathOrUrl(); } }else{ // Map-target is the source directory if(buildDirProject) { Path build = buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()); // We're in the source, map into the build directory QString relativePath = build.relativePath(path); Path source = buildDirProject->path(); source.addPath(relativePath); while(!QFile::exists(source.path())) source = source.parent(); return source.pathOrUrl(); }else if(sourceDirProject && fallbackRoot) { // We're in the source directory, map to the root return sourceDirProject->path().pathOrUrl(); } } return QString(); } void ProjectController::reparseProject( IProject* project, bool forceUpdate ) { if (auto job = d->m_parseJobs.value(project)) { job->kill(); } d->m_parseJobs[project] = new KDevelop::ParseProjectJob(project, forceUpdate); ICore::self()->runController()->registerJob(d->m_parseJobs[project]); } } #include "moc_projectcontroller.cpp" diff --git a/shell/projectsourcepage.cpp b/shell/projectsourcepage.cpp index d2a9ed2a3e..a3c18b5e02 100644 --- a/shell/projectsourcepage.cpp +++ b/shell/projectsourcepage.cpp @@ -1,274 +1,274 @@ /*************************************************************************** * Copyright (C) 2010 by Aleix Pol Gonzalez * * * * 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. * * * ***************************************************************************/ #include "projectsourcepage.h" #include "ui_projectsourcepage.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; static const int FROM_FILESYSTEM_SOURCE_INDEX = 0; ProjectSourcePage::ProjectSourcePage(const QUrl& initial, QWidget* parent) : QWidget(parent) { m_ui = new Ui::ProjectSourcePage; m_ui->setupUi(this); m_ui->status->setCloseButtonVisible(false); m_ui->status->setMessageType(KMessageWidget::Error); m_ui->workingDir->setUrl(initial); m_ui->workingDir->setMode(KFile::Directory); m_ui->remoteWidget->setLayout(new QVBoxLayout(m_ui->remoteWidget)); m_ui->sources->addItem(QIcon::fromTheme(QStringLiteral("folder")), i18n("From File System")); - m_plugins.append(0); + m_plugins.append(nullptr); IPluginController* pluginManager = ICore::self()->pluginController(); QList plugins = pluginManager->allPluginsForExtension( QStringLiteral("org.kdevelop.IBasicVersionControl") ); foreach( IPlugin* p, plugins ) { m_plugins.append(p); m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension()->name()); } plugins = pluginManager->allPluginsForExtension( QStringLiteral("org.kdevelop.IProjectProvider") ); foreach( IPlugin* p, plugins ) { m_plugins.append(p); m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension()->name()); } connect(m_ui->workingDir, &KUrlRequester::textChanged, this, &ProjectSourcePage::reevaluateCorrection); connect(m_ui->sources, static_cast(&QComboBox::currentIndexChanged), this, &ProjectSourcePage::setSourceIndex); connect(m_ui->get, &QPushButton::clicked, this, &ProjectSourcePage::checkoutVcsProject); emit isCorrect(false); setSourceIndex(FROM_FILESYSTEM_SOURCE_INDEX); if(!m_plugins.isEmpty()) m_ui->sources->setCurrentIndex(1); } ProjectSourcePage::~ProjectSourcePage() { delete m_ui; } void ProjectSourcePage::setSourceIndex(int index) { - m_locationWidget = 0; - m_providerWidget = 0; + m_locationWidget = nullptr; + m_providerWidget = nullptr; QLayout* remoteWidgetLayout = m_ui->remoteWidget->layout(); QLayoutItem *child; - while ((child = remoteWidgetLayout->takeAt(0)) != 0) { + while ((child = remoteWidgetLayout->takeAt(0)) != nullptr) { delete child->widget(); delete child; } IBasicVersionControl* vcIface = vcsPerIndex(index); IProjectProvider* providerIface; bool found=false; if(vcIface) { found=true; m_locationWidget=vcIface->vcsLocation(m_ui->sourceBox); connect(m_locationWidget, &VcsLocationWidget::changed, this, &ProjectSourcePage::locationChanged); remoteWidgetLayout->addWidget(m_locationWidget); } else { providerIface = providerPerIndex(index); if(providerIface) { found=true; m_providerWidget=providerIface->providerWidget(m_ui->sourceBox); connect(m_providerWidget, &IProjectProviderWidget::changed, this, &ProjectSourcePage::projectChanged); remoteWidgetLayout->addWidget(m_providerWidget); } } reevaluateCorrection(); m_ui->sourceBox->setVisible(found); } IBasicVersionControl* ProjectSourcePage::vcsPerIndex(int index) { IPlugin* p = m_plugins.value(index); if(!p) - return 0; + return nullptr; else return p->extension(); } IProjectProvider* ProjectSourcePage::providerPerIndex(int index) { IPlugin* p = m_plugins.value(index); if(!p) - return 0; + return nullptr; else return p->extension(); } VcsJob* ProjectSourcePage::jobPerCurrent() { QUrl url=m_ui->workingDir->url(); IPlugin* p=m_plugins[m_ui->sources->currentIndex()]; - VcsJob* job=0; + VcsJob* job=nullptr; if(IBasicVersionControl* iface=p->extension()) { Q_ASSERT(iface && m_locationWidget); job=iface->createWorkingCopy(m_locationWidget->location(), url); } else if(m_providerWidget) { job=m_providerWidget->createWorkingCopy(url); } return job; } void ProjectSourcePage::checkoutVcsProject() { QUrl url=m_ui->workingDir->url(); QDir d(url.toLocalFile()); if(!url.isLocalFile() && !d.exists()) { bool corr = d.mkpath(d.path()); if(!corr) { - KMessageBox::error(0, i18n("Could not create the directory: %1", d.path())); + KMessageBox::error(nullptr, i18n("Could not create the directory: %1", d.path())); return; } } VcsJob* job=jobPerCurrent(); if (!job) { return; } m_ui->sources->setEnabled(false); m_ui->sourceBox->setEnabled(false); m_ui->workingDir->setEnabled(false); m_ui->get->setEnabled(false); m_ui->creationProgress->setValue(m_ui->creationProgress->minimum()); connect(job, &VcsJob::result, this, &ProjectSourcePage::projectReceived); // Can't use new signal-slot syntax, KJob::percent is private :/ connect(job, SIGNAL(percent(KJob*,ulong)), SLOT(progressChanged(KJob*,ulong))); connect(job, &VcsJob::infoMessage, this, &ProjectSourcePage::infoMessage); ICore::self()->runController()->registerJob(job); } void ProjectSourcePage::progressChanged(KJob*, unsigned long value) { m_ui->creationProgress->setValue(value); } void ProjectSourcePage::infoMessage(KJob* , const QString& text, const QString& /*rich*/) { m_ui->creationProgress->setFormat(i18nc("Format of the progress bar text. progress and info", "%1 : %p%", text)); } void ProjectSourcePage::projectReceived(KJob* job) { if (job->error()) { m_ui->creationProgress->setValue(0); } else { m_ui->creationProgress->setValue(m_ui->creationProgress->maximum()); } reevaluateCorrection(); m_ui->creationProgress->setFormat(QStringLiteral("%p%")); } void ProjectSourcePage::reevaluateCorrection() { //TODO: Probably we should just ignore remote URL's, I don't think we're ever going //to support checking out to remote directories const QUrl cwd = m_ui->workingDir->url(); const QDir dir = cwd.toLocalFile(); // case where we import a project from local file system if (m_ui->sources->currentIndex() == FROM_FILESYSTEM_SOURCE_INDEX) { emit isCorrect(dir.exists()); return; } // all other cases where remote locations need to be specified bool correct=!cwd.isRelative() && (!cwd.isLocalFile() || QDir(cwd.adjusted(QUrl::RemoveFilename).toLocalFile()).exists()); emit isCorrect(correct && m_ui->creationProgress->value() == m_ui->creationProgress->maximum()); bool validWidget = ((m_locationWidget && m_locationWidget->isCorrect()) || (m_providerWidget && m_providerWidget->isCorrect())); bool validToCheckout = correct && validWidget; //To checkout, if it exists, it should be an empty dir if (validToCheckout && cwd.isLocalFile() && dir.exists()) { validToCheckout = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty(); } m_ui->get->setEnabled(validToCheckout); m_ui->creationProgress->setEnabled(validToCheckout); if(!correct) setStatus(i18n("You need to specify a valid or nonexistent directory to check out a project")); else if(!m_ui->get->isEnabled() && m_ui->workingDir->isEnabled()) setStatus(i18n("You need to specify a valid project location")); else clearStatus(); } void ProjectSourcePage::locationChanged() { Q_ASSERT(m_locationWidget); if(m_locationWidget->isCorrect()) { QString currentUrl = m_ui->workingDir->text(); currentUrl = currentUrl.left(currentUrl.lastIndexOf('/')+1); QUrl current = QUrl::fromUserInput(currentUrl + m_locationWidget->projectName()); m_ui->workingDir->setUrl(current); } else reevaluateCorrection(); } void ProjectSourcePage::projectChanged(const QString& name) { Q_ASSERT(m_providerWidget); QString currentUrl = m_ui->workingDir->text(); currentUrl = currentUrl.left(currentUrl.lastIndexOf('/')+1); QUrl current = QUrl::fromUserInput(currentUrl + name); m_ui->workingDir->setUrl(current); } void ProjectSourcePage::setStatus(const QString& message) { m_ui->status->setText(message); m_ui->status->animatedShow(); } void ProjectSourcePage::clearStatus() { m_ui->status->animatedHide(); } QUrl ProjectSourcePage::workingDir() const { return m_ui->workingDir->url(); } diff --git a/shell/runcontroller.cpp b/shell/runcontroller.cpp index 1a411da180..565e179327 100644 --- a/shell/runcontroller.cpp +++ b/shell/runcontroller.cpp @@ -1,1056 +1,1056 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "unitylauncher.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { namespace Strings { QString LaunchConfigurationsGroup() { return QStringLiteral("Launch"); } QString LaunchConfigurationsListEntry() { return QStringLiteral("Launch Configurations"); } QString CurrentLaunchConfigProjectEntry() { return QStringLiteral("Current Launch Config Project"); } QString CurrentLaunchConfigNameEntry() { return QStringLiteral("Current Launch Config GroupName"); } QString ConfiguredFromProjectItemEntry() { return QStringLiteral("Configured from ProjectItem"); } } } typedef QPair Target; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("tools-report-bug")); } QString id() const override { return QStringLiteral("debug"); } QString name() const override { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); } QString id() const override { return QStringLiteral("profile"); } QString name() const override { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); } QString id() const override { return QStringLiteral("execute"); } QString name() const override { return i18n("Execute"); } }; class RunController::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QSignalMapper* launchChangeMapper; QSignalMapper* launchAsMapper; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; UnityLauncher* unityLauncher; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QLatin1String("") ); grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() ); grp.sync(); } } void configureLaunches() { LaunchConfigurationDialog dlg; dlg.exec(); } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { - ILauncher* launcher = 0; + ILauncher* launcher = nullptr; foreach (ILauncher *l, type->launchers()) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } if (launcher) { QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); - ILaunchConfiguration* ilaunch = 0; + ILaunchConfiguration* ilaunch = nullptr; foreach (LaunchConfiguration *l, launchConfigurations) { QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; ilaunch = l; break; } } if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); LaunchConfiguration* launch = dynamic_cast( ilaunch ); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" ); QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" ); - LaunchConfiguration* l = 0; + LaunchConfiguration* l = nullptr; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { foreach( QAction* a, currentTargetAction->actions() ) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().at(0)->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(qVariantFromValue(l)); } void readLaunchConfigs( KSharedConfigPtr cfg, IProject* prj ) { KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup()); QStringList configs = group.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); foreach( const QString& cfg, configs ) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qWarning() << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } - return 0; + return nullptr; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d(new RunControllerPrivate) { setObjectName(QStringLiteral("RunController")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"), this, QDBusConnection::ExportScriptableSlots); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed - d->currentTargetAction = 0; + d->currentTargetAction = nullptr; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->launchChangeMapper = new QSignalMapper( this ); - d->launchAsMapper = 0; - d->contextItem = 0; - d->executeMode = 0; - d->debugMode = 0; - d->profileMode = 0; + d->launchAsMapper = nullptr; + d->contextItem = nullptr; + d->executeMode = nullptr; + d->debugMode = nullptr; + d->profileMode = nullptr; d->unityLauncher = new UnityLauncher(this); d->unityLauncher->setLauncherId(KAboutData::applicationData().desktopFileName()); if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() { delete d; } void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { delete d->executeMode; - d->executeMode = 0; + d->executeMode = nullptr; delete d->profileMode; - d->profileMode = 0; + d->profileMode = nullptr; delete d->debugMode; - d->debugMode = 0; + d->debugMode = nullptr; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); - d->readLaunchConfigs( Core::self()->activeSession()->config(), 0 ); + d->readLaunchConfigs( Core::self()->activeSession()->config(), nullptr ); foreach (IProject* project, Core::self()->projectController()->projects()) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; - return 0; + return nullptr; } LaunchConfiguration *run = dynamic_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { KMessageBox::error( qApp->activeWindow(), i18n("The current launch configuration does not support the '%1' mode.", runMode), QLatin1String("")); - return 0; + return nullptr; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); action = new QAction(i18n("Configure Launches..."), this); ac->addAction(QStringLiteral("configure_launches"), action); action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, [&] { d->configureLaunches(); }); d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction(QStringLiteral("run_execute"), d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18n("Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction(QStringLiteral("run_debug"), d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape"))); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_all"), action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action); action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_menu"), action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { if (!d->currentTargetAction) return; foreach (QAction* action, d->currentTargetAction->actions()) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().at(0)->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( QStringLiteral("debug") ); } void RunController::slotProfile() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( QStringLiteral("profile") ); } void RunController::slotExecute() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( QStringLiteral("execute") ); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); - return 0; + return nullptr; } void KDevelop::RunController::registerJob(KJob * job) { if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qWarning() << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { - QAction* stopJobAction = 0; + QAction* stopJobAction = nullptr; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", job->staticMetaObject.className()) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); // FIXME percent is a private signal and thus we cannot use new connext syntax connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(jobPercentChanged())); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { bool running = false; int jobCount = 0; int totalProgress = 0; for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) { KJob *job = it.key(); if (!job->isSuspended()) { running = true; ++jobCount; totalProgress += job->percent(); } } d->unityLauncher->setProgressVisible(running); if (jobCount > 0) { d->unityLauncher->setProgress((totalProgress + 1) / jobCount); } else { d->unityLauncher->setProgress(0); } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 // foreach already iterates over a copy foreach (KJob* job, d->jobs.keys()) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qWarning() << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { QAction* action = dynamic_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { ///WARNING: do *not* use a nested event loop here, it might cause /// random crashes later on, see e.g.: /// https://bugs.kde.org/show_bug.cgi?id=309811 auto dialog = new QDialog(qApp->activeWindow()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Process Error")); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, dialog); KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Warning, job->errorString(), QStringList(), - QString(), 0, KMessageBox::NoExec); + QString(), nullptr, KMessageBox::NoExec); dialog->show(); } } } void RunController::jobDestroyed(QObject* job) { KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qWarning() << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void RunController::jobPercentChanged() { checkState(); } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; foreach (LaunchConfiguration *config, launchConfigurationsInternal()) configs << config; return configs; } QList RunController::launchConfigurationsInternal() const { return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { foreach( LaunchConfiguration* l, d->launchConfigurations ) { if( l->type() == type ) { removeLaunchConfigurationInternal( l ); } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { QMap::iterator it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } - return 0; + return nullptr; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launcherGroup.sync(); removeLaunchConfigurationInternal( l ); } void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { auto dl = defaultLaunch(); if( !dl ) { qWarning() << "no default launch!"; return; } execute( runMode, dl ); } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { foreach(ILaunchConfiguration* config, Core::self()->runControllerInternal()->launchConfigurations()) { if(config->name()==name) return true; } return false; } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); uint num = 0; QString baseName = QStringLiteral("Launch Configuration"); while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launchGroup.sync(); LaunchConfiguration* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { return d->delegate; } ContextMenuExtension RunController::contextMenuExtension ( Context* ctx ) { delete d->launchAsMapper; d->launchAsMapper = new QSignalMapper( this ); connect( d->launchAsMapper, static_cast(&QSignalMapper::mapped), this, [&] (int id) { d->launchAs(id); } ); d->launchAsInfo.clear(); - d->contextItem = 0; + d->contextItem = nullptr; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { KDevelop::ProjectItemContext* prjctx = dynamic_cast( ctx ); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; foreach( ILaunchMode* mode, d->launchModes ) { KActionMenu* menu = new KActionMenu( i18n("%1 As...", mode->name() ), this ); foreach( LaunchConfigurationType* type, launchConfigurationTypes() ) { bool hasLauncher = false; foreach( ILauncher* launcher, type->launchers() ) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); QAction* act = new QAction( d->launchAsMapper ); act->setText( type->name() ); qCDebug(SHELL) << "Setting up mapping for:" << i << "for action" << act->text() << "in mode" << mode->name(); d->launchAsMapper->setMapping( act, i ); connect( act, &QAction::triggered, d->launchAsMapper, static_cast(&QSignalMapper::map) ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/shell/session.cpp b/shell/session.cpp index 3700061cf9..21448b4e58 100644 --- a/shell/session.cpp +++ b/shell/session.cpp @@ -1,235 +1,235 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "session.h" #include #include #include #include #include #include #include #include #include #include "core.h" #include "sessioncontroller.h" #include #include #include namespace KDevelop { const QString Session::cfgSessionNameEntry = QStringLiteral("SessionName"); const QString Session::cfgSessionDescriptionEntry = QStringLiteral("SessionPrettyContents"); const QString Session::cfgSessionProjectsEntry = QStringLiteral("Open Projects"); const QString Session::cfgSessionOptionsGroup = QStringLiteral("General Options"); class SessionPrivate { public: SessionInfo info; Session* q; bool isTemporary; QUrl pluginArea( const IPlugin* plugin ) { QString name = Core::self()->pluginController()->pluginInfo(plugin).pluginId(); QUrl url = QUrl::fromLocalFile( info.path + '/' + name ); if( !QFile::exists( url.toLocalFile() ) ) { QDir( info.path ).mkdir( name ); } return url; } SessionPrivate( Session* session, const QString& id ) : info( Session::parse( id, true ) ) , q( session ) , isTemporary( false ) { } void updateDescription() { buildDescription( info ); emit q->sessionUpdated( q ); } static QString generatePrettyContents( const SessionInfo& info ); static QString generateDescription( const SessionInfo& info ); static void buildDescription( SessionInfo& info ); }; Session::Session( const QString& id, QObject* parent ) : ISession(parent) , d( new SessionPrivate( this, id ) ) { } Session::~Session() { delete d; } QString Session::name() const { return d->info.name; } QList Session::containedProjects() const { return d->info.projects; } QString Session::description() const { return d->info.description; } QUrl Session::pluginDataArea( const IPlugin* p ) { return d->pluginArea( p ); } KSharedConfigPtr Session::config() { return d->info.config; } QUuid Session::id() const { return d->info.uuid; } void Session::setName( const QString& newname ) { d->info.name = newname; d->info.config->group( QString() ).writeEntry( cfgSessionNameEntry, newname ); d->updateDescription(); } void Session::setContainedProjects( const QList& projects ) { d->info.projects = projects; d->info.config->group( cfgSessionOptionsGroup ).writeEntry( cfgSessionProjectsEntry, projects ); d->updateDescription(); } void Session::setTemporary(bool temp) { d->isTemporary = temp; } bool Session::isTemporary() const { return d->isTemporary; } QString Session::path() const { return d->info.path; } QString SessionPrivate::generatePrettyContents( const SessionInfo& info ) { if( info.projects.isEmpty() ) return QString(); QStringList projectNames; projectNames.reserve( info.projects.size() ); foreach( const QUrl& url, info.projects ) { - IProject* project = 0; + IProject* project = nullptr; if( ICore::self() && ICore::self()->projectController() ) { project = ICore::self()->projectController()->findProjectForUrl( url ); } if( project ) { projectNames << project->name(); } else { QString projectName = url.fileName(); projectName.remove( QRegExp( "\\.kdev4$", Qt::CaseInsensitive ) ); projectNames << projectName; } } if( projectNames.isEmpty() ) { return i18n("(no projects)"); } else { return projectNames.join( QStringLiteral(", ") ); } } QString SessionPrivate::generateDescription( const SessionInfo& info ) { QString prettyContentsFormatted = generatePrettyContents( info ); QString description; if( info.name.isEmpty() ) { description = prettyContentsFormatted; } else { description = info.name + ": " + prettyContentsFormatted; } return description; } void SessionPrivate::buildDescription( SessionInfo& info ) { QString description = generateDescription( info ); info.description = description; info.config->group( QString() ).writeEntry( Session::cfgSessionDescriptionEntry, description ); info.config->sync(); } SessionInfo Session::parse( const QString& id, bool mkdir ) { SessionInfo ret; QString sessionPath = SessionController::sessionDirectory(id); QDir sessionDir( sessionPath ); if( !sessionDir.exists() ) { if( mkdir ) { sessionDir.mkpath(sessionPath); Q_ASSERT( sessionDir.exists() ); } else { return ret; } } ret.uuid = id; ret.path = sessionPath; ret.config = KSharedConfig::openConfig( sessionPath + "/sessionrc" ); KConfigGroup cfgRootGroup = ret.config->group( QString() ); KConfigGroup cfgOptionsGroup = ret.config->group( cfgSessionOptionsGroup ); ret.name = cfgRootGroup.readEntry( cfgSessionNameEntry, QString() ); ret.projects = cfgOptionsGroup.readEntry( cfgSessionProjectsEntry, QList() ); SessionPrivate::buildDescription( ret ); return ret; } } diff --git a/shell/sessioncontroller.cpp b/shell/sessioncontroller.cpp index c9bf27498e..a8aab80bb8 100644 --- a/shell/sessioncontroller.cpp +++ b/shell/sessioncontroller.cpp @@ -1,664 +1,664 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessioncontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.h" #include "debug.h" #include #include #include #include #include namespace KDevelop { namespace { int argc = 0; - char** argv = 0; + char** argv = nullptr; }; void SessionController::setArguments(int _argc, char** _argv) { argc = _argc; argv = _argv; } static QStringList standardArguments() { QStringList ret; for(int a = 0; a < argc; ++a) { QString arg = QString::fromLocal8Bit(argv[a]); if(arg.startsWith(QLatin1String("-graphicssystem")) || arg.startsWith(QLatin1String("-style"))) { ret << '-' + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: SessionControllerPrivate( SessionController* s ) : q(s) - , activeSession(0) - , grp(0) + , activeSession(nullptr) + , grp(nullptr) { } ~SessionControllerPrivate() override { } Session* findSessionForName( const QString& name ) const { foreach( Session* s, sessionActions.keys() ) { if( s->name() == name ) return s; } - return 0; + return nullptr; } Session* findSessionForId(QString idString) { QUuid id(idString); foreach( Session* s, sessionActions.keys() ) { if( s->id() == id) return s; } - return 0; + return nullptr; } void newSession() { qsrand(QDateTime::currentDateTimeUtc().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << QStringLiteral("-s") << session->id().toString() << standardArguments()); delete session; #if 0 //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); #endif } void deleteCurrentSession() { int choice = KMessageBox::warningContinueCancel(Core::self()->uiController()->activeMainWindow(), i18n("The current session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?")); if(choice == KMessageBox::Continue) { q->deleteSessionFromDisk(sessionLock); q->emitQuitSession(); } } void renameSession() { bool ok; auto newSessionName = QInputDialog::getText(Core::self()->uiController()->activeMainWindow(), i18n("Rename Session"), i18n("New Session Name:"), QLineEdit::Normal, q->activeSession()->name(), &ok); if (ok) { static_cast(q->activeSession())->setName(newSessionName); } q->updateXmlGuiActionList(); // resort } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << QStringLiteral("-s") << s->id().toString() << standardArguments()); return true; } TryLockSessionResult activateSession( Session* s ) { Q_ASSERT( s ); activeSession = s; TryLockSessionResult result = SessionController::tryLockSession( s->id().toString()); if( !result.lock ) { - activeSession = 0; + activeSession = nullptr; return result; } Q_ASSERT(s->id().toString() == result.lock->id()); sessionLock = result.lock; KConfigGroup grp = KSharedConfig::openConfig()->group( SessionController::cfgSessionGroup() ); grp.writeEntry( SessionController::cfgActiveSessionEntry(), s->id().toString() ); grp.sync(); if (Core::self()->setupFlags() & Core::NoUi) return result; QHash::iterator it = sessionActions.find(s); Q_ASSERT( it != sessionActions.end() ); (*it)->setCheckable(true); (*it)->setChecked(true); for(it = sessionActions.begin(); it != sessionActions.end(); ++it) { if(it.key() != s) (*it)->setCheckable(false); } return result; } void loadSessionFromAction(QAction* action) { auto session = action->data().value(); loadSessionExternally(session); } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { - sessionActions[s] = 0; + sessionActions[s] = nullptr; return; } QAction* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData(QVariant::fromValue(s)); sessionActions[s] = a; q->actionCollection()->addAction( "session_"+s->id().toString(), a ); connect( s, &Session::sessionUpdated, this, &SessionControllerPrivate::sessionUpdated ); sessionUpdated( s ); } SessionController* q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ qApp->applicationName() + "/sessions/"; } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } private slots: void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } }; SessionController::SessionController( QObject *parent ) : QObject( parent ), d(new SessionControllerPrivate(this)) { setObjectName(QStringLiteral("SessionController")); setComponentName(QStringLiteral("kdevsession"), QStringLiteral("KDevSession")); setXMLFile(QStringLiteral("kdevsessionui.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/SessionController"), this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction( QStringLiteral("new_session"), this, SLOT(newSession()) ); action->setText( i18nc("@action:inmenu", "Start New Session") ); action->setToolTip( i18nc("@info:tooltip", "Start a new KDevelop instance with an empty session") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); action = actionCollection()->addAction( QStringLiteral("rename_session"), this, SLOT(renameSession()) ); action->setText( i18n("Rename Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); action = actionCollection()->addAction( QStringLiteral("delete_session"), this, SLOT(deleteCurrentSession()) ); action->setText( i18n("Delete Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action = actionCollection()->addAction( QStringLiteral("quit"), this, SIGNAL(quitSession()) ); action->setText( i18n("Quit") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'Quit KDevelop...' actionCollection()->setDefaultShortcut( action, Qt::CTRL | Qt::Key_Q ); action->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); d->grp = new QActionGroup( this ); connect( d->grp, &QActionGroup::triggered, this, [&] (QAction* a) { d->loadSessionFromAction(a); } ); } SessionController::~SessionController() { delete d; } void SessionController::startNewSession() { d->newSession(); } void SessionController::cleanup() { if (d->activeSession) { Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); if (d->activeSession->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } - d->activeSession = 0; + d->activeSession = nullptr; } d->sessionLock.clear(); qDeleteAll(d->sessionActions); d->sessionActions.clear(); } void SessionController::initialize( const QString& session ) { QDir sessiondir( SessionControllerPrivate::sessionBaseDirectory() ); foreach( const QString& s, sessiondir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot ) ) { QUuid id( s ); if( id.isNull() ) continue; // Only create sessions for directories that represent proper uuid's Session* ses = new Session( id.toString(), this ); //Delete sessions that have no name and are empty if( ses->containedProjects().isEmpty() && ses->name().isEmpty() && (session.isEmpty() || (ses->id().toString() != session && ses->name() != session)) ) { TryLockSessionResult result = tryLockSession(s); if (result.lock) { deleteSessionFromDisk(result.lock); } delete ses; } else { d->addSession( ses ); } } loadDefaultSession( session ); updateXmlGuiActionList(); } ISession* SessionController::activeSession() const { return d->activeSession; } ISessionLock::Ptr SessionController::activeSessionLock() const { return d->sessionLock; } void SessionController::loadSession( const QString& nameOrId ) { d->loadSessionExternally( session( nameOrId ) ); } QList SessionController::sessionNames() const { QStringList l; foreach( const Session* s, d->sessionActions.keys() ) { l << s->name(); } return l; } QList< const KDevelop::Session* > SessionController::sessions() const { QList< const KDevelop::Session* > ret; foreach( const Session* s, d->sessionActions.keys() ) { ret << s; } return ret; } Session* SessionController::createSession( const QString& name ) { Session* s; if(name.startsWith('{')) { s = new Session( QUuid(name).toString() ); }else{ qsrand(QDateTime::currentDateTimeUtc().toTime_t()); s = new Session( QUuid::createUuid().toString() ); s->setName( name ); } d->addSession( s ); updateXmlGuiActionList(); return s; } void SessionController::deleteSession( const ISessionLock::Ptr& lock ) { Session* s = session(lock->id()); QHash::iterator it = d->sessionActions.find(s); Q_ASSERT( it != d->sessionActions.end() ); unplugActionList( QStringLiteral("available_sessions") ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( QStringLiteral("available_sessions"), d->grp->actions() ); } if (s == d->activeSession) { d->activeSession = nullptr; } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); delete s; } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { qCDebug(SHELL) << "Deleting session:" << lock->id(); static_cast(lock.data())->removeFromDisk(); ItemRepositoryRegistry::deleteRepositoryFromDisk( lock ); } void SessionController::loadDefaultSession( const QString& session ) { QString load = session; if (load.isEmpty()) { KConfigGroup grp = KSharedConfig::openConfig()->group( cfgSessionGroup() ); load = grp.readEntry( cfgActiveSessionEntry(), "default" ); } // Iteratively try to load the session, asking user what to do in case of failure // If showForceOpenDialog() returns empty string, stop trying - Session* s = 0; + Session* s = nullptr; do { s = this->session( load ); if( !s ) { s = createSession( load ); } TryLockSessionResult result = d->activateSession( s ); if( result.lock ) { Q_ASSERT(d->activeSession == s); Q_ASSERT(d->sessionLock = result.lock); break; } load = handleLockedSession( s->name(), s->id().toString(), result.runInfo ); } while( !load.isEmpty() ); } Session* SessionController::session( const QString& nameOrId ) const { Session* ret = d->findSessionForName( nameOrId ); if(ret) return ret; return d->findSessionForId( nameOrId ); } QString SessionController::cloneSession( const QString& nameOrid ) { Session* origSession = session( nameOrid ); qsrand(QDateTime::currentDateTimeUtc().toTime_t()); QUuid id = QUuid::createUuid(); auto copyJob = KIO::copy(QUrl::fromLocalFile(sessionDirectory(origSession->id().toString())), QUrl::fromLocalFile(sessionDirectory( id.toString()))); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); copyJob->exec(); Session* newSession = new Session( id.toString() ); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); updateXmlGuiActionList(); return newSession->name(); } void SessionController::updateXmlGuiActionList() { unplugActionList( QStringLiteral("available_sessions") ); if (d->grp) { auto actions = d->grp->actions(); std::sort(actions.begin(), actions.end(), [](const QAction* lhs, const QAction* rhs) { auto s1 = lhs->data().value(); auto s2 = rhs->data().value(); return QString::localeAwareCompare(s1->description(), s2->description()) < 0; }); plugActionList(QStringLiteral("available_sessions"), actions); } } QString SessionController::cfgSessionGroup() { return QStringLiteral("Sessions"); } QString SessionController::cfgActiveSessionEntry() { return QStringLiteral("Active Session ID"); } QList SessionController::availableSessionInfo() { return availableSessionInfos().toList(); } SessionInfos SessionController::availableSessionInfos() { SessionInfos sessionInfos; foreach( const QString& sessionId, QDir( SessionControllerPrivate::sessionBaseDirectory() ).entryList( QDir::AllDirs ) ) { if( !QUuid( sessionId ).isNull() ) { sessionInfos << Session::parse( sessionId ); } } return sessionInfos; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id, bool doLocking) { return SessionLock::tryLockSession(id, doLocking); } bool SessionController::isSessionRunning(const QString& id) { return sessionRunInfo(id).isRunning; } SessionRunInfo SessionController::sessionRunInfo(const QString& id) { return SessionLock::tryLockSession(id, false).runInfo; } QString SessionController::showSessionChooserDialog(QString headerText, bool onlyRunning) { ///FIXME: move this code into sessiondialog.cpp QListView* view = new QListView; QLineEdit* filter = new QLineEdit; filter->setClearButtonEnabled( true ); filter->setPlaceholderText(i18n("Search")); QStandardItemModel* model = new QStandardItemModel(view); QSortFilterProxyModel *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, &QLineEdit::textChanged, proxy, &QSortFilterProxyModel::setFilterFixedString); SessionChooserDialog dialog(view, proxy, filter); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QVBoxLayout layout(dialog.mainWidget()); if(!headerText.isEmpty()) { QLabel* heading = new QLabel(headerText); QFont font = heading->font(); font.setBold(true); heading->setFont(font); layout.addWidget(heading); } model->setColumnCount(3); model->setHeaderData(0, Qt::Horizontal,i18n("Identity")); model->setHeaderData(1, Qt::Horizontal, i18n("Contents")); model->setHeaderData(2, Qt::Horizontal,i18n("State")); view->setModel(proxy); view->setModelColumn(1); QHBoxLayout* filterLayout = new QHBoxLayout(); filterLayout->addWidget(new QLabel(i18n("Filter:"))); filterLayout->addWidget(filter); layout.addLayout(filterLayout); layout.addWidget(view); filter->setFocus(); int row = 0; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); ++row; } model->sort(1); if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); const QModelIndex defaultSessionIndex = model->match(model->index(0, 0), Qt::DisplayRole, defaultSession, 1, Qt::MatchExactly).value(0); view->selectionModel()->setCurrentIndex(proxy->mapFromSource(defaultSessionIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ///@todo We need a way to get a proper size-hint from the view, but unfortunately, that only seems possible after the view was shown. dialog.resize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); if (!selected.isValid()) return QString(); const QString selectedSessionId = selected.sibling(selected.row(), 0).data().toString(); if (selectedSessionId.isEmpty()) { // "Create New Session" item selected, return a fresh UUID qsrand(QDateTime::currentDateTimeUtc().toTime_t()); return QUuid::createUuid().toString(); } return selectedSessionId; } QString SessionController::handleLockedSession( const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo ) { return SessionLock::handleLockedSession(sessionName, sessionId, runInfo); } QString SessionController::sessionDir() { if( !activeSession() ) return QString(); return d->ownSessionDirectory(); } QString SessionController::sessionName() { if(!activeSession()) return QString(); return activeSession()->description(); } } #include "sessioncontroller.moc" #include "moc_sessioncontroller.cpp" diff --git a/shell/sessionlock.cpp b/shell/sessionlock.cpp index 1ab95bd802..6f11b8cb34 100644 --- a/shell/sessionlock.cpp +++ b/shell/sessionlock.cpp @@ -1,225 +1,225 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "sessionlock.h" #include "sessioncontroller.h" #include #include #include #include #include #include using namespace KDevelop; namespace { QString lockFileForSession( const QString& id ) { return SessionController::sessionDirectory( id ) + QLatin1String("/lock"); } QString dBusServiceNameForSession( const QString& id ) { // We remove starting "{" and ending "}" from the string UUID representation // as D-Bus apparently doesn't allow them in service names return QStringLiteral( "org.kdevelop.kdevplatform-lock-" ) + id.midRef( 1, id.size() - 2 ); } /// Force-removes the lock-file. void forceRemoveLockfile(const QString& lockFilename) { if( QFile::exists( lockFilename ) ) { QFile::remove( lockFilename ); } } } TryLockSessionResult SessionLock::tryLockSession(const QString& sessionId, bool doLocking) { ///FIXME: if this is hit, someone tried to lock a non-existing session /// this should be fixed by using a proper data type distinct from /// QString for id's, i.e. QUuid or similar. Q_ASSERT(QFile::exists(SessionController::sessionDirectory( sessionId ))); /* * We've got two locking mechanisms here: D-Bus unique service name (based on the session id) * and a plain lockfile (QLockFile). * The latter is required to get the appname/pid of the locking instance * in case if it's stale/hanging/crashed (to make user know which PID he needs to kill). * D-Bus mechanism is the primary one. * * Since there is a kind of "logic tree", the code is a bit hard. */ const QString service = dBusServiceNameForSession( sessionId ); QDBusConnection connection = QDBusConnection::sessionBus(); QDBusConnectionInterface* connectionInterface = connection.interface(); const QString lockFilename = lockFileForSession( sessionId ); QSharedPointer lockFile(new QLockFile( lockFilename )); const bool haveDBus = connection.isConnected(); const bool canLockDBus = haveDBus && connectionInterface && !connectionInterface->isServiceRegistered( service ); bool lockedDBus = false; // Lock D-Bus if we can and we need to if( doLocking && canLockDBus ) { lockedDBus = connection.registerService( service ); } // Attempt to lock file, despite the possibility to do so and presence of the request (doLocking) // This is required as QLockFile::getLockInfo() works only after QLockFile::lock() is called bool lockResult = lockFile->tryLock(); SessionRunInfo runInfo; if (lockResult) { // Unlock immediately if we shouldn't have locked it if( haveDBus && !lockedDBus ) { lockFile->unlock(); } } else { // If locking failed, retrieve the lock's metadata lockFile->getLockInfo(&runInfo.holderPid, &runInfo.holderHostname, &runInfo.holderApp ); runInfo.isRunning = !haveDBus || !canLockDBus; if( haveDBus && lockedDBus ) { // Since the lock-file is secondary, try to force-lock it if D-Bus locking succeeded forceRemoveLockfile(lockFilename); lockResult = lockFile->tryLock(); Q_ASSERT(lockResult); } } // Set the result by D-Bus status if (doLocking && (haveDBus ? lockedDBus : lockResult)) { return TryLockSessionResult(QSharedPointer(new SessionLock(sessionId, lockFile))); } else { return TryLockSessionResult(runInfo); } } QString SessionLock::id() { return m_sessionId; } SessionLock::SessionLock(const QString& sessionId, const QSharedPointer& lockFile) : m_sessionId(sessionId) , m_lockFile(lockFile) { Q_ASSERT(lockFile->isLocked()); } SessionLock::~SessionLock() { m_lockFile->unlock(); bool unregistered = QDBusConnection::sessionBus().unregisterService( dBusServiceNameForSession(m_sessionId) ); Q_UNUSED(unregistered); } void SessionLock::removeFromDisk() { Q_ASSERT(m_lockFile->isLocked()); // unlock first to prevent warnings: "Could not remove our own lock file ..." m_lockFile->unlock(); QDir(SessionController::sessionDirectory(m_sessionId)).removeRecursively(); } QString SessionLock::handleLockedSession(const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo) { if( !runInfo.isRunning ) { return sessionId; } // try to make the locked session active { // The timeout for "ensureVisible" call // Leave it sufficiently low to avoid waiting for hung instances. static const int timeout_ms = 1000; QDBusMessage message = QDBusMessage::createMethodCall( dBusServiceNameForSession(sessionId), QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"), QStringLiteral("ensureVisible") ); QDBusMessage reply = QDBusConnection::sessionBus().call( message, QDBus::Block, timeout_ms ); if( reply.type() == QDBusMessage::ReplyMessage ) { QTextStream out(stdout); out << i18nc( "@info:shell", "made running %1 instance (PID: %2) visible", runInfo.holderApp, runInfo.holderPid ) << endl; return QString(); } else { qWarning() << i18nc("@info:shell", "running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid); } } // otherwise ask the user whether we should retry QString problemDescription = i18nc("@info", "The given application did not respond to a DBUS call, " "it may have crashed or is hanging."); QString problemHeader; if( runInfo.holderPid != -1 ) { problemHeader = i18nc("@info", "Failed to lock the session %1, " "already locked by %2 on %3 (PID %4).", sessionName, runInfo.holderApp, runInfo.holderHostname, runInfo.holderPid); } else { problemHeader = i18nc("@info", "Failed to lock the session %1 (lock-file unavailable).", sessionName); } QString problemResolution = i18nc("@info", "

Please, close the offending application instance " "or choose another session to launch.

"); QString errmsg = "

" + problemHeader + "
" + problemDescription + "

" + problemResolution; KGuiItem retry = KStandardGuiItem::cont(); retry.setText(i18nc("@action:button", "Retry startup")); KGuiItem choose = KStandardGuiItem::configure(); choose.setText(i18nc("@action:button", "Choose another session")); KGuiItem cancel = KStandardGuiItem::quit(); - int ret = KMessageBox::warningYesNoCancel(0, errmsg, i18nc("@title:window", "Failed to Lock Session %1", sessionName), + int ret = KMessageBox::warningYesNoCancel(nullptr, errmsg, i18nc("@title:window", "Failed to Lock Session %1", sessionName), retry, choose, cancel); switch( ret ) { case KMessageBox::Yes: return sessionId; break; case KMessageBox::No: { QString errmsg = i18nc("@info", "The session %1 is already active in another running instance.", sessionName); return SessionController::showSessionChooserDialog(errmsg); break; } case KMessageBox::Cancel: default: break; } return QString(); } diff --git a/shell/settings/sourceformattersettings.cpp b/shell/settings/sourceformattersettings.cpp index 42f9260de2..66887dc8e5 100644 --- a/shell/settings/sourceformattersettings.cpp +++ b/shell/settings/sourceformattersettings.cpp @@ -1,540 +1,540 @@ /* This file is part of KDevelop * Copyright (C) 2008 Cédric Pasteur 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sourceformattersettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "editstyledialog.h" #include "../debug.h" #define STYLE_ROLE (Qt::UserRole+1) using KDevelop::Core; using KDevelop::ISourceFormatter; using KDevelop::SourceFormatterStyle; using KDevelop::SourceFormatterController; using KDevelop::SourceFormatter; namespace { namespace Strings { QString userStylePrefix() { return QStringLiteral("User"); } } } LanguageSettings::LanguageSettings() - : selectedFormatter(0), selectedStyle(0) { + : selectedFormatter(nullptr), selectedStyle(nullptr) { } SourceFormatterSettings::SourceFormatterSettings(QWidget* parent) : KDevelop::ConfigPage(nullptr, nullptr, parent) { setupUi(this); connect( cbLanguages, static_cast(&KComboBox::currentIndexChanged), this, &SourceFormatterSettings::selectLanguage ); connect( cbFormatters, static_cast(&KComboBox::currentIndexChanged), this, &SourceFormatterSettings::selectFormatter ); connect( chkKateModelines, &QCheckBox::toggled, this, &SourceFormatterSettings::somethingChanged ); connect( chkKateOverrideIndentation, &QCheckBox::toggled, this, &SourceFormatterSettings::somethingChanged ); connect( styleList, &QListWidget::currentRowChanged, this, &SourceFormatterSettings::selectStyle ); connect( btnDelStyle, &QPushButton::clicked, this, &SourceFormatterSettings::deleteStyle ); connect( btnNewStyle, &QPushButton::clicked, this, &SourceFormatterSettings::newStyle ); connect( btnEditStyle, &QPushButton::clicked, this, &SourceFormatterSettings::editStyle ); connect( styleList, &QListWidget::itemChanged, this, &SourceFormatterSettings::styleNameChanged ); m_document = KTextEditor::Editor::instance()->createDocument(this); m_document->setReadWrite(false); m_view = m_document->createView(textEditor); m_view->setStatusBarEnabled(false); QVBoxLayout *layout2 = new QVBoxLayout(textEditor); layout2->addWidget(m_view); textEditor->setLayout(layout2); m_view->show(); KTextEditor::ConfigInterface *iface = qobject_cast(m_view); if (iface) { iface->setConfigValue(QStringLiteral("dynamic-word-wrap"), false); iface->setConfigValue(QStringLiteral("icon-bar"), false); } } SourceFormatterSettings::~SourceFormatterSettings() { qDeleteAll(formatters); } void selectAvailableStyle(LanguageSettings& lang) { Q_ASSERT(!lang.selectedFormatter->styles.empty()); lang.selectedStyle = *lang.selectedFormatter->styles.begin(); } void SourceFormatterSettings::reset() { SourceFormatterController* fmtctrl = Core::self()->sourceFormatterControllerInternal(); QList plugins = KDevelop::ICore::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") ); foreach( KDevelop::IPlugin* plugin, plugins ) { KDevelop::ISourceFormatter* ifmt = plugin->extension(); auto info = KDevelop::Core::self()->pluginControllerInternal()->pluginInfo( plugin ); KDevelop::SourceFormatter* formatter; FormatterMap::const_iterator iter = formatters.constFind(ifmt->name()); if (iter == formatters.constEnd()) { formatter = fmtctrl->createFormatterForPlugin(ifmt); formatters[ifmt->name()] = formatter; } else { formatter = iter.value(); } foreach ( const SourceFormatterStyle* style, formatter->styles ) { foreach ( const SourceFormatterStyle::MimeHighlightPair& item, style->mimeTypes() ) { QMimeType mime = QMimeDatabase().mimeTypeForName(item.mimeType); if (!mime.isValid()) { qWarning() << "plugin" << info.name() << "supports unknown mimetype entry" << item.mimeType; continue; } QString languageName = item.highlightMode; LanguageSettings& l = languages[languageName]; l.mimetypes.append(mime); l.formatters.insert( formatter ); } } } // Sort the languages, preferring firstly active, then loaded languages QList sortedLanguages; foreach(const auto language, KDevelop::ICore::self()->languageController()->activeLanguages() + KDevelop::ICore::self()->languageController()->loadedLanguages()) { if( languages.contains( language->name() ) && !sortedLanguages.contains(language->name()) ) { sortedLanguages.push_back( language->name() ); } } foreach( const QString& name, languages.keys() ) if( !sortedLanguages.contains( name ) ) sortedLanguages.push_back( name ); foreach( const QString& name, sortedLanguages ) { // Pick the first appropriate mimetype for this language KConfigGroup grp = fmtctrl->sessionConfig(); LanguageSettings& l = languages[name]; const QList mimetypes = l.mimetypes; foreach (const QMimeType& mimetype, mimetypes) { QStringList formatterAndStyleName = grp.readEntry(mimetype.name(), QString()).split(QStringLiteral("||"), QString::KeepEmptyParts); FormatterMap::const_iterator formatterIter = formatters.constFind(formatterAndStyleName.first()); if (formatterIter == formatters.constEnd()) { qCDebug(SHELL) << "Reference to unknown formatter" << formatterAndStyleName.first(); Q_ASSERT(!l.formatters.empty()); // otherwise there should be no entry for 'name' l.selectedFormatter = *l.formatters.begin(); selectAvailableStyle(l); } else { l.selectedFormatter = formatterIter.value(); SourceFormatter::StyleMap::const_iterator styleIter = l.selectedFormatter->styles.constFind(formatterAndStyleName.at( 1 )); if (styleIter == l.selectedFormatter->styles.constEnd()) { qCDebug(SHELL) << "No style" << formatterAndStyleName.at( 1 ) << "found for formatter" << formatterAndStyleName.first(); selectAvailableStyle(l); } else { l.selectedStyle = styleIter.value(); } } break; } if (!l.selectedFormatter) { Q_ASSERT(!l.formatters.empty()); l.selectedFormatter = *l.formatters.begin(); } if (!l.selectedStyle) { selectAvailableStyle(l); } } bool b = blockSignals( true ); cbLanguages->blockSignals( !b ); cbFormatters->blockSignals( !b ); styleList->blockSignals( !b ); chkKateModelines->blockSignals( !b ); chkKateOverrideIndentation->blockSignals( !b ); cbLanguages->clear(); cbFormatters->clear(); styleList->clear(); chkKateModelines->setChecked( fmtctrl->sessionConfig().readEntry( SourceFormatterController::kateModeLineConfigKey(), false ) ); chkKateOverrideIndentation->setChecked( fmtctrl->sessionConfig().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), false ) ); foreach( const QString& name, sortedLanguages ) { cbLanguages->addItem( name ); } if( cbLanguages->count() == 0 ) { cbLanguages->setEnabled( false ); selectLanguage( -1 ); } else { cbLanguages->setCurrentIndex( 0 ); selectLanguage( 0 ); } updatePreview(); blockSignals( b ); cbLanguages->blockSignals( b ); cbFormatters->blockSignals( b ); styleList->blockSignals( b ); chkKateModelines->blockSignals( b ); chkKateOverrideIndentation->blockSignals( b ); } void SourceFormatterSettings::apply() { KConfigGroup globalConfig = Core::self()->sourceFormatterControllerInternal()->globalConfig(); foreach( SourceFormatter* fmt, formatters ) { KConfigGroup fmtgrp = globalConfig.group( fmt->formatter->name() ); // delete all styles so we don't leave any behind when all user styles are deleted foreach( const QString& subgrp, fmtgrp.groupList() ) { if( subgrp.startsWith( Strings::userStylePrefix() ) ) { fmtgrp.deleteGroup( subgrp ); } } foreach( const SourceFormatterStyle* style, fmt->styles ) { if( style->name().startsWith( Strings::userStylePrefix() ) ) { KConfigGroup stylegrp = fmtgrp.group( style->name() ); stylegrp.writeEntry( SourceFormatterController::styleCaptionKey(), style->caption() ); stylegrp.writeEntry( SourceFormatterController::styleContentKey(), style->content() ); stylegrp.writeEntry( SourceFormatterController::styleMimeTypesKey(), style->mimeTypesVariant() ); stylegrp.writeEntry( SourceFormatterController::styleSampleKey(), style->overrideSample() ); } } } KConfigGroup sessionConfig = Core::self()->sourceFormatterControllerInternal()->sessionConfig(); for ( LanguageMap::const_iterator iter = languages.constBegin(); iter != languages.constEnd(); ++iter ) { foreach(const QMimeType& mime, iter.value().mimetypes) { sessionConfig.writeEntry(mime.name(), QStringLiteral("%1||%2").arg(iter.value().selectedFormatter->formatter->name(), iter.value().selectedStyle->name())); } } sessionConfig.writeEntry( SourceFormatterController::kateModeLineConfigKey(), chkKateModelines->isChecked() ); sessionConfig.writeEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), chkKateOverrideIndentation->isChecked() ); sessionConfig.sync(); globalConfig.sync(); Core::self()->sourceFormatterControllerInternal()->settingsChanged(); } void SourceFormatterSettings::defaults() { // do nothing } void SourceFormatterSettings::enableStyleButtons() { bool userEntry = styleList->currentItem() && styleList->currentItem()->data( STYLE_ROLE ).toString().startsWith( Strings::userStylePrefix() ); QString languageName = cbLanguages->currentText(); QMap< QString, LanguageSettings >::const_iterator it = languages.constFind(languageName); bool hasEditWidget = false; if (it != languages.constEnd()) { const LanguageSettings& l = it.value(); Q_ASSERT(l.selectedFormatter); ISourceFormatter* fmt = l.selectedFormatter->formatter; hasEditWidget = ( fmt && fmt->editStyleWidget( l.mimetypes.first() ) ); } btnDelStyle->setEnabled( userEntry ); btnEditStyle->setEnabled( userEntry && hasEditWidget ); btnNewStyle->setEnabled( cbFormatters->currentIndex() >= 0 && hasEditWidget ); } void SourceFormatterSettings::selectLanguage( int idx ) { cbFormatters->clear(); if( idx < 0 ) { cbFormatters->setEnabled( false ); selectFormatter( -1 ); return; } cbFormatters->setEnabled( true ); bool b = cbFormatters->blockSignals( true ); LanguageSettings& l = languages[cbLanguages->itemText( idx )]; foreach( const SourceFormatter* fmt, l.formatters ) { cbFormatters->addItem( fmt->formatter->caption(), fmt->formatter->name() ); } cbFormatters->setCurrentIndex(cbFormatters->findData(l.selectedFormatter->formatter->name())); cbFormatters->blockSignals(b); selectFormatter( cbFormatters->currentIndex() ); emit changed(); } void SourceFormatterSettings::selectFormatter( int idx ) { styleList->clear(); if( idx < 0 ) { styleList->setEnabled( false ); enableStyleButtons(); return; } styleList->setEnabled( true ); LanguageSettings& l = languages[ cbLanguages->currentText() ]; Q_ASSERT( idx < l.formatters.size() ); FormatterMap::const_iterator formatterIter = formatters.constFind(cbFormatters->itemData( idx ).toString()); Q_ASSERT( formatterIter != formatters.constEnd() ); Q_ASSERT( l.formatters.contains(formatterIter.value()) ); if (l.selectedFormatter != formatterIter.value()) { l.selectedFormatter = formatterIter.value(); - l.selectedStyle = 0; // will hold 0 until a style is picked + l.selectedStyle = nullptr; // will hold 0 until a style is picked } foreach( const SourceFormatterStyle* style, formatterIter.value()->styles ) { if ( ! style->supportsLanguage(cbLanguages->currentText())) { // do not list items which do not support the selected language continue; } QListWidgetItem* item = addStyle( *style ); if (style == l.selectedStyle) { styleList->setCurrentItem(item); } } - if (l.selectedStyle == 0) { + if (l.selectedStyle == nullptr) { styleList->setCurrentRow(0); } enableStyleButtons(); emit changed(); } void SourceFormatterSettings::selectStyle( int row ) { if( row < 0 ) { enableStyleButtons(); return; } styleList->setCurrentRow( row ); LanguageSettings& l = languages[ cbLanguages->currentText() ]; l.selectedStyle = l.selectedFormatter->styles[styleList->item( row )->data( STYLE_ROLE ).toString()]; enableStyleButtons(); updatePreview(); emit changed(); } void SourceFormatterSettings::deleteStyle() { Q_ASSERT( styleList->currentRow() >= 0 ); QListWidgetItem* item = styleList->currentItem(); LanguageSettings& l = languages[ cbLanguages->currentText() ]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatter::StyleMap::iterator styleIter = fmt->styles.find(item->data( STYLE_ROLE ).toString()); QStringList otherLanguageNames; QList otherlanguages; for ( LanguageMap::iterator languageIter = languages.begin(); languageIter != languages.end(); ++languageIter ) { if ( &languageIter.value() != &l && languageIter.value().selectedStyle == styleIter.value() ) { otherLanguageNames.append(languageIter.key()); otherlanguages.append(&languageIter.value()); } } if (!otherLanguageNames.empty() && KMessageBox::warningContinueCancel(this, i18n("The style %1 is also used for the following languages:\n%2.\nAre you sure you want to delete it?", styleIter.value()->caption(), otherLanguageNames.join(QStringLiteral("\n"))), i18n("Style being deleted")) != KMessageBox::Continue) { return; } styleList->takeItem( styleList->currentRow() ); fmt->styles.erase(styleIter); delete item; selectStyle( styleList->count() > 0 ? 0 : -1 ); foreach (LanguageSettings* lang, otherlanguages) { selectAvailableStyle(*lang); } updatePreview(); emit changed(); } void SourceFormatterSettings::editStyle() { QString language = cbLanguages->currentText(); Q_ASSERT( languages.contains( language ) ); LanguageSettings& l = languages[ language ]; SourceFormatter* fmt = l.selectedFormatter; QMimeType mimetype = l.mimetypes.first(); - if( fmt->formatter->editStyleWidget( mimetype ) != 0 ) { + if( fmt->formatter->editStyleWidget( mimetype ) != nullptr ) { EditStyleDialog dlg( fmt->formatter, mimetype, *l.selectedStyle, this ); if( dlg.exec() == QDialog::Accepted ) { l.selectedStyle->setContent(dlg.content()); } updatePreview(); emit changed(); } } void SourceFormatterSettings::newStyle() { QListWidgetItem* item = styleList->currentItem(); LanguageSettings& l = languages[ cbLanguages->currentText() ]; SourceFormatter* fmt = l.selectedFormatter; int idx = 0; for( int i = 0; i < styleList->count(); i++ ) { QString name = styleList->item( i )->data( STYLE_ROLE ).toString(); if( name.startsWith( Strings::userStylePrefix() ) && name.midRef( Strings::userStylePrefix().length() ).toInt() >= idx ) { idx = name.midRef( Strings::userStylePrefix().length() ).toInt(); } } // Increase number for next style idx++; SourceFormatterStyle* s = new SourceFormatterStyle( QStringLiteral( "%1%2" ).arg( Strings::userStylePrefix() ).arg( idx ) ); if( item ) { SourceFormatterStyle* existstyle = fmt->styles[ item->data( STYLE_ROLE ).toString() ]; s->setCaption( i18n( "New %1", existstyle->caption() ) ); s->copyDataFrom( existstyle ); } else { s->setCaption( i18n( "New Style" ) ); } fmt->styles[ s->name() ] = s; QListWidgetItem* newitem = addStyle( *s ); selectStyle( styleList->row( newitem ) ); styleList->editItem( newitem ); emit changed(); } void SourceFormatterSettings::styleNameChanged( QListWidgetItem* item ) { if ( !item->isSelected() ) { return; } LanguageSettings& l = languages[ cbLanguages->currentText() ]; l.selectedStyle->setCaption( item->text() ); emit changed(); } QListWidgetItem* SourceFormatterSettings::addStyle( const SourceFormatterStyle& s ) { QListWidgetItem* item = new QListWidgetItem( styleList ); item->setText( s.caption() ); item->setData( STYLE_ROLE, s.name() ); if( s.name().startsWith( Strings::userStylePrefix() ) ) { item->setFlags( item->flags() | Qt::ItemIsEditable ); } styleList->addItem( item ); return item; } void SourceFormatterSettings::updatePreview() { m_document->setReadWrite( true ); QString langName = cbLanguages->itemText( cbLanguages->currentIndex() ); if( !langName.isEmpty() ) { LanguageSettings& l = languages[ langName ]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatterStyle* style = l.selectedStyle; descriptionLabel->setText( style->description() ); if( style->usePreview() ) { ISourceFormatter* ifmt = fmt->formatter; QMimeType mime = l.mimetypes.first(); m_document->setHighlightingMode( style->modeForMimetype( mime ) ); //NOTE: this is ugly, but otherwise kate might remove tabs again :-/ // see also: https://bugs.kde.org/show_bug.cgi?id=291074 KTextEditor::ConfigInterface* iface = qobject_cast(m_document); QVariant oldReplaceTabs; if (iface) { oldReplaceTabs = iface->configValue(QStringLiteral("replace-tabs")); iface->setConfigValue(QStringLiteral("replace-tabs"), false); } m_document->setText( ifmt->formatSourceWithStyle( *style, ifmt->previewText( *style, mime ), QUrl(), mime ) ); if (iface) { iface->setConfigValue(QStringLiteral("replace-tabs"), oldReplaceTabs); } previewLabel->show(); textEditor->show(); }else{ previewLabel->hide(); textEditor->hide(); } } else { m_document->setText( i18n( "No Language selected" ) ); } m_view->setCursorPosition( KTextEditor::Cursor( 0, 0 ) ); m_document->setReadWrite( false ); } void SourceFormatterSettings::somethingChanged() { // Widgets are managed manually, so we have to explicitly tell KCModule // that we have some changes, otherwise it won't call "save" and/or will not activate // "Appy" emit changed(); } QString SourceFormatterSettings::name() const { return i18n("Source Formatter"); } QString SourceFormatterSettings::fullName() const { return i18n("Configure Source Formatter"); } QIcon SourceFormatterSettings::icon() const { return QIcon::fromTheme(QStringLiteral("text-field")); } diff --git a/shell/shellextension.cpp b/shell/shellextension.cpp index a3fa1c1580..7ab0352ed7 100644 --- a/shell/shellextension.cpp +++ b/shell/shellextension.cpp @@ -1,34 +1,34 @@ /*************************************************************************** * Copyright 2004 Alexander Dymo * * * * 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. * ***************************************************************************/ #include "shellextension.h" namespace KDevelop { -ShellExtension *ShellExtension::s_instance = 0; +ShellExtension *ShellExtension::s_instance = nullptr; ShellExtension::ShellExtension() { } ShellExtension *ShellExtension::getInstance() { return s_instance; } } diff --git a/shell/sourceformattercontroller.cpp b/shell/sourceformattercontroller.cpp index a1828125e3..a45668e68f 100644 --- a/shell/sourceformattercontroller.cpp +++ b/shell/sourceformattercontroller.cpp @@ -1,641 +1,641 @@ /* 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" 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"), QStringLiteral("kdevsourceformatter")); 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("Format 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)); m_formatTextAction->setEnabled(false); m_formatFilesAction->setEnabled(true); connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &SourceFormatterController::activeDocumentChanged); // 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); activeDocumentChanged(Core::self()->documentController()->activeDocument()); } 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()] = 0; - return 0; + 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 0; + 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 0; + 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::activeDocumentChanged(IDocument* doc) { bool enabled = false; if (doc) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); if (isMimeTypeSupported(mime)) enabled = true; } m_formatTextAction->setEnabled(enabled); } void SourceFormatterController::beautifySource() { IDocument* idoc = KDevelop::ICore::self()->documentController()->activeDocument(); 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) { // 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 { 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 ) ) qWarning() << "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()) 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(); auto reply = QMessageBox::question(win, i18n("Reformat files?"), i18n("Reformat all files in the selected folder?")); if ( reply == QMessageBox::Yes ) { formatFiles(m_urls); } } void SourceFormatterController::formatFiles(QList &list) { //! \todo IStatus for (int fileCount = 0; fileCount < list.size(); fileCount++) { // check mimetype QMimeType mime = QMimeDatabase().mimeTypeForUrl(list[fileCount]); qCDebug(SHELL) << "Checking file " << list[fileCount] << " of mime type " << mime.name() << endl; ISourceFormatter *formatter = formatterForMimeType(mime); if (!formatter) // unsupported mime type continue; // if the file is opened in the editor, format the text in the editor without saving it KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); KDevelop::IDocument *doc = docController->documentForUrl(list[fileCount]); if (doc) { qCDebug(SHELL) << "Processing file " << list[fileCount] << "opened in editor" << endl; formatDocument(doc, formatter, mime); continue; } qCDebug(SHELL) << "Processing file " << list[fileCount] << endl; KIO::StoredTransferJob *job = KIO::storedGet(list[fileCount]); if (job->exec()) { QByteArray data = job->data(); QString output = formatter->formatSource(data, list[fileCount], mime); data += addModelineForCurrentLang(output, list[fileCount], mime).toUtf8(); job = KIO::storedPut(data, list[fileCount], -1); if (!job->exec()) - KMessageBox::error(0, job->errorString()); + KMessageBox::error(nullptr, job->errorString()); } else - KMessageBox::error(0, job->errorString()); + KMessageBox::error(nullptr, job->errorString()); } } KDevelop::ContextMenuExtension SourceFormatterController::contextMenuExtension(KDevelop::Context* context) { 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 = dynamic_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 = dynamic_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/shell/statusbar.cpp b/shell/statusbar.cpp index 0aa695f9c2..1f818730d6 100644 --- a/shell/statusbar.cpp +++ b/shell/statusbar.cpp @@ -1,266 +1,266 @@ /* This file is part of the KDE project Copyright 2007 Hamish Rodda 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 "statusbar.h" #include "progresswidget/statusbarprogresswidget.h" #include "progresswidget/progressmanager.h" #include "progresswidget/progressdialog.h" #include #include #include #include #include #include #include #include #include #include #include "plugincontroller.h" #include "core.h" namespace KDevelop { StatusBar::StatusBar(QWidget* parent) : QStatusBar(parent) , m_timer(new QTimer(this)) - , m_currentView(0) + , m_currentView(nullptr) , m_errorRemovalMapper(new QSignalMapper(this)) { #ifdef Q_OS_MAC /* At time of writing this is only required for OSX and only has effect on OSX. ifdef for robustness to future platform dependent theme/widget changes https://phabricator.kde.org/D656 */ setStyleSheet("QStatusBar{background:transparent;}"); #endif m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &StatusBar::slotTimeout); connect(Core::self()->pluginController(), &IPluginController::pluginLoaded, this, &StatusBar::pluginLoaded); QList plugins = Core::self()->pluginControllerInternal()->allPluginsForExtension(QStringLiteral("IStatus")); foreach (IPlugin* plugin, plugins) registerStatus(plugin); registerStatus(Core::self()->languageController()->backgroundParser()); connect(m_errorRemovalMapper, static_cast(&QSignalMapper::mapped), this, &StatusBar::removeError); m_progressController = Core::self()->progressController(); m_progressDialog = new ProgressDialog(this, parent); // construct this first, then progressWidget m_progressDialog->setVisible(false); m_progressWidget = new StatusbarProgressWidget(m_progressDialog, this); addPermanentWidget(m_progressWidget, 0); } void StatusBar::removeError(QWidget* w) { removeWidget(w); w->deleteLater(); } void StatusBar::viewChanged(Sublime::View* view) { if (m_currentView) m_currentView->disconnect(this); m_currentView = view; if (view) { connect(view, &Sublime::View::statusChanged, this, &StatusBar::viewStatusChanged); QStatusBar::showMessage(view->viewStatus(), 0); } } void StatusBar::viewStatusChanged(Sublime::View* view) { QStatusBar::showMessage(view->viewStatus(), 0); } void StatusBar::pluginLoaded(IPlugin* plugin) { if (qobject_cast(plugin)) registerStatus(plugin); } void StatusBar::registerStatus(QObject* status) { Q_ASSERT(qobject_cast(status)); // can't convert this to new signal slot syntax, IStatus is not a QObject connect(status, SIGNAL(clearMessage(KDevelop::IStatus*)), SLOT(clearMessage(KDevelop::IStatus*)), Qt::QueuedConnection); connect(status, SIGNAL(showMessage(KDevelop::IStatus*,QString,int)), SLOT(showMessage(KDevelop::IStatus*,QString,int)), Qt::QueuedConnection); connect(status, SIGNAL(hideProgress(KDevelop::IStatus*)), SLOT(hideProgress(KDevelop::IStatus*)), Qt::QueuedConnection); connect(status, SIGNAL(showProgress(KDevelop::IStatus*,int,int,int)), SLOT(showProgress(KDevelop::IStatus*,int,int,int)), Qt::QueuedConnection); // Don't try to connect when the status object doesn't provide an error message signal (ie. avoid warning) if (status->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("showErrorMessage(QString,int)")) != -1) { connect(status, SIGNAL(showErrorMessage(QString,int)), SLOT(showErrorMessage(QString,int)), Qt::QueuedConnection); } } QWidget* errorMessage(QWidget* parent, const QString& text) { KSqueezedTextLabel* label = new KSqueezedTextLabel(parent); KStatefulBrush red(KColorScheme::Window, KColorScheme::NegativeText); QPalette pal = label->palette(); pal.setBrush(QPalette::WindowText, red.brush(label)); label->setPalette(pal); label->setAlignment(Qt::AlignRight); label->setText(text); label->setToolTip(text); return label; } QTimer* StatusBar::errorTimeout(QWidget* error, int timeout) { QTimer* timer = new QTimer(error); timer->setSingleShot(true); timer->setInterval(1000*timeout); m_errorRemovalMapper->setMapping(timer, error); connect(timer, &QTimer::timeout, m_errorRemovalMapper, static_cast(&QSignalMapper::map)); return timer; } void StatusBar::showErrorMessage(const QString& message, int timeout) { QWidget* error = errorMessage(this, message); QTimer* timer = errorTimeout(error, timeout); addWidget(error); timer->start(); // triggers removeError() } void StatusBar::slotTimeout() { QMutableMapIterator it = m_messages; while (it.hasNext()) { it.next(); if (it.value().timeout) { it.value().timeout -= m_timer->interval(); if (it.value().timeout == 0) it.remove(); } } updateMessage(); } void StatusBar::updateMessage() { if (m_timer->isActive()) { m_timer->stop(); m_timer->setInterval(m_time.elapsed()); slotTimeout(); } QString ret; int timeout = 0; foreach (const Message& m, m_messages) { if (!ret.isEmpty()) ret += QLatin1String("; "); ret += m.text; if (timeout) timeout = qMin(timeout, m.timeout); else timeout = m.timeout; } if (!ret.isEmpty()) QStatusBar::showMessage(ret); else QStatusBar::clearMessage(); if (timeout) { m_time.start(); m_timer->start(timeout); } } void StatusBar::clearMessage( IStatus* status ) { if (m_messages.contains(status)) { m_messages.remove(status); updateMessage(); } } void StatusBar::showMessage( IStatus* status, const QString & message, int timeout) { if ( m_progressItems.contains(status) ) { ProgressItem* i = m_progressItems[status]; i->setStatus(message); } else { Message m; m.text = message; m.timeout = timeout; m_messages.insert(status, m); updateMessage(); } } void StatusBar::hideProgress( IStatus* status ) { if (m_progressItems.contains(status)) { m_progressItems[status]->setComplete(); m_progressItems.remove(status); } } void StatusBar::showProgress( IStatus* status, int minimum, int maximum, int value) { if (!m_progressItems.contains(status)) { bool canBeCanceled = false; m_progressItems[status] = m_progressController->createProgressItem( ProgressManager::getUniqueID(), status->statusName(), QString(), canBeCanceled);; } ProgressItem* i = m_progressItems[status]; m_progressWidget->raise(); m_progressDialog->raise(); if( minimum == 0 && maximum == 0 ) { i->setUsesBusyIndicator( true ); } else { i->setUsesBusyIndicator( false ); i->setProgress( 100*value/maximum ); } } } diff --git a/shell/testcontroller.cpp b/shell/testcontroller.cpp index 2e9c531285..7a97daf56b 100644 --- a/shell/testcontroller.cpp +++ b/shell/testcontroller.cpp @@ -1,120 +1,120 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testcontroller.h" #include "interfaces/itestsuite.h" #include "debug.h" #include #include using namespace KDevelop; class TestController::TestControllerPrivate { public: QList suites; }; TestController::TestController(QObject *parent) : ITestController(parent) , d(new TestControllerPrivate) { } TestController::~TestController() { delete d; } void TestController::initialize() { } void TestController::cleanup() { d->suites.clear(); } QList TestController::testSuites() const { return d->suites; } void TestController::removeTestSuite(ITestSuite* suite) { d->suites.removeAll(suite); emit testSuiteRemoved(suite); } void TestController::addTestSuite(ITestSuite* suite) { if (ITestSuite* existingSuite = findTestSuite(suite->project(), suite->name())) { if (existingSuite == suite) { return; } removeTestSuite(existingSuite); delete existingSuite; } d->suites.append(suite); if(!ICore::self()->shuttingDown()) emit testSuiteAdded(suite); } ITestSuite* TestController::findTestSuite(IProject* project, const QString& name) const { foreach (ITestSuite* suite, testSuitesForProject(project)) { if (suite->name() == name) { return suite; } } - return 0; + return nullptr; } QList< ITestSuite* > TestController::testSuitesForProject(IProject* project) const { QList suites; foreach (ITestSuite* suite, d->suites) { if (suite->project() == project) { suites << suite; } } return suites; } void TestController::notifyTestRunFinished(ITestSuite* suite, const TestResult& result) { qCDebug(SHELL) << "Test run finished for suite" << suite->name(); emit testRunFinished(suite, result); } void TestController::notifyTestRunStarted(ITestSuite* suite, const QStringList& test_cases) { qCDebug(SHELL) << "Test run started for suite" << suite->name(); emit testRunStarted(suite, test_cases); } diff --git a/shell/tests/test_documentcontroller.cpp b/shell/tests/test_documentcontroller.cpp index 8dffeb3740..930dd5afda 100644 --- a/shell/tests/test_documentcontroller.cpp +++ b/shell/tests/test_documentcontroller.cpp @@ -1,197 +1,197 @@ /* Unit tests for DocumentController.* Copyright 2011 Damien Flament 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. */ #include "test_documentcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; void TestDocumentController::initTestCase() { AutoTestShell::init(); TestCore::initialize(); Core::self()->languageController()->backgroundParser()->disableProcessing(); m_subject = Core::self()->documentController(); } void TestDocumentController::init() { Core::self()->documentControllerInternal()->initialize(); // create temp files m_file1.setFileTemplate(m_tempDir.path() + "/tmp_XXXXXX.txt"); m_file2.setFileTemplate(m_tempDir.path() + "/tmp_XXXXXX.txt"); if(!m_file1.open() || !m_file2.open()) { QFAIL("Can't create temp files"); } // pre-conditions QVERIFY(m_subject->openDocuments().empty()); - QVERIFY(m_subject->documentForUrl(QUrl()) == 0); - QVERIFY(m_subject->activeDocument() == 0); + QVERIFY(m_subject->documentForUrl(QUrl()) == nullptr); + QVERIFY(m_subject->activeDocument() == nullptr); } void TestDocumentController::cleanup() { // ensure there are not opened documents for next test foreach(IDocument* document, m_subject->openDocuments()) { document->close(IDocument::Discard); } Core::self()->documentControllerInternal()->cleanup(); } void TestDocumentController::cleanupTestCase() { TestCore::shutdown(); m_tempDir.remove(); } void TestDocumentController::testOpeningNewDocumentFromText() { qRegisterMetaType("KDevelop::IDocument*"); QSignalSpy createdSpy(m_subject, SIGNAL(textDocumentCreated(KDevelop::IDocument*))); QVERIFY(createdSpy.isValid()); QSignalSpy openedSpy(m_subject, SIGNAL(documentOpened(KDevelop::IDocument*))); QVERIFY(openedSpy.isValid()); IDocument* document = m_subject->openDocumentFromText(QLatin1String("")); - QVERIFY(document != 0); + QVERIFY(document != nullptr); QCOMPARE(createdSpy.count(), 1); QCOMPARE(openedSpy.count(), 1); QVERIFY(!m_subject->openDocuments().empty()); QVERIFY(m_subject->documentForUrl(document->url()) == document); QVERIFY(m_subject->activeDocument() == document); } void TestDocumentController::testOpeningDocumentFromUrl() { QUrl url = QUrl::fromLocalFile(m_file1.fileName()); IDocument* document = m_subject->openDocument(url); - QVERIFY(document != 0); + QVERIFY(document != nullptr); } void TestDocumentController::testSaveSomeDocuments() { // create documents QTemporaryDir dir; IDocument *document1 = m_subject->openDocument(createFile(dir, QStringLiteral("foo"))); IDocument *document2 = m_subject->openDocument(createFile(dir, QStringLiteral("bar"))); QCOMPARE(document1->state(), IDocument::Clean); QCOMPARE(document2->state(), IDocument::Clean); // edit both documents document1->textDocument()->insertText(KTextEditor::Cursor(), QStringLiteral("some text")); document2->textDocument()->insertText(KTextEditor::Cursor(), QStringLiteral("some text")); QCOMPARE(document1->state(), IDocument::Modified); QCOMPARE(document2->state(), IDocument::Modified); // save one document (Silent == don't ask user) m_subject->saveSomeDocuments(QList() << document1, IDocument::Silent); QCOMPARE(document1->state(), IDocument::Clean); QCOMPARE(document2->state(), IDocument::Modified); } void TestDocumentController::testSaveAllDocuments() { // create documents QTemporaryDir dir; IDocument *document1 = m_subject->openDocument(createFile(dir, QStringLiteral("foo"))); IDocument *document2 = m_subject->openDocument(createFile(dir, QStringLiteral("bar"))); QCOMPARE(document1->state(), IDocument::Clean); QCOMPARE(document2->state(), IDocument::Clean); // edit documents document1->textDocument()->insertText(KTextEditor::Cursor(), QStringLiteral("some text")); document2->textDocument()->insertText(KTextEditor::Cursor(), QStringLiteral("some text")); QCOMPARE(document1->state(), IDocument::Modified); QCOMPARE(document2->state(), IDocument::Modified); // save documents m_subject->saveAllDocuments(IDocument::Silent); QCOMPARE(document1->state(), IDocument::Clean); QCOMPARE(document2->state(), IDocument::Clean); } void TestDocumentController::testCloseAllDocuments() { // create documents m_subject->openDocumentFromText(QLatin1String("")); m_subject->openDocumentFromText(QLatin1String("")); QVERIFY(!m_subject->openDocuments().empty()); m_subject->closeAllDocuments(); QVERIFY(m_subject->openDocuments().empty()); } QUrl TestDocumentController::createFile(const QTemporaryDir& dir, const QString& filename) { QFile file(dir.path() + filename); bool success = file.open(QIODevice::WriteOnly | QIODevice::Text); if(!success) { QWARN(QString("Failed to create file: " + dir.path() + filename).toLatin1().data()); return QUrl(); } file.close(); return QUrl::fromLocalFile(dir.path() + filename); } void TestDocumentController::testEmptyUrl() { const auto first = DocumentController::nextEmptyDocumentUrl(); QVERIFY(DocumentController::isEmptyDocumentUrl(first)); QCOMPARE(DocumentController::nextEmptyDocumentUrl(), first); auto doc = m_subject->openDocumentFromText(QString()); QCOMPARE(doc->url(), first); const auto second = DocumentController::nextEmptyDocumentUrl(); QVERIFY(first != second); QVERIFY(DocumentController::isEmptyDocumentUrl(second)); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl())); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl(QStringLiteral("http://foo.org")))); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl(QStringLiteral("http://foo.org/test")))); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl::fromLocalFile(QStringLiteral("/")))); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl::fromLocalFile(QStringLiteral("/test")))); } QTEST_MAIN(TestDocumentController); diff --git a/shell/tests/test_projectcontroller.cpp b/shell/tests/test_projectcontroller.cpp index 207bef7bed..92dc542447 100644 --- a/shell/tests/test_projectcontroller.cpp +++ b/shell/tests/test_projectcontroller.cpp @@ -1,575 +1,575 @@ /*************************************************************************** * Copyright 2008 Manuel Breugelmans * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_projectcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; Q_DECLARE_METATYPE(KDevelop::IProject*) namespace { class DialogProviderFake : public IProjectDialogProvider { Q_OBJECT public: DialogProviderFake() : m_reopen(true) {} ~DialogProviderFake() override {} bool m_reopen; public slots: QUrl askProjectConfigLocation(bool /*fetch*/, const QUrl& /*startUrl*/ = QUrl()) override { return QUrl(); } bool userWantsReopen() override { return m_reopen; } }; } /*! A Filemanager plugin that allows you to setup a file & directory structure */ class FakeFileManager : public IPlugin, public IProjectFileManager { Q_OBJECT Q_INTERFACES(KDevelop::IProjectFileManager) public: FakeFileManager(QObject*, const QVariantList&) : IPlugin(ICore::self()->aboutData().componentName(), Core::self()) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) } FakeFileManager() : IPlugin(ICore::self()->aboutData().componentName(), Core::self()) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) } ~FakeFileManager() override {} Features features() const override { return IProjectFileManager::Files | IProjectFileManager::Folders; } QMap m_filesInFolder; // initialize QMap m_subFoldersInFolder; /*! Setup this manager such that @p folder contains @p file */ void addFileToFolder(const Path& folder, const Path& file) { if (!m_filesInFolder.contains(folder)) { m_filesInFolder[folder] = Path::List(); } m_filesInFolder[folder] << file; } /*! Setup this manager such that @p folder has @p subFolder */ void addSubFolderTo(const Path& folder, Path subFolder) { if (!m_subFoldersInFolder.contains(folder)) { m_subFoldersInFolder[folder] = Path::List(); } m_subFoldersInFolder[folder] << subFolder; } QList parse(ProjectFolderItem *dom) override { Path::List files = m_filesInFolder[dom->path()]; foreach (const Path& file, files) { new ProjectFileItem(dom->project(), file, dom); } Path::List folderPaths = m_subFoldersInFolder[dom->path()]; QList folders; foreach (const Path& folderPath, folderPaths) { folders << new ProjectFolderItem(dom->project(), folderPath, dom); } return folders; } ProjectFolderItem *import(IProject *project) override { ProjectFolderItem* it = new ProjectFolderItem(project, project->path()); return it; } - ProjectFolderItem* addFolder(const Path& /*folder*/, ProjectFolderItem */*parent*/) override { return 0; } - ProjectFileItem* addFile(const Path& /*file*/, ProjectFolderItem */*parent*/) override { return 0; } + ProjectFolderItem* addFolder(const Path& /*folder*/, ProjectFolderItem */*parent*/) override { return nullptr; } + ProjectFileItem* addFile(const Path& /*file*/, ProjectFolderItem */*parent*/) override { return nullptr; } bool removeFilesAndFolders(const QList &/*items*/) override { return false; } bool moveFilesAndFolders(const QList< KDevelop::ProjectBaseItem* > &/*items*/, KDevelop::ProjectFolderItem* /*newParent*/) override { return false; } bool copyFilesAndFolders(const Path::List &/*items*/, KDevelop::ProjectFolderItem* /*newParent*/) override { return false; } bool renameFile(ProjectFileItem* /*file*/, const Path& /*newPath*/) override { return false; } bool renameFolder(ProjectFolderItem* /*oldFolder*/, const Path& /*newPath*/ ) override { return false; } bool reload(ProjectFolderItem* /*item*/) override { return false; } }; class FakePluginController : public PluginController { Q_OBJECT public: FakePluginController(Core* core) : PluginController(core) , m_fakeFileManager(new FakeFileManager) { } IPlugin* pluginForExtension(const QString& extension, const QString& pluginName = {}, const QVariantMap& constraints = QVariantMap()) override { if (extension == m_fakeFileManager->extensions().at(0)) return m_fakeFileManager; return PluginController::pluginForExtension(extension, pluginName, constraints); } private: FakeFileManager* m_fakeFileManager; }; ////////////////////// Fixture /////////////////////////////////////////////// void TestProjectController::initTestCase() { AutoTestShell::init(); TestCore* testCore = new TestCore; testCore->setPluginController( new FakePluginController(testCore) ); testCore->initialize(); qRegisterMetaType(); m_core = Core::self(); m_scratchDir = QDir(QDir::tempPath()); m_scratchDir.mkdir(QStringLiteral("prjctrltest")); m_scratchDir.cd(QStringLiteral("prjctrltest")); } void TestProjectController::cleanupTestCase() { TestCore::shutdown(); } void TestProjectController::init() { m_projName = QStringLiteral("foo"); m_projFilePath = writeProjectConfig(m_projName); m_projCtrl = m_core->projectControllerInternal(); m_tmpConfigs << m_projFilePath; m_projFolder = Path(m_scratchDir.absolutePath() + '/'); } void TestProjectController::cleanup() { // also close any opened projects as we do not get a clean fixture, // following tests should start off clean. foreach(IProject* p, m_projCtrl->projects()) { m_projCtrl->closeProject(p); } foreach(const Path &cfg, m_tmpConfigs) { QFile::remove(cfg.pathOrUrl()); } qDeleteAll(m_fileManagerGarbage); m_fileManagerGarbage.clear(); } ////////////////////// Commands ////////////////////////////////////////////// #define WAIT_FOR_OPEN_SIGNAL \ {\ QSignalSpy signal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*)));\ QVERIFY2(signal.wait(30000), "Timeout while waiting for opened signal");\ } void(0) void TestProjectController::openProject() { QSignalSpy* spy = createOpenedSpy(); QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName)); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 1); IProject* proj; assertProjectOpened(m_projName, proj);QVERIFY(proj); assertSpyCaughtProject(spy, proj); QCOMPARE(proj->projectFile(), m_projFilePath); QCOMPARE(proj->path(), Path(m_scratchDir.absolutePath()+'/')); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); } void TestProjectController::closeProject() { m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; IProject* proj = m_projCtrl->findProjectByName(m_projName); Q_ASSERT(proj); QSignalSpy* spy1 = createClosedSpy(); QSignalSpy* spy2 = createClosingSpy(); m_projCtrl->closeProject(proj); QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName)); QCOMPARE(m_projCtrl->projectCount(), 0); assertProjectClosed(proj); assertSpyCaughtProject(spy1, proj); assertSpyCaughtProject(spy2, proj); } void TestProjectController::openCloseOpen() { m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; IProject* proj; assertProjectOpened(m_projName, proj); m_projCtrl->closeProject(proj); QSignalSpy* spy = createOpenedSpy(); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); QCOMPARE(m_projCtrl->projectCount(), 1); assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void TestProjectController::reopen() { m_projCtrl->setDialogProvider(new DialogProviderFake); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; QSignalSpy* spy = createOpenedSpy(); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 1); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); IProject* proj; assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void TestProjectController::reopenWhileLoading() { // Open the same project again while the first is still // loading. The second open request should be blocked. m_projCtrl->setDialogProvider(new DialogProviderFake); QSignalSpy* spy = createOpenedSpy(); m_projCtrl->openProject(m_projFilePath.toUrl()); //m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; // wait a bit for a second signal, this should timeout QSignalSpy signal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*))); QVERIFY2(!signal.wait(100), "Received 2 projectOpened signals."); QCOMPARE(m_projCtrl->projectCount(), 1); IProject* proj; assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void TestProjectController::openMultiple() { QString secondProj(QStringLiteral("bar")); Path secondCfgUrl = writeProjectConfig(secondProj); QSignalSpy* spy = createOpenedSpy(); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; m_projCtrl->openProject(secondCfgUrl.toUrl()); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 2); IProject *proj1, *proj2; assertProjectOpened(m_projName, proj1); assertProjectOpened(secondProj, proj2); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); QVERIFY(m_projCtrl->isProjectNameUsed(QStringLiteral("bar"))); QCOMPARE(spy->size(), 2); IProject* emittedProj1 = (*spy)[0][0].value(); IProject* emittedProj2 = (*spy)[1][0].value(); QCOMPARE(emittedProj1, proj1); QCOMPARE(emittedProj2, proj2); m_tmpConfigs << secondCfgUrl; } /*! Verify that the projectmodel contains a single project. Put this project's * ProjectFolderItem in the output parameter @p RootItem */ #define ASSERT_SINGLE_PROJECT_IN_MODEL(rootItem) \ {\ QCOMPARE(m_projCtrl->projectModel()->rowCount(), 1); \ QModelIndex projIndex = m_projCtrl->projectModel()->index(0,0); \ QVERIFY(projIndex.isValid()); \ ProjectBaseItem* i = m_projCtrl->projectModel()->itemFromIndex( projIndex ); \ QVERIFY(i); \ QVERIFY(i->folder()); \ rootItem = i->folder();\ } void(0) /*! Verify that the projectitem @p item has a single child item * named @p name with url @p url. @p subFolder is an output parameter * that contains the sub-folder projectitem. */ #define ASSERT_SINGLE_SUBFOLDER_IN(item, name, path__, subFolder) \ {\ QCOMPARE(item->rowCount(), 1);\ QCOMPARE(item->folderList().size(), 1);\ ProjectFolderItem* fo = item->folderList().at(0);\ QVERIFY(fo);\ QCOMPARE(fo->path(), path__);\ QCOMPARE(fo->folderName(), QStringLiteral(name));\ subFolder = fo;\ } void(0) #define ASSERT_SINGLE_FILE_IN(rootFolder, name, path__, fileItem)\ {\ QCOMPARE(rootFolder->rowCount(), 1);\ QCOMPARE(rootFolder->fileList().size(), 1);\ fileItem = rootFolder->fileList().at(0);\ QVERIFY(fileItem);\ QCOMPARE(fileItem->path(), path__);\ QCOMPARE(fileItem->fileName(), QStringLiteral(name));\ } void(0) // command void TestProjectController::emptyProject() { // verify that the project model contains a single top-level folder after loading // an empty project assertEmptyProjectModel(); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); FakeFileManager* fileMng = createFileManager(); Q_ASSERT(fileMng); proj->setManagerPlugin(fileMng); proj->reloadModel(); QTest::qWait(100); ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); // check that the project is empty QCOMPARE(rootFolder->rowCount(), 0); QCOMPARE(rootFolder->project()->name(), m_projName); QCOMPARE(rootFolder->path(), m_projFolder); } // command void TestProjectController::singleFile() { // verify that the project model contains a single file in the // top folder. First setup a FakeFileManager with this file m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); FakeFileManager* fileMng = createFileManager(); proj->setManagerPlugin(fileMng); Path filePath = Path(m_projFolder, QStringLiteral("foobar")); fileMng->addFileToFolder(m_projFolder, filePath); proj->reloadModel(); QTest::qWait(100); // NO signals for reload ... ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ProjectFileItem* fi; ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", filePath, fi); QCOMPARE(fi->rowCount(), 0); ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", filePath, fi); } // command void TestProjectController::singleDirectory() { // verify that the project model contains a single folder in the // top folder. First setup a FakeFileManager with this folder m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); Path folderPath = Path(m_projFolder, QStringLiteral("foobar/")); FakeFileManager* fileMng = createFileManager(); fileMng->addSubFolderTo(m_projFolder, folderPath); proj->setManagerPlugin(fileMng); proj->reloadModel(); QTest::qWait(100); ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); // check that the project contains a single subfolder ProjectFolderItem* sub; ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderPath, sub); QCOMPARE(sub->rowCount(), 0); } // command void TestProjectController::fileInSubdirectory() { // verify that the project model contains a single file in a subfolder // First setup a FakeFileManager with this folder + file m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); Path folderPath = Path(m_projFolder, QStringLiteral("foobar/")); FakeFileManager* fileMng = createFileManager(); fileMng->addSubFolderTo(m_projFolder, folderPath); Path filePath = Path(folderPath, QStringLiteral("zoo")); fileMng->addFileToFolder(folderPath, filePath); proj->setManagerPlugin(fileMng); ProjectFolderItem* rootFolder = nullptr; ProjectFolderItem* sub = nullptr; ProjectFileItem* file = nullptr; proj->reloadModel(); QTest::qWait(100); ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderPath, sub); ASSERT_SINGLE_FILE_IN(sub,"zoo",filePath,file); ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderPath, sub); ASSERT_SINGLE_FILE_IN(sub,"zoo",filePath,file); } void TestProjectController::prettyFileName_data() { QTest::addColumn("relativeFilePath"); QTest::newRow("basic") << "foobar.txt"; QTest::newRow("subfolder") << "sub/foobar.txt"; } void TestProjectController::prettyFileName() { QFETCH(QString, relativeFilePath); m_projCtrl->openProject(m_projFilePath.toUrl()); WAIT_FOR_OPEN_SIGNAL; Project* proj; assertProjectOpened(m_projName, (KDevelop::IProject*&)proj); FakeFileManager* fileMng = createFileManager(); proj->setManagerPlugin(fileMng); Path filePath = Path(m_projFolder, relativeFilePath); fileMng->addFileToFolder(m_projFolder, filePath); QCOMPARE(m_projCtrl->prettyFileName(filePath.toUrl(), ProjectController::FormattingOptions::FormatPlain), QString(m_projName + ':' + relativeFilePath)); } ////////////////////// Helpers /////////////////////////////////////////////// Path TestProjectController::writeProjectConfig(const QString& name) { Path configPath = Path(m_scratchDir.absolutePath() + '/' + name + ".kdev4"); QFile f(configPath.pathOrUrl()); f.open(QIODevice::WriteOnly); QTextStream str(&f); str << "[Project]\n" << "Name=" << name << "\n"; f.close(); return configPath; } ////////////////// Custom assertions ///////////////////////////////////////// void TestProjectController::assertProjectOpened(const QString& name, IProject*& proj) { QVERIFY(proj = m_projCtrl->findProjectByName(name)); QVERIFY(m_projCtrl->projects().contains(proj)); } void TestProjectController::assertSpyCaughtProject(QSignalSpy* spy, IProject* proj) { QCOMPARE(spy->size(), 1); IProject* emittedProj = (*spy)[0][0].value(); QCOMPARE(proj, emittedProj); } void TestProjectController::assertProjectClosed(IProject* proj) { IProject* p = m_projCtrl->findProjectByName(proj->name()); - QVERIFY(p == 0); + QVERIFY(p == nullptr); QVERIFY(!m_projCtrl->projects().contains(proj)); } void TestProjectController::assertEmptyProjectModel() { ProjectModel* m = m_projCtrl->projectModel(); Q_ASSERT(m); QCOMPARE(m->rowCount(), 0); } ///////////////////// Creation stuff ///////////////////////////////////////// QSignalSpy* TestProjectController::createOpenedSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*))); } QSignalSpy* TestProjectController::createClosedSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectClosed(KDevelop::IProject*))); } QSignalSpy* TestProjectController::createClosingSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectClosing(KDevelop::IProject*))); } FakeFileManager* TestProjectController::createFileManager() { FakeFileManager* fileMng = new FakeFileManager; m_fileManagerGarbage << fileMng; return fileMng; } QTEST_MAIN(TestProjectController) #include "moc_test_projectcontroller.cpp" #include "test_projectcontroller.moc" diff --git a/shell/tests/test_shellbuddy.cpp b/shell/tests/test_shellbuddy.cpp index 98704868ff..8298ec4600 100644 --- a/shell/tests/test_shellbuddy.cpp +++ b/shell/tests/test_shellbuddy.cpp @@ -1,424 +1,424 @@ /*************************************************************************** * Copyright 2011 Martin Heide * * Copyright 2012 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_shellbuddy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../documentcontroller.h" #include "../uicontroller.h" // groups files like foo.l.txt and foo.r.txt such that l is left of r class TestBuddyFinder : public KDevelop::IBuddyDocumentFinder { bool areBuddies(const QUrl& url1, const QUrl& url2) override { const QStringList name1 = url1.fileName().split('.'); const QStringList name2 = url2.fileName().split('.'); if (name1.size() != 3 || name2.size() != 3) { return false; } if (name1.last() != name2.last() || name1.first() != name2.first()) { return false; } if (name1.at(1) == name2.at(1)) { return false; } if (name1.at(1) != QLatin1String("l") && name1.at(1) != QLatin1String("r")) { return false; } if (name2.at(1) != QLatin1String("l") && name2.at(1) != QLatin1String("r")) { return false; } qDebug() << "found buddies: " << url1 << url2; return true; } bool buddyOrder(const QUrl& url1, const QUrl& /*url2*/) override { const QStringList name1 = url1.fileName().split('.'); return name1.at(1) == QLatin1String("l"); } QVector getPotentialBuddies(const QUrl& url) const override { Q_UNUSED(url); return QVector(); } }; void TestShellBuddy::initTestCase() { AutoTestShell::init(); TestCore::initialize(); m_documentController = Core::self()->documentController(); m_uiController = Core::self()->uiControllerInternal(); m_finder = new TestBuddyFinder; KDevelop::IBuddyDocumentFinder::addFinder(QStringLiteral("text/plain"), m_finder); } void TestShellBuddy::cleanupTestCase() { KDevelop::IBuddyDocumentFinder::removeFinder(QStringLiteral("text/plain")); delete m_finder; - m_finder = 0; + m_finder = nullptr; TestCore::shutdown(); } //NOTE: macro for proper line-numbers in test's output in case the check fails #define verifyFilename(view, endOfFilename) \ QVERIFY(view); \ { \ Sublime::UrlDocument *urlDoc = dynamic_cast(view->document()); \ QVERIFY(urlDoc); \ QVERIFY(urlDoc->url().toLocalFile().endsWith(QStringLiteral(endOfFilename))); \ } void TestShellBuddy::createFile(const QTemporaryDir& dir, const QString& filename) { QFile file(dir.path() + '/' + filename); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); file.close(); } void TestShellBuddy::enableBuddies(bool enable) { { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); uiGroup.writeEntry("TabBarArrangeBuddies", (enable ? 1 : 0)); uiGroup.sync(); } Core::self()->uiControllerInternal()->loadSettings(); QCOMPARE(Core::self()->uiControllerInternal()->arrangeBuddies(), enable); } void TestShellBuddy::enableOpenAfterCurrent(bool enable) { { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); uiGroup.writeEntry("TabBarOpenAfterCurrent", (enable ? 1 : 0)); uiGroup.sync(); } Core::self()->uiControllerInternal()->loadSettings(); QCOMPARE(Core::self()->uiControllerInternal()->openAfterCurrent(), enable); } // ------------------ Tests ------------------------------------------------- void TestShellBuddy::testDeclarationDefinitionOrder() { QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; createFile(dirA, QStringLiteral("a.r.txt")); createFile(dirA, QStringLiteral("b.r.txt")); createFile(dirA, QStringLiteral("c.r.txt")); createFile(dirA, QStringLiteral("a.l.txt")); createFile(dirA, QStringLiteral("b.l.txt")); createFile(dirA, QStringLiteral("c.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "b.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "c.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "b.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "c.l.txt")); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(m_uiController->activeSublimeWindow()->activeView()); QCOMPARE(m_documentController->openDocuments().count(), 6); //QCOMPARE(m_uiController->documents().count(), 6); QCOMPARE(areaIndex->viewCount(), 6); qDebug() << dynamic_cast(areaIndex->viewAt(0)->document())->url(); verifyFilename(areaIndex->views().value(0), "a.l.txt"); verifyFilename(areaIndex->views().value(1), "a.r.txt"); verifyFilename(areaIndex->views().value(2), "b.l.txt"); verifyFilename(areaIndex->views().value(3), "b.r.txt"); verifyFilename(areaIndex->views().value(4), "c.l.txt"); verifyFilename(areaIndex->views().value(5), "c.r.txt"); for(int i = 0; i < 6; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestShellBuddy::testActivation() { QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; createFile(dirA, QStringLiteral("a.l.txt")); createFile(dirA, QStringLiteral("a.r.txt")); createFile(dirA, QStringLiteral("b.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.l.txt")); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "a.l.txt"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "b.r.txt")); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "b.r.txt"); QCOMPARE(m_documentController->openDocuments().count(), 3); for(int i = 0; i < 3; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestShellBuddy::testDisableBuddies() { /* 3. Deactivate buddy option, Activate open next to active tab Open a.cpp a.l.txt Verify order (a.cpp a.l.txt) Verify that a.l.txt is activated Activate a.cpp Open b.cpp Verify order (a.cpp b.cpp a.l.txt) */ QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(false); enableOpenAfterCurrent(); QTemporaryDir dirA; createFile(dirA, QStringLiteral("a.l.txt")); createFile(dirA, QStringLiteral("a.r.txt")); createFile(dirA, QStringLiteral("b.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.l.txt")); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(m_uiController->activeSublimeWindow()->activeView()); // Buddies disabled => order of tabs should be the order of file opening verifyFilename(areaIndex->views().value(0), "a.r.txt"); verifyFilename(areaIndex->views().value(1), "a.l.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "a.l.txt"); //activate a.cpp => new doc should be opened right next to it m_uiController->activeSublimeWindow()->activateView(areaIndex->views().value(0)); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "b.r.txt")); verifyFilename(areaIndex->views().value(0), "a.r.txt"); verifyFilename(areaIndex->views().value(1), "b.r.txt"); verifyFilename(areaIndex->views().value(2), "a.l.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "b.r.txt"); QCOMPARE(m_documentController->openDocuments().count(), 3); for(int i = 0; i < 3; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestShellBuddy::testDisableOpenAfterCurrent() { /* 5. Enable buddy option, Disable open next to active tab Open foo.l.txt bar.cpp foo.cpp Verify order (foo.l.txt foo.cpp bar.cpp) Verify that foo.cpp is activated Open x.cpp => tab must be placed at the end Verify order (foo.l.txt foo.cpp bar.cpp x.cpp) Verify that x.cpp is activated*/ QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(); enableOpenAfterCurrent(false); QTemporaryDir dirA; createFile(dirA, QStringLiteral("foo.l.txt")); createFile(dirA, QStringLiteral("bar.r.txt")); createFile(dirA, QStringLiteral("foo.r.txt")); createFile(dirA, QStringLiteral("x.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "bar.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.r.txt")); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(m_uiController->activeSublimeWindow()->activeView()); verifyFilename(areaIndex->views().value(0), "foo.l.txt"); verifyFilename(areaIndex->views().value(1), "foo.r.txt"); verifyFilename(areaIndex->views().value(2), "bar.r.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "foo.r.txt"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "x.r.txt")); verifyFilename(areaIndex->views().value(0), "foo.l.txt"); verifyFilename(areaIndex->views().value(1), "foo.r.txt"); verifyFilename(areaIndex->views().value(2), "bar.r.txt"); verifyFilename(areaIndex->views().value(3), "x.r.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "x.r.txt"); QCOMPARE(m_documentController->openDocuments().count(), 4); for(int i = 0; i < 4; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestShellBuddy::testDisableAll() { /* 6. Disable buddy option, Disable open next to active tab Open foo.cpp bar.l.txt foo.l.txt Activate bar.l.txt Open bar.cpp Verify order (foo.cpp bar.l.txt foo.l.txt bar.cpp) Verify that bar.cpp is activated*/ QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(false); enableOpenAfterCurrent(false); QTemporaryDir dirA; createFile(dirA, QStringLiteral("foo.l.txt")); createFile(dirA, QStringLiteral("foo.r.txt")); createFile(dirA, QStringLiteral("bar.l.txt")); createFile(dirA, QStringLiteral("bar.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "bar.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.l.txt")); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(m_uiController->activeSublimeWindow()->activeView()); //activate bar.l.txt m_uiController->activeSublimeWindow()->activateView(areaIndex->views().value(1)); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "bar.r.txt")); verifyFilename(areaIndex->views().value(0), "foo.r.txt"); verifyFilename(areaIndex->views().value(1), "bar.l.txt"); verifyFilename(areaIndex->views().value(2), "foo.l.txt"); verifyFilename(areaIndex->views().value(3), "bar.r.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "bar.r.txt"); QCOMPARE(m_documentController->openDocuments().count(), 4); for(int i = 0; i < 4; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void TestShellBuddy::testsplitViewBuddies() { Sublime::MainWindow *pMainWindow = m_uiController->activeSublimeWindow(); QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; createFile(dirA, QStringLiteral("classA.r.txt")); createFile(dirA, QStringLiteral("classA.l.txt")); createFile(dirA, QStringLiteral("foo.txt")); Sublime::Area *pCodeArea = m_uiController->activeArea(); QVERIFY(pCodeArea); IDocument *pClassAHeader = m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "classA.l.txt")); QVERIFY(pClassAHeader); Sublime::View *pClassAHeaderView = pMainWindow->activeView(); pClassAHeaderView->setObjectName(QStringLiteral("classA.l.txt")); // now, create a split view of the active view (pClassAHeader) Sublime::View *pClassAHeaderSplitView = dynamic_cast(pClassAHeader)->createView(); pClassAHeaderSplitView->setObjectName("splitOf" + pMainWindow->activeView()->objectName()); pCodeArea->addView(pClassAHeaderSplitView, pMainWindow->activeView(), Qt::Vertical); // and activate it pMainWindow->activateView(pClassAHeaderSplitView); // get the current view's container from the mainwindow QWidget *pCentral = pMainWindow->centralWidget(); QVERIFY(pCentral); QVERIFY(pCentral->inherits("QWidget")); QWidget *pSplitter = pCentral->findChild(); QVERIFY(pSplitter); QVERIFY(pSplitter->inherits("QSplitter")); Sublime::Container *pLeftContainer = pSplitter->findChildren().at(1); QVERIFY(pLeftContainer); Sublime::Container *pRightContainer = pSplitter->findChildren().at(0); QVERIFY(pRightContainer); // check that it only contains pClassAHeaderSplitView QVERIFY(pRightContainer->count() == 1 && pRightContainer->hasWidget(pClassAHeaderSplitView->widget())); // now open the correponding definition file, classA.r.txt IDocument *pClassAImplem = m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "classA.r.txt")); QVERIFY(pClassAImplem); pMainWindow->activeView()->setObjectName(QStringLiteral("classA.r.txt")); // and check its presence alongside pClassAHeaderSplitView in pRightContainer QVERIFY(pRightContainer->hasWidget(pClassAHeaderSplitView->widget())); QVERIFY(pRightContainer->hasWidget(pMainWindow->activeView()->widget())); // Now reactivate left side ClassAHeaderview pMainWindow->activateView(pClassAHeaderView); // open another file IDocument *pLeftSideCpp = m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.txt")); QVERIFY(pLeftSideCpp); pMainWindow->activeView()->setObjectName(QStringLiteral("foo.txt")); // and close left side ClassAHeaderview pCodeArea->closeView(pClassAHeaderView); // try to open classAImpl (which is already on the right) // but this time it should open on the left bool successfullyReOpened = m_documentController->openDocument(pClassAImplem); QVERIFY(successfullyReOpened); pMainWindow->activeView()->setObjectName(QStringLiteral("classA.r.txt")); // and check if it correctly opened on the left side QVERIFY(pLeftContainer->hasWidget(pMainWindow->activeView()->widget())); } QTEST_MAIN(TestShellBuddy) diff --git a/shell/tests/test_shelldocumentoperation.cpp b/shell/tests/test_shelldocumentoperation.cpp index 12014573c7..fbda04c446 100644 --- a/shell/tests/test_shelldocumentoperation.cpp +++ b/shell/tests/test_shelldocumentoperation.cpp @@ -1,146 +1,146 @@ /*************************************************************************** * Copyright 2008 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_shelldocumentoperation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../documentcontroller.h" #include "../uicontroller.h" using namespace KDevelop; void TestShellDocumentOperation::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void TestShellDocumentOperation::cleanupTestCase() { TestCore::shutdown(); } void TestShellDocumentOperation::testOpenDocumentFromText() { //open some docs IDocumentController *documentController = Core::self()->documentController(); documentController->openDocumentFromText(QStringLiteral("Test1")); //test that we have this document in the list, signals are emitted and so on QCOMPARE(documentController->openDocuments().count(), 1); QCOMPARE(documentController->openDocuments().at(0)->textDocument()->text(), QStringLiteral("Test1")); Sublime::Area *area = Core::self()->uiControllerInternal()->activeArea(); QCOMPARE(area->views().count(), 1); documentController->openDocuments().at(0)->close(IDocument::Discard); // We used to have a bug where closing document failed to remove its // views from area, so check it here. QCOMPARE(area->views().count(), 0); } void TestShellDocumentOperation::testClosing() { // Test that both the view and the view widget is deleted when closing // document. { IDocumentController *documentController = Core::self()->documentController(); documentController->openDocumentFromText(QStringLiteral("Test1")); Sublime::Area *area = Core::self()->uiControllerInternal()->activeArea(); QCOMPARE(area->views().count(), 1); QPointer the_view = area->views().at(0); QPointer the_widget = the_view->widget(); documentController->openDocuments().at(0)->close(IDocument::Discard); - QCOMPARE(the_view.data(), (Sublime::View*)0); - QCOMPARE(the_widget.data(), (QWidget*)0); + QCOMPARE(the_view.data(), (Sublime::View*)nullptr); + QCOMPARE(the_widget.data(), (QWidget*)nullptr); } // Now try the same, where there are two open documents. { IDocumentController *documentController = Core::self()->documentController(); // Annoying, the order of documents in // documentController->openDocuments() depends on how URLs hash. So, // to reliably close the second one, get hold of a pointer. IDocument* doc1 = documentController->openDocumentFromText(QStringLiteral("Test1")); IDocument* doc2 = documentController->openDocumentFromText(QStringLiteral("Test2")); Sublime::Area *area = Core::self()->uiControllerInternal()->activeArea(); QCOMPARE(area->views().count(), 2); QPointer the_view = area->views().at(1); qDebug() << this << "see views " << area->views().at(0) << " " << area->views().at(1); QPointer the_widget = the_view->widget(); doc2->close(IDocument::Discard); - QCOMPARE(the_view.data(), (Sublime::View*)0); - QCOMPARE(the_widget.data(), (QWidget*)0); + QCOMPARE(the_view.data(), (Sublime::View*)nullptr); + QCOMPARE(the_widget.data(), (QWidget*)nullptr); doc1->close(IDocument::Discard); } } void TestShellDocumentOperation::testKateDocumentAndViewCreation() { //create one document IDocumentController *documentController = Core::self()->documentController(); documentController->openDocumentFromText(QLatin1String("")); QCOMPARE(documentController->openDocuments().count(), 1); //assure we have only one kate view for the newly created document KTextEditor::Document *doc = documentController->openDocuments().at(0)->textDocument(); QCOMPARE(doc->views().count(), 1); QCOMPARE(dynamic_cast(doc)->revision(), qint64(0)); //also assure the view's xmlgui is plugged in KParts::MainWindow *main = Core::self()->uiControllerInternal()->activeMainWindow(); QVERIFY(main); QVERIFY(main->guiFactory()->clients().contains(doc->views().at(0))); //KTextEditor::views is internally a QHash::keys() call: so the order of the views will vary const auto originalView = doc->views().at(0); //create the new view and activate it (using split action from mainwindow) QAction *splitAction = main->actionCollection()->action(QStringLiteral("split_vertical")); QVERIFY(splitAction); splitAction->trigger(); const auto viewList = doc->views(); QCOMPARE(viewList.count(), 2); const auto newlySplitView = originalView == viewList[0] ? viewList[1] : viewList[0]; //check that we did switch to the new xmlguiclient QVERIFY(!main->guiFactory()->clients().contains(originalView)); QVERIFY(main->guiFactory()->clients().contains(newlySplitView)); documentController->openDocuments().at(0)->close(IDocument::Discard); } QTEST_MAIN(TestShellDocumentOperation) diff --git a/shell/tests/test_testcontroller.cpp b/shell/tests/test_testcontroller.cpp index 3056cd886a..35b6e6cb02 100644 --- a/shell/tests/test_testcontroller.cpp +++ b/shell/tests/test_testcontroller.cpp @@ -1,244 +1,244 @@ /* Unit tests for TestController. Copyright 2012 Miha Čančula 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. */ #include "test_testcontroller.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; const char* TestSuiteName = "TestTestSuite"; const char* TestSuiteNameTwo = "TestTestSuiteTwo"; const char* TestCaseNameOne = "TestTestCaseOne"; const char* TestCaseNameTwo = "TestTestCaseTwo"; Q_DECLARE_METATYPE(KDevelop::TestResult) Q_DECLARE_METATYPE(KDevelop::ITestSuite*) class FakeTestSuite : public KDevelop::ITestSuite { public: FakeTestSuite(const QString& name, IProject* project, const QStringList& cases = QStringList()) : m_name(name), m_project(project), m_cases(cases) {} ~FakeTestSuite() override {} IProject* project() const override {return m_project;} QString name() const override {return m_name;} QStringList cases() const override {return m_cases;} IndexedDeclaration declaration() const override; IndexedDeclaration caseDeclaration(const QString& testCase) const override; KJob* launchAllCases(TestJobVerbosity verbosity) override; KJob* launchCase(const QString& testCase, TestJobVerbosity verbosity) override; KJob* launchCases(const QStringList& testCases, TestJobVerbosity verbosity) override; private: QString m_name; IProject* m_project; QStringList m_cases; }; IndexedDeclaration FakeTestSuite::declaration() const { return IndexedDeclaration(); } IndexedDeclaration FakeTestSuite::caseDeclaration(const QString& testCase) const { Q_UNUSED(testCase); return IndexedDeclaration(); } KJob* FakeTestSuite::launchAllCases(ITestSuite::TestJobVerbosity verbosity) { Q_UNUSED(verbosity); - return 0; + return nullptr; } KJob* FakeTestSuite::launchCase(const QString& testCase, ITestSuite::TestJobVerbosity verbosity) { Q_UNUSED(testCase); Q_UNUSED(verbosity); - return 0; + return nullptr; } KJob* FakeTestSuite::launchCases(const QStringList& testCases, ITestSuite::TestJobVerbosity verbosity) { Q_UNUSED(testCases); Q_UNUSED(verbosity); - return 0; + return nullptr; } void TestTestController::emitTestResult(ITestSuite* suite, TestResult::TestCaseResult caseResult) { TestResult result; result.suiteResult = caseResult; foreach (const QString& testCase, suite->cases()) { result.testCaseResults.insert(testCase, caseResult); } m_testController->notifyTestRunFinished(suite, result); } void TestTestController::initTestCase() { AutoTestShell::init(); TestCore::initialize( Core::NoUi ); m_testController = Core::self()->testControllerInternal(); m_project = new TestProject(Path(), this); qRegisterMetaType("KDevelop::ITestSuite*"); qRegisterMetaType("KDevelop::TestResult"); } void TestTestController::cleanupTestCase() { delete m_project; TestCore::shutdown(); } void TestTestController::addSuite() { FakeTestSuite suite(TestSuiteName, m_project); m_testController->addTestSuite(&suite); ITestSuite* found = m_testController->findTestSuite(m_project, TestSuiteName); QVERIFY(found); QCOMPARE(found->name(), QString(TestSuiteName)); QCOMPARE(found->project(), m_project); m_testController->removeTestSuite(&suite); } void TestTestController::removeSuite() { FakeTestSuite suite(TestSuiteName, m_project); m_testController->addTestSuite(&suite); QVERIFY(m_testController->findTestSuite(m_project, TestSuiteName)); m_testController->removeTestSuite(&suite); - QCOMPARE(m_testController->findTestSuite(m_project, TestSuiteName), (ITestSuite*)0); + QCOMPARE(m_testController->findTestSuite(m_project, TestSuiteName), (ITestSuite*)nullptr); QVERIFY(m_testController->testSuites().isEmpty()); } void TestTestController::replaceSuite() { FakeTestSuite* suiteOne = new FakeTestSuite(TestSuiteName, m_project, QStringList() << TestCaseNameOne); m_testController->addTestSuite(suiteOne); QCOMPARE(m_testController->findTestSuite(m_project, TestSuiteName)->name(), QString(TestSuiteName)); QCOMPARE(m_testController->findTestSuite(m_project, TestSuiteName)->cases().size(), 1); FakeTestSuite* suiteTwo = new FakeTestSuite(TestSuiteName, m_project, QStringList() << TestCaseNameOne << TestCaseNameTwo); m_testController->addTestSuite(suiteTwo); QCOMPARE(m_testController->testSuites().size(), 1); QCOMPARE(m_testController->findTestSuite(m_project, TestSuiteName)->name(), QString(TestSuiteName)); QCOMPARE(m_testController->findTestSuite(m_project, TestSuiteName)->cases().size(), 2); // TestController deletes the old suite when replacing it, so make sure we don't delete suiteOne manually m_testController->removeTestSuite(suiteTwo); delete suiteTwo; } void TestTestController::findByProject() { IProject* otherProject = new TestProject(Path(), this); ITestSuite* suiteOne = new FakeTestSuite(TestSuiteName, m_project); ITestSuite* suiteTwo = new FakeTestSuite(TestSuiteName, otherProject); m_testController->addTestSuite(suiteOne); m_testController->addTestSuite(suiteTwo); QCOMPARE(m_testController->testSuites().size(), 2); QCOMPARE(m_testController->testSuitesForProject(m_project).size(), 1); QCOMPARE(m_testController->testSuitesForProject(m_project).at(0), suiteOne); m_testController->removeTestSuite(suiteOne); m_testController->removeTestSuite(suiteTwo); delete suiteOne; delete suiteTwo; delete otherProject; } void TestTestController::testResults() { ITestSuite* suite = new FakeTestSuite(TestSuiteName, m_project); m_testController->addTestSuite(suite); QSignalSpy spy(m_testController, SIGNAL(testRunFinished(KDevelop::ITestSuite*,KDevelop::TestResult))); QVERIFY(spy.isValid()); QList results; results << TestResult::Passed << TestResult::Failed << TestResult::Error << TestResult::Skipped << TestResult::NotRun; foreach (const TestResult::TestCaseResult result, results) { emitTestResult(suite, result); QCOMPARE(spy.size(), 1); QVariantList arguments = spy.takeFirst(); QCOMPARE(arguments.size(), 2); QVERIFY(arguments.first().canConvert()); QCOMPARE(arguments.first().value(), suite); QVERIFY(arguments.at(1).canConvert()); QCOMPARE(arguments.at(1).value().suiteResult, result); foreach (const QString& testCase, suite->cases()) { QCOMPARE(arguments.at(1).value().testCaseResults[testCase], result); } } QCOMPARE(spy.size(), 0); ITestSuite* suiteTwo = new FakeTestSuite(TestSuiteNameTwo, m_project); m_testController->addTestSuite(suiteTwo); // Verify that only one signal gets emitted even with more suites present emitTestResult(suiteTwo, TestResult::Passed); QCOMPARE(spy.size(), 1); m_testController->removeTestSuite(suite); m_testController->removeTestSuite(suiteTwo); delete suite; delete suiteTwo; } QTEST_GUILESS_MAIN(TestTestController) diff --git a/shell/textdocument.cpp b/shell/textdocument.cpp index 2fa957647b..4c6f6e4a16 100644 --- a/shell/textdocument.cpp +++ b/shell/textdocument.cpp @@ -1,793 +1,793 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "textdocument.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 #include "core.h" #include "mainwindow.h" #include "uicontroller.h" #include "partcontroller.h" #include "plugincontroller.h" #include "documentcontroller.h" #include "debug.h" #include #include namespace KDevelop { const int MAX_DOC_SETTINGS = 20; // This sets cursor position and selection on the view to the given // range. Selection is set only for non-empty ranges // Factored into a function since its needed in 3 places already static void selectAndReveal( KTextEditor::View* view, const KTextEditor::Range& range ) { Q_ASSERT(view); if (range.isValid()) { view->setCursorPosition(range.start()); if (!range.isEmpty()) { view->setSelection(range); } } } struct TextDocumentPrivate { TextDocumentPrivate(TextDocument *textDocument) : document(nullptr), state(IDocument::Clean), encoding(), q(textDocument) - , m_loaded(false), m_addedContextMenu(0) + , m_loaded(false), m_addedContextMenu(nullptr) { } ~TextDocumentPrivate() { delete m_addedContextMenu; - m_addedContextMenu = 0; + m_addedContextMenu = nullptr; saveSessionConfig(); delete document; } QPointer document; IDocument::DocumentState state; QString encoding; void setStatus(KTextEditor::Document* document, bool dirty) { QIcon statusIcon; if (document->isModified()) if (dirty) { state = IDocument::DirtyAndModified; statusIcon = QIcon::fromTheme(QStringLiteral("edit-delete")); } else { state = IDocument::Modified; statusIcon = QIcon::fromTheme(QStringLiteral("document-save")); } else if (dirty) { state = IDocument::Dirty; statusIcon = QIcon::fromTheme(QStringLiteral("document-revert")); } else { state = IDocument::Clean; } q->notifyStateChanged(); Core::self()->uiControllerInternal()->setStatusIcon(q, statusIcon); } inline KConfigGroup katePartSettingsGroup() const { return KSharedConfig::openConfig()->group("KatePart Settings"); } inline QString docConfigGroupName() const { return document->url().toDisplayString(QUrl::PreferLocalFile); } inline KConfigGroup docConfigGroup() const { return katePartSettingsGroup().group(docConfigGroupName()); } void saveSessionConfig() { if(document && document->url().isValid()) { // make sure only MAX_DOC_SETTINGS entries are stored KConfigGroup katePartSettings = katePartSettingsGroup(); // ordered list of documents QStringList documents = katePartSettings.readEntry("documents", QStringList()); // ensure this document is "new", i.e. at the end of the list documents.removeOne(docConfigGroupName()); documents.append(docConfigGroupName()); // remove "old" documents + their group while(documents.size() >= MAX_DOC_SETTINGS) { katePartSettings.group(documents.takeFirst()).deleteGroup(); } // update order katePartSettings.writeEntry("documents", documents); // actually save session config KConfigGroup group = docConfigGroup(); document->writeSessionConfig(group); } } void loadSessionConfig() { if (!document || !katePartSettingsGroup().hasGroup(docConfigGroupName())) { return; } document->readSessionConfig(docConfigGroup(), {QStringLiteral("SkipUrl")}); } // Determines whether the current contents of this document in the editor // could be retrieved from the VCS if they were dismissed. void queryCanRecreateFromVcs(KTextEditor::Document* document) const { - IProject* project = 0; + IProject* project = nullptr; // Find projects by checking which one contains the file's parent directory, // to avoid issues with the cmake manager temporarily removing files from a project // during reloading. KDevelop::Path path(document->url()); foreach ( KDevelop::IProject* current, Core::self()->projectController()->projects() ) { if ( current->path().isParentOf(path) ) { project = current; break; } } if (!project) { return; } IContentAwareVersionControl* iface; iface = qobject_cast< KDevelop::IContentAwareVersionControl* >(project->versionControlPlugin()); if (!iface) { return; } if ( !qobject_cast( document ) ) { return; } CheckInRepositoryJob* req = iface->isInRepository( document ); if ( !req ) { return; } QObject::connect(req, &CheckInRepositoryJob::finished, q, &TextDocument::repositoryCheckFinished); // Abort the request when the user edits the document QObject::connect(q->textDocument(), &KTextEditor::Document::textChanged, req, &CheckInRepositoryJob::abort); } void modifiedOnDisk(KTextEditor::Document *document, bool /*isModified*/, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { bool dirty = false; switch (reason) { case KTextEditor::ModificationInterface::OnDiskUnmodified: break; case KTextEditor::ModificationInterface::OnDiskModified: case KTextEditor::ModificationInterface::OnDiskCreated: case KTextEditor::ModificationInterface::OnDiskDeleted: dirty = true; break; } // In some cases, the VCS (e.g. git) can know whether the old contents are "valuable", i.e. // not retrieveable from the VCS. If that is not the case, then the document can safely be // reloaded without displaying a dialog asking the user. if ( dirty ) { queryCanRecreateFromVcs(document); } setStatus(document, dirty); } TextDocument * const q; bool m_loaded; // we want to remove the added stuff when the menu hides QMenu* m_addedContextMenu; }; struct TextViewPrivate { TextViewPrivate(TextView* q) : q(q) {} TextView* const q; QPointer view; KTextEditor::Range initialRange; }; TextDocument::TextDocument(const QUrl &url, ICore* core, const QString& encoding) :PartDocument(url, core), d(new TextDocumentPrivate(this)) { d->encoding = encoding; } TextDocument::~TextDocument() { delete d; } bool TextDocument::isTextDocument() const { if( !d->document ) { /// @todo Somehow it can happen that d->document is zero, which makes /// code relying on "isTextDocument() == (bool)textDocument()" crash qWarning() << "Broken text-document: " << url(); return false; } return true; } KTextEditor::Document *TextDocument::textDocument() const { return d->document; } QWidget *TextDocument::createViewWidget(QWidget *parent) { - KTextEditor::View* view = 0L; + KTextEditor::View* view = nullptr; if (!d->document) { d->document = Core::self()->partControllerInternal()->createTextPart(); // Connect to the first text changed signal, it occurs before the completed() signal connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::slotDocumentLoaded); // Also connect to the completed signal, sometimes the first text changed signal is missed because the part loads too quickly (? TODO - confirm this is necessary) connect(d->document.data(), static_cast(&KTextEditor::Document::completed), this, &TextDocument::slotDocumentLoaded); // force a reparse when a document gets reloaded connect(d->document.data(), &KTextEditor::Document::reloaded, this, [] (KTextEditor::Document* document) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(document->url()), (TopDUContext::Features) ( TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate ), - BackgroundParser::BestPriority, 0); + BackgroundParser::BestPriority, nullptr); }); // Set encoding passed via constructor // Needs to be done before openUrl, else katepart won't use the encoding // @see KTextEditor::Document::setEncoding if (!d->encoding.isEmpty()) d->document->setEncoding(d->encoding); if (!url().isEmpty() && !DocumentController::isEmptyDocumentUrl(url())) d->document->openUrl( url() ); d->setStatus(d->document, false); /* It appears, that by default a part will be deleted the first view containing it is deleted. Since we do want to have several views, disable that behaviour. */ d->document->setAutoDeletePart(false); Core::self()->partController()->addPart(d->document, false); d->loadSessionConfig(); connect(d->document.data(), &KTextEditor::Document::modifiedChanged, this, &TextDocument::newDocumentStatus); connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::textChanged); connect(d->document.data(), &KTextEditor::Document::documentUrlChanged, this, &TextDocument::documentUrlChanged); connect(d->document.data(), &KTextEditor::Document::documentSavedOrUploaded, this, &TextDocument::documentSaved ); if (qobject_cast(d->document)) { // can't use new signal/slot syntax here, MarkInterface is not a QObject connect(d->document.data(), SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(saveSessionConfig())); } if (auto iface = qobject_cast(d->document)) { iface->setModifiedOnDiskWarning(true); // can't use new signal/slot syntax here, ModificationInterface is not a QObject connect(d->document.data(), SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } notifyTextDocumentCreated(); } view = d->document->createView(parent); // get rid of some actions regarding the config dialog, we merge that one into the kdevelop menu already delete view->actionCollection()->action(QStringLiteral("set_confdlg")); delete view->actionCollection()->action(QStringLiteral("editor_options")); view->setStatusBarEnabled(Core::self()->partControllerInternal()->showTextEditorStatusBar()); connect(view, &KTextEditor::View::contextMenuAboutToShow, this, &TextDocument::populateContextMenu); if (KTextEditor::CodeCompletionInterface* cc = dynamic_cast(view)) cc->setAutomaticInvocationEnabled(core()->languageController()->completionSettings()->automaticCompletionEnabled()); if (KTextEditor::ConfigInterface *config = qobject_cast(view)) { config->setConfigValue(QStringLiteral("allow-mark-menu"), false); config->setConfigValue(QStringLiteral("default-mark-type"), KTextEditor::MarkInterface::BreakpointActive); } return view; } KParts::Part *TextDocument::partForView(QWidget *view) const { if (d->document && d->document->views().contains((KTextEditor::View*)view)) return d->document; - return 0; + return nullptr; } // KDevelop::IDocument implementation void TextDocument::reload() { if (!d->document) return; - KTextEditor::ModificationInterface* modif=0; + KTextEditor::ModificationInterface* modif=nullptr; if(d->state==Dirty) { modif = qobject_cast(d->document); modif->setModifiedOnDiskWarning(false); } d->document->documentReload(); if(modif) modif->setModifiedOnDiskWarning(true); } bool TextDocument::save(DocumentSaveMode mode) { if (!d->document) return true; if (mode & Discard) return true; switch (d->state) { case IDocument::Clean: return true; case IDocument::Modified: break; case IDocument::Dirty: case IDocument::DirtyAndModified: if (!(mode & Silent)) { int code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The file \"%1\" is modified on disk.\n\nAre " "you sure you want to overwrite it? (External " "changes will be lost.)", d->document->url().toLocalFile()), i18nc("@title:window", "Document Externally Modified")); if (code != KMessageBox::Yes) return false; } break; } if (!KDevelop::ensureWritable(QList() << url())) { return false; } QUrl urlBeforeSave = d->document->url(); if (d->document->documentSave()) { if (d->document->url() != urlBeforeSave) notifyUrlChanged(); return true; } return false; } IDocument::DocumentState TextDocument::state() const { return d->state; } KTextEditor::Cursor KDevelop::TextDocument::cursorPosition() const { if (!d->document) { return KTextEditor::Cursor::invalid(); } KTextEditor::View *view = activeTextView(); if (view) return view->cursorPosition(); return KTextEditor::Cursor::invalid(); } void TextDocument::setCursorPosition(const KTextEditor::Cursor &cursor) { if (!cursor.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); // Rodda: Cursor must be accurate here, to the definition of accurate for KTextEditor::Cursor. // ie, starting from 0,0 if (view) view->setCursorPosition(cursor); } KTextEditor::Range TextDocument::textSelection() const { if (!d->document) { return KTextEditor::Range::invalid(); } KTextEditor::View *view = activeTextView(); if (view && view->selection()) { return view->selectionRange(); } return PartDocument::textSelection(); } QString TextDocument::text(const KTextEditor::Range &range) const { if (!d->document) { return QString(); } return d->document->text( range ); } QString TextDocument::textLine() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { return d->document->line( view->cursorPosition().line() ); } return PartDocument::textLine(); } QString TextDocument::textWord() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { KTextEditor::Cursor start = view->cursorPosition(); qCDebug(SHELL) << "got start position from view:" << start.line() << start.column(); QString linestr = textLine(); int startPos = qMax( qMin( start.column(), linestr.length() - 1 ), 0 ); int endPos = startPos; startPos --; while( startPos >= 0 && ( linestr[startPos].isLetterOrNumber() || linestr[startPos] == '_' || linestr[startPos] == '~' ) ) { --startPos; } while( endPos < linestr.length() && ( linestr[endPos].isLetterOrNumber() || linestr[endPos] == '_' || linestr[endPos] == '~' ) ) { ++endPos; } if( startPos != endPos ) { qCDebug(SHELL) << "found word" << startPos << endPos << linestr.mid( startPos+1, endPos - startPos - 1 ); return linestr.mid( startPos + 1, endPos - startPos - 1 ); } } return PartDocument::textWord(); } void TextDocument::setTextSelection(const KTextEditor::Range &range) { if (!range.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); if (view) { selectAndReveal(view, range); } } bool TextDocument::close(DocumentSaveMode mode) { if (!PartDocument::close(mode)) return false; if ( d->document ) { d->saveSessionConfig(); delete d->document; //We have to delete the document right now, to prevent random crashes in the event handler } return true; } Sublime::View* TextDocument::newView(Sublime::Document* doc) { Q_UNUSED(doc); return new TextView(this); } } KDevelop::TextView::TextView(TextDocument * doc) : View(doc, View::TakeOwnership), d(new TextViewPrivate(this)) { } KDevelop::TextView::~TextView() { delete d; } QWidget * KDevelop::TextView::createWidget(QWidget * parent) { auto textDocument = qobject_cast(document()); Q_ASSERT(textDocument); QWidget* widget = textDocument->createViewWidget(parent); d->view = qobject_cast(widget); Q_ASSERT(d->view); connect(d->view.data(), &KTextEditor::View::cursorPositionChanged, this, &KDevelop::TextView::sendStatusChanged); return widget; } QString KDevelop::TextView::viewState() const { if (d->view) { if (d->view->selection()) { KTextEditor::Range selection = d->view->selectionRange(); return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } else { KTextEditor::Cursor cursor = d->view->cursorPosition(); return QStringLiteral("Cursor=%1,%2").arg(cursor.line()).arg(cursor.column()); } } else { KTextEditor::Range selection = d->initialRange; return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } } void KDevelop::TextView::setInitialRange(const KTextEditor::Range& range) { if (d->view) { selectAndReveal(d->view, range); } else { d->initialRange = range; } } KTextEditor::Range KDevelop::TextView::initialRange() const { return d->initialRange; } void KDevelop::TextView::setState(const QString & state) { static QRegExp reCursor("Cursor=([\\d]+),([\\d]+)"); static QRegExp reSelection("Selection=([\\d]+),([\\d]+),([\\d]+),([\\d]+)"); if (reCursor.exactMatch(state)) { setInitialRange(KTextEditor::Range(KTextEditor::Cursor(reCursor.cap(1).toInt(), reCursor.cap(2).toInt()), 0)); } else if (reSelection.exactMatch(state)) { KTextEditor::Range range(reSelection.cap(1).toInt(), reSelection.cap(2).toInt(), reSelection.cap(3).toInt(), reSelection.cap(4).toInt()); setInitialRange(range); } } QString KDevelop::TextDocument::documentType() const { return QStringLiteral("Text"); } QIcon KDevelop::TextDocument::defaultIcon() const { if (d->document) { QMimeType mime = QMimeDatabase().mimeTypeForName(d->document->mimeType()); QIcon icon = QIcon::fromTheme(mime.iconName()); if (!icon.isNull()) { return icon; } } return PartDocument::defaultIcon(); } KTextEditor::View *KDevelop::TextView::textView() const { return d->view; } QString KDevelop::TextView::viewStatus() const { // only show status when KTextEditor's own status bar isn't already enabled const bool showStatus = !Core::self()->partControllerInternal()->showTextEditorStatusBar(); if (!showStatus) { return QString(); } const KTextEditor::Cursor pos = d->view ? d->view->cursorPosition() : KTextEditor::Cursor::invalid(); return i18n(" Line: %1 Col: %2 ", pos.line() + 1, pos.column() + 1); } void KDevelop::TextView::sendStatusChanged() { emit statusChanged(this); } KTextEditor::View* KDevelop::TextDocument::activeTextView() const { KTextEditor::View* fallback = nullptr; for (auto view : views()) { auto textView = qobject_cast(view)->textView(); if (!textView) { continue; } if (textView->hasFocus()) { return textView; } else if (textView->isVisible()) { fallback = textView; } else if (!fallback) { fallback = textView; } } return fallback; } void KDevelop::TextDocument::newDocumentStatus(KTextEditor::Document *document) { bool dirty = (d->state == IDocument::Dirty || d->state == IDocument::DirtyAndModified); d->setStatus(document, dirty); } void KDevelop::TextDocument::textChanged(KTextEditor::Document *document) { Q_UNUSED(document); notifyContentChanged(); } void KDevelop::TextDocument::populateContextMenu( KTextEditor::View* v, QMenu* menu ) { if (d->m_addedContextMenu) { foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->removeAction(action); } delete d->m_addedContextMenu; } d->m_addedContextMenu = new QMenu(); EditorContext c(v, v->cursorPosition()); auto extensions = Core::self()->pluginController()->queryPluginsForContextMenuExtensions(&c); ContextMenuExtension::populateMenu(d->m_addedContextMenu, extensions); { QUrl url = v->document()->url(); QList< ProjectBaseItem* > items = Core::self()->projectController()->projectModel()->itemsForPath( IndexedString(url) ); if (!items.isEmpty()) { populateParentItemsMenu( items.front(), d->m_addedContextMenu ); } } foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->addAction(action); } } void KDevelop::TextDocument::repositoryCheckFinished(bool canRecreate) { if ( d->state != IDocument::Dirty && d->state != IDocument::DirtyAndModified ) { // document is not dirty for whatever reason, nothing to do. return; } if ( ! canRecreate ) { return; } KTextEditor::ModificationInterface* modIface = qobject_cast( d->document ); Q_ASSERT(modIface); // Ok, all safe, we can clean up the document. Close it if the file is gone, // and reload if it's still there. d->setStatus(d->document, false); modIface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified); if ( QFile::exists(d->document->url().path()) ) { reload(); } else { close(KDevelop::IDocument::Discard); } } void KDevelop::TextDocument::slotDocumentLoaded() { if (d->m_loaded) return; // Tell the editor integrator first d->m_loaded = true; notifyLoaded(); } void KDevelop::TextDocument::documentSaved(KTextEditor::Document* document, bool saveAs) { Q_UNUSED(document); Q_UNUSED(saveAs); notifySaved(); notifyStateChanged(); } void KDevelop::TextDocument::documentUrlChanged(KTextEditor::Document* document) { Q_UNUSED(document); if (url() != d->document->url()) setUrl(d->document->url()); } #include "moc_textdocument.cpp" diff --git a/shell/uicontroller.cpp b/shell/uicontroller.cpp index 7a39a42c2a..aabb5c0529 100644 --- a/shell/uicontroller.cpp +++ b/shell/uicontroller.cpp @@ -1,752 +1,752 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "uicontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "configpage.h" #include "configdialog.h" #include "debug.h" #include "editorconfigpage.h" #include "shellextension.h" #include "partcontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "partdocument.h" #include "textdocument.h" #include "documentcontroller.h" #include #include "workingsetcontroller.h" #include "workingsets/workingset.h" #include "settings/bgpreferences.h" #include "settings/languagepreferences.h" #include "settings/environmentpreferences.h" #include "settings/pluginpreferences.h" #include "settings/projectpreferences.h" #include "settings/sourceformattersettings.h" #include "settings/uipreferences.h" #include "settings/templateconfig.h" #include "settings/analyzerspreferences.h" namespace KDevelop { class UiControllerPrivate { public: UiControllerPrivate(UiController *controller) : areasRestored(false), m_controller(controller) { if (Core::self()->workingSetControllerInternal()) Core::self()->workingSetControllerInternal()->initializeController(m_controller); m_controller->connect(m_controller, &Sublime::Controller::mainWindowAdded, m_controller, &UiController::mainWindowAdded); QMap desired; desired[QStringLiteral("org.kdevelop.ClassBrowserView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.DocumentsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.FileManagerView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProblemReporterView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.OutputView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.ContextBrowser")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.SnippetView")] = Sublime::Right; desired[QStringLiteral("org.kdevelop.ExternalScriptView")] = Sublime::Right; Sublime::Area* a = new Sublime::Area(m_controller, QStringLiteral("code"), i18n("Code")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("document-edit")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.debugger.VariablesView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.debugger.BreakpointsView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.StackView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.ConsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("debug"), i18n("Debug")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("tools-report-bug")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.PatchReview")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("review"), i18n("Review")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("applications-engineering")); m_controller->addDefaultArea(a); if(!(Core::self()->setupFlags() & Core::NoUi)) { defaultMainWindow = new MainWindow(m_controller); m_controller->addMainWindow(defaultMainWindow); activeSublimeWindow = defaultMainWindow; } else { - activeSublimeWindow = defaultMainWindow = 0; + activeSublimeWindow = defaultMainWindow = nullptr; } m_assistantTimer.setSingleShot(true); m_assistantTimer.setInterval(100); } void widgetChanged(QWidget*, QWidget* now) { if (now) { Sublime::MainWindow* win = qobject_cast(now->window()); if( win ) { activeSublimeWindow = win; } } } Core *core; QPointer defaultMainWindow; QHash factoryDocuments; QPointer activeSublimeWindow; bool areasRestored; /// QWidget implementing IToolViewActionListener interface, or null QPointer activeActionListener; QTimer m_assistantTimer; private: UiController *m_controller; }; class UiToolViewFactory: public Sublime::ToolFactory { public: UiToolViewFactory(IToolViewFactory *factory): m_factory(factory) {} ~UiToolViewFactory() override { delete m_factory; } - QWidget* create(Sublime::ToolDocument *doc, QWidget *parent = 0) override + QWidget* create(Sublime::ToolDocument *doc, QWidget *parent = nullptr) override { Q_UNUSED( doc ); return m_factory->create(parent); } QList< QAction* > contextMenuActions(QWidget* viewWidget) const override { return m_factory->contextMenuActions( viewWidget ); } QList toolBarActions( QWidget* viewWidget ) const override { return m_factory->toolBarActions( viewWidget ); } QString id() const override { return m_factory->id(); } private: IToolViewFactory *m_factory; }; class ViewSelectorItem: public QListWidgetItem { public: - ViewSelectorItem(const QString &text, QListWidget *parent = 0, int type = Type) + ViewSelectorItem(const QString &text, QListWidget *parent = nullptr, int type = Type) :QListWidgetItem(text, parent, type) {} IToolViewFactory *factory; }; class NewToolViewListWidget: public QListWidget { Q_OBJECT public: - NewToolViewListWidget(MainWindow *mw, QWidget* parent = 0) + NewToolViewListWidget(MainWindow *mw, QWidget* parent = nullptr) :QListWidget(parent), m_mw(mw) { connect(this, &NewToolViewListWidget::doubleClicked, this, &NewToolViewListWidget::addNewToolViewByDoubleClick); } Q_SIGNALS: void addNewToolView(MainWindow *mw, QListWidgetItem *item); private Q_SLOTS: void addNewToolViewByDoubleClick(QModelIndex index) { QListWidgetItem *item = itemFromIndex(index); // Disable item so that the toolview can not be added again. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); emit addNewToolView(m_mw, item); } private: MainWindow *m_mw; }; UiController::UiController(Core *core) - :Sublime::Controller(0), IUiController(), d(new UiControllerPrivate(this)) + :Sublime::Controller(nullptr), IUiController(), d(new UiControllerPrivate(this)) { setObjectName(QStringLiteral("UiController")); d->core = core; if (!defaultMainWindow() || (Core::self()->setupFlags() & Core::NoUi)) return; connect(qApp, &QApplication::focusChanged, this, [&] (QWidget* old, QWidget* now) { d->widgetChanged(old, now); } ); setupActions(); } UiController::~UiController() { delete d; } void UiController::setupActions() { } void UiController::mainWindowAdded(Sublime::MainWindow* mainWindow) { connect(mainWindow, &MainWindow::activeToolViewChanged, this, &UiController::slotActiveToolViewChanged); connect(mainWindow, &MainWindow::areaChanged, this, &UiController::slotAreaChanged); // also check after area reconstruction } // FIXME: currently, this always create new window. Probably, // should just rename it. void UiController::switchToArea(const QString &areaName, SwitchMode switchMode) { if (switchMode == ThisWindow) { showArea(areaName, activeSublimeWindow()); return; } MainWindow *main = new MainWindow(this); addMainWindow(main); showArea(areaName, main); main->initialize(); // WTF? First, enabling this code causes crashes since we // try to disconnect some already-deleted action, or something. // Second, this code will disconnection the clients from guiFactory // of the previous main window. Ick! #if 0 //we need to add all existing guiclients to the new mainwindow //@todo adymo: add only ones that belong to the area (when the area code is there) foreach (KXMLGUIClient *client, oldMain->guiFactory()->clients()) main->guiFactory()->addClient(client); #endif main->show(); } QWidget* UiController::findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags) { if(!d->areasRestored || !activeArea()) - return 0; + return nullptr; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { Sublime::ToolDocument *doc = dynamic_cast(view->document()); if(doc && doc->title() == name && view->widget()) { if(flags & Raise) view->requestRaise(); return view->widget(); } } - QWidget* ret = 0; + QWidget* ret = nullptr; if(flags & Create) { Sublime::ToolDocument* doc = d->factoryDocuments.value(factory); if(!doc) { doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments.insert(factory, doc); } Sublime::View* view = addToolViewToArea(factory, doc, activeArea()); if(view) ret = view->widget(); if(flags & Raise) findToolView(name, factory, Raise); } return ret; } void UiController::raiseToolView(QWidget* toolViewWidget) { if(!d->areasRestored) return; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { if(view->widget() == toolViewWidget) { view->requestRaise(); return; } } } void UiController::addToolView(const QString & name, IToolViewFactory *factory, FindFlags state) { if (!factory) return; qCDebug(SHELL) ; Sublime::ToolDocument *doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments[factory] = doc; /* Until areas are restored, we don't know which views should be really added, and which not, so we just record view availability. */ if (d->areasRestored && state != None) { foreach (Sublime::Area* area, allAreas()) { addToolViewToArea(factory, doc, area); } } } void KDevelop::UiController::raiseToolView(Sublime::View * view) { foreach( Sublime::Area* area, allAreas() ) { if( area->toolViews().contains( view ) ) area->raiseToolView( view ); } slotActiveToolViewChanged(view); } void UiController::slotAreaChanged(Sublime::Area*) { // this slot gets call if an area in *any* MainWindow changed // so let's first get the "active area" const auto area = activeSublimeWindow()->area(); if (area) { // walk through shown tool views and maku sure the const auto shownIds = area->shownToolViews(Sublime::AllPositions); foreach (Sublime::View* toolView, area->toolViews()) { if (shownIds.contains(toolView->document()->documentSpecifier())) { slotActiveToolViewChanged(toolView); } } } } void UiController::slotActiveToolViewChanged(Sublime::View* view) { if (!view) { return; } // record the last active tool view action listener if (qobject_cast(view->widget())) { d->activeActionListener = view->widget(); } } void KDevelop::UiController::removeToolView(IToolViewFactory *factory) { if (!factory) return; qCDebug(SHELL) ; //delete the tooldocument Sublime::ToolDocument *doc = d->factoryDocuments.value(factory); ///@todo adymo: on document deletion all its views shall be also deleted foreach (Sublime::View *view, doc->views()) { foreach (Sublime::Area *area, allAreas()) if (area->removeToolView(view)) view->deleteLater(); } d->factoryDocuments.remove(factory); delete doc; } Sublime::Area *UiController::activeArea() { Sublime::MainWindow *m = activeSublimeWindow(); if (m) return activeSublimeWindow()->area(); - return 0; + return nullptr; } Sublime::MainWindow *UiController::activeSublimeWindow() { return d->activeSublimeWindow; } MainWindow *UiController::defaultMainWindow() { return d->defaultMainWindow; } void UiController::initialize() { defaultMainWindow()->initialize(); } void UiController::cleanup() { foreach (Sublime::MainWindow* w, mainWindows()) w->saveSettings(); saveAllAreas(KSharedConfig::openConfig()); } void UiController::selectNewToolViewToAdd(MainWindow *mw) { if (!mw || !mw->area()) return; QDialog *dia = new QDialog(mw); dia->setWindowTitle(i18n("Select Tool View to Add")); auto mainLayout = new QVBoxLayout(dia); NewToolViewListWidget *list = new NewToolViewListWidget(mw, dia); list->setSelectionMode(QAbstractItemView::ExtendedSelection); list->setSortingEnabled(true); for (QHash::const_iterator it = d->factoryDocuments.constBegin(); it != d->factoryDocuments.constEnd(); ++it) { ViewSelectorItem *item = new ViewSelectorItem(it.value()->title(), list); item->factory = it.key(); if (!item->factory->allowMultiple() && toolViewPresent(it.value(), mw->area())) { // Disable item if the toolview is already present. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } list->addItem(item); } list->setFocus(); connect(list, &NewToolViewListWidget::addNewToolView, this, &UiController::addNewToolView); mainLayout->addWidget(list); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dia->connect(buttonBox, &QDialogButtonBox::accepted, dia, &QDialog::accept); dia->connect(buttonBox, &QDialogButtonBox::rejected, dia, &QDialog::reject); mainLayout->addWidget(buttonBox); if (dia->exec() == QDialog::Accepted) { foreach (QListWidgetItem* item, list->selectedItems()) { addNewToolView(mw, item); } } delete dia; } void UiController::addNewToolView(MainWindow *mw, QListWidgetItem* item) { ViewSelectorItem *current = static_cast(item); Sublime::ToolDocument *doc = d->factoryDocuments[current->factory]; Sublime::View *view = doc->createView(); mw->area()->addToolView(view, Sublime::dockAreaToPosition(current->factory->defaultPosition())); current->factory->viewCreated(view); } void UiController::showSettingsDialog() { auto parent = activeMainWindow(); auto editorConfigPage = new EditorConfigPage(parent); auto languageConfigPage = new LanguagePreferences(parent); auto analyzersPreferences = new AnalyzersPreferences(parent); auto configPages = QVector { new UiPreferences(parent), new PluginPreferences(parent), new SourceFormatterSettings(parent), new ProjectPreferences(parent), new EnvironmentPreferences(QString(), parent), new TemplateConfig(parent), editorConfigPage }; ConfigDialog cfgDlg(configPages, parent); auto addPluginPages = [&](IPlugin* plugin) { for (int i = 0, numPages = plugin->configPages(); i < numPages; ++i) { auto page = plugin->configPage(i, &cfgDlg); if (!page) continue; if (page->configPageType() == ConfigPage::LanguageConfigPage) { cfgDlg.addSubConfigPage(languageConfigPage, page); } else if (page->configPageType() == ConfigPage::AnalyzerConfigPage) { cfgDlg.addSubConfigPage(analyzersPreferences, page); } else { // insert them before the editor config page cfgDlg.addConfigPage(page, editorConfigPage); } } }; cfgDlg.addConfigPage(analyzersPreferences, configPages[5]); cfgDlg.addConfigPage(languageConfigPage, analyzersPreferences); cfgDlg.addSubConfigPage(languageConfigPage, new BGPreferences(parent)); foreach (IPlugin* plugin, ICore::self()->pluginController()->loadedPlugins()) { addPluginPages(plugin); } // TODO: only load settings if a UI related page was changed? connect(&cfgDlg, &ConfigDialog::configSaved, activeSublimeWindow(), &Sublime::MainWindow::loadSettings); // make sure that pages get added whenever a new plugin is loaded (probably from the plugin selection dialog) // removal on plugin unload is already handled in ConfigDialog connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, &cfgDlg, addPluginPages); cfgDlg.exec(); } Sublime::Controller* UiController::controller() { return this; } KParts::MainWindow *UiController::activeMainWindow() { return activeSublimeWindow(); } void UiController::saveArea(Sublime::Area * area, KConfigGroup & group) { area->save(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); set->saveFromArea(area, area->rootIndex()); } } void UiController::loadArea(Sublime::Area * area, const KConfigGroup & group) { area->load(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); Q_ASSERT(set->isConnected(area)); Q_UNUSED(set); } } void UiController::saveAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = mainWindows().size(); uiConfig.writeEntry("Main Windows Count", wc); for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); foreach (Sublime::Area* defaultArea, defaultAreas()) { // FIXME: using object name seems ugly. QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); areaConfig.deleteGroup(); areaConfig.writeEntry("id", type); saveArea(area, areaConfig); areaConfig.sync(); } } uiConfig.sync(); } void UiController::loadAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = uiConfig.readEntry("Main Windows Count", 1); /* It is expected the main windows are restored before restoring areas. */ if (wc > mainWindows().size()) wc = mainWindows().size(); QList changedAreas; /* Offer all toolviews to the default areas. */ foreach (Sublime::Area *area, defaultAreas()) { QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } /* Restore per-windows areas. */ for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); Sublime::MainWindow *mw = mainWindows()[w]; /* We loop over default areas. This means that if the config file has an area of some type that is not in default set, we'd just ignore it. I think it's fine -- the model were a given mainwindow can has it's own area types not represented in the default set is way too complex. */ foreach (Sublime::Area* defaultArea, defaultAreas()) { QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); qCDebug(SHELL) << "Trying to restore area " << type; /* This is just an easy check that a group exists, to avoid "restoring" area from empty config group, wiping away programmatically installed defaults. */ if (areaConfig.readEntry("id", "") == type) { qCDebug(SHELL) << "Restoring area " << type; loadArea(area, areaConfig); } // At this point we know which toolviews the area wants. // Tender all tool views we have. QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } // Force reload of the changes. showAreaInternal(mw->area(), mw); mw->enableAreaSettingsSave(); } d->areasRestored = true; } void UiController::addToolViewToDockArea(IToolViewFactory* factory, Qt::DockWidgetArea area) { addToolViewToArea(factory, d->factoryDocuments.value(factory), activeArea(), Sublime::dockAreaToPosition(area)); } bool UiController::toolViewPresent(Sublime::ToolDocument* doc, Sublime::Area* area) { foreach (Sublime::View *view, doc->views()) { if( area->toolViews().contains( view ) ) return true; } return false; } void UiController::addToolViewIfWanted(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area) { if (area->wantToolView(factory->id())) { addToolViewToArea(factory, doc, area); } } Sublime::View* UiController::addToolViewToArea(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area, Sublime::Position p) { Sublime::View* view = doc->createView(); area->addToolView( view, p == Sublime::AllPositions ? Sublime::dockAreaToPosition(factory->defaultPosition()) : p); connect(view, &Sublime::View::raise, this, static_cast(&UiController::raiseToolView)); factory->viewCreated(view); return view; } void UiController::registerStatus(QObject* status) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; mw->registerStatus(status); } void UiController::showErrorMessage(const QString& message, int timeout) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; QMetaObject::invokeMethod(mw, "showErrorMessage", Q_ARG(QString, message), Q_ARG(int, timeout)); } const QHash< IToolViewFactory*, Sublime::ToolDocument* >& UiController::factoryDocuments() const { return d->factoryDocuments; } QWidget* UiController::activeToolViewActionListener() const { return d->activeActionListener; } QList UiController::allAreas() const { return Sublime::Controller::allAreas(); } } #include "uicontroller.moc" #include "moc_uicontroller.cpp" diff --git a/shell/watcheddocumentset.cpp b/shell/watcheddocumentset.cpp index 029fd7cfac..367b20aab6 100644 --- a/shell/watcheddocumentset.cpp +++ b/shell/watcheddocumentset.cpp @@ -1,187 +1,187 @@ /* * KDevelop Problem Reporter * * Copyright 2010 Dmitry Risenberg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "watcheddocumentset.h" #include #include #include #include #include #include #include namespace KDevelop { WatchedDocumentSet::WatchedDocumentSet(QObject* parent) :QObject(parent) { } void WatchedDocumentSet::setCurrentDocument(const IndexedString&) { } WatchedDocumentSet::DocumentSet WatchedDocumentSet::get() const { return m_documents; } CurrentDocumentSet::CurrentDocumentSet(const IndexedString& document, QObject *parent) : WatchedDocumentSet(parent) { m_documents.insert(document); } void CurrentDocumentSet::setCurrentDocument(const IndexedString& url) { m_documents.clear(); m_documents.insert(url); emit changed(); } ProblemScope CurrentDocumentSet::getScope() const { return CurrentDocument; } OpenDocumentSet::OpenDocumentSet(QObject *parent) : WatchedDocumentSet(parent) { QList docs = ICore::self()->documentController()->openDocuments(); foreach (IDocument* doc, docs) { m_documents.insert(IndexedString(doc->url())); } connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &OpenDocumentSet::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &OpenDocumentSet::documentCreated); } void OpenDocumentSet::documentClosed(IDocument* doc) { if (m_documents.remove(IndexedString(doc->url()))) { emit changed(); } } void OpenDocumentSet::documentCreated(IDocument* doc) { m_documents.insert(IndexedString(doc->url())); emit changed(); } ProblemScope OpenDocumentSet::getScope() const { return OpenDocuments; } ProjectSet::ProjectSet(QObject *parent) : WatchedDocumentSet(parent) { } void ProjectSet::fileAdded(ProjectFileItem* file) { m_documents.insert(file->indexedPath()); emit changed(); } void ProjectSet::fileRemoved(ProjectFileItem* file) { if (m_documents.remove(file->indexedPath())) { emit changed(); } } void ProjectSet::fileRenamed(const Path& oldFile, ProjectFileItem* newFile) { if (m_documents.remove(IndexedString(oldFile.pathOrUrl()))) { m_documents.insert(newFile->indexedPath()); } } void ProjectSet::trackProjectFiles(const IProject* project) { if (project) { // The implementation should derive from QObject somehow QObject* fileManager = dynamic_cast(project->projectFileManager()); if (fileManager) { // can't use new signal/slot syntax here, IProjectFileManager is no a QObject connect(fileManager, SIGNAL(fileAdded(ProjectFileItem*)), this, SLOT(fileAdded(ProjectFileItem*))); connect(fileManager, SIGNAL(fileRemoved(ProjectFileItem*)), this, SLOT(fileRemoved(ProjectFileItem*))); connect(fileManager, SIGNAL(fileRenamed(Path,ProjectFileItem*)), this, SLOT(fileRenamed(Path,ProjectFileItem*))); } } } CurrentProjectSet::CurrentProjectSet(const IndexedString& document, QObject *parent) - : ProjectSet(parent), m_currentProject(0) + : ProjectSet(parent), m_currentProject(nullptr) { setCurrentDocumentInternal(document); trackProjectFiles(m_currentProject); } void CurrentProjectSet::setCurrentDocument(const IndexedString& url) { setCurrentDocumentInternal(url); } void CurrentProjectSet::setCurrentDocumentInternal(const IndexedString& url) { IProject* projectForUrl = ICore::self()->projectController()->findProjectForUrl(url.toUrl()); if (projectForUrl && projectForUrl != m_currentProject) { m_documents.clear(); m_currentProject = projectForUrl; foreach (const IndexedString &indexedString, m_currentProject->fileSet()) { m_documents.insert(indexedString); } emit changed(); } } ProblemScope CurrentProjectSet::getScope() const { return CurrentProject; } AllProjectSet::AllProjectSet(QObject *parent) : ProjectSet(parent) { foreach(const IProject* project, ICore::self()->projectController()->projects()) { foreach (const IndexedString &indexedString, project->fileSet()) { m_documents.insert(indexedString); } trackProjectFiles(project); } } ProblemScope AllProjectSet::getScope() const { return AllProjects; } } diff --git a/shell/workingsetcontroller.cpp b/shell/workingsetcontroller.cpp index 235e312132..1aa4a06eda 100644 --- a/shell/workingsetcontroller.cpp +++ b/shell/workingsetcontroller.cpp @@ -1,341 +1,341 @@ /* Copyright David Nolden 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. */ #include "workingsetcontroller.h" #include #include #include "mainwindow.h" #include "partdocument.h" #include "uicontroller.h" #include #include #include #include #include #include "workingsets/workingset.h" #include "workingsets/workingsettooltipwidget.h" #include "workingsets/workingsetwidget.h" #include "workingsets/closedworkingsetswidget.h" #include "core.h" #include "debug.h" using namespace KDevelop; const int toolTipTimeout = 2000; WorkingSetController::WorkingSetController() - : m_emptyWorkingSet(0), m_changingWorkingSet(false) + : m_emptyWorkingSet(nullptr), m_changingWorkingSet(false) { m_hideToolTipTimer = new QTimer(this); m_hideToolTipTimer->setInterval(toolTipTimeout); m_hideToolTipTimer->setSingleShot(true); } void WorkingSetController::initialize() { //Load all working-sets KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); foreach(const QString& set, setConfig.groupList()) { // do not load working set if the id contains an '|', because it then belongs to an area. // this is functionally equivalent to the if ( ! config->icon ) stuff which was there before. if ( set.contains('|') ) { continue; } getWorkingSet(set); } m_emptyWorkingSet = new WorkingSet(QStringLiteral("empty")); if(!(Core::self()->setupFlags() & Core::NoUi)) { setupActions(); } } void WorkingSetController::cleanup() { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { foreach (Sublime::Area *area, window->areas()) { if (!area->workingSet().isEmpty()) { Q_ASSERT(m_workingSets.contains(area->workingSet())); m_workingSets[area->workingSet()]->saveFromArea(area, area->rootIndex()); } } } foreach(WorkingSet* set, m_workingSets) { qCDebug(SHELL) << "set" << set->id() << "persistent" << set->isPersistent() << "has areas:" << set->hasConnectedAreas() << "files" << set->fileList(); if(!set->isPersistent() && !set->hasConnectedAreas()) { qCDebug(SHELL) << "deleting"; set->deleteSet(true, true); } delete set; } m_workingSets.clear(); delete m_emptyWorkingSet; - m_emptyWorkingSet = 0; + m_emptyWorkingSet = nullptr; } const QString WorkingSetController::makeSetId(const QString& prefix) const { QString newId; const uint maxRetries = 10; for(uint retry = 2; retry <= maxRetries; retry++) { newId = QStringLiteral("%1_%2").arg(prefix).arg(qrand() % 10000000); WorkingSetIconParameters params(newId); foreach(WorkingSet* set, m_workingSets) { if(set->isEmpty()) { continue; } // The last retry will always generate a valid set if(retry != maxRetries && WorkingSetIconParameters(set->id()).similarity(params) >= retry*8) { newId = QString(); break; } } if(! newId.isEmpty()) { break; } } return newId; } WorkingSet* WorkingSetController::newWorkingSet(const QString& prefix) { return getWorkingSet(makeSetId(prefix)); } WorkingSet* WorkingSetController::getWorkingSet(const QString& id) { if(id.isEmpty()) return m_emptyWorkingSet; if(!m_workingSets.contains(id)) { WorkingSet* set = new WorkingSet(id); connect(set, &WorkingSet::aboutToRemove, this, &WorkingSetController::aboutToRemoveWorkingSet); m_workingSets[id] = set; emit workingSetAdded(set); } return m_workingSets[id]; } QWidget* WorkingSetController::createSetManagerWidget(MainWindow* parent, Sublime::Area* fixedArea) { if (fixedArea) { return new WorkingSetWidget(fixedArea, parent); } else { return new ClosedWorkingSetsWidget(parent); } } void WorkingSetController::setupActions() { /* KActionCollection * ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = ac->addAction ( "view_next_window" ); action->setText( i18n( "Next Document" ) ); action->setIcon( QIcon::fromTheme("go-next") ); action->setShortcut( Qt::ALT + Qt::SHIFT + Qt::Key_Right ); action->setWhatsThis( i18n( "Switch the focus to the next open document." ) ); action->setStatusTip( i18n( "Switch the focus to the next open document." ) ); connect( action, SIGNAL(triggered()), this, SLOT(nextDocument()) ); action = ac->addAction ( "view_previous_window" ); action->setText( i18n( "Previous Document" ) ); action->setIcon( QIcon::fromTheme("go-previous") ); action->setShortcut( Qt::ALT + Qt::SHIFT + Qt::Key_Left ); action->setWhatsThis( i18n( "Switch the focus to the previous open document." ) ); action->setStatusTip( i18n( "Switch the focus to the previous open document." ) ); connect( action, SIGNAL(triggered()), this, SLOT(previousDocument()) ); */ } ActiveToolTip* WorkingSetController::tooltip() const { return m_tooltip; } void WorkingSetController::showToolTip(WorkingSet* set, const QPoint& pos) { delete m_tooltip; KDevelop::MainWindow* window = static_cast(Core::self()->uiControllerInternal()->activeMainWindow()); m_tooltip = new KDevelop::ActiveToolTip(window, pos); QVBoxLayout* layout = new QVBoxLayout(m_tooltip); layout->setMargin(0); WorkingSetToolTipWidget* widget = new WorkingSetToolTipWidget(m_tooltip, set, window); layout->addWidget(widget); m_tooltip->resize( m_tooltip->sizeHint() ); connect(widget, &WorkingSetToolTipWidget::shouldClose, m_tooltip.data(), &ActiveToolTip::close); ActiveToolTip::showToolTip(m_tooltip); } void WorkingSetController::showGlobalToolTip() { KDevelop::MainWindow* window = static_cast(Core::self()->uiControllerInternal()->activeMainWindow()); showToolTip(getWorkingSet(window->area()->workingSet()), window->mapToGlobal(window->geometry().topRight())); connect(m_hideToolTipTimer, &QTimer::timeout, m_tooltip.data(), &ActiveToolTip::deleteLater); m_hideToolTipTimer->start(); connect(m_tooltip.data(), &ActiveToolTip::mouseIn, m_hideToolTipTimer, &QTimer::stop); connect(m_tooltip.data(), &ActiveToolTip::mouseOut, m_hideToolTipTimer, static_cast(&QTimer::start)); } WorkingSetToolTipWidget* WorkingSetController::workingSetToolTip() { if(!m_tooltip) showGlobalToolTip(); m_hideToolTipTimer->stop(); m_hideToolTipTimer->start(toolTipTimeout); if(m_tooltip) { WorkingSetToolTipWidget* widget = m_tooltip->findChild(); Q_ASSERT(widget); return widget; } return nullptr; } void WorkingSetController::nextDocument() { auto widget = workingSetToolTip(); if (widget) { widget->nextDocument(); } } void WorkingSetController::previousDocument() { auto widget = workingSetToolTip(); if (widget) { widget->previousDocument(); } } void WorkingSetController::initializeController( UiController* controller ) { connect( controller, &UiController::areaCreated, this, &WorkingSetController::areaCreated ); } QList< WorkingSet* > WorkingSetController::allWorkingSets() const { return m_workingSets.values(); } void WorkingSetController::areaCreated( Sublime::Area* area ) { if (!area->workingSet().isEmpty()) { WorkingSet* set = getWorkingSet( area->workingSet() ); set->connectArea( area ); } connect(area, &Sublime::Area::changingWorkingSet, this, &WorkingSetController::changingWorkingSet); connect(area, &Sublime::Area::changedWorkingSet, this, &WorkingSetController::changedWorkingSet); connect(area, &Sublime::Area::viewAdded, this, &WorkingSetController::viewAdded); connect(area, &Sublime::Area::clearWorkingSet, this, &WorkingSetController::clearWorkingSet); } void WorkingSetController::changingWorkingSet(Sublime::Area* area, const QString& from, const QString& to) { qCDebug(SHELL) << "changing working-set from" << from << "to" << to << "area" << area; if (from == to) return; if (!from.isEmpty()) { WorkingSet* oldSet = getWorkingSet(from); oldSet->disconnectArea(area); if (!oldSet->id().isEmpty()) { oldSet->saveFromArea(area, area->rootIndex()); } } } void WorkingSetController::changedWorkingSet(Sublime::Area* area, const QString& from, const QString& to) { qCDebug(SHELL) << "changed working-set from" << from << "to" << to << "area" << area; if (from == to || m_changingWorkingSet) return; if (!to.isEmpty()) { WorkingSet* newSet = getWorkingSet(to); newSet->connectArea(area); newSet->loadToArea(area, area->rootIndex()); }else{ // Clear silently, any user-interaction should have happened before area->clearViews(true); } emit workingSetSwitched(); } void WorkingSetController::viewAdded( Sublime::AreaIndex* , Sublime::View* ) { Sublime::Area* area = qobject_cast< Sublime::Area* >(sender()); Q_ASSERT(area); if (area->workingSet().isEmpty()) { //Spawn a new working-set m_changingWorkingSet = true; WorkingSet* set = Core::self()->workingSetControllerInternal()->newWorkingSet(area->objectName()); qCDebug(SHELL) << "Spawned new working-set" << set->id() << "because a view was added"; set->connectArea(area); set->saveFromArea(area, area->rootIndex()); area->setWorkingSet(set->id()); m_changingWorkingSet = false; } } void WorkingSetController::clearWorkingSet(Sublime::Area * area) { const QString workingSet = area->workingSet(); if (workingSet.isEmpty()) { // Nothing to do - area has no working set return; } WorkingSet* set = getWorkingSet(workingSet); set->deleteSet(true); WorkingSet* newSet = getWorkingSet(workingSet); newSet->connectArea(area); newSet->loadToArea(area, area->rootIndex()); Q_ASSERT(newSet->fileList().isEmpty()); } diff --git a/shell/workingsets/closedworkingsetswidget.cpp b/shell/workingsets/closedworkingsetswidget.cpp index ef8e887369..27a87d8d1a 100644 --- a/shell/workingsets/closedworkingsetswidget.cpp +++ b/shell/workingsets/closedworkingsetswidget.cpp @@ -1,131 +1,131 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2010 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "closedworkingsetswidget.h" #include #include #include "mainwindow.h" #include "workingsetcontroller.h" #include "workingset.h" #include "workingsettoolbutton.h" #include "../debug.h" using namespace KDevelop; WorkingSet* getWorkingSet(const QString& id) { return Core::self()->workingSetControllerInternal()->getWorkingSet(id); } ClosedWorkingSetsWidget::ClosedWorkingSetsWidget( MainWindow* window ) - : QWidget(0), m_mainWindow(window) + : QWidget(nullptr), m_mainWindow(window) { connect(window, &MainWindow::areaChanged, this, &ClosedWorkingSetsWidget::areaChanged); m_layout = new QHBoxLayout(this); m_layout->setMargin(0); if (window->area()) { areaChanged(window->area()); } connect(Core::self()->workingSetControllerInternal(), &WorkingSetController::aboutToRemoveWorkingSet, this, &ClosedWorkingSetsWidget::removeWorkingSet); connect(Core::self()->workingSetControllerInternal(), &WorkingSetController::workingSetAdded, this, &ClosedWorkingSetsWidget::addWorkingSet); } void ClosedWorkingSetsWidget::areaChanged( Sublime::Area* area ) { if (m_connectedArea) { disconnect(area, &Sublime::Area::changedWorkingSet, this, &ClosedWorkingSetsWidget::changedWorkingSet); } m_connectedArea = area; connect(m_connectedArea.data(), &Sublime::Area::changedWorkingSet, this, &ClosedWorkingSetsWidget::changedWorkingSet); // clear layout qDeleteAll(m_buttons); m_buttons.clear(); // add sets from new area foreach(WorkingSet* set, Core::self()->workingSetControllerInternal()->allWorkingSets()) { addWorkingSet(set); } } void ClosedWorkingSetsWidget::changedWorkingSet( Sublime::Area* area, const QString& from, const QString& to ) { Q_ASSERT(area == m_connectedArea); Q_UNUSED(area); if (!from.isEmpty()) { WorkingSet* oldSet = getWorkingSet(from); addWorkingSet(oldSet); } if (!to.isEmpty()) { WorkingSet* newSet = getWorkingSet(to); removeWorkingSet(newSet); } } void ClosedWorkingSetsWidget::removeWorkingSet( WorkingSet* set ) { delete m_buttons.take(set); Q_ASSERT(m_buttons.size() == m_layout->count()); setVisible(!m_buttons.isEmpty()); } void ClosedWorkingSetsWidget::addWorkingSet( WorkingSet* set ) { if (m_buttons.contains(set)) { return; } // Don't show working-sets that are active in an area belong to this main-window, as those // can be activated directly through the icons in the tabs if (set->hasConnectedAreas(m_mainWindow->areas())) { return; } if (set->isEmpty()) { // qCDebug(SHELL) << "skipping" << set->id() << "because empty"; return; } // qCDebug(SHELL) << "adding button for" << set->id(); WorkingSetToolButton* button = new WorkingSetToolButton(this, set); button->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored)); m_layout->addWidget(button); m_buttons[set] = button; setVisible(!m_buttons.isEmpty()); } diff --git a/shell/workingsets/workingset.cpp b/shell/workingsets/workingset.cpp index 6bf9d531fd..722fd742c3 100644 --- a/shell/workingsets/workingset.cpp +++ b/shell/workingsets/workingset.cpp @@ -1,565 +1,565 @@ /* Copyright David Nolden 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. */ #include "workingset.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define SYNC_OFTEN using namespace KDevelop; bool WorkingSet::m_loading = false; namespace { QIcon generateIcon(const WorkingSetIconParameters& params) { QImage pixmap(16, 16, QImage::Format_ARGB32); // fill the background with a transparent color pixmap.fill(QColor::fromRgba(qRgba(0, 0, 0, 0))); const uint coloredCount = params.coloredCount; // coordinates of the rectangles to draw, for 16x16 icons specifically QList rects; rects << QRect(1, 1, 5, 5) << QRect(1, 9, 5, 5) << QRect(9, 1, 5, 5) << QRect(9, 9, 5, 5); if ( params.swapDiagonal ) { rects.swap(1, 2); } QPainter painter(&pixmap); // color for non-colored squares, paint them brighter if the working set is the active one const int inact = 40; QColor darkColor = QColor::fromRgb(inact, inact, inact); // color for colored squares // this code is not fragile, you can just tune the magic formulas at random and see what looks good. // just make sure to keep it within the 0-360 / 0-255 / 0-255 space of the HSV model QColor brightColor = QColor::fromHsv(params.hue, qMin(255, 215 + (params.setId*5) % 150), 205 + (params.setId*11) % 50); // Y'UV "Y" value, the approximate "lightness" of the color // If it is above 0.6, then making the color darker a bit is okay, // if it is below 0.35, then the color should be a bit brighter. float brightY = 0.299 * brightColor.redF() + 0.587 * brightColor.greenF() + 0.114 * brightColor.blueF(); if ( brightY > 0.6 ) { if ( params.setId % 7 < 2 ) { // 2/7 chance to make the color significantly darker brightColor = brightColor.darker(120 + (params.setId*7) % 35); } else if ( params.setId % 5 == 0 ) { // 1/5 chance to make it a bit darker brightColor = brightColor.darker(110 + (params.setId*3) % 10); } } if ( brightY < 0.35 ) { // always make the color brighter to avoid very dark colors (like rgb(0, 0, 255)) brightColor = brightColor.lighter(120 + (params.setId*13) % 55); } int at = 0; foreach ( const QRect rect, rects ) { QColor currentColor; // pick the colored squares; you can get different patterns by re-ordering the "rects" list if ( (at + params.setId*7) % 4 < coloredCount ) { currentColor = brightColor; } else { currentColor = darkColor; } // draw the filling of the square painter.setPen(QColor(currentColor)); painter.setBrush(QBrush(currentColor)); painter.drawRect(rect); // draw a slight set-in shadow for the square -- it's barely recognizeable, // but it looks way better than without painter.setBrush(Qt::NoBrush); painter.setPen(QColor(0, 0, 0, 50)); painter.drawRect(rect); painter.setPen(QColor(0, 0, 0, 25)); painter.drawRect(rect.x() + 1, rect.y() + 1, rect.width() - 2, rect.height() - 2); at += 1; } return QIcon(QPixmap::fromImage(pixmap)); } } WorkingSet::WorkingSet(const QString& id) : QObject() , m_id(id) , m_icon(generateIcon(id)) { } void WorkingSet::saveFromArea( Sublime::Area* a, Sublime::AreaIndex * area, KConfigGroup setGroup, KConfigGroup areaGroup ) { if (area->isSplit()) { setGroup.writeEntry("Orientation", area->orientation() == Qt::Horizontal ? "Horizontal" : "Vertical"); if (area->first()) { saveFromArea(a, area->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0")); } if (area->second()) { saveFromArea(a, area->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1")); } } else { setGroup.writeEntry("View Count", area->viewCount()); areaGroup.writeEntry("View Count", area->viewCount()); int index = 0; foreach (Sublime::View* view, area->views()) { //The working set config gets an updated list of files QString docSpec = view->document()->documentSpecifier(); //only save the documents from protocols KIO understands //otherwise we try to load kdev:// too early if (!KProtocolInfo::isKnownProtocol(QUrl(docSpec))) { continue; } setGroup.writeEntry(QStringLiteral("View %1").arg(index), docSpec); //The area specific config stores the working set documents in order along with their state areaGroup.writeEntry(QStringLiteral("View %1").arg(index), docSpec); areaGroup.writeEntry(QStringLiteral("View %1 State").arg(index), view->viewState()); ++index; } } } bool WorkingSet::isEmpty() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return !group.hasKey("Orientation") && group.readEntry("View Count", 0) == 0; } struct DisableMainWindowUpdatesFromArea { DisableMainWindowUpdatesFromArea(Sublime::Area* area) : m_area(area) { if(area) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { if(window->updatesEnabled()) { wasUpdatesEnabled.insert(window); window->setUpdatesEnabled(false); } } } } } ~DisableMainWindowUpdatesFromArea() { if(m_area) { foreach(Sublime::MainWindow* window, wasUpdatesEnabled) { window->setUpdatesEnabled(wasUpdatesEnabled.contains(window)); } } } Sublime::Area* m_area; QSet wasUpdatesEnabled; }; void loadFileList(QStringList& ret, KConfigGroup group) { if (group.hasKey("Orientation")) { QStringList subgroups = group.groupList(); if (subgroups.contains(QStringLiteral("0"))) { { KConfigGroup subgroup(&group, "0"); loadFileList(ret, subgroup); } if (subgroups.contains(QStringLiteral("1"))) { KConfigGroup subgroup(&group, "1"); loadFileList(ret, subgroup); } } } else { int viewCount = group.readEntry("View Count", 0); for (int i = 0; i < viewCount; ++i) { QString specifier = group.readEntry(QStringLiteral("View %1").arg(i), QString()); ret << specifier; } } } QStringList WorkingSet::fileList() const { QStringList ret; KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); loadFileList(ret, group); return ret; } void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { PushValue enableLoading(m_loading, true); /// We cannot disable the updates here, because (probably) due to a bug in Qt, /// which causes the updates to stay disabled forever after some complex operations /// on the sub-views. This could be reproduced by creating two working-sets with complex /// split-view configurations and switching between them. Re-enabling the updates doesn't help. // DisableMainWindowUpdatesFromArea updatesDisabler(area); qCDebug(SHELL) << "loading working-set" << m_id << "into area" << area; QMultiMap recycle; foreach( Sublime::View* view, area->views() ) recycle.insert( view->document()->documentSpecifier(), area->removeView(view) ); qCDebug(SHELL) << "recycling" << recycle.size() << "old views"; Q_ASSERT( area->views().empty() ); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); loadToArea(area, areaIndex, setGroup, areaGroup, recycle); // Delete views which were not recycled qCDebug(SHELL) << "deleting " << recycle.size() << " old views"; qDeleteAll( recycle ); - area->setActiveView(0); + area->setActiveView(nullptr); //activate view in the working set /// @todo correctly select one out of multiple equal views QString activeView = areaGroup.readEntry("Active View", QString()); foreach (Sublime::View *v, area->views()) { if (v->document()->documentSpecifier() == activeView) { area->setActiveView(v); break; } } if( !area->activeView() && !area->views().isEmpty() ) area->setActiveView( area->views().at(0) ); if( area->activeView() ) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { window->activateView( area->activeView() ); } } } } void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup, QMultiMap& recycle) { Q_ASSERT( !areaIndex->isSplit() ); if (setGroup.hasKey("Orientation")) { QStringList subgroups = setGroup.groupList(); /// @todo also save and restore the ratio if (subgroups.contains(QStringLiteral("0")) && subgroups.contains(QStringLiteral("1"))) { // qCDebug(SHELL) << "has zero, split:" << split; Qt::Orientation orientation = setGroup.readEntry("Orientation", "Horizontal") == QLatin1String("Vertical") ? Qt::Vertical : Qt::Horizontal; if(!areaIndex->isSplit()){ areaIndex->split(orientation); }else{ areaIndex->setOrientation(orientation); } loadToArea(area, areaIndex->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0"), recycle); loadToArea(area, areaIndex->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1"), recycle); if( areaIndex->first()->viewCount() == 0 ) areaIndex->unsplit(areaIndex->first()); else if( areaIndex->second()->viewCount() == 0 ) areaIndex->unsplit(areaIndex->second()); } } else { //Load all documents from the workingset into this areaIndex int viewCount = setGroup.readEntry("View Count", 0); QMap createdViews; for (int i = 0; i < viewCount; ++i) { QString specifier = setGroup.readEntry(QStringLiteral("View %1").arg(i), QString()); if (specifier.isEmpty()) { continue; } - Sublime::View* previousView = area->views().empty() ? 0 : area->views().at(area->views().size() - 1); + Sublime::View* previousView = area->views().empty() ? nullptr : area->views().at(area->views().size() - 1); QMultiMap::iterator it = recycle.find( specifier ); if( it != recycle.end() ) { area->addView( *it, areaIndex, previousView ); recycle.erase( it ); continue; } IDocument* doc = Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(specifier), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *document = dynamic_cast(doc); if (document) { Sublime::View* view = document->createView(); area->addView(view, areaIndex, previousView); createdViews[i] = view; } else { qWarning() << "Unable to create view" << specifier; } } //Load state for (int i = 0; i < viewCount; ++i) { QString state = areaGroup.readEntry(QStringLiteral("View %1 State").arg(i)); if (!state.isEmpty() && createdViews.contains(i)) createdViews[i]->setState(state); } } } void deleteGroupRecursive(KConfigGroup group) { // qCDebug(SHELL) << "deleting" << group.name(); foreach(const QString& entry, group.entryMap().keys()) { group.deleteEntry(entry); } Q_ASSERT(group.entryMap().isEmpty()); foreach(const QString& subGroup, group.groupList()) { deleteGroupRecursive(group.group(subGroup)); group.deleteGroup(subGroup); } //Why doesn't this work? // Q_ASSERT(group.groupList().isEmpty()); group.deleteGroup(); } void WorkingSet::deleteSet(bool force, bool silent) { if(m_areas.isEmpty() || force) { emit aboutToRemove(this); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); deleteGroupRecursive(group); #ifdef SYNC_OFTEN setConfig.sync(); #endif if(!silent) emit setChangedSignificantly(); } } void WorkingSet::saveFromArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { qCDebug(SHELL) << "saving" << m_id << "from area"; bool wasPersistent = isPersistent(); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); deleteGroupRecursive(setGroup); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); QString lastActiveView = areaGroup.readEntry("Active View", ""); deleteGroupRecursive(areaGroup); if (area->activeView() && area->activeView()->document()) areaGroup.writeEntry("Active View", area->activeView()->document()->documentSpecifier()); else areaGroup.writeEntry("Active View", lastActiveView); saveFromArea(area, areaIndex, setGroup, areaGroup); if(isEmpty()) { deleteGroupRecursive(setGroup); deleteGroupRecursive(areaGroup); } setPersistent(wasPersistent); #ifdef SYNC_OFTEN setConfig.sync(); #endif emit setChangedSignificantly(); } void WorkingSet::areaViewAdded(Sublime::AreaIndex*, Sublime::View*) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); qCDebug(SHELL) << "added view in" << area << ", id" << m_id; if (m_loading) { qCDebug(SHELL) << "doing nothing because loading"; return; } changed(area); } void WorkingSet::areaViewRemoved(Sublime::AreaIndex*, Sublime::View* view) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); qCDebug(SHELL) << "removed view in" << area << ", id" << m_id; if (m_loading) { qCDebug(SHELL) << "doing nothing because loading"; return; } foreach(Sublime::Area* otherArea, m_areas) { if(otherArea == area) continue; bool hadDocument = false; foreach(Sublime::View* areaView, otherArea->views()) if(view->document() == areaView->document()) hadDocument = true; if(!hadDocument) { // We do this to prevent UI flicker. The view has already been removed from // one of the connected areas, so the working-set has already recorded the change. return; } } changed(area); } void WorkingSet::setPersistent(bool persistent) { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); group.writeEntry("persistent", persistent); #ifdef SYNC_OFTEN group.sync(); #endif qCDebug(SHELL) << "setting" << m_id << "persistent:" << persistent; } bool WorkingSet::isPersistent() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return group.readEntry("persistent", false); } QIcon WorkingSet::icon() const { return m_icon; } bool WorkingSet::isConnected( Sublime::Area* area ) { return m_areas.contains( area ); } QString WorkingSet::id() const { return m_id; } bool WorkingSet::hasConnectedAreas() const { return !m_areas.isEmpty(); } bool WorkingSet::hasConnectedAreas( QList< Sublime::Area* > areas ) const { foreach( Sublime::Area* area, areas ) if ( m_areas.contains( area ) ) return true; return false; } void WorkingSet::connectArea( Sublime::Area* area ) { if ( m_areas.contains( area ) ) { qCDebug(SHELL) << "tried to double-connect area"; return; } qCDebug(SHELL) << "connecting" << m_id << "to area" << area; // Q_ASSERT(area->workingSet() == m_id); m_areas.push_back( area ); connect( area, &Sublime::Area::viewAdded, this, &WorkingSet::areaViewAdded ); connect( area, &Sublime::Area::viewRemoved, this, &WorkingSet::areaViewRemoved ); } void WorkingSet::disconnectArea( Sublime::Area* area ) { if ( !m_areas.contains( area ) ) { qCDebug(SHELL) << "tried to disconnect not connected area"; return; } qCDebug(SHELL) << "disconnecting" << m_id << "from area" << area; // Q_ASSERT(area->workingSet() == m_id); disconnect( area, &Sublime::Area::viewAdded, this, &WorkingSet::areaViewAdded ); disconnect( area, &Sublime::Area::viewRemoved, this, &WorkingSet::areaViewRemoved ); m_areas.removeAll( area ); } void WorkingSet::changed( Sublime::Area* area ) { if ( m_loading ) { return; } { //Do not capture changes done while loading PushValue enableLoading( m_loading, true ); qCDebug(SHELL) << "recording change done to" << m_id; saveFromArea( area, area->rootIndex() ); for ( QList< QPointer< Sublime::Area > >::iterator it = m_areas.begin(); it != m_areas.end(); ++it ) { if (( *it ) != area ) { loadToArea(( *it ), ( *it )->rootIndex() ); } } } emit setChangedSignificantly(); } diff --git a/shell/workingsets/workingsettooltipwidget.cpp b/shell/workingsets/workingsettooltipwidget.cpp index e5fc9c8de8..83e5710e6f 100644 --- a/shell/workingsets/workingsettooltipwidget.cpp +++ b/shell/workingsets/workingsettooltipwidget.cpp @@ -1,382 +1,382 @@ /* Copyright David Nolden 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. */ #include "workingsettooltipwidget.h" #include #include #include #include #include "core.h" #include "documentcontroller.h" #include "mainwindow.h" #include #include #include #include #include #include #include #include "workingset.h" #include "workingsetcontroller.h" #include "workingsetfilelabel.h" #include "workingsettoolbutton.h" #include "workingsethelpers.h" using namespace KDevelop; WorkingSetToolTipWidget::WorkingSetToolTipWidget(QWidget* parent, WorkingSet* set, MainWindow* mainwindow) : QWidget(parent), m_set(set) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); connect(static_cast(mainwindow)->area(), &Sublime::Area::viewAdded, this, &WorkingSetToolTipWidget::updateFileButtons, Qt::QueuedConnection); connect(static_cast(mainwindow)->area(), &Sublime::Area::viewRemoved, this, &WorkingSetToolTipWidget::updateFileButtons, Qt::QueuedConnection); connect(Core::self()->workingSetControllerInternal(), &WorkingSetController::workingSetSwitched, this, &WorkingSetToolTipWidget::updateFileButtons); // title bar { QHBoxLayout* topLayout = new QHBoxLayout; m_setButton = new WorkingSetToolButton(this, set); m_setButton->hide(); topLayout->addSpacing(5); QLabel* icon = new QLabel; topLayout->addWidget(icon); topLayout->addSpacing(5); QString label; if (m_set->isConnected(mainwindow->area())) { label = i18n("Active Working Set"); } else { label = i18n("Working Set"); } QLabel* name = new QLabel(label); name->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); topLayout->addWidget(name); topLayout->addSpacing(10); icon->setPixmap(m_setButton->icon().pixmap(name->sizeHint().height()+8, name->sizeHint().height()+8)); topLayout->addStretch(); m_openButton = new QPushButton; m_openButton->setFlat(true); topLayout->addWidget(m_openButton); m_deleteButton = new QPushButton; m_deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); m_deleteButton->setText(i18n("Delete")); m_deleteButton->setToolTip(i18n("Remove this working set. The contained documents are not affected.")); m_deleteButton->setFlat(true); connect(m_deleteButton, &QPushButton::clicked, m_set, [&] { m_set->deleteSet(false); }); connect(m_deleteButton, &QPushButton::clicked, this, &WorkingSetToolTipWidget::shouldClose); topLayout->addWidget(m_deleteButton); layout->addLayout(topLayout); // horizontal line QFrame* line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Raised); layout->addWidget(line); } // everything else is added to the following widget which just has a different background color QVBoxLayout* bodyLayout = new QVBoxLayout; { QWidget* body = new QWidget(); body->setLayout(bodyLayout); layout->addWidget(body); body->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); } // document list actions { QHBoxLayout* actionsLayout = new QHBoxLayout; m_documentsLabel = new QLabel(i18n("Documents:")); m_documentsLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); actionsLayout->addWidget(m_documentsLabel); actionsLayout->addStretch(); m_mergeButton = new QPushButton; m_mergeButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_mergeButton->setText(i18n("Add All")); m_mergeButton->setToolTip(i18n("Add all documents that are part of this working set to the currently active working set.")); m_mergeButton->setFlat(true); connect(m_mergeButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::mergeSet); actionsLayout->addWidget(m_mergeButton); m_subtractButton = new QPushButton; m_subtractButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_subtractButton->setText(i18n("Remove All")); m_subtractButton->setToolTip(i18n("Remove all documents that are part of this working set from the currently active working set.")); m_subtractButton->setFlat(true); connect(m_subtractButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::subtractSet); actionsLayout->addWidget(m_subtractButton); bodyLayout->addLayout(actionsLayout); } QSet hadFiles; QVBoxLayout* filesLayout = new QVBoxLayout; filesLayout->setMargin(0); foreach(const QString& file, m_set->fileList()) { if(hadFiles.contains(file)) continue; hadFiles.insert(file); FileWidget* widget = new FileWidget; widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); QHBoxLayout* fileLayout = new QHBoxLayout(widget); QToolButton* plusButton = new QToolButton; plusButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); fileLayout->addWidget(plusButton); WorkingSetFileLabel* fileLabel = new WorkingSetFileLabel; fileLabel->setTextFormat(Qt::RichText); // We add spaces behind and after, to make it look nicer fileLabel->setText(" " + Core::self()->projectController()->prettyFileName(QUrl::fromUserInput(file)) + " "); fileLabel->setToolTip(i18nc("@info:tooltip", "Click to open and activate this document.")); fileLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); fileLayout->addWidget(fileLabel); fileLayout->setMargin(0); plusButton->setMaximumHeight(fileLabel->sizeHint().height() + 4); plusButton->setMaximumWidth(plusButton->maximumHeight()); plusButton->setObjectName(file); fileLabel->setObjectName(file); fileLabel->setCursor(QCursor(Qt::PointingHandCursor)); widget->m_button = plusButton; widget->m_label = fileLabel; filesLayout->addWidget(widget); m_fileWidgets.insert(file, widget); m_orderedFileWidgets.push_back(widget); connect(plusButton, &QToolButton::clicked, this, &WorkingSetToolTipWidget::buttonClicked); connect(fileLabel, &WorkingSetFileLabel::clicked, this, &WorkingSetToolTipWidget::labelClicked); } bodyLayout->addLayout(filesLayout); updateFileButtons(); connect(set, &WorkingSet::setChangedSignificantly, this, &WorkingSetToolTipWidget::updateFileButtons); connect(mainwindow->area(), &Sublime::Area::changedWorkingSet, this, &WorkingSetToolTipWidget::updateFileButtons, Qt::QueuedConnection); QMetaObject::invokeMethod(this, "updateFileButtons"); } void WorkingSetToolTipWidget::nextDocument() { int active = -1; for(int a = 0; a < m_orderedFileWidgets.size(); ++a) if(m_orderedFileWidgets[a]->m_label->isActive()) active = a; if(active == -1) { qWarning() << "Found no active document"; return; } int next = (active + 1) % m_orderedFileWidgets.size(); while(m_orderedFileWidgets[next]->isHidden() && next != active) next = (next + 1) % m_orderedFileWidgets.size(); m_orderedFileWidgets[next]->m_label->emitClicked(); } void WorkingSetToolTipWidget::previousDocument() { int active = -1; for(int a = 0; a < m_orderedFileWidgets.size(); ++a) if(m_orderedFileWidgets[a]->m_label->isActive()) active = a; if(active == -1) { qWarning() << "Found no active document"; return; } int next = active - 1; if(next < 0) next += m_orderedFileWidgets.size(); while(m_orderedFileWidgets[next]->isHidden() && next != active) { next -= 1; if(next < 0) next += m_orderedFileWidgets.size(); } m_orderedFileWidgets[next]->m_label->emitClicked(); } void WorkingSetToolTipWidget::updateFileButtons() { MainWindow* mainWindow = dynamic_cast(Core::self()->uiController()->activeMainWindow()); Q_ASSERT(mainWindow); WorkingSetController* controller = Core::self()->workingSetControllerInternal(); ActiveToolTip* tooltip = controller->tooltip(); QString activeFile; if(mainWindow->area()->activeView()) activeFile = mainWindow->area()->activeView()->document()->documentSpecifier(); - WorkingSet* currentWorkingSet = 0; + WorkingSet* currentWorkingSet = nullptr; QSet openFiles; if(!mainWindow->area()->workingSet().isEmpty()) { currentWorkingSet = controller->getWorkingSet(mainWindow->area()->workingSet()); openFiles = currentWorkingSet->fileList().toSet(); } bool allOpen = true; bool noneOpen = true; bool needResize = false; bool allHidden = true; for(QMap< QString, FileWidget* >::iterator it = m_fileWidgets.begin(); it != m_fileWidgets.end(); ++it) { if(openFiles.contains(it.key())) { noneOpen = false; (*it)->m_button->setToolTip(i18n("Remove this file from the current working set")); (*it)->m_button->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); (*it)->show(); }else{ allOpen = false; (*it)->m_button->setToolTip(i18n("Add this file to the current working set")); (*it)->m_button->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); if(currentWorkingSet == m_set) { (*it)->hide(); needResize = true; } } if(!(*it)->isHidden()) allHidden = false; (*it)->m_label->setIsActiveFile(it.key() == activeFile); } // NOTE: always hide merge&subtract all on current working set // if we want to enable mergeButton, we have to fix it's behavior since it operates directly on the // set contents and not on the m_fileWidgets m_mergeButton->setHidden(allOpen || currentWorkingSet == m_set); m_subtractButton->setHidden(noneOpen || currentWorkingSet == m_set); m_deleteButton->setHidden(m_set->hasConnectedAreas()); m_documentsLabel->setHidden(m_mergeButton->isHidden() && m_subtractButton->isHidden() && m_deleteButton->isHidden()); if(currentWorkingSet == m_set) { disconnect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::loadSet); connect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::closeSet); connect(m_openButton, &QPushButton::clicked, this, &WorkingSetToolTipWidget::shouldClose); m_openButton->setIcon(QIcon::fromTheme(QStringLiteral("project-development-close"))); m_openButton->setText(i18n("Stash")); }else{ disconnect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::closeSet); connect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::loadSet); disconnect(m_openButton, &QPushButton::clicked, this, &WorkingSetToolTipWidget::shouldClose); m_openButton->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); m_openButton->setText(i18n("Load")); } if(allHidden && tooltip) tooltip->hide(); if(needResize && tooltip) tooltip->resize(tooltip->sizeHint()); } void WorkingSetToolTipWidget::buttonClicked(bool) { QPointer stillExists(this); QToolButton* s = qobject_cast(sender()); Q_ASSERT(s); MainWindow* mainWindow = dynamic_cast(Core::self()->uiController()->activeMainWindow()); Q_ASSERT(mainWindow); QSet openFiles = Core::self()->workingSetControllerInternal()->getWorkingSet(mainWindow->area()->workingSet())->fileList().toSet(); if(!openFiles.contains(s->objectName())) { Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(s->objectName())); }else{ openFiles.remove(s->objectName()); filterViews(openFiles); } if(stillExists) updateFileButtons(); } void WorkingSetToolTipWidget::labelClicked() { QPointer stillExists(this); WorkingSetFileLabel* s = qobject_cast(sender()); Q_ASSERT(s); bool found = false; Sublime::MainWindow* window = static_cast(ICore::self()->uiController()->activeMainWindow()); foreach(Sublime::View* view, window->area()->views()) { if(view->document()->documentSpecifier() == s->objectName()) { window->activateView(view); found = true; break; } } if(!found) Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(s->objectName())); if(stillExists) updateFileButtons(); } diff --git a/shell/workingsets/workingsetwidget.cpp b/shell/workingsets/workingsetwidget.cpp index e49cde6f65..0713767452 100644 --- a/shell/workingsets/workingsetwidget.cpp +++ b/shell/workingsets/workingsetwidget.cpp @@ -1,89 +1,89 @@ /* Copyright David Nolden Copyright 2010 Milian Wolff 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. */ #include "workingsetwidget.h" #include "../debug.h" #include #include "workingsetcontroller.h" #include "workingset.h" #include "workingsettoolbutton.h" #include using namespace KDevelop; WorkingSet* getSet(const QString& id) { if (id.isEmpty()) { - return 0; + return nullptr; } return Core::self()->workingSetControllerInternal()->getWorkingSet(id); } WorkingSetWidget::WorkingSetWidget(Sublime::Area* area, QWidget* parent) - : WorkingSetToolButton(parent, 0) + : WorkingSetToolButton(parent, nullptr) , m_area(area) { //Queued connect so the change is already applied to the area when we start processing connect(m_area.data(), &Sublime::Area::changingWorkingSet, this, &WorkingSetWidget::changingWorkingSet, Qt::QueuedConnection); setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored)); changingWorkingSet(m_area, QString(), area->workingSet()); } void WorkingSetWidget::setVisible( bool visible ) { // never show empty working sets // TODO: I overloaded this only because hide() in the ctor does not work, other ideas? // It's not that it doesn't work from the constructor, it's that the value changes when the button is added on a layout. QWidget::setVisible( visible && (workingSet() && !workingSet()->isEmpty()) ); } void WorkingSetWidget::changingWorkingSet( Sublime::Area* area, const QString& /*from*/, const QString& newSet) { qCDebug(SHELL) << "re-creating widget" << m_area; Q_ASSERT(area == m_area); Q_UNUSED(area); if (workingSet()) { disconnect(workingSet(), &WorkingSet::setChangedSignificantly, this, &WorkingSetWidget::setChangedSignificantly); } WorkingSet* set = getSet(newSet); setWorkingSet(set); if (set) { connect(set, &WorkingSet::setChangedSignificantly, this, &WorkingSetWidget::setChangedSignificantly); } setVisible(set && !set->isEmpty()); } void WorkingSetWidget::setChangedSignificantly() { setVisible(!workingSet()->isEmpty()); } diff --git a/sublime/aggregatemodel.cpp b/sublime/aggregatemodel.cpp index 0365eacec1..d1ceea0ff1 100644 --- a/sublime/aggregatemodel.cpp +++ b/sublime/aggregatemodel.cpp @@ -1,216 +1,216 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "aggregatemodel.h" #include #include namespace Sublime { struct AggregateModelPrivate { /*Instance of this class is used as an internal pointer to the aggregator items in the model to differentiate between aggregators and non-aggregators.*/ class AggregateInternalData { }; AggregateModelPrivate() { internal = new AggregateInternalData(); } ~AggregateModelPrivate() { delete internal; } QList modelList; QMap modelNames; AggregateInternalData *internal; }; AggregateModel::AggregateModel(QObject *parent) :QAbstractItemModel(parent) { d = new AggregateModelPrivate(); } AggregateModel::~AggregateModel() { delete d; } void AggregateModel::addModel(const QString &name, QStandardItemModel *model) { beginResetModel(); d->modelList << model; d->modelNames[model] = name; endResetModel(); } void AggregateModel::removeModel(QStandardItemModel *model) { beginResetModel(); d->modelList.removeAll(model); d->modelNames.remove(model); endResetModel(); } // reimplemented methods from QAbstractItemModel Qt::ItemFlags AggregateModel::flags(const QModelIndex &index) const { if (!index.isValid()) - return 0; + return nullptr; return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } QVariant AggregateModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section); Q_UNUSED(orientation); Q_UNUSED(role); //there's nothing to return here because aggregated models will have different headers //so we just use empty headers for aggregate model. return ""; } int AggregateModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); //only 1 column is supported atm return 1; } int AggregateModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { //toplevel items represent aggregated models return d->modelList.count(); } else { //Qt model guideline - only 1st column has children if (parent.column() != 0) return 0; //find out if the parent is an aggregator if (parent.internalPointer() == d->internal) { //return the number of toplevel rows in the source model return d->modelList[parent.row()]->rowCount(QModelIndex()); } else { //we have a standard item in the source model - just map it into our model QStandardItem *item = static_cast(parent.internalPointer()); return item->rowCount(); } } } QVariant AggregateModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || (role != Qt::DisplayRole)) return QVariant(); if (!index.parent().isValid()) { //aggregator item return d->modelNames[d->modelList[index.row()]]; } else { //we have a standard item in the source model - just map it into our model QStandardItem *item = static_cast(index.internalPointer()); return item->data(role); } } QModelIndex AggregateModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); if (index.internalPointer() == d->internal) { //this is aggregator item, it has no parents return QModelIndex(); } //this is just an item from the model QStandardItem *item = static_cast(index.internalPointer()); QModelIndex parent; if (!item->parent()) { //we need to find the aggregator item that owns this index //first find the model for this index QStandardItemModel *model = item->model(); //next find the row number of the aggregator item int row = d->modelList.indexOf(model); parent = createIndex(row, 0, d->internal); } else { //we have a standard item in the source model - just map it into our model parent = createIndex(item->parent()->row(), 0, item->parent()); } return parent; } QModelIndex AggregateModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0) return QModelIndex(); if (!parent.isValid()) { if (column > 1 || row >= d->modelList.count()) return QModelIndex(); //this is an aggregator item return createIndex(row, column, d->internal); } else if (parent.internalPointer() == d->internal) { //the parent is an aggregator //find the model that holds the items QStandardItemModel *model = d->modelList[parent.row()]; //this is the first level of items QStandardItem *item = model->item(row, column); if (item) return createIndex(row, column, item); else return QModelIndex(); } else { //we have a standard item in the source model - just map it into our model QStandardItem *parentItem = static_cast(parent.internalPointer()); return createIndex(row, column, parentItem->child(row, column)); } } } diff --git a/sublime/area.cpp b/sublime/area.cpp index c588cbd278..61ad011238 100644 --- a/sublime/area.cpp +++ b/sublime/area.cpp @@ -1,484 +1,484 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "area.h" #include #include #include #include #include #include "view.h" #include "document.h" #include "areaindex.h" #include "controller.h" #include "sublimedebug.h" namespace Sublime { // struct AreaPrivate struct AreaPrivate { AreaPrivate() : rootIndex(new RootAreaIndex) , currentIndex(rootIndex.data()) , controller(nullptr) { } AreaPrivate(const AreaPrivate &p) : title(p.title) , rootIndex(new RootAreaIndex(*(p.rootIndex))) , currentIndex(rootIndex.data()) , controller(p.controller) , toolViewPositions() , desiredToolViews(p.desiredToolViews) , shownToolViews(p.shownToolViews) , iconName(p.iconName) , workingSet(p.workingSet) , m_actions(p.m_actions) { } ~AreaPrivate() { } struct ViewFinder { - ViewFinder(View *_view): view(_view), index(0) {} + ViewFinder(View *_view): view(_view), index(nullptr) {} Area::WalkerMode operator() (AreaIndex *idx) { if (idx->hasView(view)) { index = idx; return Area::StopWalker; } return Area::ContinueWalker; } View *view; AreaIndex *index; }; struct ViewLister { Area::WalkerMode operator()(AreaIndex *idx) { views += idx->views(); return Area::ContinueWalker; } QList views; }; QString title; QScopedPointer rootIndex; AreaIndex *currentIndex; Controller *controller; QList toolViews; QMap toolViewPositions; QMap desiredToolViews; QMap shownToolViews; QString iconName; QString workingSet; QPointer activeView; QList m_actions; }; // class Area Area::Area(Controller *controller, const QString &name, const QString &title) :QObject(controller), d( new AreaPrivate() ) { // FIXME: using objectName seems fishy. Introduce areaType method, // or some such. setObjectName(name); d->title = title; d->controller = controller; d->iconName = QStringLiteral("kdevelop"); d->workingSet.clear(); qCDebug(SUBLIME) << "initial working-set:" << d->workingSet; initialize(); } Area::Area(const Area &area) : QObject(area.controller()), d( new AreaPrivate( *(area.d) ) ) { setObjectName(area.objectName()); //clone toolviews d->toolViews.clear(); foreach (View *view, area.toolViews()) addToolView(view->document()->createView(), area.toolViewPosition(view)); initialize(); } void Area::initialize() { connect(this, &Area::viewAdded, d->controller, &Controller::notifyViewAdded); connect(this, &Area::aboutToRemoveView, d->controller, &Controller::notifyViewRemoved); connect(this, &Area::toolViewAdded, d->controller, &Controller::notifyToolViewAdded); connect(this, &Area::aboutToRemoveToolView, d->controller, &Controller::notifyToolViewRemoved); connect(this, &Area::toolViewMoved, d->controller, &Controller::toolViewMoved); /* In theory, ownership is passed to us, so should not bother detecting deletion outside. */ // Functor will be called after destructor has run -> capture controller pointer by value // otherwise we crash because we access the already freed pointer this->d auto controller = d->controller; connect(this, &Area::destroyed, controller, [controller] (QObject* obj) { controller->removeArea(static_cast(obj)); }); } Area::~Area() { delete d; } View* Area::activeView() { return d->activeView.data(); } void Area::setActiveView(View* view) { d->activeView = view; } void Area::addView(View *view, AreaIndex *index, View *after) { //View *after = 0; if (!after && controller()->openAfterCurrent()) { after = activeView(); } index->add(view, after); connect(view, &View::positionChanged, this, &Area::positionChanged); qCDebug(SUBLIME) << "view added in" << this; connect(this, &Area::destroyed, view, &View::deleteLater); emit viewAdded(index, view); } void Area::addView(View *view, View *after) { AreaIndex *index = d->currentIndex; if (after) { AreaIndex *i = indexOf(after); if (i) index = i; } addView(view, index); } void Area::addView(View *view, View *viewToSplit, Qt::Orientation orientation) { AreaIndex *indexToSplit = indexOf(viewToSplit); addView(view, indexToSplit, orientation); } void Area::addView(View* view, AreaIndex* indexToSplit, Qt::Orientation orientation) { indexToSplit->split(view, orientation); emit viewAdded(indexToSplit, view); connect(this, &Area::destroyed, view, &View::deleteLater); } View* Area::removeView(View *view) { AreaIndex *index = indexOf(view); Q_ASSERT(index); emit aboutToRemoveView(index, view); index->remove(view); emit viewRemoved(index, view); return view; } AreaIndex *Area::indexOf(View *view) { AreaPrivate::ViewFinder f(view); walkViews(f, d->rootIndex.data()); return f.index; } RootAreaIndex *Area::rootIndex() const { return d->rootIndex.data(); } void Area::addToolView(View *view, Position defaultPosition) { d->toolViews.append(view); const QString id = view->document()->documentSpecifier(); const Position position = d->desiredToolViews.value(id, defaultPosition); d->desiredToolViews[id] = position; d->toolViewPositions[view] = position; emit toolViewAdded(view, position); } void Sublime::Area::raiseToolView(View * toolView) { emit requestToolViewRaise(toolView); } View* Area::removeToolView(View *view) { if (!d->toolViews.contains(view)) - return 0; + return nullptr; emit aboutToRemoveToolView(view, d->toolViewPositions[view]); QString id = view->document()->documentSpecifier(); qCDebug(SUBLIME) << this << "removed tool view " << id; d->desiredToolViews.remove(id); d->toolViews.removeAll(view); d->toolViewPositions.remove(view); return view; } void Area::moveToolView(View *toolView, Position newPosition) { if (!d->toolViews.contains(toolView)) return; QString id = toolView->document()->documentSpecifier(); d->desiredToolViews[id] = newPosition; d->toolViewPositions[toolView] = newPosition; emit toolViewMoved(toolView, newPosition); } QList &Area::toolViews() const { return d->toolViews; } Position Area::toolViewPosition(View *toolView) const { return d->toolViewPositions[toolView]; } Controller *Area::controller() const { return d->controller; } QList Sublime::Area::views() { AreaPrivate::ViewLister lister; walkViews(lister, d->rootIndex.data()); return lister.views; } QString Area::title() const { return d->title; } void Area::setTitle(const QString &title) { d->title = title; } void Area::save(KConfigGroup& group) const { QStringList desired; QMap::iterator i, e; for (i = d->desiredToolViews.begin(), e = d->desiredToolViews.end(); i != e; ++i) { desired << i.key() + ':' + QString::number(static_cast(i.value())); } group.writeEntry("desired views", desired); qCDebug(SUBLIME) << "save " << this << "wrote" << group.readEntry("desired views", ""); group.writeEntry("view on left", shownToolViews(Sublime::Left)); group.writeEntry("view on right", shownToolViews(Sublime::Right)); group.writeEntry("view on top", shownToolViews(Sublime::Top)); group.writeEntry("view on bottom", shownToolViews(Sublime::Bottom)); group.writeEntry("working set", d->workingSet); } void Area::load(const KConfigGroup& group) { qCDebug(SUBLIME) << "loading areas config"; d->desiredToolViews.clear(); QStringList desired = group.readEntry("desired views", QStringList()); foreach (const QString &s, desired) { int i = s.indexOf(':'); if (i != -1) { QString id = s.left(i); int pos_i = s.midRef(i+1).toInt(); Sublime::Position pos = static_cast(pos_i); if (pos != Sublime::Left && pos != Sublime::Right && pos != Sublime::Top && pos != Sublime::Bottom) { pos = Sublime::Bottom; } d->desiredToolViews[id] = pos; } } setShownToolViews(Sublime::Left, group.readEntry("view on left", QStringList())); setShownToolViews(Sublime::Right, group.readEntry("view on right", QStringList())); setShownToolViews(Sublime::Top, group.readEntry("view on top", QStringList())); setShownToolViews(Sublime::Bottom, group.readEntry("view on bottom", QStringList())); setWorkingSet(group.readEntry("working set", d->workingSet)); } bool Area::wantToolView(const QString& id) { return (d->desiredToolViews.contains(id)); } void Area::setShownToolViews(Sublime::Position pos, const QStringList& ids) { d->shownToolViews[pos] = ids; } QStringList Area::shownToolViews(Sublime::Position pos) const { if (pos == Sublime::AllPositions) { QStringList allIds; std::for_each(d->shownToolViews.constBegin(), d->shownToolViews.constEnd(), [&](const QStringList& ids) { allIds << ids; }); return allIds; } return d->shownToolViews[pos]; } void Area::setDesiredToolViews( const QMap& desiredToolViews) { d->desiredToolViews = desiredToolViews; } QString Area::iconName() const { return d->iconName; } void Area::setIconName(const QString& iconName) { d->iconName = iconName; } void Area::positionChanged(View *view, int newPos) { qCDebug(SUBLIME) << view << newPos; AreaIndex *index = indexOf(view); index->views().move(index->views().indexOf(view), newPos); } QString Area::workingSet() const { return d->workingSet; } void Area::setWorkingSet(QString name) { if(name != d->workingSet) { qCDebug(SUBLIME) << this << "setting new working-set" << name; QString oldName = d->workingSet; emit changingWorkingSet(this, oldName, name); d->workingSet = name; emit changedWorkingSet(this, oldName, name); } } bool Area::closeView(View* view, bool silent) { QPointer doc = view->document(); // We don't just delete the view, because if silent is false, we might need to ask the user. if(doc && !silent) { // Do some counting to check whether we need to ask the user for feedback qCDebug(SUBLIME) << "Closing view for" << view->document()->documentSpecifier() << "views" << view->document()->views().size() << "in area" << this; int viewsInCurrentArea = 0; // Number of views for the same document in the current area int viewsInOtherAreas = 0; // Number of views for the same document in other areas int viewsInOtherWorkingSets = 0; // Number of views for the same document in areas with different working-set foreach(View* otherView, doc.data()->views()) { Area* area = controller()->areaForView(otherView); if(area == this) viewsInCurrentArea += 1; if(!area || (area != this)) viewsInOtherAreas += 1; if(area && area != this && area->workingSet() != workingSet()) viewsInOtherWorkingSets += 1; } if(viewsInCurrentArea == 1 && (viewsInOtherAreas == 0 || viewsInOtherWorkingSets == 0)) { // Time to ask the user for feedback, because the document will be completely closed // due to working-set synchronization if( !doc.data()->askForCloseFeedback() ) return false; } } // otherwise we can silently close the view, // the document will still have an opened view somewhere delete removeView(view); return true; } void Area::clearViews(bool silent) { foreach(Sublime::View* view, views()) { closeView(view, silent); } } void Area::clearDocuments() { if (views().isEmpty()) emit clearWorkingSet(this); else clearViews(true); } QList Area::actions() const { return d->m_actions; } void Area::addAction(QAction* action) { Q_ASSERT(!d->m_actions.contains(action)); connect(action, &QAction::destroyed, this, &Area::actionDestroyed); d->m_actions.append(action); } void Area::actionDestroyed(QObject* action) { d->m_actions.removeAll(qobject_cast(action)); } } diff --git a/sublime/areaindex.cpp b/sublime/areaindex.cpp index 0e38cd8817..a809009985 100644 --- a/sublime/areaindex.cpp +++ b/sublime/areaindex.cpp @@ -1,251 +1,251 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "areaindex.h" #include #include "view.h" #include "document.h" #include "sublimedebug.h" namespace Sublime { // struct AreaIndexPrivate struct AreaIndexPrivate { AreaIndexPrivate() - :parent(0), first(0), second(0), orientation(Qt::Horizontal) + :parent(nullptr), first(nullptr), second(nullptr), orientation(Qt::Horizontal) { } ~AreaIndexPrivate() { delete first; delete second; foreach( View* v, views ) { // Do the same as AreaIndex::remove(), seems like deletion of the view is happening elsewhere views.removeAll( v ); } } AreaIndexPrivate(const AreaIndexPrivate &p) { - parent = 0; + parent = nullptr; orientation = p.orientation; - first = p.first ? new AreaIndex(*(p.first)) : 0; - second = p.second ? new AreaIndex(*(p.second)) : 0; + first = p.first ? new AreaIndex(*(p.first)) : nullptr; + second = p.second ? new AreaIndex(*(p.second)) : nullptr; } bool isSplit() const { return first || second; } QList views; AreaIndex *parent; AreaIndex *first; AreaIndex *second; Qt::Orientation orientation; }; // class AreaIndex AreaIndex::AreaIndex() : d(new AreaIndexPrivate) { } AreaIndex::AreaIndex(AreaIndex *parent) : d(new AreaIndexPrivate) { d->parent = parent; } AreaIndex::AreaIndex(const AreaIndex &index) : d(new AreaIndexPrivate( *(index.d) ) ) { qCDebug(SUBLIME) << "copying area index"; if (d->first) d->first->setParent(this); if (d->second) d->second->setParent(this); //clone views in this index d->views.clear(); foreach (View *view, index.views()) add(view->document()->createView()); } AreaIndex::~AreaIndex() { delete d; } void AreaIndex::add(View *view, View *after) { //we can not add views to the areas that have already been split if (d->isSplit()) return; if (after) d->views.insert(d->views.indexOf(after)+1, view); else d->views.append(view); } void AreaIndex::remove(View *view) { if (d->isSplit()) return; d->views.removeAll(view); if (d->parent && (d->views.count() == 0)) d->parent->unsplit(this); } void AreaIndex::split(Qt::Orientation orientation, bool moveViewsToSecond) { //we can not split areas that have already been split if (d->isSplit()) return; d->first = new AreaIndex(this); d->second = new AreaIndex(this); d->orientation = orientation; if(moveViewsToSecond) moveViewsTo(d->second); else moveViewsTo(d->first); } void AreaIndex::split(View *newView, Qt::Orientation orientation) { split(orientation); //make new view as second widget in splitter d->second->add(newView); } void AreaIndex::unsplit(AreaIndex *childToRemove) { if (!d->isSplit()) return; AreaIndex *other = d->first == childToRemove ? d->second : d->first; other->moveViewsTo(this); d->orientation = other->orientation(); - d->first = 0; - d->second = 0; + d->first = nullptr; + d->second = nullptr; other->copyChildrenTo(this); delete other; delete childToRemove; } void AreaIndex::copyChildrenTo(AreaIndex *target) { if (!d->first || !d->second) return; target->d->first = d->first; target->d->second = d->second; target->d->first->setParent(target); target->d->second->setParent(target); - d->first = 0; - d->second = 0; + d->first = nullptr; + d->second = nullptr; } void AreaIndex::moveViewsTo(AreaIndex *target) { target->d->views = d->views; d->views.clear(); } QList &AreaIndex::views() const { return d->views; } View *AreaIndex::viewAt(int position) const { - return d->views.value(position, 0); + return d->views.value(position, nullptr); } int AreaIndex::viewCount() const { return d->views.count(); } bool AreaIndex::hasView(View *view) const { return d->views.contains(view); } AreaIndex *AreaIndex::parent() const { return d->parent; } void AreaIndex::setParent(AreaIndex *parent) { d->parent = parent; } AreaIndex *AreaIndex::first() const { return d->first; } AreaIndex *AreaIndex::second() const { return d->second; } Qt::Orientation AreaIndex::orientation() const { return d->orientation; } bool Sublime::AreaIndex::isSplit() const { return d->isSplit(); } void Sublime::AreaIndex::setOrientation(Qt::Orientation orientation) const { d->orientation = orientation; } // class RootAreaIndex RootAreaIndex::RootAreaIndex() - :AreaIndex(), d(0) + :AreaIndex(), d(nullptr) { } QString AreaIndex::print() const { if(isSplit()) return " [ " + first()->print() + (orientation() == Qt::Horizontal ? " / " : " - ") + second()->print() + " ] "; QStringList ret; foreach(Sublime::View* view, views()) ret << view->document()->title(); return ret.join(QStringLiteral(" ")); } } diff --git a/sublime/container.cpp b/sublime/container.cpp index 478606d824..a189b1e593 100644 --- a/sublime/container.cpp +++ b/sublime/container.cpp @@ -1,668 +1,668 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "container.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "view.h" #include "document.h" #include namespace Sublime { // struct ContainerPrivate class ContainerTabBar : public QTabBar { Q_OBJECT public: ContainerTabBar(Container* container) : QTabBar(container), m_container(container) { installEventFilter(this); } bool event(QEvent* ev) override { if(ev->type() == QEvent::ToolTip) { ev->accept(); int tab = tabAt(mapFromGlobal(QCursor::pos())); if(tab != -1) { m_container->showTooltipForTab(tab); } return true; } return QTabBar::event(ev); } void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::MidButton) { // just close on midbutton, drag can still be done with left mouse button int tab = tabAt(mapFromGlobal(QCursor::pos())); if (tab != -1) { emit tabCloseRequested(tab); } return; } QTabBar::mousePressEvent(event); } bool eventFilter(QObject* obj, QEvent* event) override { if (obj != this) { return QObject::eventFilter(obj, event); } // TODO Qt6: Move to mouseDoubleClickEvent when fixme in qttabbar.cpp is resolved // see "fixme Qt 6: move to mouseDoubleClickEvent(), here for BC reasons." in qtabbar.cpp if (event->type() == QEvent::MouseButtonDblClick) { // block tabBarDoubleClicked signals with RMB, see https://bugs.kde.org/show_bug.cgi?id=356016 auto mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::MidButton) { return true; } } return QObject::eventFilter(obj, event); } Q_SIGNALS: void newTabRequested(); private: Container* m_container; }; bool sortViews(const View* const lhs, const View* const rhs) { return lhs->document()->title().compare(rhs->document()->title(), Qt::CaseInsensitive) < 0; } struct ContainerPrivate { QBoxLayout* layout; QHash viewForWidget; ContainerTabBar *tabBar; QStackedWidget *stack; KSqueezedTextLabel *fileNameCorner; QLabel *fileStatus; KSqueezedTextLabel *statusCorner; QPointer leftCornerWidget; QToolButton* documentListButton; QMenu* documentListMenu; QHash documentListActionForView; /** * Updates the context menu which is shown when * the document list button in the tab bar is clicked. * * It shall build a popup menu which contains all currently * enabled views using the title their document provides. */ void updateDocumentListPopupMenu() { qDeleteAll(documentListActionForView); documentListActionForView.clear(); documentListMenu->clear(); // create a lexicographically sorted list QVector views; views.reserve(viewForWidget.size()); foreach(View* view, viewForWidget){ views << view; } std::sort(views.begin(), views.end(), sortViews); for (int i = 0; i < views.size(); ++i) { View *view = views.at(i); QString visibleEntryTitle; // if filename is not unique, prepend containing directory if ((i < views.size() - 1 && view->document()->title() == views.at(i + 1)->document()->title()) || (i > 0 && view->document()->title() == views.at(i - 1)->document()->title()) ) { auto urlDoc = qobject_cast(view->document()); if (!urlDoc) { visibleEntryTitle = view->document()->title(); } else { auto url = urlDoc->url().toString(); int secondOffset = url.lastIndexOf('/'); secondOffset = url.lastIndexOf('/', secondOffset - 1); visibleEntryTitle = url.right(url.length() - url.lastIndexOf('/', secondOffset) - 1); } } else { visibleEntryTitle = view->document()->title(); } QAction* action = documentListMenu->addAction(visibleEntryTitle); action->setData(QVariant::fromValue(view)); documentListActionForView[view] = action; action->setIcon(view->document()->icon()); ///FIXME: push this code somehow into shell, such that we can access the project model for /// icons and also get a neat, short path like the document switcher. } } }; class UnderlinedLabel: public KSqueezedTextLabel { Q_OBJECT public: - UnderlinedLabel(QTabBar *tabBar, QWidget* parent = 0) + UnderlinedLabel(QTabBar *tabBar, QWidget* parent = nullptr) :KSqueezedTextLabel(parent), m_tabBar(tabBar) { } protected: void paintEvent(QPaintEvent *ev) override { if (m_tabBar->isVisible() && m_tabBar->count() > 0) { QStylePainter p(this); QStyleOptionTabBarBase optTabBase; optTabBase.init(m_tabBar); optTabBase.shape = m_tabBar->shape(); optTabBase.tabBarRect = m_tabBar->rect(); optTabBase.tabBarRect.moveRight(0); QStyleOptionTab tabOverlap; tabOverlap.shape = m_tabBar->shape(); int overlap = style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, m_tabBar); if( overlap > 0 ) { QRect rect; rect.setRect(0, height()-overlap, width(), overlap); optTabBase.rect = rect; } if( m_tabBar->drawBase() ) { p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase); } } KSqueezedTextLabel::paintEvent(ev); } QTabBar *m_tabBar; }; class StatusLabel: public UnderlinedLabel { Q_OBJECT public: - StatusLabel(QTabBar *tabBar, QWidget* parent = 0): + StatusLabel(QTabBar *tabBar, QWidget* parent = nullptr): UnderlinedLabel(tabBar, parent) { setAlignment(Qt::AlignRight | Qt::AlignVCenter); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); } QSize minimumSizeHint() const override { QRect rect = style()->itemTextRect(fontMetrics(), QRect(), Qt::AlignRight, true, i18n("Line: 00000 Col: 000")); rect.setHeight(m_tabBar->height()); return rect.size(); } }; // class Container Container::Container(QWidget *parent) :QWidget(parent), d(new ContainerPrivate()) { KAcceleratorManager::setNoAccel(this); QBoxLayout *l = new QBoxLayout(QBoxLayout::TopToBottom, this); l->setMargin(0); l->setSpacing(0); d->layout = new QBoxLayout(QBoxLayout::LeftToRight); d->layout->setMargin(0); d->layout->setSpacing(0); d->documentListMenu = new QMenu(this); d->documentListButton = new QToolButton(this); d->documentListButton->setIcon(QIcon::fromTheme(QStringLiteral("format-list-unordered"))); d->documentListButton->setMenu(d->documentListMenu); d->documentListButton->setPopupMode(QToolButton::InstantPopup); d->documentListButton->setAutoRaise(true); d->documentListButton->setToolTip(i18n("Show sorted list of opened documents")); d->documentListButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); d->layout->addWidget(d->documentListButton); d->tabBar = new ContainerTabBar(this); d->tabBar->setContextMenuPolicy(Qt::CustomContextMenu); d->layout->addWidget(d->tabBar); d->fileStatus = new QLabel( this ); d->fileStatus->setFixedSize( QSize( 16, 16 ) ); d->layout->addWidget(d->fileStatus); d->fileNameCorner = new UnderlinedLabel(d->tabBar, this); d->layout->addWidget(d->fileNameCorner); d->statusCorner = new StatusLabel(d->tabBar, this); d->layout->addWidget(d->statusCorner); l->addLayout(d->layout); d->stack = new QStackedWidget(this); l->addWidget(d->stack); connect(d->tabBar, &ContainerTabBar::currentChanged, this, &Container::widgetActivated); connect(d->tabBar, &ContainerTabBar::tabCloseRequested, this, static_cast(&Container::requestClose)); connect(d->tabBar, &ContainerTabBar::newTabRequested, this, &Container::newTabRequested); connect(d->tabBar, &ContainerTabBar::tabMoved, this, &Container::tabMoved); connect(d->tabBar, &ContainerTabBar::customContextMenuRequested, this, &Container::contextMenu); connect(d->tabBar, &ContainerTabBar::tabBarDoubleClicked, this, &Container::doubleClickTriggered); connect(d->documentListMenu, &QMenu::triggered, this, &Container::documentListActionTriggered); setTabBarHidden(!configTabBarVisible()); d->tabBar->setTabsClosable(true); d->tabBar->setMovable(true); d->tabBar->setExpanding(false); d->tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); } bool Container::configTabBarVisible() { KConfigGroup group = KSharedConfig::openConfig()->group("UiSettings"); return group.readEntry("TabBarVisibility", 1); } void Container::setLeftCornerWidget(QWidget* widget) { if(d->leftCornerWidget.data() == widget) { if(d->leftCornerWidget) - d->leftCornerWidget.data()->setParent(0); + d->leftCornerWidget.data()->setParent(nullptr); }else{ delete d->leftCornerWidget.data(); d->leftCornerWidget.clear(); } d->leftCornerWidget = widget; if(!widget) return; widget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); d->layout->insertWidget(0, widget); widget->show(); } Container::~Container() { delete d; } QList Container::views() const { return d->viewForWidget.values(); } void Container::requestClose(int idx) { emit requestClose(widget(idx)); } void Container::widgetActivated(int idx) { if (idx < 0) return; if (QWidget* w = d->stack->widget(idx)) { Sublime::View* view = d->viewForWidget.value(w); if(view) emit activateView(view); } } void Container::addWidget(View *view, int position) { QWidget *w = view->widget(this); int idx = 0; if (position != -1) { idx = d->stack->insertWidget(position, w); } else idx = d->stack->addWidget(w); d->tabBar->insertTab(idx, view->document()->statusIcon(), view->document()->title()); Q_ASSERT(view); d->viewForWidget[w] = view; // Update document list context menu. This has to be called before // setCurrentWidget, because we call the status icon and title update slots // already, which in turn need the document list menu to be setup. d->updateDocumentListPopupMenu(); setCurrentWidget(d->stack->currentWidget()); // This fixes a strange layouting bug, that could be reproduced like this: Open a few files in KDevelop, activate the rightmost tab. // Then temporarily switch to another area, and then switch back. After that, the tab-bar was gone. // The problem could only be fixed by closing/opening another view. d->tabBar->setMinimumHeight(d->tabBar->sizeHint().height()); connect(view, &View::statusChanged, this, &Container::statusChanged); connect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); connect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); } void Container::statusChanged(Sublime::View* view) { d->statusCorner->setText(view->viewStatus()); } void Container::statusIconChanged(Document* doc) { QHashIterator it = d->viewForWidget; while (it.hasNext()) { if (it.next().value()->document() == doc) { d->fileStatus->setPixmap( doc->statusIcon().pixmap( QSize( 16,16 ) ) ); int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabIcon(tabIndex, doc->statusIcon()); } // Update the document title's menu associated action // using the View* index map Q_ASSERT(d->documentListActionForView.contains(it.value())); d->documentListActionForView[it.value()]->setIcon(doc->icon()); break; } } } void Container::documentTitleChanged(Sublime::Document* doc) { QHashIterator it = d->viewForWidget; while (it.hasNext()) { Sublime::View* view = it.next().value(); if (view->document() == doc) { if (currentView() == view) { d->fileNameCorner->setText( doc->title(Document::Extended) + i18n(" (Press Ctrl+Tab to switch)") ); } int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabText(tabIndex, doc->title()); } break; } } // Update document list popup title d->updateDocumentListPopupMenu(); } int Container::count() const { return d->stack->count(); } QWidget* Container::currentWidget() const { return d->stack->currentWidget(); } void Container::setCurrentWidget(QWidget* w) { d->stack->setCurrentWidget(w); //prevent from emitting activateView() signal on tabbar active tab change //this function is called from MainWindow::activateView() //which does the activation without any additional signals d->tabBar->blockSignals(true); d->tabBar->setCurrentIndex(d->stack->indexOf(w)); d->tabBar->blockSignals(false); if (View* view = viewForWidget(w)) { statusChanged(view); if (!d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us statusIconChanged( view->document() ); documentTitleChanged( view->document() ); } } } QWidget* Container::widget(int i) const { return d->stack->widget(i); } int Container::indexOf(QWidget* w) const { return d->stack->indexOf(w); } void Container::removeWidget(QWidget *w) { if (w) { int widgetIdx = d->stack->indexOf(w); d->stack->removeWidget(w); d->tabBar->removeTab(widgetIdx); if (d->tabBar->currentIndex() != -1 && !d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us View* view = currentView(); if( view ) { statusIconChanged( view->document() ); documentTitleChanged( view->document() ); } } View* view = d->viewForWidget.take(w); if (view) { disconnect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); disconnect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); disconnect(view, &View::statusChanged, this, &Container::statusChanged); // Update document list context menu Q_ASSERT(d->documentListActionForView.contains(view)); delete d->documentListActionForView.take(view); } } } bool Container::hasWidget(QWidget *w) { return d->stack->indexOf(w) != -1; } View *Container::viewForWidget(QWidget *w) const { return d->viewForWidget.value(w); } void Container::setTabBarHidden(bool hide) { if (hide) { d->tabBar->hide(); d->fileNameCorner->show(); d->fileStatus->show(); } else { d->fileNameCorner->hide(); d->fileStatus->hide(); d->tabBar->show(); } View* v = currentView(); if (v) { documentTitleChanged(v->document()); } } void Container::resetTabColors(const QColor& color) { for (int i = 0; i < count(); i++){ d->tabBar->setTabTextColor(i, color); } } void Container::setTabColor(const View* view, const QColor& color) { for (int i = 0; i < count(); i++){ if (view == viewForWidget(widget(i))) { d->tabBar->setTabTextColor(i, color); } } } void Container::setTabColors(const QHash& colors) { for (int i = 0; i < count(); i++) { auto view = viewForWidget(widget(i)); auto color = colors[view]; if (color.isValid()) { d->tabBar->setTabTextColor(i, color); } } } void Container::tabMoved(int from, int to) { QWidget *w = d->stack->widget(from); d->stack->removeWidget(w); d->stack->insertWidget(to, w); d->viewForWidget[w]->notifyPositionChanged(to); } void Container::contextMenu( const QPoint& pos ) { QWidget* senderWidget = qobject_cast(sender()); Q_ASSERT(senderWidget); int currentTab = d->tabBar->tabAt(pos); QMenu menu; Sublime::View* view = viewForWidget(widget(currentTab)); emit tabContextMenuRequested(view, &menu); menu.addSeparator(); QAction* copyPathAction = nullptr; QAction* closeTabAction = nullptr; QAction* closeOtherTabsAction = nullptr; if (view) { copyPathAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Filename")); menu.addSeparator(); closeTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close File")); closeOtherTabsAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close Other Files")); } QAction* closeAllTabsAction = menu.addAction( QIcon::fromTheme(QStringLiteral("document-close")), i18n( "Close All Files" ) ); QAction* triggered = menu.exec(senderWidget->mapToGlobal(pos)); if (triggered) { if ( triggered == closeTabAction ) { requestClose(currentTab); } else if ( triggered == closeOtherTabsAction ) { // activate the remaining tab widgetActivated(currentTab); // first get the widgets to be closed since otherwise the indices will be wrong QList otherTabs; for ( int i = 0; i < count(); ++i ) { if ( i != currentTab ) { otherTabs << widget(i); } } // finally close other tabs foreach( QWidget* tab, otherTabs ) { requestClose(tab); } } else if ( triggered == closeAllTabsAction ) { // activate last tab widgetActivated(count() - 1); // close all for ( int i = 0; i < count(); ++i ) { requestClose(widget(i)); } } else if( triggered == copyPathAction ) { auto view = viewForWidget( widget( currentTab ) ); auto urlDocument = qobject_cast( view->document() ); if( urlDocument ) { QApplication::clipboard()->setText( urlDocument->url().toString() ); } } // else the action was handled by someone else } } void Container::showTooltipForTab(int tab) { emit tabToolTipRequested(viewForWidget(widget(tab)), this, tab); } bool Container::isCurrentTab(int tab) const { return d->tabBar->currentIndex() == tab; } QRect Container::tabRect(int tab) const { return d->tabBar->tabRect(tab).translated(d->tabBar->mapToGlobal(QPoint(0, 0))); } void Container::doubleClickTriggered(int tab) { if (tab == -1) { emit newTabRequested(); } else { emit tabDoubleClicked(viewForWidget(widget(tab))); } } void Container::documentListActionTriggered(QAction* action) { Sublime::View* view = action->data().value< Sublime::View* >(); Q_ASSERT(view); QWidget* widget = d->viewForWidget.key(view); Q_ASSERT(widget); setCurrentWidget(widget); } Sublime::View* Container::currentView() const { return d->viewForWidget.value(widget( d->tabBar->currentIndex() )); } } #include "container.moc" diff --git a/sublime/controller.cpp b/sublime/controller.cpp index 0203374495..fda3cd79c4 100644 --- a/sublime/controller.cpp +++ b/sublime/controller.cpp @@ -1,418 +1,418 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "controller.h" #include #include #include #include #include #include #include "area.h" #include "view.h" #include "document.h" #include "mainwindow.h" #include "sublimedebug.h" namespace Sublime { struct WidgetFinder { - WidgetFinder(QWidget *_w) :w(_w), view(0) {} + WidgetFinder(QWidget *_w) :w(_w), view(nullptr) {} Area::WalkerMode operator()(AreaIndex *index) { foreach (View *v, index->views()) { if (v->hasWidget() && (v->widget() == w)) { view = v; return Area::StopWalker; } } return Area::ContinueWalker; } QWidget *w; View *view; }; struct ToolWidgetFinder { - ToolWidgetFinder(QWidget *_w) :w(_w), view(0) {} + ToolWidgetFinder(QWidget *_w) :w(_w), view(nullptr) {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && (v->widget() == w)) { view = v; return Area::StopWalker; } return Area::ContinueWalker; } QWidget *w; View *view; }; // struct ControllerPrivate struct ControllerPrivate { ControllerPrivate() { } QList documents; QList areas; QList allAreas; QMap namedAreas; // FIXME: remove this. QMap shownAreas; QList controlledWindows; QVector< QList > mainWindowAreas; bool openAfterCurrent; bool arrangeBuddies; }; // class Controller Controller::Controller(QObject *parent) :QObject(parent), MainWindowOperator(), d( new ControllerPrivate() ) { init(); } void Controller::init() { loadSettings(); qApp->installEventFilter(this); } Controller::~Controller() { qDeleteAll(d->controlledWindows); delete d; } void Controller::showArea(Area *area, MainWindow *mainWindow) { - Area *areaToShow = 0; + Area *areaToShow = nullptr; //if the area is already shown in another mainwindow then we need to clone it if (d->shownAreas.contains(area) && (mainWindow != d->shownAreas[area])) areaToShow = new Area(*area); else areaToShow = area; d->shownAreas[areaToShow] = mainWindow; showAreaInternal(areaToShow, mainWindow); } void Controller::showAreaInternal(Area* area, MainWindow *mainWindow) { /* Disconnect the previous area. We really don't want to mess with main window if an area not visible now is modified. Further, if showAreaInternal is called with the same area as is current now, we don't want to connect the same signals twice. */ MainWindowOperator::setArea(mainWindow, area); } void Controller::removeArea(Area *obj) { d->areas.removeAll(obj); } void Controller::removeDocument(Document *obj) { d->documents.removeAll(obj); } void Controller::showArea(const QString& areaTypeId, MainWindow *mainWindow) { int index = d->controlledWindows.indexOf(mainWindow); Q_ASSERT(index != -1); - Area* area = NULL; + Area* area = nullptr; foreach (Area* a, d->mainWindowAreas[index]) { qCDebug(SUBLIME) << "Object name: " << a->objectName() << " id " << areaTypeId; if (a->objectName() == areaTypeId) { area = a; break; } } Q_ASSERT (area); showAreaInternal(area, mainWindow); } void Controller::resetCurrentArea(MainWindow *mainWindow) { QString id = mainWindow->area()->objectName(); int areaIndex = 0; - Area* def = NULL; + Area* def = nullptr; foreach (Area* a, d->areas) { if (a->objectName() == id) { def = a; break; } ++areaIndex; } Q_ASSERT(def); int index = d->controlledWindows.indexOf(mainWindow); Q_ASSERT(index != -1); Area* prev = d->mainWindowAreas[index][areaIndex]; d->mainWindowAreas[index][areaIndex] = new Area(*def); showAreaInternal(d->mainWindowAreas[index][areaIndex], mainWindow); delete prev; } const QList &Controller::defaultAreas() const { return d->areas; } const QList< Area* >& Controller::areas(MainWindow* mainWindow) const { int index = d->controlledWindows.indexOf(mainWindow); Q_ASSERT(index != -1); return areas(index); } const QList &Controller::areas(int mainWindow) const { return d->mainWindowAreas[mainWindow]; } const QList &Controller::allAreas() const { return d->allAreas; } const QList &Controller::documents() const { return d->documents; } void Controller::addDefaultArea(Area *area) { d->areas.append(area); d->allAreas.append(area); d->namedAreas[area->objectName()] = area; emit areaCreated(area); } void Controller::addMainWindow(MainWindow* mainWindow) { Q_ASSERT(mainWindow); Q_ASSERT (!d->controlledWindows.contains(mainWindow)); d->controlledWindows << mainWindow; d->mainWindowAreas.resize(d->controlledWindows.size()); int index = d->controlledWindows.size()-1; foreach (Area* area, defaultAreas()) { Area *na = new Area(*area); d->allAreas.append(na); d->mainWindowAreas[index].push_back(na); emit areaCreated(na); } showAreaInternal(d->mainWindowAreas[index][0], mainWindow); emit mainWindowAdded( mainWindow ); } void Controller::addDocument(Document *document) { d->documents.append(document); } void Controller::areaReleased() { MainWindow *w = reinterpret_cast(sender()); qCDebug(SUBLIME) << "marking areas as mainwindow-free" << w << d->controlledWindows.contains(w) << d->shownAreas.keys(w); foreach (Area *area, d->shownAreas.keys(w)) { qCDebug(SUBLIME) << "" << area->objectName(); areaReleased(area); - disconnect(area, 0, w, 0); + disconnect(area, nullptr, w, nullptr); } d->controlledWindows.removeAll(w); } void Controller::areaReleased(Sublime::Area *area) { d->shownAreas.remove(area); d->namedAreas.remove(area->objectName()); } Area *Controller::defaultArea(const QString &id) const { return d->namedAreas[id]; } Area *Controller::area(int mainWindow, const QString& id) const { foreach (Area* area, areas(mainWindow)) { if (area->objectName() == id) return area; } - return 0; + return nullptr; } Area* Controller::areaForView(View* view) const { foreach (Area* area, allAreas()) if(area->views().contains(view)) return area; - return 0; + return nullptr; } /*We need this to catch activation of views and toolviews so that we can always tell what view and toolview is active. "Active" doesn't mean focused. It means that it is focused now or was focused before and no other view/toolview wasn't focused after that."*/ //implementation is based upon KParts::PartManager::eventFilter bool Controller::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() != QEvent::MouseButtonPress && ev->type() != QEvent::MouseButtonDblClick && ev->type() != QEvent::FocusIn) return false; //not a widget? - return if (!obj->isWidgetType()) return false; //is dialog or popup? - return QWidget *w = static_cast(obj); if (((w->windowFlags().testFlag(Qt::Dialog)) && w->isModal()) || (w->windowFlags().testFlag(Qt::Popup)) || (w->windowFlags().testFlag(Qt::Tool))) return false; //not a mouse button that should activate the widget? - return - QMouseEvent *mev = 0; + QMouseEvent *mev = nullptr; if (ev->type() == QEvent::MouseButtonPress || ev->type() == QEvent::MouseButtonDblClick) { mev = static_cast(ev); int activationButtonMask = Qt::LeftButton | Qt::MidButton | Qt::RightButton; if ((mev->button() & activationButtonMask) == 0) return false; } while (w) { //not inside sublime mainwindow MainWindow *mw = qobject_cast(w->topLevelWidget()); if (!mw || !d->controlledWindows.contains(mw)) return false; Area *area = mw->area(); ///@todo adymo: this is extra slow - optimize //find this widget in views WidgetFinder widgetFinder(w); area->walkViews(widgetFinder, area->rootIndex()); if (widgetFinder.view && widgetFinder.view != mw->activeView()) { setActiveView(mw, widgetFinder.view); ///@todo adymo: shall we filter out the event? return false; } //find this widget in toolviews ToolWidgetFinder toolFinder(w); area->walkToolViews(toolFinder, Sublime::AllPositions); if (toolFinder.view && toolFinder.view != mw->activeToolView()) { setActiveToolView(mw, toolFinder.view); ///@todo adymo: shall we filter out the event? return false; } w = w->parentWidget(); } return false; } const QList< MainWindow * > & Controller::mainWindows() const { return d->controlledWindows; } void Controller::notifyToolViewRemoved(Sublime::View *view, Sublime::Position) { emit aboutToRemoveToolView(view); } void Controller::notifyToolViewAdded(Sublime::View *view, Sublime::Position) { emit toolViewAdded(view); } void Controller::notifyViewRemoved(Sublime::AreaIndex*, Sublime::View *view) { emit aboutToRemoveView(view); } void Controller::notifyViewAdded(Sublime::AreaIndex*, Sublime::View *view) { emit viewAdded(view); } void Controller::setStatusIcon(Document * document, const QIcon & icon) { document->setStatusIcon(icon); } void Controller::loadSettings() { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); d->openAfterCurrent = (uiGroup.readEntry("TabBarOpenAfterCurrent", 1) == 1); d->arrangeBuddies = (uiGroup.readEntry("TabBarArrangeBuddies", 1) == 1); } bool Controller::openAfterCurrent() const { return d->openAfterCurrent; } bool Controller::arrangeBuddies() const { return d->arrangeBuddies; } } #include "moc_controller.cpp" diff --git a/sublime/examples/example1main.cpp b/sublime/examples/example1main.cpp index 2850d04a23..aaad219a3e 100644 --- a/sublime/examples/example1main.cpp +++ b/sublime/examples/example1main.cpp @@ -1,111 +1,111 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "example1main.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Example1Main::Example1Main() - :KXmlGuiWindow(0) + :KXmlGuiWindow(nullptr) { //documents m_controller = new Sublime::Controller(this); Sublime::Document *doc1 = new Sublime::UrlDocument(m_controller, QUrl::fromLocalFile(QStringLiteral("~/foo.cpp"))); Sublime::Document *doc2 = new Sublime::UrlDocument(m_controller, QUrl::fromLocalFile(QStringLiteral("~/boo.cpp"))); Sublime::Document *doc3 = new Sublime::UrlDocument(m_controller, QUrl::fromLocalFile(QStringLiteral("~/moo.cpp"))); //documents for toolviews Sublime::Document *tool1 = new Sublime::ToolDocument(QStringLiteral("ListView"), m_controller, new Sublime::SimpleToolWidgetFactory(QStringLiteral("ListView"))); Sublime::Document *tool2 = new Sublime::ToolDocument(QStringLiteral("TextEdit"), m_controller, new Sublime::SimpleToolWidgetFactory(QStringLiteral("TextEdit"))); //areas (aka perspectives) qDebug() << "constructing area 1"; m_area1 = new Sublime::Area(m_controller, QStringLiteral("Area 1")); m_controller->addDefaultArea(m_area1); m_area1->addView(doc1->createView()); m_area1->addView(doc2->createView()); m_area1->addView(doc3->createView()); m_area1->addToolView(tool1->createView(), Sublime::Left); m_area1->addToolView(tool2->createView(), Sublime::Bottom); qDebug() << "constructing area 2"; m_area2 = new Sublime::Area(m_controller, QStringLiteral("Area 2")); m_controller->addDefaultArea(m_area2); Sublime::View *view1 = doc1->createView(); m_area2->addView(view1); Sublime::View *view2 = doc2->createView(); m_area2->addView(view2, view1, Qt::Vertical); m_area2->addView(doc3->createView(), view2, Qt::Horizontal); m_area2->addToolView(tool1->createView(), Sublime::Bottom); m_area2->addToolView(tool2->createView(), Sublime::Right); //example main window stuff QWidget *w = new QWidget(this); setCentralWidget(w); QVBoxLayout *l = new QVBoxLayout(w); QMenu *areaMenu = menuBar()->addMenu(QStringLiteral("Areas")); areaMenu->addAction(QStringLiteral("Area 1"), this, SLOT(selectArea1())); areaMenu->addAction(QStringLiteral("Area 2"), this, SLOT(selectArea2())); QPushButton *b1 = new QPushButton(QStringLiteral("Area 1"), this); connect(b1, &QPushButton::clicked, this, &Example1Main::selectArea1); l->addWidget(b1); QPushButton *b2 = new QPushButton(QStringLiteral("Area 2"), this); connect(b2, &QPushButton::clicked, this, &Example1Main::selectArea2); l->addWidget(b2); } void Example1Main::selectArea1() { Sublime::MainWindow *main = new Sublime::MainWindow(m_controller); connect(main, &Sublime::MainWindow::areaChanged, this, &Example1Main::updateTitle); m_controller->showArea(m_area1, main); main->show(); } void Example1Main::selectArea2() { Sublime::MainWindow *main = new Sublime::MainWindow(m_controller); connect(main, &Sublime::MainWindow::areaChanged, this, &Example1Main::updateTitle); m_controller->showArea(m_area2, main); main->show(); } void Example1Main::updateTitle(Sublime::Area *area) { Sublime::MainWindow *main = qobject_cast(sender()); main->setWindowTitle(area->objectName()); } diff --git a/sublime/idealbuttonbarwidget.cpp b/sublime/idealbuttonbarwidget.cpp index 1e3ca49ba2..ba78cd42f0 100644 --- a/sublime/idealbuttonbarwidget.cpp +++ b/sublime/idealbuttonbarwidget.cpp @@ -1,285 +1,285 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2011 Alexander Dymo Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "idealbuttonbarwidget.h" #include "mainwindow.h" #include "idealdockwidget.h" #include "ideallayout.h" #include "idealtoolbutton.h" #include "document.h" #include "view.h" #include #include #include #include #include using namespace Sublime; IdealButtonBarWidget::IdealButtonBarWidget(Qt::DockWidgetArea area, IdealController *controller, Sublime::MainWindow *parent) : QWidget(parent) , _area(area) , _controller(controller) - , _corner(0) + , _corner(nullptr) , _showState(false) { setContextMenuPolicy(Qt::CustomContextMenu); setToolTip(i18nc("@info:tooltip", "Right click to add new tool views.")); if (area == Qt::BottomDockWidgetArea) { QBoxLayout *statusLayout = new QBoxLayout(QBoxLayout::RightToLeft, this); statusLayout->setMargin(0); statusLayout->setSpacing(IDEAL_LAYOUT_SPACING); statusLayout->setContentsMargins(0, IDEAL_LAYOUT_MARGIN, 0, IDEAL_LAYOUT_MARGIN); IdealButtonBarLayout *l = new IdealButtonBarLayout(orientation()); statusLayout->addLayout(l); _corner = new QWidget(this); QBoxLayout *cornerLayout = new QBoxLayout(QBoxLayout::LeftToRight, _corner); cornerLayout->setMargin(0); cornerLayout->setSpacing(0); statusLayout->addWidget(_corner); statusLayout->addStretch(1); } else (void) new IdealButtonBarLayout(orientation(), this); } QAction* IdealButtonBarWidget::addWidget(const QString& title, IdealDockWidget *dock, Area *area, View *view) { QAction* action = new QAction(this); action->setCheckable(true); action->setText(title); action->setIcon(dock->windowIcon()); //restore toolview shortcut config KConfigGroup config = KSharedConfig::openConfig()->group("UI"); QList shortcuts; QStringList shortcutStrings = config.readEntry(QStringLiteral("Shortcut for %1").arg(view->document()->title()), QStringList()); shortcuts << QKeySequence::fromString(shortcutStrings.value(0)) << QKeySequence::fromString(shortcutStrings.value(1)); action->setShortcuts(shortcuts); if (_area == Qt::BottomDockWidgetArea || _area == Qt::TopDockWidgetArea) dock->setFeatures( dock->features() | IdealDockWidget::DockWidgetVerticalTitleBar ); dock->setArea(area); dock->setView(view); dock->setDockWidgetArea(_area); _widgets[action] = dock; bool wasEmpty = actions().isEmpty(); addAction(action); if(wasEmpty) emit emptyChanged(); return action; } QWidget* IdealButtonBarWidget::corner() { return _corner; } void IdealButtonBarWidget::removeAction(QAction * action) { _widgets.remove(action); delete _buttons.take(action); delete action; } bool IdealButtonBarWidget::isEmpty() { return actions().isEmpty(); } bool IdealButtonBarWidget::isShown() { return std::any_of(actions().cbegin(), actions().cend(), [](const QAction* action){ return action->isChecked(); }); } void IdealButtonBarWidget::saveShowState() { _showState = isShown(); } bool IdealButtonBarWidget::lastShowState() { return _showState; } Qt::Orientation IdealButtonBarWidget::orientation() const { if (_area == Qt::LeftDockWidgetArea || _area == Qt::RightDockWidgetArea) return Qt::Vertical; return Qt::Horizontal; } Qt::DockWidgetArea IdealButtonBarWidget::area() const { return _area; } void IdealButtonBarWidget::showWidget(bool checked) { - Q_ASSERT(parentWidget() != 0); + Q_ASSERT(parentWidget() != nullptr); QAction *action = qobject_cast(sender()); Q_ASSERT(action); showWidget(action, checked); } void IdealButtonBarWidget::showWidget(QAction *widgetAction, bool checked) { IdealDockWidget *widget = _widgets.value(widgetAction); Q_ASSERT(widget); IdealToolButton* button = _buttons.value(widgetAction); Q_ASSERT(button); if (checked) { IdealController::RaiseMode mode = IdealController::RaiseMode(widgetAction->property("raise").toInt()); if ( mode == IdealController::HideOtherViews ) { // Make sure only one widget is visible at any time. // The alternative to use a QActionCollection and setting that to "exclusive" // has a big drawback: QActions in a collection that is exclusive cannot // be un-checked by the user, e.g. in the View -> Tool Views menu. foreach(QAction *otherAction, actions()) { if ( otherAction != widgetAction && otherAction->isChecked() ) otherAction->setChecked(false); } } _controller->lastDockWidget[_area] = widget; } _controller->showDockWidget(widget, checked); widgetAction->setChecked(checked); button->setChecked(checked); } void IdealButtonBarWidget::actionEvent(QActionEvent *event) { QAction *action = qobject_cast(event->action()); if (! action) return; switch (event->type()) { case QEvent::ActionAdded: { bool wasEmpty = isEmpty(); if (! _buttons.contains(action)) { IdealToolButton *button = new IdealToolButton(_area); //apol: here we set the usual width of a button for the vertical toolbars as the minimumWidth //this is done because otherwise when we remove all the buttons and re-add new ones we get all //the screen flickering. This is solved by not defaulting to a smaller width when it's empty int w = button->sizeHint().width(); if(orientation()==Qt::Vertical && w>minimumWidth()) setMinimumWidth(w); _buttons.insert(action, button); button->setText(action->text()); button->setToolTip(i18n("Toggle '%1' tool view.", action->text())); button->setIcon(action->icon()); button->setShortcut(QKeySequence()); button->setChecked(action->isChecked()); Q_ASSERT(_widgets.contains(action)); _widgets[action]->setWindowTitle(action->text()); layout()->addWidget(button); connect(action, &QAction::toggled, this, static_cast(&IdealButtonBarWidget::showWidget)); connect(button, &IdealToolButton::clicked, this, &IdealButtonBarWidget::buttonPressed); connect(button, &IdealToolButton::customContextMenuRequested, _widgets[action], &IdealDockWidget::contextMenuRequested); if ( wasEmpty ) { emit emptyChanged(); } } } break; case QEvent::ActionRemoved: { if (IdealToolButton *button = _buttons.value(action)) { for (int index = 0; index < layout()->count(); ++index) { if (QLayoutItem *item = layout()->itemAt(index)) { if (item->widget() == button) { action->disconnect(this); delete layout()->takeAt(index); break; } } } } if(layout()->isEmpty()) { emit emptyChanged(); } } break; case QEvent::ActionChanged: { if (IdealToolButton *button = _buttons.value(action)) { button->setText(action->text()); button->setIcon(action->icon()); button->setShortcut(QKeySequence()); Q_ASSERT(_widgets.contains(action)); } } break; default: break; } } IdealDockWidget * IdealButtonBarWidget::widgetForAction(QAction *action) const { return _widgets.value(action); } void IdealButtonBarWidget::buttonPressed(bool state) { auto button = qobject_cast(sender()); Q_ASSERT(button); auto action = _buttons.key(button); Q_ASSERT(action); const bool forceGrouping = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier); if (forceGrouping) { action->setProperty("raise", IdealController::GroupWithOtherViews); } action->setChecked(state); if (forceGrouping) { // need to reset the raise property so that subsequent // showWidget()'s will not do grouping unless explicitly asked action->setProperty("raise", IdealController::HideOtherViews); } } diff --git a/sublime/idealcontroller.cpp b/sublime/idealcontroller.cpp index 65aa90fad4..58a48e8d66 100644 --- a/sublime/idealcontroller.cpp +++ b/sublime/idealcontroller.cpp @@ -1,504 +1,504 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2011 Alexander Dymo Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "idealcontroller.h" #include #include #include #include #include #include #include "area.h" #include "view.h" #include "document.h" #include "mainwindow.h" #include "ideallayout.h" #include "idealtoolbutton.h" #include "idealdockwidget.h" #include "idealbuttonbarwidget.h" using namespace Sublime; IdealController::IdealController(Sublime::MainWindow* mainWindow): QObject(mainWindow), m_mainWindow(mainWindow) { leftBarWidget = new IdealButtonBarWidget(Qt::LeftDockWidgetArea, this, m_mainWindow); connect(leftBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); rightBarWidget = new IdealButtonBarWidget(Qt::RightDockWidgetArea, this, m_mainWindow); connect(rightBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); bottomBarWidget = new IdealButtonBarWidget(Qt::BottomDockWidgetArea, this, m_mainWindow); bottomStatusBarLocation = bottomBarWidget->corner(); connect(bottomBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); topBarWidget = new IdealButtonBarWidget(Qt::TopDockWidgetArea, this, m_mainWindow); connect(topBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); m_docks = qobject_cast(mainWindow->action("docks_submenu")); m_showLeftDock = qobject_cast(m_mainWindow->action("show_left_dock")); m_showRightDock = qobject_cast(m_mainWindow->action("show_right_dock")); m_showBottomDock = qobject_cast(m_mainWindow->action("show_bottom_dock")); // the 'show top dock' action got removed (IOW, it's never created) // (let's keep this code around if we ever want to reintroduce the feature... auto action = m_mainWindow->action("show_top_dock"); if (action) { m_showTopDock = qobject_cast(action); } connect(m_mainWindow, &MainWindow::settingsLoaded, this, &IdealController::loadSettings); } void IdealController::addView(Qt::DockWidgetArea area, View* view) { IdealDockWidget *dock = new IdealDockWidget(this, m_mainWindow); // dock object name is used to store toolview settings QString dockObjectName = view->document()->title(); // support different configuration for same docks opened in different areas if (m_mainWindow->area()) dockObjectName += '_' + m_mainWindow->area()->objectName(); dock->setObjectName(dockObjectName); KAcceleratorManager::setNoAccel(dock); QWidget *w = view->widget(dock); - if (w->parent() == 0) + if (w->parent() == nullptr) { /* Could happen when we're moving the widget from one IdealDockWidget to another. See moveView below. In this case, we need to reparent the widget. */ w->setParent(dock); } QList toolBarActions = view->toolBarActions(); if (toolBarActions.isEmpty()) { dock->setWidget(w); } else { QMainWindow *toolView = new QMainWindow(); QToolBar *toolBar = new QToolBar(toolView); int iconSize = m_mainWindow->style()->pixelMetric(QStyle::PM_SmallIconSize); toolBar->setIconSize(QSize(iconSize, iconSize)); toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); toolBar->setWindowTitle(i18n("%1 Tool Bar", w->windowTitle())); toolBar->setFloatable(false); toolBar->setMovable(false); toolBar->addActions(toolBarActions); toolView->setCentralWidget(w); toolView->addToolBar(toolBar); dock->setWidget(toolView); } dock->setWindowTitle(view->widget()->windowTitle()); dock->setWindowIcon(view->widget()->windowIcon()); dock->setFocusProxy(dock->widget()); if (IdealButtonBarWidget* bar = barForDockArea(area)) { QAction* action = bar->addWidget( view->document()->title(), dock, static_cast(parent())->area(), view); m_dockwidget_to_action[dock] = m_view_to_action[view] = action; m_docks->addAction(action); connect(dock, &IdealDockWidget::closeRequested, action, &QAction::toggle); } connect(dock, &IdealDockWidget::dockLocationChanged, this, &IdealController::dockLocationChanged); dock->hide(); docks.insert(dock); } void IdealController::dockLocationChanged(Qt::DockWidgetArea area) { IdealDockWidget *dock = qobject_cast(sender()); View *view = dock->view(); QAction* action = m_view_to_action.value(view); if (dock->dockWidgetArea() == area) { // this event can happen even when dock changes its location within the same area // usecases: // 1) user drags to the same area // 2) user rearranges toolviews inside the same area // 3) state restoration shows the dock widget // in 3rd case we need to show dock if we don't want it to be shown // TODO: adymo: invent a better solution for the restoration problem if (!action->isChecked() && dock->isVisible()) { dock->hide(); } return; } if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea())) bar->removeAction(action); docks.insert(dock); if (IdealButtonBarWidget* bar = barForDockArea(area)) { QAction* action = bar->addWidget( view->document()->title(), dock, static_cast(parent())->area(), view); m_dockwidget_to_action[dock] = m_view_to_action[view] = action; // at this point the dockwidget is visible (user dragged it) // properly set up UI state bar->showWidget(action, true); // the dock should now be the "last" opened in a new area, not in the old area for (auto it = lastDockWidget.begin(); it != lastDockWidget.end(); ++it) { if (it->data() == dock) it->clear(); } lastDockWidget[area] = dock; // after drag, the toolview loses focus, so focus it again dock->setFocus(Qt::ShortcutFocusReason); m_docks->addAction(action); } if (area == Qt::BottomDockWidgetArea || area == Qt::TopDockWidgetArea) dock->setFeatures( QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | IdealDockWidget::DockWidgetVerticalTitleBar | QDockWidget::DockWidgetMovable); else dock->setFeatures( QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetMovable ); } IdealButtonBarWidget* IdealController::barForDockArea(Qt::DockWidgetArea area) const { switch (area) { case Qt::LeftDockWidgetArea: return leftBarWidget; case Qt::TopDockWidgetArea: return topBarWidget; case Qt::RightDockWidgetArea: return rightBarWidget; case Qt::BottomDockWidgetArea: return bottomBarWidget; default: Q_ASSERT(false); - return 0; + return nullptr; } } void IdealController::slotDockBarContextMenuRequested(QPoint position) { IdealButtonBarWidget* bar = qobject_cast(sender()); Q_ASSERT(bar); emit dockBarContextMenuRequested(bar->area(), bar->mapToGlobal(position)); } void IdealController::raiseView(View* view, RaiseMode mode) { /// @todo GroupWithOtherViews is disabled for now by forcing "mode = HideOtherViews". /// for the release of KDevelop 4.3. /// Reason: Inherent bugs which need significant changes to be fixed. /// Example: Open two equal toolviews (for example 2x konsole), /// activate one, switch area, switch back, -> Both are active instead of one. /// The problem is that views are identified purely by their factory-id, which is equal /// for toolviews of the same type. mode = HideOtherViews; QAction* action = m_view_to_action.value(view); Q_ASSERT(action); QWidget *focusWidget = m_mainWindow->focusWidget(); action->setProperty("raise", mode); action->setChecked(true); // TODO: adymo: hack: focus needs to stay inside the previously // focused widget (setChecked will focus the toolview) if (focusWidget) focusWidget->setFocus(Qt::ShortcutFocusReason); } QList< IdealDockWidget* > IdealController::allDockWidgets() { return docks.toList(); } void IdealController::showDockWidget(IdealDockWidget* dock, bool show) { Q_ASSERT(docks.contains(dock)); Qt::DockWidgetArea area = dock->dockWidgetArea(); if (show) { m_mainWindow->addDockWidget(area, dock); dock->show(); } else { m_mainWindow->removeDockWidget(dock); } setShowDockStatus(area, show); emit dockShown(dock->view(), Sublime::dockAreaToPosition(area), show); if (!show) // Put the focus back on the editor if a dock was hidden focusEditor(); else { // focus the dock dock->setFocus(Qt::ShortcutFocusReason); } } void IdealController::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } QWidget* IdealController::statusBarLocation() const { return bottomStatusBarLocation; } QAction* IdealController::actionForView(View* view) const { return m_view_to_action.value(view); } void IdealController::setShowDockStatus(Qt::DockWidgetArea area, bool checked) { QAction* action = actionForArea(area); if (action->isChecked() != checked) { bool blocked = action->blockSignals(true); action->setChecked(checked); action->blockSignals(blocked); } } QAction* IdealController::actionForArea(Qt::DockWidgetArea area) const { switch (area) { case Qt::LeftDockWidgetArea: default: return m_showLeftDock; case Qt::RightDockWidgetArea: return m_showRightDock; case Qt::TopDockWidgetArea: return m_showTopDock; case Qt::BottomDockWidgetArea: return m_showBottomDock; } } void IdealController::removeView(View* view, bool nondestructive) { Q_ASSERT(m_view_to_action.contains(view)); QAction* action = m_view_to_action.value(view); QWidget *viewParent = view->widget()->parentWidget(); IdealDockWidget *dock = qobject_cast(viewParent); if (!dock) { // toolviews with a toolbar live in a QMainWindow which lives in a Dock Q_ASSERT(qobject_cast(viewParent)); viewParent = viewParent->parentWidget(); dock = qobject_cast(viewParent); } Q_ASSERT(dock); /* Hide the view, first. This is a workaround -- if we try to remove IdealDockWidget without this, then eventually a call to IdealMainLayout::takeAt will be made, which method asserts immediately. */ action->setChecked(false); if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea())) bar->removeAction(action); m_view_to_action.remove(view); m_dockwidget_to_action.remove(dock); if (nondestructive) - view->widget()->setParent(0); + view->widget()->setParent(nullptr); delete dock; } void IdealController::moveView(View *view, Qt::DockWidgetArea area) { removeView(view); addView(area, view); } void IdealController::showBottomDock(bool show) { showDock(Qt::BottomDockWidgetArea, show); } void IdealController::showLeftDock(bool show) { showDock(Qt::LeftDockWidgetArea, show); } void IdealController::showRightDock(bool show) { showDock(Qt::RightDockWidgetArea, show); } void IdealController::hideDocks(IdealButtonBarWidget *bar) { foreach (QAction *action, bar->actions()) { if (action->isChecked()) action->setChecked(false); } focusEditor(); } void IdealController::showDock(Qt::DockWidgetArea area, bool show) { IdealButtonBarWidget *bar = barForDockArea(area); if (!bar) return; IdealDockWidget *lastDock = lastDockWidget[area].data(); if (lastDock && lastDock->isVisible() && !lastDock->hasFocus()) { lastDock->setFocus(Qt::ShortcutFocusReason); // re-sync action state given we may have asked for the dock to be hidden QAction* action = actionForArea(area); if (!action->isChecked()) { action->blockSignals(true); action->setChecked(true); action->blockSignals(false); } return; } if (!show) { hideDocks(bar); } else { // open the last opened toolview (or the first one) and focus it if (lastDock) { if (QAction *action = m_dockwidget_to_action.value(lastDock)) action->setChecked(show); lastDock->setFocus(Qt::ShortcutFocusReason); return; } if (!barForDockArea(area)->actions().isEmpty()) barForDockArea(area)->actions().first()->setChecked(show); } } // returns currently focused dock widget (if any) IdealDockWidget* IdealController::currentDockWidget() { QWidget *w = m_mainWindow->focusWidget(); while (true) { if (!w) break; IdealDockWidget *dockCandidate = qobject_cast(w); if (dockCandidate) return dockCandidate; w = w->parentWidget(); } - return 0; + return nullptr; } void IdealController::goPrevNextDock(IdealController::Direction direction) { IdealDockWidget *currentDock = currentDockWidget(); if (!currentDock) return; IdealButtonBarWidget *bar = barForDockArea(currentDock->dockWidgetArea()); int index = bar->actions().indexOf(m_dockwidget_to_action.value(currentDock)); int step = (direction == NextDock) ? 1 : -1; if (bar->area() == Qt::BottomDockWidgetArea) step = -step; index += step; if (index < 0) index = bar->actions().count() - 1; if (index > bar->actions().count() - 1) index = 0; bar->actions().at(index)->setChecked(true); } void IdealController::toggleDocksShown() { bool anyBarShown = leftBarWidget->isShown() || bottomBarWidget->isShown() || rightBarWidget->isShown(); if (anyBarShown) { leftBarWidget->saveShowState(); bottomBarWidget->saveShowState(); rightBarWidget->saveShowState(); } toggleDocksShown(leftBarWidget, !anyBarShown && leftBarWidget->lastShowState()); toggleDocksShown(bottomBarWidget, !anyBarShown && bottomBarWidget->lastShowState()); toggleDocksShown(rightBarWidget, !anyBarShown && rightBarWidget->lastShowState()); } void IdealController::toggleDocksShown(IdealButtonBarWidget* bar, bool show) { if (!show) { hideDocks(bar); } else { IdealDockWidget *lastDock = lastDockWidget[bar->area()].data(); if (lastDock) m_dockwidget_to_action[lastDock]->setChecked(true); } } void IdealController::loadSettings() { KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); int bottomOwnsBottomLeft = cg.readEntry("BottomLeftCornerOwner", 0); if (bottomOwnsBottomLeft) m_mainWindow->setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); else m_mainWindow->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); int bottomOwnsBottomRight = cg.readEntry("BottomRightCornerOwner", 0); if (bottomOwnsBottomRight) m_mainWindow->setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); else m_mainWindow->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); } void IdealController::emitWidgetResized(Qt::DockWidgetArea dockArea, int thickness) { emit widgetResized(dockArea, thickness); } diff --git a/sublime/idealdockwidget.cpp b/sublime/idealdockwidget.cpp index dae0ea2cf4..c79b1ba588 100644 --- a/sublime/idealdockwidget.cpp +++ b/sublime/idealdockwidget.cpp @@ -1,205 +1,205 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2011 Alexander Dymo Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "idealdockwidget.h" #include "mainwindow.h" #include "area.h" #include "document.h" #include "view.h" #include #include #include #include #include #include #include #include #include using namespace Sublime; IdealDockWidget::IdealDockWidget(IdealController *controller, Sublime::MainWindow *parent) : QDockWidget(parent), - m_area(0), - m_view(0), + m_area(nullptr), + m_view(nullptr), m_docking_area(Qt::NoDockWidgetArea), m_controller(controller) { setAutoFillBackground(true); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &IdealDockWidget::customContextMenuRequested, this, &IdealDockWidget::contextMenuRequested); QAbstractButton *closeButton = findChild(QStringLiteral("qt_dockwidget_closebutton")); if (closeButton) { - disconnect(closeButton, &QAbstractButton::clicked, 0, 0); + disconnect(closeButton, &QAbstractButton::clicked, nullptr, nullptr); connect(closeButton, &QAbstractButton::clicked, this, &IdealDockWidget::closeRequested); } setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); // do not allow to move docks to the top dock area (no buttonbar there in our current UI) setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea); } IdealDockWidget::~IdealDockWidget() { } Area *IdealDockWidget::area() const { return m_area; } void IdealDockWidget::setArea(Area *area) { m_area = area; } View *IdealDockWidget::view() const { return m_view; } void IdealDockWidget::setView(View *view) { m_view = view; } Qt::DockWidgetArea IdealDockWidget::dockWidgetArea() const { return m_docking_area; } void IdealDockWidget::setDockWidgetArea(Qt::DockWidgetArea dockingArea) { m_docking_area = dockingArea; } void IdealDockWidget::slotRemove() { m_area->removeToolView(m_view); } void IdealDockWidget::contextMenuRequested(const QPoint &point) { QWidget* senderWidget = qobject_cast(sender()); Q_ASSERT(senderWidget); QMenu menu; menu.addSection(windowIcon(), m_view->document()->title()); QList< QAction* > viewActions = m_view->contextMenuActions(); if(!viewActions.isEmpty()) { menu.addActions(viewActions); menu.addSeparator(); } ///TODO: can this be cleaned up? if(QToolBar* toolBar = widget()->findChild()) { menu.addAction(toolBar->toggleViewAction()); menu.addSeparator(); } /// start position menu QMenu* positionMenu = menu.addMenu(i18n("Toolview Position")); QActionGroup *g = new QActionGroup(this); QAction *left = new QAction(i18nc("toolview position", "Left"), g); QAction *bottom = new QAction(i18nc("toolview position", "Bottom"), g); QAction *right = new QAction(i18nc("toolview position", "Right"), g); QAction *detach = new QAction(i18nc("toolview position", "Detached"), g); for (auto action : {left, bottom, right, detach}) { positionMenu->addAction(action); action->setCheckable(true); } if (isFloating()) { detach->setChecked(true); } else if (m_docking_area == Qt::BottomDockWidgetArea) bottom->setChecked(true); else if (m_docking_area == Qt::LeftDockWidgetArea) left->setChecked(true); else if (m_docking_area == Qt::RightDockWidgetArea) right->setChecked(true); /// end position menu menu.addSeparator(); QAction *setShortcut = menu.addAction(QIcon::fromTheme(QStringLiteral("configure-shortcuts")), i18n("Assign Shortcut...")); setShortcut->setToolTip(i18n("Use this shortcut to trigger visibility of the toolview.")); menu.addSeparator(); QAction* remove = menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-close")), i18n("Remove Toolview")); QAction* triggered = menu.exec(senderWidget->mapToGlobal(point)); if (triggered) { if ( triggered == remove ) { slotRemove(); return; } else if ( triggered == setShortcut ) { QDialog* dialog(new QDialog(this)); dialog->setWindowTitle(i18n("Assign Shortcut For '%1' Tool View", m_view->document()->title())); KShortcutWidget *w = new KShortcutWidget(dialog); w->setShortcut(m_controller->actionForView(m_view)->shortcuts()); QVBoxLayout* dialogLayout = new QVBoxLayout(dialog); dialogLayout->addWidget(w); QDialogButtonBox* buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel ); dialogLayout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); if (dialog->exec() == QDialog::Accepted) { m_controller->actionForView(m_view)->setShortcuts(w->shortcut()); //save shortcut config KConfigGroup config = KSharedConfig::openConfig()->group("UI"); QStringList shortcuts; shortcuts << w->shortcut().value(0).toString(); shortcuts << w->shortcut().value(1).toString(); config.writeEntry(QStringLiteral("Shortcut for %1").arg(m_view->document()->title()), shortcuts); config.sync(); } delete dialog; return; } else if ( triggered == detach ) { setFloating(true); m_area->raiseToolView(m_view); return; } if (isFloating()) { setFloating(false); } Sublime::Position pos; if (triggered == left) pos = Sublime::Left; else if (triggered == bottom) pos = Sublime::Bottom; else if (triggered == right) pos = Sublime::Right; else return; Area *area = m_area; View *view = m_view; /* This call will delete *this, so we no longer can access member variables. */ m_area->moveToolView(m_view, pos); area->raiseToolView(view); } } diff --git a/sublime/ideallayout.cpp b/sublime/ideallayout.cpp index 81d3b387dd..68fe066dc1 100644 --- a/sublime/ideallayout.cpp +++ b/sublime/ideallayout.cpp @@ -1,251 +1,251 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2008 Vladimir Prus Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ideallayout.h" #include "idealcontroller.h" #include #include using namespace Sublime; IdealButtonBarLayout::IdealButtonBarLayout(Qt::Orientation orientation, QWidget *parent) : QLayout(parent) , _orientation(orientation) , _height(0) { if (orientation == Qt::Vertical) setContentsMargins(IDEAL_LAYOUT_MARGIN, 0, IDEAL_LAYOUT_MARGIN, 0); else setContentsMargins(0, IDEAL_LAYOUT_MARGIN, 0, IDEAL_LAYOUT_MARGIN); setSpacing(IDEAL_LAYOUT_SPACING); invalidate(); } void IdealButtonBarLayout::invalidate() { m_minSizeDirty = true; m_sizeHintDirty = true; m_layoutDirty = true; QLayout::invalidate(); } IdealButtonBarLayout::~IdealButtonBarLayout() { qDeleteAll(_items); } void IdealButtonBarLayout::setHeight(int height) { Q_ASSERT(orientation() == Qt::Vertical); _height = height; (void) invalidate(); } Qt::Orientation IdealButtonBarLayout::orientation() const { return _orientation; } Qt::Orientations IdealButtonBarLayout::expandingDirections() const { return orientation(); } QSize IdealButtonBarLayout::minimumSize() const { // The code below appears to be completely wrong -- // it will return the maximum size of a single button, not any // estimate as to how much space is necessary to draw all buttons // in a minimally acceptable way. if (m_minSizeDirty) { if (orientation() == Qt::Vertical) { const int width = doVerticalLayout(QRect(0, 0, 0, _height), false); return QSize(width, 0); } m_min = QSize(0, 0); foreach (QLayoutItem *item, _items) m_min = m_min.expandedTo(item->minimumSize()); m_minSizeDirty = false; } return m_min; } QSize IdealButtonBarLayout::sizeHint() const { if (m_sizeHintDirty) { int orientationSize = 0; int crossSize = 0; bool first = true; foreach (QLayoutItem *item, _items) { QSize hint = item->sizeHint(); int orientationSizeHere; int crossSizeHere; if (orientation() == Qt::Vertical) { orientationSizeHere = hint.height(); crossSizeHere = hint.width(); } else { orientationSizeHere = hint.width(); crossSizeHere = hint.height(); } if (first) { crossSize = crossSizeHere; } else { orientationSize += spacing(); } orientationSize += orientationSizeHere; first = false; } if (orientation() == Qt::Vertical) m_hint = QSize(crossSize, orientationSize); else m_hint = QSize(orientationSize, crossSize); if (!_items.empty()) { /* If we have no items, just use (0, 0) as hint, don't append any margins. */ int l, t, r, b; getContentsMargins(&l, &t, &r, &b); m_hint += QSize(l+r, t+b); } m_sizeHintDirty = false; } return m_hint; } void IdealButtonBarLayout::setGeometry(const QRect &rect) { if (m_layoutDirty || rect != geometry()) { if (orientation() == Qt::Vertical) doVerticalLayout(rect); else doHorizontalLayout(rect); } } void IdealButtonBarLayout::addItem(QLayoutItem *item) { _items.append(item); invalidate(); } QLayoutItem* IdealButtonBarLayout::itemAt(int index) const { - return _items.value(index, 0); + return _items.value(index, nullptr); } QLayoutItem* IdealButtonBarLayout::takeAt(int index) { if (index >= 0 && index < _items.count()) return _items.takeAt(index); invalidate(); - return 0; + return nullptr; } int IdealButtonBarLayout::count() const { return _items.count(); } int IdealButtonBarLayout::doVerticalLayout(const QRect &rect, bool updateGeometry) const { int l, t, r, b; getContentsMargins(&l, &t, &r, &b); int x = rect.x() + l; int y = rect.y() + t; int currentLineWidth = 0; foreach (QLayoutItem *item, _items) { const QSize itemSizeHint = item->sizeHint(); if (y + itemSizeHint.height() + b > rect.height()) { int newX = x + currentLineWidth + spacing(); if (newX + itemSizeHint.width() + r <= rect.width()) { x += currentLineWidth + spacing(); y = rect.y() + t; } } if (updateGeometry) item->setGeometry(QRect(x, y, itemSizeHint.width(), itemSizeHint.height())); currentLineWidth = qMax(currentLineWidth, itemSizeHint.width()); y += itemSizeHint.height() + spacing(); } m_layoutDirty = updateGeometry; return x + currentLineWidth + r; } int IdealButtonBarLayout::doHorizontalLayout(const QRect &rect, bool updateGeometry) const { int l, t, r, b; getContentsMargins(&l, &t, &r, &b); int x = rect.x() + l; int y = rect.y() + t; int currentLineHeight = 0; foreach (QLayoutItem *item, _items) { QSize itemSizeHint = item->sizeHint(); if (x + itemSizeHint.width() + r > rect.width()) { // Run out of horizontal space. Try to move button to another // row. int newY = y + currentLineHeight + spacing(); if (newY + itemSizeHint.height() + b <= rect.height()) { y = newY; x = rect.x() + l; currentLineHeight = 0; } } if (updateGeometry) item->setGeometry(QRect(x, y, itemSizeHint.width(), itemSizeHint.height())); currentLineHeight = qMax(currentLineHeight, itemSizeHint.height()); x += itemSizeHint.width() + spacing(); } m_layoutDirty = updateGeometry; return y + currentLineHeight + b; } diff --git a/sublime/mainwindow.cpp b/sublime/mainwindow.cpp index 44e9a4f09a..8349f0f573 100644 --- a/sublime/mainwindow.cpp +++ b/sublime/mainwindow.cpp @@ -1,432 +1,432 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow.h" #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "controller.h" #include "container.h" #include "idealcontroller.h" #include "holdupdates.h" #include "sublimedebug.h" Q_LOGGING_CATEGORY(SUBLIME, "kdevplatform.sublime") namespace Sublime { MainWindow::MainWindow(Controller *controller, Qt::WindowFlags flags) -: KParts::MainWindow(0, flags), d(new MainWindowPrivate(this, controller)) +: KParts::MainWindow(nullptr, flags), d(new MainWindowPrivate(this, controller)) { connect(this, &MainWindow::destroyed, controller, static_cast(&Controller::areaReleased)); loadGeometry(KSharedConfig::openConfig()->group("Main Window")); // don't allow AllowTabbedDocks - that doesn't make sense for "ideal" UI setDockOptions(QMainWindow::AnimatedDocks); } bool MainWindow::containsView(View* view) const { foreach(Area* area, areas()) if(area->views().contains(view)) return true; return false; } QList< Area* > MainWindow::areas() const { QList< Area* > areas = controller()->areas(const_cast(this)); if(areas.isEmpty()) areas = controller()->defaultAreas(); return areas; } MainWindow::~MainWindow() { qCDebug(SUBLIME) << "destroying mainwindow"; delete d; } void MainWindow::reconstructViews(QList topViews) { d->reconstructViews(topViews); } QList MainWindow::getTopViews() const { QList topViews; foreach(View* view, d->area->views()) { if(view->hasWidget()) { QWidget* widget = view->widget(); if(widget->parent() && widget->parent()->parent()) { Container* container = qobject_cast(widget->parent()->parent()); if(container->currentWidget() == widget) topViews << view; } } } return topViews; } QList MainWindow::containers() const { return d->viewContainers.values(); } void MainWindow::setArea(Area *area) { if (d->area) - disconnect(d->area, 0, d, 0); + disconnect(d->area, nullptr, d, nullptr); bool differentArea = (area != d->area); /* All views will be removed from dock area now. However, this does not mean those are removed from area, so prevent slotDockShown from recording those views as no longer shown in the area. */ d->ignoreDockShown = true; if (d->autoAreaSettingsSave && differentArea) saveSettings(); HoldUpdates hu(this); if (d->area) clearArea(); d->area = area; d->reconstruct(); if(d->area->activeView()) activateView(d->area->activeView()); else d->activateFirstVisibleView(); initializeStatusBar(); emit areaChanged(area); d->ignoreDockShown = false; hu.stop(); loadSettings(); connect(area, &Area::viewAdded, d, &MainWindowPrivate::viewAdded); connect(area, &Area::viewRemoved, d, &MainWindowPrivate::viewRemovedInternal); connect(area, &Area::requestToolViewRaise, d, &MainWindowPrivate::raiseToolView); connect(area, &Area::aboutToRemoveView, d, &MainWindowPrivate::aboutToRemoveView); connect(area, &Area::toolViewAdded, d, &MainWindowPrivate::toolViewAdded); connect(area, &Area::aboutToRemoveToolView, d, &MainWindowPrivate::aboutToRemoveToolView); connect(area, &Area::toolViewMoved, d, &MainWindowPrivate::toolViewMoved); } void MainWindow::initializeStatusBar() { //nothing here, reimplement in the subclasses if you want to have status bar //inside the bottom toolview buttons row } void MainWindow::resizeEvent(QResizeEvent* event) { return KParts::MainWindow::resizeEvent(event); } void MainWindow::clearArea() { emit areaCleared(d->area); d->clearArea(); } QList MainWindow::toolDocks() const { return d->docks; } Area *Sublime::MainWindow::area() const { return d->area; } Controller *MainWindow::controller() const { return d->controller; } View *MainWindow::activeView() const { return d->activeView; } View *MainWindow::activeToolView() const { return d->activeToolView; } void MainWindow::activateView(Sublime::View* view, bool focus) { if (!d->viewContainers.contains(view)) return; d->viewContainers[view]->setCurrentWidget(view->widget()); setActiveView(view, focus); d->area->setActiveView(view); } void MainWindow::setActiveView(View *view, bool focus) { View* oldActiveView = d->activeView; d->activeView = view; if (focus && view && !view->widget()->hasFocus()) view->widget()->setFocus(); if(d->activeView != oldActiveView) emit activeViewChanged(view); } void Sublime::MainWindow::setActiveToolView(View *view) { d->activeToolView = view; emit activeToolViewChanged(view); } void MainWindow::saveSettings() { d->disableConcentrationMode(); QString group = QStringLiteral("MainWindow"); if (area()) group += '_' + area()->objectName(); KConfigGroup cg = KSharedConfig::openConfig()->group(group); /* This will try to save window size, too. But it's OK, since we won't use this information when loading. */ saveMainWindowSettings(cg); //debugToolBar visibility is stored separately to allow a area dependent default value foreach (KToolBar* toolbar, toolBars()) { if (toolbar->objectName() == QLatin1String("debugToolBar")) { cg.writeEntry("debugToolBarVisibility", toolbar->isVisibleTo(this)); } } cg.sync(); } void MainWindow::loadSettings() { HoldUpdates hu(this); qCDebug(SUBLIME) << "loading settings for " << (area() ? area()->objectName() : QLatin1String("")); QString group = QStringLiteral("MainWindow"); if (area()) group += '_' + area()->objectName(); KConfigGroup cg = KSharedConfig::openConfig()->group(group); // What follows is copy-paste from applyMainWindowSettings. Unfortunately, // we don't really want that one to try restoring window size, and we also // cannot stop it from doing that in any clean way. QStatusBar* sb = findChild(); if (sb) { QString entry = cg.readEntry("StatusBar", "Enabled"); if ( entry == QLatin1String("Disabled") ) sb->hide(); else sb->show(); } QMenuBar* mb = findChild(); if (mb) { QString entry = cg.readEntry ("MenuBar", "Enabled"); if ( entry == QLatin1String("Disabled") ) mb->hide(); else mb->show(); } if ( !autoSaveSettings() || cg.name() == autoSaveGroup() ) { QString entry = cg.readEntry ("ToolBarsMovable", "Enabled"); if ( entry == QLatin1String("Disabled") ) KToolBar::setToolBarsLocked(true); else KToolBar::setToolBarsLocked(false); } // Utilise the QMainWindow::restoreState() functionality // Note that we're fixing KMainWindow bug here -- the original // code has this fragment above restoring toolbar properties. // As result, each save/restore would move the toolbar a bit to // the left. if (cg.hasKey("State")) { QByteArray state; state = cg.readEntry("State", state); state = QByteArray::fromBase64(state); // One day will need to load the version number, but for now, assume 0 restoreState(state); } else { // If there's no state we use a default size of 870x650 // Resize only when showing "code" area. If we do that for other areas, // then we'll hit bug https://bugs.kde.org/show_bug.cgi?id=207990 // TODO: adymo: this is more like a hack, we need a proper first-start initialization if (area() && area()->objectName() == QLatin1String("code")) resize(870,650); } int n = 1; // Toolbar counter. toolbars are counted from 1, foreach (KToolBar* toolbar, toolBars()) { QString group(QStringLiteral("Toolbar")); // Give a number to the toolbar, but prefer a name if there is one, // because there's no real guarantee on the ordering of toolbars group += (toolbar->objectName().isEmpty() ? QString::number(n) : QStringLiteral(" ")+toolbar->objectName()); KConfigGroup toolbarGroup(&cg, group); toolbar->applySettings(toolbarGroup); if (toolbar->objectName() == QLatin1String("debugToolBar")) { //debugToolBar visibility is stored separately to allow a area dependent default value bool visibility = cg.readEntry("debugToolBarVisibility", area()->objectName() == QLatin1String("debug")); toolbar->setVisible(visibility); } n++; } const bool tabBarHidden = !Container::configTabBarVisible(); foreach (Container *container, d->viewContainers) { container->setTabBarHidden(tabBarHidden); } hu.stop(); emit settingsLoaded(); d->disableConcentrationMode(); } bool MainWindow::queryClose() { // saveSettings(); KConfigGroup config(KSharedConfig::openConfig(), "Main Window"); saveGeometry(config); config.sync(); return KParts::MainWindow::queryClose(); } QString MainWindow::screenKey() const { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->screenGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) desk = QApplication::desktop()->screenGeometry(QApplication::desktop()->screen()); return QStringLiteral("Desktop %1 %2") .arg(desk.width()).arg(desk.height()); } void MainWindow::saveGeometry(KConfigGroup &config) { config.writeEntry(screenKey(), geometry()); } void MainWindow::loadGeometry(const KConfigGroup &config) { // The below code, essentially, is copy-paste from // KMainWindow::restoreWindowSize. Right now, that code is buggy, // as per http://permalink.gmane.org/gmane.comp.kde.devel.core/52423 // so we implement a less theoretically correct, but working, version // below QRect g = config.readEntry(screenKey(), QRect()); if (!g.isEmpty()) setGeometry(g); } void MainWindow::enableAreaSettingsSave() { d->autoAreaSettingsSave = true; } QWidget *MainWindow::statusBarLocation() { return d->idealController->statusBarLocation(); } void MainWindow::setTabBarLeftCornerWidget(QWidget* widget) { d->setTabBarLeftCornerWidget(widget); } void MainWindow::tabDoubleClicked(View* view) { Q_UNUSED(view); d->toggleDocksShown(); } void MainWindow::tabContextMenuRequested(View* , QMenu* ) { // do nothing } void MainWindow::tabToolTipRequested(View*, Container*, int) { // do nothing } void MainWindow::newTabRequested() { } void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea , const QPoint& ) { // do nothing } View* MainWindow::viewForPosition(QPoint globalPos) const { foreach(Container* container, d->viewContainers) { QRect globalGeom = QRect(container->mapToGlobal(QPoint(0,0)), container->mapToGlobal(QPoint(container->width(), container->height()))); if(globalGeom.contains(globalPos)) { return d->widgetToView[container->currentWidget()]; } } - return 0; + return nullptr; } void MainWindow::setBackgroundCentralWidget(QWidget* w) { d->setBackgroundCentralWidget(w); } } #include "moc_mainwindow.cpp" diff --git a/sublime/mainwindow_p.cpp b/sublime/mainwindow_p.cpp index 3e272f053c..769922f2ac 100644 --- a/sublime/mainwindow_p.cpp +++ b/sublime/mainwindow_p.cpp @@ -1,800 +1,800 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "areaindex.h" #include "document.h" #include "container.h" #include "controller.h" #include "mainwindow.h" #include "idealcontroller.h" #include "holdupdates.h" #include "idealbuttonbarwidget.h" #include "sublimedebug.h" class IdealToolBar : public QToolBar { Q_OBJECT public: explicit IdealToolBar(const QString& title, bool hideWhenEmpty, Sublime::IdealButtonBarWidget* buttons, QMainWindow* parent) : QToolBar(title, parent) , m_buttons(buttons) , m_hideWhenEmpty(hideWhenEmpty) { setMovable(false); setFloatable(false); setObjectName(title); layout()->setMargin(0); addWidget(m_buttons); if (m_hideWhenEmpty) { connect(m_buttons, &Sublime::IdealButtonBarWidget::emptyChanged, this, &IdealToolBar::updateVisibilty); } } private slots: void updateVisibilty() { setVisible(!m_buttons->isEmpty()); } private: Sublime::IdealButtonBarWidget* m_buttons; const bool m_hideWhenEmpty; }; namespace Sublime { MainWindowPrivate::MainWindowPrivate(MainWindow *w, Controller* controller) -:controller(controller), area(0), activeView(0), activeToolView(0), bgCentralWidget(0), +:controller(controller), area(nullptr), activeView(nullptr), activeToolView(nullptr), bgCentralWidget(nullptr), ignoreDockShown(false), autoAreaSettingsSave(false), m_mainWindow(w) { KActionCollection *ac = m_mainWindow->actionCollection(); m_concentrationModeAction = new QAction(i18n("Concentration Mode"), this); m_concentrationModeAction->setIcon(QIcon::fromTheme(QStringLiteral("page-zoom"))); m_concentrationModeAction->setToolTip(i18n("Removes most of the controls so you can focus on what matters.")); m_concentrationModeAction->setCheckable(true); m_concentrationModeAction->setChecked(false); ac->setDefaultShortcut(m_concentrationModeAction, Qt::META | Qt::Key_C); connect(m_concentrationModeAction, &QAction::toggled, this, &MainWindowPrivate::restoreConcentrationMode); ac->addAction(QStringLiteral("toggle_concentration_mode"), m_concentrationModeAction); QAction* action = new QAction(i18n("Show Left Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Left); connect(action, &QAction::toggled, this, &MainWindowPrivate::showLeftDock); ac->addAction(QStringLiteral("show_left_dock"), action); action = new QAction(i18n("Show Right Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Right); connect(action, &QAction::toggled, this, &MainWindowPrivate::showRightDock); ac->addAction(QStringLiteral("show_right_dock"), action); action = new QAction(i18n("Show Bottom Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Down); connect(action, &QAction::toggled, this, &MainWindowPrivate::showBottomDock); ac->addAction(QStringLiteral("show_bottom_dock"), action); action = new QAction(i18nc("@action", "Focus Editor"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_E); connect(action, &QAction::triggered, this, &MainWindowPrivate::focusEditor); ac->addAction(QStringLiteral("focus_editor"), action); action = new QAction(i18n("Hide/Restore Docks"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Up); connect(action, &QAction::triggered, this, &MainWindowPrivate::toggleDocksShown); ac->addAction(QStringLiteral("hide_all_docks"), action); action = new QAction(i18n("Next Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_N); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextDock); ac->addAction(QStringLiteral("select_next_dock"), action); action = new QAction(i18n("Previous Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_P); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPreviousDock); ac->addAction(QStringLiteral("select_previous_dock"), action); action = new KActionMenu(i18n("Tool Views"), this); ac->addAction(QStringLiteral("docks_submenu"), action); idealController = new IdealController(m_mainWindow); m_leftToolBar = new IdealToolBar(i18n("Left Button Bar"), true, idealController->leftBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::LeftToolBarArea, m_leftToolBar); m_rightToolBar = new IdealToolBar(i18n("Right Button Bar"), true, idealController->rightBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::RightToolBarArea, m_rightToolBar); m_bottomToolBar = new IdealToolBar(i18n("Bottom Button Bar"), false, idealController->bottomBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::BottomToolBarArea, m_bottomToolBar); // adymo: intentionally do not add a toolbar for top buttonbar // this doesn't work well with toolbars added via xmlgui centralWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(centralWidget); centralWidget->setLayout(layout); layout->setMargin(0); splitterCentralWidget = new QSplitter(centralWidget); layout->addWidget(splitterCentralWidget); m_mainWindow->setCentralWidget(centralWidget); connect(idealController, &IdealController::dockShown, this, &MainWindowPrivate::slotDockShown); connect(idealController, &IdealController::widgetResized, this, &MainWindowPrivate::widgetResized); connect(idealController, &IdealController::dockBarContextMenuRequested, m_mainWindow, &MainWindow::dockBarContextMenuRequested); } MainWindowPrivate::~MainWindowPrivate() { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } void MainWindowPrivate::disableConcentrationMode() { m_concentrationModeAction->setChecked(false); restoreConcentrationMode(); } void MainWindowPrivate::restoreConcentrationMode() { const bool concentrationModeOn = m_concentrationModeAction->isChecked(); QWidget* cornerWidget = nullptr; if (m_concentrateToolBar) { QLayout* l = m_concentrateToolBar->layout(); QLayoutItem* li = l->takeAt(1); //ensure the cornerWidget isn't destroyed with the toolbar if (li) { cornerWidget = li->widget(); delete li; } m_concentrateToolBar->deleteLater(); } m_mainWindow->menuBar()->setVisible(!concentrationModeOn); m_bottomToolBar->setVisible(!concentrationModeOn); m_leftToolBar->setVisible(!concentrationModeOn); m_rightToolBar->setVisible(!concentrationModeOn); if (concentrationModeOn) { m_concentrateToolBar = new QToolBar(m_mainWindow); m_concentrateToolBar->setObjectName(QStringLiteral("concentrateToolBar")); m_concentrateToolBar->addAction(m_concentrationModeAction); QWidgetAction *action = new QWidgetAction(this); action->setDefaultWidget(m_mainWindow->menuBar()->cornerWidget(Qt::TopRightCorner)); m_concentrateToolBar->addAction(action); m_concentrateToolBar->setMovable(false); m_mainWindow->addToolBar(Qt::TopToolBarArea, m_concentrateToolBar); - m_mainWindow->menuBar()->setCornerWidget(0, Qt::TopRightCorner); + m_mainWindow->menuBar()->setCornerWidget(nullptr, Qt::TopRightCorner); } else if (cornerWidget) { m_mainWindow->menuBar()->setCornerWidget(cornerWidget, Qt::TopRightCorner); cornerWidget->show(); } if (concentrationModeOn) { m_mainWindow->installEventFilter(this); } else { m_mainWindow->removeEventFilter(this); } } bool MainWindowPrivate::eventFilter(QObject* obj, QEvent* event) { Q_ASSERT(m_mainWindow == obj); if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { const auto ev = static_cast(event); Qt::KeyboardModifiers modifiers = ev->modifiers(); //QLineEdit banned mostly so that alt navigation can be used from QuickOpen const bool visible = modifiers == Qt::AltModifier && ev->type() == QEvent::KeyPress && !qApp->focusWidget()->inherits("QLineEdit"); m_mainWindow->menuBar()->setVisible(visible); } return false; } void MainWindowPrivate::showLeftDock(bool b) { idealController->showLeftDock(b); } void MainWindowPrivate::showBottomDock(bool b) { idealController->showBottomDock(b); } void MainWindowPrivate::showRightDock(bool b) { idealController->showRightDock(b); } void MainWindowPrivate::setBackgroundCentralWidget(QWidget* w) { delete bgCentralWidget; QLayout* l=m_mainWindow->centralWidget()->layout(); l->addWidget(w); bgCentralWidget=w; setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::setBackgroundVisible(bool v) { if(!bgCentralWidget) return; bgCentralWidget->setVisible(v); splitterCentralWidget->setVisible(!v); } void MainWindowPrivate::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } void MainWindowPrivate::toggleDocksShown() { idealController->toggleDocksShown(); } void MainWindowPrivate::selectNextDock() { idealController->goPrevNextDock(IdealController::NextDock); } void MainWindowPrivate::selectPreviousDock() { idealController->goPrevNextDock(IdealController::PrevDock); } Area::WalkerMode MainWindowPrivate::IdealToolViewCreator::operator() (View *view, Sublime::Position position) { if (!d->docks.contains(view)) { d->docks << view; //add view d->idealController->addView(d->positionToDockArea(position), view); } return Area::ContinueWalker; } Area::WalkerMode MainWindowPrivate::ViewCreator::operator() (AreaIndex *index) { QSplitter *splitter = d->m_indexSplitters.value(index); if (!splitter) { //no splitter - we shall create it and populate with views if (!index->parent()) { qCDebug(SUBLIME) << "reconstructing root area"; //this is root area splitter = d->splitterCentralWidget; d->m_indexSplitters[index] = splitter; d->centralWidget->layout()->addWidget(splitter); } else { if (!d->m_indexSplitters.value(index->parent())) { // can happen in working set code, as that adds a view to a child index first // hence, recursively reconstruct the parent indizes first operator()(index->parent()); } QSplitter *parent = d->m_indexSplitters.value(index->parent()); splitter = new QSplitter(parent); d->m_indexSplitters[index] = splitter; if(index == index->parent()->first()) parent->insertWidget(0, splitter); else parent->addWidget(splitter); } Q_ASSERT(splitter); } if (index->isSplit()) //this is a visible splitter splitter->setOrientation(index->orientation()); else { - Container *container = 0; + Container *container = nullptr; while(splitter->count() && qobject_cast(splitter->widget(0))) { // After unsplitting, we might have to remove old splitters QWidget* widget = splitter->widget(0); qCDebug(SUBLIME) << "deleting" << widget; - widget->setParent(0); + widget->setParent(nullptr); delete widget; } if (!splitter->widget(0)) { //we need to create view container container = new Container(splitter); connect(container, &Container::activateView, d->m_mainWindow, &MainWindow::activateViewAndFocus); connect(container, &Container::tabDoubleClicked, d->m_mainWindow, &MainWindow::tabDoubleClicked); connect(container, &Container::tabContextMenuRequested, d->m_mainWindow, &MainWindow::tabContextMenuRequested); connect(container, &Container::tabToolTipRequested, d->m_mainWindow, &MainWindow::tabToolTipRequested); connect(container, static_cast(&Container::requestClose), d, &MainWindowPrivate::widgetCloseRequest, Qt::QueuedConnection); connect(container, &Container::newTabRequested, d->m_mainWindow, &MainWindow::newTabRequested); splitter->addWidget(container); } else container = qobject_cast(splitter->widget(0)); container->show(); int position = 0; bool hadActiveView = false; Sublime::View* activeView = d->activeView; foreach (View *view, index->views()) { QWidget *widget = view->widget(container); if (widget) { if(!container->hasWidget(widget)) { container->addWidget(view, position); d->viewContainers[view] = container; d->widgetToView[widget] = view; } if(activeView == view) { hadActiveView = true; container->setCurrentWidget(widget); }else if(topViews.contains(view) && !hadActiveView) container->setCurrentWidget(widget); } position++; } } return Area::ContinueWalker; } void MainWindowPrivate::reconstructViews(QList topViews) { ViewCreator viewCreator(this, topViews); area->walkViews(viewCreator, area->rootIndex()); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::reconstruct() { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); - m_leftTabbarCornerWidget->setParent(0); + m_leftTabbarCornerWidget->setParent(nullptr); } IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, Sublime::AllPositions); reconstructViews(); m_mainWindow->blockSignals(true); qCDebug(SUBLIME) << "RECONSTRUCT" << area << area->shownToolViews(Sublime::Left); foreach (View *view, area->toolViews()) { QString id = view->document()->documentSpecifier(); if (!id.isEmpty()) { Sublime::Position pos = area->toolViewPosition(view); if (area->shownToolViews(pos).contains(id)) idealController->raiseView(view, IdealController::GroupWithOtherViews); } } m_mainWindow->blockSignals(false); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::clearArea() { if(m_leftTabbarCornerWidget) - m_leftTabbarCornerWidget->setParent(0); + m_leftTabbarCornerWidget->setParent(nullptr); //reparent toolview widgets to 0 to prevent their deletion together with dockwidgets foreach (View *view, area->toolViews()) { // FIXME should we really delete here?? bool nonDestructive = true; idealController->removeView(view, nonDestructive); if (view->hasWidget()) - view->widget()->setParent(0); + view->widget()->setParent(nullptr); } docks.clear(); //reparent all view widgets to 0 to prevent their deletion together with central //widget. this reparenting is necessary when switching areas inside the same mainwindow foreach (View *view, area->views()) { if (view->hasWidget()) - view->widget()->setParent(0); + view->widget()->setParent(nullptr); } cleanCentralWidget(); - m_mainWindow->setActiveView(0); + m_mainWindow->setActiveView(nullptr); m_indexSplitters.clear(); - area = 0; + area = nullptr; viewContainers.clear(); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::cleanCentralWidget() { while(splitterCentralWidget->count()) delete splitterCentralWidget->widget(0); setBackgroundVisible(true); } struct ShownToolViewFinder { ShownToolViewFinder() {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && v->widget()->isVisible()) views << v; return Area::ContinueWalker; } QList views; }; void MainWindowPrivate::slotDockShown(Sublime::View* /*view*/, Sublime::Position pos, bool /*shown*/) { if (ignoreDockShown) return; ShownToolViewFinder finder; m_mainWindow->area()->walkToolViews(finder, pos); QStringList ids; foreach (View *v, finder.views) { ids << v->document()->documentSpecifier(); } area->setShownToolViews(pos, ids); } void MainWindowPrivate::viewRemovedInternal(AreaIndex* index, View* view) { Q_UNUSED(index); Q_UNUSED(view); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::viewAdded(Sublime::AreaIndex *index, Sublime::View *view) { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); - m_leftTabbarCornerWidget->setParent(0); + m_leftTabbarCornerWidget->setParent(nullptr); } // Remove container objects in the hierarchy from the parents, // because they are not needed anymore, and might lead to broken splitter hierarchy and crashes. for(Sublime::AreaIndex* current = index; current; current = current->parent()) { QSplitter *splitter = m_indexSplitters[current]; if (current->isSplit() && splitter) { // Also update the orientation splitter->setOrientation(current->orientation()); for(int w = 0; w < splitter->count(); ++w) { Container *container = qobject_cast(splitter->widget(w)); //we need to remove extra container before reconstruction //first reparent widgets in container so that they are not deleted if(container) { while (container->count()) { - container->widget(0)->setParent(0); + container->widget(0)->setParent(nullptr); } //and then delete the container delete container; } } } } ViewCreator viewCreator(this); area->walkViews(viewCreator, index); emit m_mainWindow->viewAdded( view ); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); setBackgroundVisible(false); } void Sublime::MainWindowPrivate::raiseToolView(Sublime::View * view) { idealController->raiseView(view); } void MainWindowPrivate::aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view) { QSplitter *splitter = m_indexSplitters[index]; if (!splitter) return; qCDebug(SUBLIME) << "index " << index << " root " << area->rootIndex(); qCDebug(SUBLIME) << "splitter " << splitter << " container " << splitter->widget(0); qCDebug(SUBLIME) << "structure: " << index->print() << " whole structure: " << area->rootIndex()->print(); //find the container for the view and remove the widget Container *container = qobject_cast(splitter->widget(0)); if (!container) { qWarning() << "Splitter does not have a left widget!"; return; } emit m_mainWindow->aboutToRemoveView( view ); if (view->widget()) widgetToView.remove(view->widget()); viewContainers.remove(view); const bool wasActive = m_mainWindow->activeView() == view; if (container->count() > 1) { //container is not empty or this is a root index //just remove a widget if( view->widget() ) { container->removeWidget(view->widget()); - view->widget()->setParent(0); + view->widget()->setParent(nullptr); //activate what is visible currently in the container if the removed view was active if (wasActive) return m_mainWindow->setActiveView(container->viewForWidget(container->currentWidget())); } } else { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); - m_leftTabbarCornerWidget->setParent(0); + m_leftTabbarCornerWidget->setParent(nullptr); } // We've about to remove the last view of this container. It will // be empty, so have to delete it, as well. // If we have a container, then it should be the only child of // the splitter. Q_ASSERT(splitter->count() == 1); container->removeWidget(view->widget()); if (view->widget()) - view->widget()->setParent(0); + view->widget()->setParent(nullptr); else qWarning() << "View does not have a widget!"; Q_ASSERT(container->count() == 0); // We can be called from signal handler of container // (which is tab widget), so defer deleting it. container->deleteLater(); - container->setParent(0); + container->setParent(nullptr); /* If we're not at the top level, we get to collapse split views. */ if (index->parent()) { /* The splitter used to have container as the only child, now it's time to get rid of it. Make sure deleting splitter does not delete container -- per above comment, we'll delete it later. */ - container->setParent(0); + container->setParent(nullptr); m_indexSplitters.remove(index); delete splitter; AreaIndex *parent = index->parent(); QSplitter *parentSplitter = m_indexSplitters[parent]; AreaIndex *sibling = parent->first() == index ? parent->second() : parent->first(); QSplitter *siblingSplitter = m_indexSplitters[sibling]; if(siblingSplitter) { HoldUpdates du(parentSplitter); //save sizes and orientation of the sibling splitter parentSplitter->setOrientation(siblingSplitter->orientation()); QList sizes = siblingSplitter->sizes(); /* Parent has two children -- 'index' that we've deleted and 'sibling'. We move all children of 'sibling' into parent, and delete 'sibling'. sibling either contains a single Container instance, or a bunch of further QSplitters. */ while (siblingSplitter->count() > 0) { //reparent contents into parent splitter QWidget *siblingWidget = siblingSplitter->widget(0); siblingWidget->setParent(parentSplitter); parentSplitter->addWidget(siblingWidget); } m_indexSplitters.remove(sibling); delete siblingSplitter; parentSplitter->setSizes(sizes); } qCDebug(SUBLIME) << "after deleation " << parent << " has " << parentSplitter->count() << " elements"; //find the container somewhere to activate Container *containerToActivate = parentSplitter->findChild(); //activate the current view there if (containerToActivate) { m_mainWindow->setActiveView(containerToActivate->viewForWidget(containerToActivate->currentWidget())); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); return; } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); if ( wasActive ) { - m_mainWindow->setActiveView(0L); + m_mainWindow->setActiveView(nullptr); } } void MainWindowPrivate::toolViewAdded(Sublime::View* /*toolView*/, Sublime::Position position) { IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, position); } void MainWindowPrivate::aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position /*position*/) { if (!docks.contains(toolView)) return; idealController->removeView(toolView); // TODO are Views unique? docks.removeAll(toolView); } void MainWindowPrivate::toolViewMoved( Sublime::View *toolView, Sublime::Position position) { if (!docks.contains(toolView)) return; idealController->moveView(toolView, positionToDockArea(position)); } Qt::DockWidgetArea MainWindowPrivate::positionToDockArea(Position position) { switch (position) { case Sublime::Left: return Qt::LeftDockWidgetArea; case Sublime::Right: return Qt::RightDockWidgetArea; case Sublime::Bottom: return Qt::BottomDockWidgetArea; case Sublime::Top: return Qt::TopDockWidgetArea; default: return Qt::LeftDockWidgetArea; } } void MainWindowPrivate::switchToArea(QAction *action) { qCDebug(SUBLIME) << "for" << action; controller->showArea(m_actionAreas.value(action), m_mainWindow); } void MainWindowPrivate::updateAreaSwitcher(Sublime::Area *area) { QAction* action = m_areaActions.value(area); if (action) action->setChecked(true); } void MainWindowPrivate::activateFirstVisibleView() { QList views = area->views(); if (views.count() > 0) m_mainWindow->activateView(views.first()); } void MainWindowPrivate::widgetResized(Qt::DockWidgetArea /*dockArea*/, int /*thickness*/) { //TODO: adymo: remove all thickness business } void MainWindowPrivate::widgetCloseRequest(QWidget* widget) { if (View *view = widgetToView.value(widget)) { area->closeView(view); } } void MainWindowPrivate::setTabBarLeftCornerWidget(QWidget* widget) { if(widget != m_leftTabbarCornerWidget.data()) { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } m_leftTabbarCornerWidget = widget; if(!widget || !area || viewContainers.isEmpty()) return; AreaIndex* putToIndex = area->rootIndex(); QSplitter* splitter = m_indexSplitters[putToIndex]; while(putToIndex->isSplit()) { putToIndex = putToIndex->first(); splitter = m_indexSplitters[putToIndex]; } // Q_ASSERT(splitter || putToIndex == area->rootIndex()); - Container* c = 0; + Container* c = nullptr; if(splitter) { c = qobject_cast(splitter->widget(0)); }else{ c = *viewContainers.constBegin(); } Q_ASSERT(c); c->setLeftCornerWidget(widget); } } #include "mainwindow_p.moc" #include "moc_mainwindow_p.cpp" diff --git a/sublime/tests/test_areaoperation.cpp b/sublime/tests/test_areaoperation.cpp index b2e2f33cc7..98ef9aca1e 100644 --- a/sublime/tests/test_areaoperation.cpp +++ b/sublime/tests/test_areaoperation.cpp @@ -1,728 +1,728 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_areaoperation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "areaprinter.h" using namespace Sublime; struct ViewCounter { ViewCounter(): count(0) {} Area::WalkerMode operator()(AreaIndex *index) { count += index->views().count(); return Area::ContinueWalker; } int count; }; void TestAreaOperation::init() { m_controller = new Controller(this); Document *doc1 = new UrlDocument(m_controller, QUrl::fromLocalFile(QStringLiteral("~/foo.cpp"))); Document *doc2 = new UrlDocument(m_controller, QUrl::fromLocalFile(QStringLiteral("~/boo.cpp"))); Document *doc3 = new UrlDocument(m_controller, QUrl::fromLocalFile(QStringLiteral("~/moo.cpp"))); Document *doc4 = new UrlDocument(m_controller, QUrl::fromLocalFile(QStringLiteral("~/zoo.cpp"))); //documents for toolviews Document *tool1 = new ToolDocument(QStringLiteral("tool1"), m_controller, new SimpleToolWidgetFactory(QStringLiteral("tool1"))); Document *tool2 = new ToolDocument(QStringLiteral("tool2"), m_controller, new SimpleToolWidgetFactory(QStringLiteral("tool2"))); Document *tool3 = new ToolDocument(QStringLiteral("tool3"), m_controller, new SimpleToolWidgetFactory(QStringLiteral("tool3"))); //areas (aka perspectives) //view object names are in form AreaNumber.DocumentNumber.ViewNumber //"tool" prefix is there for tooldocument views m_area1 = new Area(m_controller, QStringLiteral("Area 1")); m_pView111 = doc1->createView(); m_pView111->setObjectName(QStringLiteral("view1.1.1")); m_area1->addView(m_pView111); m_pView121 = doc2->createView(); m_pView121->setObjectName(QStringLiteral("view1.2.1")); m_area1->addView(m_pView121); m_pView122 = doc2->createView(); m_pView122->setObjectName(QStringLiteral("view1.2.2")); m_area1->addView(m_pView122); m_pView131 = doc3->createView(); m_pView131->setObjectName(QStringLiteral("view1.3.1")); m_area1->addView(m_pView131); View *view = tool1->createView(); view->setObjectName(QStringLiteral("toolview1.1.1")); m_area1->addToolView(view, Sublime::Left); view = tool2->createView(); view->setObjectName(QStringLiteral("toolview1.2.1")); m_area1->addToolView(view, Sublime::Bottom); view = tool2->createView(); view->setObjectName(QStringLiteral("toolview1.2.2")); m_area1->addToolView(view, Sublime::Bottom); m_area2 = new Area(m_controller, QStringLiteral("Area 2")); View *view211 = doc1->createView(); view211->setObjectName(QStringLiteral("view2.1.1")); m_area2->addView(view211); View *view212 = doc1->createView(); view212->setObjectName(QStringLiteral("view2.1.2")); m_area2->addView(view212); View *view221 = doc2->createView(); view221->setObjectName(QStringLiteral("view2.2.1")); m_area2->addView(view221, view211, Qt::Vertical); View *view231 = doc3->createView(); view231->setObjectName(QStringLiteral("view2.3.1")); m_area2->addView(view231, view221, Qt::Horizontal); View *view241 = doc4->createView(); view241->setObjectName(QStringLiteral("view2.4.1")); m_area2->addView(view241, view212, Qt::Vertical); view = tool1->createView(); view->setObjectName(QStringLiteral("toolview2.1.1")); m_area2->addToolView(view, Sublime::Bottom); view = tool2->createView(); view->setObjectName(QStringLiteral("toolview2.2.1")); m_area2->addToolView(view, Sublime::Right); view = tool3->createView(); view->setObjectName(QStringLiteral("toolview2.3.1")); m_area2->addToolView(view, Sublime::Top); view = tool3->createView(); view->setObjectName(QStringLiteral("toolview2.3.2")); m_area2->addToolView(view, Sublime::Top); m_area3 = new Area(m_controller, QStringLiteral("Area 3")); View *view0 = doc1->createView(); view0->setObjectName(QStringLiteral("view3.1.1")); m_area3->addView(view0); View *view1 = doc2->createView(); view1->setObjectName(QStringLiteral("view3.1.2")); m_area3->addView(view1, view0); View *view2 = doc3->createView(); view2->setObjectName(QStringLiteral("view3.1.3")); m_area3->addView(view2, view1); View *view3 = doc4->createView(); view3->setObjectName(QStringLiteral("view3.1.4")); m_area3->addView(view3, view1); m_controller->addDefaultArea(m_area1); m_controller->addDefaultArea(m_area2); m_controller->addDefaultArea(m_area3); } void TestAreaOperation::cleanup() { delete m_area1; delete m_area2; delete m_controller; - m_area1 = 0; - m_area2 = 0; - m_controller = 0; + m_area1 = nullptr; + m_area2 = nullptr; + m_controller = nullptr; } void TestAreaOperation::areaConstruction() { //check if areas has proper object names QCOMPARE(m_area1->objectName(), QStringLiteral("Area 1")); QCOMPARE(m_area2->objectName(), QStringLiteral("Area 2")); //check that area1 contents is properly initialised AreaViewsPrinter viewsPrinter1; m_area1->walkViews(viewsPrinter1, m_area1->rootIndex()); QCOMPARE(viewsPrinter1.result, QStringLiteral("\n\ [ view1.1.1 view1.2.1 view1.2.2 view1.3.1 ]\n\ ")); AreaToolViewsPrinter toolViewsPrinter1; m_area1->walkToolViews(toolViewsPrinter1, Sublime::AllPositions); QCOMPARE(toolViewsPrinter1.result, QStringLiteral("\n\ toolview1.1.1 [ left ]\n\ toolview1.2.1 [ bottom ]\n\ toolview1.2.2 [ bottom ]\n\ ")); //check that area2 contents is properly initialised AreaViewsPrinter viewsPrinter2; m_area2->walkViews(viewsPrinter2, m_area2->rootIndex()); QCOMPARE(viewsPrinter2.result, QStringLiteral("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ view2.1.1 view2.1.2 ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ view2.2.1 ]\n\ [ view2.3.1 ]\n\ ")); AreaToolViewsPrinter toolViewsPrinter2; m_area2->walkToolViews(toolViewsPrinter2, Sublime::AllPositions); QCOMPARE(toolViewsPrinter2.result, QStringLiteral("\n\ toolview2.1.1 [ bottom ]\n\ toolview2.2.1 [ right ]\n\ toolview2.3.1 [ top ]\n\ toolview2.3.2 [ top ]\n\ ")); } void TestAreaOperation::mainWindowConstruction() { //====== check for m_area1 ====== MainWindow mw1(m_controller); m_controller->showArea(m_area1, &mw1); checkArea1(&mw1); ///////////// //====== check for m_area2 ====== MainWindow mw2(m_controller); m_controller->showArea(m_area2, &mw2); checkArea2(&mw2); } void TestAreaOperation::checkArea1(MainWindow *mw) { Area *area = mw->area(); //check that all docks have their widgets foreach (View *dock, mw->toolDocks()) { //QVERIFY(dock->widget() != 0); QVERIFY(dock->hasWidget()); } QCOMPARE(mw->toolDocks().count(), area->toolViews().count()); //check that mainwindow have all splitters and widgets in splitters inside centralWidget QWidget *central = mw->centralWidget(); - QVERIFY(central != 0); + QVERIFY(central != nullptr); QVERIFY(central->inherits("QWidget")); QWidget *splitter = central->findChild(); QVERIFY(splitter); QVERIFY(splitter->inherits("QSplitter")); //check that we have a container and 4 views inside Container *container = splitter->findChild(); QVERIFY(container); ViewCounter c; area->walkViews(c, area->rootIndex()); QCOMPARE(container->count(), c.count); for (int i = 0; i < container->count(); ++i) - QVERIFY(container->widget(i) != 0); + QVERIFY(container->widget(i) != nullptr); } void TestAreaOperation::checkArea2(MainWindow *mw) { Area *area = mw->area(); //check that all docks have their widgets foreach (View *dock, mw->toolDocks()) { //QVERIFY(dock->widget() != 0); QVERIFY(dock->hasWidget()); } QCOMPARE(mw->toolDocks().count(), area->toolViews().count()); //check that mainwindow have all splitters and widgets in splitters inside centralWidget QWidget *central = mw->centralWidget(); - QVERIFY(central != 0); + QVERIFY(central != nullptr); QVERIFY(central->inherits("QWidget")); QWidget *splitter = central->findChild(); QVERIFY(splitter); QVERIFY(splitter->inherits("QSplitter")); //check that we have 4 properly initialized containers QList containers = splitter->findChildren(); QCOMPARE(containers.count(), 4); int widgetCount = 0; foreach (Container *c, containers) { for (int i = 0; i < c->count(); ++i) - QVERIFY(c->widget(i) != 0); + QVERIFY(c->widget(i) != nullptr); widgetCount += c->count(); } ViewCounter c; area->walkViews(c, area->rootIndex()); QCOMPARE(widgetCount, c.count); //check that we have 7 splitters: 2 vertical and 1 horizontal, rest is not split QList splitters = splitter->findChildren(); splitters.append(qobject_cast(splitter)); QCOMPARE(splitters.count(), 6+1); //6 child splitters + 1 central itself = 7 splitters int verticalSplitterCount = 0; int horizontalSplitterCount = 0; foreach (QSplitter *s, splitters) { if (s->count() == 1) continue; //this is a splitter with container inside, its orientation is not relevant if (s->orientation() == Qt::Vertical) verticalSplitterCount += 1; else horizontalSplitterCount += 1; } QCOMPARE(verticalSplitterCount, 2); QCOMPARE(horizontalSplitterCount, 1); } void TestAreaOperation::areaCloning() { //show m_area1 in MainWindow1 MainWindow mw1(m_controller); m_controller->showArea(m_area1, &mw1); checkArea1(&mw1); //now try to show the same area in MainWindow2 and check that we get a clone MainWindow mw2(m_controller); m_controller->showArea(m_area1, &mw2); //two mainwindows have different areas QVERIFY(mw1.area() != mw2.area()); //the area for the second mainwindow is a clone of the //original area and should have the same name. QVERIFY(mw2.area()->objectName() == mw1.area()->objectName()); //check mainwindow layouts - original and copy checkArea1(&mw1); checkArea1(&mw2); } /*! Functor used by areaSwitchingInSameMainWindow() Walks all Views and checks if they got a widget. hasWidget will be set to false if any View lacks a widget.*/ struct AreaWidgetChecker { AreaWidgetChecker(): foundViewWithoutWidget(false), failureMessage(QLatin1String("")) {} Area::WalkerMode operator()(AreaIndex *index) { foreach (View *view, index->views()) { if (!view->hasWidget()) { failureMessage += view->objectName() + " has no widget\n"; foundViewWithoutWidget = true; } } return Area::ContinueWalker; } Area::WalkerMode operator()(View *view, Sublime::Position) { if (!view->hasWidget()) { foundViewWithoutWidget = true; failureMessage += view->objectName() + " has no widget\n"; } return Area::ContinueWalker; } char* message() { return qstrdup(failureMessage.toLatin1().data()); } bool foundViewWithoutWidget; QString failureMessage; }; void TestAreaOperation::areaSwitchingInSameMainwindow() { MainWindow mw(m_controller); m_controller->showArea(m_area1, &mw); checkArea1(&mw); m_controller->showArea(m_area2, &mw); checkArea2(&mw); //check what happened to area1 widgets, they should be intact AreaWidgetChecker checker; m_area1->walkViews(checker, m_area1->rootIndex()); m_area1->walkToolViews(checker, Sublime::AllPositions); QVERIFY2(!checker.foundViewWithoutWidget, checker.message()); } void TestAreaOperation::simpleViewAdditionAndDeletion() { // set TabBarOpenAfterCurrent=0, otherwise we'd have a different order of tabs int oldTabBarOpenAfterCurrent; { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); oldTabBarOpenAfterCurrent = uiGroup.readEntry("TabBarOpenAfterCurrent", 1); uiGroup.writeEntry("TabBarOpenAfterCurrent", 0); uiGroup.sync(); } m_controller->loadSettings(); MainWindow mw(m_controller); m_controller->addMainWindow(&mw); m_controller->showArea(m_area1, &mw); checkArea1(&mw); Document *doc5 = new UrlDocument(m_controller, QUrl::fromLocalFile(QStringLiteral("~/new.cpp"))); View *view = doc5->createView(); view->setObjectName(QStringLiteral("view1.5.1")); m_area1->addView(view); checkAreaViewsDisplay(&mw, m_area1, QStringLiteral("\n[ view1.1.1 view1.2.1 view1.2.2 view1.3.1 view1.5.1 ]\n"), 1, 1, QStringLiteral("Added an url view (view1.5.1)")); //now remove view and check that area is valid delete m_area1->removeView(view); checkAreaViewsDisplay(&mw, m_area1, QStringLiteral("\n[ view1.1.1 view1.2.1 view1.2.2 view1.3.1 ]\n"), 1, 1, QStringLiteral("Removed the url view (view1.5.1)")); //now remove all other views one by one and leave an empty container QList list(m_area1->views()); foreach (View *view, list) delete m_area1->removeView(view); checkAreaViewsDisplay(&mw, m_area1, QStringLiteral("\n[ horizontal splitter ]\n"), 0, 1, QStringLiteral("Removed all views. Only horizontal splitter should remain.")); //add a view again and check that mainwindow is correctly reconstructed view = doc5->createView(); view->setObjectName(QStringLiteral("view1.5.1")); m_area1->addView(view); checkAreaViewsDisplay(&mw, m_area1, QStringLiteral("\n[ view1.5.1 ]\n"), 1, 1, QStringLiteral("Added a single view to previously emptied mainwindow.")); { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); uiGroup.writeEntry("TabBarOpenAfterCurrent", oldTabBarOpenAfterCurrent); uiGroup.sync(); } m_controller->loadSettings(); } void TestAreaOperation::complexViewAdditionAndDeletion() { Area *area = m_area2; MainWindow mw(m_controller); m_controller->addMainWindow(&mw); m_controller->showArea(m_area2, &mw); Document *doc5 = new UrlDocument(m_controller, QUrl::fromLocalFile(QStringLiteral("~/new.cpp"))); View *view = doc5->createView(); view->setObjectName(QStringLiteral("view2.5.1")); View *view221 = findNamedView(area, QStringLiteral("view2.2.1")); QVERIFY(view221); area->addView(view, view221, Qt::Vertical); checkAreaViewsDisplay(&mw, area, QStringLiteral("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ view2.1.1 view2.1.2 ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ vertical splitter ]\n\ [ view2.2.1 ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 5, 8+1); //now delete view221 delete area->removeView(view221); checkAreaViewsDisplay(&mw, area, QStringLiteral("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ view2.1.1 view2.1.2 ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 4, 6+1); //remove one more view, this time the one inside non-empty container View *view211 = findNamedView(area, QStringLiteral("view2.1.1")); delete m_area2->removeView(view211); checkAreaViewsDisplay(&mw, area, QStringLiteral("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ view2.1.2 ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 4, 6+1); //and now remove all remaining views one by one delete m_area2->removeView(findNamedView(area, QStringLiteral("view2.1.2"))); checkAreaViewsDisplay(&mw, area, QStringLiteral("\n\ [ vertical splitter ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 3, 4+1); delete m_area2->removeView(findNamedView(area, QStringLiteral("view2.4.1"))); checkAreaViewsDisplay(&mw, area, QStringLiteral("\n\ [ horizontal splitter ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 2, 2+1); delete m_area2->removeView(findNamedView(area, QStringLiteral("view2.5.1"))); checkAreaViewsDisplay(&mw, area, QStringLiteral("\n\ [ view2.3.1 ]\n\ "), 1, 1); delete m_area2->removeView(findNamedView(area, QStringLiteral("view2.3.1"))); checkAreaViewsDisplay(&mw, area, QStringLiteral("\n\ [ horizontal splitter ]\n\ "), 0, 1); } void TestAreaOperation::toolViewAdditionAndDeletion() { MainWindow mw(m_controller); m_controller->showArea(m_area1, &mw); checkArea1(&mw); Document *tool4 = new ToolDocument(QStringLiteral("tool4"), m_controller, new SimpleToolWidgetFactory(QStringLiteral("tool4"))); View *view = tool4->createView(); view->setObjectName(QStringLiteral("toolview1.4.1")); m_area1->addToolView(view, Sublime::Right); //check that area is in valid state AreaToolViewsPrinter toolViewsPrinter1; m_area1->walkToolViews(toolViewsPrinter1, Sublime::AllPositions); QCOMPARE(toolViewsPrinter1.result, QStringLiteral("\n\ toolview1.1.1 [ left ]\n\ toolview1.2.1 [ bottom ]\n\ toolview1.2.2 [ bottom ]\n\ toolview1.4.1 [ right ]\n\ ")); //check that mainwindow has newly added toolview foreach (View *dock, mw.toolDocks()) - QVERIFY(dock->widget() != 0); + QVERIFY(dock->widget() != nullptr); QCOMPARE(mw.toolDocks().count(), m_area1->toolViews().count()); //now remove toolview m_area1->removeToolView(view); AreaToolViewsPrinter toolViewsPrinter2; //check that area doesn't have it anymore m_area1->walkToolViews(toolViewsPrinter2, Sublime::AllPositions); QCOMPARE(toolViewsPrinter2.result, QStringLiteral("\n\ toolview1.1.1 [ left ]\n\ toolview1.2.1 [ bottom ]\n\ toolview1.2.2 [ bottom ]\n\ ")); //check that mainwindow has newly added toolview foreach (View *dock, mw.toolDocks()) - QVERIFY(dock->widget() != 0); + QVERIFY(dock->widget() != nullptr); QCOMPARE(mw.toolDocks().count(), m_area1->toolViews().count()); } void TestAreaOperation::testAddingViewAfter() { QList list(m_area3->views()); foreach (View *view, list){ qDebug() << "name of view : " << view->objectName() << " , it's index : " << m_area3->views().indexOf(view); } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void TestAreaOperation::splitViewActiveTabsTest() { MainWindow mw(m_controller); m_controller->showArea(m_area1, &mw); checkArea1(&mw); // at first show of the area, the active view should be m_pView111 QCOMPARE(mw.activeView(), m_pView111); // Try to get to the main container : // get the central widget QWidget *pCentral = mw.centralWidget(); QVERIFY(pCentral); QVERIFY(pCentral->inherits("QWidget")); // get its first splitter QWidget *pSplitter = pCentral->findChild(); QVERIFY(pSplitter); QVERIFY(pSplitter->inherits("QSplitter")); // finally, get the splitter's container Container *pContainer = pSplitter->findChild(); QVERIFY(pContainer); // verify that the current active widget in the container is the one in activeview (m_pView111) QCOMPARE(pContainer->currentWidget(), mw.activeView()->widget()); // activate the second tab of the area (view212) mw.activateView(m_pView121); // verify that the active view was correctly updated to m_pView121 QCOMPARE(mw.activeView(), m_pView121); // check if the container's current widget was updated to the active view's QCOMPARE(pContainer->currentWidget(), mw.activeView()->widget()); // now, create a split view of the active view (m_pView121) Sublime::View *pNewView = mw.activeView()->document()->createView(); pNewView->setObjectName("splitOf" + mw.activeView()->objectName()); m_area1->addView(pNewView, mw.activeView(), Qt::Vertical); // verify that creating a new view did not break the central widget QCOMPARE(pCentral, mw.centralWidget()); // verify that creating a new view did not break the main splitter QCOMPARE(pSplitter, pCentral->findChild()); // creating a new view created two new children splitters, get them QVERIFY(pSplitter->findChildren().size() == 2); QWidget *pFirstSplitter = pSplitter->findChildren().at(0); QVERIFY(pFirstSplitter); QWidget *pSecondSplitter = pSplitter->findChildren().at(1); QVERIFY(pSecondSplitter); // for each splitter, get the corresponding container Container *pFirstContainer = pFirstSplitter->findChild(); QVERIFY(pFirstContainer); Container *pSecondContainer = pSecondSplitter->findChild(); QVERIFY(pSecondContainer); // the active view should have remained view121 QCOMPARE(mw.activeView(), m_pView121); // pFirstContainer should contain the newView's widget QVERIFY(pFirstContainer->hasWidget(pNewView->widget())); // the new view's widget should be the current widget of the new container QCOMPARE(pFirstContainer->currentWidget(), pNewView->widget()); // pSecondContainer should contain all the old views widgets QVERIFY(pSecondContainer->hasWidget(m_pView111->widget())); QVERIFY(pSecondContainer->hasWidget(m_pView121->widget())); QVERIFY(pSecondContainer->hasWidget(m_pView122->widget())); QVERIFY(pSecondContainer->hasWidget(m_pView131->widget())); // the active widget should be the current widget of the second container QCOMPARE(pSecondContainer->currentWidget(), mw.activeView()->widget()); //////////////////////////////////////////////////////////////////////////// // now, activate the new view and check if all went well mw.activateView(pNewView); // active view should now be newView QCOMPARE(mw.activeView(), pNewView); // the active widget should be the current widget of the new container QCOMPARE(pFirstContainer->currentWidget(), mw.activeView()->widget()); // the current widget of the old container should have remained view121's QCOMPARE(pSecondContainer->currentWidget(), m_pView121->widget()); //////////////////////////////////////////////////////////////////////////// // now delete newView and check area state delete m_area1->removeView(pNewView); // verify that deleting the view did not broke the central widget QCOMPARE(pCentral, mw.centralWidget()); // removing the view should have destroyed the sub splitters and containers, // so get the main one and verify that deleting the view did not break it QCOMPARE(pSplitter, pCentral->findChild()); // get the new container inside the main splitter pContainer = pSplitter->findChild(); QVERIFY(pContainer); // active view should now be back to m_pView121 again QCOMPARE(mw.activeView(), m_pView121); // check also the container current widget QCOMPARE(pContainer->currentWidget(), mw.activeView()->widget()); } void TestAreaOperation::checkAreaViewsDisplay(MainWindow *mw, Area *area, const QString &printedAreas, int containerCount, int splitterCount, QString location) { //check area AreaViewsPrinter viewsPrinter; area->walkViews(viewsPrinter, area->rootIndex()); QCOMPARE(viewsPrinter.result, printedAreas); //check mainwindow QWidget *central = mw->centralWidget(); - QVERIFY(central != 0); + QVERIFY(central != nullptr); QVERIFY(central->inherits("QWidget")); QWidget *splitter = central->findChild(); QVERIFY(splitter); QVERIFY(splitter->inherits("QSplitter")); //check containers QList containers = splitter->findChildren(); QString failMsg = QStringLiteral("\nFailure while checking area contents @ %1\n" "Expected %2 containers in central splitter but got %3 \n"). arg(location).arg(containerCount).arg(containers.count()); QVERIFY2(containers.count() == containerCount, failMsg.toLatin1().data()); int widgetCount = 0; foreach (Container *c, containers) { for (int i = 0; i < c->count(); ++i) { - QVERIFY(c->widget(i) != 0); - QVERIFY(c->widget(i)->parentWidget() != 0); + QVERIFY(c->widget(i) != nullptr); + QVERIFY(c->widget(i)->parentWidget() != nullptr); } widgetCount += c->count(); } ViewCounter c; area->walkViews(c, area->rootIndex()); QCOMPARE(widgetCount, c.count); QList splitters = splitter->findChildren(); splitters.append(qobject_cast(splitter)); QCOMPARE(splitters.count(), splitterCount); } View *TestAreaOperation::findNamedView(Area *area, const QString &name) { foreach (View *view, area->views()) if (view->objectName() == name) return view; - return 0; + return nullptr; } /////////// QTEST_MAIN(TestAreaOperation) diff --git a/sublime/tests/test_controller.cpp b/sublime/tests/test_controller.cpp index d91a8b2081..c2166c0962 100644 --- a/sublime/tests/test_controller.cpp +++ b/sublime/tests/test_controller.cpp @@ -1,87 +1,87 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_controller.h" #include #include #include #include #include #include #include using namespace Sublime; void TestController::documentDeletion() { Controller controller; Document *doc = new ToolDocument(QStringLiteral("tool"), &controller, new SimpleToolWidgetFactory(QStringLiteral("tool"))); QCOMPARE(controller.documents().count(), 1); delete doc; QCOMPARE(controller.documents().count(), 0); } void TestController::areaDeletion() { Controller* controller = new Controller; Document *doc = new ToolDocument(QStringLiteral("tool"), controller, new SimpleToolWidgetFactory(QStringLiteral("tool"))); //create a view which does not belong to an area View* view1 = doc->createView(); Q_UNUSED(view1); //create an area and two views in it Area *area = new Area(controller, QStringLiteral("MyArea")); controller->addDefaultArea(area); QCOMPARE(controller->defaultAreas().count(), 1); View* view2 = doc->createView(); view2->setObjectName(QStringLiteral("VIEW2")); area->addView(view2); View* view3 = doc->createView(); view3->setObjectName(QStringLiteral("VIEW3")); area->addView(view3); QCOMPARE(doc->views().count(), 3); QCOMPARE(area->views().count(), 2); delete area; - view2 = 0; view3= 0; + view2 = nullptr; view3= nullptr; QEXPECT_FAIL("", "Fails because of delayed view deletion", Continue); QCOMPARE(doc->views().count(), 1); QCOMPARE(controller->defaultAreas().count(), 0); QTest::qWait(100); // wait for deleteLaters qDebug() << "Deleting doc"; delete doc; QTest::qWait(100); // wait for deleteLaters qDebug() << "View2 & view3 are destructored at this point (but no earlier)."; } void TestController::namedAreas() { Controller controller; Area *area1 = new Area(&controller, QStringLiteral("1")); controller.addDefaultArea(area1); Area *area2 = new Area(&controller, QStringLiteral("2")); controller.addDefaultArea(area2); QCOMPARE(controller.defaultArea(QStringLiteral("1")), area1); QCOMPARE(controller.defaultArea(QStringLiteral("2")), area2); } QTEST_MAIN(TestController) diff --git a/sublime/tests/test_toolviewtoolbar.cpp b/sublime/tests/test_toolviewtoolbar.cpp index 6841b6dfc9..67d3147634 100644 --- a/sublime/tests/test_toolviewtoolbar.cpp +++ b/sublime/tests/test_toolviewtoolbar.cpp @@ -1,134 +1,134 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Andreas Pakulat * * Copyright 2008 Manuel Breugelmans * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_toolviewtoolbar.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Sublime; class ToolViewToolBarFactory : public SimpleToolWidgetFactory { public: ToolViewToolBarFactory(const QString &id): SimpleToolWidgetFactory(id) {} QList toolBarActions( QWidget* ) const override { - QAction* action = new QAction(actionText, 0); + QAction* action = new QAction(actionText, nullptr); return QList() << action; } QString actionText; }; void TestToolViewToolBar::init() { // this is starting to become a GeneralFixture controller = new Controller(this); area = new Area( controller, QStringLiteral("Area") ); MainWindow* mw = new MainWindow(controller); // a horizontal tool with toolbar ToolViewToolBarFactory* factoryT1 = new ToolViewToolBarFactory(QStringLiteral("tool1factory")); actionTextT1 = QStringLiteral("Tool1Action"); factoryT1->actionText = actionTextT1; tool1 = new ToolDocument( QStringLiteral("tool1"), controller, factoryT1 ); viewT11 = tool1->createView(); area->addToolView( viewT11, Sublime::Bottom ); // a vertical tool with toolbar ToolViewToolBarFactory* factoryT2 = new ToolViewToolBarFactory(QStringLiteral("tool2factory")); actionTextT2 = QStringLiteral("Tool2Action"); factoryT2->actionText = actionTextT2; tool2 = new ToolDocument( QStringLiteral("tool2"), controller, factoryT2 ); viewT21 = tool2->createView(); area->addToolView( viewT21, Sublime::Left ); controller->showArea(area, mw); } void TestToolViewToolBar::cleanup() { delete controller; } QToolBar* TestToolViewToolBar::fetchToolBarFor(Sublime::View* view) { QWidget* toolWidget = view->widget(); const char* loc = "fetchToolBarFor"; Q_UNUSED(loc); Q_ASSERT_X(toolWidget, loc, "Tool refuses to create widget (null)."); Q_ASSERT(toolWidget->parent()); QMainWindow* toolWin = dynamic_cast(toolWidget->parent()); Q_ASSERT_X(toolWin, loc, "Tool widget's parent is not a QMainWindow."); QList toolBars = toolWin->findChildren(); int barCount = toolBars.count(); char* failMsg = qstrdup(QStringLiteral("Expected to find a toolbar but found %1").arg(barCount).toLatin1().data()); Q_UNUSED(failMsg); Q_ASSERT_X(barCount == 1, loc, failMsg); return toolBars.at(0); } void TestToolViewToolBar::assertGoodBar(QToolBar* toolbar, QString actionText) { QVERIFY( toolbar ); QVERIFY( !toolbar->isFloatable() ); QCOMPARE( toolbar->iconSize(), QSize( 16, 16 ) ); QList actions = toolbar->actions(); QCOMPARE( actions.count(), 1 ); QCOMPARE( actions.at(0)->text(), actionText); QCOMPARE( toolbar->orientation(), Qt::Horizontal ); } void TestToolViewToolBar::horizontalTool() { // viewT11 was added with Sublime::Bottom, so it should have a horizontal bar QToolBar* bar = fetchToolBarFor(viewT11); assertGoodBar(bar, actionTextT1); } void TestToolViewToolBar::verticalTool() { // viewT21 was added with Sublime::Left, so it should have a vertical bar QToolBar* bar = fetchToolBarFor(viewT21); assertGoodBar(bar, actionTextT2); } void TestToolViewToolBar::toolViewMove() { area->moveToolView( viewT11, Sublime::Right ); area->moveToolView( viewT21, Sublime::Bottom ); QToolBar* barT1 = fetchToolBarFor(viewT11); QToolBar* barT2 = fetchToolBarFor(viewT21); assertGoodBar(barT1, actionTextT1); assertGoodBar(barT2, actionTextT2); } QTEST_MAIN(TestToolViewToolBar) diff --git a/sublime/tests/test_view.cpp b/sublime/tests/test_view.cpp index d661d6dea0..f4df89302f 100644 --- a/sublime/tests/test_view.cpp +++ b/sublime/tests/test_view.cpp @@ -1,73 +1,73 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_view.h" #include #include #include #include #include using namespace Sublime; void TestView::widgetDeletion() { Controller controller; Document *doc = new ToolDocument(QStringLiteral("tool"), &controller, new SimpleToolWidgetFactory(QStringLiteral("tool"))); View *view = doc->createView(); //create the widget view->widget(); QVERIFY(view->hasWidget()); QCOMPARE(view->widget()->metaObject()->className(), "QTextEdit"); //delete the widget and check that view knows about that delete view->widget(); QVERIFY(!view->hasWidget()); } class Test: public View { Q_OBJECT public: Test(Document *doc): View(doc) {} }; class TestDocument: public Document { Q_OBJECT public: TestDocument(Controller *controller): Document(QStringLiteral("TestDocument"), controller) {} QString documentType() const override { return QStringLiteral("Test"); } QString documentSpecifier() const override { return QString(); } protected: - QWidget *createViewWidget(QWidget *parent = 0) override { return new QWidget(parent); } + QWidget *createViewWidget(QWidget *parent = nullptr) override { return new QWidget(parent); } View *newView(Document *doc) override { return new Test(doc); } }; void TestView::viewReimplementation() { Controller controller; Document *doc = new TestDocument(&controller); View *view = doc->createView(); - QVERIFY(dynamic_cast(view) != 0); + QVERIFY(dynamic_cast(view) != nullptr); } QTEST_MAIN(TestView) #include "test_view.moc" diff --git a/sublime/tests/test_viewactivation.cpp b/sublime/tests/test_viewactivation.cpp index d4c3d2005d..beb8d0e089 100644 --- a/sublime/tests/test_viewactivation.cpp +++ b/sublime/tests/test_viewactivation.cpp @@ -1,227 +1,227 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "test_viewactivation.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Sublime; template class SpecialWidgetFactory: public SimpleToolWidgetFactory { public: SpecialWidgetFactory(const QString &id): SimpleToolWidgetFactory(id) {} - QWidget* create(ToolDocument *doc, QWidget *parent = 0) override + QWidget* create(ToolDocument *doc, QWidget *parent = nullptr) override { QWidget *w = new QWidget(parent); Widget *inner = new Widget(w); inner->setObjectName(doc->title()+"_inner"); w->setObjectName(doc->title()+"_outer"); return w; } }; void TestViewActivation::initTestCase() { qRegisterMetaType("View*"); } void TestViewActivation::init() { controller = new Controller(this); doc1 = new ToolDocument(QStringLiteral("doc1"), controller, new SimpleToolWidgetFactory(QStringLiteral("doc1"))); //this document will create special widgets - QListView nested in QWidget doc2 = new ToolDocument(QStringLiteral("doc2"), controller, new SpecialWidgetFactory(QStringLiteral("doc2"))); doc3 = new ToolDocument(QStringLiteral("doc3"), controller, new SimpleToolWidgetFactory(QStringLiteral("doc3"))); doc4 = new ToolDocument(QStringLiteral("doc4"), controller, new SimpleToolWidgetFactory(QStringLiteral("doc4"))); tool1 = new ToolDocument(QStringLiteral("tool1"), controller, new SimpleToolWidgetFactory(QStringLiteral("tool1"))); tool2 = new ToolDocument(QStringLiteral("tool2"), controller, new SimpleToolWidgetFactory(QStringLiteral("tool2"))); tool3 = new ToolDocument(QStringLiteral("tool3"), controller, new SimpleToolWidgetFactory(QStringLiteral("tool3"))); area = new Area(controller, QStringLiteral("Area")); view211 = doc1->createView(); view211->setObjectName(QStringLiteral("view211")); area->addView(view211); view212 = doc1->createView(); view212->setObjectName(QStringLiteral("view212")); area->addView(view212); view221 = doc2->createView(); area->addView(view221, view211, Qt::Vertical); view231 = doc3->createView(); area->addView(view231, view221, Qt::Horizontal); view241 = doc4->createView(); area->addView(view241, view212, Qt::Vertical); viewT11 = tool1->createView(); area->addToolView(viewT11, Sublime::Bottom); viewT21 = tool2->createView(); area->addToolView(viewT21, Sublime::Right); viewT31 = tool3->createView(); area->addToolView(viewT31, Sublime::Top); viewT32 = tool3->createView(); area->addToolView(viewT32, Sublime::Top); } void TestViewActivation::cleanup() { delete controller; } void TestViewActivation::signalsOnViewCreationAndDeletion() { Controller *controller = new Controller(this); ToolDocument *doc1 = new ToolDocument(QStringLiteral("doc1"), controller, new SimpleToolWidgetFactory(QStringLiteral("doc1"))); Area *area = new Area(controller, QStringLiteral("Area")); QSignalSpy spy(controller, SIGNAL(viewAdded(Sublime::View*))); View *v = doc1->createView(); area->addView(v); QCOMPARE(spy.count(), 1); QSignalSpy spy2(controller, SIGNAL(aboutToRemoveView(Sublime::View*))); area->removeView(v); QCOMPARE(spy2.count(), 1); QSignalSpy spy3(controller, SIGNAL(toolViewAdded(Sublime::View*))); v = doc1->createView(); area->addToolView(v, Sublime::Bottom); QCOMPARE(spy3.count(), 1); QSignalSpy spy4(controller, SIGNAL(aboutToRemoveToolView(Sublime::View*))); area->removeToolView(v); QCOMPARE(spy4.count(), 1); delete controller; } void TestViewActivation::viewActivation() { MainWindow* mw = new MainWindow(controller); controller->addDefaultArea(area); // Q_ASSERT without this. controller->addMainWindow(mw); controller->showArea(area, mw); //we should have an active view immediatelly after the area is shown QCOMPARE(mw->activeView(), view211); //add some widgets that are not in layout QTextEdit *breaker = new QTextEdit(mw); breaker->setObjectName(QStringLiteral("breaker")); QTextEdit *toolBreaker = new QTextEdit(mw); toolBreaker->setObjectName(QStringLiteral("toolBreaker")); QDockWidget *dock = new QDockWidget(mw); dock->setWidget(toolBreaker); mw->addDockWidget(Qt::LeftDockWidgetArea, dock); //now post events to the widgets and see if mainwindow has the right active views //activate view qApp->sendEvent(view212->widget(), new QFocusEvent(QEvent::FocusIn)); QString failMsg = QStringLiteral("\nWas expecting %1 to be active but got %2"). arg(view212->objectName(), mw->activeView()->objectName()); QVERIFY2(mw->activeView() == view212, failMsg.toLatin1().data()); //activate toolview and check that both view and toolview are active qApp->sendEvent(viewT31->widget(), new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view212); QCOMPARE(mw->activeToolView(), viewT31); //active another view qApp->sendEvent(view241->widget(), new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view241); QCOMPARE(mw->activeToolView(), viewT31); //focus a widget not in the area qApp->sendEvent(breaker, new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view241); QCOMPARE(mw->activeToolView(), viewT31); //focus a dock not in the area qApp->sendEvent(toolBreaker, new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view241); QCOMPARE(mw->activeToolView(), viewT31); //focus inner widget for view221 QListView *inner = mw->findChild(QStringLiteral("doc2_inner")); QVERIFY(inner); qApp->sendEvent(inner, new QFocusEvent(QEvent::FocusIn)); QCOMPARE(mw->activeView(), view221); QCOMPARE(mw->activeToolView(), viewT31); } void TestViewActivation::activationInMultipleMainWindows() { MainWindow mw(controller); controller->showArea(area, &mw); QCOMPARE(mw.activeView(), view211); //check that new mainwindow always have active view right after displaying area MainWindow mw2(controller); controller->showArea(area, &mw2); QVERIFY(mw2.activeView()); QCOMPARE(mw2.activeView()->document(), doc1); } void TestViewActivation::activationAfterViewRemoval() { MainWindow mw(controller); controller->showArea(area, &mw); QCOMPARE(mw.activeView(), view211); //check what happens if we remove a view which is not the only one in container delete area->removeView(view211); QCOMPARE(mw.activeView(), view212); //check what happens if we remove a view which is alone in container mw.activateView(view231); QCOMPARE(mw.activeView(), view231); delete area->removeView(view231); QCOMPARE(mw.activeView(), view221); } void TestViewActivation::activationAfterRemovalSimplestCase() { //we don't have split views - just two views in one area index MainWindow mw(controller); Area *area = new Area(controller, QStringLiteral("Area")); View *v1 = doc1->createView(); View *v2 = doc2->createView(); area->addView(v1); area->addView(v2, v1); controller->showArea(area, &mw); mw.activateView(v2); //delete active view and check that previous is activated delete area->removeView(v2); QCOMPARE(mw.activeView(), v1); } QTEST_MAIN(TestViewActivation) diff --git a/sublime/view.cpp b/sublime/view.cpp index fe616e7528..8632cb83d7 100644 --- a/sublime/view.cpp +++ b/sublime/view.cpp @@ -1,139 +1,139 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "view.h" #include #include "document.h" #include "tooldocument.h" namespace Sublime { class View; class Document; struct ViewPrivate { ViewPrivate(); Document *doc; QWidget *widget; void unsetWidget(); View::WidgetOwnership ws; }; ViewPrivate::ViewPrivate() - :doc(0), widget(0) + :doc(nullptr), widget(nullptr) { } void ViewPrivate::unsetWidget() { - widget = 0; + widget = nullptr; } View::View(Document *doc, WidgetOwnership ws ) :QObject(doc), d(new ViewPrivate) { d->doc = doc; d->ws = ws; } View::~View() { if (d->widget && d->ws == View::TakeOwnership ) { d->widget->hide(); - d->widget->setParent(0); + d->widget->setParent(nullptr); d->widget->deleteLater(); } delete d; } Document *View::document() const { return d->doc; } QWidget *View::widget(QWidget *parent) { if (!d->widget) { d->widget = createWidget(parent); connect(d->widget, &QWidget::destroyed, this, [&] { d->unsetWidget(); }); } return d->widget; } QWidget *View::createWidget(QWidget *parent) { return d->doc->createViewWidget(parent); } bool View::hasWidget() const { - return d->widget != 0; + return d->widget != nullptr; } void View::requestRaise() { emit raise(this); } QString View::viewState() const { return QString(); } void View::setState(const QString & state) { Q_UNUSED(state); } QList View::toolBarActions() const { ToolDocument* tooldoc = dynamic_cast( document() ); if( tooldoc ) { return tooldoc->factory()->toolBarActions( d->widget ); } return QList(); } QList< QAction* > View::contextMenuActions() const { ToolDocument* tooldoc = dynamic_cast( document() ); if( tooldoc ) { return tooldoc->factory()->contextMenuActions( d->widget ); } return QList(); } QString View::viewStatus() const { return QString(); } void View::notifyPositionChanged(int newPositionInArea) { emit positionChanged(this, newPositionInArea); } } #include "moc_view.cpp" diff --git a/tests/kdevsignalspy.cpp b/tests/kdevsignalspy.cpp index de1f63e579..7fdc74434a 100644 --- a/tests/kdevsignalspy.cpp +++ b/tests/kdevsignalspy.cpp @@ -1,62 +1,62 @@ /* * This file is part of KDevelop * Copyright 2008 Manuel Breugelmans * * 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. */ #include "kdevsignalspy.h" #include #include namespace KDevelop { KDevSignalSpy::KDevSignalSpy(QObject *obj, const char *signal, Qt::ConnectionType ct ) - : QObject(0), m_obj(obj), m_emitted(false) + : QObject(nullptr), m_obj(obj), m_emitted(false) { m_timer = new QTimer(this); m_loop = new QEventLoop(this); connect(obj, signal, this, SLOT(signalEmitted()), ct); } bool KDevSignalSpy::wait(int timeout) { Q_ASSERT(!m_loop->isRunning()); Q_ASSERT(!m_timer->isActive()); m_emitted = false; if (timeout > 0) { connect(m_timer, &QTimer::timeout, m_loop, &QEventLoop::quit); m_timer->setSingleShot(true); m_timer->start(timeout); } m_loop->exec(); return m_emitted; } void KDevSignalSpy::signalEmitted() { m_emitted = true; - disconnect(m_obj, 0, this, 0); + disconnect(m_obj, nullptr, this, nullptr); m_timer->stop(); m_loop->quit(); } } // KDevelop diff --git a/tests/testplugincontroller.cpp b/tests/testplugincontroller.cpp index 1227bce298..f86f58e2e1 100644 --- a/tests/testplugincontroller.cpp +++ b/tests/testplugincontroller.cpp @@ -1,88 +1,88 @@ /* * This file is part of KDevelop * * Copyright 2012 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testplugincontroller.h" #include using namespace KDevelop; TestPluginController::TestPluginController(Core* core) : PluginController(core) { } void TestPluginController::initialize() { } QList TestPluginController::allPluginsForExtension(const QString& extension, const QVariantMap& constraints) { Q_UNUSED(extension); Q_UNUSED(constraints); return QList(); } QList TestPluginController::loadedPlugins() const { return QList(); } IPlugin* TestPluginController::pluginForExtension(const QString& extension, const QString& pluginname, const QVariantMap& constraints) { Q_UNUSED(extension); Q_UNUSED(pluginname); Q_UNUSED(constraints); - return 0; + return nullptr; } QVector TestPluginController::queryExtensionPlugins(const QString& extension, const QVariantMap& constraints) const { Q_UNUSED(extension); Q_UNUSED(constraints); return {}; } IPlugin* TestPluginController::loadPlugin(const QString& pluginName) { Q_UNUSED(pluginName); - return 0; + return nullptr; } KPluginMetaData TestPluginController::pluginInfo(const IPlugin*) const { return KPluginMetaData(); } QList< ContextMenuExtension > TestPluginController::queryPluginsForContextMenuExtensions(Context* context) const { Q_UNUSED(context); return QList< ContextMenuExtension >(); } bool TestPluginController::unloadPlugin(const QString& plugin) { Q_UNUSED(plugin); return false; } diff --git a/tests/testproject.cpp b/tests/testproject.cpp index 0a8e598789..659881f902 100644 --- a/tests/testproject.cpp +++ b/tests/testproject.cpp @@ -1,132 +1,132 @@ /*************************************************************************** * Copyright 2010 Niko Sams * * Copyright 2012 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "testproject.h" #include #include using namespace KDevelop; TestProject::TestProject(const Path& path, QObject* parent) : IProject(parent) -, m_root(0) +, m_root(nullptr) , m_projectConfiguration(KSharedConfig::openConfig()) { m_path = path.isValid() ? path : Path(QStringLiteral("/tmp/kdev-testproject/")); m_root = new ProjectFolderItem(this, m_path); ICore::self()->projectController()->projectModel()->appendRow( m_root ); } void TestProject::setPath(const Path& path) { m_path = path; if (m_root) { m_root->setPath(path); } } TestProject::~TestProject() { if (m_root) { delete m_root; } } ProjectFolderItem* TestProject::projectItem() const { return m_root; } void TestProject::setProjectItem(ProjectFolderItem* item) { if (m_root) { ICore::self()->projectController()->projectModel()->removeRow( m_root->row() ); - m_root = 0; + m_root = nullptr; m_path.clear(); } if (item) { m_root = item; m_path = item->path(); ICore::self()->projectController()->projectModel()->appendRow( m_root ); } } Path TestProject::projectFile() const { return Path(m_path, m_path.lastPathSegment() + ".kdev4"); } Path TestProject::path() const { return m_path; } bool TestProject::inProject(const IndexedString& path) const { return m_path.isParentOf(Path(path.str())); } void findFileItems(ProjectBaseItem* root, QList& items, const Path& path = {}) { foreach(ProjectBaseItem* item, root->children()) { if (item->file() && (path.isEmpty() || item->path() == path)) { items << item->file(); } if (item->rowCount()) { findFileItems(item, items, path); } } } QList< ProjectFileItem* > TestProject::files() const { QList ret; findFileItems(m_root, ret); return ret; } QList TestProject::filesForPath(const IndexedString& path) const { QList ret; findFileItems(m_root, ret, Path(path.toUrl())); return ret; } void TestProject::addToFileSet(ProjectFileItem* file) { if (!m_fileSet.contains(file->indexedPath())) { m_fileSet.insert(file->indexedPath()); emit fileAddedToSet(file); } } void TestProject::removeFromFileSet(ProjectFileItem* file) { if (m_fileSet.remove(file->indexedPath())) { emit fileRemovedFromSet(file); } } void TestProjectController::initialize() { } diff --git a/util/dbus_socket_transformer/main.cpp b/util/dbus_socket_transformer/main.cpp index 2e951a1e42..d5ff38baf0 100644 --- a/util/dbus_socket_transformer/main.cpp +++ b/util/dbus_socket_transformer/main.cpp @@ -1,416 +1,416 @@ /*************************************************************************** * Copyright 2011 David Nolden * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif /** * The goal of this utility is transforming the abstract unix-socket which is used by dbus * into a TCP socket which can be forwarded to a target machine by ssh tunneling, and then on * the target machine back into an abstract unix socket. * * This tool basically works similar to the "socat" utility, except that it works properly * for this special case. It is merely responsible for the transformation between abstract unix * sockets and tcp sockets. * * Furthermore, this tool makes the 'EXTERNAL' dbus authentication mechanism work even across * machines with different user IDs. * * This is how the EXTERNAL mechanism works (I found this in a comment of some ruby dbus library): * Take the user id (eg integer 1000) make a string out of it "1000", take * each character and determin hex value "1" => 0x31, "0" => 0x30. You * obtain for "1000" => 31303030 This is what the server is expecting. * Why? I dunno. How did I come to that conclusion? by looking at rbus * code. I have no idea how he found that out. * * The dbus client performs the EXTERNAL authentication by sending "AUTH EXTERNAL 31303030\r\n" once * after opening the connection, so we can "repair" the authentication by overwriting the token in that * string through the correct one. * */ const bool debug = false; /** * Returns the valid dbus EXTERNAL authentication token for the current user (see above) * */ std::string getAuthToken() { // Get uid int uid = getuid(); std::ostringstream uidStream; uidStream << uid; std::string uidStr = uidStream.str(); const char hexdigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; std::ostringstream hexStream; for(uint i = 0; i < uidStr.size(); ++i) { unsigned char byte = (unsigned char)uidStr[i]; hexStream << hexdigits[byte >> 4] << hexdigits[byte & 0x0f]; } return hexStream.str(); } /** * Shuffles all data between the two file-descriptors until one of them fails or reaches EOF. * */ void shuffleBetweenStreams(int side1, int side2, bool fixSide1AuthToken) { char buffer[1000]; char buffer2[1000]; // Set non-blocking mode int opts = fcntl(side1,F_GETFL); opts |= O_NONBLOCK; fcntl(side1, F_SETFL, opts); opts = fcntl(side2,F_GETFL); opts |= O_NONBLOCK; fcntl(side2, F_SETFL, opts); while(true) { int r1 = read(side1, buffer, 500); // We read less than 1000, so we have same additional space when changing the auth token int r2 = read(side2, buffer2, 500); if(r1 < -1 || r1 == 0) { if(debug) std::cerr << "stream 1 failed: " << r1 << std::endl; return; } if(r2 < -1 || r2 == 0) { if(debug) std::cerr << "stream 2 failed: " << r2 << std::endl; return; } if(r1 > 0) { if(debug) std::cerr << "transferring " << r1 << " from 1 to 2" << std::endl; if(fixSide1AuthToken) { if(r1 > 15 && memcmp(buffer, "\0AUTH EXTERNAL ", 15) == 0) { int endPos = -1; for(int i = 15; i < r1; ++i) { if(buffer[i] == '\r') { endPos = i; break; } } if(endPos != -1) { std::string oldToken = std::string(buffer + 15, endPos - 15); std::string newToken = getAuthToken(); int difference = newToken.size() - oldToken.size(); r1 += difference; assert(r1 > 0 && r1 <= 1000); memmove(buffer + endPos + difference, buffer + endPos, r1 - difference - endPos); memcpy(buffer + 15, newToken.data(), newToken.size()); assert(buffer[endPos + difference] == '\r'); assert(buffer[endPos + difference - 1] == newToken[newToken.size()-1]); }else{ std::cout << "could not fix auth token, not enough data available" << std::endl; } }else{ std::cout << "could not fix auth token" << std::endl; } fixSide1AuthToken = false; } opts = fcntl(side2,F_GETFL); opts ^= O_NONBLOCK; fcntl(side2, F_SETFL, opts); int w2 = send(side2, buffer, r1, MSG_NOSIGNAL); if(w2 < 0) { if(debug) std::cerr << "writing to side 2 failed, ending: " << w2 << std::endl; return; } assert(w2 == r1); opts = fcntl(side2,F_GETFL); opts |= O_NONBLOCK; fcntl(side2, F_SETFL, opts); } if(r2 > 0) { if(debug) std::cerr << "transferring " << r2 << " from 2 to 1" << std::endl; opts = fcntl(side1,F_GETFL); opts ^= O_NONBLOCK; fcntl(side1, F_SETFL, opts); int w1 = send(side1, buffer2, r2, MSG_NOSIGNAL); if(w1 < 0) { if(debug) std::cerr << "writing to side 1 failed, ending: " << w1 << std::endl; return; } assert(w1 == r2); opts = fcntl(side1,F_GETFL); opts |= O_NONBLOCK; fcntl(side1, F_SETFL, opts); } usleep(1000); } } int main(int argc, char** argv) { int serverfd; if(argc < 2) { std::cerr << "need arguments:" << std::endl; std::cerr << "[port] - Open a server on this TCP port and forward connections to the local DBUS session" " (the DBUS_SESSION_BUS_ADDRESS environment variable must be set)"; std::cerr << "[port] [fake dbus path] - Open a server on the fake dbus path and forward connections to the given local TCP port"; std::cerr << "" "The last argument may be the --bind-only option, in which case the application only tries to" "open the server, but does not wait for clients to connect. This is useful to test whether the" "server port/path is available."; return 10; } bool waitForClients = true; if(std::string(argv[argc-1]) == "--bind-only") { waitForClients = false; argc -= 1; } std::string dbusAddress(getenv("DBUS_SESSION_BUS_ADDRESS")); std::string path; if(argc == 2) { if(waitForClients && debug) std::cout << "forwarding from the local TCP port " << argv[1] << " to the local DBUS session at " << dbusAddress.data() << std::endl; if(dbusAddress.empty()) { std::cerr << "The DBUS_SESSION_BUS_ADDRESS environment variable is not set" << std::endl; return 1; } // Open a TCP server std::string abstractPrefix("unix:abstract="); if(dbusAddress.substr(0, abstractPrefix.size()) != abstractPrefix) { std::cerr << "DBUS_SESSION_BUS_ADDRESS does not seem to use an abstract unix domain socket as expected" << std::endl; return 2; } path = dbusAddress.substr(abstractPrefix.size(), dbusAddress.size() - abstractPrefix.size()); if(path.find(",guid=") != std::string::npos) path = path.substr(0, path.find(",guid=")); // Mark it as an abstract unix domain socket path = path; serverfd = socket(AF_INET, SOCK_STREAM, 0); if (serverfd < 0) { if(waitForClients) std::cerr << "ERROR opening server socket" << std::endl; return 3; } int portno = atoi(argv[1]); sockaddr_in server_addr; bzero((char *) &server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(portno); if (bind(serverfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) { if(waitForClients) std::cerr << "ERROR opening the server" << std::endl; return 7; } }else if(argc == 3) { if(waitForClients && debug) std::cout << "forwarding from the local abstract unix domain socket " << argv[2] << " to the local TCP port " << argv[1] << std::endl; // Open a unix domain socket server serverfd = socket(AF_UNIX, SOCK_STREAM, 0); if (serverfd < 0) { if(waitForClients) std::cerr << "ERROR opening server socket" << std::endl; return 3; } path = std::string(argv[2]); sockaddr_un serv_addr; bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; serv_addr.sun_path[0] = '\0'; // Mark as an abstract socket strcpy(serv_addr.sun_path+1, path.data()); if(debug) std::cout << "opening at " << path.data() << std::endl; if (bind(serverfd,(sockaddr *) &serv_addr, sizeof (serv_addr.sun_family) + 1 + path.length()) < 0) { if(waitForClients) std::cerr << "ERROR opening the server" << std::endl; return 7; } }else{ std::cerr << "Wrong arguments"; return 1; } listen(serverfd, 10); while(waitForClients) { if(debug) std::cerr << "waiting for client" << std::endl; sockaddr_in cli_addr; socklen_t clilen = sizeof(cli_addr); int connectedclientsockfd = accept(serverfd, (struct sockaddr *) &cli_addr, &clilen); if(connectedclientsockfd < 0) { std::cerr << "ERROR on accept" << std::endl; return 8; } if(debug) std::cerr << "got client" << std::endl; int sockfd; int addrSize; - sockaddr* useAddr = 0; + sockaddr* useAddr = nullptr; sockaddr_un serv_addru; sockaddr_in serv_addrin; if(argc == 2) { sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) { std::cerr << "ERROR opening socket" << std::endl; return 3; } bzero((char *) &serv_addru, sizeof(serv_addru)); serv_addru.sun_family = AF_UNIX; serv_addru.sun_path[0] = '\0'; // Mark as an abstract socket strcpy(serv_addru.sun_path+1, path.data()); addrSize = sizeof (serv_addru.sun_family) + 1 + path.size(); useAddr = (sockaddr*)&serv_addru; if(debug) std::cout << "connecting to " << path.data() << std::endl; }else{ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { std::cerr << "ERROR opening socket" << std::endl; return 3; } int port = atoi(argv[1]); hostent *server = gethostbyname("localhost"); - if(server == NULL) + if(server == nullptr) { std::cerr << "failed to get server" << std::endl; return 5; } bzero((char *) &serv_addrin, sizeof(serv_addrin)); serv_addrin.sin_family = AF_INET; serv_addrin.sin_addr.s_addr = INADDR_ANY; serv_addrin.sin_port = htons(port); bcopy((char *)server->h_addr, (char *)&serv_addrin.sin_addr.s_addr, server->h_length); addrSize = sizeof (serv_addrin); useAddr = (sockaddr*)&serv_addrin; if(debug) std::cout << "connecting to port " << port << std::endl; } if (connect(sockfd, useAddr, addrSize) < 0) { int res = errno; if(res == ECONNREFUSED) std::cerr << "ERROR while connecting: connection refused" << std::endl; else if(res == ENOENT) std::cerr << "ERROR while connecting: no such file or directory" << std::endl; else std::cerr << "ERROR while connecting" << std::endl; return 5; } shuffleBetweenStreams(connectedclientsockfd, sockfd, argc == 2); close(sockfd); close(connectedclientsockfd); } return 0; } diff --git a/util/focusedtreeview.cpp b/util/focusedtreeview.cpp index 0e9f8b9208..b1ce25a462 100644 --- a/util/focusedtreeview.cpp +++ b/util/focusedtreeview.cpp @@ -1,141 +1,141 @@ /* 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 "focusedtreeview.h" #include #include namespace KDevelop { struct FocusedTreeView::Private { bool autoScrollAtEnd = false; QTimer timer; bool wasAtEnd = false; int insertedBegin = -1; int insertedEnd = -1; }; FocusedTreeView::FocusedTreeView(QWidget* parent) : QTreeView(parent) , d(new Private) { setVerticalScrollMode(ScrollPerItem); d->timer.setInterval(200); d->timer.setSingleShot(true); connect(&d->timer, &QTimer::timeout, this, &FocusedTreeView::delayedAutoScrollAndResize); connect(verticalScrollBar(), &QScrollBar::valueChanged, &d->timer, static_cast(&QTimer::start)); } FocusedTreeView::~FocusedTreeView() { } void FocusedTreeView::setModel(QAbstractItemModel* newModel) { if (QAbstractItemModel* oldModel = model()) { - disconnect(oldModel, 0, this, 0); + disconnect(oldModel, nullptr, this, nullptr); } QTreeView::setModel(newModel); if (newModel) { connect(newModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &FocusedTreeView::rowsAboutToBeInserted); connect(newModel, &QAbstractItemModel::rowsRemoved, this, &FocusedTreeView::rowsRemoved); } } void FocusedTreeView::setAutoScrollAtEnd(bool enable) { d->autoScrollAtEnd = enable; } int FocusedTreeView::sizeHintForColumn(int column) const { QModelIndex i = indexAt(QPoint(0, 0)); if(i.isValid()) { QSize hint = sizeHintForIndex(i); int maxWidth = hint.width(); if(hint.height()) { //Also consider one item above, because else we can get problems with //the vertical scroll-bar for(int a = -1; a < (height() / hint.height())+1; ++a) { QModelIndex current = i.sibling(i.row()+a, column); QSize tempHint = sizeHintForIndex(current); if(tempHint.width() > maxWidth) maxWidth = tempHint.width(); } } return maxWidth; } return columnWidth(column); } void FocusedTreeView::delayedAutoScrollAndResize() { if (!model()) { // might happen on shutdown return; } if (d->autoScrollAtEnd && d->insertedBegin != -1 && d->wasAtEnd && d->insertedEnd == model()->rowCount()) { scrollToBottom(); } for(int a = 0; a < model()->columnCount(); ++a) resizeColumnToContents(a); d->insertedBegin = -1; // Timer is single-shot, but the code above may have recursively restarted the timer // (e.g. via the connection from the scroll-bar signal), so explicitly prevent a redundant // call here. d->timer.stop(); } void FocusedTreeView::rowsAboutToBeInserted(const QModelIndex&, int first, int last) { if (d->insertedBegin == -1) { d->insertedBegin = d->insertedEnd = first; d->wasAtEnd = true; QModelIndex last = model()->index(model()->rowCount() - 1, 0); if (last.isValid()) { auto rect = visualRect(last); d->wasAtEnd = rect.isValid() && viewport()->rect().intersects(rect); } } if (first == d->insertedEnd) { d->insertedEnd = last + 1; } if (!d->timer.isActive()) d->timer.start(); } // Removing rows can make longer rows move into the visible region, so we also must trigger a // column resize void FocusedTreeView::rowsRemoved(const QModelIndex& parent, int first, int last) { QTreeView::rowsRemoved(parent, first, last); if (!d->timer.isActive()) d->timer.start(); } } diff --git a/util/foregroundlock.cpp b/util/foregroundlock.cpp index 30a2d493e0..6da5bb5239 100644 --- a/util/foregroundlock.cpp +++ b/util/foregroundlock.cpp @@ -1,242 +1,242 @@ /* Copyright 2010 David Nolden 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. */ #include "foregroundlock.h" #include #include #include using namespace KDevelop; namespace { QMutex internalMutex; QMutex tryLockMutex; QMutex waitMutex; QMutex finishMutex; QWaitCondition condition; -volatile QThread* holderThread = 0; +volatile QThread* holderThread = nullptr; volatile int recursion = 0; void lockForegroundMutexInternal() { if(holderThread == QThread::currentThread()) { // We already have the mutex ++recursion; }else{ internalMutex.lock(); - Q_ASSERT(recursion == 0 && holderThread == 0); + Q_ASSERT(recursion == 0 && holderThread == nullptr); holderThread = QThread::currentThread(); recursion = 1; } } bool tryLockForegroundMutexInternal(int interval = 0) { if(holderThread == QThread::currentThread()) { // We already have the mutex ++recursion; return true; }else{ if(internalMutex.tryLock(interval)) { - Q_ASSERT(recursion == 0 && holderThread == 0); + Q_ASSERT(recursion == 0 && holderThread == nullptr); holderThread = QThread::currentThread(); recursion = 1; return true; }else{ return false; } } } void unlockForegroundMutexInternal(bool duringDestruction = false) { /// Note: QThread::currentThread() might already be invalid during destruction. if (!duringDestruction) { Q_ASSERT(holderThread == QThread::currentThread()); } Q_ASSERT(recursion > 0); recursion -= 1; if(recursion == 0) { - holderThread = 0; + holderThread = nullptr; internalMutex.unlock(); } } } ForegroundLock::ForegroundLock(bool lock) : m_locked(false) { if(lock) relock(); } void KDevelop::ForegroundLock::relock() { Q_ASSERT(!m_locked); if(!QApplication::instance() || // Initialization isn't complete yet QThread::currentThread() == QApplication::instance()->thread() || // We're the main thread (deadlock might happen if we'd enter the trylock loop) holderThread == QThread::currentThread()) // We already have the foreground lock (deadlock might happen if we'd enter the trylock loop) { lockForegroundMutexInternal(); }else{ QMutexLocker lock(&tryLockMutex); while(!tryLockForegroundMutexInternal(10)) { // In case an additional event-loop was started from within the foreground, we send // events to the foreground to temporarily release the lock. class ForegroundReleaser : public DoInForeground { public: void doInternal() override { // By locking the mutex, we make sure that the requester is actually waiting for the condition waitMutex.lock(); // Now we release the foreground lock TemporarilyReleaseForegroundLock release; // And signalize to the requester that we've released it condition.wakeAll(); // Allow the requester to actually wake up, by unlocking m_waitMutex waitMutex.unlock(); // Now wait until the requester is ready QMutexLocker lock(&finishMutex); } }; static ForegroundReleaser releaser; QMutexLocker lockWait(&waitMutex); QMutexLocker lockFinish(&finishMutex); QMetaObject::invokeMethod(&releaser, "doInternalSlot", Qt::QueuedConnection); // We limit the waiting time here, because sometimes it may happen that the foreground-lock is released, // and the foreground is waiting without an event-loop running. (For example through TemporarilyReleaseForegroundLock) condition.wait(&waitMutex, 30); if(tryLockForegroundMutexInternal()) { //success break; }else{ //Probably a third thread has creeped in and //got the foreground lock before us. Just try again. } } } m_locked = true; Q_ASSERT(holderThread == QThread::currentThread()); Q_ASSERT(recursion > 0); } bool KDevelop::ForegroundLock::isLockedForThread() { return QThread::currentThread() == holderThread; } bool KDevelop::ForegroundLock::tryLock() { if(tryLockForegroundMutexInternal()) { m_locked = true; return true; } return false; } void KDevelop::ForegroundLock::unlock() { Q_ASSERT(m_locked); unlockForegroundMutexInternal(); m_locked = false; } TemporarilyReleaseForegroundLock::TemporarilyReleaseForegroundLock() { Q_ASSERT(holderThread == QThread::currentThread()); m_recursion = 0; while(holderThread == QThread::currentThread()) { unlockForegroundMutexInternal(); ++m_recursion; } } TemporarilyReleaseForegroundLock::~TemporarilyReleaseForegroundLock() { for(int a = 0; a < m_recursion; ++a) lockForegroundMutexInternal(); Q_ASSERT(recursion == m_recursion && holderThread == QThread::currentThread()); } KDevelop::ForegroundLock::~ForegroundLock() { if(m_locked) unlock(); } bool KDevelop::ForegroundLock::isLocked() const { return m_locked; } namespace KDevelop { void DoInForeground::doIt() { if(QThread::currentThread() == QApplication::instance()->thread()) { // We're already in the foreground, just call the handler code doInternal(); }else{ QMutexLocker lock(&m_mutex); QMetaObject::invokeMethod(this, "doInternalSlot", Qt::QueuedConnection); m_wait.wait(&m_mutex); } } DoInForeground::~DoInForeground() { } DoInForeground::DoInForeground() { moveToThread(QApplication::instance()->thread()); } void DoInForeground::doInternalSlot() { VERIFY_FOREGROUND_LOCKED doInternal(); QMutexLocker lock(&m_mutex); m_wait.wakeAll(); } } // Important: The foreground lock has to be held by default, so lock it during static initialization static struct StaticLock { StaticLock() { lockForegroundMutexInternal(); } ~StaticLock() { unlockForegroundMutexInternal(true); } } staticLock; diff --git a/util/multilevellistview.cpp b/util/multilevellistview.cpp index 4f2cf01d10..1e1742c6d5 100644 --- a/util/multilevellistview.cpp +++ b/util/multilevellistview.cpp @@ -1,455 +1,455 @@ /* 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 "multilevellistview.h" #include #include #include #include #include /** * Interface to set the label of a model. */ class LabeledProxy { public: virtual ~LabeledProxy() { } void setLabel(const QString& label) { m_label = label; } QVariant header(QAbstractItemModel* model, int section, Qt::Orientation orientation, int role) const { if (model && section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { return m_label; } else { return QVariant(); } } protected: QString m_label; }; /** * The left-most view's model which only contains the root nodes of the source model. */ class RootProxyModel : public QSortFilterProxyModel, public LabeledProxy { Q_OBJECT public: - RootProxyModel( QObject* parent = 0 ) + RootProxyModel( QObject* parent = nullptr ) : QSortFilterProxyModel( parent ) { } bool filterAcceptsRow( int /*source_row*/, const QModelIndex& source_parent ) const override { return !source_parent.isValid(); } QVariant headerData( int section, Qt::Orientation orientation, int role ) const override { return header(sourceModel(), section, orientation, role); } }; /** * A class that automatically updates its contents based on the selection in another view. */ class SubTreeProxyModel : public KSelectionProxyModel, public LabeledProxy { Q_OBJECT public: - explicit SubTreeProxyModel( QItemSelectionModel* selectionModel, QObject* parent = 0 ) + explicit SubTreeProxyModel( QItemSelectionModel* selectionModel, QObject* parent = nullptr ) : KSelectionProxyModel( selectionModel, parent ) {} QVariant headerData( int section, Qt::Orientation orientation, int role ) const override { return header(sourceModel(), section, orientation, role); } Qt::ItemFlags flags(const QModelIndex& index) const override { Qt::ItemFlags ret = KSelectionProxyModel::flags(index); if (filterBehavior() == KSelectionProxyModel::SubTreesWithoutRoots && hasChildren(index)) { // we want to select child items ret &= ~Qt::ItemIsSelectable; } return ret; } }; using namespace KDevelop; class KDevelop::MultiLevelListViewPrivate { public: MultiLevelListViewPrivate(MultiLevelListView* view); ~MultiLevelListViewPrivate(); void viewSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void lastViewsContentsChanged(); void ensureViewSelected(QTreeView* view); /** * @param index index in any of our proxy models * @return an index in the source model */ QModelIndex mapToSource(QModelIndex index); /** * @param index an index in the source model * @return an index in the view's model at level @p level */ QModelIndex mapFromSource(QModelIndex index, int level); MultiLevelListView* view; int levels; QList views; QList proxies; QList layouts; QAbstractItemModel* model; }; MultiLevelListViewPrivate::MultiLevelListViewPrivate(MultiLevelListView* view_) : view(view_) , levels(0) -, model(0) +, model(nullptr) { } MultiLevelListViewPrivate::~MultiLevelListViewPrivate() { } void MultiLevelListViewPrivate::viewSelectionChanged(const QModelIndex& current, const QModelIndex& previous) { if (!current.isValid()) { // ignore, as we should always have some kind of selection return; } // figure out which proxy this signal belongs to QAbstractProxyModel* proxy = qobject_cast( const_cast(current.model())); Q_ASSERT(proxy); // what level is this proxy in int level = -1; for(int i = 0; i < levels; ++i) { if (views.at(i)->model() == proxy) { level = i; break; } } Q_ASSERT(level >= 0 && level < levels); if (level + 1 == levels) { // right-most view if (current.child(0, 0).isValid()) { // select the first leaf node for this view QModelIndex idx = current; QModelIndex child = idx.child(0, 0); while(child.isValid()) { idx = child; child = idx.child(0, 0); } views.last()->setCurrentIndex(idx); return; } // signal that our actual selection has changed emit view->currentIndexChanged(mapToSource(current), mapToSource(previous)); } else { // some leftish view // ensure the next view's first item is selected QTreeView* treeView = views.at(level + 1); // we need to delay the call, because at this point the child view // will still have its old data which is going to be invalidated // right after this method exits // be we must not set the index to 0,0 here directly, since e.g. // MultiLevelListView::setCurrentIndex might have been used, which // sets a proper index already. QMetaObject::invokeMethod(view, "ensureViewSelected", Qt::QueuedConnection, Q_ARG(QTreeView*, treeView)); } } void MultiLevelListViewPrivate::lastViewsContentsChanged() { views.last()->expandAll(); } void MultiLevelListViewPrivate::ensureViewSelected(QTreeView* view) { if (!view->currentIndex().isValid()) { view->setCurrentIndex(view->model()->index(0, 0)); } } QModelIndex MultiLevelListViewPrivate::mapToSource(QModelIndex index) { if (!index.isValid()) { return index; } while(index.model() != model) { QAbstractProxyModel* proxy = qobject_cast( const_cast(index.model())); Q_ASSERT(proxy); index = proxy->mapToSource(index); Q_ASSERT(index.isValid()); } return index; } QModelIndex MultiLevelListViewPrivate::mapFromSource(QModelIndex index, int level) { if (!index.isValid()) { return index; } Q_ASSERT(index.model() == model); QAbstractProxyModel* proxy = qobject_cast(views.at(level)->model()); Q_ASSERT(proxy); // find all proxies between the source and our view QVector proxies; proxies << proxy; forever { QAbstractProxyModel* child = qobject_cast(proxy->sourceModel()); if (child) { proxy = child; proxies << proxy; } else { Q_ASSERT(proxy->sourceModel() == model); break; } } // iterate in reverse order to find the view's index for(int i = proxies.size() - 1; i >= 0; --i) { proxy = proxies.at(i); index = proxy->mapFromSource(index); Q_ASSERT(index.isValid()); } return index; } MultiLevelListView::MultiLevelListView(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new MultiLevelListViewPrivate(this)) { delete layout(); setLayout(new QHBoxLayout()); layout()->setContentsMargins(0, 0, 0, 0); qRegisterMetaType("QTreeView*"); } MultiLevelListView::~MultiLevelListView() { delete d; } int MultiLevelListView::levels() const { return d->levels; } void MultiLevelListView::setLevels(int levels) { qDeleteAll(d->views); qDeleteAll(d->proxies); qDeleteAll(d->layouts); d->views.clear(); d->proxies.clear(); d->layouts.clear(); d->levels = levels; - QTreeView* previousView = 0; + QTreeView* previousView = nullptr; for (int i = 0; i < d->levels; ++i) { QVBoxLayout* levelLayout = new QVBoxLayout(); QTreeView* view = new QTreeView(this); view->setContentsMargins(0, 0, 0, 0); // only the right-most view is decorated view->setRootIsDecorated(i + 1 == d->levels); view->setHeaderHidden(false); view->setSelectionMode(QAbstractItemView::SingleSelection); if (!previousView) { // the root, i.e. left-most view RootProxyModel* root = new RootProxyModel(this); root->setDynamicSortFilter(true); d->proxies << root; root->setSourceModel(d->model); view->setModel(root); } else { SubTreeProxyModel* subTreeProxy = new SubTreeProxyModel(previousView->selectionModel(), this); if (i + 1 < d->levels) { // middel views only shows children of selection subTreeProxy->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); } else { // right-most view shows the rest subTreeProxy->setFilterBehavior(KSelectionProxyModel::SubTreesWithoutRoots); } d->proxies << subTreeProxy; subTreeProxy->setSourceModel(d->model); // sorting requires another proxy in-between QSortFilterProxyModel* sortProxy = new QSortFilterProxyModel(subTreeProxy); sortProxy->setSourceModel(subTreeProxy); sortProxy->setDynamicSortFilter(true); view->setModel(sortProxy); } // view->setModel creates the selection model connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, [&] (const QModelIndex& current, const QModelIndex& previous) { d->viewSelectionChanged(current, previous); }); if (i + 1 == d->levels) { connect(view->model(), &QAbstractItemModel::rowsInserted, this, [&] { d->lastViewsContentsChanged(); }); } view->setSortingEnabled(true); view->sortByColumn(0, Qt::AscendingOrder); levelLayout->addWidget(view); layout()->addItem(levelLayout); d->layouts << levelLayout; d->views << view; previousView = view; } setModel(d->model); } QAbstractItemModel* MultiLevelListView::model() const { return d->model; } void MultiLevelListView::setModel(QAbstractItemModel* model) { d->model = model; foreach (LabeledProxy* proxy, d->proxies) { dynamic_cast(proxy)->setSourceModel(model); } if (model && !d->views.isEmpty()) { d->views.first()->setCurrentIndex(d->views.first()->model()->index(0, 0)); } } QTreeView* MultiLevelListView::viewForLevel( int level ) const { return d->views[level]; } void MultiLevelListView::addWidget(int level, QWidget* widget) { Q_ASSERT(level < d->levels); d->layouts[level]->addWidget(widget); } QModelIndex MultiLevelListView::currentIndex() const { return d->mapToSource(d->views.last()->currentIndex()); } void MultiLevelListView::setCurrentIndex(const QModelIndex& index) { // incoming index is for the original model Q_ASSERT(!index.isValid() || index.model() == d->model); const QModelIndex previous = currentIndex(); QModelIndex idx(index); QVector indexes; while (idx.isValid()) { indexes.prepend(idx); idx = idx.parent(); } for (int i = 0; i < d->levels; ++i) { QTreeView* view = d->views.at(i); if (indexes.size() <= i) { // select first item by default view->setCurrentIndex(view->model()->index(0, 0)); continue; } QModelIndex index; if (i + 1 == d->levels) { // select the very last index in the list (i.e. might be deep down in the actual tree) index = indexes.last(); } else { // select the first index for that level index = indexes.at(i); } view->setCurrentIndex(d->mapFromSource(index, i)); } emit currentIndexChanged(index, previous); } void MultiLevelListView::setRootIndex(const QModelIndex& index) { Q_ASSERT(!index.isValid() || index.model() == d->model); d->views.first()->setRootIndex(index); } void MultiLevelListView::setHeaderLabels(const QStringList& labels) { int n = qMin(d->levels, labels.size()); for (int i = 0; i < n; ++i) { d->proxies.at(i)->setLabel(labels[i]); } } void MultiLevelListView::setLastModelsFilterBehavior(KSelectionProxyModel::FilterBehavior filter) { if (d->proxies.isEmpty()) { return; } dynamic_cast(d->proxies.last())->setFilterBehavior(filter); } #include "multilevellistview.moc" #include "moc_multilevellistview.cpp" diff --git a/util/projecttestjob.cpp b/util/projecttestjob.cpp index fcc10cbb8e..d79d7c93f3 100644 --- a/util/projecttestjob.cpp +++ b/util/projecttestjob.cpp @@ -1,126 +1,126 @@ /* * This file is part of KDevelop * * Copyright 2012 Miha Čančula * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projecttestjob.h" #include #include #include #include #include using namespace KDevelop; struct ProjectTestJob::Private { Private(ProjectTestJob* q) : q(q) - , m_currentJob(0) - , m_currentSuite(0) + , m_currentJob(nullptr) + , m_currentSuite(nullptr) {} void runNext(); void gotResult(ITestSuite* suite, const TestResult& result); ProjectTestJob* q; QList m_suites; KJob* m_currentJob; ITestSuite* m_currentSuite; ProjectTestResult m_result; }; void ProjectTestJob::Private::runNext() { m_currentSuite = m_suites.takeFirst(); m_currentJob = m_currentSuite->launchAllCases(ITestSuite::Silent); m_currentJob->start(); } void ProjectTestJob::Private::gotResult(ITestSuite* suite, const TestResult& result) { if (suite == m_currentSuite) { m_result.total++; q->emitPercent(m_result.total, m_result.total + m_suites.size()); switch (result.suiteResult) { case TestResult::Passed: m_result.passed++; break; case TestResult::Failed: m_result.failed++; break; case TestResult::Error: m_result.error++; break; default: break; } if (m_suites.isEmpty()) { q->emitResult(); } else { runNext(); } } } ProjectTestJob::ProjectTestJob(IProject* project, QObject* parent) : KJob(parent) , d(new Private(this)) { setCapabilities(Killable); setObjectName(i18n("Run all tests in %1", project->name())); d->m_suites = ICore::self()->testController()->testSuitesForProject(project); connect(ICore::self()->testController(), &ITestController::testRunFinished, this, [&] (ITestSuite* suite, const TestResult& result) { d->gotResult(suite, result); }); } ProjectTestJob::~ProjectTestJob() { } void ProjectTestJob::start() { d->runNext(); } bool ProjectTestJob::doKill() { if (d->m_currentJob) { d->m_currentJob->kill(); } else { d->m_suites.clear(); } return true; } ProjectTestResult ProjectTestJob::testResult() { return d->m_result; } #include "moc_projecttestjob.cpp" diff --git a/util/tests/test_embeddedfreetree.cpp b/util/tests/test_embeddedfreetree.cpp index a2d1b73cef..6abdda43fe 100644 --- a/util/tests/test_embeddedfreetree.cpp +++ b/util/tests/test_embeddedfreetree.cpp @@ -1,609 +1,609 @@ /* This file is part of KDevelop 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. */ unsigned int extractor_div_with = 0; #include #include #include #include #include #include #include #include #include #include #include #include struct TestItem { explicit TestItem(uint _value = 0) : value(_value), leftChild(-1), rightChild(-1) { } uint value; int leftChild; int rightChild; bool operator==(const TestItem& rhs) const { return value == rhs.value; } bool operator<(const TestItem& item) const { return value < item.value; } }; struct TestItemConversion { static uint toIndex(const TestItem& item) { return item.value; } static TestItem toItem(uint index) { return TestItem(index); } }; struct Extractor{ static TestItem extract(const TestItem& item) { return TestItem(item.value/extractor_div_with); } }; clock_t std_insertion = 0, std_removal = 0, std_contains = 0, std_iteration = 0, emb_insertion = 0, emb_removal = 0, emb_contains = 0, emb_iteration = 0; QString toString(std::set set) { QString ret; for(std::set::const_iterator it = set.begin(); it != set.end(); ++it) ret += QStringLiteral("%1 ").arg(*it); return ret; } bool operator==(const std::set a, const std::set b) { if(a.size() != b.size()) { qDebug() << "size mismatch" << toString(a) << ": " << toString(b); return false; } std::set::const_iterator aIt = a.begin(); std::set::const_iterator bIt = b.begin(); for(; aIt != a.end(); ++aIt, ++bIt) if(*aIt != *bIt) { qDebug() << "mismatch" << toString(a) << ": " << toString(b); return false; } return true; } struct TestItemHandler { public: static int rightChild(const TestItem& m_data) { return m_data.rightChild; } static int leftChild(const TestItem& m_data) { return m_data.leftChild; } static void setLeftChild(TestItem& m_data, int child) { m_data.leftChild = child; } static void setRightChild(TestItem& m_data, int child) { m_data.rightChild = child; } //Copies this item into the given one static void copyTo(const TestItem& m_data, TestItem& data) { data = m_data; } static void createFreeItem(TestItem& data) { data = TestItem(); } static inline bool isFree(const TestItem& m_data) { return !m_data.value; } static const TestItem& data(const TestItem& m_data) { return m_data; } inline static bool equals(const TestItem& m_data, const TestItem& rhs) { return m_data.value == rhs.value; } }; class TestItemBasedSet { public: TestItemBasedSet() : m_centralFree(-1) { } void insert(uint i) { TestItem item(i); KDevelop::EmbeddedTreeAddItem add(data.data(), data.size(), m_centralFree, item); if((int)add.newItemCount() != (int)data.size()) { QVector newData; newData.resize(add.newItemCount()); add.transferData(newData.data(), newData.size()); data = newData; } } bool contains(uint item) { KDevelop::EmbeddedTreeAlgorithms alg(data.data(), data.size(), m_centralFree); return alg.indexOf(TestItem(item)) != -1; } void remove(uint i) { TestItem item(i); KDevelop::EmbeddedTreeRemoveItem remove(data.data(), data.size(), m_centralFree, item); if((int)remove.newItemCount() != (int)data.size()) { QVector newData; newData.resize(remove.newItemCount()); remove.transferData(newData.data(), newData.size()); data = newData; } } std::set toSet() const { std::set ret; for(int a = 0; a < data.size(); ++a) if(data[a].value) ret.insert(data[a].value); return ret; } void verify() { //1. verify order uint last = 0; uint freeCount = 0; for(int a = 0; a < data.size(); ++a) { if(data[a].value) { QVERIFY(last < data[a].value); last = data[a].value; }else{ ++freeCount; } } KDevelop::EmbeddedTreeAlgorithms algorithms(data.data(), data.size(), m_centralFree); uint countFree = algorithms.countFreeItems(); QCOMPARE(freeCount, countFree); algorithms.verifyTreeConsistent(); } uint getItem(uint number) const { Q_ASSERT(number < (uint)data.size()); uint ret = 0; uint size = (uint)data.size(); uint current = 0; for(uint a = 0; a < size; ++a) { if(data[a].value) { //Only count the non-free items if(current == number) ret = data[a].value; ++current; }else{ //This is a free item } } return ret; } private: int m_centralFree; QVector data; }; class TestSet { public: void add(uint i) { if(realSet.find(i) != realSet.end()) { QVERIFY(set.contains(i)); return; }else{ QVERIFY(!set.contains(i)); } clock_t start = clock(); realSet.insert(i); std_insertion += clock() - start; start = clock(); set.insert(i); emb_insertion += clock() - start; start = clock(); bool contained = realSet.find(i) != realSet.end(); std_contains += clock() - start; start = clock(); set.contains(i); emb_contains += clock() - start; QVERIFY(set.contains(i)); QVERIFY(contained); set.verify(); } void remove(uint i) { if(realSet.find(i) != realSet.end()) { QVERIFY(set.contains(i)); }else{ QVERIFY(!set.contains(i)); return; } clock_t start = clock(); set.remove(i); emb_removal += clock() - start; start = clock(); realSet.erase(i); std_removal += clock() - start; QVERIFY(!set.contains(i)); } uint size() const { return realSet.size(); } uint getItem(uint number) const { Q_ASSERT(number < size()); uint current = 0; uint ret = 0; clock_t start = clock(); for(std::set::const_iterator it = realSet.begin(); it != realSet.end(); ++it) { if(current == number) { ret = *it; } ++current; } std_iteration += clock() - start; start = clock(); set.getItem(number); emb_iteration += clock() - start; Q_ASSERT(ret); return ret; } void verify() { QVERIFY(realSet == set.toSet()); set.verify(); } private: std::set realSet; TestItemBasedSet set; }; float toSeconds(clock_t time) { return ((float)time) / CLOCKS_PER_SEC; } struct StaticRepository { static Utils::BasicSetRepository* repository() { static Utils::BasicSetRepository repository(QStringLiteral("test repository")); return &repository; } }; struct UintSetVisitor { std::set& s; UintSetVisitor(std::set& _s) : s(_s) { } inline bool operator() (const TestItem& item) { s.insert(item.value); return true; } }; struct NothingDoVisitor { inline bool operator() (const TestItem& item) { Q_UNUSED(item); return true; } }; class TestEmbeddedFreeTree : public QObject { Q_OBJECT private slots: void initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); } void cleanupTestCase() { KDevelop::TestCore::shutdown(); } void randomizedTest() { const int cycles = 10000; const int valueRange = 1000; const int removeProbability = 40; //Percent TestSet set; - srand(time(NULL)); + srand(time(nullptr)); for(int a = 0; a < cycles; ++a) { if(a % (cycles / 10) == 0) { qDebug() << "cycle" << a; } bool remove = (rand() % 100) < removeProbability; if(remove && set.size()) { set.remove(set.getItem(rand() % set.size())); }else{ int value = (rand() % valueRange) + 1; set.add(value); } set.verify(); } qDebug() << "Performance embedded list: insertion:" << toSeconds(emb_insertion) << "removal:" << toSeconds(emb_removal) << "contains:" << toSeconds(emb_contains) << "iteration:" << toSeconds(emb_iteration); qDebug() << "Performance std::set: insertion:" << toSeconds(std_insertion) << "removal:" << toSeconds(std_removal) << "contains:" << toSeconds(std_contains) << "iteration:" << toSeconds(std_iteration); } void sequentialTest() { TestSet set; set.add(5); set.verify(); set.remove(5); set.verify(); set.add(3); set.verify(); set.add(4); set.verify(); set.add(7); set.verify(); set.remove(3); set.verify(); set.remove(7); set.verify(); set.add(6); set.verify(); set.add(1); set.verify(); set.add(9); set.verify(); set.remove(4); set.verify(); set.remove(9); set.verify(); set.add(1); set.verify(); set.add(2); set.verify(); set.add(3); set.verify(); set.add(4); set.verify(); set.add(5); set.verify(); set.remove(1); set.verify(); set.remove(3); set.verify(); set.add(15); set.verify(); set.add(16); set.verify(); set.add(17); set.verify(); set.add(18); set.verify(); set.remove(18); set.verify(); set.remove(17); set.verify(); set.add(9); set.verify(); } void testFiltering() { clock_t stdTime = 0; clock_t algoTime = 0; clock_t treeAlgoTime = 0; clock_t treeAlgoVisitorTime = 0; clock_t insertionStdTime = 0; clock_t insertionAlgoTime = 0; clock_t insertionTreeAlgoTime = 0; typedef Utils::StorableSet RepositorySet; const uint cycles = 3000; const uint setSize = 1500; uint totalItems = 0, totalFilteredItems = 0; - srand(time(NULL)); + srand(time(nullptr)); for(uint a = 0; a < cycles; ++a) { KDevelop::ConvenientFreeListSet set1; std::set testSet1; KDevelop::ConvenientFreeListSet set2; std::set testSet2; RepositorySet repSet2; if(a % (cycles / 10) == 0) { qDebug() << "cycle" << a; } //Build the sets extractor_div_with = (rand() % 10) + 1; for(uint a = 0; a < setSize; ++a) { uint value = rand() % 3000; uint divValue = value/extractor_div_with; if(!divValue) continue; // qDebug() << "inserting" << value; std::set::const_iterator it = testSet1.lower_bound(value); int pos = set1.iterator().lowerBound(TestItem(value)); //This tests the upperBound functionality if (pos != -1) { QVERIFY(it != testSet1.end()); QVERIFY(set1.data()[pos].value == *it); } else { QVERIFY(it == testSet1.end()); } if((rand() % 10) == 0) { set1.insert(TestItem(value)); testSet1.insert(value); } //This is tuned so in the end, about 99% of all declarations are filtered out, like in the symbol table. if((rand() % (extractor_div_with*100)) == 0) { clock_t start = clock(); set2.insert(TestItem(divValue)); insertionStdTime += clock() - start; start = clock(); testSet2.insert(divValue); insertionAlgoTime += clock() - start; start = clock(); repSet2.insert(TestItem(divValue)); insertionTreeAlgoTime += clock() - start; start = clock(); } } std::set verifySet1; for(KDevelop::ConvenientFreeListSet::Iterator it = set1.iterator(); it; ++it) verifySet1.insert(it->value); std::set verifySet2; for(KDevelop::ConvenientFreeListSet::Iterator it = set2.iterator(); it; ++it) verifySet2.insert(it->value); std::set verifyRepSet2; for(RepositorySet::Iterator it = repSet2.iterator(); it; ++it) verifyRepSet2.insert((*it).value); QCOMPARE(verifySet1, testSet1); QCOMPARE(verifySet2, testSet2); QCOMPARE(verifyRepSet2, testSet2); std::set algoFiltered; std::set treeAlgoFiltered; std::set treeAlgoVisitorFiltered; { //Do the filtering once without actions on the filtered items, just for calculating the time clock_t start = clock(); { KDevelop::ConvenientEmbeddedSetFilterIterator filterIterator(set1.iterator(), set2.iterator()); while(filterIterator) ++filterIterator; algoTime += clock() - start; } start = clock(); { KDevelop::ConvenientEmbeddedSetTreeFilterIterator filterIterator(set1.iterator(), repSet2); while(filterIterator) ++filterIterator; treeAlgoTime += clock() - start; } { start = clock(); NothingDoVisitor v; KDevelop::ConvenientEmbeddedSetTreeFilterVisitor visit(v, set1.iterator(), repSet2); treeAlgoVisitorTime += clock() - start; } start = clock(); for(std::set::const_iterator it = testSet1.begin(); it != testSet1.end(); ++it) { if(testSet2.count((*it) / extractor_div_with) == 1) { } } stdTime += clock() - start; } { KDevelop::ConvenientEmbeddedSetFilterIterator filterIterator(set1.iterator(), set2.iterator()); while(filterIterator) { algoFiltered.insert(filterIterator->value); ++filterIterator; } } { KDevelop::ConvenientEmbeddedSetTreeFilterIterator filterIterator(set1.iterator(), repSet2); while(filterIterator) { treeAlgoFiltered.insert((*filterIterator).value); ++filterIterator; } } { UintSetVisitor v(treeAlgoVisitorFiltered); KDevelop::ConvenientEmbeddedSetTreeFilterVisitor visit(v, set1.iterator(), repSet2); } totalItems += testSet1.size(); totalFilteredItems += algoFiltered.size(); std::set stdFiltered; for(std::set::const_iterator it = testSet1.begin(); it != testSet1.end(); ++it) { if(testSet2.count((*it) / extractor_div_with) == 1) { stdFiltered.insert(*it); } } QCOMPARE(algoFiltered, stdFiltered); QCOMPARE(treeAlgoFiltered, stdFiltered); QCOMPARE(treeAlgoVisitorFiltered, stdFiltered); } qDebug() << "Filtering performance: embedded-list filtering:" << toSeconds(algoTime) << "set-repository filtering:" << toSeconds(treeAlgoTime) << "set-repository visitor filtering:" << toSeconds(treeAlgoVisitorTime) << "std::set filtering:" << toSeconds(stdTime) << "Normal -> Embedded speedup ratio:" << (toSeconds(stdTime) / toSeconds(algoTime)) << "Normal -> Repository speedup ratio:" << (toSeconds(stdTime) / toSeconds(treeAlgoVisitorTime)) << "total processed items:" << totalItems << "total items after filtering:" << totalFilteredItems; qDebug() << "Insertion: embedded-list:" << toSeconds(insertionAlgoTime) << "set-repository:" << toSeconds(insertionTreeAlgoTime) << "std::set:" << toSeconds(insertionStdTime); } }; #include "test_embeddedfreetree.moc" QTEST_MAIN(TestEmbeddedFreeTree) diff --git a/vcs/dvcs/dvcsjob.cpp b/vcs/dvcs/dvcsjob.cpp index a45d87ce34..481febf01d 100644 --- a/vcs/dvcs/dvcsjob.cpp +++ b/vcs/dvcs/dvcsjob.cpp @@ -1,333 +1,333 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2002-2003 Christian Loose * * Copyright 2007 Robert Gruber * * * * Adapted for DVCS * * Copyright 2008 Evgeniy Ivanov * * Copyright Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "dvcsjob.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; struct DVcsJobPrivate { - DVcsJobPrivate() : childproc(new KProcess), vcsplugin(0), ignoreError(false) + DVcsJobPrivate() : childproc(new KProcess), vcsplugin(nullptr), ignoreError(false) {} ~DVcsJobPrivate() { delete childproc; } KProcess* childproc; VcsJob::JobStatus status; QByteArray output; QByteArray errorOutput; IPlugin* vcsplugin; QVariant results; OutputModel* model; bool ignoreError; }; DVcsJob::DVcsJob(const QDir& workingDir, IPlugin* parent, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity), d(new DVcsJobPrivate) { Q_ASSERT(workingDir.exists()); d->status = JobNotStarted; d->vcsplugin = parent; d->childproc->setWorkingDirectory(workingDir.absolutePath()); d->model = new OutputModel; d->ignoreError = false; setModel(d->model); setCapabilities(Killable); connect(d->childproc, static_cast(&KProcess::finished), this, &DVcsJob::slotProcessExited); connect(d->childproc, static_cast(&KProcess::error), this, &DVcsJob::slotProcessError); connect(d->childproc, &KProcess::readyReadStandardOutput, this, &DVcsJob::slotReceivedStdout); } DVcsJob::~DVcsJob() { delete d; } QDir DVcsJob::directory() const { return QDir(d->childproc->workingDirectory()); } DVcsJob& DVcsJob::operator<<(const QString& arg) { *d->childproc << arg; return *this; } DVcsJob& DVcsJob::operator<<(const char* arg) { *d->childproc << arg; return *this; } DVcsJob& DVcsJob::operator<<(const QStringList& args) { *d->childproc << args; return *this; } QStringList DVcsJob::dvcsCommand() const { return d->childproc->program(); } QString DVcsJob::output() const { QByteArray stdoutbuf = rawOutput(); int endpos = stdoutbuf.size(); if (d->status==JobRunning) { // We may have received only part of a code-point. apol: ASSERT? endpos = stdoutbuf.lastIndexOf('\n')+1; // Include the final newline or become 0, when there is no newline } return QString::fromLocal8Bit(stdoutbuf, endpos); } QByteArray DVcsJob::rawOutput() const { return d->output; } QByteArray DVcsJob::errorOutput() const { return d->errorOutput; } void DVcsJob::setIgnoreError(bool ignore) { d->ignoreError = ignore; } void DVcsJob::setResults(const QVariant &res) { d->results = res; } QVariant DVcsJob::fetchResults() { return d->results; } void DVcsJob::start() { Q_ASSERT_X(d->status != JobRunning, "DVCSjob::start", "Another proccess was started using this job class"); const QDir& workingdir = directory(); if( !workingdir.exists() ) { QString error = i18n( "Working Directory does not exist: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } if( !workingdir.isAbsolute() ) { QString error = i18n( "Working Directory is not absolute: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } QString commandDisplay = KShell::joinArgs(dvcsCommand()); qCDebug(VCS) << "Execute dvcs command:" << commandDisplay; QString service; if(d->vcsplugin) service = d->vcsplugin->objectName(); setObjectName(service+": "+commandDisplay); d->status = JobRunning; d->childproc->setOutputChannelMode(KProcess::SeparateChannels); //the started() and error() signals may be delayed! It causes crash with deferred deletion!!! d->childproc->start(); d->model->appendLine(directory().path() + "> " + commandDisplay); } void DVcsJob::setCommunicationMode(KProcess::OutputChannelMode comm) { d->childproc->setOutputChannelMode(comm); } void DVcsJob::cancel() { d->childproc->kill(); } void DVcsJob::slotProcessError( QProcess::ProcessError err ) { d->status = JobFailed; setError(OutputJob::FailedShownError); //we don't want to trigger a message box d->errorOutput = d->childproc->readAllStandardError(); QString displayCommand = KShell::joinArgs(dvcsCommand()); QString completeErrorText = i18n("Process '%1' exited with status %2\n%3", displayCommand, d->childproc->exitCode(), QString::fromLocal8Bit(d->errorOutput) ); setErrorText( completeErrorText ); QString errorValue; //if trolls add Q_ENUMS for QProcess, then we can use better solution than switch: //QMetaObject::indexOfEnumerator(char*), QQStringLiteral(QMetaEnum::valueToKey())... switch (err) { case QProcess::FailedToStart: errorValue = QStringLiteral("FailedToStart"); break; case QProcess::Crashed: errorValue = QStringLiteral("Crashed"); break; case QProcess::Timedout: errorValue = QStringLiteral("Timedout"); break; case QProcess::WriteError: errorValue = QStringLiteral("WriteError"); break; case QProcess::ReadError: errorValue = QStringLiteral("ReadError"); break; case QProcess::UnknownError: errorValue = QStringLiteral("UnknownError"); break; } qCDebug(VCS) << "Found an error while running" << displayCommand << ":" << errorValue << "Exit code is:" << d->childproc->exitCode(); qCDebug(VCS) << "Error:" << completeErrorText; displayOutput(QString::fromLocal8Bit(d->errorOutput)); d->model->appendLine(i18n("Command finished with error %1.", errorValue)); if(verbosity()==Silent) { setVerbosity(Verbose); startOutput(); } emitResult(); } void DVcsJob::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { d->status = JobSucceeded; d->model->appendLine(i18n("Command exited with value %1.", exitCode)); if (exitStatus == QProcess::CrashExit) slotProcessError(QProcess::Crashed); else if (exitCode != 0 && !d->ignoreError) slotProcessError(QProcess::UnknownError); else jobIsReady(); } void DVcsJob::displayOutput(const QString& data) { d->model->appendLines(data.split('\n')); } void DVcsJob::slotReceivedStdout() { QByteArray output = d->childproc->readAllStandardOutput(); // accumulate output d->output.append(output); displayOutput(QString::fromLocal8Bit(output)); } VcsJob::JobStatus DVcsJob::status() const { return d->status; } IPlugin* DVcsJob::vcsPlugin() const { return d->vcsplugin; } DVcsJob& DVcsJob::operator<<(const QUrl& url) { *d->childproc << url.toLocalFile(); return *this; } DVcsJob& DVcsJob::operator<<(const QList< QUrl >& urls) { foreach(const QUrl &url, urls) operator<<(url); return *this; } bool DVcsJob::doKill() { if (d->childproc->state() == QProcess::NotRunning) { return true; } static const int terminateKillTimeout = 1000; // ms d->childproc->terminate(); bool terminated = d->childproc->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->childproc->kill(); terminated = d->childproc->waitForFinished( terminateKillTimeout ); } return terminated; } void DVcsJob::jobIsReady() { emit readyForParsing(this); //let parsers to set status emitResult(); //KJob emit resultsReady(this); //VcsJob } KProcess* DVcsJob::process() {return d->childproc;} diff --git a/vcs/interfaces/ipatchsource.cpp b/vcs/interfaces/ipatchsource.cpp index 7e5ac977a4..166cd1e999 100644 --- a/vcs/interfaces/ipatchsource.cpp +++ b/vcs/interfaces/ipatchsource.cpp @@ -1,77 +1,77 @@ /* Copyright 2006 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 "ipatchsource.h" #include using namespace KDevelop; QIcon IPatchSource::icon() const { return QIcon(); } IPatchReview::~IPatchReview() { } void IPatchSource::cancelReview() { } bool IPatchSource::finishReview(QList< QUrl > selection) { Q_UNUSED(selection); return true; } bool IPatchSource::canCancel() const { return false; } QMap IPatchSource::additionalSelectableFiles() const { return QMap(); } bool IPatchSource::canSelectFiles() const { return false; } QString IPatchSource::finishReviewCustomText() const { return QString(); } QWidget* IPatchSource::customWidget() const { - return 0; + return nullptr; } uint IPatchSource::depth() const { return 0; } diff --git a/vcs/models/brancheslistmodel.cpp b/vcs/models/brancheslistmodel.cpp index 980e0255bf..60fd01f8d4 100644 --- a/vcs/models/brancheslistmodel.cpp +++ b/vcs/models/brancheslistmodel.cpp @@ -1,212 +1,212 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2012 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "brancheslistmodel.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include "util/path.h" #include #include #include #include using namespace std; using namespace KDevelop; class KDevelop::BranchesListModelPrivate { public: BranchesListModelPrivate() { } IBranchingVersionControl * dvcsplugin; QUrl repo; }; class BranchItem : public QStandardItem { public: BranchItem(const QString& name, bool current=false) : QStandardItem(name) { setEditable(true); setCurrent(current); } void setCurrent(bool current) { setData(current, BranchesListModel::CurrentRole); setIcon(QIcon::fromTheme( current ? QStringLiteral("arrow-right") : QStringLiteral(""))); } void setData(const QVariant& value, int role = Qt::UserRole + 1) override { if(role==Qt::EditRole && value.toString()!=text()) { QString newBranch = value.toString(); BranchesListModel* bmodel = qobject_cast(model()); if(!bmodel->findItems(newBranch).isEmpty()) { - KMessageBox::messageBox(0, KMessageBox::Sorry, i18n("Branch \"%1\" already exists.", newBranch)); + KMessageBox::messageBox(nullptr, KMessageBox::Sorry, i18n("Branch \"%1\" already exists.", newBranch)); return; } - int ret = KMessageBox::messageBox(0, KMessageBox::WarningYesNo, + int ret = KMessageBox::messageBox(nullptr, KMessageBox::WarningYesNo, i18n("Are you sure you want to rename \"%1\" to \"%2\"?", text(), newBranch)); if (ret == KMessageBox::No) { return; // ignore event } KDevelop::VcsJob *branchJob = bmodel->interface()->renameBranch(bmodel->repository(), newBranch, text()); ret = branchJob->exec(); qCDebug(VCS) << "Renaming " << text() << " to " << newBranch << ':' << ret; if (!ret) { return; // ignore event } } QStandardItem::setData(value, role); } }; static QVariant runSynchronously(KDevelop::VcsJob* job) { job->setVerbosity(KDevelop::OutputJob::Silent); QVariant ret; if(job->exec() && job->status()==KDevelop::VcsJob::JobSucceeded) { ret = job->fetchResults(); } delete job; return ret; } BranchesListModel::BranchesListModel(QObject* parent) : QStandardItemModel(parent), d(new BranchesListModelPrivate()) { } BranchesListModel::~BranchesListModel() { } QHash BranchesListModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles.insert(CurrentRole, "isCurrent"); return roles; } void BranchesListModel::createBranch(const QString& baseBranch, const QString& newBranch) { qCDebug(VCS) << "Creating " << baseBranch << " based on " << newBranch; KDevelop::VcsRevision rev; rev.setRevisionValue(baseBranch, KDevelop::VcsRevision::GlobalNumber); KDevelop::VcsJob* branchJob = d->dvcsplugin->branch(d->repo, rev, newBranch); qCDebug(VCS) << "Adding new branch"; if (branchJob->exec()) appendRow(new BranchItem(newBranch)); } void BranchesListModel::removeBranch(const QString& branch) { KDevelop::VcsJob *branchJob = d->dvcsplugin->deleteBranch(d->repo, branch); qCDebug(VCS) << "Removing branch:" << branch; if (branchJob->exec()) { QList< QStandardItem* > items = findItems(branch); foreach(QStandardItem* item, items) removeRow(item->row()); } } QUrl BranchesListModel::repository() const { return d->repo; } KDevelop::IBranchingVersionControl* BranchesListModel::interface() { return d->dvcsplugin; } void BranchesListModel::initialize(KDevelop::IBranchingVersionControl* branching, const QUrl& r) { d->dvcsplugin = branching; d->repo = r; refresh(); } void BranchesListModel::refresh() { QStringList branches = runSynchronously(d->dvcsplugin->branches(d->repo)).toStringList(); QString curBranch = runSynchronously(d->dvcsplugin->currentBranch(d->repo)).toString(); foreach(const QString& branch, branches) appendRow(new BranchItem(branch, branch == curBranch)); } void BranchesListModel::resetCurrent() { refresh(); emit currentBranchChanged(); } QString BranchesListModel::currentBranch() const { return runSynchronously(d->dvcsplugin->currentBranch(d->repo)).toString(); } KDevelop::IProject* BranchesListModel::project() const { return KDevelop::ICore::self()->projectController()->findProjectForUrl(d->repo); } void BranchesListModel::setProject(KDevelop::IProject* p) { if(!p || !p->versionControlPlugin()) { qCDebug(VCS) << "null or invalid project" << p; return; } KDevelop::IBranchingVersionControl* branching = p->versionControlPlugin()->extension(); if(branching) { initialize(branching, p->path().toUrl()); } else qCDebug(VCS) << "not a branching vcs project" << p->name(); } void BranchesListModel::setCurrentBranch(const QString& branch) { KDevelop::VcsJob* job = d->dvcsplugin->switchBranch(d->repo, branch); connect(job, &VcsJob::finished, this, &BranchesListModel::currentBranchChanged); KDevelop::ICore::self()->runController()->registerJob(job); } diff --git a/vcs/models/vcsfilechangesmodel.cpp b/vcs/models/vcsfilechangesmodel.cpp index 8c5f1e4a9d..7991a15d83 100644 --- a/vcs/models/vcsfilechangesmodel.cpp +++ b/vcs/models/vcsfilechangesmodel.cpp @@ -1,270 +1,270 @@ /* 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 #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: 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 == 0) + 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 { for(int i=0, c=parent->rowCount(); ichild(i); if(indexFromItem(item).data(UrlRole).toUrl() == url) { return parent->child(i); } } - return 0; + 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 { 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 { 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()); if(!d->allowSelection) return; 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()); } } diff --git a/vcs/vcspluginhelper.cpp b/vcs/vcspluginhelper.cpp index a3c1da97b1..2c103327fb 100644 --- a/vcs/vcspluginhelper.cpp +++ b/vcs/vcspluginhelper.cpp @@ -1,484 +1,484 @@ /*************************************************************************** * Copyright 2008 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * * * 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. * * * ***************************************************************************/ #include "vcspluginhelper.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 #include #include #include #include #include #include #include #include "interfaces/idistributedversioncontrol.h" #include "vcsstatusinfo.h" #include "vcsevent.h" #include "widgets/vcsdiffpatchsources.h" namespace KDevelop { struct VcsPluginHelper::VcsPluginHelperPrivate { IPlugin * plugin; IBasicVersionControl * vcs; QList ctxUrls; QAction* commitAction; QAction* addAction; QAction* updateAction; QAction* historyAction; QAction* annotationAction; QAction* diffToBaseAction; QAction* revertAction; QAction* diffForRevAction; QAction* diffForRevGlobalAction; QAction* pushAction; QAction* pullAction; void createActions(VcsPluginHelper* parent) { commitAction = new QAction(QIcon::fromTheme(QStringLiteral("svn-commit")), i18n("Commit..."), parent); updateAction = new QAction(QIcon::fromTheme(QStringLiteral("svn-update")), i18n("Update"), parent); addAction = new QAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add"), parent); diffToBaseAction = new QAction(QIcon::fromTheme(QStringLiteral("text-x-patch")), i18n("Show Differences..."), parent); revertAction = new QAction(QIcon::fromTheme(QStringLiteral("archive-remove")), i18n("Revert"), parent); historyAction = new QAction(QIcon::fromTheme(QStringLiteral("view-history")), i18n("History..."), parent); annotationAction = new QAction(QIcon::fromTheme(QStringLiteral("user-properties")), i18n("Annotation..."), parent); diffForRevAction = new QAction(QIcon::fromTheme(QStringLiteral("text-x-patch")), i18n("Show Diff..."), parent); diffForRevGlobalAction = new QAction(QIcon::fromTheme(QStringLiteral("text-x-patch")), i18n("Show Diff (all files)..."), parent); pushAction = new QAction(QIcon::fromTheme(QStringLiteral("arrow-up-double")), i18n("Push"), parent); pullAction = new QAction(QIcon::fromTheme(QStringLiteral("arrow-down-double")), i18n("Pull"), parent); connect(commitAction, &QAction::triggered, parent, &VcsPluginHelper::commit); connect(addAction, &QAction::triggered, parent, &VcsPluginHelper::add); connect(updateAction, &QAction::triggered, parent, &VcsPluginHelper::update); connect(diffToBaseAction, &QAction::triggered, parent, &VcsPluginHelper::diffToBase); connect(revertAction, &QAction::triggered, parent, &VcsPluginHelper::revert); connect(historyAction, &QAction::triggered, parent, [=] { parent->history(); }); connect(annotationAction, &QAction::triggered, parent, &VcsPluginHelper::annotation); connect(diffForRevAction, &QAction::triggered, parent, static_cast(&VcsPluginHelper::diffForRev)); connect(diffForRevGlobalAction, &QAction::triggered, parent, &VcsPluginHelper::diffForRevGlobal); connect(pullAction, &QAction::triggered, parent, &VcsPluginHelper::pull); connect(pushAction, &QAction::triggered, parent, &VcsPluginHelper::push); } bool allLocalFiles(const QList& urls) { bool ret=true; foreach(const QUrl &url, urls) { QFileInfo info(url.toLocalFile()); ret &= info.isFile(); } return ret; } QMenu* createMenu() { bool allVersioned=true; foreach(const QUrl &url, ctxUrls) { allVersioned=allVersioned && vcs->isVersionControlled(url); if(!allVersioned) break; } QMenu* menu = new QMenu(vcs->name()); menu->setIcon(QIcon::fromTheme(ICore::self()->pluginController()->pluginInfo(plugin).iconName())); menu->addAction(commitAction); if(plugin->extension()) { menu->addAction(pushAction); menu->addAction(pullAction); } else { menu->addAction(updateAction); } menu->addSeparator(); menu->addAction(addAction); menu->addAction(revertAction); menu->addSeparator(); menu->addAction(historyAction); menu->addAction(annotationAction); menu->addAction(diffToBaseAction); const bool singleVersionedFile = ctxUrls.count() == 1 && allVersioned; historyAction->setEnabled(singleVersionedFile); annotationAction->setEnabled(singleVersionedFile && allLocalFiles(ctxUrls)); diffToBaseAction->setEnabled(singleVersionedFile); commitAction->setEnabled(singleVersionedFile); return menu; } }; VcsPluginHelper::VcsPluginHelper(KDevelop::IPlugin* parent, KDevelop::IBasicVersionControl* vcs) : QObject(parent) , d(new VcsPluginHelperPrivate()) { Q_ASSERT(vcs); Q_ASSERT(parent); d->plugin = parent; d->vcs = vcs; d->createActions(this); } VcsPluginHelper::~VcsPluginHelper() {} void VcsPluginHelper::addContextDocument(const QUrl &url) { d->ctxUrls.append(url); } void VcsPluginHelper::disposeEventually(KTextEditor::View *, bool dont) { if ( ! dont ) { deleteLater(); } } void VcsPluginHelper::disposeEventually(KTextEditor::Document *) { deleteLater(); } void VcsPluginHelper::setupFromContext(Context* context) { d->ctxUrls = context->urls(); } QList VcsPluginHelper::contextUrlList() const { return d->ctxUrls; } QMenu* VcsPluginHelper::commonActions() { /* TODO: the following logic to determine which actions need to be enabled * or disabled does not work properly. What needs to be implemented is that * project items that are vc-controlled enable all except add, project * items that are not vc-controlled enable add action. For urls that cannot * be made into a project item, or if the project has no associated VC * plugin we need to check whether a VC controls the parent dir, if we have * one we assume the urls can be added but are not currently controlled. If * the url is already version controlled then just enable all except add */ return d->createMenu(); } #define EXECUTE_VCS_METHOD( method ) \ d->plugin->core()->runController()->registerJob( d->vcs-> method ( d->ctxUrls ) ) #define SINGLEURL_SETUP_VARS \ KDevelop::IBasicVersionControl* iface = d->vcs;\ const QUrl &url = d->ctxUrls.front(); void VcsPluginHelper::revert() { VcsJob* job=d->vcs->revert(d->ctxUrls); connect(job, &VcsJob::finished, this, &VcsPluginHelper::revertDone); foreach(const QUrl &url, d->ctxUrls) { IDocument* doc=ICore::self()->documentController()->documentForUrl(url); if(doc && doc->textDocument()) { KTextEditor::ModificationInterface* modif = dynamic_cast(doc->textDocument()); if (modif) { modif->setModifiedOnDiskWarning(false); } doc->textDocument()->setModified(false); } } job->setProperty("urls", QVariant::fromValue(d->ctxUrls)); d->plugin->core()->runController()->registerJob(job); } void VcsPluginHelper::revertDone(KJob* job) { QTimer* modificationTimer = new QTimer; modificationTimer->setInterval(100); connect(modificationTimer, &QTimer::timeout, this, &VcsPluginHelper::delayedModificationWarningOn); connect(modificationTimer, &QTimer::timeout, modificationTimer, &QTimer::deleteLater); modificationTimer->setProperty("urls", job->property("urls")); modificationTimer->start(); } void VcsPluginHelper::delayedModificationWarningOn() { QObject* timer = sender(); QList urls = timer->property("urls").value>(); foreach(const QUrl &url, urls) { IDocument* doc=ICore::self()->documentController()->documentForUrl(url); if(doc) { doc->reload(); KTextEditor::ModificationInterface* modif=dynamic_cast(doc->textDocument()); modif->setModifiedOnDiskWarning(true); } } } void VcsPluginHelper::diffJobFinished(KJob* job) { KDevelop::VcsJob* vcsjob = qobject_cast(job); Q_ASSERT(vcsjob); if (vcsjob->status() == KDevelop::VcsJob::JobSucceeded) { KDevelop::VcsDiff d = vcsjob->fetchResults().value(); if(d.isEmpty()) KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18n("There are no differences."), i18n("VCS support")); else { VCSDiffPatchSource* patch=new VCSDiffPatchSource(d); showVcsDiff(patch); } } else { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), vcsjob->errorString(), i18n("Unable to get difference.")); } } void VcsPluginHelper::diffToBase() { SINGLEURL_SETUP_VARS ICore::self()->documentController()->saveAllDocuments(); VCSDiffPatchSource* patch =new VCSDiffPatchSource(new VCSStandardDiffUpdater(iface, url)); showVcsDiff(patch); } void VcsPluginHelper::diffForRev() { if (d->ctxUrls.isEmpty()) { return; } diffForRev(d->ctxUrls.first()); } void VcsPluginHelper::diffForRevGlobal() { if (d->ctxUrls.isEmpty()) { return; } QUrl url = d->ctxUrls.first(); IProject* project = ICore::self()->projectController()->findProjectForUrl( url ); if( project ) { url = project->path().toUrl(); } diffForRev(url); } void VcsPluginHelper::diffForRev(const QUrl& url) { QAction* action = qobject_cast( sender() ); Q_ASSERT(action); Q_ASSERT(action->data().canConvert()); VcsRevision rev = action->data().value(); ICore::self()->documentController()->saveAllDocuments(); VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous); KDevelop::VcsJob* job = d->vcs->diff(url, prev, rev ); connect(job, &VcsJob::finished, this, &VcsPluginHelper::diffJobFinished); d->plugin->core()->runController()->registerJob(job); } void VcsPluginHelper::history(const VcsRevision& rev) { SINGLEURL_SETUP_VARS QDialog* dlg = new QDialog(ICore::self()->uiController()->activeMainWindow()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowTitle(i18nc("%1: path or URL, %2: name of a version control system", "%2 History (%1)", url.toDisplayString(QUrl::PreferLocalFile), iface->name())); QVBoxLayout *mainLayout = new QVBoxLayout(dlg); KDevelop::VcsEventWidget* logWidget = new KDevelop::VcsEventWidget(url, rev, iface, dlg); mainLayout->addWidget(logWidget); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(buttonBox); dlg->show(); } void VcsPluginHelper::annotation() { SINGLEURL_SETUP_VARS KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (!doc) doc = ICore::self()->documentController()->openDocument(url); KTextEditor::AnnotationInterface* annotateiface = qobject_cast(doc->textDocument()); KTextEditor::AnnotationViewInterface* viewiface = qobject_cast(doc->activeTextView()); if (viewiface && viewiface->isAnnotationBorderVisible()) { viewiface->setAnnotationBorderVisible(false); return; } if (doc && doc->textDocument() && iface) { KDevelop::VcsJob* job = iface->annotate(url); if( !job ) { qWarning() << "Couldn't create annotate job for:" << url << "with iface:" << iface << dynamic_cast( iface ); return; } QColor foreground(Qt::black); QColor background(Qt::white); if (KTextEditor::View* view = doc->activeTextView()) { KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal); foreground = style->foreground().color(); if (style->hasProperty(QTextFormat::BackgroundBrush)) { background = style->background().color(); } } if (annotateiface && viewiface) { KDevelop::VcsAnnotationModel* model = new KDevelop::VcsAnnotationModel(job, url, doc->textDocument(), foreground, background); annotateiface->setAnnotationModel(model); viewiface->setAnnotationBorderVisible(true); // can't use new signal slot syntax here, AnnotationInterface is not a QObject connect(doc->activeTextView(), SIGNAL(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)), this, SLOT(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int))); } else { - KMessageBox::error(0, i18n("Cannot display annotations, missing interface KTextEditor::AnnotationInterface for the editor.")); + KMessageBox::error(nullptr, i18n("Cannot display annotations, missing interface KTextEditor::AnnotationInterface for the editor.")); delete job; } } else { - KMessageBox::error(0, i18n("Cannot execute annotate action because the " + KMessageBox::error(nullptr, i18n("Cannot execute annotate action because the " "document was not found, or was not a text document:\n%1", url.toDisplayString(QUrl::PreferLocalFile))); } } void VcsPluginHelper::annotationContextMenuAboutToShow( KTextEditor::View* view, QMenu* menu, int line ) { KTextEditor::AnnotationInterface* annotateiface = qobject_cast(view->document()); VcsAnnotationModel* model = qobject_cast( annotateiface->annotationModel() ); Q_ASSERT(model); VcsRevision rev = model->revisionForLine(line); // check if the user clicked on a row without revision information if (rev.revisionType() == VcsRevision::Invalid) { // in this case, do not action depending on revision informations return; } d->diffForRevAction->setData(QVariant::fromValue(rev)); d->diffForRevGlobalAction->setData(QVariant::fromValue(rev)); menu->addSeparator(); menu->addAction(d->diffForRevAction); menu->addAction(d->diffForRevGlobalAction); QAction* action = nullptr; action = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Revision")); connect(action, &QAction::triggered, this, [this, rev]() { QApplication::clipboard()->setText(rev.revisionValue().toString()); }); action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-history")), i18n("History...")); connect(action, &QAction::triggered, this, [this, rev]() { history(rev); }); } void VcsPluginHelper::update() { EXECUTE_VCS_METHOD(update); } void VcsPluginHelper::add() { EXECUTE_VCS_METHOD(add); } void VcsPluginHelper::commit() { Q_ASSERT(!d->ctxUrls.isEmpty()); ICore::self()->documentController()->saveAllDocuments(); QUrl url = d->ctxUrls.first(); // We start the commit UI no matter whether there is real differences, as it can also be used to commit untracked files VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(d->vcs, url)); bool ret = showVcsDiff(patchSource); if(!ret) { VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } void VcsPluginHelper::push() { foreach(const QUrl &url, d->ctxUrls) { VcsJob* job = d->plugin->extension()->push(url, VcsLocation()); ICore::self()->runController()->registerJob(job); } } void VcsPluginHelper::pull() { foreach(const QUrl &url, d->ctxUrls) { VcsJob* job = d->plugin->extension()->pull(VcsLocation(), url); ICore::self()->runController()->registerJob(job); } } } diff --git a/vcs/widgets/vcsdiffpatchsources.cpp b/vcs/widgets/vcsdiffpatchsources.cpp index a339c89676..42cf1d8449 100644 --- a/vcs/widgets/vcsdiffpatchsources.cpp +++ b/vcs/widgets/vcsdiffpatchsources.cpp @@ -1,318 +1,318 @@ /* Copyright 2009 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 "vcsdiffpatchsources.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vcsdiff.h" #include "vcsjob.h" #include "../debug.h" using namespace KDevelop; VCSCommitDiffPatchSource::VCSCommitDiffPatchSource(VCSDiffUpdater* updater) : VCSDiffPatchSource(updater), m_vcs(updater->vcs()) { Q_ASSERT(m_vcs); m_commitMessageWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(m_commitMessageWidget.data()); m_commitMessageEdit = new KTextEdit; m_commitMessageEdit.data()->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_commitMessageEdit.data()->setLineWrapMode(QTextEdit::NoWrap); m_vcs->setupCommitMessageEditor(updater->url(), m_commitMessageEdit.data()); QHBoxLayout* titleLayout = new QHBoxLayout; titleLayout->addWidget(new QLabel(i18n("Commit Message:"))); m_oldMessages = new KComboBox(m_commitMessageWidget.data()); m_oldMessages->addItem(i18n("Old Messages")); foreach(const QString& message, oldMessages()) m_oldMessages->addItem(message, message); m_oldMessages->setMaximumWidth(200); connect(m_oldMessages, static_cast(&KComboBox::currentIndexChanged), this, &VCSCommitDiffPatchSource::oldMessageChanged); titleLayout->addWidget(m_oldMessages); layout->addLayout(titleLayout); layout->addWidget(m_commitMessageEdit.data()); connect(this, &VCSCommitDiffPatchSource::reviewCancelled, this, &VCSCommitDiffPatchSource::addMessageToHistory); connect(this, &VCSCommitDiffPatchSource::reviewFinished, this, &VCSCommitDiffPatchSource::addMessageToHistory); } QStringList VCSCommitDiffPatchSource::oldMessages() const { KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); return vcsGroup.readEntry("OldCommitMessages", QStringList()); } void VCSCommitDiffPatchSource::addMessageToHistory(const QString& message) { if(ICore::self()->shuttingDown()) return; KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); const int maxMessages = 10; QStringList oldMessages = vcsGroup.readEntry("OldCommitMessages", QStringList()); oldMessages.removeAll(message); oldMessages.push_front(message); oldMessages = oldMessages.mid(0, maxMessages); vcsGroup.writeEntry("OldCommitMessages", oldMessages); } void VCSCommitDiffPatchSource::oldMessageChanged(QString text) { if(m_oldMessages->currentIndex() != 0) { m_oldMessages->setCurrentIndex(0); m_commitMessageEdit.data()->setText(text); } } void VCSCommitDiffPatchSource::jobFinished(KJob *job) { if (!job || job->error() != 0 ) { QString details = job ? job->errorText() : QString(); if (details.isEmpty()) { //errorText may be empty details = i18n("For more detailed information please see the Version Control toolview"); } - KMessageBox::detailedError(0, i18n("Unable to commit"), details, i18n("Commit unsuccessful")); + KMessageBox::detailedError(nullptr, i18n("Unable to commit"), details, i18n("Commit unsuccessful")); } deleteLater(); } VCSDiffPatchSource::VCSDiffPatchSource(VCSDiffUpdater* updater) : m_updater(updater) { update(); KDevelop::IBasicVersionControl* vcs = m_updater->vcs(); QUrl url = m_updater->url(); QScopedPointer statusJob(vcs->status(QList() << url)); QVariant varlist; if( statusJob->exec() && statusJob->status() == VcsJob::JobSucceeded ) { varlist = statusJob->fetchResults(); foreach( const QVariant &var, varlist.toList() ) { VcsStatusInfo info = var.value(); m_infos += info; if(info.state()!=VcsStatusInfo::ItemUpToDate) m_selectable[info.url()] = info.state(); } } else qCDebug(VCS) << "Couldn't get status for urls: " << url; } VCSDiffPatchSource::VCSDiffPatchSource(const KDevelop::VcsDiff& diff) - : m_updater(0) + : m_updater(nullptr) { updateFromDiff(diff); } VCSDiffPatchSource::~VCSDiffPatchSource() { QFile::remove(m_file.toLocalFile()); delete m_updater; } QUrl VCSDiffPatchSource::baseDir() const { return m_base; } QUrl VCSDiffPatchSource::file() const { return m_file; } QString VCSDiffPatchSource::name() const { return m_name; } uint VCSDiffPatchSource::depth() const { return m_depth; } void VCSDiffPatchSource::updateFromDiff(VcsDiff vcsdiff) { if(!m_file.isValid()) { QTemporaryFile temp2(QDir::tempPath() + QLatin1String("/kdevelop_XXXXXX.patch")); temp2.setAutoRemove(false); temp2.open(); QTextStream t2(&temp2); t2 << vcsdiff.diff(); qCDebug(VCS) << "filename:" << temp2.fileName(); m_file = QUrl::fromLocalFile(temp2.fileName()); temp2.close(); }else{ QFile file(m_file.path()); file.open(QIODevice::WriteOnly); QTextStream t2(&file); t2 << vcsdiff.diff(); } qCDebug(VCS) << "using file" << m_file << vcsdiff.diff() << "base" << vcsdiff.baseDiff(); m_name = QStringLiteral("VCS Diff"); m_base = vcsdiff.baseDiff(); m_depth = vcsdiff.depth(); emit patchChanged(); } void VCSDiffPatchSource::update() { if(!m_updater) return; updateFromDiff(m_updater->update()); } VCSCommitDiffPatchSource::~VCSCommitDiffPatchSource() { delete m_commitMessageWidget.data(); } bool VCSCommitDiffPatchSource::canSelectFiles() const { return true; } QMap< QUrl, KDevelop::VcsStatusInfo::State> VCSDiffPatchSource::additionalSelectableFiles() const { return m_selectable; } QWidget* VCSCommitDiffPatchSource::customWidget() const { return m_commitMessageWidget.data(); } QString VCSCommitDiffPatchSource::finishReviewCustomText() const { return i18nc("@action:button To make a commit", "Commit"); } bool VCSCommitDiffPatchSource::canCancel() const { return true; } void VCSCommitDiffPatchSource::cancelReview() { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); emit reviewCancelled(message); deleteLater(); } bool VCSCommitDiffPatchSource::finishReview(QList< QUrl > selection) { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); qCDebug(VCS) << "Finishing with selection" << selection; QString files; foreach(const QUrl& url, selection) files += "
  • "+ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain) + "
  • "; QString text = i18n("Files will be committed:\n
      %1
    \nWith message:\n
    %2
    ", files, message); - int res = KMessageBox::warningContinueCancel(0, text, i18n("About to commit to repository"), + int res = KMessageBox::warningContinueCancel(nullptr, text, i18n("About to commit to repository"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("ShouldAskConfirmCommit")); if (res != KMessageBox::Continue) { return false; } emit reviewFinished(message, selection); VcsJob* job = m_vcs->commit(message, selection, KDevelop::IBasicVersionControl::NonRecursive); if (!job) { return false; } connect (job, &VcsJob::finished, this, &VCSCommitDiffPatchSource::jobFinished); ICore::self()->runController()->registerJob(job); return true; } bool showVcsDiff(IPatchSource* vcsDiff) { KDevelop::IPatchReview* patchReview = ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IPatchReview")); if( patchReview ) { patchReview->startReview(vcsDiff); return true; } else { qWarning() << "Patch review plugin not found"; return false; } } VcsDiff VCSStandardDiffUpdater::update() const { QScopedPointer diffJob(m_vcs->diff(m_url, KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Base), KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Working))); const bool success = diffJob ? diffJob->exec() : false; if (!success) { - KMessageBox::error(0, i18n("Could not create a patch for the current version.")); + KMessageBox::error(nullptr, i18n("Could not create a patch for the current version.")); return {}; } return diffJob->fetchResults().value(); } VCSStandardDiffUpdater::VCSStandardDiffUpdater(IBasicVersionControl* vcs, QUrl url) : m_vcs(vcs), m_url(url) { } VCSStandardDiffUpdater::~VCSStandardDiffUpdater() { } VCSDiffUpdater::~VCSDiffUpdater() { }