diff --git a/debugger/breakpoint/breakpointmodel.cpp b/debugger/breakpoint/breakpointmodel.cpp index 672ebf434..6fece2b84 100644 --- a/debugger/breakpoint/breakpointmodel.cpp +++ b/debugger/breakpoint/breakpointmodel.cpp @@ -1,642 +1,641 @@ /* 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 #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"), 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 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 = 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()); ) { QSignalBlocker blocker(doc->textDocument()); 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); } } } //remove marks foreach (IDocument *doc, ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; { QSignalBlocker blocker(doc->textDocument()); 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:; } } } } 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(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 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 nullptr; } diff --git a/debugger/breakpoint/breakpointwidget.cpp b/debugger/breakpoint/breakpointwidget.cpp index 32e47e854..978324992 100644 --- a/debugger/breakpoint/breakpointwidget.cpp +++ b/debugger/breakpoint/breakpointwidget.cpp @@ -1,315 +1,314 @@ /* * 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 #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(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(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/breakpoint/breakpointwidget.h b/debugger/breakpoint/breakpointwidget.h index 9163476de..c338037b8 100644 --- a/debugger/breakpoint/breakpointwidget.h +++ b/debugger/breakpoint/breakpointwidget.h @@ -1,87 +1,85 @@ /* * 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. */ #ifndef KDEVPLATFORM_BREAKPOINTWIDGET_H #define KDEVPLATFORM_BREAKPOINTWIDGET_H #include #include "debuggerexport.h" class QAbstractProxyModel; class QModelIndex; -class QItemSelection; class QTreeView; class QMenu; -class QSplitter; namespace KDevelop { class IDebugController; class Breakpoint; class BreakpointDetails; class KDEVPLATFORMDEBUGGER_EXPORT BreakpointWidget : public AutoOrientedSplitter { Q_OBJECT public: BreakpointWidget(IDebugController *controller, QWidget *parent); protected: void contextMenuEvent(QContextMenuEvent* event) override; void showEvent(QShowEvent * event) override; private: void setupPopupMenu(); void edit(KDevelop::Breakpoint *n); private Q_SLOTS: void slotAddBlankBreakpoint(); void slotAddBlankWatchpoint(); void slotAddBlankReadWatchpoint(); void slotAddBlankAccessWatchpoint(); void slotRemoveBreakpoint(); void slotUpdateBreakpointDetail(); void slotDataInserted(int column, const QVariant& value); void slotOpenFile(const QModelIndex& breakpointIdx); void breakpointError(int row, const QString& msg); void breakpointHit(int row); void slotDisableAllBreakpoints(); void slotEnableAllBreakpoints(); void slotRemoveAllBreakpoints(); void slotPopupMenuAboutToShow(); private: QTreeView* m_breakpointsView; BreakpointDetails* m_details; QMenu* m_popup; bool m_firstShow; IDebugController *m_debugController; QAction* m_breakpointDisableAllAction; QAction* m_breakpointEnableAllAction; QAction* m_breakpointRemoveAll; QAbstractProxyModel* m_proxyModel; }; } #endif // KDEVPLATFORM_BREAKPOINTWIDGET_H diff --git a/debugger/framestack/framestackmodel.h b/debugger/framestack/framestackmodel.h index e7a03b53b..327283a90 100644 --- a/debugger/framestack/framestackmodel.h +++ b/debugger/framestack/framestackmodel.h @@ -1,105 +1,104 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_FRAMESTACKMODEL_H #define KDEVPLATFORM_FRAMESTACKMODEL_H #include -#include #include #include #include namespace KDevelop { class DebugController; class FrameStackModelPrivate; /** FIXME: This class needs rework, since at present it is not true model. Client cannot just obtain frames by grabbing a thread and listing children. It should first setCurrentThread beforehand, and it is the method that will actually fetch threads. Therefore, if this model is submitted to plain QTreeView, it won't work at all. Ideally, this should hold current thread and current frame numbers, and only fetch the list of threads, and list of frames inside thread when asked for by the view. */ class KDEVPLATFORMDEBUGGER_EXPORT FrameStackModel : public IFrameStackModel { Q_OBJECT public: explicit FrameStackModel(IDebugSession* session); ~FrameStackModel() override; struct ThreadItem { int nr; QString name; }; void setThreads(const QList &threads); /** * Update frames for thread @p threadNumber * * @note The currentFrame property will be set to the first frame * containing debug information */ void setFrames(int threadNumber, QList frames); void insertFrames(int threadNumber, const QList &frames); void setHasMoreFrames(int threadNumber, bool hasMoreFrames); FrameItem frame(const QModelIndex &index) override; QList frames(int threadNumber) const; //ItemModel implementation QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& child) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void setCurrentThread(int threadNumber) override; void setCurrentThread(const QModelIndex &index) override; int currentThread() const override; QModelIndex currentThreadIndex() const override; int currentFrame() const override; QModelIndex currentFrameIndex() const override; void setCurrentFrame(int frame) override; void fetchMoreFrames() override; void setCrashedThreadIndex(int index); int crashedThreadIndex() const; private Q_SLOTS: void stateChanged(KDevelop::IDebugSession::DebuggerState state); private: QScopedPointer d; void handleEvent(IDebugSession::event_t event) override; }; } #endif diff --git a/debugger/framestack/framestackwidget.cpp b/debugger/framestack/framestackwidget.cpp index 7b811218c..be18b573a 100644 --- a/debugger/framestack/framestackwidget.cpp +++ b/debugger/framestack/framestackwidget.cpp @@ -1,262 +1,261 @@ /* * 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(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() : 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; setEnabled(enable); } } #include "framestackwidget.moc" diff --git a/debugger/interfaces/ibreakpointcontroller.h b/debugger/interfaces/ibreakpointcontroller.h index b9f6088d5..5526276f3 100644 --- a/debugger/interfaces/ibreakpointcontroller.h +++ b/debugger/interfaces/ibreakpointcontroller.h @@ -1,105 +1,104 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh 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. */ #ifndef KDEVPLATFORM_IBREAKPOINTCONTROLLER_H #define KDEVPLATFORM_IBREAKPOINTCONTROLLER_H #include #include #include #include #include "idebugsession.h" #include "../breakpoint/breakpoint.h" #include "../breakpoint/breakpointmodel.h" -class QModelIndex; namespace KDevelop { class IDebugSession; class KDEVPLATFORMDEBUGGER_EXPORT IBreakpointController : public QObject { Q_OBJECT public: explicit IBreakpointController(IDebugSession* parent); /** * Called just after a breakpoint is added in the given row. * The breakpoint which was previously at the given row and all later breakpoints have * been moved. * * Implementations may implement this function to maintain their internal data structures. * Note, however, that breakpoints are typically still empty (i.e. without a useful location) * when this method is called. */ virtual void breakpointAdded(int row); /** * Implementors must handle changes to the breakpoint model, which are signaled via * this method, by forwarding the changes to the backend debugger. */ virtual void breakpointModelChanged(int row, BreakpointModel::ColumnFlags columns); /** * Called when a breakpoint is about to be deleted from the model. */ virtual void breakpointAboutToBeDeleted(int row); /** * Called when the debugger state changed. * * Note: this method exists to ease the API transition; it should probably go away eventually, * since controller implementations that do want to listen to debugger state changes probably * have better ways to do so. */ virtual void debuggerStateChanged(KDevelop::IDebugSession::DebuggerState); protected: IDebugSession *debugSession() const; BreakpointModel *breakpointModel() const; void updateState(int row, Breakpoint::BreakpointState state); void updateHitCount(int row, int hitCount); void updateErrorText(int row, const QString& errorText); void notifyHit(int row, const QString & msg); // The API below is obsolete and will be removed soon protected: void breakpointStateChanged(Breakpoint* breakpoint); void setHitCount(Breakpoint* breakpoint, int count); void error(Breakpoint* breakpoint, const QString& msg, Breakpoint::Column column); void hit(Breakpoint* breakpoint, const QString& msg = QString()); void sendMaybeAll(); virtual void sendMaybe(Breakpoint *breakpoint) = 0; QMap > m_dirty; QSet m_pending; QMap > m_errors; int m_dontSendChanges; }; } #endif // KDEVPLATFORM_IBREAKPOINTCONTROLLER_H diff --git a/debugger/interfaces/idebugsession.cpp b/debugger/interfaces/idebugsession.cpp index c8356dbb3..ee01c16eb 100644 --- a/debugger/interfaces/idebugsession.cpp +++ b/debugger/interfaces/idebugsession.cpp @@ -1,136 +1,134 @@ /*************************************************************************** * 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 "idebugsession.h" #include "iframestackmodel.h" #include "ivariablecontroller.h" #include "util/debug.h" -#include -#include #include namespace KDevelop { class IDebugSessionPrivate { public: explicit IDebugSessionPrivate(IDebugSession* q) : q(q) {} void slotStateChanged(IDebugSession::DebuggerState state); IDebugSession* q; /// Current position in debugged program, gets set when the state changes QUrl m_url; int m_line; QString m_addr; }; IDebugSession::IDebugSession() : d(new IDebugSessionPrivate(this)) { connect(this, &IDebugSession::stateChanged, this, [&](IDebugSession::DebuggerState state) { d->slotStateChanged(state); }); } IDebugSession::~IDebugSession() { } bool IDebugSession::isRunning() const { DebuggerState s = state(); return (s == ActiveState || s == PausedState); } void IDebugSession::raiseEvent(event_t e) { if (IFrameStackModel* model = frameStackModel()) { model->handleEvent(e); } if (IVariableController* variables = variableController()) { variables->handleEvent(e); } // FIXME: consider if we actually need signals emit event(e); } QPair IDebugSession::convertToLocalUrl(const QPair &remoteUrl) const { return remoteUrl; } QPair IDebugSession::convertToRemoteUrl(const QPair& localUrl) const { return localUrl; } void IDebugSession::clearCurrentPosition() { qCDebug(DEBUGGER); d->m_url.clear(); d->m_addr.clear(); d->m_line = -1; emit clearExecutionPoint(); } void IDebugSession::setCurrentPosition(const QUrl& url, int line, const QString& addr) { qCDebug(DEBUGGER) << url << line << addr; if (url.isEmpty() || !QFileInfo::exists(convertToLocalUrl(qMakePair(url,line)).first.path())) { clearCurrentPosition(); d->m_addr = addr; emit showStepInDisassemble(addr); } else { d->m_url = url; d->m_line = line; d->m_addr = addr; emit showStepInSource(url, line, addr); } } QUrl IDebugSession::currentUrl() const { return d->m_url; } int IDebugSession::currentLine() const { return d->m_line; } QString IDebugSession::currentAddr() const { return d->m_addr; } void IDebugSessionPrivate::slotStateChanged(IDebugSession::DebuggerState state) { if (state != IDebugSession::PausedState) { q->clearCurrentPosition(); } } } #include "moc_idebugsession.cpp" diff --git a/debugger/interfaces/idebugsession.h b/debugger/interfaces/idebugsession.h index 40dda451d..699af7d0c 100644 --- a/debugger/interfaces/idebugsession.h +++ b/debugger/interfaces/idebugsession.h @@ -1,198 +1,197 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_IDEBUGSESSION_H #define KDEVPLATFORM_IDEBUGSESSION_H #include #include -#include #include namespace KDevelop { class IDebugSessionPrivate; class IVariableController; class IBreakpointController; class IFrameStackModel; class Breakpoint; class StackModel; class KDEVPLATFORMDEBUGGER_EXPORT IDebugSession : public QObject { Q_OBJECT Q_ENUMS(DebuggerState) public: IDebugSession(); ~IDebugSession() override; enum DebuggerState { NotStartedState, StartingState, ActiveState, PausedState, StoppingState, StoppedState, EndedState }; enum event_t { program_state_changed = 1, program_exited, debugger_exited, // Emitted when the thread or frame that is selected in UI // changes. thread_or_frame_changed, debugger_busy, debugger_ready, // Raised when debugger believe that program start running. // Can be used to hide current line indicator. // Don't count on this being raise in all cases where // program is running. program_running, // Raise when the debugger is in touch with the program, // and should have access to its debug symbols. The program // is not necessary running yet, or might already exited, // or be otherwise dead. connected_to_program }; public: /** * Current state of the debug session */ virtual DebuggerState state() const = 0; /** * Should return if restart is currently available */ virtual bool restartAvaliable() const = 0; /** * Returns if the debugee is currently running. This includes paused. */ bool isRunning() const; /** * Returns the local Url for a source file used in the current debug session. * * The default implementation just returns the url and is sufficient for * local debuggers. Remote debuggers can implement a path mapping mechanism. */ virtual QPair convertToLocalUrl(const QPair &remoteUrl) const; /** * Returns the remote Url for a source file used in the current debug session. * * The default implementation just returns the url and is sufficient for * local debuggers. Remote debuggers can implement a path mapping mechanism. */ virtual QPair convertToRemoteUrl(const QPair &localUrl) const; /** * @return the breakpoint controller of this session * * @note Implementations must ensure that a breakpoint controller always exists (even if it * is a dummy stub implementation that does nothing), and that it does not change during * the lifetime of a session. */ virtual IBreakpointController* breakpointController() const = 0; /** * @return the variable controller of this session * * @note Implementations must ensure that a variable controller always exists (even if it * is a dummy stub implementation that does nothing), and that it does not change during * the lifetime of a session. */ virtual IVariableController* variableController() const = 0; /** * @return the frame stack model of this session * * @note Implementations must ensure that a frame stack model always exists (even if it * is a dummy stub implementation that does nothing), and that it does not change during * the lifetime of a session. */ virtual IFrameStackModel* frameStackModel() const = 0; public Q_SLOTS: virtual void restartDebugger() = 0; virtual void stopDebugger() = 0; virtual void interruptDebugger() = 0; virtual void run() = 0; virtual void runToCursor() = 0; virtual void jumpToCursor() = 0; virtual void stepOver() = 0; virtual void stepIntoInstruction() = 0; virtual void stepInto() = 0; virtual void stepOverInstruction() = 0; virtual void stepOut() = 0; Q_SIGNALS: void stateChanged(KDevelop::IDebugSession::DebuggerState state); void showStepInSource(const QUrl& file, int line, const QString &addr); void showStepInDisassemble(const QString &addr); void clearExecutionPoint(); void finished(); void raiseFramestackViews(); /** This signal is emitted whenever the given event in a program happens. See DESIGN.txt for expected handled of each event. NOTE: this signal should never be emitted directly. Instead, use raiseEvent. */ void event(IDebugSession::event_t e); public: using QObject::event; // prevent hiding of base method. QUrl currentUrl() const; int currentLine() const; QString currentAddr() const; protected: // Clear the position before running code void clearCurrentPosition(); /// Sets new position and emits showStepInSource or showStepInDisassemble (if source file is unavailable) signal void setCurrentPosition(const QUrl& url, int line, const QString& addr); /** Raises the specified event. Should be used instead of emitting 'event' directly, since this method can perform additional book-keeping for events. FIXME: it might make sense to automatically route events to all debugger components, as opposed to requiring that they connect to any signal. */ virtual void raiseEvent(event_t e); friend class FrameStackModel; private: friend IDebugSessionPrivate; QScopedPointer d; }; } #endif diff --git a/debugger/interfaces/ivariablecontroller.h b/debugger/interfaces/ivariablecontroller.h index 4c0ebcfbf..ef0d9e4cd 100644 --- a/debugger/interfaces/ivariablecontroller.h +++ b/debugger/interfaces/ivariablecontroller.h @@ -1,102 +1,100 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_IVARIABLECONTROLLER_H #define KDEVPLATFORM_IVARIABLECONTROLLER_H #include #include #include "idebugsession.h" -class QString; - namespace KTextEditor { class Document; class Cursor; } namespace KDevelop { class IDebugSession; class VariableCollection; class Variable; class TreeModel; class TreeItem; class KDEVPLATFORMDEBUGGER_EXPORT IVariableController : public QObject { Q_OBJECT public: explicit IVariableController(IDebugSession* parent); /* Create a variable for the specified expression in the currentl thread and frame. */ virtual Variable* createVariable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display = {}) = 0; virtual KTextEditor::Range expressionRangeUnderCursor(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor) = 0; virtual void addWatch(Variable* variable) = 0; virtual void addWatchpoint(Variable* variable) = 0; enum UpdateType { UpdateNone = 0x0, UpdateLocals = 0x1, UpdateWatches = 0x2 }; Q_DECLARE_FLAGS(UpdateTypes, UpdateType) void setAutoUpdate(QFlags autoUpdate); QFlags autoUpdate(); protected: /** * Convenience function that returns the VariableCollection **/ VariableCollection *variableCollection(); /** * Convenience function that returns the used DebugSession **/ IDebugSession *session() const; virtual void update() = 0; virtual void handleEvent(IDebugSession::event_t event); friend class IDebugSession; private Q_SLOTS: void stateChanged(KDevelop::IDebugSession::DebuggerState); private: void updateIfFrameOrThreadChanged(); QFlags m_autoUpdate; int m_activeThread; int m_activeFrame; }; } // namespace KDevelop Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::IVariableController::UpdateTypes) #endif diff --git a/debugger/util/pathmappings.cpp b/debugger/util/pathmappings.cpp index 105ab91a3..d6b8669cd 100644 --- a/debugger/util/pathmappings.cpp +++ b/debugger/util/pathmappings.cpp @@ -1,258 +1,257 @@ /* * 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 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 "pathmappings.h" #include "debug.h" #include #include -#include #include #include #include #include #include #include #include namespace { static QUrl rebaseMatchingUrl(const QUrl& toRebase, const KConfigGroup& config, const QString& baseEntry, const QString& rebaseEntry) { const QUrl::UrlFormattingOption matchOpts = QUrl::NormalizePathSegments; foreach (const QString &group, config.groupList()) { KConfigGroup pathCfg = config.group(group); const QString baseStr = pathCfg.readEntry(baseEntry, QUrl()).url(matchOpts); const QString searchStr = toRebase.url(matchOpts); if (searchStr.contains(baseStr)) { const QUrl rebase = pathCfg.readEntry(rebaseEntry, QUrl()); return rebase.resolved(QUrl(searchStr.mid(baseStr.length()))); } } //No mapping found return toRebase; } } namespace KDevelop { const QString PathMappings::pathMappingsEntry(QStringLiteral("Path Mappings")); const QString PathMappings::pathMappingRemoteEntry(QStringLiteral("Remote")); const QString PathMappings::pathMappingLocalEntry(QStringLiteral("Local")); QUrl PathMappings::convertToLocalUrl(const KConfigGroup& config, const QUrl& remoteUrl) { if (remoteUrl.isLocalFile() && QFile::exists(remoteUrl.toLocalFile())) { return remoteUrl; } KConfigGroup cfg = config.group(pathMappingsEntry); return rebaseMatchingUrl(remoteUrl, cfg, pathMappingRemoteEntry, pathMappingLocalEntry); } QUrl PathMappings::convertToRemoteUrl(const KConfigGroup& config, const QUrl& localUrl) { KConfigGroup cfg = config.group(pathMappingsEntry); return rebaseMatchingUrl(localUrl, cfg, pathMappingLocalEntry, pathMappingRemoteEntry); } class PathMappingModel : public QAbstractTableModel { Q_OBJECT public: int columnCount(const QModelIndex& parent = QModelIndex()) const override { if (parent.isValid()) return 0; return 2; } int rowCount(const QModelIndex& parent = QModelIndex()) const override { if (parent.isValid()) return 0; return m_paths.count() + 1; } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) { return i18n("Remote Path"); } else if (section == 1) { return i18n("Local Path"); } } return QAbstractTableModel::headerData(section, orientation, role); } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { if (!index.isValid()) return QVariant(); if (index.parent().isValid()) return QVariant(); if (index.column() > 1) return QVariant(); if (index.row() > m_paths.count()) return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) { if (index.row() == m_paths.count()) return QString(); if (index.column() == 0) { return m_paths[index.row()].remote.toDisplayString(QUrl::PreferLocalFile); } else if (index.column() == 1) { return m_paths[index.row()].local.toDisplayString(QUrl::PreferLocalFile); } } return QVariant(); } Qt::ItemFlags flags(const QModelIndex& index) const override { if (index.parent().isValid()) return Qt::NoItemFlags; if (!index.isValid()) return Qt::NoItemFlags; return ( Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled ); } bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { if (!index.isValid()) return false; if (index.parent().isValid()) return false; if (index.column() > 1) return false; if (index.row() > m_paths.count()) return false; if (role == Qt::EditRole) { if (index.row() == m_paths.count()) { beginInsertRows(QModelIndex(), index.row()+1, index.row()+1); m_paths.append(Path()); endInsertRows(); } if (index.column() == 0) { m_paths[index.row()].remote = QUrl::fromUserInput(value.toString()); } else if (index.column() == 1) { m_paths[index.row()].local = QUrl::fromLocalFile(value.toString()); } dataChanged(index, index); return true; } return false; } bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override { if (parent.isValid()) return false; if (row+count > m_paths.count()) return false; beginRemoveRows(parent, row, row+count-1); for (int i=0; i m_paths; }; PathMappingsWidget::PathMappingsWidget(QWidget* parent): QWidget(parent) { QVBoxLayout *verticalLayout = new QVBoxLayout(this); m_pathMappingTable = new QTableView(this); m_pathMappingTable->setSelectionBehavior(QAbstractItemView::SelectRows); m_pathMappingTable->horizontalHeader()->setDefaultSectionSize(150); m_pathMappingTable->horizontalHeader()->setStretchLastSection(true); verticalLayout->addWidget(m_pathMappingTable); m_pathMappingTable->setModel(new PathMappingModel()); connect(m_pathMappingTable->model(), &QAbstractItemModel::dataChanged, this, &PathMappingsWidget::changed); connect(m_pathMappingTable->model(), &QAbstractItemModel::rowsRemoved, this, &PathMappingsWidget::changed); connect(m_pathMappingTable->model(), &QAbstractItemModel::rowsInserted, this, &PathMappingsWidget::changed); QAction* deletePath = new QAction( QIcon::fromTheme(QStringLiteral("edit-delete")), i18n( "Delete" ), this ); connect(deletePath, &QAction::triggered, this, &PathMappingsWidget::deletePath); deletePath->setShortcut(Qt::Key_Delete); deletePath->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_pathMappingTable->addAction(deletePath); } void PathMappingsWidget::deletePath() { foreach (const QModelIndex &i, m_pathMappingTable->selectionModel()->selectedRows()) { m_pathMappingTable->model()->removeRow(i.row(), i.parent()); } } void PathMappingsWidget::loadFromConfiguration(const KConfigGroup& cfg) { static_cast(m_pathMappingTable->model())->loadFromConfiguration(cfg); } void PathMappingsWidget::saveToConfiguration(KConfigGroup cfg) const { static_cast(m_pathMappingTable->model())->saveToConfiguration(cfg); } } #include "pathmappings.moc" #include "moc_pathmappings.cpp" diff --git a/debugger/util/treeitem.cpp b/debugger/util/treeitem.cpp index 062347139..7daaeafb8 100644 --- a/debugger/util/treeitem.cpp +++ b/debugger/util/treeitem.cpp @@ -1,262 +1,261 @@ /* * 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_(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_ = 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. qDeleteAll(copy); } 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_ = 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 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_ = 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/treeitem.h b/debugger/util/treeitem.h index f924649f5..9edeb0be3 100644 --- a/debugger/util/treeitem.h +++ b/debugger/util/treeitem.h @@ -1,128 +1,126 @@ /* * 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. */ #ifndef KDEVPLATFORM_TREEITEM_H #define KDEVPLATFORM_TREEITEM_H -#include #include -#include #include #include #include #include namespace KDevelop { class TreeModel; class KDEVPLATFORMDEBUGGER_EXPORT TreeItem: public QObject { Q_OBJECT public: ~TreeItem() override; // FIXME: should be protected public: // Methods that the derived classes should implement /** Fetches more children, and adds them by calling appendChild. The amount of children to fetch is up to the implementation. After fetching, should call setHasMore. */ virtual void fetchMoreChildren()=0; virtual void setColumn(int index, const QVariant& data) { Q_UNUSED(index); Q_UNUSED(data); } void emitAllChildrenFetched(); protected: // Interface for derived classes /** Creates a tree item with the specified data. FIXME: do we actually have to have the model pointer. */ explicit TreeItem(TreeModel* model, TreeItem *parent = nullptr); /** Set the data to be shown for the item itself. */ void setData(const QVector &data); /** Adds a new child and notifies the interested parties. Clears the "hasMore" flag. */ void appendChild(TreeItem *child, bool initial = false); void insertChild(int position, TreeItem *child, bool initial = false); void removeChild(int index); void removeSelf(); void deleteChildren(); /** Report change in data of this item. */ void reportChange(); void reportChange(int column); /** Clears all children. */ void clear(); /** Sets a flag that tells if we have some more children that are not fetched yet. */ void setHasMore(bool more); void setHasMoreInitial(bool more); TreeModel* model() { return model_; } bool isExpanded() const { return expanded_; } Q_SIGNALS: void expanded(); void collapsed(); void allChildrenFetched(); protected: // Backend implementation of Model/View friend class TreeModel; TreeItem *child(int row); int childCount() const; int columnCount() const; virtual QVariant data(int column, int role) const; int row() const; TreeItem *parent(); bool hasMore() const { return more_; } void setExpanded(bool b); virtual void clicked() {} virtual QVariant icon(int column) const; protected: QVector childItems; QVector itemData; TreeItem *parentItem; TreeModel *model_; bool more_; TreeItem *ellipsis_; bool expanded_; }; } #endif diff --git a/debugger/util/treemodel.cpp b/debugger/util/treemodel.cpp index 4c50186ee..1300b488b 100644 --- a/debugger/util/treemodel.cpp +++ b/debugger/util/treemodel.cpp @@ -1,206 +1,204 @@ /* * 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_(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 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() == 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/variable/variablecollection.cpp b/debugger/variable/variablecollection.cpp index fd9a899b6..9bb8b8b6f 100644 --- a/debugger/variable/variablecollection.cpp +++ b/debugger/variable/variablecollection.cpp @@ -1,548 +1,547 @@ /* * 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_expression(expression) , m_inScope(true) , m_topLevel(true) , m_changed(false) , m_showError(false) , m_format(Natural) { // 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_(nullptr) { setData(QVector() << i18n("Auto") << QString()); } Variable* Watches::add(const QString& expression) { 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_ = 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/variablecollection.h b/debugger/variable/variablecollection.h index 5efb0a508..ede5712b4 100644 --- a/debugger/variable/variablecollection.h +++ b/debugger/variable/variablecollection.h @@ -1,255 +1,253 @@ /* * 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. */ #ifndef KDEVPLATFORM_VARIABLECOLLECTION_H #define KDEVPLATFORM_VARIABLECOLLECTION_H -#include -#include #include #include #include #include #include "../util/treemodel.h" #include "../util/treeitem.h" #include "../../interfaces/idocument.h" #include "../interfaces/idebugsession.h" #include "../../interfaces/idebugcontroller.h" namespace KDevMI { namespace GDB { class GdbTest; } } namespace KDevelop { class VariableToolTip; class KDEVPLATFORMDEBUGGER_EXPORT Variable : public TreeItem { Q_OBJECT friend class KDevMI::GDB::GdbTest; public: protected: Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display = {}); public: enum format_t { Natural, Binary, Octal, Decimal, Hexadecimal }; static format_t str2format(const QString& str); static QString format2str(format_t format); QString expression() const; bool inScope() const; void setInScope(bool v); void setValue(const QString &v); QString value() const; void setType(const QString& type); QString type() const; void setTopLevel(bool v); void setShowError(bool v); bool showError(); using TreeItem::setHasMore; using TreeItem::setHasMoreInitial; using TreeItem::appendChild; using TreeItem::deleteChildren; using TreeItem::isExpanded; using TreeItem::parent; using TreeItem::model; ~Variable() override; /* Connects this variable to debugger, fetching the current value and otherwise preparing this variable to be displayed everywhere. The attempt may fail, for example if the expression is invalid. Calls slot 'callbackMethod' in 'callback' to notify of the result. The slot should be taking 'bool ok' parameter. */ virtual void attachMaybe(QObject *callback = nullptr, const char *callbackMethod = nullptr) = 0; virtual bool canSetFormat() const { return false; } void setFormat(format_t format); format_t format() const { return m_format; } virtual void formatChanged(); // get/set 'changed' state, if the variable changed it will be highlighted bool isChanged() const { return m_changed; } void setChanged(bool c); void resetChanged(); public slots: void die(); protected: bool topLevel() const { return m_topLevel; } private: // TreeItem overrides QVariant data(int column, int role) const override; private: bool isPotentialProblematicValue() const; QString m_expression; bool m_inScope; bool m_topLevel; bool m_changed; bool m_showError; format_t m_format; }; class KDEVPLATFORMDEBUGGER_EXPORT TooltipRoot : public TreeItem { Q_OBJECT public: explicit TooltipRoot(TreeModel* model) : TreeItem(model) {} void init(Variable *var) { appendChild(var); } void fetchMoreChildren() override {} }; class KDEVPLATFORMDEBUGGER_EXPORT Watches : public TreeItem { Q_OBJECT friend class KDevMI::GDB::GdbTest; public: Watches(TreeModel* model, TreeItem* parent); Variable* add(const QString& expression); void reinstall(); Variable *addFinishResult(const QString& convenienceVarible); void removeFinishResult(); void resetChanged(); using TreeItem::childCount; friend class VariableCollection; friend class IVariableController; private: QVariant data(int column, int role) const override; void fetchMoreChildren() override {} Variable* finishResult_; }; class KDEVPLATFORMDEBUGGER_EXPORT Locals : public TreeItem { Q_OBJECT public: Locals(TreeModel* model, TreeItem* parent, const QString &name); QList updateLocals(QStringList locals); void resetChanged(); using TreeItem::deleteChildren; using TreeItem::setHasMore; friend class VariableCollection; friend class IVariableController; private: void fetchMoreChildren() override {} }; class KDEVPLATFORMDEBUGGER_EXPORT VariablesRoot : public TreeItem { Q_OBJECT public: explicit VariablesRoot(TreeModel* model); Watches *watches() const { return m_watches; } Locals *locals(const QString &name = QStringLiteral("Locals")); QHash allLocals() const; void fetchMoreChildren() override {} void resetChanged(); private: Watches *m_watches; QHash m_locals; }; class VariableProvider : public KTextEditor::TextHintProvider { public: explicit VariableProvider(VariableCollection* collection); QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) override; private: VariableCollection* m_collection; }; class KDEVPLATFORMDEBUGGER_EXPORT VariableCollection : public TreeModel { Q_OBJECT public: enum Column { NameColumn, ValueColumn, TypeColumn }; explicit VariableCollection(IDebugController* parent); ~VariableCollection() override; VariablesRoot* root() const { return m_universe; } Watches* watches() const { return m_universe->watches(); } Locals* locals(const QString &name = i18n("Locals")) const { return m_universe->locals(name); } QHash allLocals() const { return m_universe->allLocals(); } public Q_SLOTS: void variableWidgetShown(); void variableWidgetHidden(); private Q_SLOTS: void updateAutoUpdate(KDevelop::IDebugSession* session = nullptr); void textDocumentCreated( KDevelop::IDocument*); void viewCreated(KTextEditor::Document*, KTextEditor::View*); private: VariablesRoot* m_universe; QPointer m_activeTooltip; bool m_widgetVisible; friend class VariableProvider; VariableProvider m_textHintProvider; }; } #endif diff --git a/debugger/variable/variablesortmodel.h b/debugger/variable/variablesortmodel.h index c44f61bdf..632d05f95 100644 --- a/debugger/variable/variablesortmodel.h +++ b/debugger/variable/variablesortmodel.h @@ -1,48 +1,46 @@ /* * KDevelop Debugger Support * * Copyright 2016 Mikhail Ivchenko * * 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. */ #ifndef KDEVPLATFORM_VARIABLESORTMODEL_H #define KDEVPLATFORM_VARIABLESORTMODEL_H #include #include -class QModelIndex; - namespace KDevelop { class VariableSortProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit VariableSortProxyModel(QObject *parent = nullptr); protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; private: QCollator m_collator; }; } #endif diff --git a/debugger/variable/variabletooltip.cpp b/debugger/variable/variabletooltip.cpp index d69c05e1c..149b7d94d 100644 --- a/debugger/variable/variabletooltip.cpp +++ b/debugger/variable/variabletooltip.cpp @@ -1,221 +1,217 @@ /* * KDevelop Debugger Support * * 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 "variabletooltip.h" #include #include -#include #include -#include -#include #include #include -#include #include #include #include #include #include #include #include #include #include "variablecollection.h" #include "../breakpoint/breakpointmodel.h" #include "../interfaces/ivariablecontroller.h" #include "../../util/activetooltip.h" #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" namespace KDevelop { class SizeGrip : public QWidget { Q_OBJECT public: explicit SizeGrip(QWidget* parent) : QWidget(parent) { m_parent = parent; } protected: void paintEvent(QPaintEvent *) override { QPainter painter(this); QStyleOptionSizeGrip opt; opt.init(this); opt.corner = Qt::BottomRightCorner; style()->drawControl(QStyle::CE_SizeGrip, &opt, &painter, this); } void mousePressEvent(QMouseEvent* e) override { if (e->button() == Qt::LeftButton) { m_pos = e->globalPos(); m_startSize = m_parent->size(); e->ignore(); } } void mouseReleaseEvent(QMouseEvent*) override { m_pos = QPoint(); } void mouseMoveEvent(QMouseEvent* e) override { if (!m_pos.isNull()) { m_parent->resize( m_startSize.width() + (e->globalPos().x() - m_pos.x()), m_startSize.height() + (e->globalPos().y() - m_pos.y()) ); } } private: QWidget *m_parent; QSize m_startSize; QPoint m_pos; }; VariableToolTip::VariableToolTip(QWidget* parent, const QPoint& position, const QString& identifier) : ActiveToolTip(parent, position) { setPalette( QApplication::palette() ); m_model = new TreeModel(QVector() << i18n("Name") << i18n("Value") << i18n("Type"), this); TooltipRoot* tr = new TooltipRoot(m_model); m_model->setRootItem(tr); m_var = ICore::self()->debugController()->currentSession()-> variableController()->createVariable( m_model, tr, identifier); tr->init(m_var); m_var->attachMaybe(this, "variableCreated"); QVBoxLayout* l = new QVBoxLayout(this); l->setContentsMargins(0, 0, 0, 0); // setup proxy model m_proxy = new QSortFilterProxyModel; m_view = new AsyncTreeView(m_model, m_proxy, this); m_proxy->setSourceModel(m_model); m_view->setModel(m_proxy); m_view->header()->resizeSection(0, 150); m_view->header()->resizeSection(1, 90); m_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_view->setSelectionMode(QAbstractItemView::SingleSelection); m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); l->addWidget(m_view); m_itemHeight = m_view->indexRowSizeHint(m_model->indexForItem(m_var, 0)); connect(m_view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &VariableToolTip::slotRangeChanged); m_selection = m_view->selectionModel(); m_selection->select(m_model->indexForItem(m_var, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); QHBoxLayout* buttonBox = new QHBoxLayout(); buttonBox->setContentsMargins(11, 0, 11, 6); QPushButton* watchThisButton = new QPushButton(i18n("Watch This")); buttonBox->addWidget(watchThisButton); QPushButton* stopOnChangeButton = new QPushButton(i18n("Stop on Change")); buttonBox->addWidget(stopOnChangeButton); QSignalMapper* mapper = new QSignalMapper(this); connect(watchThisButton, &QPushButton::clicked, mapper, static_cast(&QSignalMapper::map)); mapper->setMapping(watchThisButton, QStringLiteral("add_watch")); connect(stopOnChangeButton, &QPushButton::clicked, mapper, static_cast(&QSignalMapper::map)); mapper->setMapping(stopOnChangeButton, QStringLiteral("add_watchpoint")); connect(mapper, static_cast(&QSignalMapper::mapped), this, &VariableToolTip::slotLinkActivated); QHBoxLayout* inner = new QHBoxLayout(); l->addLayout(inner); inner->setContentsMargins(0, 0, 0, 0); inner->addLayout(buttonBox); inner->addStretch(); SizeGrip* g = new SizeGrip(this); g->setFixedSize(16, 16); inner->addWidget(g, 0, (Qt::Alignment)(Qt::AlignRight | Qt::AlignBottom)); move(position); } void VariableToolTip::variableCreated(bool hasValue) { m_view->resizeColumns(); if (hasValue) { ActiveToolTip::showToolTip(this, 0.0); } else { close(); } } void VariableToolTip::slotLinkActivated(const QString& link) { Variable* v = m_var; QItemSelection s = m_selection->selection(); if (!s.empty()) { QModelIndex index = s.front().topLeft(); const auto sourceIndex = m_proxy->mapToSource(index); TreeItem *item = m_model->itemForIndex(sourceIndex); if (item) { Variable* v2 = qobject_cast(item); if (v2) v = v2; } } IDebugSession *session = ICore::self()->debugController()->currentSession(); if (session && session->state() != IDebugSession::NotStartedState && session->state() != IDebugSession::EndedState) { if (link == QLatin1String("add_watch")) { session->variableController()->addWatch(v); } else if (link == QLatin1String("add_watchpoint")) { session->variableController()->addWatchpoint(v); } } close(); } void VariableToolTip::slotRangeChanged(int min, int max) { Q_ASSERT(min == 0); Q_UNUSED(min); QRect rect = QApplication::desktop()->screenGeometry(this); if (pos().y() + height() + max*m_itemHeight < rect.bottom()) resize(width(), height() + max*m_itemHeight); else { // Oh, well, I'm sorry, but here's the scrollbar you was // longing to see m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } } #include "variabletooltip.moc" diff --git a/debugger/variable/variabletooltip.h b/debugger/variable/variabletooltip.h index a89337ec5..60d4c7ef6 100644 --- a/debugger/variable/variabletooltip.h +++ b/debugger/variable/variabletooltip.h @@ -1,64 +1,61 @@ /* * KDevelop Debugger Support * * 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. */ #ifndef KDEVPLATFORM_VARIABLETOOLTIP_H #define KDEVPLATFORM_VARIABLETOOLTIP_H #include "../../util/activetooltip.h" #include "../util/treeview.h" -#include - class QItemSelectionModel; class QString; -class QResizeEvent; class QSortFilterProxyModel; namespace KDevelop { class Variable; class TreeModel; class TreeItem; class VariableToolTip : public ActiveToolTip { Q_OBJECT public: VariableToolTip(QWidget* parent, const QPoint& position, const QString& identifier); Variable* variable() const { return m_var; }; private slots: void variableCreated(bool hasValue); void slotLinkActivated(const QString& link); void slotRangeChanged(int min, int max); private: TreeModel* m_model; Variable* m_var; QItemSelectionModel* m_selection; int m_itemHeight; AsyncTreeView* m_view; QSortFilterProxyModel* m_proxy; }; } #endif diff --git a/debugger/variable/variablewidget.cpp b/debugger/variable/variablewidget.cpp index 014851a0a..2a54bbe7f 100644 --- a/debugger/variable/variablewidget.cpp +++ b/debugger/variable/variablewidget.cpp @@ -1,512 +1,513 @@ // ************************************************************************** // 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 #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 nullptr; auto item = selectionModel()->currentIndex().data(TreeModel::ItemRole).value(); 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/debugger/variable/variablewidget.h b/debugger/variable/variablewidget.h index 22ecc9b86..1d7cbeb35 100644 --- a/debugger/variable/variablewidget.h +++ b/debugger/variable/variablewidget.h @@ -1,119 +1,119 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_VARIABLEWIDGET_H #define KDEVPLATFORM_VARIABLEWIDGET_H -#include #include -#include #include #include "../util/treeview.h" #include "variablecollection.h" class KHistoryComboBox; class QSortFilterProxyModel; +class QAction; + namespace KDevelop { class IDebugController; class TreeModel; class VariableTree; class AbstractVariableItem; class KDEVPLATFORMDEBUGGER_EXPORT VariableWidget : public QWidget { Q_OBJECT public: explicit VariableWidget( IDebugController *controller, QWidget *parent=nullptr ); Q_SIGNALS: void requestRaise(); void addWatchVariable(const QString& indent); void evaluateExpression(const QString& indent); public Q_SLOTS: void slotAddWatch(const QString &ident); protected: void showEvent(QShowEvent* e) override; void hideEvent(QHideEvent* e) override; private: VariableTree *m_varTree; KHistoryComboBox *m_watchVarEditor; VariablesRoot *m_variablesRoot; QSortFilterProxyModel *m_proxy; }; /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ class VariableTree : public KDevelop::AsyncTreeView { Q_OBJECT public: VariableTree(IDebugController *controller, VariableWidget *parent, QSortFilterProxyModel *proxy); ~VariableTree() override; VariableCollection* collection() const; private: void setupActions(); void contextMenuEvent(QContextMenuEvent* event) override; Variable *selectedVariable() const; private slots: void changeVariableFormat(int); void watchDelete(); void copyVariableValue(); void stopOnChange(); #if 0 Q_SIGNALS: void toggleWatchpoint(const QString &varName); protected: virtual void contextMenuEvent(QContextMenuEvent* event); virtual void keyPressEvent(QKeyEvent* e); virtual void showEvent(QShowEvent* event); private: // helper functions void handleAddressComputed(const GDBMI::ResultRecord& r); void updateCurrentFrame(); void copyToClipboard(AbstractVariableItem* item); QMenu* activePopup_; QAction* toggleWatch_; #endif private: QAction *m_contextMenuTitle; QMenu *m_formatMenu; QAction *m_watchDelete; QAction *m_copyVariableValue; QAction *m_stopOnChange; QSignalMapper *m_signalMapper; QSortFilterProxyModel *m_proxy; TreeModel *m_model; }; } #endif diff --git a/documentation/documentationview.cpp b/documentation/documentationview.cpp index f40fa8c97..253dacfcf 100644 --- a/documentation/documentationview.cpp +++ b/documentation/documentationview.cpp @@ -1,380 +1,378 @@ /* 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 "documentationview.h" #include #include #include -#include #include -#include #include #include -#include #include - #include + +#include #include #include #include #include #include #include #include "documentationfindwidget.h" #include "debug.h" Q_LOGGING_CATEGORY(DOCUMENTATION, "kdevplatform.documentation") using namespace KDevelop; DocumentationView::DocumentationView(QWidget* parent, ProvidersModel* model) : QWidget(parent), mProvidersModel(model) { setWindowIcon(QIcon::fromTheme(QStringLiteral("documentation"), windowIcon())); setWindowTitle(i18n("Documentation")); setLayout(new QVBoxLayout(this)); layout()->setMargin(0); layout()->setSpacing(0); //TODO: clean this up, simply use addAction as that will create a toolbar automatically // use custom QAction's with createWidget for mProviders and mIdentifiers mActions = new KToolBar(this); // set window title so the QAction from QToolBar::toggleViewAction gets a proper name set mActions->setWindowTitle(i18n("Documentation Tool Bar")); mActions->setToolButtonStyle(Qt::ToolButtonIconOnly); int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize); mActions->setIconSize(QSize(iconSize, iconSize)); mFindDoc = new DocumentationFindWidget; mFindDoc->hide(); mBack = mActions->addAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Back")); mForward = mActions->addAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Forward")); mFind = mActions->addAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Find"), mFindDoc, SLOT(startSearch())); mActions->addSeparator(); mActions->addAction(QIcon::fromTheme(QStringLiteral("go-home")), i18n("Home"), this, SLOT(showHome())); mProviders = new QComboBox(mActions); mIdentifiers = new QLineEdit(mActions); mIdentifiers->setClearButtonEnabled(true); mIdentifiers->setPlaceholderText(i18n("Search...")); mIdentifiers->setCompleter(new QCompleter(mIdentifiers)); // mIdentifiers->completer()->setCompletionMode(QCompleter::UnfilteredPopupCompletion); mIdentifiers->completer()->setCaseSensitivity(Qt::CaseInsensitive); /* vertical size policy should be left to the style. */ mIdentifiers->setSizePolicy(QSizePolicy::Expanding, mIdentifiers->sizePolicy().verticalPolicy()); connect(mIdentifiers->completer(), static_cast(&QCompleter::activated), this, &DocumentationView::changedSelection); connect(mIdentifiers, &QLineEdit::returnPressed, this, &DocumentationView::returnPressed); QWidget::setTabOrder(mProviders, mIdentifiers); mActions->addWidget(mProviders); mActions->addWidget(mIdentifiers); mBack->setEnabled(false); mForward->setEnabled(false); connect(mBack, &QAction::triggered, this, &DocumentationView::browseBack); connect(mForward, &QAction::triggered, this, &DocumentationView::browseForward); mCurrent = mHistory.end(); layout()->addWidget(mActions); layout()->addWidget(new QWidget(this)); layout()->addWidget(mFindDoc); setFocusProxy(mIdentifiers); QMetaObject::invokeMethod(this, "initialize", Qt::QueuedConnection); } void DocumentationView::initialize() { mProviders->setModel(mProvidersModel); connect(mProviders, static_cast(&QComboBox::activated), this, &DocumentationView::changedProvider); foreach (IDocumentationProvider* p, mProvidersModel->providers()) { // can't use new signal/slot syntax here, IDocumentation is not a QObject connect(dynamic_cast(p), SIGNAL(addHistory(KDevelop::IDocumentation::Ptr)), this, SLOT(addHistory(KDevelop::IDocumentation::Ptr))); } connect(mProvidersModel, &ProvidersModel::providersChanged, this, &DocumentationView::emptyHistory); if (mProvidersModel->rowCount() > 0) { changedProvider(0); } } void DocumentationView::browseBack() { --mCurrent; mBack->setEnabled(mCurrent != mHistory.begin()); mForward->setEnabled(true); updateView(); } void DocumentationView::browseForward() { ++mCurrent; mForward->setEnabled(mCurrent+1 != mHistory.end()); mBack->setEnabled(true); updateView(); } void DocumentationView::showHome() { auto prov = mProvidersModel->provider(mProviders->currentIndex()); showDocumentation(prov->homePage()); } void DocumentationView::returnPressed() { // Exit if search text is empty. It's necessary because of empty // line edit text not leads to "empty" completer indexes. if (mIdentifiers->text().isEmpty()) return; // Exit if completer popup has selected item - in this case 'Return' // key press emits QCompleter::activated signal which is already connected. if (mIdentifiers->completer()->popup()->currentIndex().isValid()) return; // If user doesn't select any item in popup we will try to use the first row. if (mIdentifiers->completer()->setCurrentRow(0)) changedSelection(mIdentifiers->completer()->currentIndex()); } void DocumentationView::changedSelection(const QModelIndex& idx) { if (idx.isValid()) { // Skip view update if user try to show already opened documentation mIdentifiers->setText(idx.data(Qt::DisplayRole).toString()); if (mIdentifiers->text() == (*mCurrent)->name()) { return; } IDocumentationProvider* prov = mProvidersModel->provider(mProviders->currentIndex()); auto doc = prov->documentationForIndex(idx); if (doc) { showDocumentation(doc); } } } void DocumentationView::showDocumentation(const IDocumentation::Ptr& doc) { qCDebug(DOCUMENTATION) << "showing" << doc->name(); addHistory(doc); updateView(); } void DocumentationView::addHistory(const IDocumentation::Ptr& doc) { mBack->setEnabled(!mHistory.isEmpty()); mForward->setEnabled(false); // clear all history following the current item, unless we're already // at the end (otherwise this code crashes when history is empty, which // happens when addHistory is first called on startup to add the // homepage) if (mCurrent+1 < mHistory.end()) { mHistory.erase(mCurrent+1, mHistory.end()); } mHistory.append(doc); mCurrent = mHistory.end()-1; // NOTE: we assume an existing widget was used to navigate somewhere // but this history entry actually contains the new info for the // title... this is ugly and should be refactored somehow if (mIdentifiers->completer()->model() == (*mCurrent)->provider()->indexModel()) { mIdentifiers->setText((*mCurrent)->name()); } } void DocumentationView::emptyHistory() { mHistory.clear(); mCurrent = mHistory.end(); mBack->setEnabled(false); mForward->setEnabled(false); if (mProviders->count() > 0) { mProviders->setCurrentIndex(0); changedProvider(0); } } void DocumentationView::updateView() { mProviders->setCurrentIndex(mProvidersModel->rowForProvider((*mCurrent)->provider())); mIdentifiers->completer()->setModel((*mCurrent)->provider()->indexModel()); mIdentifiers->setText((*mCurrent)->name()); mIdentifiers->completer()->setCompletionPrefix((*mCurrent)->name()); QLayoutItem* lastview = layout()->takeAt(1); Q_ASSERT(lastview); if (lastview->widget()->parent() == this) { lastview->widget()->deleteLater(); } delete lastview; mFindDoc->setEnabled(false); QWidget* w = (*mCurrent)->documentationWidget(mFindDoc, this); Q_ASSERT(w); QWidget::setTabOrder(mIdentifiers, w); mFind->setEnabled(mFindDoc->isEnabled()); if (!mFindDoc->isEnabled()) { mFindDoc->hide(); } QLayoutItem* findWidget = layout()->takeAt(1); layout()->addWidget(w); layout()->addItem(findWidget); } void DocumentationView::changedProvider(int row) { mIdentifiers->completer()->setModel(mProvidersModel->provider(row)->indexModel()); mIdentifiers->clear(); showHome(); } ////////////// ProvidersModel ////////////////// ProvidersModel::ProvidersModel(QObject* parent) : QAbstractListModel(parent) , mProviders(ICore::self()->documentationController()->documentationProviders()) { connect(ICore::self()->pluginController(), &IPluginController::unloadingPlugin, this, &ProvidersModel::unloaded); connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, this, &ProvidersModel::loaded); connect(ICore::self()->documentationController(), &IDocumentationController::providersChanged, this, &ProvidersModel::reloadProviders); } void ProvidersModel::reloadProviders() { beginResetModel(); mProviders = ICore::self()->documentationController()->documentationProviders(); std::sort(mProviders.begin(), mProviders.end(), [](const KDevelop::IDocumentationProvider* a, const KDevelop::IDocumentationProvider* b) { return a->name() < b->name(); }); endResetModel(); emit providersChanged(); } QVariant ProvidersModel::data(const QModelIndex& index, int role) const { if (index.row() >= mProviders.count() || index.row() < 0) return QVariant(); QVariant ret; switch (role) { case Qt::DisplayRole: ret = provider(index.row())->name(); break; case Qt::DecorationRole: ret = provider(index.row())->icon(); break; } return ret; } void ProvidersModel::addProvider(IDocumentationProvider* provider) { if (!provider || mProviders.contains(provider)) return; int pos = 0; while (pos < mProviders.size() && mProviders[pos]->name() < provider->name()) ++pos; beginInsertRows(QModelIndex(), pos, pos); mProviders.insert(pos, provider); endInsertRows(); emit providersChanged(); } void ProvidersModel::removeProvider(IDocumentationProvider* provider) { int pos; if (!provider || (pos = mProviders.indexOf(provider)) < 0) return; beginRemoveRows(QModelIndex(), pos, pos); mProviders.removeAt(pos); endRemoveRows(); emit providersChanged(); } void ProvidersModel::unloaded(IPlugin* plugin) { removeProvider(plugin->extension()); IDocumentationProviderProvider* providerProvider = plugin->extension(); if (providerProvider) { foreach(IDocumentationProvider* provider, providerProvider->providers()) removeProvider(provider); } } void ProvidersModel::loaded(IPlugin* plugin) { addProvider(plugin->extension()); IDocumentationProviderProvider* providerProvider = plugin->extension(); if (providerProvider) { foreach(IDocumentationProvider* provider, providerProvider->providers()) addProvider(provider); } } int ProvidersModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : mProviders.count(); } int ProvidersModel::rowForProvider(IDocumentationProvider* provider) { return mProviders.indexOf(provider); } IDocumentationProvider* ProvidersModel::provider(int pos) const { return mProviders[pos]; } QList ProvidersModel::providers() { return mProviders; } diff --git a/documentation/documentationview.h b/documentation/documentationview.h index fbf9ca949..6d2609126 100644 --- a/documentation/documentationview.h +++ b/documentation/documentationview.h @@ -1,101 +1,101 @@ /* 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. */ #ifndef KDEVPLATFORM_DOCUMENTATIONVIEW_H #define KDEVPLATFORM_DOCUMENTATIONVIEW_H #include #include -#include #include #include "documentationexport.h" namespace KDevelop { class IPlugin; class DocumentationFindWidget; } class QModelIndex; class QLineEdit; class ProvidersModel; class QComboBox; +class KToolBar; class KDEVPLATFORMDOCUMENTATION_EXPORT DocumentationView : public QWidget { Q_OBJECT public: DocumentationView(QWidget* parent, ProvidersModel* m); void showDocumentation(const KDevelop::IDocumentation::Ptr& doc); public slots: void initialize(); void addHistory(const KDevelop::IDocumentation::Ptr& doc); void emptyHistory(); void browseForward(); void browseBack(); void changedSelection(const QModelIndex& idx); void changedProvider(int); void showHome(); private: void updateView(); void returnPressed(); KToolBar* mActions; QAction* mForward; QAction* mBack; QAction* mFind; QLineEdit* mIdentifiers; QList< KDevelop::IDocumentation::Ptr > mHistory; QList< KDevelop::IDocumentation::Ptr >::iterator mCurrent; QComboBox* mProviders; ProvidersModel* mProvidersModel; KDevelop::DocumentationFindWidget* mFindDoc; }; class KDEVPLATFORMDOCUMENTATION_EXPORT ProvidersModel : public QAbstractListModel { Q_OBJECT public: explicit ProvidersModel(QObject* parent = nullptr); QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& idx = QModelIndex()) const override; QList providers(); KDevelop::IDocumentationProvider* provider(int pos) const; int rowForProvider(KDevelop::IDocumentationProvider* provider); public slots: void unloaded(KDevelop::IPlugin* p); void loaded(KDevelop::IPlugin* p); void reloadProviders(); private: void addProvider(KDevelop::IDocumentationProvider* provider); void removeProvider(KDevelop::IDocumentationProvider* provider); QList mProviders; signals: void providersChanged(); }; #endif // KDEVPLATFORM_DOCUMENTATIONVIEW_H diff --git a/interfaces/configpage.cpp b/interfaces/configpage.cpp index 898c6b8ee..181fc29ed 100644 --- a/interfaces/configpage.cpp +++ b/interfaces/configpage.cpp @@ -1,129 +1,129 @@ /* * This file is part of KDevelop * Copyright 2014 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) 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 "configpage.h" -#include -#include +#include +#include namespace KDevelop { struct ConfigPagePrivate { explicit ConfigPagePrivate(IPlugin* plugin) : plugin(plugin) {} QScopedPointer configManager; KCoreConfigSkeleton* configSkeleton = nullptr; IPlugin* plugin; }; ConfigPage::ConfigPage(IPlugin* plugin, KCoreConfigSkeleton* config, QWidget* parent) : KTextEditor::ConfigPage(parent) , d(new ConfigPagePrivate(plugin)) { setConfigSkeleton(config); } ConfigPage::~ConfigPage() { } void ConfigPage::apply() { // if d->configManager is null, this method must be overriden Q_ASSERT_X(d->configManager, metaObject()->className(), "Config page does not use a KConfigSkeleton, but doesn't override apply()"); QSignalBlocker blockSigs(this); // we don't want to emit changed() while calling apply() d->configManager->updateSettings(); d->configSkeleton->load(); d->configManager->updateWidgets(); } void ConfigPage::defaults() { // if d->configManager is null, this method must be overriden Q_ASSERT_X(d->configManager, metaObject()->className(), "Config page does not use a KConfigSkeleton, but doesn't override defaults()"); d->configManager->updateWidgetsDefault(); } void ConfigPage::reset() { // if d->configManager is null, this method must be overriden Q_ASSERT_X(d->configManager, metaObject()->className(), "Config page does not use a KConfigSkeleton, but doesn't override reset()"); d->configManager->updateWidgets(); } void ConfigPage::initConfigManager() { if (d->configManager) { d->configManager->addWidget(this); } } KCoreConfigSkeleton* ConfigPage::configSkeleton() const { return d->configSkeleton; } void ConfigPage::setConfigSkeleton(KCoreConfigSkeleton* skel) { if (d->configSkeleton == skel) { return; } d->configSkeleton = skel; if (!skel) { d->configManager.reset(); return; } // create the config dialog manager if it didn't exist or recreate it. // This is needed because the used config skeleton has changed // and no setter for that exists in KConfigDialogManager d->configManager.reset(new KConfigDialogManager(this, d->configSkeleton)); connect(d->configManager.data(), &KConfigDialogManager::widgetModified, this, &ConfigPage::changed); // d->configManager->addWidget(this) must be called from the config dialog, // since the widget tree is probably not complete when calling this function } int ConfigPage::childPages() const { return 0; } ConfigPage* ConfigPage::childPage(int number) { Q_UNUSED(number) return nullptr; } IPlugin* ConfigPage::plugin() const { return d->plugin; } ConfigPage::ConfigPageType ConfigPage::configPageType() const { return DefaultConfigPage; } } // namespace KDevelop diff --git a/interfaces/configpage.h b/interfaces/configpage.h index 8d2ffda80..2a3aaa015 100644 --- a/interfaces/configpage.h +++ b/interfaces/configpage.h @@ -1,112 +1,112 @@ /* * This file is part of KDevelop * Copyright 2014 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) 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 . */ #ifndef KDEVELOP_CONFIGPAGE_H #define KDEVELOP_CONFIGPAGE_H #include -#include -#include #include #include "interfacesexport.h" +class KCoreConfigSkeleton; + namespace KDevelop { class IPlugin; struct ConfigPagePrivate; class KDEVPLATFORMINTERFACES_EXPORT ConfigPage : public KTextEditor::ConfigPage { Q_OBJECT public: /** * Create a new config page * @param plugin the plugin that created this config page * @param config the config skeleton that is used to store the preferences. If you don't use * a K(Core)ConfigSkeleton to save the settings you can also pass null here. * However this means that you will have to manually implement the apply(), defaults() and reset() slots */ explicit ConfigPage(IPlugin* plugin, KCoreConfigSkeleton* config = nullptr, QWidget* parent = nullptr); ~ConfigPage() override; /** * Get the number of subpages of this page * @return The number of child pages or an integer < 1 if there are none. * The default implementation returns zero. */ virtual int childPages() const; /** * @return the child config page for index @p number or @c nullptr if there is none. * The default implementation returns @c nullptr. */ virtual ConfigPage* childPage(int number); enum ConfigPageType { DefaultConfigPage, LanguageConfigPage, ///< A config page that contains language specific settings. This page is appended as a child page to the "Language support" config page. AnalyzerConfigPage, ///< A config page that contains settings for some analyzer. This page is appended as a child page to the "Analyzers" config page. DocumentationConfigPage ///< A config page that contains settings for some documentation plugin. This page is appended as a child page to the "Documentation" config page. }; /** * @return The type of this config page. Default implementaion returns DefaultConfigPageType */ virtual ConfigPageType configPageType() const; /** * @return the plugin that this config page was created by or nullptr if it was not created by a plugin. */ IPlugin* plugin() const; /** * Initializes the KConfigDialogManager. * Must be called explicitly since not all child widgets are available at the end of the constructor. * This is handled automatically by KDevelop::ConfigDialog, subclasses don't need to call this. */ void initConfigManager(); /** * @return the KCoreConfigSkeleton used to store the settings for this page or @c nullptr * if settings are managed differently */ KCoreConfigSkeleton* configSkeleton() const; /** * Sets the config skeleton to @p skel and will create a KConfigDialogManager if needed. * This can be used if the KCoreConfigSkeleton* doesn't exist yet when calling the base class constructor. */ void setConfigSkeleton(KCoreConfigSkeleton* skel); public Q_SLOTS: void apply() override; void defaults() override; void reset() override; private: QScopedPointer d; }; } #endif // KDEVELOP_CONFIGPAGE_H diff --git a/interfaces/context.cpp b/interfaces/context.cpp index 7e80ca391..a3059c4dd 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 #include namespace KDevelop { Context::Context() : d(nullptr) {} Context::~Context() {} bool Context::hasType( int aType ) const { return aType == this->type(); } class FileContextPrivate { public: explicit 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: explicit 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/context.h b/interfaces/context.h index 3a82b3642..efada9e61 100644 --- a/interfaces/context.h +++ b/interfaces/context.h @@ -1,212 +1,211 @@ /* 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 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. */ #ifndef KDEVPLATFORM_CONTEXT_H #define KDEVPLATFORM_CONTEXT_H #include "interfacesexport.h" -#include -#include #include +class QMimeType; template class QList; namespace KDevelop { class ProjectBaseItem; /** Base class for every context. Think of a Context-based class as "useful information associated with a context menu". When a context menu with a certain "context" associated appears, the platform's PluginController requests all plugins to return a list of QActions* they want to add to the context menu and a QString that should be used as the submenu entry. For example, a SVN plugin could add "commit" and "update" actions to the context menu of a document in a submenu called "Subversion". The plugin that originally gets the contextmenu event shouldn't add its own actions directly to the menu but instead use the same mechanism. How to show a context menu from a plugin: -# Create a QMenu in context menu event handler: @code QMenu menu(this); @endcode -# Create a context: @code FileContext context(list). @endcode -# Query for plugins: @code @code QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( context ); @endcode -# Populate the menu: @code ContextMenuExtension::populateMenu(menu, extensions); @endcode -# Show the popup menu: @code menu.exec(mapToGlobal(pos)); @endcode How to fill a context menu from a plugin: -# Implement the @code contextMenuExtension(Context*) @endcode function in your plugin class. -# Depending on the context fill the returned ContextMenuExtension with actions:\n @code ContextMenuExtension ext; if (context->hasType(Context::EditorContext)) { ext.addAction(ContextMenuExtension::EditorGroup, new QAction(...)); } else if context->hasType(Context::FileContext)) { ext.addAction(ContextMenuExtension::FileGroup, new QAction(...)); ... } return ext; @endcode */ class KDEVPLATFORMINTERFACES_EXPORT Context { public: /**Destructor.*/ virtual ~Context(); /**Pre-defined context types. More may be added so it is possible to add custom contexts. We reserve enum values until 1000 (yeah, it is one thousand ) for kdevplatform official context types.*/ enum Type { FileContext, /** urls() const = 0; /**@return The type of this Context, so clients can discriminate between different file contexts.*/ bool hasType( int type ) const; protected: /**Constructor.*/ Context(); private: class ContextPrivate* const d; Q_DISABLE_COPY(Context) }; /** A context for the a list of selected urls. */ class KDEVPLATFORMINTERFACES_EXPORT FileContext : public Context { public: /**Builds the file context using a @ref QList @param urls The list of selected url.*/ explicit FileContext( const QList &urls ); /**Destructor.*/ virtual ~FileContext(); int type() const override; /**@return A reference to the selected URLs.*/ QList urls() const override; private: class FileContextPrivate* const d; Q_DISABLE_COPY(FileContext) }; /** A context for ProjectItem's. */ class KDEVPLATFORMINTERFACES_EXPORT ProjectItemContext : public Context { public: /**Builds the context. @param items The items to build the context from.*/ explicit ProjectItemContext( const QList &items ); /**Destructor.*/ virtual ~ProjectItemContext(); int type() const override; /** * @return The project model items for the selected items. */ QList items() const; private: class ProjectItemContextPrivate* const d; Q_DISABLE_COPY(ProjectItemContext) }; /** * Context menu to open files with custom applications. */ class KDEVPLATFORMINTERFACES_EXPORT OpenWithContext : public Context { public: /** * @p url The files to open. * @p mimeType The mime type of said file. */ OpenWithContext(const QList& urls, const QMimeType& mimeType); virtual ~OpenWithContext(); /** * @return Context::OpenWithContext */ int type() const override; /** * @return The files to open. */ QList urls() const override; /** * @return The mimetype of the url to open. */ QMimeType mimeType() const; private: class OpenWithContextPrivate* const d; Q_DISABLE_COPY(OpenWithContext) }; } #endif diff --git a/interfaces/contextmenuextension.h b/interfaces/contextmenuextension.h index adbb14697..7a45cf502 100644 --- a/interfaces/contextmenuextension.h +++ b/interfaces/contextmenuextension.h @@ -1,108 +1,107 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_CONTEXTMENUEXTENSION_H #define KDEVPLATFORM_CONTEXTMENUEXTENSION_H #include #include #include "interfacesexport.h" class QAction; class QMenu; -template class QList; namespace KDevelop { /** * For documentation on how to use this class, @see interfaces/context.h */ class KDEVPLATFORMINTERFACES_EXPORT ContextMenuExtension { public: /** The menu group containing file actions */ static const QString FileGroup; /** menu group containing refactoring actions */ static const QString RefactorGroup; /** menu group to contain build support actions */ static const QString BuildGroup; /** menu group to contain run actions */ static const QString RunGroup; /** menu group to contain debug actions */ static const QString DebugGroup; /** menu group to contain editing actions */ static const QString EditGroup; /** menu group to contain version control actions */ static const QString VcsGroup; /** menu group to contain project actions */ static const QString ProjectGroup; /** menu group to contain open in embedded editor actions */ static const QString OpenEmbeddedGroup; /** menu group to contain open with external application actions */ static const QString OpenExternalGroup; /** menu group to contain analyze actions */ static const QString AnalyzeGroup; /** The menu group containing navigation actions */ static const QString NavigationGroup; /** menu group that can contain any extension menu. * Actions for this extension will always be at the end * of the menu. Plugins using this should think about * providing a submenu, so the context menu doesn't get cluttered. */ static const QString ExtensionGroup; /** * create new context menu extension object */ ContextMenuExtension(); ~ContextMenuExtension(); ContextMenuExtension( const ContextMenuExtension& rhs ); ContextMenuExtension& operator=( const ContextMenuExtension& rhs ); /** * Add an action to the given menu group * @param group the menu group to which the action should be added * @param action the action to add to the menu group */ void addAction( const QString& group, QAction* action ); /** * Return all actions that are in the menu group * @param group the menu group from which to get the actions * @returns a list of actions for that menu group */ QList actions( const QString& group ) const; /** * Populate a QMenu with the actions in the given context menu extensions. */ static void populateMenu(QMenu* menu, const QList& extensions); private: class ContextMenuExtensionPrivate* const d; }; } #endif diff --git a/interfaces/iassistant.cpp b/interfaces/iassistant.cpp index b9747ffa5..35c3ce43c 100644 --- a/interfaces/iassistant.cpp +++ b/interfaces/iassistant.cpp @@ -1,145 +1,143 @@ /* 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 "iassistant.h" #include "icore.h" #include -#include -#include #include using namespace KDevelop; Q_DECLARE_METATYPE(QExplicitlySharedDataPointer) //BEGIN IAssistant void IAssistant::createActions() { } QAction* IAssistantAction::toKAction(QObject* parent) const { Q_ASSERT(QThread::currentThread() == ICore::self()->thread() && "Actions must be created in the application main thread" "(implement createActions() to create your actions)"); QAction* ret = new QAction(icon(), description(), parent); ret->setToolTip(toolTip()); //Add the data as a QExplicitlySharedDataPointer to the action, so this assistant stays alive at least as long as the QAction ret->setData(QVariant::fromValue(QExplicitlySharedDataPointer(const_cast(this)))); connect(ret, &QAction::triggered, this, &IAssistantAction::execute); return ret; } IAssistant::~IAssistant() { } IAssistantAction::IAssistantAction() : QObject() , KSharedObject(*(QObject*)this) { } IAssistantAction::~IAssistantAction() { } QIcon IAssistantAction::icon() const { return QIcon(); } QString IAssistantAction::toolTip() const { return QString(); } //END IAssistantAction //BEGIN AssistantLabelAction AssistantLabelAction::AssistantLabelAction(const QString& description) : m_description(description) { } QString AssistantLabelAction::description() const { return m_description; } void AssistantLabelAction::execute() { // do nothing } QAction* AssistantLabelAction::toKAction(QObject* parent) const { Q_UNUSED(parent); return nullptr; } //END AssistantLabelAction //BEGIN: IAssistant IAssistant::IAssistant() : KSharedObject(*(QObject*)this) { } QIcon IAssistant::icon() const { return QIcon(); } QString IAssistant::title() const { return QString(); } void IAssistant::doHide() { emit hide(); } QList< IAssistantAction::Ptr > IAssistant::actions() const { if ( m_actions.isEmpty() ) { const_cast(this)->createActions(); } return m_actions; } void IAssistant::addAction(const IAssistantAction::Ptr& action) { m_actions << action; } void IAssistant::clearActions() { m_actions.clear(); } //END IAssistant diff --git a/interfaces/iassistant.h b/interfaces/iassistant.h index 24fbb6f24..0e12dabd1 100644 --- a/interfaces/iassistant.h +++ b/interfaces/iassistant.h @@ -1,151 +1,150 @@ /* 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. */ #ifndef KDEVPLATFORM_IASSISTANT_H #define KDEVPLATFORM_IASSISTANT_H -#include #include #include #include "interfacesexport.h" #include class QAction; namespace KDevelop { ///Represents a single assistant action. ///Subclass it to create your own actions. class KDEVPLATFORMINTERFACES_EXPORT IAssistantAction : public QObject, public KSharedObject { Q_OBJECT public: IAssistantAction(); typedef QExplicitlySharedDataPointer Ptr; ~IAssistantAction() override; ///Creates a QAction that represents this exact assistant action. ///The caller owns the action, and is responsible for deleting it. virtual QAction* toKAction(QObject* parent = nullptr) const; ///Should return a short description of the action. ///It may contain simple HTML formatting. ///Must be very short, so it nicely fits into the assistant popups. virtual QString description() const = 0; ///May return additional tooltip hover information. ///The default implementation returns an empty string. virtual QString toolTip() const; ///May return an icon for this action. ///The default implementation returns an invalid icon, which means that no icon is shown. virtual QIcon icon() const; public Q_SLOTS: /** * Execute this action. * * NOTE: Implementations should properly emit executed(this) after being executed. */ virtual void execute() = 0; Q_SIGNALS: /** * Gets emitted when this action was executed. */ void executed(IAssistantAction* action); }; /** * A fake action that only shows a label. */ class KDEVPLATFORMINTERFACES_EXPORT AssistantLabelAction : public IAssistantAction { Q_OBJECT public: /** * @p description The label to show. */ explicit AssistantLabelAction(const QString& description); /** * @return the label contents. */ QString description() const override; /** * The label cannot be executed. */ void execute() override; /** * No action is returned. */ QAction* toKAction(QObject* parent = nullptr) const override; private: QString m_description; }; ///Represents a single assistant popup. ///Subclass it to create your own assistants. class KDEVPLATFORMINTERFACES_EXPORT IAssistant : public QObject, public KSharedObject { Q_OBJECT public: IAssistant(); ~IAssistant() override; typedef QExplicitlySharedDataPointer Ptr; ///Returns the stored list of actions QList actions() const; ///Implement this and have it create the actions for your assistant. ///It will only be called if the assistant is displayed, which saves ///memory compared to creating the actions right away. ///Default implementation does nothing. virtual void createActions(); ///Adds the given action to the list of actions. ///Does not emit actionsChanged(), you have to do that when you're ready. virtual void addAction(const IAssistantAction::Ptr& action); ///Clears the stored list of actions. ///Does not emit actionsChanged(), you have to do that when you're ready. virtual void clearActions(); ///May return an icon for this assistant virtual QIcon icon() const; ///May return the title of this assistant ///The text may be html formatted. If it can be confused with HTML, ///use Qt::escape(..). virtual QString title() const; public Q_SLOTS: ///Emits hide(), which causes this assistant to be hidden virtual void doHide(); Q_SIGNALS: ///Can be emitted by the assistant when it should be hidden void hide(); ///Can be emitted by the assistant when its actions have changed and should be re-read void actionsChanged(); private: QList m_actions; }; } #endif // KDEVPLATFORM_IASSISTANT_H diff --git a/interfaces/ibuddydocumentfinder.h b/interfaces/ibuddydocumentfinder.h index 621d02977..ea8755e98 100644 --- a/interfaces/ibuddydocumentfinder.h +++ b/interfaces/ibuddydocumentfinder.h @@ -1,143 +1,142 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_IBUDDYDOCUMENTFINDER_H #define KDEVPLATFORM_IBUDDYDOCUMENTFINDER_H -#include #include #include #include "interfacesexport.h" namespace KDevelop { /** * @short Implement this to add buddy document functionality to your language plugin. * * It enables the DocumentController (shell) to find related documents * (normally declaration/definition files, like foo.h and foo.cpp). * The DocumentController will position tabs of buddy documents next to * each other if the option UISettings.TabBarArrangeBuddies is 1. * The implementation also determines the order of the tabs in the tabbar. * * For finding a buddy document, the DocumentController addresses the * IBuddyDocumentFinder object that is registerd for this mimetype: * * This class provides static "registry" functions to handle a specific buddy finding * method (i.e. an object of class @class IBuddyDocumentFinder) for different * mimetypes. Like this, you can have for example an IBuddyDocumentFinder * for C++ files which considers foo.cpp and foo.h as buddies, and an * IBuddyDocumentFinder for Ada files which considers bar.adb and bar.ads as * buddies. * Like this, the concept of buddy documents is extensible for every language * for which support is added to KDevelop. * * If you want to add @class IBuddyDocumentFinder functionality to your language * plugin, your main class will inherit from IBuddyDocumentFinder (or have an * attribute of this type). Then in your constructor, call * @code addFinder(mimetype, this) @endcode * for each mimetype that your plugin supports. It is no problem to register the * same IBuddyDocumentFinder for several mimetypes. * * In the same way, in your destructor, you'll call * @code removeFinder(mimetype, this) @endcode * for each supported mimetype, to avoid that your IBuddyDocumentFinder object * is used beyond its lifetime. * * After you have registered it, your IBuddyDocumentFinder implementation will be found by * @code finderForMimeType(mimetype) @endcode * For example, the shell's DocumentController calls this function with idoc->mimeType() * in order to find a buddy document of a particular IDocument *idoc. */ class KDEVPLATFORMINTERFACES_EXPORT IBuddyDocumentFinder { public: virtual ~IBuddyDocumentFinder(){} /** * Called to determine if two document URLs should be considered as related. * * @return true, if the two documents are buddies. * For example, a C++ implementation would return true for * areBuddies(QUrl::fromLocalFile("...../foo.h"), QUrl::fromLocalFile("...../foo.cpp")). */ virtual bool areBuddies(const QUrl& url1, const QUrl& url2) = 0; /** * @brief Called to determine the order of two documents in the tabbar. * * Example: a C++ implementation that wants to place the tab of the .h * file left of the .cpp tab must return true for
* buddyOrder(QUrl::fromLocalFile("foo.h"), QUrl::fromLocalFile("foo.cpp"))
* and false for
* buddyOrder(QUrl::fromLocalFile("foo.cpp"), QUrl::fromLocalFile("foo.h")) * * accepts two documents which are buddies, * this means areBuddies(url1,url2) returned true. * @return true if url1's tab should be placed left of url2's tab and * false for the inverse. */ virtual bool buddyOrder(const QUrl& url1, const QUrl& url2) = 0; /** * Returns a list of QUrls of potential buddies of the document * provided by @p url. * * The urls are potential buddies and it is not ensured that the files * really exist. * * @returns list of potential buddy documents or an empty list * if non are available. */ virtual QVector getPotentialBuddies(const QUrl& url) const = 0; /** * @brief Registers a IBuddyDocumentFinder object for a mimetype. * * @details To be called in the constructor of language plugins. * Afterwards, finderForMimeType( @p mimeType ) will return @p finder , * as long as the entry is not overwritten by another call to addFinder. */ static void addFinder(const QString& mimeType, IBuddyDocumentFinder* finder); /** * @brief Un-registers a IBuddyDocumentFinder object for a mimetype. * * @details To be called in the destructor of language plugins. * Afterwards, finderForMimeType( @p mimeType ) will return 0, until a new * entry for this mimetype is created by addFinder(). */ static void removeFinder(const QString& mimeType); /** * Returns the registered IBuddyDocumentFinder for this mimetype, or 0. * * Used in the DocumentController (shell). */ static IBuddyDocumentFinder* finderForMimeType(const QString& mimeType); private: struct Private; }; } #endif diff --git a/interfaces/icompletionsettings.cpp b/interfaces/icompletionsettings.cpp index f13537143..48591d60a 100644 --- a/interfaces/icompletionsettings.cpp +++ b/interfaces/icompletionsettings.cpp @@ -1,28 +1,26 @@ /*************************************************************************** * 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 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 "icompletionsettings.h" -#include - namespace KDevelop { ICompletionSettings::~ICompletionSettings() { } } diff --git a/interfaces/icore.h b/interfaces/icore.h index 37eac915a..d647bfcc9 100644 --- a/interfaces/icore.h +++ b/interfaces/icore.h @@ -1,154 +1,149 @@ /* 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. */ #ifndef KDEVPLATFORM_ICORE_H #define KDEVPLATFORM_ICORE_H #include #include "interfacesexport.h" #include "isessionlock.h" class KAboutData; -namespace KParts -{ -class PartManager; -} - /** * The KDevelop namespace contains all classes provided by the KDevelop * platform libraries. */ namespace KDevelop { class IUiController; class IPluginController; class IProjectController; class ILanguageController; class IDocumentController; class ISessionController; class IRunController; class ISourceFormatterController; class ISession; class ISelectionController; class IDocumentationController; class IDebugController; class IPartController; class IDashboardController; class ITestController; /** * ICore is the container class for all the various objects in use by * KDevelop. If access is needed to a particular controller, then this class * should be used. * * ICore can provide the user with instances of the following things: * - the main window(s) * - the document controller(s) * - the plugin controller * - the project controller * - the language controller * - the KPart manager * * When an object is provided to ICore so it can be used later, ICore * will take ownership of the object and upon application shutdown will take * responsibility for deleting the objects stored by ICore. */ class KDEVPLATFORMINTERFACES_EXPORT ICore: public QObject { Q_OBJECT public: ~ICore() override; /** @return the static ICore instance */ static ICore *self(); /** @return ui controller */ Q_SCRIPTABLE virtual KDevelop::IUiController *uiController() = 0; /** @return plugin controller */ Q_SCRIPTABLE virtual KDevelop::IPluginController *pluginController() = 0; /** @return project controller */ Q_SCRIPTABLE virtual KDevelop::IProjectController *projectController() = 0; /** @return language controller */ Q_SCRIPTABLE virtual KDevelop::ILanguageController *languageController() = 0; /** @return part manager */ Q_SCRIPTABLE virtual KDevelop::IPartController *partController() = 0; /** @return document controller */ Q_SCRIPTABLE virtual KDevelop::IDocumentController *documentController() = 0; /** @return run controller */ Q_SCRIPTABLE virtual KDevelop::IRunController *runController() = 0; /** @return the active session */ Q_SCRIPTABLE virtual KDevelop::ISession *activeSession() = 0; /** @return the session lock for the active session */ Q_SCRIPTABLE virtual KDevelop::ISessionLock::Ptr activeSessionLock() = 0; /** @return the sourceformatter controller */ Q_SCRIPTABLE virtual KDevelop::ISourceFormatterController *sourceFormatterController() = 0; /** @return the selection controller */ Q_SCRIPTABLE virtual KDevelop::ISelectionController* selectionController() = 0; /** @return the documentation controller */ Q_SCRIPTABLE virtual KDevelop::IDocumentationController* documentationController() = 0; /** @return the debug controller */ Q_SCRIPTABLE virtual KDevelop::IDebugController* debugController() = 0; /** @return the test controller */ Q_SCRIPTABLE virtual KDevelop::ITestController* testController() = 0; /** @return the about data of the framework, different from the main about data which is created by the application */ virtual KAboutData aboutData() const = 0; /** @return true if the application is currently being shut down */ virtual bool shuttingDown() const = 0; Q_SIGNALS: /** Emitted when the initialization of the core components has been completed */ void initializationCompleted(); /** * Emitted immediately before tearing down the session and UI. Useful when performing any last minute * preparations such as saving settings. */ void aboutToShutdown(); /** * Emitted when the teardown of the core components has been completed. */ void shutdownCompleted(); protected: explicit ICore(QObject *parent = nullptr); static ICore *m_self; }; } #endif diff --git a/interfaces/idocumentation.cpp b/interfaces/idocumentation.cpp index 6806ea181..00f48ee26 100644 --- a/interfaces/idocumentation.cpp +++ b/interfaces/idocumentation.cpp @@ -1,34 +1,33 @@ /* This file is part of KDevelop 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 "idocumentation.h" -#include namespace KDevelop { IDocumentation::IDocumentation() : KSharedObject(*(QObject*)this) {} IDocumentation::~IDocumentation() {} } diff --git a/interfaces/idocumentationprovider.h b/interfaces/idocumentationprovider.h index a5c5b987e..8627ac70f 100644 --- a/interfaces/idocumentationprovider.h +++ b/interfaces/idocumentationprovider.h @@ -1,66 +1,65 @@ /* This file is part of KDevelop 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. */ #ifndef KDEVPLATFORM_IDOCUMENTATIONPROVIDER_H #define KDEVPLATFORM_IDOCUMENTATIONPROVIDER_H -#include #include "interfacesexport.h" #include "idocumentation.h" class QIcon; class QModelIndex; class QAbstractListModel; namespace KDevelop { class Declaration; class KDEVPLATFORMINTERFACES_EXPORT IDocumentationProvider { public: virtual ~IDocumentationProvider(); /** @returns an IDocument instance for the specified declaration or a null pointer if none could be found.*/ virtual IDocumentation::Ptr documentationForDeclaration(KDevelop::Declaration* declaration) const = 0; /** @returns an instance of an interface to create an index for all the items provided by this class. */ virtual QAbstractListModel* indexModel() const = 0; /** @returns the documentation information related to the index in the model. */ virtual IDocumentation::Ptr documentationForIndex(const QModelIndex& idx) const = 0; /** @returns some icon associated to the provider. */ virtual QIcon icon() const = 0; /** @returns a name to identify the provider to the user. */ virtual QString name() const = 0; /** @returns a documentation item where we can show some home page information such a context index. */ virtual IDocumentation::Ptr homePage() const = 0; Q_SIGNALS: virtual void addHistory(const KDevelop::IDocumentation::Ptr& doc) const = 0; }; } Q_DECLARE_INTERFACE( KDevelop::IDocumentationProvider, "org.kdevelop.IDocumentationProvider") #endif diff --git a/interfaces/idocumentationproviderprovider.h b/interfaces/idocumentationproviderprovider.h index 0820aceec..ceff3e375 100644 --- a/interfaces/idocumentationproviderprovider.h +++ b/interfaces/idocumentationproviderprovider.h @@ -1,48 +1,47 @@ /* This file is part of KDevelop 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 as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_IDOCUMENTATIONPROVIDERPROVIDER_H #define KDEVPLATFORM_IDOCUMENTATIONPROVIDERPROVIDER_H -#include #include #include "interfacesexport.h" namespace KDevelop { class IDocumentationProvider; class KDEVPLATFORMINTERFACES_EXPORT IDocumentationProviderProvider { public: virtual ~IDocumentationProviderProvider(); /** @returns a list of providers provided by this class. */ virtual QList providers() = 0; Q_SIGNALS: virtual void changedProvidersList() const=0; }; } Q_DECLARE_INTERFACE( KDevelop::IDocumentationProviderProvider, "org.kdevelop.IDocumentationProviderProvider") #endif diff --git a/interfaces/ipartcontroller.cpp b/interfaces/ipartcontroller.cpp index 7edc59e89..89119efb9 100644 --- a/interfaces/ipartcontroller.cpp +++ b/interfaces/ipartcontroller.cpp @@ -1,98 +1,96 @@ /*************************************************************************** * 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, 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 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 = nullptr; for ( uint i = 0; i < length; ++i ) { KPluginFactory* editorFactory = findPartFactory( mimetype, QString::fromLatin1(services[ i ]), prefName ); if ( editorFactory ) { part = editorFactory->create( nullptr, this ); break; } } return part; } } diff --git a/interfaces/ipartcontroller.h b/interfaces/ipartcontroller.h index 22fa84235..c5a26f4a2 100644 --- a/interfaces/ipartcontroller.h +++ b/interfaces/ipartcontroller.h @@ -1,61 +1,56 @@ /*************************************************************************** * 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 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_IPARTCONTROLLER_H #define KDEVPLATFORM_IPARTCONTROLLER_H #include #include "interfacesexport.h" class KPluginFactory; -namespace KParts -{ -class Factory; -} - namespace KTextEditor { class Editor; } namespace KDevelop { class ICore; class KDEVPLATFORMINTERFACES_EXPORT IPartController : public KParts::PartManager { Q_OBJECT public: explicit IPartController( QWidget* parent ); static KPluginFactory* findPartFactory( const QString& mimetype, const QString& parttype, const QString& preferredName = QString() ); KParts::Part* createPart( const QString& mimetype, const QString& prefName = QString() ); /** * Returns the global editor instance. */ virtual KTextEditor::Editor* editorPart() const = 0; }; } #endif diff --git a/interfaces/iplugin.h b/interfaces/iplugin.h index 936afd6fb..f2d870da2 100644 --- a/interfaces/iplugin.h +++ b/interfaces/iplugin.h @@ -1,281 +1,280 @@ /* This file is part of the KDE project Copyright 1999-2001 Bernd Gehrmann Copyright 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. */ #ifndef KDEVPLATFORM_IPLUGIN_H #define KDEVPLATFORM_IPLUGIN_H #include -#include #include #include "interfacesexport.h" namespace Sublime { class MainWindow; } /** * This macro adds an extension interface to register with the extension manager * Call this macro for all interfaces your plugin implements in its constructor */ #define KDEV_USE_EXTENSION_INTERFACE(Extension) \ _Pragma("message(\"Using deprecated function: KDEV_USE_EXTENSION_INTERFACE. Just remove the use of it.\")") namespace KDevelop { class ICore; class ConfigPage; class Context; class ContextMenuExtension; struct ProjectConfigOptions; /** * The base class for all KDevelop plugins. * * Plugin is a component which is loaded into KDevelop shell at startup or by * request. Each plugin should have corresponding .desktop file with a * description. The .desktop file template looks like: * @code * [Desktop Entry] * Type=Service * Exec=blubb * Name= * GenericName= * Comment= * Icon= * ServiceTypes=KDevelop/Plugin * X-KDE-Library= * X-KDE-PluginInfo-Name= * X-KDE-PluginInfo-Author= * X-KDE-PluginInfo-Version= * X-KDE-PluginInfo-License= * X-KDE-PluginInfo-Category= * X-KDevelop-Version= * X-KDevelop-Category= * X-KDevelop-Mode=GUI * X-KDevelop-LoadMode= * X-KDevelop-Languages= * X-KDevelop-SupportedMimeTypes= * X-KDevelop-Interfaces= * X-KDevelop-IOptional= * X-KDevelop-IRequired= * @endcode * Description of parameters in .desktop file: * - Name is a translatable name of a plugin, it is used in the plugin * selection list (required); * - GenericName is a translatable generic name of a plugin, it should * describe in general what the plugin can do (required); * - Comment is a short description about the plugin (optional); * - Icon is a plugin icon (preferred); * X-KDE-librarythis is the name of the .so file to load for this plugin (required); * - X-KDE-PluginInfo-Name is a non-translatable user-readable plugin * identifier used in KTrader queries (required); * - X-KDE-PluginInfo-Author is a non-translateable name of the plugin * author (optional); * - X-KDE-PluginInfo-Version is version number of the plugin (optional); * - X-KDE-PluginInfo-License is a license (optional). can be: GPL, * LGPL, BSD, Artistic, QPL or Custom. If this property is not set, license is * considered as unknown; * - X-KDE-PluginInfo-Category is used to categorize plugins (optional). can be: * Core, Project Management, Version Control, Utilities, Documentation, * Language Support, Debugging, Other * If this property is not set, "Other" is assumed * - X-KDevelop-Version is the KDevPlatform API version this plugin * works with (required); * - X-KDevelop-Interfaces is a list of extension interfaces that this * plugin implements (optional); * - X-KDevelop-IRequired is a list of extension interfaces that this * plugin depends on (optional); A list entry can also be of the form @c interface@pluginname, * in which case a plugin of the given name is required which implements the interface. * - X-KDevelop-IOptional is a list of extension interfaces that this * plugin will use if they are available (optional); * - X-KDevelop-Languages is a list of the names of the languages the plugin provides * support for (optional); * - X-KDevelop-SupportedMimeTypes is a list of mimetypes that the * language-parser in this plugin supports (optional); * - X-KDevelop-Mode is either GUI or NoGUI to indicate whether a plugin can run * with the GUI components loaded or not (required); * - X-KDevelop-Category is a scope of a plugin (see below for * explanation) (required); * - X-KDevelop-LoadMode can be set to AlwaysOn in which case the plugin will * never be unloaded even if requested via the API. (optional); * * Plugin scope can be either: * - Global * - Project * . * Global plugins are plugins which require only the shell to be loaded and do not operate on * the Project interface and/or do not use project wide information.\n * Core plugins are global plugins which offer some important "core" functionality and thus * are not selectable by user in plugin configuration pages.\n * Project plugins require a project to be loaded and are usually loaded/unloaded along with * the project. * If your plugin uses the Project interface and/or operates on project-related * information then this is a project plugin. * * * @sa Core class documentation for information about features available to * plugins from shell applications. */ class KDEVPLATFORMINTERFACES_EXPORT IPlugin: public QObject, public KXMLGUIClient { Q_OBJECT public: /**Constructs a plugin. * @param componentName The component name for this plugin. * @param parent The parent object for the plugin. */ IPlugin(const QString &componentName, QObject *parent); /**Destructs a plugin.*/ ~IPlugin() override; /** * Signal the plugin that it should cleanup since it will be unloaded soon. */ Q_SCRIPTABLE virtual void unload(); /** * Provides access to the ICore implementation */ Q_SCRIPTABLE ICore *core() const; /** * Convenience API to access an interface inherited by this plugin * * @return Instance to the specified interface, or nullptr */ template inline Extension* extension() { return qobject_cast(this); } /** * Ask the plugin for a ContextActionContainer, which contains actions * that will be merged into the context menu. * @param context the context describing where the context menu was requested * @returns a container describing which actions to merge into which context menu part */ virtual ContextMenuExtension contextMenuExtension( KDevelop::Context* context ); /** * Can create a new KXMLGUIClient, and set it up correctly with all the plugins per-window GUI actions. * * The caller owns the created object, including all contained actions. The object is destroyed as soon as * the mainwindow is closed. * * The default implementation calls the convenience function @ref createActionsForMainWindow and uses it to fill a custom KXMLGUIClient. * * Only override this version if you need more specific features of KXMLGUIClient, or other special per-window handling. * * @param window The main window the actions are created for */ virtual KXMLGUIClient* createGUIForMainWindow( Sublime::MainWindow* window ); /** * This function allows allows setting up plugin actions conveniently. Unless createGUIForMainWindow was overridden, * this is called for each new mainwindow, and the plugin should add its actions to @p actions, and write its KXMLGui xml file * into @p xmlFile. * * @param window The main window the actions are created for * @param xmlFile Change this to the xml file that needed for merging the actions into the GUI * @param actions Add your actions here. A new set of actions has to be created for each mainwindow. */ virtual void createActionsForMainWindow( Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions ); /** * This function is necessary because there is no proper way to signal errors from plugin constructor. * @returns True if the plugin has encountered an error (and therefore has an error description), * false otherwise. */ bool hasError() const; /** * Description of the last encountered error, of an empty string if none. */ QString errorDescription() const; /** * Set a @p description of the errror encountered. An empty error * description implies no error in the plugin. */ void setErrorDescription(QString const& description); /** * Get the global config page with the \p number, config pages from 0 to * configPages()-1 are available if configPages() > 0. * * @param number index of config page * @param parent parent widget for config page * @return newly created config page or NULL, if the number is out of bounds, default implementation returns NULL. * This config page should inherit from ProjectConfigPage, but it is not a strict requirement. * The default implementation returns @c nullptr. * @see perProjectConfigPages(), ProjectConfigPage */ virtual ConfigPage* configPage(int number, QWidget *parent); /** * Get the number of available config pages for global settings. * @return number of global config pages. The default implementation returns zero. * @see configPage() */ virtual int configPages() const; /** * Get the number of available config pages for per project settings. * @return number of per project config pages. The default implementation returns zero. * @see perProjectConfigPage() */ virtual int perProjectConfigPages() const; /** * Get the per project config page with the \p number, config pages from 0 to * perProjectConfigPages()-1 are available if perProjectConfigPages() > 0. * * @param number index of config page * @param options The options used to initialize the ProjectConfigPage * @param parent parent widget for config page * @return newly created config page or NULL, if the number is out of bounds, default implementation returns NULL. * This config page should inherit from ProjectConfigPage, but it is not a strict requirement. * The default implementation returns @c nullptr. * @see perProjectConfigPages(), ProjectConfigPage */ virtual ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent); protected: /** * Initialize the XML GUI State. */ virtual void initializeGuiState(); private: friend class IPluginPrivate; class IPluginPrivate* const d; }; } #endif diff --git a/interfaces/iplugincontroller.h b/interfaces/iplugincontroller.h index 5039548f5..e4eb96d8f 100644 --- a/interfaces/iplugincontroller.h +++ b/interfaces/iplugincontroller.h @@ -1,199 +1,200 @@ /* This file is part of the KDE project Copyright 2004, 2007 Alexander Dymo Copyright 2006 Matt Rogers Copyright 2007 Andreas Pakulat Based on code from Kopete Copyright 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. */ #ifndef KDEVPLATFORM_IPLUGINCONTROLLER_H #define KDEVPLATFORM_IPLUGINCONTROLLER_H #include #include +#include #include #include "iplugin.h" #include "interfacesexport.h" class QExtensionManager; namespace KDevelop { class ProfileEngine; /** * The KDevelop plugin controller. * The Plugin controller is responsible for querying, loading and unloading * available plugins. * * Most of the time if you want to get at a plugin you should be using * extensionForPlugin with the extension interface name. If you need to get at * the actual \c IPlugin* pointer to connect signals/slots you should use * \c pluginForExtension() and then the IPlugin's extension member function to get * at the extension interface if necessary. * * If you have the need to load a specific plugin for a given extension both * functions have an optional second parameter that allows one to specify the * name of the plugin as declared in the \c .desktop file under the * \c X-KDE-PluginInfo-Name property. This should be used only very seldomly in * real code and is mostly meant for testing and for implementation in the * shell as it makes the code dependent on the plugin name which may change and * also the actual plugin implementation so users cannot exchange one plugin * with another also implementing the same interface. * */ class KDEVPLATFORMINTERFACES_EXPORT IPluginController : public QObject { Q_OBJECT public: /** * \brief Indicates the plugin type * This is used to determine how the plugin is loaded */ enum PluginType { Global = 0, ///< Indicates that the plugin is loaded at startup Project ///< Indicates that the plugin is loaded with the first opened project }; explicit IPluginController( QObject* parent = nullptr ); ~IPluginController() override; /** * Get the plugin info for a loaded plugin */ virtual KPluginMetaData pluginInfo(const IPlugin*) const = 0; /** * Find the KPluginMetaData structure for the given @p pluginId. */ virtual KPluginMetaData infoForPluginId(const QString &pluginId) const = 0; /** * Get a list of currently loaded plugins */ Q_SCRIPTABLE virtual QList loadedPlugins() const = 0; /** * @brief Unloads the plugin specified by @p plugin * * @param plugin The name of the plugin as specified by the * X-KDE-PluginInfo-Name key of the .desktop file for the plugin */ Q_SCRIPTABLE virtual bool unloadPlugin( const QString & plugin ) = 0; /** * @brief Loads the plugin specified by @p pluginName * * @param pluginName the name of the plugin, as given in the X-KDE-PluginInfo-Name property * @returns a pointer to the plugin instance or 0 */ Q_SCRIPTABLE virtual IPlugin* loadPlugin( const QString & pluginName ) = 0; /** * Retrieve a plugin which supports the given extension interface. * * All already loaded plugins will be queried and the first one to support the extension interface * will be returned. Any plugin can be an extension, only the "ServiceTypes=..." entry is * required in .desktop file for that plugin. * * If no already-loaded plugin was found, we try to load a plugin for the given extension. * * If no plugin was found, a nullptr will be returned. * * @param extension The extension interface. Can be empty if you want to find a plugin by name or other constraint. * @param pluginName The name of the plugin to load if multiple plugins for the extension exist, corresponds to the X-KDE-PluginInfo-Name * @param constraints A map of constraints on other plugin info properties. * @return A KDevelop extension plugin for given service type or 0 if no plugin supports it */ Q_SCRIPTABLE virtual IPlugin *pluginForExtension(const QString &extension, const QString& pluginName = {}, const QVariantMap& constraints = QVariantMap()) = 0; /** * Retrieve a list of plugins which supports the given extension interface. * All already loaded plugins will be queried and the first one to support the extension interface * will be returned. Any plugin can be an extension, only the "ServiceTypes=..." entry is * required in .desktop file for that plugin. * @param extension The extension interface * @param constraints A map of constraints on other plugin info properties. * @return A KDevelop extension plugin for given service type or 0 if no plugin supports it */ virtual QList allPluginsForExtension(const QString &extension, const QVariantMap& constraints = QVariantMap()) = 0; /** * Retrieve the plugin which supports given extension interface and * returns a pointer to the extension interface. * * All already loaded plugins will be queried and the first one to support the extension interface * will be returned. Any plugin can be an extension, only "ServiceTypes=..." entry is * required in .desktop file for that plugin. * @param extension The extension interface * @param pluginName The name of the plugin to load if multiple plugins for the extension exist, corresponds to the X-KDE-PluginInfo-Name * @return Pointer to the extension interface or 0 if no plugin supports it */ template Extension* extensionForPlugin( const QString &extension = {}, const QString &pluginName = {}) { QString ext; if( extension.isEmpty() ) { ext = qobject_interface_iid(); } else { ext = extension; } IPlugin *plugin = pluginForExtension(ext, pluginName); if (plugin) { return plugin->extension(); } return nullptr; } /** * Query for plugin information on KDevelop plugins implementing the given extension. * * The service version is checked for automatically and the only serviceType * searched for is "KDevelop/Plugin" * * @param extension The extension that should be implemented by the plugin, i.e. listed in X-KDevelop-Interfaces. * @param constraints A map of constraints on other plugin info properties. * @return The list of plugin offers. */ virtual QVector queryExtensionPlugins(const QString &extension, const QVariantMap& constraints = QVariantMap()) const = 0; virtual QList queryPluginsForContextMenuExtensions( KDevelop::Context* context ) const = 0; Q_SIGNALS: void loadingPlugin( const QString& ); void pluginLoaded( KDevelop::IPlugin* ); void unloadingPlugin( KDevelop::IPlugin* ); /** * This signal is emitted whenever a plugin is unloaded. * @note: that you shouldn't use the pointer anymore * except for comparing it against against other pointers. The plugin instance can already have been completely * deleted when this signal is emitted. */ void pluginUnloaded( KDevelop::IPlugin* ); private: friend class IPlugin; }; } #endif diff --git a/interfaces/iproblem.h b/interfaces/iproblem.h index b39dbb13f..33d172a64 100644 --- a/interfaces/iproblem.h +++ b/interfaces/iproblem.h @@ -1,148 +1,147 @@ /* * 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. */ #ifndef IPROBLEM_H #define IPROBLEM_H #include -#include #include #include namespace KDevelop { class IAssistant; /// Interface for the Problem classes class KDEVPLATFORMLANGUAGE_EXPORT IProblem : public QSharedData { public: typedef QExplicitlySharedDataPointer Ptr; /// The source of the problem. That is which tool / which part found this problem. enum Source { Unknown, Disk, Preprocessor, Lexer, Parser, DUChainBuilder, SemanticAnalysis, ToDo, Plugin /// The source is a problem checker plugin }; /// Severity of the problem. That is, how serious is the found problem. enum Severity { NoSeverity = 0, Error = 1, Warning = 2, Hint = 4 }; Q_DECLARE_FLAGS(Severities, Severity) /// Final location mode of the problem. Used during highlighting. enum FinalLocationMode { /// Location range used "As Is" Range = 0, /// Location range used to highlight whole line. /// /// Mode applied only if location range is wholly contained within one line WholeLine, /// Location range used to highlight only trimmed part of the line. /// For example for the line: " int x = 0; \t" /// only "int x = 0;" will be highlighted. /// /// Mode applied only if location range is wholly contained within one line TrimmedLine }; IProblem(){} virtual ~IProblem(){} /// Returns the source of the problem virtual Source source() const = 0; /// Sets the source of the problem virtual void setSource(Source source) = 0; /// Returns a string containing the source of the problem virtual QString sourceString() const = 0; /// Returns the location of the problem (path, line, column) virtual KDevelop::DocumentRange finalLocation() const = 0; /// Sets the location of the problem (path, line, column) virtual void setFinalLocation(const KDevelop::DocumentRange& location) = 0; /// Returns the final location mode of the problem virtual FinalLocationMode finalLocationMode() const = 0; /// Sets the final location mode of the problem virtual void setFinalLocationMode(FinalLocationMode mode) = 0; /// Returns the short description of the problem. virtual QString description() const = 0; /// Sets the short description of the problem virtual void setDescription(const QString& description) = 0; /// Returns the detailed explanation of the problem. virtual QString explanation() const = 0; /// Sets the detailed explanation of the problem virtual void setExplanation(const QString& explanation) = 0; /// Returns the severity of the problem virtual Severity severity() const = 0; /// Sets the severity of the problem virtual void setSeverity(Severity severity) = 0; /// Returns a string containing the severity of the problem virtual QString severityString() const = 0; /// Returns the diagnostics of the problem. virtual QVector diagnostics() const = 0; /// Sets the diagnostics of the problem virtual void setDiagnostics(const QVector &diagnostics) = 0; /// Adds a diagnostic line to the problem virtual void addDiagnostic(const Ptr &diagnostic) = 0; /// Clears all diagnostics virtual void clearDiagnostics() = 0; /// Returns a solution assistant for the problem, if applicable that is. virtual QExplicitlySharedDataPointer solutionAssistant() const = 0; }; Q_DECLARE_OPERATORS_FOR_FLAGS(IProblem::Severities) } Q_DECLARE_METATYPE(KDevelop::IProblem::Ptr) #endif diff --git a/interfaces/iproject.cpp b/interfaces/iproject.cpp index 941047862..7ead2cb4f 100644 --- a/interfaces/iproject.cpp +++ b/interfaces/iproject.cpp @@ -1,46 +1,44 @@ /* 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 "iproject.h" -#include - namespace KDevelop { class Path; IProject::IProject(QObject *parent) : QObject(parent) { } IProject::~IProject() { } } diff --git a/interfaces/iproject.h b/interfaces/iproject.h index a83eb4b59..a22f122dc 100644 --- a/interfaces/iproject.h +++ b/interfaces/iproject.h @@ -1,183 +1,182 @@ /* This file is part of the KDE project Copyright 2001 Matthias Hoelzer-Kluepfel Copyright 2001-2002 Bernd Gehrmann 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. */ #ifndef KDEVPLATFORM_IPROJECT_H #define KDEVPLATFORM_IPROJECT_H #include -#include #include #include "interfacesexport.h" class KJob; template class QList; template class QSet; namespace KDevelop { class IPlugin; class IProjectFileManager; class IBuildSystemManager; class Path; class ProjectBaseItem; class ProjectFileItem; class ProjectFolderItem; class IndexedString; /** * \brief Object which represents a KDevelop project * * Provide better descriptions */ class KDEVPLATFORMINTERFACES_EXPORT IProject : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kdevelop.Project") Q_PROPERTY(QString projectName READ name CONSTANT) public: /** * Constructs a project. * * @param parent The parent object for the plugin. */ explicit IProject(QObject *parent = nullptr); /// Destructor. ~IProject() override; /** * Get the file manager for the project * * @return the file manager for the project, if one exists; otherwise null */ Q_SCRIPTABLE virtual IProjectFileManager* projectFileManager() const = 0; /** * Get the build system manager for the project * * @return the build system manager for the project, if one exists; otherwise null */ Q_SCRIPTABLE virtual IBuildSystemManager* buildSystemManager() const = 0; /** * Get the plugin that manages the project * This can be used to get other interfaces like IBuildSystemManager */ Q_SCRIPTABLE virtual IPlugin* managerPlugin() const = 0; /** * Get the version control plugin for this project * This may return 0 if the project is not under version control * or version control has been disabled for this project */ Q_SCRIPTABLE virtual IPlugin* versionControlPlugin() const = 0; /** * With this the top-level project item can be retrieved */ Q_SCRIPTABLE virtual ProjectFolderItem* projectItem() const = 0; /** * @return all items with the corresponding @p path */ Q_SCRIPTABLE virtual QList itemsForPath( const IndexedString& path ) const = 0; /** * @return all file items with the corresponding @p file path */ Q_SCRIPTABLE virtual QList filesForPath( const IndexedString& file ) const = 0; /** * @return all folder items with the corresponding @p folder path */ Q_SCRIPTABLE virtual QList foldersForPath( const IndexedString& folder ) const = 0; /** * @return the path to the project file */ virtual Path projectFile() const = 0; /** Get the url of the project file.*/ virtual KSharedConfigPtr projectConfiguration() const = 0; virtual void addToFileSet( ProjectFileItem* item ) = 0; virtual void removeFromFileSet( ProjectFileItem* item ) = 0; virtual QSet fileSet() const = 0; /** Returns whether the project is ready to be used or not. A project won't be ready for use when it's being reloaded or still loading */ virtual bool isReady() const=0; /** * @brief Get the project path * @return The canonical absolute directory of the project. */ virtual Path path() const = 0; /** Returns the name of the project. */ virtual Q_SCRIPTABLE QString name() const = 0; /** * @brief Check if the project contains an item with the given @p path. * * @param path the path to check * * @return true if the path @a path is a part of the project. */ virtual Q_SCRIPTABLE bool inProject(const IndexedString &path) const = 0; /** * @brief Tells the project what job is reloading it * * It's useful so that we can tell whether the project manager is busy or not. */ virtual void setReloadJob(KJob* job) = 0; Q_SIGNALS: /** * Gets emitted whenever a file was added to the project. */ void fileAddedToSet( KDevelop::ProjectFileItem* item ); /** * Gets emitted whenever a file was removed from the project. */ void fileRemovedFromSet( KDevelop::ProjectFileItem* item ); public Q_SLOTS: /** Make the model to reload */ virtual void reloadModel() = 0; /** This method is invoked when the project needs to be closed. */ virtual void close() = 0; }; } #endif diff --git a/interfaces/iprojectcontroller.h b/interfaces/iprojectcontroller.h index 516ba79d5..fb697922a 100644 --- a/interfaces/iprojectcontroller.h +++ b/interfaces/iprojectcontroller.h @@ -1,206 +1,204 @@ /* 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. */ #ifndef KDEVPLATFORM_IPROJECTCONTROLLER_H #define KDEVPLATFORM_IPROJECTCONTROLLER_H #include #include #include #include "interfacesexport.h" -class QItemSelectionModel; - namespace KDevelop { class IProject; class ProjectBuildSetModel; class ProjectModel; class ProjectBaseItem; class ProjectChangesModel; /** * @class IProjectController */ class KDEVPLATFORMINTERFACES_EXPORT IProjectController : public QObject { Q_OBJECT public: explicit IProjectController( QObject *parent = nullptr ); ~IProjectController() override; Q_SCRIPTABLE virtual KDevelop::IProject* projectAt( int ) const = 0; Q_SCRIPTABLE virtual int projectCount() const = 0; Q_SCRIPTABLE virtual QList projects() const = 0; /** * Provides access to the model representing the open projects * @returns the model containing the projects and their items */ Q_SCRIPTABLE virtual ProjectModel* projectModel() = 0; /** * @returns an instance to the model that keeps track of the state * of the files per project. */ Q_SCRIPTABLE virtual ProjectChangesModel* changesModel() = 0; Q_SCRIPTABLE virtual ProjectBuildSetModel* buildSetModel() = 0; /** * Find an open project using the name of the project * @param name the name of the project to be found * @returns the project or null if no project with that name is open */ Q_SCRIPTABLE virtual KDevelop::IProject* findProjectByName( const QString& name ) = 0; /** * Finding an open project for a given file or folder in the project * @param url the url of a file/folder belonging to an open project * @returns the first open project containing the url or null if no such * project can be found */ Q_SCRIPTABLE virtual IProject* findProjectForUrl( const QUrl& url ) const = 0; /** * Checks whether the given project name is used already or not. The project * controller supports only 1 project with a given name to be open at any time * @param name the name of the project to be opened or created * @returns whether the name is already used for an open project */ Q_SCRIPTABLE virtual bool isProjectNameUsed( const QString& name ) const = 0; virtual QUrl projectsBaseDirectory() const = 0; enum FormattingOptions { FormatHtml, FormatPlain }; /** * Returns a pretty short representation of the base path of the url, considering the currently loaded projects: * When the file is part of a currently loaded project, that projects name is shown as prefix instead of the * the full file path. * The returned path always has a training slash. * @param format formatting used for the string */ Q_SCRIPTABLE virtual QString prettyFilePath(const QUrl& url, FormattingOptions format = FormatHtml) const = 0; /** * Returns a pretty short representation of the given url, considering the currently loaded projects: * When the file is part of a currently loaded project, that projects name is shown as prefix instead of the * the full file path. * @param format formatting used for the string */ Q_SCRIPTABLE virtual QString prettyFileName(const QUrl& url, FormattingOptions format = FormatHtml) const = 0; /** * @returns whether project files should be parsed or not */ static bool parseAllProjectSources(); public Q_SLOTS: /** * Tries finding a project-file for the given source-url and opens it. * If no .kdev4 project file is found, the user is asked to import a project. */ virtual void openProjectForUrl( const QUrl &sourceUrl ) = 0; /** * open the project from the given kdev4 project file. This only reads * the file and starts creating the project model from it. The opening process * is finished once @ref projectOpened signal is emitted. * @param url a kdev4 project file top open */ virtual void openProject( const QUrl & url = QUrl() ) = 0; /** * close the given project. Closing the project is done in steps and * the @ref projectClosing and @ref projectClosed signals are emitted. Only when * the latter signal is emitted it is guaranteed that the project has been closed. * The @ref IProject object will be deleted after the closing has finished. */ virtual void closeProject( IProject* ) = 0; /** * Close all projects * * This usually calls closeProject() on all controlled projects * @sa closeProject() */ virtual void closeAllProjects() = 0; virtual void configureProject( IProject* ) = 0; /// Schedules all files of the @p project for reparsing by @see BackgroundParser virtual void reparseProject( IProject* project, bool ForceUpdate = false ) = 0; // virtual void changeCurrentProject( KDevelop::ProjectBaseItem* ) = 0; Q_SIGNALS: /** * Emitted right before a project is started to be loaded. * * At this point all sanity checks have been done, so the project * is really going to be loaded. Will be followed by @ref projectOpened signal * when loading completes or by @ref projectOpeningAborted if there are errors during loading * or it is aborted. * * @param project the project that is about to be opened. */ void projectAboutToBeOpened( KDevelop::IProject* project ); /** * emitted after a project is completely opened and the project model * has been populated. * @param project the project that has been opened. */ void projectOpened( KDevelop::IProject* project ); /** * emitted when starting to close a project that has been completely loaded before * (the @ref projectOpened signal has been emitted). * @param project the project that is going to be closed. */ void projectClosing( KDevelop::IProject* project ); /** * emitted when a project has been closed completely. * The project object is still valid, the deletion will be done * delayed during the next run of the event loop. * @param project the project that has been closed. */ void projectClosed( KDevelop::IProject* project ); /** * emitted when a project could not be loaded correctly or loading was aborted. * @ref project contents may not be initialized properly. * @param project the project which loading has been aborted. */ void projectOpeningAborted( KDevelop::IProject* project ); /** * emitted whenever the project configuration dialog accepted * changes * @param project the project whose configuration has changed */ void projectConfigurationChanged( KDevelop::IProject* project ); }; } #endif diff --git a/interfaces/iprojectprovider.h b/interfaces/iprojectprovider.h index b2d29c843..5301d03da 100644 --- a/interfaces/iprojectprovider.h +++ b/interfaces/iprojectprovider.h @@ -1,70 +1,69 @@ /* This file is part of KDevelop * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef KDEVPLATFORM_IPROJECTPROVIDER_H #define KDEVPLATFORM_IPROJECTPROVIDER_H -#include #include #include "interfacesexport.h" class QUrl; namespace KDevelop { class VcsLocationWidget; class VcsLocation; class VcsJob; class KDEVPLATFORMINTERFACES_EXPORT IProjectProviderWidget : public QWidget { Q_OBJECT public: explicit IProjectProviderWidget(QWidget* parent = nullptr); /** * @returns a job that will create a working copy given the current state of the widget. * * @param destinationDirectory where the project will be downloaded. */ virtual VcsJob* createWorkingCopy(const QUrl & destinationDirectory) = 0; /** @returns whether we have a correct location in the widget. */ virtual bool isCorrect() const = 0; signals: void changed(const QString& name); }; class KDEVPLATFORMINTERFACES_EXPORT IProjectProvider { public: virtual ~IProjectProvider(); virtual QString name() const = 0; virtual IProjectProviderWidget* providerWidget(QWidget* parent) = 0; }; } Q_DECLARE_INTERFACE( KDevelop::IProjectProvider, "org.kdevelop.IProjectProvider" ) #endif diff --git a/interfaces/iruncontroller.h b/interfaces/iruncontroller.h index 001b96a69..a8b0a80f2 100644 --- a/interfaces/iruncontroller.h +++ b/interfaces/iruncontroller.h @@ -1,170 +1,168 @@ /* 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. */ #ifndef KDEVPLATFORM_IRUNCONTROLLER_H #define KDEVPLATFORM_IRUNCONTROLLER_H -#include - #include #include "interfacesexport.h" class KJob; namespace KDevelop { class IProject; class ILaunchMode; class ILaunchConfiguration; class LaunchConfigurationType; /** * The main controller for running processes. */ class KDEVPLATFORMINTERFACES_EXPORT IRunController : public KJobTrackerInterface { Q_OBJECT Q_ENUMS(State) public: ///Constructor. explicit IRunController(QObject *parent); /** * Interrogate the current managed jobs */ Q_SCRIPTABLE virtual QList currentJobs() const = 0; /** * An enumeration of the possible states for the run controller. */ enum State { Idle /**< No processes are currently running */, Running /**< processes are currently running */ }; /** * Get a list of all launch modes that the app knows * @returns a list of registered launch modes */ virtual QList launchModes() const = 0; /** * Get a list of all available launch configurations */ virtual QList launchConfigurations() const = 0; /** * Get a specific launch mode based using its \a id * @param id the identifier of the launchmode to get * @returns launch mode for the given id or 0 if no such mode is known */ virtual ILaunchMode* launchModeForId( const QString& id ) const = 0; /** * add @p mode to the list of registered launch modes * @param mode the mode to be registered */ virtual void addLaunchMode( ILaunchMode* mode ) = 0; /** * remove @p mode from the list of registered launch modes * @param mode the mode to be unregistered */ virtual void removeLaunchMode( ILaunchMode* mode ) = 0; /** * Get a list of all configuration types that are registered * @returns a list of run configuration types */ virtual QList launchConfigurationTypes() const = 0; /** * Adds @p type to the list of known run config types * @param type the new run configuration type */ virtual void addConfigurationType( LaunchConfigurationType* type ) = 0; /** * Removes @p type from the list of known run config types * @param type run configuration type that should be removed */ virtual void removeConfigurationType( LaunchConfigurationType* type ) = 0; /** * Executes the default launch in the given mode * @param runMode the launch mode to start with */ virtual void executeDefaultLaunch( const QString& runMode ) = 0; virtual KJob* execute(const QString& launchMode, ILaunchConfiguration* launch) = 0; /** * tries to find a launch config type for the given @p id * @param id the id of the launch configuration type to search * @returns the launch configuration type if found, or 0 otherwise */ virtual LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) = 0; /** * Creates a new launch configuration in the given project or in the session if no project * was provided using the given launch configuration type and launcher. The configuration * is also added to the list of configurations in the runcontroller. * * @param type the launch configuration type to be used for the new config * @param launcher the mode and id of the launcher to be used in the config * @param project the project in which the launch configuration should be stored * @param name the name of the new launch configuration, if this is empty a new default name will be generated * @returns a new launch configuration */ virtual ILaunchConfiguration* createLaunchConfiguration( LaunchConfigurationType* type, const QPair& launcher, KDevelop::IProject* project = nullptr, const QString& name = QString() ) = 0; /// Opens a dialog to setup new launch configurations, or to change the existing ones. virtual void showConfigurationDialog() const = 0; public Q_SLOTS: /** * Request for all running processes to be killed. */ virtual void stopAllProcesses() = 0; Q_SIGNALS: /** * Notify that the state of the run controller has changed to \a {state}. */ void runStateChanged(State state); /** * Notify that a new job has been registered. */ void jobRegistered(KJob* job); /** * Notify that a job has been unregistered. */ void jobUnregistered(KJob* job); }; } #endif // KDEVPLATFORM_IRUNCONTROLLER_H diff --git a/interfaces/isourceformatter.cpp b/interfaces/isourceformatter.cpp index 14ba47b77..daf71d904 100644 --- a/interfaces/isourceformatter.cpp +++ b/interfaces/isourceformatter.cpp @@ -1,197 +1,199 @@ /* This file is part of KDevelop 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 "isourceformatter.h" #include + #include +#include namespace KDevelop { SettingsWidget::SettingsWidget(QWidget *parent) : QWidget(parent) { } SettingsWidget::~SettingsWidget() { } ISourceFormatter::~ISourceFormatter() { } SourceFormatterStyle::SourceFormatterStyle() : m_usePreview(false) { }; SourceFormatterStyle::SourceFormatterStyle(const QString &name) : m_usePreview(false) , m_name(name) { } void SourceFormatterStyle::setContent(const QString &content) { m_content = content; } void SourceFormatterStyle::setCaption(const QString &caption) { m_caption = caption; } QString SourceFormatterStyle::content() const { return m_content; } QString SourceFormatterStyle::caption() const { return m_caption; } QString SourceFormatterStyle::name() const { return m_name; } QString SourceFormatterStyle::description() const { return m_description; } void SourceFormatterStyle::setDescription(const QString &desc) { m_description = desc; } bool SourceFormatterStyle::usePreview() const { return m_usePreview; } void SourceFormatterStyle::setUsePreview(bool use) { m_usePreview = use; } void SourceFormatterStyle::setMimeTypes(const SourceFormatterStyle::MimeList& types) { m_mimeTypes = types; } void SourceFormatterStyle::setMimeTypes(const QStringList& types) { foreach ( auto& t, types ) { auto items = t.split('|'); if ( items.size() != 2 ) { continue; } m_mimeTypes << MimeHighlightPair{items.at(0), items.at(1)}; } } void SourceFormatterStyle::setOverrideSample(const QString &sample) { m_overrideSample = sample; } QString SourceFormatterStyle::overrideSample() const { return m_overrideSample; } SourceFormatterStyle::MimeList SourceFormatterStyle::mimeTypes() const { return m_mimeTypes; } QVariant SourceFormatterStyle::mimeTypesVariant() const { QStringList result; for ( const auto& item: m_mimeTypes ) { result << item.mimeType + "|" + item.highlightMode; } return QVariant::fromValue(result); } bool SourceFormatterStyle::supportsLanguage(const QString &language) const { for ( const auto& item: m_mimeTypes ) { if ( item.highlightMode == language ) { return true; } } return false; } QString SourceFormatterStyle::modeForMimetype(const QMimeType& mime) const { foreach (const auto& item, mimeTypes()) { if (mime.inherits(item.mimeType)) { return item.highlightMode; } } return QString(); } void SourceFormatterStyle::copyDataFrom(SourceFormatterStyle *other) { m_content = other->content(); m_mimeTypes = other->mimeTypes(); m_overrideSample = other->overrideSample(); } QString ISourceFormatter::optionMapToString(const QMap &map) { QString options; QMap::const_iterator it = map.constBegin(); for (; it != map.constEnd(); ++it) { options += it.key(); options += '='; options += it.value().toString(); options += ','; } return options; } QMap ISourceFormatter::stringToOptionMap(const QString &options) { QMap map; QStringList pairs = options.split(',', QString::SkipEmptyParts); QStringList::const_iterator it; for (it = pairs.constBegin(); it != pairs.constEnd(); ++it) { QStringList bits = (*it).split('='); map[bits[0]] = bits[1]; } return map; } QString ISourceFormatter::missingExecutableMessage(const QString &name) { return i18n("The executable %1 cannot be found. Please make sure" " it is installed and can be executed.
" "The plugin will not work until you fix this problem.", "" + name + ""); } } // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/interfaces/isourceformatter.h b/interfaces/isourceformatter.h index e90e67a45..3c6eae1a5 100644 --- a/interfaces/isourceformatter.h +++ b/interfaces/isourceformatter.h @@ -1,231 +1,231 @@ /* This file is part of KDevelop 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. */ #ifndef KDEVPLATFORM_ISOURCEFORMATTER_H #define KDEVPLATFORM_ISOURCEFORMATTER_H #include -#include -#include #include "interfacesexport.h" class QUrl; +class QMimeType; +class QStringList; namespace KDevelop { class KDEVPLATFORMINTERFACES_EXPORT SourceFormatterStyle { public: struct MimeHighlightPair { QString mimeType; QString highlightMode; }; typedef QVector MimeList; SourceFormatterStyle(); explicit SourceFormatterStyle( const QString& name ); void setContent( const QString& content ); void setCaption( const QString& caption ); QString content() const; QString caption() const; QString name() const; QString description() const; void setDescription( const QString& desc ); bool usePreview() const; void setUsePreview(bool use); void setMimeTypes( const MimeList& types ); void setMimeTypes( const QStringList& types ); /// Provides the possibility to the item to return a better-suited /// code sample. If empty, the default is used. QString overrideSample() const; void setOverrideSample( const QString& sample ); MimeList mimeTypes() const; /// mime types as a QVariantList, type and mode separated by | in strings QVariant mimeTypesVariant() const; bool supportsLanguage(const QString& language) const; /// get the language / highlight mode for a given @p mime QString modeForMimetype(const QMimeType& mime) const; /// Copy content, mime types and code sample from @p other. void copyDataFrom(SourceFormatterStyle *other); private: bool m_usePreview; QString m_name; QString m_caption; QString m_content; QString m_description; QString m_overrideSample; MimeList m_mimeTypes; }; /** * @brief An object describing a style associated with a plugin * which can deal with this style. */ struct SourceFormatterStyleItem { QString engine; SourceFormatterStyle style; }; typedef QVector SourceFormatterItemList; /** * @short A widget to edit a style * A plugin should inherit this widget to create a widget to * edit a style. * @author Cédric Pasteur */ class KDEVPLATFORMINTERFACES_EXPORT SettingsWidget : public QWidget { Q_OBJECT public: explicit SettingsWidget(QWidget *parent = nullptr); ~SettingsWidget() override; /** This function is called after the creation of the dialog. * it should initialise the widgets with the values corresponding to * the predefined style \arg name if it's not empty, or * to the string \arg content. */ virtual void load(const SourceFormatterStyle&) = 0; /** \return A string representing the state of the config. */ virtual QString save() = 0; Q_SIGNALS: /** Emits this signal when a setting was changed and the preview * needs to be updated. \arg text is the text that will be shown in the * editor. One might want to show different text * according to the different pages shown in the widget. * Text should already be formatted. */ void previewTextChanged(const QString &text); }; /** * @short An interface for a source beautifier * An example of a plugin using an external executable to do * the formatting can be found in kdevelop/plugins/formatters/indent_plugin.cpp. * @author Cédric Pasteur */ class KDEVPLATFORMINTERFACES_EXPORT ISourceFormatter { public: virtual ~ISourceFormatter(); /** \return The name of the plugin. This should contain only * ASCII chars and no spaces. This will be used internally to identify * the plugin. */ virtual QString name() = 0; /** \return A caption describing the plugin. */ virtual QString caption() = 0; /** \return A more complete description of the plugin. * The string should be written in Rich text. It can eg contain * a link to the project homepage. */ virtual QString description() = 0; /** Formats using the current style. * @param text The text to format * @param url The URL to which the text belongs (its contents must not be changed). * @param leftContext The context at the left side of the text. If it is in another line, it must end with a newline. * @param rightContext The context at the right side of the text. If it is in the next line, it must start with a newline. * * If the source-formatter cannot work correctly with the context, it will just return the input text. */ virtual QString formatSource(const QString &text, const QUrl& url, const QMimeType &mime, const QString& leftContext = QString(), const QString& rightContext = QString()) = 0; /** * Format with the given style, this is mostly for the kcm to format the preview text * Its a bit of a hassle that this needs to be public API, but I can't find a better way * to do this. */ virtual QString formatSourceWithStyle( SourceFormatterStyle, const QString& text, const QUrl& url, const QMimeType &mime, const QString& leftContext = QString(), const QString& rightContext = QString() ) = 0; /** \return A map of predefined styles (a key and a caption for each type) */ virtual QList predefinedStyles() = 0; /** \return The widget to edit a style. */ virtual SettingsWidget* editStyleWidget(const QMimeType &mime) = 0; /** \return The text used in the config dialog to preview the current style. */ virtual QString previewText(const SourceFormatterStyle& style, const QMimeType &mime) = 0; struct Indentation { Indentation() : indentationTabWidth(0), indentWidth(0) { } // If this indentation is really valid bool isValid() const { return indentationTabWidth != 0 || indentWidth != 0; } // The length of one tab used for indentation. // Zero if unknown, -1 if tabs should not be used for indentation int indentationTabWidth; // The number of columns that equal one indentation level. // If this is zero, the default should be used. int indentWidth; }; /** \return The indentation of the style applicable for the given url. */ virtual Indentation indentation(const QUrl& url) = 0; /** \return A string representing the map. Values are written in the form * key=value and separated with ','. */ static QString optionMapToString(const QMap &map); /** \return A map corresponding to the string, that was created with * \ref optionMapToString. */ static QMap stringToOptionMap(const QString &option); /** \return A message to display when an executable needed by a * plugin is missing. This should be returned as description * if a needed executable is not found. */ static QString missingExecutableMessage(const QString &name); }; } Q_DECLARE_INTERFACE(KDevelop::ISourceFormatter, "org.kdevelop.ISourceFormatter") Q_DECLARE_TYPEINFO(KDevelop::SourceFormatterStyle::MimeHighlightPair, Q_MOVABLE_TYPE); #endif // KDEVPLATFORM_ISOURCEFORMATTER_H // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/interfaces/isourceformattercontroller.h b/interfaces/isourceformattercontroller.h index 5c09710ba..c30e6efa9 100644 --- a/interfaces/isourceformattercontroller.h +++ b/interfaces/isourceformattercontroller.h @@ -1,71 +1,71 @@ /* 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. */ #ifndef KDEVPLATFORM_ISOURCEFORMATTERCONTROLLER_H #define KDEVPLATFORM_ISOURCEFORMATTERCONTROLLER_H -#include #include #include "interfacesexport.h" class QUrl; +class QMimeType; namespace KDevelop { class ISourceFormatter; class SourceFormatterStyle; /** \short An interface to the controller managing all source formatter plugins */ class KDEVPLATFORMINTERFACES_EXPORT ISourceFormatterController : public QObject { Q_OBJECT public: explicit ISourceFormatterController(QObject *parent = nullptr); ~ISourceFormatterController() override; /** \return The formatter corresponding to the language * of the document corresponding to the \arg url. */ virtual ISourceFormatter* formatterForUrl(const QUrl &url) = 0; /** Loads and returns a source formatter for this mime type. * The language is then activated and the style is loaded. * The source formatter is then ready to use on a file. */ virtual ISourceFormatter* formatterForMimeType(const QMimeType &mime) = 0; /** \return Whether this mime type is supported by any plugin. */ virtual bool isMimeTypeSupported(const QMimeType &mime) = 0; virtual KDevelop::SourceFormatterStyle styleForMimeType( const QMimeType& mime ) = 0; ///Set whether or not source formatting is disabled with \arg disable virtual void disableSourceFormatting(bool disable) = 0; ///\return Whether or not source formatting is enabled virtual bool sourceFormattingEnabled() = 0; }; } #endif // KDEVPLATFORM_ISOURCEFORMATTERCONTROLLER_H // kate: indent-mode cstyle; space-indent off; tab-width 4 = 0; diff --git a/interfaces/itestcontroller.h b/interfaces/itestcontroller.h index 66dd5259c..febd38793 100644 --- a/interfaces/itestcontroller.h +++ b/interfaces/itestcontroller.h @@ -1,141 +1,141 @@ /* 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. */ #ifndef KDEVPLATFORM_ITESTCONTROLLER_H #define KDEVPLATFORM_ITESTCONTROLLER_H #include "interfacesexport.h" #include #include #include -#include +#include -class KJob; +class QStringList; namespace KDevelop { struct TestResult; class IProject; class ITestSuite; /** * The result of a single unit test run **/ struct KDEVPLATFORMINTERFACES_EXPORT TestResult { /** * Enumeration of possible test case results **/ enum TestCaseResult { NotRun, ///< The test case was not selected for running. Skipped, ///< The test case was skipped. Passed, ///< The test case was run and passed. Failed, ///< The test case was run and failed. ExpectedFail, ///< The test case was expected to fail, and did. UnexpectedPass, ///< The test case was expected to fail, but passed. Error, ///< There was an error while trying to run the test case. }; /** * The individual results of all test cases. **/ QHash testCaseResults; /** * The total result of the entire suite. * * This is usually the worst outcome of the individual test cases, * but can be different especially when dealing with errors. */ TestCaseResult suiteResult; }; class KDEVPLATFORMINTERFACES_EXPORT ITestController : public QObject { Q_OBJECT public: explicit ITestController(QObject* parent = nullptr); ~ITestController() override; /** * Add a new test suite. * * No ownership is taken, the caller stays responsible for the suite. * * If a suite with the same project and same name exists, the old one will be removed and deleted. */ virtual void addTestSuite(ITestSuite* suite) = 0; /** * Remove a test suite from the controller. * * This does not delete the test suite. */ virtual void removeTestSuite(ITestSuite* suite) = 0; /** * Returns the list of all known test suites. */ virtual QList testSuites() const = 0; /** * Find a test suite in @p project with @p name. */ virtual ITestSuite* findTestSuite(IProject* project, const QString& name) const = 0; /** * Return the list of all test suites in @p project. */ virtual QList testSuitesForProject(IProject* project) const = 0; /** * Notify the controller that a test run for @p suite was finished with result @p result */ virtual void notifyTestRunFinished(ITestSuite* suite, const KDevelop::TestResult& result) = 0; /** * Notify the controller that a test run for @p suite was started */ virtual void notifyTestRunStarted(KDevelop::ITestSuite* suite, const QStringList& test_cases) = 0; Q_SIGNALS: /** * Emitted whenever a new test suite gets added. */ void testSuiteAdded(KDevelop::ITestSuite* suite) const; /** * Emitted whenever a test suite gets removed. */ void testSuiteRemoved(KDevelop::ITestSuite* suite) const; /** * Emitted after a test suite was run. */ void testRunFinished(KDevelop::ITestSuite* suite, const KDevelop::TestResult& result) const; /** * Emitted when a test suite starts. */ void testRunStarted(KDevelop::ITestSuite* suite, const QStringList& test_cases) const; }; } Q_DECLARE_INTERFACE( KDevelop::ITestController, "org.kdevelop.ITestController") #endif // KDEVPLATFORM_ITESTCONTROLLER_H diff --git a/interfaces/itestsuite.h b/interfaces/itestsuite.h index e064746b5..ea56daac3 100644 --- a/interfaces/itestsuite.h +++ b/interfaces/itestsuite.h @@ -1,119 +1,119 @@ /* 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. */ #ifndef KDEVPLATFORM_ITESTSUITE_H #define KDEVPLATFORM_ITESTSUITE_H #include "interfacesexport.h" -#include - class KJob; +class QString; +class QStringList; namespace KDevelop { class IndexedDeclaration; class IProject; /** * A unit test suite class. **/ class KDEVPLATFORMINTERFACES_EXPORT ITestSuite { public: /** * Specifies how much output a test job should produce **/ enum TestJobVerbosity { Silent, ///< No toolview is raised by the job Verbose ///< The job raises an output toolview }; /** * Destructor **/ virtual ~ITestSuite(); /** * @returns the display name of this suite. It has to be unique within a project. **/ virtual QString name() const = 0; /** * @returns the list of all test cases in this suite. **/ virtual QStringList cases() const = 0; /** * Get the project to which this test suite belongs. * Since all suites must have a project associated, * this function should never return 0. * * @returns the test suite's project. **/ virtual IProject* project() const = 0; /** * Return a job that will execute all the test cases in this suite. * * The implementation of this class is responsible for creating the job * and interpreting its results. After the job is finished, the test results * should be made available to the result() function. * * Starting the job is up to the caller, usually by registering it with * the run controller. **/ virtual KJob* launchAllCases(TestJobVerbosity verbosity) = 0; /** * @param testCases list of test cases to run * @returns a KJob that will run the specified @p testCases. * @sa launchAllCases() **/ virtual KJob* launchCases(const QStringList& testCases, TestJobVerbosity verbosity) = 0; /** * @param testCase the test case to run * @returns a KJob that will run only @p testCase. * @sa launchAllCases() **/ virtual KJob* launchCase(const QString& testCase, TestJobVerbosity verbosity) = 0; /** * The location in source code where the test suite is declared. * If no such declaration can be found, an invalid declaration is returned. **/ virtual IndexedDeclaration declaration() const = 0; /** * The location in source code where the test case @p testCase is declared. * If no such declaration can be found, an invalid declaration is returned. * * This function may also return declarations for setup and teardown functions, * even though these functions are not included in cases(). * * @param testCase the test case **/ virtual IndexedDeclaration caseDeclaration(const QString& testCase) const = 0; }; } #endif // KDEVPLATFORM_ITESTSUITE_H diff --git a/interfaces/iuicontroller.h b/interfaces/iuicontroller.h index ad51bad36..0555c3510 100644 --- a/interfaces/iuicontroller.h +++ b/interfaces/iuicontroller.h @@ -1,174 +1,170 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_IUICONTROLLER_H #define KDEVPLATFORM_IUICONTROLLER_H #include "interfacesexport.h" -#include #include class QAction; -template -class QExplicitlySharedDataPointer; - namespace KParts { class MainWindow; } namespace Sublime{ class Controller; class View; class Area; } namespace KDevelop { class IDocument; class IAssistant; class KDEVPLATFORMINTERFACES_EXPORT IToolViewFactory { public: virtual ~IToolViewFactory() {} /** * called to create a new widget for this toolview * @param parent the parent to use as parent for the widget * @returns the new widget for the toolview */ virtual QWidget* create(QWidget *parent = nullptr) = 0; /** * @returns the identifier of this toolview. The identifier * is used to remember which areas the tool view should appear * in, and must never change. */ virtual QString id() const = 0; /** * @returns the default position where this toolview should appear */ virtual Qt::DockWidgetArea defaultPosition() = 0; /** * Fetch a list of actions to add to the toolbar of the toolview @p view * @param viewWidget the view to which the actions should be added * @returns a list of actions to be added to the toolbar */ virtual QList toolBarActions( QWidget* viewWidget ) const { return viewWidget->actions(); } /** * Fetch a list of actions to be shown in the context menu of the toolview @p view. * The default implementation will return all actions of @p viewWidget. * * @param viewWidget the view for which the context menu should be shown * @returns a list of actions to be shown in the context menu */ virtual QList contextMenuActions( QWidget* viewWidget ) const { return viewWidget->actions(); } /** * called when a new view is created from this template * @param view the new sublime view that is being shown */ virtual void viewCreated(Sublime::View* view); /** * @returns if multiple tool views can by created by this factory in the same area. */ virtual bool allowMultiple() const { return false; } }; /** * * Allows to access various parts of the user-interface, like the toolviews or the mainwindow */ class KDEVPLATFORMINTERFACES_EXPORT IUiController { public: virtual ~IUiController(); enum SwitchMode { ThisWindow /**< indicates that the area switch should be in this window */, NewWindow /**< indicates that the area switch should be using a new window */ }; enum FindFlags { None = 0, Create = 1, ///The tool-view is created if it doesn't exist in the current area yet Raise = 2, ///The tool-view is raised if it was found/created CreateAndRaise = Create | Raise ///The tool view is created and raised }; virtual void switchToArea(const QString &areaName, SwitchMode switchMode) = 0; virtual void addToolView(const QString &name, IToolViewFactory *factory, FindFlags state = Create) = 0; virtual void removeToolView(IToolViewFactory *factory) = 0; /** Makes sure that this tool-view exists in the current area, raises it, and returns the contained widget * Returns zero on failure */ virtual QWidget* findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags = CreateAndRaise) = 0; /** * Makes sure that the toolview that contains the widget @p toolViewWidget is visible to the user. */ virtual void raiseToolView(QWidget* toolViewWidget) = 0; /** @return active mainwindow or 0 if no such mainwindow is active.*/ virtual KParts::MainWindow *activeMainWindow() = 0; /*! @p status must implement KDevelop::IStatus */ virtual void registerStatus(QObject* status) = 0; /** * This is meant to be used by IDocument subclasses to initialize the * Sublime::Document. */ virtual Sublime::Controller* controller() = 0; /** Shows an error message in the status bar. * * Unlike all other functions in this class, this function is thread-safe. * You can call it from the background. * * @p message The message * @p timeout The timeout in seconds how long to show the message */ virtual void showErrorMessage(const QString& message, int timeout = 1) = 0; /** @return area for currently active sublime mainwindow or 0 if no sublime mainwindow is active.*/ virtual Sublime::Area *activeArea() = 0; /** * Widget which is currently responsible for consuming special events in the UI * (such as shortcuts) * * @sa IToolViewActionListener * @return QWidget implementing the IToolViewActionListener interface */ virtual QWidget* activeToolViewActionListener() const = 0; /** * @returns all areas in the shell * * @note there will be one per mainwindow, of each type, plus the default ones. */ virtual QList allAreas() const = 0; protected: IUiController(); }; } #endif diff --git a/interfaces/launchconfigurationtype.h b/interfaces/launchconfigurationtype.h index d44b52d0b..f32162047 100644 --- a/interfaces/launchconfigurationtype.h +++ b/interfaces/launchconfigurationtype.h @@ -1,152 +1,152 @@ /* 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. */ #ifndef KDEVPLATFORM_LAUNCHCONFIGURATIONTYPE_H #define KDEVPLATFORM_LAUNCHCONFIGURATIONTYPE_H #include "interfacesexport.h" -#include #include class QMenu; class QIcon; class QUrl; +class QStringList; class KConfigGroup; namespace KDevelop { class IProject; class ILaunchConfiguration; class ProjectBaseItem; class ILauncher; class LaunchConfigurationPageFactory; /** * Launch configuration types are used to be able to create * new launch configurations. Each launch configuration has a * specific type, which specifies which launchers can be used * for the configuration as well as which config pages are needed * to setup the launch configuration */ class KDEVPLATFORMINTERFACES_EXPORT LaunchConfigurationType : public QObject { Q_OBJECT public: LaunchConfigurationType(); ~LaunchConfigurationType() override; /** * Provide a unique identifier for the type * among other things this will be used to create a config group in launch * configurations for the pages of this config type * @returns a unique identifier for this launch configuration type */ virtual QString id() const = 0; /** * Provide a user visible name for the type * @returns a translatable string for the type */ virtual QString name() const = 0; /** * Add @p starter to this configuration type * @param starter the launcher that can start configurations of this type */ void addLauncher( ILauncher* starter ); /** * remove @p starter from this configuration type * @param starter the launcher that should not start configurations of this type */ void removeLauncher( ILauncher* starter ); /** * Access all launchers that are usable with this type * @returns a list of launchers that can be used with configurations of this type */ QList launchers() const; /** * Convenience method to access a launcher given its @p id * @param id the id of the launcher to be found * @returns the launcher with the given id or 0 if there's no such launcher in this configuration type */ ILauncher* launcherForId( const QString& id ); /** * Provide a list of widgets to configure a launch configuration for this type * @returns a list of factories to create config pages from. */ virtual QList configPages() const = 0; /** * Provide an icon for this launch configuration type * @returns an icon to be used for representing launch configurations of this type */ virtual QIcon icon() const = 0; /** * Check whether this launch configuration type can launch the given project item * @param item the project tree item to test * @returns true if this configuration type can launch the given item, false otherwise */ virtual bool canLaunch( KDevelop::ProjectBaseItem* item ) const = 0; /** * Configure the given launch configuration to execute the selected item * @param config the configuration to setup * @param item the item to launch */ virtual void configureLaunchFromItem( KConfigGroup config, KDevelop::ProjectBaseItem* item ) const = 0; /** * Configure the given launch configuration to execute the selected item * @param config the configuration to setup * @param args argument list */ virtual void configureLaunchFromCmdLineArguments( KConfigGroup config, const QStringList &args ) const = 0; /** * Check whether this launch configuration type can launch the given file * @param file the file to test launchability * @returns true if this configuration type can launch the given file, false otherwise */ virtual bool canLaunch( const QUrl& file ) const = 0; /** * Returns a menu that will be added to the UI where the interface will be * able to add any suggestion it needs, like default targets. */ virtual QMenu* launcherSuggestions() { return nullptr; } signals: void signalAddLaunchConfiguration(KDevelop::ILaunchConfiguration* launch); private: class LaunchConfigurationTypePrivate* const d; }; } #endif diff --git a/language/assistant/staticassistantsmanager.cpp b/language/assistant/staticassistantsmanager.cpp index 608c5a401..06002af0e 100644 --- a/language/assistant/staticassistantsmanager.cpp +++ b/language/assistant/staticassistantsmanager.cpp @@ -1,190 +1,188 @@ /* Copyright 2009 David Nolden 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 "staticassistantsmanager.h" #include #include "util/debug.h" -#include - #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KTextEditor; struct StaticAssistantsManager::Private { explicit Private(StaticAssistantsManager* qq) : q(qq) { } void updateReady(const IndexedString& document, const KDevelop::ReferencedTopDUContext& topContext); void documentLoaded(KDevelop::IDocument*); void textInserted(KTextEditor::Document* document, const Cursor& cursor, const QString& text); void textRemoved(KTextEditor::Document* document, const Range& cursor, const QString& removedText); StaticAssistantsManager* q; QVector m_registeredAssistants; }; StaticAssistantsManager::StaticAssistantsManager(QObject* parent) : QObject(parent) , d(new Private(this)) { connect(KDevelop::ICore::self()->documentController(), &IDocumentController::documentLoaded, this, [&] (IDocument* document) { d->documentLoaded(document); }); foreach (IDocument* document, ICore::self()->documentController()->openDocuments()) { d->documentLoaded(document); } connect(DUChain::self(), &DUChain::updateReady, this, &StaticAssistantsManager::notifyAssistants); } StaticAssistantsManager::~StaticAssistantsManager() { } void StaticAssistantsManager::registerAssistant(const StaticAssistant::Ptr assistant) { if (d->m_registeredAssistants.contains(assistant)) return; d->m_registeredAssistants << assistant; } void StaticAssistantsManager::unregisterAssistant(const StaticAssistant::Ptr assistant) { d->m_registeredAssistants.removeOne(assistant); } QVector StaticAssistantsManager::registeredAssistants() const { return d->m_registeredAssistants; } void StaticAssistantsManager::Private::documentLoaded(IDocument* document) { if (document->textDocument()) { auto doc = document->textDocument(); connect(doc, &KTextEditor::Document::textInserted, q, [&] (KTextEditor::Document* doc, const Cursor& cursor, const QString& text) { textInserted(doc, cursor, text); }); connect(doc, &KTextEditor::Document::textRemoved, q, [&] (KTextEditor::Document* doc, const Range& range, const QString& removedText) { textRemoved(doc, range, removedText); }); } } void StaticAssistantsManager::Private::textInserted(Document* doc, const Cursor& cursor, const QString& text) { auto changed = false; Q_FOREACH ( auto assistant, m_registeredAssistants ) { auto range = Range(cursor, cursor+Cursor(0, text.size())); auto wasUseful = assistant->isUseful(); assistant->textChanged(doc, range, {}); if ( wasUseful != assistant->isUseful() ) { changed = true; } } if ( changed ) { Q_EMIT q->problemsChanged(IndexedString(doc->url())); } } void StaticAssistantsManager::Private::textRemoved(Document* doc, const Range& range, const QString& removedText) { auto changed = false; Q_FOREACH ( auto assistant, m_registeredAssistants ) { auto wasUseful = assistant->isUseful(); assistant->textChanged(doc, range, removedText); if ( wasUseful != assistant->isUseful() ) { changed = true; } } if ( changed ) { Q_EMIT q->problemsChanged(IndexedString(doc->url())); } } void StaticAssistantsManager::notifyAssistants(const IndexedString& url, const KDevelop::ReferencedTopDUContext& context) { Q_FOREACH ( auto assistant, d->m_registeredAssistants ) { assistant->updateReady(url, context); } } QVector KDevelop::StaticAssistantsManager::problemsForContext(const KDevelop::ReferencedTopDUContext& top) { View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view || !top || IndexedString(view->document()->url()) != top->url()) { return {}; } auto doc = top->url(); auto language = ICore::self()->languageController()->languagesForUrl(doc.toUrl()).value(0); if (!language) { return {}; } auto ret = QVector(); qCDebug(LANGUAGE) << "Trying to find assistants for language" << language->name(); foreach (const auto& assistant, d->m_registeredAssistants) { if (assistant->supportedLanguage() != language) continue; if (assistant->isUseful()) { qDebug() << "assistant is now useful:" << assistant.data(); auto p = new KDevelop::StaticAssistantProblem(); auto range = assistant->displayRange(); qDebug() << "range:" << range; p->setFinalLocation(DocumentRange(doc, range)); p->setSource(KDevelop::IProblem::SemanticAnalysis); p->setSeverity(KDevelop::IProblem::Warning); p->setDescription(assistant->title()); p->setSolutionAssistant(IAssistant::Ptr(assistant.data())); ret.append(KDevelop::Problem::Ptr(p)); } } return ret; } #include "moc_staticassistantsmanager.cpp" diff --git a/language/assistant/staticassistantsmanager.h b/language/assistant/staticassistantsmanager.h index 4ea8dd8a5..86033e4ab 100644 --- a/language/assistant/staticassistantsmanager.h +++ b/language/assistant/staticassistantsmanager.h @@ -1,79 +1,77 @@ /* Copyright 2009 David Nolden 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. */ #ifndef KDEVPLATFORM_STATICASSISTANTSMANAGER_H #define KDEVPLATFORM_STATICASSISTANTSMANAGER_H #include #include #include "staticassistant.h" #include #include #include #include #include #include #include -class QTimer; - typedef QPointer SafeDocumentPointer; namespace KDevelop { class IDocument; class DUContext; class TopDUContext; /** * @brief Class managing instances of StaticAssistant * * Invokes the appropriate methods on registered StaticAssistant instances, such as StaticAssistant::textChanged * * @sa StaticAssistant::textChanged */ class KDEVPLATFORMLANGUAGE_EXPORT StaticAssistantsManager : public QObject { Q_OBJECT public: explicit StaticAssistantsManager(QObject* parent = nullptr); ~StaticAssistantsManager() override; void registerAssistant(const StaticAssistant::Ptr assistant); void unregisterAssistant(const StaticAssistant::Ptr assistant); QVector registeredAssistants() const; void notifyAssistants(const IndexedString& url, const KDevelop::ReferencedTopDUContext& context); QVector problemsForContext(const ReferencedTopDUContext& top); signals: void problemsChanged(const IndexedString& url); private: struct Private; QScopedPointer const d; }; } #endif // KDEVPLATFORM_STATICASSISTANTSMANAGER_H diff --git a/language/backgroundparser/backgroundparser.h b/language/backgroundparser/backgroundparser.h index 642b0e128..ab7eb0cb9 100644 --- a/language/backgroundparser/backgroundparser.h +++ b/language/backgroundparser/backgroundparser.h @@ -1,246 +1,239 @@ /* * 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. */ #ifndef KDEVPLATFORM_BACKGROUNDPARSER_H #define KDEVPLATFORM_BACKGROUNDPARSER_H -#include -#include -#include -#include - #include #include #include #include "parsejob.h" -class QMutex; - namespace ThreadWeaver { class Job; class QObjectDecorator; class Weaver; } namespace KDevelop { class DocumentChangeTracker; class IDocument; class IProject; class ILanguageController; class ParseJob; class ParserDependencyPolicy; /** * This class handles the creation of parse jobs for given file URLs. * * For performance reasons you must always use clean, canonical URLs. If you do not do that, * issues might arise (and the debug build will assert). */ class KDEVPLATFORMLANGUAGE_EXPORT BackgroundParser : public QObject, public IStatus { Q_OBJECT Q_INTERFACES( KDevelop::IStatus ) public: explicit BackgroundParser(ILanguageController *languageController); ~BackgroundParser() override; QString statusName() const override; enum { BestPriority = -10000, ///Best possible job-priority. No jobs should actually have this. NormalPriority = 0, ///Standard job-priority. This priority is used for parse-jobs caused by document-editing/opening. ///There is an additional parsing-thread reserved for jobs with this and better priority, to improve responsiveness. InitialParsePriority = 10000, ///Priority used when adding file on project loading WorstPriority = 100000 ///Worst possible job-priority. }; /** * Queries the background parser as to whether there is currently * a parse job for @p document, and if so, returns it. * * This may not contain all of the parse jobs that are intended * unless you call in from your job's ThreadWeaver::Job::aboutToBeQueued() * function. */ Q_SCRIPTABLE ParseJob* parseJobForDocument(const IndexedString& document) const; /** * Set how many ThreadWeaver threads the background parser should set up and use. */ Q_SCRIPTABLE void setThreadCount(int threadCount); /** * Return how many ThreadWeaver threads the background parser should set up and use. */ Q_SCRIPTABLE int threadCount() const; /** * Set the delay in milliseconds before the background parser starts parsing. */ Q_SCRIPTABLE void setDelay(int milliseconds); /** * Returns all documents that were added through addManagedTopRange. This is typically the currently * open documents. */ Q_SCRIPTABLE QList managedDocuments(); /** * Returns the tracker for the given url if the document is being tracked, else returns zero. * This function is thread-safe, but the returned object also isn't, so you must not use it * when you're in a background thread without the foreground lock acquired. * */ DocumentChangeTracker* trackerForUrl(const IndexedString& url) const; Q_SIGNALS: /** * Emitted whenever a document parse-job has finished. * The job contains the du-chain(if one was created) etc. * * The job is deleted after this signal has been emitted. Receivers should not hold * references to it. * * Note that if you want to be get updated for all DUChain updates, use * DUChain::updateReady instead, as a single ParseJob may update multiple * DUChain top contexts. * * @sa DUChain::updateReady */ void parseJobFinished(KDevelop::ParseJob* job); // Implementations of IStatus signals void clearMessage( KDevelop::IStatus* ) override; void showMessage( KDevelop::IStatus*, const QString & message, int timeout = 0) override; void hideProgress( KDevelop::IStatus* ) override; void showProgress( KDevelop::IStatus*, int minimum, int maximum, int value) override; void showErrorMessage( const QString&, int ) override; public Q_SLOTS: /** * Aborts all parse jobs */ void abortAllJobs(); /** * Suspends execution of the background parser */ void suspend(); /** * Resumes execution of the background parser */ void resume(); /// Reverts all requests that were made for the given notification-target. /// priorities and requested features will be reverted as well. /// When @p notifyWhenReady is set to a nullptr, all requests will be reverted. void revertAllRequests(QObject* notifyWhenReady); /** * Queues up the @p url to be parsed. * @p features The minimum features that should be computed for this top-context * @p priority A value that manages the order of parsing. Documents with lowest priority are parsed first. * @param notifyWhenReady An optional pointer to a QObject that should contain a slot * "void updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext)". * The notification is guaranteed to be called once for each call to addDocument. The given top-context * may be invalid if the update failed. * @param flags Flags indicating how the document should be treated in the queue * @param delay_ms The delay in milliseconds to add the job with, or one of the values of the * ILanguageSupport::ReparseDelaySpecialValues enum. */ void addDocument(const IndexedString& url, TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts, int priority = 0, QObject* notifyWhenReady = nullptr, ParseJob::SequentialProcessingFlags flags = ParseJob::IgnoresSequentialProcessing, int delay_ms = ILanguageSupport::DefaultDelay); /** * Removes the @p url that is registered for the given notification from the url. * * @param notifyWhenReady Notifier the document was added with. */ void removeDocument(const IndexedString& url, QObject* notifyWhenReady = nullptr); /** * Forces the current queue to be parsed. */ void parseDocuments(); void updateProgressData(); /// Disables processing for all jobs that have a worse priority than @p priority /// This can be used to temporarily limit the processing to only the most important jobs. /// To only enable processing for important jobs, call setNeededPriority(0). /// This should only be used to temporarily alter the processing. A progress-bar /// will still be shown for the not yet processed jobs. void setNeededPriority(int priority); /// Disables all processing of new jobs, equivalent to setNeededPriority(BestPriority) void disableProcessing(); /// Enables all processing of new jobs, equivalent to setNeededPriority(WorstPriority) void enableProcessing(); /// Returns true if the given url is queued for parsing bool isQueued(const IndexedString& url) const; /// Retrieve the current priority for the given URL. /// You need to check whether @param url is queued before calling this function. int priorityForDocument(const IndexedString& url) const; /// Returns the number of queued jobs (not yet running nor submitted to ThreadWeaver) int queuedCount() const; /// Returns true if there are no jobs running nor queued anywhere bool isIdle() const; void documentClosed(KDevelop::IDocument*); void documentLoaded(KDevelop::IDocument*); void documentUrlChanged(KDevelop::IDocument*); void loadSettings(); protected Q_SLOTS: void parseComplete(const ThreadWeaver::JobPointer& job); void parseProgress(KDevelop::ParseJob*, float value, QString text); void startTimer(int delay); void aboutToQuit(); void updateProgressBar(); private: friend class BackgroundParserPrivate; class BackgroundParserPrivate *d; private Q_SLOTS: /// Tracking of projects in state of loading. void projectAboutToBeOpened(KDevelop::IProject* project); void projectOpened(KDevelop::IProject* project); void projectOpeningAborted(KDevelop::IProject* project); }; } #endif diff --git a/language/backgroundparser/documentchangetracker.cpp b/language/backgroundparser/documentchangetracker.cpp index ec3361209..e1014a570 100644 --- a/language/backgroundparser/documentchangetracker.cpp +++ b/language/backgroundparser/documentchangetracker.cpp @@ -1,471 +1,468 @@ /* * 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(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 = 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/parsejob.cpp b/language/backgroundparser/parsejob.cpp index 4e02d1242..9690437c8 100644 --- a/language/backgroundparser/parsejob.cpp +++ b/language/backgroundparser/parsejob.cpp @@ -1,521 +1,519 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 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 "parsejob.h" #include -#include #include #include -#include #include #include #include #include #include "backgroundparser.h" #include "util/debug.h" #include "duchain/topducontext.h" #include "duchain/duchainlock.h" #include "duchain/duchain.h" #include "duchain/parsingenvironment.h" #include #include #include #include #include #include #include #include using namespace KTextEditor; static QMutex minimumFeaturesMutex; static QHash > staticMinimumFeatures; namespace KDevelop { class ParseJobPrivate { public: ParseJobPrivate(const IndexedString& url_, ILanguageSupport* languageSupport_) : url( url_ ) , languageSupport( languageSupport_ ) , abortRequested( 0 ) , hasReadContents( false ) , aborted( false ) , features( TopDUContext::VisibleDeclarationsAndContexts ) , parsePriority( 0 ) , sequentialProcessingFlags( ParseJob::IgnoresSequentialProcessing ) { } ~ParseJobPrivate() { } ReferencedTopDUContext duContext; IndexedString url; ILanguageSupport* languageSupport; ParseJob::Contents contents; QAtomicInt abortRequested; bool hasReadContents : 1; bool aborted : 1; TopDUContext::Features features; QList > notify; QPointer tracker; RevisionReference revision; RevisionReference previousRevision; int parsePriority; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; }; ParseJob::ParseJob( const IndexedString& url, KDevelop::ILanguageSupport* languageSupport ) : ThreadWeaver::Sequence(), d(new ParseJobPrivate(url, languageSupport)) { } ParseJob::~ParseJob() { typedef QPointer QObjectPointer; foreach(const QObjectPointer &p, d->notify) { if(p) { QMetaObject::invokeMethod(p.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, d->url), Q_ARG(KDevelop::ReferencedTopDUContext, d->duContext)); } } delete d; } ILanguageSupport* ParseJob::languageSupport() const { return d->languageSupport; } void ParseJob::setParsePriority(int priority) { d->parsePriority = priority; } int ParseJob::parsePriority() const { return d->parsePriority; } bool ParseJob::requiresSequentialProcessing() const { return d->sequentialProcessingFlags & RequiresSequentialProcessing; } bool ParseJob::respectsSequentialProcessing() const { return d->sequentialProcessingFlags & RespectsSequentialProcessing; } void ParseJob::setSequentialProcessingFlags(SequentialProcessingFlags flags) { d->sequentialProcessingFlags = flags; } IndexedString ParseJob::document() const { return d->url; } bool ParseJob::success() const { return !d->aborted; } void ParseJob::setMinimumFeatures(TopDUContext::Features features) { d->features = features; } bool ParseJob::hasStaticMinimumFeatures() { QMutexLocker lock(&minimumFeaturesMutex); return !::staticMinimumFeatures.isEmpty(); } TopDUContext::Features ParseJob::staticMinimumFeatures(const IndexedString& url) { QMutexLocker lock(&minimumFeaturesMutex); TopDUContext::Features features = (TopDUContext::Features)0; if(::staticMinimumFeatures.contains(url)) foreach(const TopDUContext::Features f, ::staticMinimumFeatures[url]) features = (TopDUContext::Features)(features | f); return features; } TopDUContext::Features ParseJob::minimumFeatures() const { return (TopDUContext::Features)(d->features | staticMinimumFeatures(d->url)); } void ParseJob::setDuChain(ReferencedTopDUContext duChain) { d->duContext = duChain; } ReferencedTopDUContext ParseJob::duChain() const { return d->duContext; } bool ParseJob::abortRequested() const { return d->abortRequested.load(); } void ParseJob::requestAbort() { d->abortRequested = 1; } void ParseJob::abortJob() { d->aborted = true; setStatus(Status_Aborted); } void ParseJob::setNotifyWhenReady(const QList >& notify ) { d->notify = notify; } void ParseJob::setStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].append(features); } void ParseJob::unsetStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].removeOne(features); if(::staticMinimumFeatures[url].isEmpty()) ::staticMinimumFeatures.remove(url); } KDevelop::ProblemPointer ParseJob::readContents() { Q_ASSERT(!d->hasReadContents); d->hasReadContents = true; QString localFile(document().toUrl().toLocalFile()); QFileInfo fileInfo( localFile ); QDateTime lastModified = fileInfo.lastModified(); d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); //Try using an artificial code-representation, which overrides everything else if(artificialCodeRepresentationExists(document())) { CodeRepresentation::Ptr repr = createCodeRepresentation(document()); d->contents.contents = repr->text().toUtf8(); qCDebug(LANGUAGE) << "took contents for " << document().str() << " from artificial code-representation"; return KDevelop::ProblemPointer(); } bool hadTracker = false; if(d->tracker) { ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { // The file is open in an editor d->previousRevision = t->revisionAtLastReset(); t->reset(); // Reset the tracker to the current revision Q_ASSERT(t->revisionAtLastReset()); d->contents.contents = t->document()->text().toUtf8(); d->contents.modification = KDevelop::ModificationRevision( lastModified, t->revisionAtLastReset()->revision() ); d->revision = t->acquireRevision(d->contents.modification.revision); hadTracker = true; } } if (!hadTracker) { // We have to load the file from disk static const int maximumFileSize = 5 * 1024 * 1024; // 5 MB if (fileInfo.size() > maximumFileSize) { KFormat f; KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18nc("%1: filename", "Skipped file that is too large: '%1'", localFile )); p->setExplanation(i18nc("%1: file size, %2: limit file size", "The file is %1 and exceeds the limit of %2.", f.formatByteSize(fileInfo.size()), f.formatByteSize(maximumFileSize))); p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << p->description() << p->explanation(); return p; } QFile file( localFile ); if ( !file.open( QIODevice::ReadOnly ) ) { KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18n( "Could not open file '%1'", localFile )); switch (file.error()) { case QFile::ReadError: p->setExplanation(i18n("File could not be read from disk.")); break; case QFile::OpenError: p->setExplanation(i18n("File could not be opened.")); break; case QFile::PermissionsError: p->setExplanation(i18n("File could not be read from disk due to permissions.")); break; default: break; } p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << "Could not open file" << document().str() << "(path" << localFile << ")" ; return p; } d->contents.contents = file.readAll(); ///@todo Convert from local encoding to utf-8 if they don't match d->contents.modification = KDevelop::ModificationRevision(lastModified); file.close(); } return KDevelop::ProblemPointer(); } const KDevelop::ParseJob::Contents& ParseJob::contents() const { Q_ASSERT(d->hasReadContents); return d->contents; } struct MovingRangeTranslator : public DUChainVisitor { MovingRangeTranslator(qint64 _source, qint64 _target, MovingInterface* _moving) : source(_source), target(_target), moving(_moving) { } void visit(DUContext* context) override { translateRange(context); ///@todo Also map import-positions // Translate uses uint usesCount = context->usesCount(); for(uint u = 0; u < usesCount; ++u) { RangeInRevision r = context->uses()[u].m_range; translateRange(r); context->changeUseRange(u, r); } } void visit(Declaration* declaration) override { translateRange(declaration); } void translateRange(DUChainBase* object) { RangeInRevision r = object->range(); translateRange(r); object->setRange(r); } void translateRange(RangeInRevision& r) { // PHP and python use top contexts that start at (0, 0) end at INT_MAX, so make sure that doesn't overflow // or translate the start of the top context away from (0, 0) if ( r.start.line != 0 || r.start.column != 0 ) { moving->transformCursor(r.start.line, r.start.column, MovingCursor::MoveOnInsert, source, target); } if ( r.end.line != std::numeric_limits::max() || r.end.column != std::numeric_limits::max() ) { moving->transformCursor(r.end.line, r.end.column, MovingCursor::StayOnInsert, source, target); } } KTextEditor::Range range; qint64 source; qint64 target; MovingInterface* moving; }; void ParseJob::translateDUChainToRevision(TopDUContext* context) { qint64 targetRevision = d->contents.modification.revision; if(targetRevision == -1) { qCDebug(LANGUAGE) << "invalid target revision" << targetRevision; return; } qint64 sourceRevision; { DUChainReadLocker duChainLock; Q_ASSERT(context->parsingEnvironmentFile()); // Cannot map if there is no source revision sourceRevision = context->parsingEnvironmentFile()->modificationRevision().revision; if(sourceRevision == -1) { qCDebug(LANGUAGE) << "invalid source revision" << sourceRevision; return; } } if(sourceRevision > targetRevision) { qCDebug(LANGUAGE) << "for document" << document().str() << ": source revision is higher than target revision:" << sourceRevision << " > " << targetRevision; return; } ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { if(!d->previousRevision) { qCDebug(LANGUAGE) << "not translating because there is no valid predecessor-revision"; return; } if(sourceRevision != d->previousRevision->revision() || !d->previousRevision->valid()) { qCDebug(LANGUAGE) << "not translating because the document revision does not match the tracker start revision (maybe the document was cleared)"; return; } if(!t->holdingRevision(sourceRevision) || !t->holdingRevision(targetRevision)) { qCDebug(LANGUAGE) << "lost one of the translation revisions, not doing the map"; return; } // Perform translation MovingInterface* moving = t->documentMovingInterface(); DUChainWriteLocker wLock; MovingRangeTranslator translator(sourceRevision, targetRevision, moving); context->visit(translator); QList< ProblemPointer > problems = context->problems(); for(QList< ProblemPointer >::iterator problem = problems.begin(); problem != problems.end(); ++problem) { RangeInRevision r = (*problem)->range(); translator.translateRange(r); (*problem)->setRange(r); } // Update the modification revision in the meta-data ModificationRevision modRev = context->parsingEnvironmentFile()->modificationRevision(); modRev.revision = targetRevision; context->parsingEnvironmentFile()->setModificationRevision(modRev); } } bool ParseJob::isUpdateRequired(const IndexedString& languageString) { if (abortRequested()) { return false; } if (minimumFeatures() & TopDUContext::ForceUpdate) { return true; } DUChainReadLocker lock; if (abortRequested()) { return false; } foreach(const ParsingEnvironmentFilePointer &file, DUChain::self()->allEnvironmentFiles(document())) { if (file->language() != languageString) { continue; } if (!file->needsUpdate(environment()) && file->featuresSatisfied(minimumFeatures())) { qCDebug(LANGUAGE) << "Already up to date" << document().str(); setDuChain(file->topContext()); lock.unlock(); highlightDUChain(); return false; } break; } return !abortRequested(); } const ParsingEnvironment* ParseJob::environment() const { return nullptr; } void ParseJob::highlightDUChain() { ENSURE_CHAIN_NOT_LOCKED if (!d->languageSupport->codeHighlighting() || !duChain() || abortRequested()) { // language doesn't support highlighting return; } if (!d->hasReadContents && !d->tracker) { d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); } if (d->tracker) { d->languageSupport->codeHighlighting()->highlightDUChain(duChain()); } } ControlFlowGraph* ParseJob::controlFlowGraph() { return nullptr; } DataAccessRepository* ParseJob::dataAccessInformation() { return nullptr; } bool ParseJob::hasTracker() const { return d->tracker; } } diff --git a/language/backgroundparser/tests/testlanguagesupport.cpp b/language/backgroundparser/tests/testlanguagesupport.cpp index 4f59cc6d8..3759e3a79 100644 --- a/language/backgroundparser/tests/testlanguagesupport.cpp +++ b/language/backgroundparser/tests/testlanguagesupport.cpp @@ -1,46 +1,44 @@ /* * 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 "testlanguagesupport.h" #include "testparsejob.h" #include "test_backgroundparser.h" -#include - using namespace KDevelop; ParseJob* TestLanguageSupport::createParseJob(const IndexedString& url) { qDebug() << "creating test language support parse job"; ParseJob* job = nullptr; emit aboutToCreateParseJob(url, &job); if (!job) { job = new TestParseJob(url, this); } emit parseJobCreated(job); return job; } QString TestLanguageSupport::name() const { return QStringLiteral("TestLanguageSupport"); } diff --git a/language/checks/controlflowgraph.h b/language/checks/controlflowgraph.h index 107db6b02..e72bd75ff 100644 --- a/language/checks/controlflowgraph.h +++ b/language/checks/controlflowgraph.h @@ -1,78 +1,77 @@ /* 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. */ #ifndef KDEVPLATFORM_CONTROLFLOWGRAPH_H #define KDEVPLATFORM_CONTROLFLOWGRAPH_H #include -#include #include namespace KDevelop { class Declaration; class ControlFlowNode; /** * @brief The ControlFlowGraph describes the way a code interacts with the current state of a system * * This class will store the information regarding how is the code flow going to change depending * on what current state we have in our system. It will tell us what different code paths we have * available by listing them in different ways and it will let us know what those paths depend on * so that we can analyze it. */ class KDEVPLATFORMLANGUAGE_EXPORT ControlFlowGraph { public: /** Creates an empty graph. */ ControlFlowGraph(); ~ControlFlowGraph(); /** Adds an entry @p n to the graph. The graph takes the ownership of @p n */ void addEntry(KDevelop::ControlFlowNode* n); /** Adds an entry @p n to the graph given @p decl declaration. The graph takes the ownership of @p n */ void addEntry(KDevelop::Declaration* d, KDevelop::ControlFlowNode* n); /** Adds a node that does belong to the graph but that can't be accessed by any means. The graph takes the ownership of @p n */ void addDeadNode(ControlFlowNode* n); /** Clears the current graph as if it was just constructed */ void clear(); /** @returns all declarations that have a node attached to */ QList declarations() const; /** @returns the node attached to the declaration @p d*/ ControlFlowNode* nodeForDeclaration(KDevelop::Declaration* d) const; /** @returns all root nodes in the graph */ QList rootNodes() const; /** @returns all dead nodes in the graph */ QVector deadNodes() const; private: ControlFlowGraph(const ControlFlowGraph&); struct Private; Private* d; }; } #endif diff --git a/language/classmodel/classmodelnode.h b/language/classmodel/classmodelnode.h index bf1b1e561..9c46c06b6 100644 --- a/language/classmodel/classmodelnode.h +++ b/language/classmodel/classmodelnode.h @@ -1,332 +1,331 @@ /* * 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. */ #ifndef KDEVPLATFORM_CLASSMODELNODE_H #define KDEVPLATFORM_CLASSMODELNODE_H #include "classmodel.h" #include #include "../duchain/identifier.h" #include "../duchain/duchainpointer.h" #include "classmodelnodescontroller.h" -class QTimer; class NodesModelInterface; namespace KDevelop { class ClassDeclaration; class ClassFunctionDeclaration; class ClassMemberDeclaration; class Declaration; } namespace ClassModelNodes { /// Base node class - provides basic functionality. class Node { public: Node(const QString& a_displayName, NodesModelInterface* a_model); virtual ~Node(); public: // Operations /// Clear all the children from the node. void clear(); /// Called by the model to collapse the node and remove sub-items if needed. virtual void collapse() {}; /// Called by the model to expand the node and populate it with sub-nodes if needed. virtual void expand() {}; /// Append a new child node to the list. void addNode(Node* a_child); /// Remove child node from the list and delete it. void removeNode(Node* a_child); /// Remove this node and delete it. void removeSelf() { m_parentNode->removeNode(this); } /// Called once the node has been populated to sort the entire tree / branch. void recursiveSort(); public: // Info retrieval /// Return the parent associated with this node. Node* getParent() const { return m_parentNode; } /// Get my index in the parent node int row(); /// Return the display name for the node. QString displayName() const { return m_displayName; } /// Returns a list of child nodes const QList& getChildren() const { return m_children; } /// Return an icon representation for the node. /// @note It calls the internal getIcon and caches the result. QIcon getCachedIcon(); public: // overridables /// Return a score when sorting the nodes. virtual int getScore() const = 0; /// Return true if the node contains sub-nodes. virtual bool hasChildren() const { return !m_children.empty(); } /// We use this string when sorting items. virtual QString getSortableString() const { return m_displayName; } protected: /// fill a_resultIcon with a display icon for the node. /// @param a_resultIcon returned icon. /// @return true if result was returned. virtual bool getIcon(QIcon& a_resultIcon) = 0; private: Node* m_parentNode; /// Called once the node has been populated to sort the entire tree / branch. void recursiveSortInternal(); protected: typedef QList< Node* > NodesList; NodesList m_children; QString m_displayName; QIcon m_cachedIcon; NodesModelInterface* m_model; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Base class for nodes that generate and populate their child nodes dynamically class DynamicNode : public Node { public: DynamicNode(const QString& a_displayName, NodesModelInterface* a_model); /// Return true if the node was populated already. bool isPopulated() const { return m_populated; } /// Populate the node and mark the flag - called from expand or can be used internally. void performPopulateNode(bool a_forceRepopulate = false); public: // Node overrides. void collapse() override; void expand() override; bool hasChildren() const override; protected: // overridables /// Called by the framework when the node is about to be expanded /// it should be populated with sub-nodes if applicable. virtual void populateNode() {} /// Called after the nodes have been removed. /// It's for derived classes to clean cached data. virtual void nodeCleared() {} private: bool m_populated; /// Clear all the child nodes and mark flag. void performNodeCleanup(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Base class for nodes associated with a @ref KDevelop::QualifiedIdentifier class IdentifierNode : public DynamicNode { public: IdentifierNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model, const QString& a_displayName = QString()); public: /// Returns the qualified identifier for this node by going through the tree const KDevelop::IndexedQualifiedIdentifier& getIdentifier() const { return m_identifier; } public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; public: // Overridables /// Return the associated declaration /// @note DU CHAIN MUST BE LOCKED FOR READ virtual KDevelop::Declaration* getDeclaration(); private: KDevelop::IndexedQualifiedIdentifier m_identifier; KDevelop::IndexedDeclaration m_indexedDeclaration; KDevelop::DeclarationPointer m_cachedDeclaration; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// A node that represents an enum value. class EnumNode : public IdentifierNode { public: EnumNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); public: // Node overrides int getScore() const override { return 102; } bool getIcon(QIcon& a_resultIcon) override; void populateNode() override; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides display for a single class. class ClassNode : public IdentifierNode, public ClassModelNodeDocumentChangedInterface { public: ClassNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); ~ClassNode() override; /// Lookup a contained class and return the related node. /// @return the node pointer or 0 if non was found. ClassNode* findSubClass(const KDevelop::IndexedQualifiedIdentifier& a_id); public: // Node overrides int getScore() const override { return 300; } void populateNode() override; void nodeCleared() override; bool hasChildren() const override { return true; } protected: // ClassModelNodeDocumentChangedInterface overrides void documentChanged(const KDevelop::IndexedString& a_file) override; private: typedef QMap< uint, Node* > SubIdentifiersMap; /// Set of known sub-identifiers. It's used for updates check. SubIdentifiersMap m_subIdentifiers; /// We use this variable to know if we've registered for change notification or not. KDevelop::IndexedString m_cachedUrl; /// Updates the node to reflect changes in the declaration. /// @note DU CHAIN MUST BE LOCKED FOR READ /// @return true if something was updated. bool updateClassDeclarations(); /// Add "Base classes" and "Derived classes" folders, if needed /// @return true if one of the folders was added. bool addBaseAndDerived(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a display for a single class function. class FunctionNode : public IdentifierNode { public: FunctionNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); public: // Node overrides int getScore() const override { return 400; } QString getSortableString() const override { return m_sortableString; } private: QString m_sortableString; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides display for a single class variable. class ClassMemberNode : public IdentifierNode { public: ClassMemberNode(KDevelop::ClassMemberDeclaration* a_decl, NodesModelInterface* a_model); public: // Node overrides int getScore() const override { return 500; } bool getIcon(QIcon& a_resultIcon) override; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a folder node with a static list of nodes. class FolderNode : public Node { public: FolderNode(const QString& a_displayName, NodesModelInterface* a_model); public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; int getScore() const override { return 100; } }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a folder node with a dynamic list of nodes. class DynamicFolderNode : public DynamicNode { public: DynamicFolderNode(const QString& a_displayName, NodesModelInterface* a_model); public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; int getScore() const override { return 100; } }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Special folder - the parent is assumed to be a ClassNode. /// It then displays the base classes for the class it sits in. class BaseClassesFolderNode : public DynamicFolderNode { public: explicit BaseClassesFolderNode(NodesModelInterface* a_model); public: // Node overrides void populateNode() override; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Special folder - the parent is assumed to be a ClassNode. /// It then displays list of derived classes from the parent class. class DerivedClassesFolderNode : public DynamicFolderNode { public: explicit DerivedClassesFolderNode(NodesModelInterface* a_model); public: // Node overrides void populateNode() override; }; } // namespace classModelNodes #endif // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/codecompletion/codecompletion.h b/language/codecompletion/codecompletion.h index ddafc9f69..c8dd44cfb 100644 --- a/language/codecompletion/codecompletion.h +++ b/language/codecompletion/codecompletion.h @@ -1,79 +1,78 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006 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. */ #ifndef KDEVPLATFORM_CODECOMPLETION_H #define KDEVPLATFORM_CODECOMPLETION_H #include #include -namespace KParts { class Part; } namespace KTextEditor { class Document; class View; class CodeCompletionModel; } namespace KDevelop { class IDocument; class ILanguage; // TODO: cleanup this class for 5.1 class KDEVPLATFORMLANGUAGE_EXPORT CodeCompletion : public QObject { Q_OBJECT public: /** CodeCompletion will be the @p aModel parent. * If @p language is empty, the completion model will work for all files, * otherwise only for ones that contain the selected language. */ CodeCompletion(QObject* parent, KTextEditor::CodeCompletionModel* aModel, const QString& language); ~CodeCompletion() override; private Q_SLOTS: void textDocumentCreated(KDevelop::IDocument*); void viewCreated(KTextEditor::Document *document, KTextEditor::View *view); void documentUrlChanged(KDevelop::IDocument*); /** * check already opened documents, * needs to be done via delayed call to prevent infinite loop in * checkDocument() -> load lang plugin -> register CodeCompletion -> checkDocument() -> ... */ void checkDocuments(); signals: void registeredToView(KTextEditor::View* view); void unregisteredFromView(KTextEditor::View* view); private: void unregisterDocument(KTextEditor::Document*); void checkDocument(KTextEditor::Document*); KTextEditor::CodeCompletionModel* m_model; QString m_language; }; } #endif diff --git a/language/codecompletion/codecompletionmodel.cpp b/language/codecompletion/codecompletionmodel.cpp index 5fc18add1..f90d7ddb7 100644 --- a/language/codecompletion/codecompletionmodel.cpp +++ b/language/codecompletion/codecompletionmodel.cpp @@ -1,445 +1,441 @@ /* * 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 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: explicit CompletionWorkerThread(CodeCompletionModel* model) : QThread(model), m_model(model), m_worker(m_model->createCompletionWorker()) { 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(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) { qCDebug(LANGUAGE) << "================== NO CONTEXT FOUND ======================="; beginResetModel(); m_completionItems.clear(); m_navigationWidgets.clear(); endResetModel(); qCDebug(LANGUAGE) << "Completion invoked for unknown context. Document:" << url << ", Known documents:" << DUChain::self()->documents(); return; } setCurrentTopContext(TopDUContextPointer(top)); RangeInRevision rangeInRevision = top->transformToLocalRevision(KTextEditor::Range(range)); 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."; } 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(); } else { thisContext = top; } qCDebug(LANGUAGE) << "context is set to" << thisContext.data(); } lock.unlock(); if(m_forceWaitForModel) emit waitForReset(); emit completionsNeeded(thisContext, range.start(), view); } 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/codecompletionmodel.h b/language/codecompletion/codecompletionmodel.h index d304252e6..bbeda7e67 100644 --- a/language/codecompletion/codecompletionmodel.h +++ b/language/codecompletion/codecompletionmodel.h @@ -1,151 +1,149 @@ /* * 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. */ #ifndef KDEVPLATFORM_CODECOMPLETIONMODEL_H #define KDEVPLATFORM_CODECOMPLETIONMODEL_H #include #include #include #include #include #include "../duchain/duchainpointer.h" #include #include "codecompletioncontext.h" #include "codecompletionitem.h" #include #include #include -class QIcon; -class QString; class QMutex; namespace KDevelop { class DUContext; class Declaration; class CodeCompletionWorker; class CompletionWorkerThread; class KDEVPLATFORMLANGUAGE_EXPORT CodeCompletionModel : public KTextEditor::CodeCompletionModel , public KTextEditor::CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: explicit CodeCompletionModel(QObject* parent); ~CodeCompletionModel() override; ///This MUST be called after the creation of this completion-model. ///If you use use the KDevelop::CodeCompletion helper-class, that one cares about it. virtual void initialize(); ///Entry-point for code-completion. This determines ome settings, clears the model, and then calls completionInvokedInternal for further processing. void completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, KTextEditor::CodeCompletionModel::InvocationType invocationType) override; QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount ( const QModelIndex & parent = QModelIndex() ) const override; QModelIndex parent ( const QModelIndex & index ) const override; ///Use this to set whether the code-completion widget should wait for this model until it's shown ///This makes sense when the model takes some time but not too much time, to make the UI less flickering and ///annoying. ///The default is false ///@todo Remove this option, make it true by default, and make sure in CodeCompletionWorker that the whole thing cannot break void setForceWaitForModel(bool wait); bool forceWaitForModel(); ///Convenience-storage for use by the inherited completion model void setCompletionContext(QExplicitlySharedDataPointer completionContext); QExplicitlySharedDataPointer completionContext() const; ///Convenience-storage for use by the inherited completion model KDevelop::TopDUContextPointer currentTopContext() const; void setCurrentTopContext(KDevelop::TopDUContextPointer topContext); ///Tracks navigation widget so they can be interactive with through the keyboard later on void addNavigationWidget(const CompletionTreeElement* element, QWidget* widget) const; ///Whether the completion should be fully detailed. If false, it should be simplifed, so no argument-hints, ///no expanding information, no type-information, etc. bool fullCompletion() const; MatchReaction matchingItem(const QModelIndex& matched) override; QString filterString(KTextEditor::View* view, const KTextEditor::Range& range, const KTextEditor::Cursor& position) override; void clear(); ///Returns the tree-element that belogns to the index, or zero QExplicitlySharedDataPointer itemForIndex(QModelIndex index) const; Q_SIGNALS: ///Connection from this completion-model into the background worker thread. You should emit this from within completionInvokedInternal. void completionsNeeded(KDevelop::DUContextPointer context, const KTextEditor::Cursor& position, KTextEditor::View* view); ///Additional signal that allows directly stepping into the worker-thread, bypassing computeCompletions(..) etc. ///doSpecialProcessing(data) will be executed in the background thread. void doSpecialProcessingInBackground(uint data); protected Q_SLOTS: ///Connection from the background-thread into the model: This is called when the background-thread is ready virtual void foundDeclarations(const QList>& item, const QExplicitlySharedDataPointer& completionContext); protected: ///Eventually override this, determine the context or whatever, and then emit completionsNeeded(..) to continue processing in the background tread. ///The default-implementation does this completely, so if you don't need to do anything special, you can just leave it. virtual void completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, KTextEditor::CodeCompletionModel::InvocationType invocationType, const QUrl& url); void executeCompletionItem(KTextEditor::View* view, const KTextEditor::Range& word, const QModelIndex& index) const override; QExplicitlySharedDataPointer m_completionContext; typedef QPair > DeclarationContextPair; mutable QMap > m_navigationWidgets; QList< QExplicitlySharedDataPointer > m_completionItems; /// Should create a completion-worker. The worker must have no parent object, /// because else its thread-affinity can not be changed. virtual CodeCompletionWorker* createCompletionWorker() = 0; friend class CompletionWorkerThread; CodeCompletionWorker* worker() const; private: bool m_forceWaitForModel; bool m_fullCompletion; QMutex* m_mutex; CompletionWorkerThread* m_thread; QString m_filterString; KDevelop::TopDUContextPointer m_currentTopContext; }; } #endif diff --git a/language/codecompletion/codecompletiontesthelper.h b/language/codecompletion/codecompletiontesthelper.h index 80f2a9cc8..cc549b612 100644 --- a/language/codecompletion/codecompletiontesthelper.h +++ b/language/codecompletion/codecompletiontesthelper.h @@ -1,234 +1,234 @@ /* 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. */ #ifndef KDEVPLATFORM_CODECOMPLETIONTESTHELPER_H #define KDEVPLATFORM_CODECOMPLETIONTESTHELPER_H -#include +#include #include #include "../duchain/declaration.h" #include "../duchain/duchain.h" #include "codecompletionitem.h" #include #include #include #include using namespace KTextEditor; using namespace KDevelop; /** * Helper-class for testing completion-items * Just initialize it with the context and the text, and then use the members, for simple cases only "names" * the template parameter is your language specific CodeCompletionContext */ template struct CodeCompletionItemTester { using Element = QExplicitlySharedDataPointer; using Item = QExplicitlySharedDataPointer; using Context = QExplicitlySharedDataPointer; //Standard constructor CodeCompletionItemTester(DUContext* context, const QString& text = "; ", const QString& followingText = QString(), const CursorInRevision& position = CursorInRevision::invalid()) : completionContext(new T(DUContextPointer(context), text, followingText, position.isValid() ? position : context->range().end)) { init(); } //Can be used if you already have the completion context CodeCompletionItemTester(const Context& context) : completionContext(context) { init(); } //Creates a CodeCompletionItemTester for the parent context CodeCompletionItemTester parent() const { Context parent = Context(dynamic_cast(completionContext->parentContext())); Q_ASSERT(parent); return CodeCompletionItemTester(parent); } void addElements(const QList& elements) { foreach(Element element, elements) { Item item(dynamic_cast(element.data())); if(item) items << item; CompletionTreeNode* node = dynamic_cast(element.data()); if(node) addElements(node->children); } } bool containsDeclaration(Declaration* dec) const { foreach(Item item, items) { if (item->declaration().data() == dec) { return true; } } return false; } QList items; // All items retrieved QStringList names; // Names of all completion-items Context completionContext; //Convenience-function to retrieve data from completion-items by name QVariant itemData(QString itemName, int column = KTextEditor::CodeCompletionModel::Name, int role = Qt::DisplayRole) const { return itemData(names.indexOf(itemName), column, role); } QVariant itemData(int itemNumber, int column = KTextEditor::CodeCompletionModel::Name, int role = Qt::DisplayRole) const { if(itemNumber < 0 || itemNumber >= items.size()) return QVariant(); return itemData(items[itemNumber], column, role); } QVariant itemData(Item item, int column = KTextEditor::CodeCompletionModel::Name, int role = Qt::DisplayRole) const { return item->data(fakeModel().index(0, column), role, 0); } Item findItem(const QString& itemName) const { const auto idx = names.indexOf(itemName); if (idx < 0) { return {}; } return items[idx]; } private: void init() { if ( !completionContext || !completionContext->isValid() ) { qWarning() << "invalid completion context"; return; } bool abort = false; items = completionContext->completionItems(abort); addElements(completionContext->ungroupedElements()); foreach(Item i, items) { names << i->data(fakeModel().index(0, KTextEditor::CodeCompletionModel::Name), Qt::DisplayRole, 0).toString(); } } static QStandardItemModel& fakeModel() { static QStandardItemModel model; model.setColumnCount(10); model.setRowCount(10); return model; } }; /** * Helper class that inserts the given text into the duchain under the specified name, * allows parsing it with a simple call to parse(), and automatically releases the top-context * * The duchain must not be locked when this object is destroyed */ struct InsertIntoDUChain { ///Artificially inserts a file called @p name with the text @p text InsertIntoDUChain(QString name, QString text) : m_insertedCode(IndexedString(name), text), m_topContext(0) { } ~InsertIntoDUChain() { get(); release(); } ///The duchain must not be locked when this is called void release() { if(m_topContext) { DUChainWriteLocker lock; m_topContext = 0; QList< TopDUContext* > chains = DUChain::self()->chainsForDocument(m_insertedCode.file()); foreach(TopDUContext* top, chains) DUChain::self()->removeDocumentChain(top); } } TopDUContext* operator->() { get(); return m_topContext.data(); } TopDUContext* tryGet() { DUChainReadLocker lock; return DUChain::self()->chainForDocument(m_insertedCode.file(), false); } void get() { if(!m_topContext) m_topContext = tryGet(); } ///Helper function: get a declaration based on its qualified identifier Declaration* getDeclaration(QString id) { get(); if(!topContext()) return 0; return DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(id))).getDeclaration(topContext()); } TopDUContext* topContext() { return m_topContext.data(); } /** * Parses this inserted code as a stand-alone top-context * The duchain must not be locked when this is called * * @param features The features that should be requested for the top-context * @param update Whether the top-context should be updated if it already exists. Else it will be deleted. */ void parse(uint features = TopDUContext::AllDeclarationsContextsAndUses, bool update = false) { if(!update) release(); m_topContext = DUChain::self()->waitForUpdate(m_insertedCode.file(), (TopDUContext::Features)features, false); Q_ASSERT(m_topContext); DUChainReadLocker lock; Q_ASSERT(!m_topContext->parsingEnvironmentFile()->isProxyContext()); } InsertArtificialCodeRepresentation m_insertedCode; ReferencedTopDUContext m_topContext; }; #endif // KDEVPLATFORM_CODECOMPLETIONTESTHELPER_H diff --git a/language/codecompletion/codecompletionworker.h b/language/codecompletion/codecompletionworker.h index d0306a4e7..bd4ca8f06 100644 --- a/language/codecompletion/codecompletionworker.h +++ b/language/codecompletion/codecompletionworker.h @@ -1,111 +1,110 @@ /* * 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. */ #ifndef KDEVPLATFORM_CODECOMPLETIONWORKER_H #define KDEVPLATFORM_CODECOMPLETIONWORKER_H -#include #include #include #include "../duchain/duchainpointer.h" #include "../codecompletion/codecompletioncontext.h" class QMutex; namespace KTextEditor { class Range; class View; class Cursor; } namespace KDevelop { class CompletionTreeElement; class CodeCompletionModel; class KDEVPLATFORMLANGUAGE_EXPORT CodeCompletionWorker : public QObject { Q_OBJECT public: explicit CodeCompletionWorker(CodeCompletionModel* model); ~CodeCompletionWorker() override; virtual void abortCurrentCompletion(); void setFullCompletion(bool); bool fullCompletion() const; KDevelop::CodeCompletionModel* model() const; ///When this is called, the result is shown in the completion-list. ///Call this from within your code void foundDeclarations(const QList>&, const CodeCompletionContext::Ptr& completionContext); Q_SIGNALS: ///Internal connections into the foreground completion model void foundDeclarationsReal(const QList>&, const QExplicitlySharedDataPointer& completionContext); protected: virtual void computeCompletions(DUContextPointer context, const KTextEditor::Cursor& position, QString followingText, const KTextEditor::Range& contextRange, const QString& contextText); ///This can be overridden to compute an own grouping in the completion-list. ///The default implementation groups items in a way that improves the efficiency of the completion-model, thus the default-implementation should be preferred. virtual QList > computeGroups(QList items, QExplicitlySharedDataPointer completionContext); ///If you don't need to reimplement computeCompletions, you can implement only this. virtual KDevelop::CodeCompletionContext* createCompletionContext(KDevelop::DUContextPointer context, const QString &contextText, const QString &followingText, const CursorInRevision &position) const; ///Override this to change the text-range which is used as context-information for the completion context ///The foreground-lock and a DUChain read lock are held when this is called virtual void updateContextRange(KTextEditor::Range& contextRange, KTextEditor::View* view, DUContextPointer context) const; ///Can be used to retrieve and set the aborting flag(Enabling it is equivalent to caling abortCompletion()) ///Is always reset from within computeCompletions bool& aborting(); ///Emits foundDeclarations() with an empty list. Always call this when you abort the process of computing completions void failed(); public Q_SLOTS: ///Connection from the foreground thread within CodeCompletionModel void computeCompletions(KDevelop::DUContextPointer context, const KTextEditor::Cursor& position, KTextEditor::View* view); ///This can be used to do special processing within the background, completely bypassing the normal computeCompletions(..) etc. system. ///It will be executed within the background when the model emits doSpecialProcessingInBackground virtual void doSpecialProcessing(uint data); private: bool m_hasFoundDeclarations; QMutex* m_mutex; bool m_abort; bool m_fullCompletion; KDevelop::CodeCompletionModel* m_model; }; } #endif // KDEVPLATFORM_CODECOMPLETIONWORKER_H diff --git a/language/codegen/archivetemplateloader.cpp b/language/codegen/archivetemplateloader.cpp index b514dfced..d720385cb 100644 --- a/language/codegen/archivetemplateloader.cpp +++ b/language/codegen/archivetemplateloader.cpp @@ -1,113 +1,109 @@ /* 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 "archivetemplateloader.h" #include "templateengine.h" #include #include -#include -#include - -#include using namespace KDevelop; class KDevelop::ArchiveTemplateLoaderPrivate { public: QList locations; }; ArchiveTemplateLoader* ArchiveTemplateLoader::self() { static ArchiveTemplateLoader* loader = new ArchiveTemplateLoader; return loader; } ArchiveTemplateLoader::ArchiveTemplateLoader() : d(new ArchiveTemplateLoaderPrivate) { } ArchiveTemplateLoader::~ArchiveTemplateLoader() { delete d; } void ArchiveTemplateLoader::addLocation(ArchiveTemplateLocation* location) { d->locations.append(location); } void ArchiveTemplateLoader::removeLocation(ArchiveTemplateLocation* location) { d->locations.removeOne(location); } bool ArchiveTemplateLoader::canLoadTemplate(const QString& name) const { foreach(ArchiveTemplateLocation* location, d->locations) { if (location->hasTemplate(name)) { return true; } } return false; } Grantlee::Template ArchiveTemplateLoader::loadByName(const QString& name, const Grantlee::Engine* engine) const { foreach(ArchiveTemplateLocation* location, d->locations) { if (location->hasTemplate(name)) { return engine->newTemplate(location->templateContents(name), name); } } return Grantlee::Template(); } QPair< QString, QString > ArchiveTemplateLoader::getMediaUri(const QString& fileName) const { Q_UNUSED(fileName); return QPair(); } ArchiveTemplateLocation::ArchiveTemplateLocation(const KArchiveDirectory* directory) : m_directory(directory) { ArchiveTemplateLoader::self()->addLocation(this); } ArchiveTemplateLocation::~ArchiveTemplateLocation() { ArchiveTemplateLoader::self()->removeLocation(this); } bool ArchiveTemplateLocation::hasTemplate(const QString& name) const { return m_directory->entry(name) && m_directory->entry(name)->isFile(); } QString ArchiveTemplateLocation::templateContents(const QString& name) const { const KArchiveFile* file = dynamic_cast(m_directory->entry(name)); Q_ASSERT(file); return file->data(); } diff --git a/language/codegen/codegenerator.cpp b/language/codegen/codegenerator.cpp index e399965d7..31f7d6d9d 100644 --- a/language/codegen/codegenerator.cpp +++ b/language/codegen/codegenerator.cpp @@ -1,242 +1,241 @@ /* Copyright 2008 Hamish Rodda Copyright 2009 Ramon Zarazua 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 "codegenerator.h" #include "documentchangeset.h" #include "duchainchangeset.h" -#include #include #include #include #include #include "applychangeswidget.h" #include "util/debug.h" namespace KDevelop { class CodeGeneratorPrivate { public: CodeGeneratorPrivate() : autoGen(false), context(0) {} QMap duchainChanges; DocumentChangeSet documentChanges; bool autoGen; DUContext * context; DocumentRange range; QString error; }; CodeGeneratorBase::CodeGeneratorBase() : d(new CodeGeneratorPrivate) { } CodeGeneratorBase::~CodeGeneratorBase() { clearChangeSets(); delete d; } void CodeGeneratorBase::autoGenerate ( DUContext* context, const KDevelop::DocumentRange* range ) { d->autoGen = true; d->context = context; d->range = *range; } void CodeGeneratorBase::addChangeSet(DUChainChangeSet * duchainChange) { IndexedString file = duchainChange->topDuContext().data()->url() ; QMap::iterator it = d->duchainChanges.find(file); //if we already have an entry for this file, merge it if(it !=d->duchainChanges.end()) { **it << *duchainChange; delete duchainChange; } else d->duchainChanges.insert(file, duchainChange); } void CodeGeneratorBase::addChangeSet(DocumentChangeSet & docChangeSet) { d->documentChanges << docChangeSet; } DocumentChangeSet & CodeGeneratorBase::documentChangeSet() { return d->documentChanges; } const QString & CodeGeneratorBase::errorText() const { return d->error; } bool CodeGeneratorBase::autoGeneration() const { return d->autoGen; } void CodeGeneratorBase::setErrorText(const QString & errorText) { d->error = errorText; } void CodeGeneratorBase::clearChangeSets() { qCDebug(LANGUAGE) << "Cleaning up all the changesets registered by the generator"; foreach(DUChainChangeSet * changeSet, d->duchainChanges) delete changeSet; d->duchainChanges.clear(); d->documentChanges = DocumentChangeSet(); } bool CodeGeneratorBase::execute() { qCDebug(LANGUAGE) << "Checking Preconditions for the codegenerator"; //Shouldn't there be a method in iDocument to get a DocumentRange as well? QUrl document; if(!d->autoGen) { if( !ICore::self()->documentController()->activeDocument() ) { setErrorText( i18n("Could not find an open document" ) ); return false; } document = ICore::self()->documentController()->activeDocument()->url(); if(d->range.isEmpty()) { DUChainReadLocker lock(DUChain::lock()); d->range = DocumentRange(document.url(), ICore::self()->documentController()->activeDocument()->textSelection()); } } if(!d->context) { DUChainReadLocker lock(DUChain::lock()); TopDUContext * documentChain = DUChain::self()->chainForDocument(document); if(!documentChain) { setErrorText(i18n("Could not find the chain for the selected document: %1").arg(document.url())); return false; } d->context = documentChain->findContextIncluding(d->range); if(!d->context) { //Attempt to get the context again QList contexts = DUChain::self()->chainsForDocument(document); foreach(TopDUContext * top, contexts) { qCDebug(LANGUAGE) << "Checking top context with range: " << top->range() << " for a context"; if((d->context = top->findContextIncluding(d->range))) break; } } } if(!d->context) { setErrorText(i18n("Error finding context for selection range")); return false; } if(!checkPreconditions(d->context,d->range)) { setErrorText(i18n("Error checking conditions to generate code: %1",errorText())); return false; } if(!d->autoGen) { qCDebug(LANGUAGE) << "Gathering user information for the codegenerator"; if(!gatherInformation()) { setErrorText(i18n("Error Gathering user information: %1",errorText())); return false; } } qCDebug(LANGUAGE) << "Generating code"; if(!process()) { setErrorText(i18n("Error generating code: %1",errorText())); return false; } if(!d->autoGen) { qCDebug(LANGUAGE) << "Submitting to the user for review"; return displayChanges(); } //If it is autogenerated, it shouldn't need to apply changes, instead return them to client that my be another generator DocumentChangeSet::ChangeResult result(true); if(!d->autoGen && !(result = d->documentChanges.applyAllChanges())) { setErrorText(result.m_failureReason); return false; } return true; } bool CodeGeneratorBase::displayChanges() { DocumentChangeSet::ChangeResult result = d->documentChanges.applyAllToTemp(); if(!result) { setErrorText(result.m_failureReason); return false; } ApplyChangesWidget widget; //TODO: Find some good information to put widget.setInformation("Info?"); QMap temps = d->documentChanges.tempNamesForAll(); for(QMap::iterator it = temps.begin(); it != temps.end(); ++it) widget.addDocuments(it.key() , it.value()); if(widget.exec()) return widget.applyAllChanges(); else return false; } } diff --git a/language/codegen/templateengine.cpp b/language/codegen/templateengine.cpp index 2db564aba..14176b5aa 100644 --- a/language/codegen/templateengine.cpp +++ b/language/codegen/templateengine.cpp @@ -1,67 +1,66 @@ /* * 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 "templateengine.h" #include "templateengine_p.h" #include "codedescription.h" #include "codedescriptionmetatypes.h" #include "archivetemplateloader.h" #include #include -#include using namespace KDevelop; using namespace Grantlee; TemplateEngine* TemplateEngine::self() { static TemplateEngine* engine = new TemplateEngine; return engine; } TemplateEngine::TemplateEngine() : d(new TemplateEnginePrivate) { d->engine.setSmartTrimEnabled(true); addTemplateDirectories(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevcodegen/templates"), QStandardPaths::LocateDirectory)); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); d->engine.addTemplateLoader(QSharedPointer(ArchiveTemplateLoader::self())); } TemplateEngine::~TemplateEngine() { } void TemplateEngine::addTemplateDirectories(const QStringList& directories) { FileSystemTemplateLoader* loader = new FileSystemTemplateLoader; loader->setTemplateDirs(directories); d->engine.addTemplateLoader(QSharedPointer(loader)); } diff --git a/language/codegen/tests/test_templateclassgenerator.h b/language/codegen/tests/test_templateclassgenerator.h index c9dd4bdad..32db01476 100644 --- a/language/codegen/tests/test_templateclassgenerator.h +++ b/language/codegen/tests/test_templateclassgenerator.h @@ -1,68 +1,68 @@ /* * 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. */ #ifndef KDEVPLATFORM_TESTTEMPLATECLASSGENERATOR_H #define KDEVPLATFORM_TESTTEMPLATECLASSGENERATOR_H -#include +#include #include #include #include "language/codegen/codedescription.h" namespace KDevelop { class TemplateClassGenerator; } class TestTemplateClassGenerator : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void fileLabelsCpp(); void fileLabelsYaml(); void defaultFileUrlsCpp(); void defaultFileUrlsYaml(); void customOptions(); void templateVariablesCpp(); void templateVariablesYaml(); void codeDescription(); void generate(); void cppOutput(); void yamlOutput(); private: KDevelop::TemplateClassGenerator* loadTemplate(const QString& name); private: QUrl baseUrl; QTemporaryDir tempDir; KDevelop::ClassDescription description; void setLowercaseFileNames(KDevelop::TemplateClassGenerator* generator); }; #endif // KDEVPLATFORM_TESTTEMPLATECLASSGENERATOR_H diff --git a/language/codegen/tests/test_templaterenderer.h b/language/codegen/tests/test_templaterenderer.h index d64df0d24..58ab65dd6 100644 --- a/language/codegen/tests/test_templaterenderer.h +++ b/language/codegen/tests/test_templaterenderer.h @@ -1,52 +1,52 @@ /* * 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. */ #ifndef KDEVPLATFORM_TEST_TEMPLATERENDERER_H #define KDEVPLATFORM_TEST_TEMPLATERENDERER_H -#include +#include #include namespace KDevelop { class TemplateRenderer; } class TestTemplateRenderer : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void simpleVariables_data(); void simpleVariables(); void includeTemplates(); void kdevFilters(); void kdevFiltersWithLookup(); private: KDevelop::TemplateRenderer* renderer; }; #endif // KDEVPLATFORM_TEST_TEMPLATERENDERER_H diff --git a/language/codegen/tests/test_templatesmodel.cpp b/language/codegen/tests/test_templatesmodel.cpp index a614c1e78..1cf2733bc 100644 --- a/language/codegen/tests/test_templatesmodel.cpp +++ b/language/codegen/tests/test_templatesmodel.cpp @@ -1,88 +1,90 @@ /* * 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 "test_templatesmodel.h" #include "codegen_tests_config.h" #include #include #include +#include + using namespace KDevelop; void TestTemplatesModel::initTestCase() { // avoid translated desktop entries, tests use untranslated strings QLocale::setDefault(QLocale::c()); AutoTestShell::init(); TestCore::initialize(Core::NoUi); model = new TemplatesModel(QStringLiteral("kdevcodegentest"), this); model->addDataPath(QStringLiteral(CODEGEN_TESTS_DATA_DIR) + "/"); model->refresh(); } void TestTemplatesModel::cleanupTestCase() { delete model; TestCore::shutdown(); } void TestTemplatesModel::descriptionExtraction() { QCOMPARE(model->rowCount(), 1); QModelIndex testingCategoryIndex = model->index(0, 0); QCOMPARE(model->rowCount(testingCategoryIndex), 3); for (int i = 0; i < 3; ++i) { QModelIndex languageCategoryIndex = model->index(i, 0, testingCategoryIndex); QCOMPARE(model->rowCount(languageCategoryIndex), 1); QModelIndex templateIndex = model->index(0, 0, languageCategoryIndex); QCOMPARE(model->rowCount(templateIndex), 0); } } void TestTemplatesModel::descriptionParsing() { QList items = model->findItems(QStringLiteral("Testing YAML template"), Qt::MatchRecursive); QCOMPARE(items.size(), 1); QStandardItem* item = items.first(); QCOMPARE(item->data(TemplatesModel::CommentRole).toString(), QStringLiteral("Describes a class using YAML syntax")); QVERIFY(item->data(TemplatesModel::IconNameRole).toString().isEmpty()); QString descriptionFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kdevcodegentest/template_descriptions/test_yaml.desktop"; QVERIFY(QFile::exists(descriptionFile)); QCOMPARE(item->data(TemplatesModel::DescriptionFileRole).toString(), descriptionFile); } void TestTemplatesModel::templateIndexes() { QModelIndexList indexes = model->templateIndexes(QStringLiteral("test_yaml.tar.bz2")); QCOMPARE(indexes.size(), 3); QCOMPARE(model->data(indexes[0]).toString(), QStringLiteral("Testing")); QCOMPARE(model->data(indexes[1]).toString(), QStringLiteral("YAML")); QCOMPARE(model->data(indexes[2]).toString(), QStringLiteral("Testing YAML template")); } QTEST_GUILESS_MAIN(TestTemplatesModel) diff --git a/language/codegen/tests/test_templatesmodel.h b/language/codegen/tests/test_templatesmodel.h index b7615e284..dcdcdb1e6 100644 --- a/language/codegen/tests/test_templatesmodel.h +++ b/language/codegen/tests/test_templatesmodel.h @@ -1,46 +1,46 @@ /* * 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. */ #ifndef KDEVPLATFORM_TESTTEMPLATESMODEL_H #define KDEVPLATFORM_TESTTEMPLATESMODEL_H -#include +#include namespace KDevelop { class TemplatesModel; } class TestTemplatesModel : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void descriptionExtraction(); void descriptionParsing(); void templateIndexes(); private: KDevelop::TemplatesModel* model; }; #endif // KDEVPLATFORM_TESTTEMPLATESMODEL_H diff --git a/language/duchain/abstractfunctiondeclaration.h b/language/duchain/abstractfunctiondeclaration.h index 3f66aa85f..3040ceee8 100644 --- a/language/duchain/abstractfunctiondeclaration.h +++ b/language/duchain/abstractfunctiondeclaration.h @@ -1,127 +1,126 @@ /* This file is part of KDevelop Copyright 2007 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. */ #ifndef KDEVPLATFORM_ABSTRACTFUNCTIONDECLARATION_H #define KDEVPLATFORM_ABSTRACTFUNCTIONDECLARATION_H -#include #include #include "indexedducontext.h" namespace KDevelop { class DUContext; class IndexedString; class AbstractFunctionDeclarationData { public: AbstractFunctionDeclarationData() : m_isVirtual(false), m_isInline(false), m_isExplicit(false) { } IndexedDUContext m_functionContext; bool m_isVirtual: 1; ///@todo move into ClassFunctionDeclaration(Only valid for class-functions) bool m_isInline: 1; bool m_isExplicit: 1; ///@todo move into ClassFunctionDeclaration(Only valid for class-functions) }; /** * Provides an interface to declarations which represent functions in a definition-use chain. * Don't inherit from this directly, use MergeAbstractFunctionDeclaration instead. */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractFunctionDeclaration { public: virtual ~AbstractFunctionDeclaration(); enum FunctionSpecifier { VirtualSpecifier = 0x1 /**< indicates a virtual function */, InlineSpecifier = 0x2 /**< indicates a inline function */, ExplicitSpecifier = 0x4 /**< indicates a explicit function */ }; Q_DECLARE_FLAGS(FunctionSpecifiers, FunctionSpecifier) void setFunctionSpecifiers(FunctionSpecifiers specifiers); bool isInline() const; void setInline(bool isInline); ///Only used for class-member function declarations(see ClassFunctionDeclaration) bool isVirtual() const; void setVirtual(bool isVirtual); ///Only used for class-member function declarations(see ClassFunctionDeclaration) bool isExplicit() const; void setExplicit(bool isExplicit); ///Return the DUContext::Function type ducontext (the function parameter context) of this function ///Same as internalContext if the function has no definition DUContext* internalFunctionContext() const; void setInternalFunctionContext(DUContext *context); /** * Returns the default-parameters that are set. The last default-parameter matches the last * argument of the function, but the returned vector will only contain default-values for those * arguments that have one, for performance-reasons. * * So the vector may be empty or smaller than the count of function-arguments. * */ virtual const IndexedString* defaultParameters() const = 0; virtual unsigned int defaultParametersSize() const = 0; virtual void addDefaultParameter(const IndexedString& str) = 0; virtual void clearDefaultParameters() = 0; ///Returns the default parameter assigned to the given argument number. ///This is a convenience-function. IndexedString defaultParameterForArgument(int index) const; private: //Must be implemented by sub-classes to provide a pointer to the data virtual const AbstractFunctionDeclarationData* data() const = 0; virtual AbstractFunctionDeclarationData* dynamicData() = 0; }; ///Use this to merge AbstractFunctionDeclaration into the class hierarchy. Base must be the base-class ///in the hierarchy, and Data must be the Data class of the following Declaration, and must be based on AbstractFunctionDeclarationData ///and BaseData. template class MergeAbstractFunctionDeclaration : public Base, public AbstractFunctionDeclaration { public: template explicit MergeAbstractFunctionDeclaration(BaseData& data) : Base(data) { } template MergeAbstractFunctionDeclaration(BaseData& data, const Arg2& arg2) : Base(data, arg2) { } template MergeAbstractFunctionDeclaration(BaseData& data, const Arg2& arg2, const Arg3& arg3) : Base(data, arg2, arg3) { } private: virtual const AbstractFunctionDeclarationData* data() const { return static_cast(Base::d_func()); } virtual AbstractFunctionDeclarationData* dynamicData() { return static_cast<_Data*>(Base::d_func_dynamic()); } }; } Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::AbstractFunctionDeclaration::FunctionSpecifiers) #endif // KDEVPLATFORM_ABSTRACTFUNCTIONDECLARATION_H diff --git a/language/duchain/appendedlist.h b/language/duchain/appendedlist.h index 60f8aec5b..8491d577a 100644 --- a/language/duchain/appendedlist.h +++ b/language/duchain/appendedlist.h @@ -1,395 +1,394 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_APPENDEDLIST_H #define KDEVPLATFORM_APPENDEDLIST_H #include #include -#include #include #include #include #include namespace KDevelop { class AbstractItemRepository; /** * This file contains macros and classes that can be used to conveniently implement classes that store the data of an arbitrary count * of additional lists within the same memory block directly behind the class data, in a way that one the whole data can be stored by one copy-operation * to another place, like needed in ItemRepository. These macros simplify having two versions of a class: One that has its lists attached in memory, * and one version that has them contained as a directly accessible KDevVarLengthArray. Both versions have their lists accessible through access-functions, * have a completeSize() function that computes the size of the one-block version, and a copyListsFrom(..) function which can copy the lists from one * version to the other. * * @warning Always follow these rules: * \li You must call initializeAppendedLists(bool) on construction, also in any copy-constructor, but before calling copyFrom(..). * \li The parameter to that function should be whether the lists in the items should be dynamic, and thus most times "true". * \li You must call freeAppendedLists() on destruction, our you will be leaking memory(only when dynamic) * * For each embedded list, you must use macros to define a global hash that will be used to allocate the temporary lists. * For example in @c identifier.cpp we have: * * @code * DEFINE_LIST_MEMBER_HASH(IdentifierPrivate, templateIdentifiers, uint); * @endcode * * In general, see @c identifier.cpp for an example on how to use these macros. * * @todo Document this a bit more * */ enum { DynamicAppendedListMask = 1 << 31 }; enum { DynamicAppendedListRevertMask = ~DynamicAppendedListMask }; /** * Manages a repository of items for temporary usage. The items will be allocated with an index on alloc(), * and freed on free(index). When freed, the same index will be re-used for a later allocation, thus no real allocations * will be happening in most cases. * The returned indices will always be ored with DynamicAppendedListMask. * */ template class TemporaryDataManager { public: explicit TemporaryDataManager(const QByteArray& id = {}) : m_id(id) { int first = alloc(); //Allocate the zero item, just to reserve that index Q_ASSERT(first == (int)DynamicAppendedListMask); Q_UNUSED(first); } ~TemporaryDataManager() { free(DynamicAppendedListMask); //Free the zero index, so we don't get wrong warnings int cnt = usedItemCount(); if(cnt) //Don't use qDebug, because that may not work during destruction std::cout << m_id.constData() << " There were items left on destruction: " << usedItemCount() << "\n"; for (int a = 0; a < m_items.size(); ++a) delete m_items.at(a); } inline T& getItem(int index) { //For performance reasons this function does not lock the mutex, it's called too often and must be //extremely fast. There is special measures in alloc() to make this safe. Q_ASSERT(index & DynamicAppendedListMask); return *m_items.at(index & KDevelop::DynamicAppendedListRevertMask); } ///Allocates an item index, which from now on you can get using getItem, until you call free(..) on the index. ///The returned item is not initialized and may contain random older content, so you should clear it after getting it for the first time int alloc() { if(threadSafe) m_mutex.lock(); int ret; if(!m_freeIndicesWithData.isEmpty()) { ret = m_freeIndicesWithData.pop(); }else if(!m_freeIndices.isEmpty()) { ret = m_freeIndices.pop(); Q_ASSERT(!m_items.at(ret)); m_items[ret] = new T; }else{ if(m_items.size() >= m_items.capacity()) { //We need to re-allocate const int newItemsSize = m_items.capacity() + 20 + (m_items.capacity()/3); const QVector oldItems = m_items; // backup m_items.reserve(newItemsSize); // detach, grow container //The only function that does not lock the mutex is getItem(..), because that function must be very efficient. //Since it's only a few instructions from the moment m_items is read to the moment it's used, //deleting the old data after a few seconds should be safe. m_deleteLater.append(qMakePair(time(nullptr), oldItems)); //We do this in this place so it isn't called too often. The result is that we will always have some additional data around. //However the index itself should anyway not consume too much data. if(!m_deleteLater.isEmpty()) { while(!m_deleteLater.isEmpty()) { //We delete after 5 seconds if(time(nullptr) - m_deleteLater.first().first > 5) { m_deleteLater.removeFirst(); }else{ break; } } } } ret = m_items.size(); m_items.append(new T); Q_ASSERT(m_items.size() <= m_items.capacity()); } if(threadSafe) m_mutex.unlock(); Q_ASSERT(!(ret & DynamicAppendedListMask)); return ret | DynamicAppendedListMask; } void free(int index) { Q_ASSERT(index & DynamicAppendedListMask); index &= KDevelop::DynamicAppendedListRevertMask; if(threadSafe) m_mutex.lock(); freeItem(m_items.at(index)); m_freeIndicesWithData.push(index); //Hold the amount of free indices with data between 100 and 200 if(m_freeIndicesWithData.size() > 200) { for(int a = 0; a < 100; ++a) { int deleteIndexData = m_freeIndicesWithData.pop(); delete m_items.at(deleteIndexData); m_items[deleteIndexData] = nullptr; m_freeIndices.push(deleteIndexData); } } if(threadSafe) m_mutex.unlock(); } int usedItemCount() const { int ret = 0; for(int a = 0; a < m_items.size(); ++a) if(m_items.at(a)) ++ret; return ret - m_freeIndicesWithData.size(); } private: //To save some memory, clear the lists void freeItem(T* item) { item->clear(); ///@todo make this a template specialization that only does this for containers } QVector m_items; /// note: non-shared, ref count of 1 when accessed with non-const methods => no detach Stack m_freeIndicesWithData; Stack m_freeIndices; QMutex m_mutex; QByteArray m_id; QList > > m_deleteLater; }; ///Foreach macro that takes a container and a function-name, and will iterate through the vector returned by that function, using the length returned by the function-name with "Size" appended. //This might be a little slow #define FOREACH_FUNCTION(item, container) \ for(uint a__ = 0, mustDo__ = 1, containerSize = container ## Size(); a__ < containerSize; ++a__) \ if((mustDo__ == 0 || mustDo__ == 1) && (mustDo__ = 2)) \ for(item(container()[a__]); mustDo__; mustDo__ = 0) #define DEFINE_LIST_MEMBER_HASH(container, member, type) \ typedef KDevelop::TemporaryDataManager > temporaryHash ## container ## member ## Type; \ Q_GLOBAL_STATIC_WITH_ARGS(temporaryHash ## container ## member ## Type, temporaryHash ## container ## member ## Static, ( #container "::" #member )) \ temporaryHash ## container ## member ## Type& temporaryHash ## container ## member() { \ return *temporaryHash ## container ## member ## Static; \ } #define DECLARE_LIST_MEMBER_HASH(container, member, type) \ KDevelop::TemporaryDataManager >& temporaryHash ## container ## member(); ///This implements the interfaces so this container can be used as a predecessor for classes with appended lists. ///You should do this within the abstract base class that opens a tree of classes that can have appended lists, ///so each class that uses them, can also give its predecessor to START_APPENDE_LISTS, to increase flexibility. ///This creates a boolean entry that is initialized when initializeAppendedLists is called. ///You can call appendedListsDynamic() to find out whether the item is marked as dynamic. ///When this item is used, the same rules have to be followed as for a class with appended lists: You have to call ///initializeAppendedLists(...) and freeAppendedLists(..) ///Also, when you use this, you have to implement a uint classSize() function, that returns the size of the class including derived classes, ///but not including the dynamic data. Optionally you can implement a static bool appendedListDynamicDefault() function, that returns the default-value for the "dynamic" parameter. ///to initializeAppendedLists. #define APPENDED_LISTS_STUB(container) \ bool m_dynamic : 1; \ unsigned int offsetBehindLastList() const { return 0; } \ uint dynamicSize() const { return classSize(); } \ template bool listsEqual(const T& /*rhs*/) const { return true; } \ template void copyAllFrom(const T& /*rhs*/) const { } \ void initializeAppendedLists(bool dynamic = appendedListDynamicDefault()) { m_dynamic = dynamic; } \ void freeAppendedLists() { } \ bool appendedListsDynamic() const { return m_dynamic; } ///use this if the class does not have a base class that also uses appended lists #define START_APPENDED_LISTS(container) \ unsigned int offsetBehindBase() const { return 0; } \ void freeDynamicData() { freeAppendedLists(); } ///Use this if one of the base-classes of the container also has the appended lists interfaces implemented. ///To reduce the probability of future problems, you should give the direct base class this one inherits from. ///@note: Multiple inheritance is not supported, however it will work ok if only one of the base-classes uses appended lists. #define START_APPENDED_LISTS_BASE(container, base) \ unsigned int offsetBehindBase() const { return base :: offsetBehindLastList(); } \ void freeDynamicData() { freeAppendedLists(); base::freeDynamicData(); } #define APPENDED_LIST_COMMON(container, type, name) \ uint name ## Data; \ unsigned int name ## Size() const { if((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) return 0; if(!appendedListsDynamic()) return name ## Data; else return temporaryHash ## container ## name().getItem(name ## Data).size(); } \ KDevVarLengthArray& name ## List() { name ## NeedDynamicList(); return temporaryHash ## container ## name().getItem(name ## Data); }\ template bool name ## Equals(const T& rhs) const { unsigned int size = name ## Size(); if(size != rhs.name ## Size()) return false; for(uint a = 0; a < size; ++a) {if(!(name()[a] == rhs.name()[a])) return false;} return true; } \ template void name ## CopyFrom( const T& rhs ) { \ if(rhs.name ## Size() == 0 && (name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) return; \ if(appendedListsDynamic()) { \ name ## NeedDynamicList(); \ KDevVarLengthArray& item( temporaryHash ## container ## name().getItem(name ## Data) ); \ item.clear(); \ const type* otherCurr = rhs.name(); \ const type* otherEnd = otherCurr + rhs.name ## Size(); \ for(; otherCurr < otherEnd; ++otherCurr) \ item.append(*otherCurr); \ }else{ \ Q_ASSERT(name ## Data == 0); /* It is dangerous to overwrite the contents of non-dynamic lists(Most probably a mistake) */ \ name ## Data = rhs.name ## Size(); \ type* curr = const_cast(name()); type* end = curr + name ## Size(); \ const type* otherCurr = rhs.name(); \ for(; curr < end; ++curr, ++otherCurr) \ new (curr) type(*otherCurr); /* Call the copy constructors */ \ }\ } \ void name ## NeedDynamicList() { \ Q_ASSERT(appendedListsDynamic()); \ if((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) {\ name ## Data = temporaryHash ## container ## name().alloc();\ Q_ASSERT(temporaryHash ## container ## name().getItem(name ## Data).isEmpty()); \ } \ } \ void name ## Initialize(bool dynamic) { name ## Data = (dynamic ? KDevelop::DynamicAppendedListMask : 0); } \ void name ## Free() { \ if(appendedListsDynamic()) { \ if(name ## Data & KDevelop::DynamicAppendedListRevertMask) temporaryHash ## container ## name().free(name ## Data);\ } else { \ type* curr = const_cast(name()); \ type* end = curr + name ## Size(); \ for(; curr < end; ++curr) curr->~type(); /*call destructors*/ \ } \ } \ ///@todo Make these things a bit faster(less recursion) #define APPENDED_LIST_FIRST(container, type, name) \ APPENDED_LIST_COMMON(container, type, name) \ const type* name() const { \ if((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) return nullptr; \ if(!appendedListsDynamic()) return reinterpret_cast(reinterpret_cast(this) + classSize() + offsetBehindBase()); \ else return temporaryHash ## container ## name().getItem(name ## Data).data(); \ } \ unsigned int name ## OffsetBehind() const { return name ## Size() * sizeof(type) + offsetBehindBase(); } \ template bool name ## ListChainEquals( const T& rhs ) const { return name ## Equals(rhs); } \ template void name ## CopyAllFrom( const T& rhs ) { name ## CopyFrom(rhs); } \ void name ## InitializeChain(bool dynamic) { name ## Initialize(dynamic); } \ void name ## FreeChain() { name ## Free(); } #define APPENDED_LIST(container, type, name, predecessor) \ APPENDED_LIST_COMMON(container, type, name) \ const type* name() const {\ if((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) return nullptr; \ if(!appendedListsDynamic()) return reinterpret_cast(reinterpret_cast(this) + classSize() + predecessor ## OffsetBehind()); \ else return temporaryHash ## container ## name().getItem(name ## Data).data(); \ } \ unsigned int name ## OffsetBehind() const { return name ## Size() * sizeof(type) + predecessor ## OffsetBehind(); } \ template bool name ## ListChainEquals( const T& rhs ) const { return name ## Equals(rhs) && predecessor ## ListChainEquals(rhs); } \ template void name ## CopyAllFrom( const T& rhs ) { predecessor ## CopyAllFrom(rhs); name ## CopyFrom(rhs); } \ void name ## InitializeChain(bool dynamic) { name ## Initialize(dynamic); predecessor ## InitializeChain(dynamic); } \ void name ## FreeChain() { name ## Free(); predecessor ## FreeChain(); } #define END_APPENDED_LISTS(container, predecessor) \ /* Returns the size of the object containing the appended lists, including them */ \ unsigned int completeSize() const { return classSize() + predecessor ## OffsetBehind(); } \ /* Compares all local appended lists(not from base classes) and returns true if they are equal */ \ template bool listsEqual(const T& rhs) const { return predecessor ## ListChainEquals(rhs); } \ /* Copies all the local appended lists(not from base classes) from the given item.*/ \ template void copyListsFrom(const T& rhs) { return predecessor ## CopyAllFrom(rhs); } \ void initializeAppendedLists(bool dynamic = appendedListDynamicDefault()) { \ predecessor ## Data = (dynamic ? KDevelop::DynamicAppendedListMask : 0); \ predecessor ## InitializeChain(dynamic); \ } \ void freeAppendedLists() { predecessor ## FreeChain(); } \ bool appendedListsDynamic() const { return predecessor ## Data & KDevelop::DynamicAppendedListMask; } \ unsigned int offsetBehindLastList() const { return predecessor ## OffsetBehind(); } \ uint dynamicSize() const { return offsetBehindLastList() + classSize(); } /** * This is a class that allows you easily putting instances of your class into an ItemRepository as seen in itemrepository.h. * All your class needs to do is: * - Be implemented using the APPENDED_LIST macros. * - Have a real copy-constructor that additionally takes a "bool dynamic = true" parameter, which should be given to initializeAppendedLists * - Except for these appended lists, only contain directly copyable data like indices(no pointers, no virtual functions) * - Implement operator==(..) which should compare everything, including the lists. @warning The default operator will not work! * - Implement a hash() function. The hash should equal for two instances when operator==(..) returns true. * - Should be completely functional without a constructor called, only the data copied * - Implement a "bool persistent() const" function, that should check the reference-count or other information to decide whether the item should stay in the repository * If those conditions are fulfilled, the data can easily be put into a repository using this request class. * */ template class AppendedListItemRequest { public: AppendedListItemRequest(const Type& item) : m_item(item) { } enum { AverageSize = sizeof(Type) + averageAppendedBytes }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.dynamicSize(); } void createItem(Type* item) const { new (item) Type(m_item, false); } static void destroy(Type* item, KDevelop::AbstractItemRepository&) { item->~Type(); } static bool persistent(const Type* item) { return item->persistent(); } bool equals(const Type* item) const { return m_item == *item; } const Type& m_item; }; } ///This function is outside of the namespace, so it can always be found. It's used as default-parameter to initializeAppendedLists(..), ///and you can for example implement a function called like this in your local class hierarchy to override this default. inline bool appendedListDynamicDefault() { return true; } #endif diff --git a/language/duchain/builders/abstractcontextbuilder.h b/language/duchain/builders/abstractcontextbuilder.h index 403874b3c..c125eadf8 100644 --- a/language/duchain/builders/abstractcontextbuilder.h +++ b/language/duchain/builders/abstractcontextbuilder.h @@ -1,669 +1,667 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Andreas Pakulat * * Copyright 2006 Roberto Raggi * * Copyright 2006-2008 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 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_ABSTRACTABSTRACTCONTEXTBUILDER_H #define KDEVPLATFORM_ABSTRACTABSTRACTCONTEXTBUILDER_H #include -#include - #include #include "../topducontext.h" #include "../duchainpointer.h" #include "../duchainlock.h" #include "../duchain.h" #include "../ducontext.h" #include "../identifier.h" #include "../parsingenvironment.h" #include #include namespace KDevelop { /** * \short Abstract definition-use chain context builder class * * The AbstractContextBuilder is a convenience class template for creating customized * definition-use chain context builders from an AST. It simplifies: * - creating or modifying an existing DUContext tree * - following a DUContext tree for second and subsequent passes, if required * - opening and closing DUContext instances * - tracking which DUContext instances are still present when recompiling, and removing DUContexts which no longer exist in the source code. * * \author Hamish Rodda \ */ template class AbstractContextBuilder { public: /// Constructor. AbstractContextBuilder() : m_compilingContexts( false ) , m_recompiling( false ) , m_lastContext( 0 ) { } virtual ~AbstractContextBuilder() { } /** * Entry point for building a definition-use chain with this builder. * * This function determines whether we are updating a chain, or creating a new one. If we are * creating a new chain, a new TopDUContext is created and registered with DUChain. * * \param url Url of the document being parsed. * \param node AST node to start building from. * \param updateContext TopDUContext to update if a duchain was previously created for this url, otherwise pass a null pointer. * * \returns the newly created or updated TopDUContext pointer. */ virtual ReferencedTopDUContext build( const IndexedString& url, T* node, ReferencedTopDUContext updateContext = ReferencedTopDUContext() ) { m_compilingContexts = true; m_url = url; ReferencedTopDUContext top; { DUChainWriteLocker lock( DUChain::lock() ); top = updateContext.data(); if( top ) { m_recompiling = true; Q_ASSERT(top->type() == DUContext::Global); Q_ASSERT(DUChain::self()->chainForIndex(top->ownIndex()) == top); } else { top = newTopContext( RangeInRevision( CursorInRevision( 0, 0 ), CursorInRevision( INT_MAX, INT_MAX ) ) ); DUChain::self()->addDocumentChain( top ); top->setType( DUContext::Global ); } setEncountered( top ); setContextOnNode( node, top ); } supportBuild( node, top ); m_compilingContexts = false; return top; } protected: /** * Support another builder by tracking the current context. * @param context the context to use. Must be set when the given node has no context. When it has one attached, this parameter is not needed. */ virtual void supportBuild( T* node, DUContext* context = 0 ) { if (!context) context = contextFromNode(node); Q_ASSERT(context); openContext( context ); startVisiting(node); closeContext(); Q_ASSERT(m_contextStack.isEmpty()); } /** * Entry point to your visitor. Reimplement and call the appropriate visit function. * * \param node AST node to visit. */ virtual void startVisiting( T* node ) = 0; /** * Associate a \a context with a given AST \a node. Once called on a \a node, the * contextFromNode() function should return this \a context when called. * * \param node AST node to associate * \param context DUContext to associate */ virtual void setContextOnNode( T* node, DUContext* context ) = 0; /** * Retrieve an associated DUContext from the given \a node. Used on second and * subsequent passes of the context builder (for supporting other builds) * * \param node AST node which was previously associated * \returns the DUContext which was previously associated */ virtual DUContext* contextFromNode( T* node ) = 0; /** * Retrieves a text range from the given nodes. * * As editor integrators have to be extended to determine ranges from AST nodes, * this function must be reimplemented to allow generic retrieving of rangs from nodes. * * \param fromNode the AST node to start from (on the start boundary) * \param toNode the AST node to end at (on the end boundary) * * \returns the text range encompassing the given AST node(s) */ virtual RangeInRevision editorFindRange( T* fromNode, T* toNode ) = 0; /** * Retrieve a text range for the given nodes. This is a special function required * by c++ support as a different range may need to be retrieved depending on * whether macros are involved. It is not usually required to implement this * function separately to editorFindRange() for other languages. * * \param fromNode the AST node to start from (on the start boundary) * \param toNode the AST node to end at (on the end boundary) * * \returns the text range encompassing the given AST node(s) */ virtual RangeInRevision editorFindRangeForContext( T* fromNode, T* toNode ) { return editorFindRange(fromNode, toNode); } /** * Determine the QualifiedIdentifier which corresponds to the given ast \a node. * * \param node ast node which represents an identifier * \return the qualified identifier determined from \a node */ virtual QualifiedIdentifier identifierForNode( NameT* node ) = 0; /** * Create a new DUContext from the given \a range. * * This exists so that you can create custom DUContext subclasses for your * language if you need to. * * \param range range for the new context to encompass * \returns the newly created context */ virtual DUContext* newContext(const RangeInRevision& range) { return new DUContext(range, currentContext()); } /** * Create a new TopDUContext from the given \a range. * * This exists so that you can create custom TopDUContext subclasses for your * language if you need to. * * \returns the newly created context */ virtual TopDUContext* newTopContext(const RangeInRevision& range, ParsingEnvironmentFile* file = 0) { return new TopDUContext(m_url, range, file); } /// Determine the currently open context. \returns the current context. inline DUContext* currentContext() const { return m_contextStack.top(); } /// Determine the last closed context. \returns the last closed context. inline DUContext* lastContext() const { return m_lastContext; } /// Clears the last closed context. inline void clearLastContext() { m_lastContext = 0; } inline void setLastContext(DUContext* context) { m_lastContext = context; } TopDUContext* topContext() const { return currentContext()->topContext(); } /** * Determine if we are recompiling an existing definition-use chain, or if * a new chain is being created from scratch. * * \returns true if an existing duchain is being updated, otherwise false. */ inline bool recompiling() const { return m_recompiling; } /** * Tell the context builder whether we are recompiling an existing definition-use chain, or if * a new chain is being created from scratch. * * \param recomp set to true if an existing duchain is being updated, otherwise false. */ inline void setRecompiling(bool recomp) { m_recompiling = recomp; } /** * Determine whether this pass will create DUContext instances. * * On the first pass of definition-use chain compiling, DUContext instances * are created to represent contexts in the source code. These contexts are * associated with their AST nodes at the time (see setContextOnNode()). * * On second and subsequent passes, the contexts already exist and thus can be * retrieved through contextFromNode(). * * \returns true if compiling contexts (ie. 1st pass), otherwise false. */ inline bool compilingContexts() const { return m_compilingContexts; } /** * Sets whether we need to create ducontexts, ie. if this is the first pass. * * \sa compilingContexts() */ inline void setCompilingContexts(bool compilingContexts) { m_compilingContexts = compilingContexts; } /** * Create child contexts for only a portion of the document. * * \param node The AST node which corresponds to the context to parse * \param parent The DUContext which encompasses the \a node. * \returns The DUContext which was reparsed, ie. \a parent. */ DUContext* buildSubContexts( T *node, DUContext* parent ) { // m_compilingContexts = true; // m_recompiling = false; setContextOnNode( node, parent ); { openContext( contextFromNode( node ) ); startVisiting( node ); closeContext(); } m_compilingContexts = false; if ( contextFromNode( node ) == parent ) { qDebug() << "Error in AbstractContextBuilder::buildSubContexts(...): du-context was not replaced with new one"; DUChainWriteLocker lock( DUChain::lock() ); deleteContextOnNode( node ); } return contextFromNode( node ); } /** * Delete the DUContext which is associated with the given \a node, * and remove the association. * * \param node Node which is associated with the context to delete. */ void deleteContextOnNode( T* node ) { delete contextFromNode( node ); setContextOnNode( node, 0 ); } /** * Open a context, and create / update it if necessary. * * \param rangeNode The range which encompasses the context. * \param type The type of context to open. * \param identifier The range which encompasses the name of this context, if one exists. * \returns the opened context. */ DUContext* openContext( T* rangeNode, DUContext::ContextType type, NameT* identifier = 0) { if ( m_compilingContexts ) { DUContext* ret = openContextInternal( editorFindRangeForContext( rangeNode, rangeNode ), type, identifier ? identifierForNode( identifier ) : QualifiedIdentifier() ); setContextOnNode( rangeNode, ret ); return ret; } else { openContext( contextFromNode(rangeNode) ); return currentContext(); } } /** * Open a context, and create / update it if necessary. * * \param node The range to associate with the context. * \param range A custom range which the context should encompass. * \param type The type of context to open. * \param identifier The range which encompasses the name of this context, if one exists. * \returns the opened context. */ DUContext* openContext(T* node, const RangeInRevision& range, DUContext::ContextType type, NameT* identifier = 0) { if (m_compilingContexts) { DUContext* ret = openContextInternal(range, type, identifier ? identifierForNode(identifier) : QualifiedIdentifier()); setContextOnNode( node, ret ); return ret; } else { openContext( contextFromNode(node) ); return currentContext(); } } /** * Open a context, and create / update it if necessary. * * \param node The range to associate with the context. * \param range A custom range which the context should encompass. * \param type The type of context to open. * \param id The identifier for this context * \returns the opened context. */ DUContext* openContext(T* node, const RangeInRevision& range, DUContext::ContextType type, const QualifiedIdentifier& id) { if (m_compilingContexts) { DUContext* ret = openContextInternal(range, type, id); setContextOnNode( node, ret ); return ret; } else { openContext( contextFromNode(node) ); return currentContext(); } } /** * Open a context, and create / update it if necessary. * * \param rangeNode The range which encompasses the context. * \param type The type of context to open. * \param identifier The identifier which corresponds to the context. * \returns the opened context. */ DUContext* openContext( T* rangeNode, DUContext::ContextType type, const QualifiedIdentifier& identifier ) { if ( m_compilingContexts ) { DUContext* ret = openContextInternal( editorFindRangeForContext( rangeNode, rangeNode ), type, identifier ); setContextOnNode( rangeNode, ret ); return ret; } else { openContext( contextFromNode(rangeNode) ); return currentContext(); } } /** * Open a context, and create / update it if necessary. * * \param fromRange The range which starts the context. * \param toRange The range which ends the context. * \param type The type of context to open. * \param identifier The identifier which corresponds to the context. * \returns the opened context. */ DUContext* openContext( T* fromRange, T* toRange, DUContext::ContextType type, const QualifiedIdentifier& identifier = QualifiedIdentifier() ) { if ( m_compilingContexts ) { DUContext* ret = openContextInternal( editorFindRangeForContext( fromRange, toRange ), type, identifier ); setContextOnNode( fromRange, ret ); return ret; } else { openContext( contextFromNode(fromRange) ); return currentContext(); } } /** * Open a newly created or previously existing context. * * The open context is put on the context stack, and becomes the new * currentContext(). * * \warning When you call this, you also have to open a range! If you want to re-use * the range associated to the context, use injectContext * * \param newContext Context to open. */ virtual void openContext( DUContext* newContext ) { m_contextStack.push( newContext ); m_nextContextStack.push( 0 ); } /** * This can be used to temporarily change the current context. * \param ctx The context to be injected * */ void injectContext( DUContext* ctx ) { openContext( ctx ); } /** * Use this to close the context previously injected with injectContext. * */ void closeInjectedContext() { m_contextStack.pop(); m_nextContextStack.pop(); } /** * Close the current DUContext. When recompiling, this function will remove any * contexts that were not encountered in this passing run. * \note The DUChain write lock is already held here. */ virtual void closeContext() { { DUChainWriteLocker lock( DUChain::lock() ); //Remove all slaves that were not encountered while parsing if(m_compilingContexts) currentContext()->cleanIfNotEncountered( m_encountered ); setEncountered( currentContext() ); m_lastContext = currentContext(); } m_contextStack.pop(); m_nextContextStack.pop(); } /** * Remember that a specific item has been encoutered while parsing. * All items that are not encountered will be deleted at some stage. * * \param item duchain item that was encountered. * */ void setEncountered( DUChainBase* item ) { m_encountered.insert( item ); } /** * Determine whether the given \a item is in the set of encountered items. * * @return true if the \a item has been encountered, otherwise false. * */ bool wasEncountered( DUChainBase* item ) { return m_encountered.contains( item ); } /** * Set the current identifier to \a id. * * \param id the new current identifier. */ void setIdentifier( const QString& id ) { m_identifier = Identifier( id ); m_qIdentifier.push( m_identifier ); } /** * Determine the current identifier. * \returns the current identifier. */ QualifiedIdentifier qualifiedIdentifier() const { return m_qIdentifier; } /** * Clears the current identifier. */ void clearQualifiedIdentifier() { m_qIdentifier.clear(); } /** * Retrieve the current context stack. This function is not expected * to be used often and may be phased out. * * \todo Audit whether access to the context stack is still required, and provide * replacement functionality if possible. */ const Stack& contextStack() const { return m_contextStack; } /** * Access the index of the child context which has been encountered. * * \todo further delineate the role of this function and rename / document better. * \todo make private again? */ int& nextContextIndex() { return m_nextContextStack.top(); } /** * Open a context, either creating it if it does not exist, or referencing a previously existing * context if already encountered in a previous duchain parse run (when recompiling()). * * \param range The range of the context. * \param type The type of context to create. * \param identifier The identifier which corresponds to the context. * \returns the opened context. */ virtual DUContext* openContextInternal( const RangeInRevision& range, DUContext::ContextType type, const QualifiedIdentifier& identifier ) { Q_ASSERT( m_compilingContexts ); DUContext* ret = 0L; { if ( recompiling() ) { DUChainReadLocker readLock( DUChain::lock() ); const QVector& childContexts = currentContext()->childContexts(); int currentIndex = nextContextIndex(); const auto indexedIdentifier = IndexedQualifiedIdentifier(identifier); for ( ; currentIndex < childContexts.count(); ++currentIndex ) { DUContext* child = childContexts.at( currentIndex ); RangeInRevision childRange = child->range(); if (child->type() != type) { continue; } // We cannot update a contexts local scope identifier, that will break many other parts, like e.g. // the CodeModel of child contexts or declarations. // For unnamed child-ranges, we still do range-comparison, because we cannot distinguish them in other ways if ((!identifier.isEmpty() && child->indexedLocalScopeIdentifier() == indexedIdentifier) || (identifier.isEmpty() && child->indexedLocalScopeIdentifier().isEmpty() && !childRange.isEmpty() && childRange == range)) { // Match ret = child; readLock.unlock(); DUChainWriteLocker writeLock( DUChain::lock() ); ret->clearImportedParentContexts(); ++currentIndex; break; } } if(ret) nextContextIndex() = currentIndex; //If we had a match, jump forward to that position ///@todo We should also somehow make sure we don't get quadratic worst-case effort while updating. } if ( !ret ) { DUChainWriteLocker writeLock( DUChain::lock() ); ret = newContext( range ); ret->setType( type ); if ( !identifier.isEmpty() ) ret->setLocalScopeIdentifier( identifier ); setInSymbolTable( ret ); }else{ DUChainWriteLocker writeLock( DUChain::lock() ); Q_ASSERT(ret->localScopeIdentifier() == identifier); if(ret->parentContext()) ret->setRange( range ); } } m_encountered.insert( ret ); openContext( ret ); return ret; } ///This function should call context->setInSymbolTable(..) with an appropriate decision. The duchain is write-locked when this is called. virtual void setInSymbolTable(DUContext* context) { if(!context->parentContext()->inSymbolTable()) { context->setInSymbolTable(false); return; } DUContext::ContextType type = context->type(); context->setInSymbolTable(type == DUContext::Class || type == DUContext::Namespace || type == DUContext::Global || type == DUContext::Helper || type == DUContext::Enum); } /// @returns the current url/path ot the document we are parsing IndexedString document() const { return m_url; } private: Identifier m_identifier; IndexedString m_url; QualifiedIdentifier m_qIdentifier; bool m_compilingContexts : 1; bool m_recompiling : 1; Stack m_nextContextStack; DUContext* m_lastContext; //Here all valid declarations/uses/... will be collected QSet m_encountered; Stack m_contextStack; }; } #endif diff --git a/language/duchain/codemodel.cpp b/language/duchain/codemodel.cpp index ca8000cf8..cc5a45fc7 100644 --- a/language/duchain/codemodel.cpp +++ b/language/duchain/codemodel.cpp @@ -1,361 +1,358 @@ /* 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 = nullptr; } } CodeModel& CodeModel::self() { static CodeModel ret; return ret; } } diff --git a/language/duchain/declarationid.h b/language/duchain/declarationid.h index 37bddeb49..e5c72e2ed 100644 --- a/language/duchain/declarationid.h +++ b/language/duchain/declarationid.h @@ -1,210 +1,208 @@ /* 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. */ #ifndef KDEVPLATFORM_DECLARATION_ID_H #define KDEVPLATFORM_DECLARATION_ID_H #include "indexeddeclaration.h" #include "identifier.h" #include "instantiationinformation.h" #include //krazy:excludeall=dpointer -class QString; - namespace KDevelop { class Declaration; class TopDUContext; /** * \short Allows clearly identifying a Declaration. * * DeclarationId is needed to uniquely address Declarations that are in another top-context, * because there may be multiple parsed versions of a file. * * There are two forms of DeclarationId, one indirect and one direct. The direct form * holds a reference to the Declaration instance, whereas the indirect form stores the qualified * identifier and an additional index to disambiguate instances of multiple declarations with the same * identifier. * * Both forms also have a specialization index. It can be used in a language-specific way to pick other * versions of the declaration. When the declaration is found, Declaration::specialize() is called on * the found declaration with this value, and the returned value is the actually found declaration. * * \note This only works when the Declaration is in the symbol table. * */ class KDEVPLATFORMLANGUAGE_EXPORT DeclarationId { public: /** * Constructor for indirect access to a declaration. The resulting DeclarationId will not * have a direct reference to the Declaration, but will look it up as needed. * * \param id Identifier for this declaration id. * \param additionalId Additional index to disambiguate * \param specialization Specialization index (see class documentation). */ explicit DeclarationId(const IndexedQualifiedIdentifier& id = IndexedQualifiedIdentifier(), uint additionalId = 0, const IndexedInstantiationInformation& specialization = IndexedInstantiationInformation()); /** * Constructor for direct access to a declaration. The resulting DeclarationId will * directly reference the Declaration * * \param decl Declaration to reference. * \param specialization Specialization index (see class documentation). */ explicit DeclarationId(const IndexedDeclaration& decl, const IndexedInstantiationInformation& specialization = IndexedInstantiationInformation()); DeclarationId(const DeclarationId& rhs); ~DeclarationId(); DeclarationId& operator=(const DeclarationId& rhs); /** * Equality operator. * * \param rhs declaration identifier to compare. * \returns true if equal, otherwise false. */ bool operator==(const DeclarationId& rhs) const { if(m_isDirect != rhs.m_isDirect) return false; if(!m_isDirect) return m_indirectData.identifier == rhs.m_indirectData.identifier && m_indirectData.additionalIdentity == rhs.m_indirectData.additionalIdentity && m_specialization == rhs.m_specialization; else return m_directData == rhs.m_directData && m_specialization == rhs.m_specialization; } /** * Not equal operator. * * \param rhs declaration identifier to compare. * \returns true if not equal, otherwise false. */ bool operator!=(const DeclarationId& rhs) const { return !operator==(rhs); } /** * Determine whether this declaration identifier references a valid declaration. */ bool isValid() const { return (m_isDirect && m_directData.isValid()) || m_indirectData.identifier.isValid(); } /** * Hash function for this declaration identifier. * * \warning This may return different hashes for the same declaration, * depending on whether the id is direct or indirect, * and thus you cannot compare hashes for declaration equality (use operator==() instead) */ uint hash() const { if(m_isDirect) return KDevHash() << m_directData.hash() << m_specialization.index(); else return KDevHash() << m_indirectData.identifier.getIndex() << m_indirectData.additionalIdentity << m_specialization.index(); } /** * Retrieve the declaration, from the perspective of \a context. * In order to be retrievable, the declaration must be in the symbol table. * * \param context Context in which to search for the Declaration. * \param instantiateIfRequired Whether the declaration should be instantiated if required * \returns the referenced Declaration, or null if none was found. * */ Declaration* getDeclaration(const TopDUContext* context, bool instantiateIfRequired = true) const; /** * Same as getDeclaration(..), but returns all matching declarations if there are multiple. * This also returns found forward-declarations. */ KDevVarLengthArray getDeclarations(const TopDUContext* context) const; /** * Set the specialization index (see class documentation). * * \param spec the new specialization index. */ void setSpecialization(const IndexedInstantiationInformation& spec); /** * Retrieve the specialization index (see class documentation). * * \returns the specialization index. */ IndexedInstantiationInformation specialization() const; /** * Determine whether this DeclarationId directly references a Declaration by indices, * or if it uses identifiers and other data to reference the Declaration. * * \returns true if direct, false if indirect. */ bool isDirect() const; /** * Return the qualified identifier for this declaration. * * \warning This is relatively expensive, and not 100% correct in all cases(actually a top-context would be needed to resolve this correctly), * so avoid using this, except for debugging purposes. */ QualifiedIdentifier qualifiedIdentifier() const; private: /// An indirect reference to the declaration, which uses the symbol-table for lookup. Should be preferred for all /// declarations that are in the symbol-table struct Indirect { IndexedQualifiedIdentifier identifier; /// Hash from signature, or similar. Used to disambiguate multiple declarations of the same name. uint additionalIdentity; Indirect& operator=(const Indirect& rhs) = default; }; union { Indirect m_indirectData; IndexedDeclaration m_directData; }; bool m_isDirect; // Can be used in a language-specific way to pick other versions of the declaration. // When the declaration is found, pickSpecialization is called on the found declaration // with this value, and the returned value is the actually found declaration. IndexedInstantiationInformation m_specialization; }; inline uint qHash(const KDevelop::DeclarationId& id) { return id.hash(); } } Q_DECLARE_TYPEINFO(KDevelop::DeclarationId, Q_MOVABLE_TYPE); #endif diff --git a/language/duchain/definitions.cpp b/language/duchain/definitions.cpp index 91211fb55..f24bb1974 100644 --- a/language/duchain/definitions.cpp +++ b/language/duchain/definitions.cpp @@ -1,230 +1,227 @@ /* This file is part of KDevelop Copyright 2008 David Nolden 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 "appendedlist.h" #include "definitions.h" #include "declaration.h" #include "declarationid.h" #include "duchainpointer.h" #include #include "serialization/itemrepository.h" -#include -#include - namespace KDevelop { DEFINE_LIST_MEMBER_HASH(DefinitionsItem, definitions, IndexedDeclaration) class DefinitionsItem { public: DefinitionsItem() { initializeAppendedLists(); } DefinitionsItem(const DefinitionsItem& rhs, bool dynamic = true) : declaration(rhs.declaration) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~DefinitionsItem() { 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 declaration.hash(); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(DefinitionsItem); } DeclarationId declaration; START_APPENDED_LISTS(DefinitionsItem); APPENDED_LIST_FIRST(DefinitionsItem, IndexedDeclaration, definitions); END_APPENDED_LISTS(DefinitionsItem, definitions); }; class DefinitionsRequestItem { public: DefinitionsRequestItem(const DefinitionsItem& 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(DefinitionsItem* item) const { new (item) DefinitionsItem(m_item, false); } static void destroy(DefinitionsItem* item, KDevelop::AbstractItemRepository&) { item->~DefinitionsItem(); } static bool persistent(const DefinitionsItem*) { return true; } bool equals(const DefinitionsItem* item) const { return m_item.declaration == item->declaration; } const DefinitionsItem& m_item; }; class DefinitionsVisitor { public: DefinitionsVisitor(Definitions* _definitions, const QTextStream& _out) : definitions(_definitions) , out(_out) { } bool operator()(const DefinitionsItem* item) { QDebug qout(out.device()); auto id = item->declaration; auto allDefinitions = definitions->definitions(id); qout << "Definitions for" << id.qualifiedIdentifier() << endl; foreach (const IndexedDeclaration decl, allDefinitions) { if(decl.data()) { qout << " " << decl.data()->qualifiedIdentifier() << "in" << decl.data()->url().byteArray() << "at" << decl.data()->rangeInCurrentRevision() << endl; } } return true; } private: const Definitions* definitions; const QTextStream& out; }; class DefinitionsPrivate { public: DefinitionsPrivate() : m_definitions(QStringLiteral("Definition Map")) { } //Maps declaration-ids to definitions ItemRepository m_definitions; }; Definitions::Definitions() : d(new DefinitionsPrivate()) { } Definitions::~Definitions() { delete d; } void Definitions::addDefinition(const DeclarationId& id, const IndexedDeclaration& definition) { DefinitionsItem item; item.declaration = id; item.definitionsList().append(definition); DefinitionsRequestItem request(item); uint index = d->m_definitions.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const DefinitionsItem* oldItem = d->m_definitions.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->definitionsSize(); ++a) { if(oldItem->definitions()[a] == definition) return; //Already there item.definitionsList().append(oldItem->definitions()[a]); } d->m_definitions.deleteItem(index); } //This inserts the changed item d->m_definitions.index(request); } void Definitions::removeDefinition(const DeclarationId& id, const IndexedDeclaration& definition) { DefinitionsItem item; item.declaration = id; DefinitionsRequestItem request(item); uint index = d->m_definitions.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const DefinitionsItem* oldItem = d->m_definitions.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->definitionsSize(); ++a) if(!(oldItem->definitions()[a] == definition)) item.definitionsList().append(oldItem->definitions()[a]); d->m_definitions.deleteItem(index); Q_ASSERT(d->m_definitions.findIndex(item) == 0); //This inserts the changed item if(item.definitionsSize() != 0) d->m_definitions.index(request); } } KDevVarLengthArray Definitions::definitions(const DeclarationId& id) const { KDevVarLengthArray ret; DefinitionsItem item; item.declaration = id; DefinitionsRequestItem request(item); uint index = d->m_definitions.findIndex(item); if(index) { const DefinitionsItem* repositoryItem = d->m_definitions.itemFromIndex(index); FOREACH_FUNCTION(const IndexedDeclaration& decl, repositoryItem->definitions) ret.append(decl); } return ret; } void Definitions::dump(const QTextStream& out) { QMutexLocker lock(d->m_definitions.mutex()); DefinitionsVisitor v(this, out); d->m_definitions.visitAllItems(v); } } diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index ec801c27b..5c457c3ab 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1751 +1,1749 @@ /* This is part of KDevelop Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain.h" #include "duchainlock.h" -#include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "../interfaces/icodehighlighting.h" #include "../backgroundparser/backgroundparser.h" #include "util/debug.h" #include "language-features.h" #include "topducontext.h" #include "topducontextdata.h" #include "topducontextdynamicdata.h" #include "parsingenvironment.h" #include "declaration.h" #include "definitions.h" #include "duchainutils.h" #include "use.h" #include "uses.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "persistentsymboltable.h" #include "serialization/itemrepository.h" #include "waitforupdate.h" #include "importers.h" #if HAVE_MALLOC_TRIM #include "malloc.h" #endif namespace { //Additional "soft" cleanup steps that are done before the actual cleanup. //During "soft" cleanup, the consistency is not guaranteed. The repository is //marked to be updating during soft cleanup, so if kdevelop crashes, it will be cleared. //The big advantage of the soft cleanup steps is, that the duchain is always only locked for //short times, which leads to no lockup in the UI. const int SOFT_CLEANUP_STEPS = 1; const uint cleanupEverySeconds = 200; ///Approximate maximum count of top-contexts that are checked during final cleanup const uint maxFinalCleanupCheckContexts = 2000; const uint minimumFinalCleanupCheckContextsPercentage = 10; //Check at least n% of all top-contexts during cleanup //Set to true as soon as the duchain is deleted } namespace KDevelop { bool DUChain::m_deleted = false; ///Must be locked through KDevelop::SpinLock before using chainsByIndex ///This lock should be locked only for very short times QMutex DUChain::chainsByIndexLock; std::vector DUChain::chainsByIndex; //This thing is not actually used, but it's needed for compiling DEFINE_LIST_MEMBER_HASH(EnvironmentInformationListItem, items, uint) //An entry for the item-repository that holds some meta-data. Behind this entry, the actual ParsingEnvironmentFileData is stored. class EnvironmentInformationItem { public: EnvironmentInformationItem(uint topContext, uint size) : m_topContext(topContext), m_size(size) { } ~EnvironmentInformationItem() { } unsigned int hash() const { return m_topContext; } unsigned int itemSize() const { return sizeof(*this) + m_size; } uint m_topContext; uint m_size;//Size of the data behind, that holds the actual item }; struct ItemRepositoryIndexHash { uint operator()(unsigned int __x) const { return 173*(__x>>2) + 11 * (__x >> 16); } }; class EnvironmentInformationRequest { public: ///This constructor should only be used for lookup EnvironmentInformationRequest(uint topContextIndex) : m_file(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(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 = nullptr; class DUChainPrivate { class CleanupThread : public QThread { public: explicit 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(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] = 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 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 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 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, 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 nullptr; } TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) 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 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 nullptr; ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext); if(envFile) { return envFile->topContext(); }else{ 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 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/ducontext.cpp b/language/duchain/ducontext.cpp index 33f3c1dc2..d5f7d736f 100644 --- a/language/duchain/ducontext.cpp +++ b/language/duchain/ducontext.cpp @@ -1,1708 +1,1707 @@ /* 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(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 = 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 = 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(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(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 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(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 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, 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 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 nullptr; foreach (Declaration* child, m_dynamicData->m_localDeclarations) { if (child->range().contains(position)) { return child; } } return nullptr; } DUContext* DUContext::findContextIncluding(const RangeInRevision& range) const { ENSURE_CAN_READ if (!this->range().contains(range)) 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(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 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 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.h b/language/duchain/dumpdotgraph.h index 95f417ace..74b5fe582 100644 --- a/language/duchain/dumpdotgraph.h +++ b/language/duchain/dumpdotgraph.h @@ -1,45 +1,45 @@ /*************************************************************************** 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_DUMPDOTGRAPH_H #define KDEVPLATFORM_DUMPDOTGRAPH_H -#include -#include -#include +#include #include +class QString; + namespace KDevelop { class TopDUContext; class DUContext; /** * A helper-class for debugging, that nicely visualizes the whole structure of a du-context. * */ class KDEVPLATFORMLANGUAGE_EXPORT DumpDotGraph { Q_DISABLE_COPY(DumpDotGraph) public: DumpDotGraph(); ~DumpDotGraph(); /** * The context, it's, and if it is not a top-context also all contexts importing it we be drawn. * Parent-contexts will not be respected, so if you want the whole structure, you will need to pass the top-context. * @param shortened if this is given sub-items like declarations, definitions, child-contexts, etc. will not be shown as separate nodes * */ QString dotGraph(KDevelop::DUContext* context, bool shortened = false); private: class DumpDotGraphPrivate* const d; }; } #endif // KDEVPLATFORM_DUMPDOTGRAPH_H diff --git a/language/duchain/identifier.h b/language/duchain/identifier.h index 69ad65c53..bfcd67e76 100644 --- a/language/duchain/identifier.h +++ b/language/duchain/identifier.h @@ -1,482 +1,483 @@ /* 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. */ #ifndef KDEVPLATFORM_IDENTIFIER_H #define KDEVPLATFORM_IDENTIFIER_H #include -#include #include #include #include #include //We use shared d-pointers, which is even better than a d-pointer, but krazy probably won't get it, so exclude the test. //krazy:excludeall=dpointer +class QStringList; + namespace KDevelop { class IndexedTypeIdentifier; class Identifier; class QualifiedIdentifier; template class QualifiedIdentifierPrivate; template class IdentifierPrivate; class IndexedString; /** * A helper-class to store an identifier by index in a type-safe way. * * The difference to Identifier is that this class only stores the index of an identifier that is in the repository, without any dynamic * abilities or access to the contained data. * * This class does "disk reference counting" * * @warning Do not use this after QCoreApplication::aboutToQuit() has been emitted, items that are not disk-referenced will be invalid at that point. */ class KDEVPLATFORMLANGUAGE_EXPORT IndexedIdentifier : public ReferenceCountManager { public: IndexedIdentifier(); explicit IndexedIdentifier(const Identifier& id); IndexedIdentifier(const IndexedIdentifier& rhs); IndexedIdentifier(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT; IndexedIdentifier& operator=(const Identifier& id); IndexedIdentifier& operator=(const IndexedIdentifier& rhs); IndexedIdentifier& operator=(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT; ~IndexedIdentifier(); bool operator==(const IndexedIdentifier& rhs) const; bool operator!=(const IndexedIdentifier& rhs) const; bool operator==(const Identifier& id) const; bool isEmpty() const; Identifier identifier() const; operator Identifier() const; uint getIndex() const { return index; } private: unsigned int index; }; /** * A helper-class to store an identifier by index in a type-safe way. * * The difference to QualifiedIdentifier is that this class only stores the index of an identifier that is in the repository, without any dynamic * abilities or access to the contained data. * * This class does "disk reference counting" * * @warning Do not use this after QCoreApplication::aboutToQuit() has been emitted, items that are not disk-referenced will be invalid at that point. */ class KDEVPLATFORMLANGUAGE_EXPORT IndexedQualifiedIdentifier : public ReferenceCountManager { public: IndexedQualifiedIdentifier(); IndexedQualifiedIdentifier(const QualifiedIdentifier& id); IndexedQualifiedIdentifier(const IndexedQualifiedIdentifier& rhs); IndexedQualifiedIdentifier(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT; IndexedQualifiedIdentifier& operator=(const QualifiedIdentifier& id); IndexedQualifiedIdentifier& operator=(const IndexedQualifiedIdentifier& id); IndexedQualifiedIdentifier& operator=(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT; ~IndexedQualifiedIdentifier(); bool operator==(const IndexedQualifiedIdentifier& rhs) const; bool operator==(const QualifiedIdentifier& id) const; bool operator<(const IndexedQualifiedIdentifier& rhs) const { return index < rhs.index; } bool isValid() const; bool isEmpty() const; QualifiedIdentifier identifier() const; operator QualifiedIdentifier() const; uint getIndex() const { return index; } private: uint index; }; /** * Flags to control the string representation of identifiers. */ enum IdentifierStringFormattingOption { NoOptions = 0x0, /// Removes explicit global prefix from the result. /// When enabled, global identifiers will be formatted as "globalIdentifierFormattedString" /// instead "::globalIdentifierFormattedString". RemoveExplicitlyGlobalPrefix = 0x1, /// Removes template information from the result. /// When enabled, TemplateClass< someDataType > will be formatted as plain "TemplateClass". RemoveTemplateInformation = 0x2 }; Q_DECLARE_FLAGS(IdentifierStringFormattingOptions, IdentifierStringFormattingOption) /** * Represents a single unqualified identifier */ class KDEVPLATFORMLANGUAGE_EXPORT Identifier { friend class QualifiedIdentifier; public: /** * @param start The position in the given string where to start searching for the identifier. (optional) * @param takenRange If this is nonzero, it will be filled with the length of the range from the beginning * of the given string, that was used to construct this identifier. (optional) * * @warning The identifier is parsed in a C++-similar way, and the result may not be what you expect. * If you want to prevent that parsing, use the constructor that takes IndexedString. */ explicit Identifier(const QString& str, uint start = 0, uint* takenRange = nullptr); /** * Preferred constructor, use this if you already have an IndexedString available. This does not decompose the given string. */ explicit Identifier(const IndexedString& str); Identifier(const Identifier& rhs); explicit Identifier(uint index); Identifier(); Identifier(Identifier&& rhs) Q_DECL_NOEXCEPT; ~Identifier(); Identifier& operator=(const Identifier& rhs); Identifier& operator=(Identifier&& rhs) Q_DECL_NOEXCEPT; static Identifier unique(int token); bool isUnique() const; int uniqueToken() const; /** * If \a token is non-zero, turns this Identifier into the special per-document unique identifier. * * This is used e.g. for anonymous namespaces. * * Pass a token which is specific to the document to allow correct equality comparison. */ void setUnique(int token); const IndexedString identifier() const; void setIdentifier(const QString& identifier); /** * Should be preferred over the other version */ void setIdentifier(const IndexedString& identifier); uint hash() const; /** * Comparison ignoring the template-identifiers */ bool nameEquals(const Identifier& rhs) const; /** * @warning This is expensive. */ IndexedTypeIdentifier templateIdentifier(int num) const; uint templateIdentifiersCount() const; void appendTemplateIdentifier(const IndexedTypeIdentifier& identifier); void clearTemplateIdentifiers(); void setTemplateIdentifiers(const QList& templateIdentifiers); QString toString(IdentifierStringFormattingOptions options = NoOptions) const; bool operator==(const Identifier& rhs) const; bool operator!=(const Identifier& rhs) const; bool isEmpty() const; /** * @return a unique index within the global identifier repository for this identifier. * * If the identifier isn't in the repository yet, it is added to the repository. */ uint index() const; bool inRepository() const; private: void makeConstant() const; void prepareWrite(); //Only one of the following pointers is valid at a given time mutable uint m_index; //Valid if cd is valid union { mutable IdentifierPrivate* dd; //Dynamic, owned by this identifier mutable const IdentifierPrivate* cd; //Constant, owned by the repository }; }; /** * Represents a qualified identifier * * QualifiedIdentifier has it's hash-values stored, so using the hash-values is very efficient. */ class KDEVPLATFORMLANGUAGE_EXPORT QualifiedIdentifier { public: explicit QualifiedIdentifier(const QString& id, bool isExpression = false); explicit QualifiedIdentifier(const Identifier& id); QualifiedIdentifier(const QualifiedIdentifier& id); explicit QualifiedIdentifier(uint index); QualifiedIdentifier(); QualifiedIdentifier(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT; ~QualifiedIdentifier(); QualifiedIdentifier& operator=(const QualifiedIdentifier& rhs); QualifiedIdentifier& operator=(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT; /** * Append @p id to this qualified identifier. */ void push(const IndexedIdentifier& id); /** * Append @p id to this qualified identifier. * * NOTE: If you have an indexed identifier available, use the above method instead. */ void push(const Identifier& id); /** * Append all identifiers of @p id to this qualified identifier. */ void push(const QualifiedIdentifier& id); /** * Pops one identifier from back: */ void pop(); void clear(); bool isEmpty() const; int count() const; Identifier first() const; IndexedIdentifier indexedFirst() const; Identifier last() const; IndexedIdentifier indexedLast() const; Identifier top() const; Identifier at(int i) const; IndexedIdentifier indexedAt(int i) const; /** * @param pos Position where to start the copy. * @param len If this is -1, the whole following part will be returned. */ QualifiedIdentifier mid(int pos, int len = -1) const; /** * Copy the leftmost \a len number of identifiers. * * @param len The number of identifiers to copy, or if negative, the number of identifiers to omit from the right */ inline QualifiedIdentifier left(int len) const { return mid(0, len > 0 ? len : count() + len); } ///@todo Remove this flag bool explicitlyGlobal() const; void setExplicitlyGlobal(bool eg); bool isQualified() const; /** * A flag that can be set by setIsExpression */ bool isExpression() const; /** * Set the expression-flag, that can be retrieved by isExpression(). * This flag is not respected while creating the hash-value and while operator==() comparison. * It is respected while isSame(..) comparison. */ void setIsExpression(bool); QString toString(IdentifierStringFormattingOptions options = NoOptions) const; QStringList toStringList(IdentifierStringFormattingOptions options = NoOptions) const; QualifiedIdentifier operator+(const QualifiedIdentifier& rhs) const; QualifiedIdentifier& operator+=(const QualifiedIdentifier& rhs); /** * Nicer interfaces to merge */ QualifiedIdentifier operator+(const Identifier& rhs) const; QualifiedIdentifier& operator+=(const Identifier& rhs); QualifiedIdentifier operator+(const IndexedIdentifier& rhs) const; QualifiedIdentifier& operator+=(const IndexedIdentifier& rhs); /** * @return a QualifiedIdentifier with this one appended to the other. * * It is explicitly global if either this or base is. */ QualifiedIdentifier merge(const QualifiedIdentifier& base) const; /** * The comparison-operators do not respect explicitlyGlobal and isExpression, they only respect the real scope. * This is for convenient use in hash-tables etc. */ bool operator==(const QualifiedIdentifier& rhs) const; bool operator!=(const QualifiedIdentifier& rhs) const; bool beginsWith(const QualifiedIdentifier& other) const; uint index() const; /** * @return true if this qualified identifier is already in the persistent identifier repository */ bool inRepository() const; /** * The hash does not respect explicitlyGlobal, only the real scope. */ uint hash() const; protected: bool sameIdentifiers(const QualifiedIdentifier& rhs) const; void makeConstant() const; void prepareWrite(); mutable uint m_index; union { mutable QualifiedIdentifierPrivate* dd; mutable const QualifiedIdentifierPrivate* cd; }; }; /** * Extends IndexedQualifiedIdentifier by: * - Arbitrary count of pointer-poperators with cv-qualifiers * - Reference operator * All the properties set here are respected in the hash value. */ class KDEVPLATFORMLANGUAGE_EXPORT IndexedTypeIdentifier { public: /** * Variables like pointerDepth, isReference, etc. are not parsed from the string, so this parsing is quite limited. */ explicit IndexedTypeIdentifier(const IndexedQualifiedIdentifier& identifier = IndexedQualifiedIdentifier()); explicit IndexedTypeIdentifier(const QString& identifer, bool isExpression = false); bool isReference() const; void setIsReference(bool); bool isRValue() const; void setIsRValue(bool); bool isConstant() const; void setIsConstant(bool); bool isVolatile() const; void setIsVolatile(bool); IndexedQualifiedIdentifier identifier() const ; void setIdentifier(const IndexedQualifiedIdentifier& id); /** * @return the pointer depth. Example for C++: "char*" has pointer-depth 1, "char***" has pointer-depth 3 */ int pointerDepth() const; /** * Sets the pointer-depth to the specified count. * * When the pointer-depth is increased, the "isConstPointer" values for new depths will be initialized with false. * * For efficiency-reasons the maximum currently is 23. */ void setPointerDepth(int); /** * Whether the target of pointer 'depthNumber' is constant */ bool isConstPointer(int depthNumber) const; void setIsConstPointer(int depthNumber, bool constant); QString toString(IdentifierStringFormattingOptions options = NoOptions) const; uint hash() const; /** * The comparison-operators do not respect explicitlyGlobal and isExpression, they only respect the real scope. * This is for convenient use in hash-tables etc. */ bool operator==(const IndexedTypeIdentifier& rhs) const; bool operator!=(const IndexedTypeIdentifier& rhs) const; private: IndexedQualifiedIdentifier m_identifier; // The overall number of bits shared by these bit-fields should not exceed 32, // so that we don't waste space. IndexedTypeIdentifer should be as compact as possible. bool m_isConstant : 1; bool m_isReference : 1; bool m_isRValue : 1; bool m_isVolatile : 1; uint m_pointerDepth : 5; uint m_pointerConstMask : 23; }; KDEVPLATFORMLANGUAGE_EXPORT uint qHash(const IndexedTypeIdentifier& id); KDEVPLATFORMLANGUAGE_EXPORT uint qHash(const QualifiedIdentifier& id); KDEVPLATFORMLANGUAGE_EXPORT uint qHash(const Identifier& id); inline uint qHash(const IndexedIdentifier& id) { return id.getIndex(); } inline uint qHash(const IndexedQualifiedIdentifier& id) { return id.getIndex(); } } Q_DECLARE_TYPEINFO(KDevelop::IndexedQualifiedIdentifier, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::IndexedIdentifier, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::IndexedQualifiedIdentifier) Q_DECLARE_METATYPE(KDevelop::IndexedIdentifier) Q_DECLARE_TYPEINFO(KDevelop::QualifiedIdentifier, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::Identifier, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::QualifiedIdentifier) Q_DECLARE_METATYPE(KDevelop::Identifier) /** * {q,k}Debug() stream operator: Writes the Identifier to the debug output. */ KDEVPLATFORMLANGUAGE_EXPORT QDebug operator<<(QDebug s, const KDevelop::Identifier& identifier); /** * {q,k}Debug() stream operator: Writes the QualifiedIdentifier to the debug output. */ KDEVPLATFORMLANGUAGE_EXPORT QDebug operator<<(QDebug s, const KDevelop::QualifiedIdentifier& identifier); #endif // KDEVPLATFORM_IDENTIFIER_H diff --git a/language/duchain/importers.cpp b/language/duchain/importers.cpp index d33a49fb3..ccbdce160 100644 --- a/language/duchain/importers.cpp +++ b/language/duchain/importers.cpp @@ -1,197 +1,194 @@ /* 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 "importers.h" -#include -#include - #include "declarationid.h" #include "duchainpointer.h" #include "serialization/itemrepository.h" #include "topducontext.h" namespace KDevelop { DEFINE_LIST_MEMBER_HASH(ImportersItem, importers, IndexedDUContext) class ImportersItem { public: ImportersItem() { initializeAppendedLists(); } ImportersItem(const ImportersItem& rhs, bool dynamic = true) : declaration(rhs.declaration) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~ImportersItem() { 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 declaration.hash(); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(ImportersItem); } DeclarationId declaration; START_APPENDED_LISTS(ImportersItem); APPENDED_LIST_FIRST(ImportersItem, IndexedDUContext, importers); END_APPENDED_LISTS(ImportersItem, importers); }; class ImportersRequestItem { public: ImportersRequestItem(const ImportersItem& 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(ImportersItem* item) const { new (item) ImportersItem(m_item, false); } static void destroy(ImportersItem* item, KDevelop::AbstractItemRepository&) { item->~ImportersItem(); } static bool persistent(const ImportersItem* /*item*/) { return true; } bool equals(const ImportersItem* item) const { return m_item.declaration == item->declaration; } const ImportersItem& m_item; }; class ImportersPrivate { public: ImportersPrivate() : m_importers(QStringLiteral("Importer Map")) { } //Maps declaration-ids to Importers ItemRepository m_importers; }; Importers::Importers() : d(new ImportersPrivate()) { } Importers::~Importers() { delete d; } void Importers::addImporter(const DeclarationId& id, const IndexedDUContext& use) { ImportersItem item; item.declaration = id; item.importersList().append(use); ImportersRequestItem request(item); uint index = d->m_importers.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const ImportersItem* oldItem = d->m_importers.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->importersSize(); ++a) { if(oldItem->importers()[a] == use) return; //Already there item.importersList().append(oldItem->importers()[a]); } d->m_importers.deleteItem(index); } //This inserts the changed item d->m_importers.index(request); } void Importers::removeImporter(const DeclarationId& id, const IndexedDUContext& use) { ImportersItem item; item.declaration = id; ImportersRequestItem request(item); uint index = d->m_importers.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const ImportersItem* oldItem = d->m_importers.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->importersSize(); ++a) if(!(oldItem->importers()[a] == use)) item.importersList().append(oldItem->importers()[a]); d->m_importers.deleteItem(index); Q_ASSERT(d->m_importers.findIndex(item) == 0); //This inserts the changed item if(item.importersSize() != 0) d->m_importers.index(request); } } KDevVarLengthArray Importers::importers(const DeclarationId& id) const { KDevVarLengthArray ret; ImportersItem item; item.declaration = id; ImportersRequestItem request(item); uint index = d->m_importers.findIndex(item); if(index) { const ImportersItem* repositoryItem = d->m_importers.itemFromIndex(index); FOREACH_FUNCTION(const IndexedDUContext& decl, repositoryItem->importers) ret.append(decl); } return ret; } Importers& Importers::self() { static Importers globalImporters; return globalImporters; } } diff --git a/language/duchain/navigation/abstractincludenavigationcontext.cpp b/language/duchain/navigation/abstractincludenavigationcontext.cpp index ef5791f5d..037d72ae6 100644 --- a/language/duchain/navigation/abstractincludenavigationcontext.cpp +++ b/language/duchain/navigation/abstractincludenavigationcontext.cpp @@ -1,176 +1,174 @@ /* 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 = nullptr; foreach(TopDUContext* ctx, duchains) { if(!ctx->parsingEnvironmentFile() || ctx->parsingEnvironmentFile()->type() != type) continue; if(ctx->childContexts().count() != 0 && (duchain == nullptr || ctx->childContexts().count() > duchain->childContexts().count())) { duchain = ctx; } if(ctx->localDeclarations().count() != 0 && (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(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 5747958ce..7cad556d4 100644 --- a/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,507 +1,506 @@ /* 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 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 ed29cb21b..43dfb86e7 100644 --- a/language/duchain/navigation/abstractnavigationwidget.cpp +++ b/language/duchain/navigation/abstractnavigationwidget.cpp @@ -1,303 +1,304 @@ /* 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 #include "../duchainlock.h" #include "util/debug.h" namespace KDevelop { AbstractNavigationWidget::AbstractNavigationWidget() : m_browser(nullptr), m_currentWidget(nullptr) { setPalette( QApplication::palette() ); setFocusPolicy(Qt::NoFocus); resize(100, 100); } const int maxNavigationWidgetWidth = 800; const int maxNavigationWidgetHeight = 400; QSize AbstractNavigationWidget::sizeHint() const { if(m_browser) { updateIdealSize(); QSize ret = QSize(qMin(m_idealTextSize.width(), maxNavigationWidgetWidth), qMin(m_idealTextSize.height(), maxNavigationWidgetHeight)); if(m_idealTextSize.height()>=maxNavigationWidgetHeight) { //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 == 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(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/abstractnavigationwidget.h b/language/duchain/navigation/abstractnavigationwidget.h index 9dfd767a5..3315b339c 100644 --- a/language/duchain/navigation/abstractnavigationwidget.h +++ b/language/duchain/navigation/abstractnavigationwidget.h @@ -1,113 +1,110 @@ /* 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_ABSTRACTNAVIGATIONWIDGET_H #define KDEVPLATFORM_ABSTRACTNAVIGATIONWIDGET_H - -#include #include #include -#include #include "../../interfaces/quickopendataprovider.h" #include #include "abstractnavigationcontext.h" class QTextBrowser; namespace KDevelop { /** * This class deleted itself when its part is deleted, so always use a QPointer when referencing it. * The duchain must be read-locked for most operations * */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractNavigationWidget : public QWidget, public QuickOpenEmbeddedWidgetInterface { Q_OBJECT public: enum DisplayHint { NoHints = 0x0, // < Normal display EmbeddableWidget = 0x1, // < Omit parts which are only useful for the navigation popup }; Q_DECLARE_FLAGS(DisplayHints, DisplayHint) AbstractNavigationWidget(); ~AbstractNavigationWidget() override; void setContext(NavigationContextPointer context, int initBrowser = 400); void setDisplayHints(DisplayHints hints); QSize sizeHint() const override; public slots: ///Keyboard-action "next" void next() override; ///Keyboard-action "previous" void previous() override; ///Keyboard-action "accept" void accept() override; void up() override; void down() override; virtual void back(); ///These are temporarily for gettings these events directly from kate ///@todo Do this through a public interface post 4.2 void embeddedWidgetRight(); ///Keyboard-action "previous" void embeddedWidgetLeft(); ///Keyboard-action "accept" void embeddedWidgetAccept(); void embeddedWidgetUp(); void embeddedWidgetDown(); NavigationContextPointer context(); Q_SIGNALS: void sizeHintChanged(); /// Emitted whenever the current navigation-context has changed /// @param wasInitial whether the previous context was the initial context /// @param isInitial whether the current context is the initial context void contextChanged(bool wasInitial, bool isInitial); public slots: void navigateDeclaration(KDevelop::IndexedDeclaration decl); private slots: void anchorClicked(const QUrl&); protected: void wheelEvent(QWheelEvent* ) override; void updateIdealSize() const; void initBrowser(int height); void update(); NavigationContextPointer m_startContext; TopDUContextPointer m_topContext; QPointer m_browser; QWidget* m_currentWidget; QString m_currentText; mutable QSize m_idealTextSize; DisplayHints m_hints = NoHints; private: NavigationContextPointer m_context; }; } #endif diff --git a/language/duchain/navigation/problemnavigationcontext.cpp b/language/duchain/navigation/problemnavigationcontext.cpp index 737176061..69ebe8e2e 100644 --- a/language/duchain/navigation/problemnavigationcontext.cpp +++ b/language/duchain/navigation/problemnavigationcontext.cpp @@ -1,274 +1,270 @@ /* 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 "problemnavigationcontext.h" -#include -#include #include "util/debug.h" -#include -#include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString KEY_INVOKE_ACTION(int num) { return QString("invoke_action_%1").arg(num); } QString iconForSeverity(IProblem::Severity severity) { switch (severity) { case IProblem::Hint: return QStringLiteral("dialog-information"); case IProblem::Warning: return QStringLiteral("dialog-warning"); case IProblem::Error: return QStringLiteral("dialog-error"); case IProblem::NoSeverity: return {}; } Q_UNREACHABLE(); return {}; } QString htmlImg(const QString& iconName, KIconLoader::Group group) { KIconLoader loader; const int size = loader.currentSize(group); return QString::fromLatin1("") .arg(size) .arg(loader.iconPath(iconName, group)); } } ProblemNavigationContext::ProblemNavigationContext(const QVector& problems, const Flags flags) : m_problems(problems) , m_flags(flags) , m_widget(nullptr) { // Sort problems vector: // 1) By severity // 2) By sourceString, if severities are equals std::sort(m_problems.begin(), m_problems.end(), [](const IProblem::Ptr a, const IProblem::Ptr b) { if (a->severity() < b->severity()) return true; if (a->severity() > b->severity()) return false; if (a->sourceString() < b->sourceString()) return true; return false; }); } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } QString ProblemNavigationContext::escapedHtml(const QString& text) const { static const QString htmlStart = QStringLiteral(""); static const QString htmlEnd = QStringLiteral(""); QString result = text.trimmed(); if (!result.startsWith(htmlStart)) return result.toHtmlEscaped(); result.remove(htmlStart); result.remove(htmlEnd); return result; } void ProblemNavigationContext::html(IProblem::Ptr problem) { auto iconPath = iconForSeverity(problem->severity()); modifyHtml() += QStringLiteral(""); modifyHtml() += QStringLiteral("").arg(htmlImg(iconPath, KIconLoader::Panel)); // BEGIN: right column modifyHtml() += QStringLiteral(""); // END: right column modifyHtml() += QStringLiteral("
%1"); modifyHtml() += i18n("Problem in %1", problem->sourceString()); modifyHtml() += QStringLiteral("
"); if (m_flags & ShowLocation) { modifyHtml() += labelHighlight(i18n("Location: ")); makeLink(QStringLiteral("%1 :%2") .arg(problem->finalLocation().document.toUrl().fileName()) .arg(problem->finalLocation().start().line() + 1), QString(), NavigationAction(problem->finalLocation().document.toUrl(), problem->finalLocation().start()) ); modifyHtml() += QStringLiteral("
"); } QString description = escapedHtml(problem->description()); QString explanation = escapedHtml(problem->explanation()); modifyHtml() += description; // Add only non-empty explanation which differs from the problem description. // Skip this if we have more than one problem. if (m_problems.size() == 1 && !explanation.isEmpty() && explanation != description) modifyHtml() += "

" + explanation + "

"; modifyHtml() += QStringLiteral("
"); auto diagnostics = problem->diagnostics(); if (!diagnostics.isEmpty()) { DUChainReadLocker lock; for (auto diagnostic : diagnostics) { modifyHtml() += QStringLiteral("

"); modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString())); modifyHtml() += escapedHtml(diagnostic->description()); const DocumentRange range = diagnostic->finalLocation(); Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()).declaration; if (declaration) { modifyHtml() += i18n("
See: "); makeLink(declaration->toString(), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); modifyHtml() += i18n(" in "); makeLink(QStringLiteral("%1 :%2") .arg(declaration->url().toUrl().fileName()) .arg(declaration->rangeInCurrentRevision().start().line() + 1), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); } else if (range.start().isValid()) { modifyHtml() += i18n("
See: "); const auto url = range.document.toUrl(); makeLink(QStringLiteral("%1 :%2") .arg(url.fileName()) .arg(range.start().line() + 1), url.toDisplayString(QUrl::PreferLocalFile), NavigationAction(url, range.start())); } modifyHtml() += QStringLiteral("

"); } } auto assistant = problem->solutionAssistant(); if (assistant && !assistant->actions().isEmpty()) { modifyHtml() += QString::fromLatin1("").arg("#b3d4ff"); modifyHtml() += QStringLiteral(""; modifyHtml() += QStringLiteral("
%1").arg(htmlImg(QStringLiteral("dialog-ok-apply"), KIconLoader::Panel)); const int startIndex = m_assistantsActions.size(); int currentIndex = startIndex; foreach (auto assistantAction, assistant->actions()) { m_assistantsActions.append(assistantAction); if (currentIndex != startIndex) modifyHtml() += "
"; makeLink(i18n("Solution (%1)", currentIndex + 1), KEY_INVOKE_ACTION( currentIndex ), NavigationAction(KEY_INVOKE_ACTION( currentIndex ))); modifyHtml() += ": " + assistantAction->description().toHtmlEscaped(); ++currentIndex; } modifyHtml() += "
"); } } QString ProblemNavigationContext::html(bool shorten) { m_shorten = shorten; clear(); m_assistantsActions.clear(); int problemIndex = 0; foreach (auto problem, m_problems) { html(problem); if (++problemIndex != m_problems.size()) modifyHtml() += "
"; } return currentHtml(); } NavigationContextPointer ProblemNavigationContext::executeKeyAction(QString key) { if (key.startsWith(QLatin1String("invoke_action_"))) { const int index = key.replace(QLatin1String("invoke_action_"), QString()).toInt(); executeAction(index); } return {}; } void ProblemNavigationContext::executeAction(int index) { if (index < 0 || index >= m_assistantsActions.size()) return; auto action = m_assistantsActions.at(index); Q_ASSERT(action); if (action) { action->execute(); if ( topContext() ) DUChain::self()->updateContextForUrl(topContext()->url(), TopDUContext::ForceUpdate); } else { qCWarning(LANGUAGE()) << "No such action"; return; } } diff --git a/language/duchain/navigation/useswidget.h b/language/duchain/navigation/useswidget.h index 1b415b4e9..edd61c385 100644 --- a/language/duchain/navigation/useswidget.h +++ b/language/duchain/navigation/useswidget.h @@ -1,165 +1,163 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_USESWIDGET_H #define KDEVPLATFORM_USESWIDGET_H #include #include #include #include #include #include #include "usescollector.h" #include class QLabel; -class QToolButton; class QVBoxLayout; class QHBoxLayout; class QBoxLayout; -class QPushButton; class QProgressBar; namespace KDevelop { class CodeRepresentation; class IndexedDeclaration; ///A widget representing one use of a Declaration in a speicific context class KDEVPLATFORMLANGUAGE_EXPORT OneUseWidget : public QWidget { Q_OBJECT public: OneUseWidget(IndexedDeclaration declaration, IndexedString document, KTextEditor::Range range, const CodeRepresentation& code); ~OneUseWidget() override; void setHighlighted(bool highlight); bool isHighlighted() const; void activateLink(); private: void mousePressEvent(QMouseEvent * event) override; void resizeEvent ( QResizeEvent * event ) override; PersistentMovingRange::Ptr m_range; IndexedDeclaration m_declaration; IndexedString m_document; QString m_sourceLine; QLabel* m_label; QLabel* m_icon; QHBoxLayout* m_layout; bool m_isHighlighted = false; }; class KDEVPLATFORMLANGUAGE_EXPORT NavigatableWidgetList : public QScrollArea { Q_OBJECT public: explicit NavigatableWidgetList(bool allowScrolling = false, uint maxHeight = 0, bool vertical = true); ~NavigatableWidgetList() override; void addItem(QWidget* widget, int pos = -1); void addHeaderItem(QWidget* widget, Qt::Alignment alignment = nullptr); ///Whether items were added to this list using addItem(..) bool hasItems() const; ///Deletes all items that were added using addItem void deleteItems(); QList items() const; void setShowHeader(bool show); protected: QBoxLayout* m_itemLayout; QVBoxLayout* m_layout; private: QHBoxLayout* m_headerLayout; bool m_allowScrolling, m_useArrows; }; class KDEVPLATFORMLANGUAGE_EXPORT ContextUsesWidget : public NavigatableWidgetList { Q_OBJECT public: ContextUsesWidget(const CodeRepresentation& code, QList usedDeclaration, IndexedDUContext context); Q_SIGNALS: void navigateDeclaration(KDevelop::IndexedDeclaration); private Q_SLOTS: void linkWasActivated(QString); private: IndexedDUContext m_context; }; class KDEVPLATFORMLANGUAGE_EXPORT DeclarationWidget : public NavigatableWidgetList { Q_OBJECT public: DeclarationWidget(const KDevelop::CodeRepresentation& code, const KDevelop::IndexedDeclaration& declaration); }; /** * Represents the uses of a declaration within one top-context */ class KDEVPLATFORMLANGUAGE_EXPORT TopContextUsesWidget : public NavigatableWidgetList { Q_OBJECT public: TopContextUsesWidget(IndexedDeclaration declaration, QList localDeclarations, IndexedTopDUContext topContext); void setExpanded(bool); int usesCount() const; private slots: void labelClicked(); private: IndexedTopDUContext m_topContext; IndexedDeclaration m_declaration; QLabel* m_icon; QLabel* m_toggleButton; QList m_allDeclarations; int m_usesCount; }; /** * A widget that allows browsing through all the uses of a declaration, and also through all declarations of it. */ class KDEVPLATFORMLANGUAGE_EXPORT UsesWidget : public NavigatableWidgetList { Q_OBJECT public: ///This class can be overridden to do additional processing while the uses-widget shows the uses. struct KDEVPLATFORMLANGUAGE_EXPORT UsesWidgetCollector : public UsesCollector { public: void setWidget(UsesWidget* widget ); explicit UsesWidgetCollector(IndexedDeclaration decl); void processUses(KDevelop::ReferencedTopDUContext topContext) override; void maximumProgress(uint max) override; void progress(uint processed, uint total) override; UsesWidget* m_widget; }; QSize sizeHint () const override; ///@param customCollector allows specifying an own subclass of UsesWidgetCollector. explicit UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector = {}); ~UsesWidget() override; void setAllExpanded(bool expanded); unsigned int countAllUses() const; Q_SIGNALS: void navigateDeclaration(KDevelop::IndexedDeclaration); private: const QString headerLineText() const; QLabel* m_headerLine; QSharedPointer m_collector; QProgressBar* m_progressBar; public slots: void headerLinkActivated(QString linkName); void redrawHeaderLine(); }; } #endif diff --git a/language/duchain/parsingenvironment.h b/language/duchain/parsingenvironment.h index eb5379ae2..3964a2055 100644 --- a/language/duchain/parsingenvironment.h +++ b/language/duchain/parsingenvironment.h @@ -1,232 +1,230 @@ /* 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_PARSINGENVIRONMENT_H #define KDEVPLATFORM_PARSINGENVIRONMENT_H -#include - #include #include #include "duchainbase.h" #include "topducontext.h" #include namespace KDevelop { /** * Just an enumeration of a few parsing-environment types. * Enumerate a few possible future parsing-environment types. * A parsing-environment could also have a type not in this enumeration, * the only important thing is that it's unique for the type. * * The type is needed to match ParsingEnvironment, ParsingEnvironmentFile, and ParsingEnvironmentManager together so they fit. * For example the c++-versions would have their specific type. * * The type must be unique(no other language may have the same type), * and the type must be persistent.(it must be same after restarting kdevelop) * * */ enum ParsingEnvironmentType { StandardParsingEnvironment /**< a basic standard parsing environment */, CppParsingEnvironment /**< a C++ parsing environment */, PythonParsingEnvironment /**< a python parsing environment */, CMakeParsingEnvironment /**< a CMake parsing environment */, CSharpParsingEnvironment /**< a CSharp parsing environment */, JavaParsingEnvironment /**< a JAva parsing environment */, RubyParsingEnvironment /**< a Ruby parsing environment */, PhpParsingEnvironment /**< a PHP parsing environment */ }; ///Internal struct StaticParsingEnvironmentData { TopDUContext::IndexedRecursiveImports simplifiedVisibleDeclarationsSatisfied; TopDUContext::IndexedRecursiveImports visibleDeclarationsSatisfied; TopDUContext::IndexedRecursiveImports allDeclarationsSatisfied; TopDUContext::IndexedRecursiveImports allDeclarationsAndUsesSatisfied; TopDUContext::IndexedRecursiveImports ASTSatisfied; }; /** * Use this as base-class to define new parsing-environments. * * Parsing-environments are needed for languages that create different * parsing-results depending on the environment. For example in c++, * the environment mainly consists of macros. The include-path can * be considered to be a part of the parsing-environment too, because * different files may be included using another include-path. * * The classes have to use the serialization scheme from the duchain. * Each class must be registered using REGISTER_DUCHAIN_ITEM with a unique id. * * \warning Access to this class must be serialized through du-chain locking * */ class KDEVPLATFORMLANGUAGE_EXPORT ParsingEnvironment { public: ParsingEnvironment(); virtual ~ParsingEnvironment(); ///@see ParsingEnvironmentType virtual int type() const; }; ///The data class used for storing. Use this as base-class of your custom data classes for classes derived from ///ParsingEnvironmentFile class KDEVPLATFORMLANGUAGE_EXPORT ParsingEnvironmentFileData : public DUChainBaseData { public: ParsingEnvironmentFileData() : m_isProxyContext(false), m_features(TopDUContext::VisibleDeclarationsAndContexts) { } bool m_isProxyContext; TopDUContext::Features m_features; KDevelop::ModificationRevision m_modificationTime; ModificationRevisionSet m_allModificationRevisions; KDevelop::IndexedString m_url; KDevelop::IndexedTopDUContext m_topContext; IndexedString m_language; ///If this is not empty, it means that the cache is used instead of the implicit structure. TopDUContext::IndexedRecursiveImports m_importsCache; }; typedef QExplicitlySharedDataPointer ParsingEnvironmentFilePointer; /** * This represents all information about a specific parsed file that is needed * to match the file to a parsing-environment. * * It is QSharedData because it is embedded into top-du-contexts and at the same time * references may be held by ParsingEnvironmentManager. * * \warning Access to this class must be serialized through du-chain locking * */ class KDEVPLATFORMLANGUAGE_EXPORT ParsingEnvironmentFile : public DUChainBase, public QSharedData { public: virtual ~ParsingEnvironmentFile(); explicit ParsingEnvironmentFile(const IndexedString& url); ParsingEnvironmentFile(ParsingEnvironmentFileData& data, const IndexedString& url); explicit ParsingEnvironmentFile(ParsingEnvironmentFileData& data); ///@see ParsingEnvironmentType virtual int type() const; ///Should return whether this file matches into the given environment. The default-implementation always returns true. virtual bool matchEnvironment(const ParsingEnvironment* environment) const; ///Convenience-function that returns the top-context TopDUContext* topContext() const override; KDevelop::IndexedTopDUContext indexedTopContext() const; KDevelop::IndexedString url() const override; ///Can additionally use language-specific information to decide whether the top-context that has this data attached needs to be reparsed. ///The standard-implementation checks the modification-time of this file stored using setModificationRevision, and all other modification-times ///stored with addModificationRevision(..). virtual bool needsUpdate(const ParsingEnvironment* environment = nullptr) const; /** * A language-specific flag used by C++ to mark one context as a proxy of another. * If this flag is set on a context, the first imported context should be used for any computations * like searches, listing, etc. instead of using this context. * * The problems should be stored within the proxy-contexts, and optionally there may be * any count of imported proxy-contexts imported behind the content-context(This can be used for tracking problems) * * Note: This flag does not directly change the behavior of the language-independent du-chain. */ bool isProxyContext() const; ///Sets the flag void setIsProxyContext(bool); ///The features of the attached top-context. They are automatically replicated here by the top-context, so they ///are accessible even without the top-context loaded. TopDUContext::Features features() const; ///Returns the parsing-environment information of all importers of the coupled TopDUContext. This is more efficient than ///loading the top-context and finding out, because when a top-context is loaded, also all its recursive imports are loaded QList< QExplicitlySharedDataPointer > importers() const; ///Returns the parsing-environment information of all imports of the coupled TopDUContext. This is more efficient than ///loading the top-context and finding out QList< QExplicitlySharedDataPointer > imports() const; ///Returns true if this top-context satisfies at least the given minimum features. ///If there is static minimum features set up in ParseJob, this also checks against those. ///Features like "ForceUpdate" are treated specially. ///@p minimumFeatures The features that must be satisfied. May be an arbitrary combination of features. bool featuresSatisfied(TopDUContext::Features minimumFeatures) const; ///Should return a correctly filled ModificationRevision of the source it was created from. void setModificationRevision( const KDevelop::ModificationRevision& rev ) ; KDevelop::ModificationRevision modificationRevision() const; ///Clears the modification times of all dependencies void clearModificationRevisions(); void addModificationRevision(const IndexedString& url, const ModificationRevision& revision); const ModificationRevisionSet& allModificationRevisions() const; void addModificationRevisions(const ModificationRevisionSet&); /// Returns the language for this top-context. If the string is empty, the language is unknown. IndexedString language() const; /// If the recursive imports of the attached TopDUContext are cached, this returns the cached imports. /// This works without loading the top-context. const TopDUContext::IndexedRecursiveImports& importsCache() const; ///Sets the language for this top-context. Each top-context should get the language assigned that can by used ///in order to load the language using ILanguageController. void setLanguage(IndexedString language); enum { Identity = 11 }; DUCHAIN_DECLARE_DATA(ParsingEnvironmentFile) private: friend class TopDUContext; friend class DUChainPrivate; static StaticParsingEnvironmentData* m_staticData; ///The features are managed by TopDUContext. They are set to TopDUContext::Empty when the top-context is persistently destroyed, ///so the persistent feature-satisfaction cache can be cleared. void setFeatures(TopDUContext::Features); void setTopContext(KDevelop::IndexedTopDUContext context); bool featuresMatch(KDevelop::TopDUContext::Features minimumFeatures, QSet< const KDevelop::ParsingEnvironmentFile* >& checked) const; void setImportsCache(const TopDUContext::IndexedRecursiveImports&); }; // TODO: why is this needed/what is it supposed to print? just the pointer value? inline QDebug operator<<(QDebug s, const QExplicitlySharedDataPointer& p) { s.nospace() << p->url(); return s.space(); } } #endif diff --git a/language/duchain/persistentsymboltable.cpp b/language/duchain/persistentsymboltable.cpp index 3fe926657..00b5260cf 100644 --- a/language/duchain/persistentsymboltable.cpp +++ b/language/duchain/persistentsymboltable.cpp @@ -1,422 +1,421 @@ /* 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"), 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 { explicit 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 = nullptr; } } struct DebugVisitor { explicit 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/tests/bench_hashes.cpp b/language/duchain/tests/bench_hashes.cpp index f6ae954fd..77b771080 100644 --- a/language/duchain/tests/bench_hashes.cpp +++ b/language/duchain/tests/bench_hashes.cpp @@ -1,316 +1,315 @@ /* * 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 "bench_hashes.h" #include #include #include #include -#include #include #include #include // similar to e.g. modificationrevision.cpp struct DataT { QDateTime a; QDateTime b; }; typedef QPair DataPair; typedef QVector InputData; struct IndexedStringHash { inline uint operator() (const KDevelop::IndexedString& str) const { return str.hash(); } }; typedef std::unordered_map StlHash; inline void insertData(StlHash& hash, const InputData& data) { foreach(const DataPair& pair, data) { hash.insert(std::make_pair(pair.first, pair.second)); } } typedef QHash QStringHash; inline void insertData(QStringHash& hash, const InputData& data) { foreach(const DataPair& pair, data) { hash.insert(pair.first, pair.second); } } QTEST_GUILESS_MAIN(BenchHashes); using namespace KDevelop; Q_DECLARE_METATYPE(InputData) void BenchHashes::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); qRegisterMetaType(); } void BenchHashes::cleanupTestCase() { TestCore::shutdown(); } void BenchHashes::feedData() { QTest::addColumn("useStl"); QTest::addColumn("data"); InputData data; QVector sizes = QVector() << 100 << 1000 << 10000 << 100000; foreach(int size, sizes) { for(int i = data.size(); i < size; ++i) { data << qMakePair(IndexedString(QString::number(i)), DataT()); } QCOMPARE(data.size(), size); QTest::newRow(qPrintable(QStringLiteral("unordered_map-%1").arg(size))) << true << data; QTest::newRow(qPrintable(QStringLiteral("qhash-%1").arg(size))) << false << data; } } void BenchHashes::insert() { QFETCH(bool, useStl); QFETCH(InputData, data); if (useStl) { QBENCHMARK { StlHash hash; insertData(hash, data); } } else { QBENCHMARK { QStringHash hash; insertData(hash, data); } } } void BenchHashes::insert_data() { feedData(); } void BenchHashes::find() { QFETCH(bool, useStl); QFETCH(InputData, data); if(useStl) { StlHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { (void) hash.find(pair.first); } } } else { QStringHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { (void) hash.find(pair.first); } } } } void BenchHashes::find_data() { feedData(); } void BenchHashes::constFind() { QFETCH(bool, useStl); QFETCH(InputData, data); if(useStl) { StlHash hash; insertData(hash, data); const StlHash& constHash = hash; QBENCHMARK { foreach(const DataPair& pair, data) { (void) constHash.find(pair.first); } } } else { QStringHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { (void) hash.constFind(pair.first); } } } } void BenchHashes::constFind_data() { feedData(); } void BenchHashes::remove() { QFETCH(bool, useStl); QFETCH(InputData, data); if(useStl) { StlHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { hash.erase(pair.first); } } } else { QStringHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { hash.remove(pair.first); } } } } void BenchHashes::remove_data() { feedData(); } struct TypeRepoTestData { size_t size; void* ptr; }; /** * somewhat artificial benchmark to test speed impact if we'd ever change * the underlying data type of the TypeSystem / TypeRegister. */ void BenchHashes::typeRepo() { QFETCH(int, type); if (type == 1 || type == 2) { QVector v; for(int i = 0; i < 100; ++i) { v.append(new TypeRepoTestData); } if (type == 1) { QBENCHMARK { for(int i = 0; i < 100; ++i) { v.at(i)->size++; } } } else if (type == 2) { TypeRepoTestData** a = v.data(); QBENCHMARK { for(int i = 0; i < 100; ++i) { a[i]->size++; } } } } else if (type == 3) { QHash v; for(int i = 0; i < 100; ++i) { v[i] = new TypeRepoTestData; } QBENCHMARK { for(int i = 0; i < 100; ++i) { v.value(i)->size++; } } } else if (type == 4) { QMap v; for(int i = 0; i < 100; ++i) { v[i] = new TypeRepoTestData; } QBENCHMARK { for(int i = 0; i < 100; ++i) { v.value(i)->size++; } } } else if (type == 5) { std::unordered_map v; for(int i = 0; i < 100; ++i) { v[i] = new TypeRepoTestData; } QBENCHMARK { for(int i = 0; i < 100; ++i) { v.at(i)->size++; } } } else if (type == 6) { // for the idea, look at c++'s lexer.cpp const int vectors = 5; typedef QPair Pair; typedef QVarLengthArray InnerVector; QVarLengthArray v; v.resize(vectors); for(int i = 0; i < 100; ++i) { v[i % vectors] << qMakePair(i, new TypeRepoTestData); } QBENCHMARK { for(int i = 0; i < 100; ++i) { foreach(const Pair& p, v.at(i % vectors)) { if (p.first == i) { p.second->size++; break; } } } } } else if (type == 0) { QBENCHMARK {} } } void BenchHashes::typeRepo_data() { QTest::addColumn("type"); QTest::newRow("noop") << 0; QTest::newRow("vector") << 1; QTest::newRow("vector-raw") << 2; QTest::newRow("qhash") << 3; QTest::newRow("qmap") << 4; QTest::newRow("unordered_map") << 5; QTest::newRow("nested-vector") << 6; } diff --git a/language/duchain/tests/test_duchainshutdown.cpp b/language/duchain/tests/test_duchainshutdown.cpp index 7f82aaa34..5ba763de8 100644 --- a/language/duchain/tests/test_duchainshutdown.cpp +++ b/language/duchain/tests/test_duchainshutdown.cpp @@ -1,99 +1,99 @@ /* * This file is part of KDevelop * Copyright 2014 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_duchainshutdown.h" #include #include #include #include #include -#include +#include using namespace KDevelop; void TestDUChainShutdown::initTestCase() { AutoTestShell::init(); m_core = TestCore::initialize(Core::NoUi); } void TestDUChainShutdown::cleanupTestCase() { TestCore::shutdown(); } void TestDUChainShutdown::runTest() { const QString ctxId = QStringLiteral("foo::bar::asdf"); const QString path = QStringLiteral("/foo/myurl"); const QString myLang = QStringLiteral("fooLang"); // step 1, store a bunch of data in the repository IndexedTopDUContext idxTop; IndexedDUContext idxCtx; { DUChainWriteLocker lock; auto file = new ParsingEnvironmentFile(IndexedString(path)); file->setLanguage(IndexedString(myLang)); ReferencedTopDUContext top(new TopDUContext(IndexedString(path), RangeInRevision(1, 2, 3, 4), file)); DUChain::self()->addDocumentChain(top); idxTop = IndexedTopDUContext(top); QVERIFY(idxTop.isValid()); QVERIFY(idxTop.isLoaded()); auto ctx = new DUContext(RangeInRevision(1, 2, 2, 3), top); ctx->setLocalScopeIdentifier(QualifiedIdentifier(ctxId)); QCOMPARE(top->childContexts().size(), 1); idxCtx = IndexedDUContext(ctx); QVERIFY(idxCtx.isValid()); } // shutdown and reinitialize - this should not crash :) m_core->setShuttingDown(true); DUChain::self()->shutdown(); m_core->setShuttingDown(false); DUChain::self()->initialize(); { DUChainReadLocker lock; // now verify that the data was properly restored QVERIFY(!idxTop.isLoaded()); QVERIFY(idxTop.isValid()); ReferencedTopDUContext top(idxTop.data()); QVERIFY(top); QVERIFY(idxTop.isLoaded()); QCOMPARE(top->childContexts().size(), 1); QCOMPARE(top->childContexts().first()->localScopeIdentifier().toString(), QStringLiteral("foo::bar::asdf")); QCOMPARE(idxCtx.data(), top->childContexts().first()); } { DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(idxTop.data()); } } QTEST_MAIN(TestDUChainShutdown) diff --git a/language/duchain/topducontext.cpp b/language/duchain/topducontext.cpp index 97ee66edc..13e864933 100644 --- a/language/duchain/topducontext.cpp +++ b/language/duchain/topducontext.cpp @@ -1,1167 +1,1165 @@ /* 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(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(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(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(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(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 == 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 = 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 = 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, 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, 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, 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, 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 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(nullptr)); } IndexedString TopDUContext::url() const { return d_func()->m_url; } } diff --git a/language/duchain/topducontextdynamicdata.cpp b/language/duchain/topducontextdynamicdata.cpp index b3ce85416..687ee559c 100644 --- a/language/duchain/topducontextdynamicdata.cpp +++ b/language/duchain/topducontextdynamicdata.cpp @@ -1,842 +1,841 @@ /* 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 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(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 = 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 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 nullptr; } TopDUContextDynamicData& target(*ret->m_dynamicData); target.m_data.clear(); target.m_dataLoaded = false; target.m_onDisk = true; ret->rebuildDynamicData(nullptr, topContextIndex); target.m_topContextData.append({topContextData, (uint)0}); return ret; }else{ 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/topducontextdynamicdata.h b/language/duchain/topducontextdynamicdata.h index bd86cf37a..78accde4d 100644 --- a/language/duchain/topducontextdynamicdata.h +++ b/language/duchain/topducontextdynamicdata.h @@ -1,189 +1,187 @@ /* This 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. */ #ifndef KDEVPLATFORM_TOPDUCONTEXTDYNAMICDATA_H #define KDEVPLATFORM_TOPDUCONTEXTDYNAMICDATA_H #include -#include #include -#include #include #include "problem.h" class QFile; namespace KDevelop { class TopDUContext; class DUContext; class Declaration; class IndexedString; class IndexedDUContext; class DUChainBaseData; ///This class contains dynamic data of a top-context, and also the repository that contains all the data within this top-context. class TopDUContextDynamicData { public: explicit TopDUContextDynamicData(TopDUContext* topContext); ~TopDUContextDynamicData(); void clear(); /** * Allocates an index for the given declaration in this top-context. * The returned index is never zero. * @param temporary whether the declaration is temporary. If it is, it will be stored separately, not stored to disk, * and a duchain write-lock is not needed. Else, you need a write-lock when calling this. */ uint allocateDeclarationIndex(Declaration* decl, bool temporary); Declaration* getDeclarationForIndex(uint index) const; bool isDeclarationForIndexLoaded(uint index) const; void clearDeclarationIndex(Declaration* decl); /** * Allocates an index for the given context in this top-context. * The returned index is never zero. * @param temporary whether the context is temporary. If it is, it will be stored separately, not stored to disk, * and a duchain write-lock is not needed. Else, you need a write-lock when calling this. */ uint allocateContextIndex(DUContext* ctx, bool temporary); DUContext* getContextForIndex(uint index) const; bool isContextForIndexLoaded(uint index) const; void clearContextIndex(DUContext* ctx); /** * Allocates an index for the given problem in this top-context. * The returned index is never zero. */ uint allocateProblemIndex(ProblemPointer problem); ProblemPointer getProblemForIndex(uint index) const; void clearProblems(); ///Stores this top-context to disk void store(); ///Stores all remainings of this top-context that are on disk. The top-context will be fully dynamic after this. void deleteOnDisk(); ///Whether this top-context is on disk(Either has been loaded, or has been stored) bool isOnDisk() const; ///Loads the top-context from disk, or returns zero on failure. The top-context will not be registered anywhere, and will have no ParsingEnvironmentFile assigned. ///Also loads all imported contexts. The Declarations/Contexts will be correctly initialized, and put into the symbol tables if needed. static TopDUContext* load(uint topContextIndex); ///Loads only the url out of the data stored on disk for the top-context. static IndexedString loadUrl(uint topContextIndex); static bool fileExists(uint topContextIndex); ///Loads only the list of importers out of the data stored on disk for the top-context. static QList loadImporters(uint topContextIndex); static QList loadImports(uint topContextIndex); bool isTemporaryContextIndex(uint index) const; bool isTemporaryDeclarationIndex(uint index) const ; bool m_deleting; ///Flag used during destruction struct ItemDataInfo { uint dataOffset; /// Offset of the data uint parentContext; /// Parent context of the data (0 means the global context) }; struct ArrayWithPosition { QByteArray array; uint position; }; private: bool hasChanged() const; void unmap(); //Converts away from an mmap opened file to a data array QString filePath() const; void loadData() const; const char* pointerInData(uint offset) const; ItemDataInfo writeDataInfo(const ItemDataInfo& info, const DUChainBaseData* data, uint& totalDataOffset); TopDUContext* m_topContext; template struct DUChainItemStorage { explicit DUChainItemStorage(TopDUContextDynamicData* data); ~DUChainItemStorage(); void clearItems(); bool itemsHaveChanged() const; void storeData(uint& currentDataOffset, const QVector& oldData); Item getItemForIndex(uint index) const; void clearItemIndex(const Item& item, const uint index); uint allocateItemIndex(const Item& item, const bool temporary); void deleteOnDisk(); bool isItemForIndexLoaded(uint index) const; void loadData(QFile* file) const; void writeData(QFile* file); //May contain zero items if they were deleted mutable QVector items; mutable QVector offsets; QVector temporaryItems; TopDUContextDynamicData* const data; }; DUChainItemStorage m_contexts; DUChainItemStorage m_declarations; DUChainItemStorage m_problems; //For temporary declarations that will not be stored to disk, like template instantiations mutable QVector m_data; mutable QVector m_topContextData; bool m_onDisk; mutable bool m_dataLoaded; mutable QFile* m_mappedFile; mutable uchar* m_mappedData; mutable size_t m_mappedDataSize; mutable bool m_itemRetrievalForbidden; }; } Q_DECLARE_TYPEINFO(KDevelop::TopDUContextDynamicData::ItemDataInfo, Q_PRIMITIVE_TYPE); #endif diff --git a/language/duchain/types/abstracttype.h b/language/duchain/types/abstracttype.h index 39157ff17..917904d48 100644 --- a/language/duchain/types/abstracttype.h +++ b/language/duchain/types/abstracttype.h @@ -1,308 +1,309 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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. */ #ifndef KDEVPLATFORM_ABSTRACTTYPE_H #define KDEVPLATFORM_ABSTRACTTYPE_H -#include #include "typepointer.h" #include +class QString; + namespace KDevelop { class AbstractTypeData; class IndexedType; class TypeVisitor; class TypeExchanger; /// This macro is used to declare type-specific data-access functions within subclasses of AbstractType #define TYPE_DECLARE_DATA(Class) \ inline Class##Data* d_func_dynamic() { makeDynamic(); return reinterpret_cast(d_ptr); } \ inline const Class##Data* d_func() const { return reinterpret_cast(d_ptr); } /// This function creates a local variable named 'd' pointing to the data type (as shortcut) #define TYPE_D(Class) const Class##Data * const d = d_func() #define TYPE_D_DYNAMIC(Class) Class##Data * const d = d_func_dynamic() /** * \brief Base class for all types. * * The AbstractType class is a base class from which all types derive. It features: * - mechanisms for visiting types * - toString() feature * - equivalence feature * - cloning of types, and * - hashing and indexing * - efficient, persistent, and reference-counted storage of types using IndexedType * * Type classes are created in a way that allows storing them in memory or on disk * efficiently. They are classes which can store arbitrary lists immediately after their * private data structures in memory (thus enabling them to be mmapped or memcopied), * or being "dynamic" where you use exactly the same class and same access functions, * but the list data is stored in a temporary KDevVarLengthArray from a central repository, * until we save it back to the static memory-region again. * * When creating an own (sub-) type, you must: * - Override equals(..), hash(). * - The functions should _fully_ distinguish all types, * in regard to all stored information, and regarding their identity. * - This can be skipped if you're overriding a base-type which already incorporates * all of your own types status within its equals/hash functions (eg. you don't add own data). * - Implement a copy-constructor in which you copy the data from the source using copyData() * - Override the clone() function in which you use the copy-constructor to clone the type * - Add an enumerator "Identity" that contains an arbitrary unique identity value of the type * - Register your type in a source-file using REGISTER_TYPE(..), @see typeregister.h * - Add a typedef "Data", that contains the actual data of the type using the mechanisms described in appendedlist.h. * That data type must follow the same inheritance chain as the type itself, so it must be based on the Data object * of the base class. See AbstractTypeData. * - Use createData() to create the data-object in a constructor (which you then reach to the parent constructor) * - Use TYPE_DECLARE_DATA(YourType) to declare the data access functions d_func and d_func_dynamic, * and then use d_func()->.. and d_func_dynamic()->.. to access your type data * - Create a constructor that only takes a reference to the type data, and passes it to the parent type * * Every type can have only one other type as base-type, * but it can have additional base-classes that are not a direct part of the type-system(@see IdentifiedType). * * \sa appendedlist.h */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractType : public QSharedData { public: typedef TypePtr Ptr; /** * An enumeration of common modifiers for data types. * If you have any language-specific modifiers that don't belong here, * you can add them at/after LanguageSpecificModifier * @warning Think twice what information you store into the type-system. * The type-system should store information that is shared among many declarations, * and attributes of specific Declarations like public/private should be stored in * the Declarations themselves, not in the type-system. */ enum CommonModifiers : quint32 { NoModifiers = 0, ConstModifier = 1 << 0, VolatileModifier = 1 << 1, TransientModifier = 1 << 2, NewModifier = 1 << 3, SealedModifier = 1 << 4, UnsafeModifier = 1 << 5, FixedModifier = 1 << 6, ShortModifier = 1 << 7, LongModifier = 1 << 8, LongLongModifier = 1 << 9, SignedModifier = 1 << 10, UnsignedModifier = 1 << 11, LanguageSpecificModifier = 1 << 12 }; /// Constructor. AbstractType(); /// Constructor from data. explicit AbstractType(AbstractTypeData& dd); /// Destructor. virtual ~AbstractType (); /** * Access the type modifiers * * \returns the type's modifiers. */ quint32 modifiers() const; /** * Set the type's modifiers. * * \param modifiers modifiers of this type. */ void setModifiers(quint32 modifiers); /** * Visitor method. Called by TypeVisitor to visit the type heirachy. * Do not reimplement this, reimplement accept0 instead. * * \param v visitor which is calling this function. */ void accept(TypeVisitor *v) const; /** * Convenience visitor method which can be called with a null type. * * \param type type to visit, may be null. * \param v visitor which is visiting the given \a type */ static void acceptType(AbstractType::Ptr type, TypeVisitor *v); /** * Returns this type as a string, preferably the same as it is expressed in the code. * * \return this type as a string */ virtual QString toString() const; ///Must always be called before anything in the data pointer is changed! ///If it's not called beforehand, the type-repository gets corrupted void makeDynamic(); ///Should return whether this type's content equals the given one ///Since this is used by the type-repository, it must compare ALL members of the data type. virtual bool equals(const AbstractType* rhs) const; /** * Should create a clone of the source-type, with as much data copied as possible without breaking the du-chain. * */ virtual AbstractType* clone() const = 0; /** * A hash-value that should have the following properties: * - When two types match on equals(), it should be same. * - When two types don't match on equals(), it should be different with a high probability. * */ virtual uint hash() const; ///This can also be called on zero types, those can then be reconstructed from the zero index IndexedType indexed() const; /// Enumeration of major data types. enum WhichType : quint8 { TypeAbstract /**< an abstract type */, TypeIntegral /**< an integral */, TypePointer /**< a pointer*/, TypeReference /**< a reference */, TypeFunction /**< a function */, TypeStructure /**< a structure */, TypeArray /**< an array */, TypeDelayed /**< a delayed type */, TypeEnumeration /**< an enumeration type */, TypeEnumerator /**< an enumerator type */, TypeAlias /**< a type-alias type */, TypeUnsure /**< may represent multiple different types */ }; /** * Determine which data type this abstract type represents. * * \returns the data type represented by this type. */ virtual WhichType whichType() const; enum { Identity = 1 }; /** * Should, like accept0, be implemented by all types that hold references to other types. * * If this is called on one type, that type should call exchangeTypes(..) with all its referenced sub-types. * The type itself does not recurse into the sub-types, that can be done by the exchanger itself if desired. * */ virtual void exchangeTypes( TypeExchanger* exchanger ); /** * Method to create copies of internal type data. You must use this to create the internal * data instances in copy constructors. It is needed, because it may need to allocate more memory * for appended lists. * * \param rhs data to copy * \returns copy of the data */ template static typename Type::Data& copyData(const typename Type::Data& rhs) { uint size; if(!rhs.m_dynamic) size = sizeof(typename Type::Data); //Create a dynamic data instance else size = rhs.dynamicSize(); //Create a constant data instance, that holds all the data embedded. typename Type::Data& ret(*new (new char[size]) typename Type::Data(rhs)); ret.template setTypeClassId(); return ret; } /** * As above, but does not support copying data into a lower class(Should not be used while cloning) */ template static DataType& copyDataDirectly(const DataType& rhs) { uint size; if(!rhs.m_dynamic) size = sizeof(DataType); //Create a dynamic data instance else size = rhs.dynamicSize(); //Create a constant data instance, that holds all the data embedded. return *new (new char[size]) DataType(rhs); } /** * Method to create internal data structures. Use this in normal constructors. * * \returns the internal data structure */ template static typename Type::Data& createData() { typename Type::Data& ret(*new (new char[sizeof(typename Type::Data)]) typename Type::Data()); ret.template setTypeClassId(); return ret; } typedef AbstractTypeData Data; protected: /** * Visitor method, reimplement to allow visiting of types. * * \param v visitor which is visiting. */ virtual void accept0 (TypeVisitor *v) const = 0; /// toString() function which can provide \a spaceOnLeft rather than on right if desired. QString toString(bool spaceOnLeft) const; AbstractTypeData* d_ptr; TYPE_DECLARE_DATA(AbstractType) friend class AbstractTypeDataRequest; private: AbstractType(const AbstractType& rhs); }; /** * You can use these instead of dynamic_cast, for basic types it has better performance because it checks the whichType() member */ template inline To fastCast(AbstractType* from) { return dynamic_cast(from); } template inline const To fastCast(const AbstractType* from) { return const_cast(fastCast(const_cast(from))); //Hack so we don't need to define the functions twice, once for const, and once for not const } } #endif diff --git a/language/duchain/types/typerepository.cpp b/language/duchain/types/typerepository.cpp index ac4f59e3e..6d38a6b31 100644 --- a/language/duchain/types/typerepository.cpp +++ b/language/duchain/types/typerepository.cpp @@ -1,156 +1,155 @@ /* 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 "typerepository.h" -#include #include #include #include "util/debug.h" #include "../types/typesystemdata.h" #include "../types/typeregister.h" #include #include #define DEBUG_TYPE_REPOSITORY #define ASSERT_ON_PROBLEM namespace KDevelop { class AbstractTypeDataRequest { public: AbstractTypeDataRequest(const AbstractType& type) : m_item(type) { } enum { AverageSize = sizeof(AbstractTypeData) + 12 }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return TypeSystem::self().dynamicSize(*m_item.d_ptr); } void createItem(AbstractTypeData* item) const { TypeSystem::self().copy(*m_item.d_ptr, *item, true); Q_ASSERT(!item->m_dynamic); #ifdef DEBUG_TYPE_REPOSITORY AbstractType::Ptr otherType( TypeSystem::self().create(const_cast(item)) ); if(!otherType->equals(&m_item)) { //For debugging, so one can trace what happened qCWarning(LANGUAGE) << "created type in repository does not equal source type:" << m_item.toString() << otherType->toString(); TypeSystem::self().copy(*m_item.d_ptr, *item, true); otherType->equals(&m_item); } #ifdef ASSERT_ON_PROBLEM Q_ASSERT(otherType->equals(&m_item)); #endif #endif item->inRepository = true; } static void destroy(AbstractTypeData* item, KDevelop::AbstractItemRepository&) { TypeSystem::self().callDestructor(item); } static bool persistent(const AbstractTypeData* item) { // Don't try to delete release items for which the factory is not loaded, as that will lead to a crash/assertion later return (bool)item->refCount || !TypeSystem::self().isFactoryLoaded(*item); } bool equals(const AbstractTypeData* item) const { AbstractType::Ptr otherType( TypeSystem::self().create(const_cast(item)) ); if(!otherType) return false; return m_item.equals(otherType.data()); } const AbstractType& m_item; }; //The object is created in a function, to prevent initialization-order issues static RepositoryManager< ItemRepository, false>& typeRepository() { static RepositoryManager< ItemRepository, false> repository(QStringLiteral("Type Repository")); return repository; } void initTypeRepository() { typeRepository(); } AbstractRepositoryManager* typeRepositoryManager() { return &typeRepository(); } uint TypeRepository::indexForType(const AbstractType::Ptr input) { if(!input) return 0; uint i = typeRepository()->index(AbstractTypeDataRequest(*input)); #ifdef DEBUG_TYPE_REPOSITORY AbstractType::Ptr t = typeForIndex(i); if(!t->equals(input.data())) { qCWarning(LANGUAGE) << "found type in repository does not equal source type:" << input->toString() << t->toString(); t->equals(input.data()); } #ifdef ASSERT_ON_PROBLEM Q_ASSERT(t->equals(input.data())); Q_ASSERT(input->equals(t.data())); #endif #endif return i; } AbstractType::Ptr TypeRepository::typeForIndex(uint index) { if(index == 0) return AbstractType::Ptr(); return AbstractType::Ptr( TypeSystem::self().create(const_cast(typeRepository()->itemFromIndex(index))) ); } void TypeRepository::increaseReferenceCount(uint index, ReferenceCountManager* manager) { if(!index) return; QMutexLocker lock(typeRepository()->mutex()); AbstractTypeData* data = typeRepository()->dynamicItemFromIndexSimple(index); Q_ASSERT(data); if(manager) manager->increase(data->refCount, index); else ++data->refCount; } void TypeRepository::decreaseReferenceCount(uint index, ReferenceCountManager* manager) { if(!index) return; QMutexLocker lock(typeRepository()->mutex()); AbstractTypeData* data = typeRepository()->dynamicItemFromIndexSimple(index); Q_ASSERT(data); Q_ASSERT(data->refCount > 0); if(manager) manager->decrease(data->refCount, index); else --data->refCount; } } diff --git a/language/duchain/types/typesystem.h b/language/duchain/types/typesystem.h index 2457f5f93..fd9793f04 100644 --- a/language/duchain/types/typesystem.h +++ b/language/duchain/types/typesystem.h @@ -1,130 +1,127 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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. */ #ifndef KDEVPLATFORM_TYPESYSTEM_H #define KDEVPLATFORM_TYPESYSTEM_H -#include -#include - #include "typepointer.h" #include "../identifier.h" #include "abstracttype.h" namespace KDevelop { class AbstractTypeDataRequest; class AbstractType; class IntegralType; class PointerType; class ReferenceType; class FunctionType; class StructureType; class ArrayType; class TypeExchanger; class KDEVPLATFORMLANGUAGE_EXPORT TypeVisitor { public: virtual ~TypeVisitor (); virtual bool preVisit (const AbstractType *) = 0; virtual void postVisit (const AbstractType *) = 0; ///Return whether sub-types should be visited(same for the other visit functions) virtual bool visit(const AbstractType*) = 0; virtual void visit (const IntegralType *) = 0; virtual bool visit (const PointerType *) = 0; virtual void endVisit (const PointerType *) = 0; virtual bool visit (const ReferenceType *) = 0; virtual void endVisit (const ReferenceType *) = 0; virtual bool visit (const FunctionType *) = 0; virtual void endVisit (const FunctionType *) = 0; virtual bool visit (const StructureType *) = 0; virtual void endVisit (const StructureType *) = 0; virtual bool visit (const ArrayType *) = 0; virtual void endVisit (const ArrayType *) = 0; }; class KDEVPLATFORMLANGUAGE_EXPORT SimpleTypeVisitor : public TypeVisitor { public: /// When using SimpleTypeVisitor, the visit taking an AbstractType is the only function /// you must override to collect all types. using TypeVisitor::visit; bool preVisit (const AbstractType *) override ; void postVisit (const AbstractType *) override ; void visit (const IntegralType *) override ; bool visit (const PointerType *) override ; void endVisit (const PointerType *) override ; bool visit (const ReferenceType *) override ; void endVisit (const ReferenceType *) override ; bool visit (const FunctionType *) override ; void endVisit (const FunctionType *) override ; bool visit (const StructureType *) override ; void endVisit (const StructureType *) override ; bool visit (const ArrayType *) override ; void endVisit (const ArrayType *) override ; }; /** * A class that can be used to walk through all types that are references from one type, and exchange them with other types. * Examples for such types: Base-classes of a class, function-argument types of a function, etc. * */ class KDEVPLATFORMLANGUAGE_EXPORT TypeExchanger { public: virtual ~TypeExchanger() { } /** * By default should return the given type, and can return another type that the given should be replaced with. * Types should allow replacing all their held types using this from within their exchangeTypes function. * The default-implementation recurses the exchange, so should be called from within the derived implementation if that is wished. * */ virtual AbstractType::Ptr exchange( const AbstractType::Ptr& ); }; ///A simple type-exchanger that replaces one type with another class KDEVPLATFORMLANGUAGE_EXPORT SimpleTypeExchanger : public TypeExchanger { public: SimpleTypeExchanger(AbstractType::Ptr replace, AbstractType::Ptr replaceWith); AbstractType::Ptr exchange( const AbstractType::Ptr& ) override ; private: AbstractType::Ptr m_replace, m_replaceWith; }; } #endif // KDEVPLATFORM_TYPESYSTEM_H diff --git a/language/duchain/uses.cpp b/language/duchain/uses.cpp index 263a2b338..0ccc29978 100644 --- a/language/duchain/uses.cpp +++ b/language/duchain/uses.cpp @@ -1,200 +1,197 @@ /* 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 "uses.h" -#include -#include - #include "declarationid.h" #include "duchainpointer.h" #include "serialization/itemrepository.h" #include "topducontext.h" namespace KDevelop { DEFINE_LIST_MEMBER_HASH(UsesItem, uses, IndexedTopDUContext) class UsesItem { public: UsesItem() { initializeAppendedLists(); } UsesItem(const UsesItem& rhs, bool dynamic = true) : declaration(rhs.declaration) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~UsesItem() { 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 declaration.hash(); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(UsesItem); } DeclarationId declaration; START_APPENDED_LISTS(UsesItem); APPENDED_LIST_FIRST(UsesItem, IndexedTopDUContext, uses); END_APPENDED_LISTS(UsesItem, uses); }; class UsesRequestItem { public: UsesRequestItem(const UsesItem& 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(UsesItem* item) const { new (item) UsesItem(m_item, false); } static void destroy(UsesItem* item, KDevelop::AbstractItemRepository&) { item->~UsesItem(); } static bool persistent(const UsesItem* /*item*/) { return true; } bool equals(const UsesItem* item) const { return m_item.declaration == item->declaration; } const UsesItem& m_item; }; class UsesPrivate { public: UsesPrivate() : m_uses(QStringLiteral("Use Map")) { } //Maps declaration-ids to Uses ItemRepository m_uses; }; Uses::Uses() : d(new UsesPrivate()) { } Uses::~Uses() { delete d; } void Uses::addUse(const DeclarationId& id, const IndexedTopDUContext& use) { UsesItem item; item.declaration = id; item.usesList().append(use); UsesRequestItem request(item); uint index = d->m_uses.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const UsesItem* oldItem = d->m_uses.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->usesSize(); ++a) { if(oldItem->uses()[a] == use) return; //Already there item.usesList().append(oldItem->uses()[a]); } d->m_uses.deleteItem(index); } //This inserts the changed item d->m_uses.index(request); } void Uses::removeUse(const DeclarationId& id, const IndexedTopDUContext& use) { UsesItem item; item.declaration = id; UsesRequestItem request(item); uint index = d->m_uses.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const UsesItem* oldItem = d->m_uses.itemFromIndex(index); for(unsigned int a = 0; a < oldItem->usesSize(); ++a) if(!(oldItem->uses()[a] == use)) item.usesList().append(oldItem->uses()[a]); d->m_uses.deleteItem(index); Q_ASSERT(d->m_uses.findIndex(item) == 0); //This inserts the changed item if(item.usesSize() != 0) d->m_uses.index(request); } } bool Uses::hasUses(const DeclarationId& id) const { UsesItem item; item.declaration = id; return (bool) d->m_uses.findIndex(item); } KDevVarLengthArray Uses::uses(const DeclarationId& id) const { KDevVarLengthArray ret; UsesItem item; item.declaration = id; UsesRequestItem request(item); uint index = d->m_uses.findIndex(item); if(index) { const UsesItem* repositoryItem = d->m_uses.itemFromIndex(index); FOREACH_FUNCTION(const IndexedTopDUContext& decl, repositoryItem->uses) ret.append(decl); } return ret; } } diff --git a/language/highlighting/codehighlighting.h b/language/highlighting/codehighlighting.h index ff8af99e4..b4217d18a 100644 --- a/language/highlighting/codehighlighting.h +++ b/language/highlighting/codehighlighting.h @@ -1,221 +1,220 @@ /* * 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. */ #ifndef KDEVPLATFORM_CODEHIGHLIGHTING_H #define KDEVPLATFORM_CODEHIGHLIGHTING_H #include #include -#include #include #include #include #include #include #include namespace KDevelop { class DUContext; class Declaration; typedef QVector ColorMap; class CodeHighlighting; struct HighlightingEnumContainer { enum Types { UnknownType, //Primary highlighting: LocalClassMemberType, InheritedClassMemberType, LocalVariableType, //Other highlighting: ClassType, FunctionType, ForwardDeclarationType, EnumType, EnumeratorType, TypeAliasType, MacroType, /// Declaration of a macro such as "#define FOO" MacroFunctionLikeType, /// Declaration of a function like macro such as "#define FOO()" //If none of the above match: MemberVariableType, NamespaceVariableType, GlobalVariableType, //Most of these are currently not used: ArgumentType, CodeType, FileType, NamespaceType, ScopeType, TemplateType, TemplateParameterType, FunctionVariableType, ErrorVariableType }; enum Contexts { DefinitionContext, DeclarationContext, ReferenceContext }; }; struct HighlightedRange { RangeInRevision range; KTextEditor::Attribute::Ptr attribute; bool operator<(const HighlightedRange& rhs) const { return range.start < rhs.range.start; } }; /** * Code highlighting instance that is used to apply code highlighting to one specific top context * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlightingInstance : public HighlightingEnumContainer { public: explicit CodeHighlightingInstance(const CodeHighlighting* highlighting) : m_useClassCache(false), m_highlighting(highlighting) { } virtual ~CodeHighlightingInstance() { } virtual void highlightDeclaration(KDevelop::Declaration* declaration, const QColor &color); virtual void highlightUse(KDevelop::DUContext* context, int index, const QColor &color); virtual void highlightUses(KDevelop::DUContext* context); void highlightDUChain(KDevelop::TopDUContext* context); void highlightDUChain(KDevelop::DUContext* context, QHash colorsForDeclarations, ColorMap); KDevelop::Declaration* localClassFromCodeContext(KDevelop::DUContext* context) const; /** * @param context Should be the context from where the declaration is used, if a use is highlighted. * */ virtual Types typeForDeclaration(KDevelop::Declaration* dec, KDevelop::DUContext* context) const; /** * Decides whether to apply auto-generated rainbow colors to @p dec. * Default implementation only applies that to local variables in functions. */ virtual bool useRainbowColor(KDevelop::Declaration* dec) const; //A temporary hash for speedup mutable QHash m_contextClasses; //Here the colors of function context are stored until they are merged into the function body mutable QMap > m_functionColorsForDeclarations; mutable QMap m_functionDeclarationsForColors; mutable bool m_useClassCache; const CodeHighlighting* m_highlighting; QVector m_highlight; }; /** * General class representing the code highlighting for one language * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlighting : public QObject, public KDevelop::ICodeHighlighting, public HighlightingEnumContainer { Q_OBJECT Q_INTERFACES(KDevelop::ICodeHighlighting) public: explicit CodeHighlighting(QObject* parent); ~CodeHighlighting() override; /// This function is thread-safe /// @warning The duchain must not be locked when this is called (->possible deadlock) void highlightDUChain(ReferencedTopDUContext context) override; //color should be zero when undecided KTextEditor::Attribute::Ptr attributeForType(Types type, Contexts context, const QColor &color) const; KTextEditor::Attribute::Ptr attributeForDepth(int depth) const; /// This function is thread-safe /// Returns whether a highlighting is already given for the given url bool hasHighlighting(IndexedString url) const override; private: //Returns whether the given attribute was set by the code highlighting, and not by something else //Always returns true when the attribute is zero bool isCodeHighlight(KTextEditor::Attribute::Ptr attr) const; protected: //Can be overridden to create an own instance type virtual CodeHighlightingInstance* createInstance() const; private: /// Highlighting of one specific document struct DocumentHighlighting { IndexedString m_document; qint64 m_waitingRevision; // The ranges are sorted by range start, so they can easily be matched QVector m_waiting; QVector m_highlightedRanges; }; QMap m_highlights; friend class CodeHighlightingInstance; mutable QHash m_definitionAttributes; mutable QHash m_declarationAttributes; mutable QHash m_referenceAttributes; mutable QList m_depthAttributes; // Should be used to enable/disable the colorization of local variables and their uses bool m_localColorization; // Should be used to enable/disable the colorization of global types and their uses bool m_globalColorization; mutable QMutex m_dataMutex; private Q_SLOTS: void clearHighlightingForDocument(KDevelop::IndexedString document); void applyHighlighting(void* highlighting); void trackerDestroyed(QObject* object); /// when the colors change we must invalidate our local caches void adaptToColorChanges(); void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*); void aboutToRemoveText(const KTextEditor::Range&); }; } #endif // 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/abbreviations.cpp b/language/interfaces/abbreviations.cpp index 7dfaab1e7..ac06edd1c 100644 --- a/language/interfaces/abbreviations.cpp +++ b/language/interfaces/abbreviations.cpp @@ -1,153 +1,155 @@ /* This file is part of KDevelop Copyright 2014 Sven Brauch 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 "abbreviations.h" +#include + bool matchesAbbreviationHelper(const QStringRef &word, const QString &typed, const QVarLengthArray< int, 32 > &offsets, int &depth, int atWord, int i) { int atLetter = 1; for ( ; i < typed.size(); i++ ) { const QChar c = typed.at(i).toLower(); bool haveNextWord = offsets.size() > atWord + 1; bool canCompare = atWord != -1 && word.size() > offsets.at(atWord) + atLetter; if ( canCompare && c == word.at(offsets.at(atWord) + atLetter).toLower() ) { // the typed letter matches a letter after the current word beginning if ( ! haveNextWord || c != word.at(offsets.at(atWord + 1)).toLower() ) { // good, simple case, no conflict atLetter += 1; continue; } // For maliciously crafted data, the code used here theoretically can have very high // complexity. Thus ensure we don't run into this case, by limiting the amount of branches // we walk through to 128. depth++; if ( depth > 128 ) { return false; } // the letter matches both the next word beginning and the next character in the word if ( haveNextWord && matchesAbbreviationHelper(word, typed, offsets, depth, atWord + 1, i + 1) ) { // resolving the conflict by taking the next word's first character worked, fine return true; } // otherwise, continue by taking the next letter in the current word. atLetter += 1; continue; } else if ( haveNextWord && c == word.at(offsets.at(atWord + 1)).toLower() ) { // the typed letter matches the next word beginning atWord++; atLetter = 1; continue; } // no match return false; } // all characters of the typed word were matched return true; } bool matchesAbbreviation(const QStringRef &word, const QString &typed) { // A mismatch is very likely for random even for the first letter, // thus this optimization makes sense. if ( word.at(0).toLower() != typed.at(0).toLower() ) { return false; } // First, check if all letters are contained in the word in the right order. int atLetter = 0; foreach ( const QChar c, typed ) { while ( c.toLower() != word.at(atLetter).toLower() ) { atLetter += 1; if ( atLetter >= word.size() ) { return false; } } } bool haveUnderscore = true; QVarLengthArray offsets; // We want to make "KComplM" match "KateCompletionModel"; this means we need // to allow parts of the typed text to be not part of the actual abbreviation, // which consists only of the uppercased / underscored letters (so "KCM" in this case). // However it might be ambigous whether a letter is part of such a word or part of // the following abbreviation, so we need to find all possible word offsets first, // then compare. for ( int i = 0; i < word.size(); i++ ) { const QChar c = word.at(i); if ( c == QLatin1Char('_') || c == QLatin1Char('-') ) { haveUnderscore = true; } else if ( haveUnderscore || c.isUpper() ) { offsets.append(i); haveUnderscore = false; } } int depth = 0; return matchesAbbreviationHelper(word, typed, offsets, depth); } bool matchesPath(const QString &path, const QString &typed) { int consumed = 0; int pos = 0; // try to find all the characters in typed in the right order in the path; // jumps are allowed everywhere while ( consumed < typed.size() && pos < path.size() ) { if ( typed.at(consumed).toLower() == path.at(pos).toLower() ) { consumed++; } pos++; } return consumed == typed.size(); } bool matchesAbbreviationMulti(const QString &word, const QStringList &typedFragments) { if ( word.size() == 0 ) { return true; } int lastSpace = 0; int matchedFragments = 0; for ( int i = 0; i < word.size(); i++ ) { const QChar& c = word.at(i); bool isDoubleColon = false; // if it's not a separation char, walk over it. if ( c != ' ' && c != '/' && i != word.size() - 1 ) { if ( c != ':' && i < word.size()-1 && word.at(i+1) != ':' ) { continue; } isDoubleColon = true; i++; } // if it's '/', ' ' or '::', split the word here and check the next sub-word. const QStringRef wordFragment = word.midRef(lastSpace, i-lastSpace); const QString& typedFragment = typedFragments.at(matchedFragments); Q_ASSERT(!typedFragment.isEmpty()); if ( !wordFragment.isEmpty() && matchesAbbreviation(wordFragment, typedFragment) ) { matchedFragments += 1; if ( matchedFragments == typedFragments.size() ) { break; } } lastSpace = isDoubleColon ? i : i+1; } return matchedFragments == typedFragments.size(); } // kate: space-indent on; indent-width 2 diff --git a/language/interfaces/abbreviations.h b/language/interfaces/abbreviations.h index 6adddb707..73e87b48d 100644 --- a/language/interfaces/abbreviations.h +++ b/language/interfaces/abbreviations.h @@ -1,52 +1,53 @@ /* This file is part of KDevelop Copyright 2014 Sven Brauch This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ABBREVIATIONS_H #define KDEVPLATFORM_ABBREVIATIONS_H -#include #include -#include -#include #include +class QStringList; +class QStringRef; +class QString; + // Taken and adapted for kdevelop from katecompletionmodel.cpp KDEVPLATFORMLANGUAGE_EXPORT bool matchesAbbreviationHelper(const QStringRef& word, const QString& typed, const QVarLengthArray& offsets, int& depth, int atWord = -1, int i = 0); KDEVPLATFORMLANGUAGE_EXPORT bool matchesAbbreviation(const QStringRef& word, const QString& typed); KDEVPLATFORMLANGUAGE_EXPORT bool matchesPath(const QString& path, const QString& typed); /** * @brief Matches a word against a list of search fragments. * The word will be split at separation characters (space, / and ::) and * the resulting fragments will be matched one-by-one against the typed fragments. * If all typed fragments can be matched against a fragment in word in the right order * (skipping is allowed), true will be returned. * @param word the word to search in * @param typedFragments the fragments which were typed * @return bool true if match, else false */ KDEVPLATFORMLANGUAGE_EXPORT bool matchesAbbreviationMulti(const QString& word, const QStringList& typedFragments); #endif // kate: space-indent on; indent-width 2 diff --git a/language/interfaces/quickopenfilter.h b/language/interfaces/quickopenfilter.h index 943d76842..bc755b496 100644 --- a/language/interfaces/quickopenfilter.h +++ b/language/interfaces/quickopenfilter.h @@ -1,281 +1,280 @@ /* * 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. */ #ifndef KDEVPLATFORM_QUICKOPEN_FILTER_H #define KDEVPLATFORM_QUICKOPEN_FILTER_H -#include #include #include "abbreviations.h" #include namespace KDevelop { /** * This is a simple filter-implementation that helps you implementing own quickopen data-providers. * You should use it when possible, because that way additional features(like regexp filtering) can * be implemented in a central place. * * This implementation does incremental filtering while * typing text, so it quite efficient for the most common case. * * The simplest way of using this is by reimplementing your data-provider * based on QuickOpenDataProviderBase and KDevelop::Filter\. * * What you need to do to use it: * * Reimplement itemText(..) to provide the text filtering * should be performend on(This must be efficient). * * Call setItems(..) when starting a new quickopen session, or when the content * changes, to initialize the filter with your data. * * Call setFilter(..) with the text that should be filtered for on user-input. * * Use filteredItems() to provide data to quickopen. * * @tparam Item should be the type that holds all the information you need. * The filter will hold the data, and you can access it through "items()". */ template class Filter { public: virtual ~Filter() { } ///Clears the filter, but not the data. void clearFilter() { m_filtered = m_items; m_oldFilterText.clear(); } ///Clears the filter and sets new data. The filter-text will be lost. void setItems( const QList& data ) { m_items = data; clearFilter(); } const QList& items() const { return m_items; } ///Returns the data that is left after the filtering const QList& filteredItems() const { return m_filtered; } ///Changes the filter-text and refilters the data void setFilter( const QString& text ) { if (m_oldFilterText == text) { return; } if (text.isEmpty()) { clearFilter(); return; } QList filterBase = m_filtered; if( !text.startsWith( m_oldFilterText ) ) { filterBase = m_items; //Start filtering based on the whole data } m_filtered.clear(); QStringList typedFragments = text.split(QStringLiteral("::"), QString::SkipEmptyParts); if (typedFragments.isEmpty()) { clearFilter(); return; } if ( typedFragments.last().endsWith(':') ) { // remove the trailing colon if there's only one; otherwise, // this breaks incremental filtering typedFragments.last().chop(1); } if (typedFragments.size() == 1 && typedFragments.last().isEmpty()) { clearFilter(); return; } foreach( const Item& data, filterBase ) { const QString& itemData = itemText( data ); if( itemData.contains(text, Qt::CaseInsensitive) || matchesAbbreviationMulti(itemData, typedFragments) ) { m_filtered << data; } } m_oldFilterText = text; } protected: ///Should return the text an item should be filtered by. virtual QString itemText( const Item& data ) const = 0; private: QString m_oldFilterText; QList m_filtered; QList m_items; }; } namespace KDevelop { template class PathFilter { public: ///Clears the filter, but not the data. void clearFilter() { m_filtered = m_items; m_oldFilterText.clear(); } ///Clears the filter and sets new data. The filter-text will be lost. void setItems( const QList& data ) { m_items = data; clearFilter(); } const QList& items() const { return m_items; } ///Returns the data that is left after the filtering const QList& filteredItems() const { return m_filtered; } ///Changes the filter-text and refilters the data void setFilter( const QStringList& text ) { if (m_oldFilterText == text) { return; } if (text.isEmpty()) { clearFilter(); return; } const QString joinedText = text.join(QString()); QList filterBase = m_filtered; if ( m_oldFilterText.isEmpty()) { filterBase = m_items; } else if (m_oldFilterText.mid(0, m_oldFilterText.count() - 1) == text.mid(0, text.count() - 1) && text.last().startsWith(m_oldFilterText.last())) { //Good, the prefix is the same, and the last item has been extended } else if (m_oldFilterText.size() == text.size() - 1 && m_oldFilterText == text.mid(0, text.size() - 1)) { //Good, an item has been added } else { //Start filtering based on the whole data, there was a big change to the filter filterBase = m_items; } // filterBase is correctly sorted, to keep it that way we add // exact matches to this list in sorted way and then prepend the whole list in one go. QList exactMatches; // similar for starting matches QList startMatches; // all other matches QList otherMatches; foreach( const Item& data, filterBase ) { const Path toFilter = static_cast(this)->itemPath(data); const QVector& segments = toFilter.segments(); if (text.count() > segments.count()) { // number of segments mismatches, thus item cannot match continue; } { bool allMatched = true; // try to put exact matches up front for(int i = segments.count() - 1, j = text.count() - 1; i >= 0 && j >= 0; --i, --j) { if (segments.at(i) != text.at(j)) { allMatched = false; break; } } if (allMatched) { exactMatches << data; continue; } } int searchIndex = 0; int pathIndex = 0; int lastMatchIndex = -1; // stop early if more search fragments remain than available after path index while (pathIndex < segments.size() && searchIndex < text.size() && (pathIndex + text.size() - searchIndex - 1) < segments.size() ) { const QString& segment = segments.at(pathIndex); const QString& typedSegment = text.at(searchIndex); lastMatchIndex = segment.indexOf(typedSegment, 0, Qt::CaseInsensitive); if (lastMatchIndex == -1 && !matchesAbbreviation(segment.midRef(0), typedSegment)) { // no match, try with next path segment ++pathIndex; continue; } // else we matched ++searchIndex; ++pathIndex; } if (searchIndex != text.size()) { if ( ! matchesPath(segments.last(), joinedText) ) { continue; } } // prefer matches whose last element starts with the filter if (pathIndex == segments.size() && lastMatchIndex == 0) { startMatches << data; } else { otherMatches << data; } } m_filtered = exactMatches + startMatches + otherMatches; m_oldFilterText = text; } private: QStringList m_oldFilterText; QList m_filtered; QList m_items; }; } #endif diff --git a/language/util/navigationtooltip.cpp b/language/util/navigationtooltip.cpp index d60c59cc2..fb40c65a0 100644 --- a/language/util/navigationtooltip.cpp +++ b/language/util/navigationtooltip.cpp @@ -1,59 +1,59 @@ /* 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 "navigationtooltip.h" #include "../duchain/navigation/abstractnavigationwidget.h" -#include +#include namespace KDevelop { NavigationToolTip::NavigationToolTip(QWidget* parent, const QPoint& point, QWidget* navigationWidget) : ActiveToolTip(parent, point), m_navigationWidget(nullptr) { Q_ASSERT(parent); setBackgroundRole(QPalette::Window); setNavigationWidget(navigationWidget); } void NavigationToolTip::sizeHintChanged() { QSize size = m_navigationWidget->size(); QSize hint = m_navigationWidget->sizeHint(); if(hint.width() > size.width()) size.setWidth( hint.width() ); if(hint.height() > size.height()) size.setHeight(hint.height()); if(size != m_navigationWidget->size()) resize(size + QSize(15, 15)); } void NavigationToolTip::setNavigationWidget(QWidget* widget) { if (auto oldWidget = qobject_cast(m_navigationWidget)) { disconnect(oldWidget, &AbstractNavigationWidget::sizeHintChanged, this, &NavigationToolTip::sizeHintChanged); } m_navigationWidget = widget; if (auto newWidget = qobject_cast(widget)) { connect(newWidget, &AbstractNavigationWidget::sizeHintChanged, this, &NavigationToolTip::sizeHintChanged); } QVBoxLayout* layout = new QVBoxLayout; setLayout(layout); layout->setMargin(0); if (m_navigationWidget) { layout->addWidget(m_navigationWidget); } } } diff --git a/outputview/outputexecutejob.h b/outputview/outputexecutejob.h index 82bdfc705..1d0f6b7cd 100644 --- a/outputview/outputexecutejob.h +++ b/outputview/outputexecutejob.h @@ -1,253 +1,251 @@ /* This file is part of KDevelop C opyright 2012 Ivan Shapoval*ov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_OUTPUTEXECUTEJOB_H #define KDEVPLATFORM_OUTPUTEXECUTEJOB_H #include "outputjob.h" #include "outputmodel.h" #include #include -class KProcess; - namespace KDevelop { class ProcessLineMaker; class OutputExecuteJobPrivate; class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputExecuteJob : public OutputJob { Q_OBJECT public: enum JobStatus { JobRunning = 0, /**< The job is running */ JobSucceeded = 1, /**< The job has succeeded */ JobCanceled = 2, /**< The job has been cancelled */ JobFailed = 3, /**< The job has failed */ JobNotStarted = 4 /**< The job hasn't been started so far */ }; enum { InvalidWorkingDirectoryError = OutputJob::UserDefinedError, UserDefinedError }; enum JobProperty { AppendProcessString = 0x001, /**< Whether to append a process string to the user-specified job name */ NeedWorkingDirectory = 0x002, /**< Whether to require a non-empty working directory to be provided */ CheckWorkingDirectory = 0x004, /**< Whether to check that the working directory actually exists (and not to create it if needed) */ PortableMessages = 0x008, /**< Whether to set LC_MESSAGES=C in the process' environment */ DisplayStdout = 0x010, /**< Whether to pass process' stdout to the output model */ DisplayStderr = 0x020, /**< Whether to pass process' stderr to the output model */ NoSilentOutput = 0x040, /**< Whether to call \ref startOutput() only if verbosity is \ref OutputJob::Verbose */ PostProcessOutput = 0x080, /**< Whether to connect line maker's signals to \ref postProcessStdout() and \ref postProcessStderr() */ IsBuilderHint = 0x100, /**< Whether to use builder-specific messages to talk to user (e. g. "build directory" instead of "working directory" */ }; Q_FLAGS(JobProperty JobProperties) Q_DECLARE_FLAGS(JobProperties, JobProperty) explicit OutputExecuteJob( QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose ); ~OutputExecuteJob() override; /** * Get the job's status (associated with the process). * * @returns The job's status. * @see JobStatus */ JobStatus status() const; /** * Get the job's output model. * * @returns The job's output model, downcasted to \ref OutputModel */ OutputModel* model() const; /** * Returns a working directory for the job's process. * * @returns URL which has been set through \ref setWorkingDirectory(); empty URL if unset. */ virtual QUrl workingDirectory() const; /** * Set a working directory for the job's process. * Effective if \ref workingDirectory() hasn't been overridden. * * @param directory a valid local directory URL, or an empty URL to unset. */ void setWorkingDirectory( const QUrl& directory ); /** * Get process' command line. * * @returns The command line for the process, with first element in list being the program path. */ virtual QStringList commandLine() const; /** * Append an element to the command line argument list for this process. * If no executable is set yet, it will be set instead. * Effective if \ref commandLine() hasn't been overridden. * * @param argument the argument to add */ OutputExecuteJob& operator<<( const QString& argument ); /** * Append a list of elements to the command line argument list for this process. * If no executable is set yet, it will be set from the first argument in given list. * Effective if \ref commandLine() hasn't been overridden. * * @param arguments the arguments to add */ OutputExecuteJob& operator<<( const QStringList& arguments ); /** * Get the privilege escalation command ("su", "sudo", etc.) used for the job's process. * * @returns The privilege escalation command name and arguments; empty list if not set. */ virtual QStringList privilegedExecutionCommand() const; /** * Set the privilege escalation command ("su", "sudo", etc.) which will be used for the job's process. * Effective if \ref privilegedExecutionCommand() hasn't been overridden. * * @param command The privilege escalation command's name and arguments; empty list to unset. * @see privilegedCommand */ void setPrivilegedExecutionCommand( const QStringList& command ); /** * A convenience function to set the job name. * * Calls \ref setTitle() and \ref setObjectName(). * * @note If you need the command-line to be appended to the job name, * make sure that it is already configured upon calling this function. * * @param name The name to set; empty string to use default (process string). */ void setJobName( const QString& name ); /** * Set one of the standard filtering strategies for the output model. */ void setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ); /** * Set the filtering strategy for the output model. */ void setFilteringStrategy(IFilterStrategy* filterStrategy); /** * Get the current properties of the job. * * @note Default-set properties are: \ref DisplayStdout. */ virtual JobProperties properties() const; /** * Set properties of the job. * Effective if \ref properties() hasn't been overridden. * * @param properties Which flags to add to the job. * @param override Whether to assign instead of doing bitwise OR. * @see JobProperties, properties(), unsetProperties() */ void setProperties( JobProperties properties, bool override = false ); /** * Unset properties of the job. * * @param properties Which flags to remove from the job * @see JobProperties, properties(), setProperties() */ void unsetProperties( JobProperties properties ); /** * Add a variable to the job's process environment. * * The variables added with this method override ones from the system environment and * the global environment profile, but are overridden by "PortableMessages" property. * * @param name The name of a variable to add * @param value The value of a variable to add; empty string to unset. */ void addEnvironmentOverride( const QString& name, const QString& value ); /** * Remove a variable from the override set. * * @param name The name of a variable to remove. * @note This does not force a variable to empty value; this is to undo the overriding itself. */ void removeEnvironmentOverride( const QString& name ); /** * Get the global environment profile name for the job's process. * * @returns The environment profile name to use in the job's process; empty if unset. */ virtual QString environmentProfile() const; /** * Set the environment profile name for the job's process. * Effective if \ref environmentProfile() hasn't been overridden. * * @param profile The name of profile to set. */ void setEnvironmentProfile( const QString& profile ); void start() override; protected: bool doKill() override; protected slots: // Redefine these functions if you want to post-process the output somehow // before it hits the output model. // Default implementations for either function call "model()->appendLines( lines );". // Do the same if you need the output to be visible. virtual void postProcessStdout( const QStringList& lines ); virtual void postProcessStderr( const QStringList& lines ); // Redefine these functions if you want to handle process' exit codes in a special manner. // One possible usage is in "cvs diff" job which returns 1 on success. virtual void childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ); virtual void childProcessError( QProcess::ProcessError processError ); private: friend class OutputExecuteJobPrivate; OutputExecuteJobPrivate* d; }; } // namespace KDevelop Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::OutputExecuteJob::JobProperties); #endif // KDEVPLATFORM_OUTPUTEXECUTEJOB_H diff --git a/outputview/outputfilteringstrategies.h b/outputview/outputfilteringstrategies.h index fb5e545e1..3854d474b 100644 --- a/outputview/outputfilteringstrategies.h +++ b/outputview/outputfilteringstrategies.h @@ -1,125 +1,123 @@ /* This file is part of KDevelop 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 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 . */ #ifndef KDEVPLATFORM_OUTPUTFILTERINGSTRATEGIES_H #define KDEVPLATFORM_OUTPUTFILTERINGSTRATEGIES_H /** * @file This file contains Concrete strategies for filtering output * in KDevelop output model. I.e. classes that inherit from ifilterstrategy. * New filtering strategies should be added here */ #include "ifilterstrategy.h" #include -#include #include -#include #include namespace KDevelop { struct CompilerFilterStrategyPrivate; /** * This filter strategy is for not applying any filtering at all. Implementation of the * interface methods are basically noops **/ class KDEVPLATFORMOUTPUTVIEW_EXPORT NoFilterStrategy : public IFilterStrategy { public: NoFilterStrategy(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; }; /** * This filter stategy checks if a given line contains output * that is defined as an error (or an action) from a compiler. **/ class KDEVPLATFORMOUTPUTVIEW_EXPORT CompilerFilterStrategy : public IFilterStrategy { public: explicit CompilerFilterStrategy(const QUrl& buildDir); virtual ~CompilerFilterStrategy(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; QVector getCurrentDirs(); private: CompilerFilterStrategyPrivate* const d; }; /** * This filter stategy filters out errors (no actions) from Python and PHP scripts. **/ class KDEVPLATFORMOUTPUTVIEW_EXPORT ScriptErrorFilterStrategy : public IFilterStrategy { public: ScriptErrorFilterStrategy(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; }; /** * This filter strategy filters out errors (no actions) from runtime debug output of native applications * * This is especially useful for runtime output of Qt applications, for example lines such as: * "ASSERT: "errors().isEmpty()" in file /tmp/foo/bar.cpp", line 49" */ class KDEVPLATFORMOUTPUTVIEW_EXPORT NativeAppErrorFilterStrategy : public IFilterStrategy { public: NativeAppErrorFilterStrategy(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; }; /** * This filter stategy filters out errors (no actions) from Static code analysis tools (Cppcheck,) **/ class KDEVPLATFORMOUTPUTVIEW_EXPORT StaticAnalysisFilterStrategy : public IFilterStrategy { public: StaticAnalysisFilterStrategy(); FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; }; } // namespace KDevelop #endif // KDEVPLATFORM_OUTPUTFILTERINGSTRATEGIES_H diff --git a/outputview/outputjob.h b/outputview/outputjob.h index a1c86cbbf..7770044c4 100644 --- a/outputview/outputjob.h +++ b/outputview/outputjob.h @@ -1,109 +1,106 @@ /* 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. */ #ifndef KDEVPLATFORM_OUTPUTJOB_H #define KDEVPLATFORM_OUTPUTJOB_H #include #include #include #include #include -class QStandardItemModel; -class QItemDelegate; - namespace KDevelop { class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputJob : public KJob { Q_OBJECT public: enum { FailedShownError = UserDefinedError + 100 //job failed and failure is shown in OutputView }; enum OutputJobVerbosity { Silent, Verbose }; explicit OutputJob(QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose); void startOutput(); OutputJobVerbosity verbosity() const; void setVerbosity(OutputJobVerbosity verbosity); QAbstractItemModel* model() const; /// Set the \a title for this job's output tab. If not set, will default to the job's objectName(). void setTitle(const QString& title); protected: void setStandardToolView(IOutputView::StandardToolView standard); void setToolTitle(const QString& title); void setToolIcon(const QIcon& icon); void setViewType(IOutputView::ViewType type); void setBehaviours(IOutputView::Behaviours behaviours); void setKillJobOnOutputClose(bool killJobOnOutputClose); /** * Sets the model for the view that shows this jobs output. * * The view takes ownership of the model, but it is safe to * use the model while the job is running. * * NOTE: Do not reuse the same model for different jobs. */ void setModel(QAbstractItemModel* model); /** * Sets the delegate for the view that shows this jobs output. * * The view takes ownership of the delegate, but it is safe to * use the delegate while the job is running. * * NOTE: Do not reuse the same delegate for different jobs. */ void setDelegate(QAbstractItemDelegate* delegate); int outputId() const; private Q_SLOTS: void outputViewRemoved(int , int id); private: int m_standardToolView; QString m_title, m_toolTitle; QIcon m_toolIcon; IOutputView::ViewType m_type; IOutputView::Behaviours m_behaviours; bool m_killJobOnOutputClose; OutputJobVerbosity m_verbosity; int m_outputId; QPointer m_outputModel; QAbstractItemDelegate* m_outputDelegate; }; } #endif diff --git a/outputview/outputmodel.cpp b/outputview/outputmodel.cpp index 0dc48e628..1da3b90ce 100644 --- a/outputview/outputmodel.cpp +++ b/outputview/outputmodel.cpp @@ -1,476 +1,475 @@ /*************************************************************************** * 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(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 { explicit 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 = 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& line ) { appendLines( QStringList() << line ); } void OutputModel::ensureAllDone() { QMetaObject::invokeMethod(d->worker, "flushBuffers"); } void OutputModel::clear() { ensureAllDone(); beginResetModel(); d->m_filteredItems.clear(); endResetModel(); } } #include "outputmodel.moc" #include "moc_outputmodel.cpp" diff --git a/outputview/tests/test_filteringstrategy.h b/outputview/tests/test_filteringstrategy.h index 5e47424f6..e928bd23e 100644 --- a/outputview/tests/test_filteringstrategy.h +++ b/outputview/tests/test_filteringstrategy.h @@ -1,52 +1,52 @@ /* This file is part of KDevelop 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 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 . */ #ifndef KDEVPLATFORM_TEST_FILTERINGSTRATEGY_H #define KDEVPLATFORM_TEST_FILTERINGSTRATEGY_H -#include +#include namespace KDevelop { class TestFilteringStrategy : public QObject { Q_OBJECT private slots: void testNoFilterStrategy_data(); void testNoFilterStrategy(); void testCompilerFilterStrategy_data(); void testCompilerFilterStrategy(); void testCompilerFilterstrategyMultipleKeywords_data(); void testCompilerFilterstrategyMultipleKeywords(); void testCompilerFilterstrategyUrlFromAction_data(); void testCompilerFilterstrategyUrlFromAction(); void testScriptErrorFilterStrategy_data(); void testScriptErrorFilterStrategy(); void testNativeAppErrorFilterStrategy_data(); void testNativeAppErrorFilterStrategy(); void testStaticAnalysisFilterStrategy_data(); void testStaticAnalysisFilterStrategy(); void testExtractionOfLineAndColumn_data(); void testExtractionOfLineAndColumn(); void benchMarkCompilerFilterAction(); }; } #endif // KDEVPLATFORM_TEST_FILTERINGSTRATEGY_H diff --git a/outputview/tests/testlinebuilderfunctions.h b/outputview/tests/testlinebuilderfunctions.h index a51319f86..d49979495 100644 --- a/outputview/tests/testlinebuilderfunctions.h +++ b/outputview/tests/testlinebuilderfunctions.h @@ -1,159 +1,158 @@ /* This file is part of KDevelop 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 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 . */ #ifndef KDEVPLATFORM_TESTLINEBUILDERFUNCTIONS_H #define KDEVPLATFORM_TESTLINEBUILDERFUNCTIONS_H -#include #include #include #define PROJECTS_SOURCE_DIR namespace KDevelop { static QString projectPath() { /// Use existing directory with one file return QFileInfo(QStringLiteral(__FILE__)).absolutePath() + QLatin1String("/onefileproject"); } QString buildCppCheckErrorLine() { /// Test CPP check output QString outputline(QStringLiteral("[")); outputline.append(projectPath()); outputline.append("main.cpp:26]: (error) Memory leak: str"); return outputline; } QString buildKrazyErrorLine() { /// Test krazy2 output QString outputline(QStringLiteral("\t")); outputline.append(projectPath()); outputline.append("main.cpp: line#22 (1)"); return outputline; } QString buildKrazyErrorLine2() { /// Test krazy2 output QString outputline(QStringLiteral("\t")); outputline.append(projectPath()); outputline.append("main.cpp: missing tags: email address line#2 (1)"); return outputline; } QString buildKrazyErrorLine3() { /// Test krazy2 output QString outputline(QStringLiteral("\t")); outputline.append(projectPath()); outputline.append("main.cpp: non-const ref iterator line#451 (1)"); return outputline; } QString buildKrazyErrorLineNoLineInfo() { /// Test krazy2 output QString outputline(QStringLiteral("\t")); outputline.append(projectPath()); outputline.append("main.cpp: missing license"); return outputline; } QString buildCompilerLine() { /// Test with compiler output QString outputline; outputline.append(projectPath()); outputline.append(">make"); return outputline; } QString buildCompilerErrorLine() { QString outputline; outputline.append(projectPath()); outputline.append("main.cpp:5:5: error: ‘RingBuffer’ was not declared in this scope"); return outputline; } QString buildCompilerInformationLine() { QString outputline; outputline.append(projectPath()); outputline.append("main.cpp:6:14: instantiated from here"); return outputline; } QString buildInfileIncludedFromFirstLine() { QString outputline(QStringLiteral("In file included from ")); outputline.append(projectPath()); outputline.append("PriorityFactory.h:52:0,"); return outputline; } QString buildInfileIncludedFromSecondLine() { QString outputline(QStringLiteral(" from ")); outputline.append(projectPath()); outputline.append("PatchBasedInpainting.hxx:29,"); return outputline; } QString buildCompilerActionLine() { return QStringLiteral("linking testCustombuild (g++)"); } QString buildCmakeConfigureMultiLine() { QString outputline; outputline.append(projectPath()); outputline.append("CMakeLists.txt:10:"); return outputline; } QString buildLinkerErrorLine() { return QStringLiteral("/path/to/file/Buffer.cpp:66: undefined reference to `Buffer::does_not_exist()'"); } QString buildPythonErrorLine() { QString outputline(QStringLiteral("File \"")); outputline.append(projectPath()); outputline.append("pythonExample.py\", line 10"); return outputline; } QString buildCppCheckInformationLine() { return QStringLiteral("(information) Cppcheck cannot find all the include files. Cpppcheck can check the code without the include\ files found. But the results will probably be more accurate if all the include files are found. Please check your project's \ include directories and add all of them as include directories for Cppcheck. To see what files Cppcheck cannot find use --check-config."); } } #endif diff --git a/plugins/appwizard/appwizarddialog.cpp b/plugins/appwizard/appwizarddialog.cpp index 3f036c0c7..7b84646e2 100644 --- a/plugins/appwizard/appwizarddialog.cpp +++ b/plugins/appwizard/appwizarddialog.cpp @@ -1,108 +1,107 @@ /*************************************************************************** * Copyright 2007 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. * * * ***************************************************************************/ #include "appwizarddialog.h" #include -#include #include #include #include #include #include "projecttemplatesmodel.h" #include "projectselectionpage.h" #include "projectvcspage.h" AppWizardDialog::AppWizardDialog(KDevelop::IPluginController* pluginController, ProjectTemplatesModel* templatesModel, QWidget *parent, Qt::WindowFlags flags) :KAssistantDialog(parent, flags) { setWindowTitle(i18n("Create New Project")); // KAssistantDialog creates a help button by default, no option to prevent that QPushButton *helpButton = button(QDialogButtonBox::Help); if (helpButton) { buttonBox()->removeButton(helpButton); delete helpButton; } m_selectionPage = new ProjectSelectionPage(templatesModel, this); m_vcsPage = new ProjectVcsPage( pluginController, this ); m_vcsPage->setSourceLocation( m_selectionPage->location() ); connect( m_selectionPage, &ProjectSelectionPage::locationChanged, m_vcsPage, &ProjectVcsPage::setSourceLocation ); m_pageItems[m_selectionPage] = addPage(m_selectionPage, i18nc("Page for general configuration options", "General")); m_pageItems[m_vcsPage] = addPage(m_vcsPage, i18nc("Page for version control options", "Version Control") ); setValid( m_pageItems[m_selectionPage], false ); m_invalidMapper = new QSignalMapper(this); m_invalidMapper->setMapping(m_selectionPage, m_selectionPage); m_invalidMapper->setMapping(m_vcsPage, m_vcsPage); m_validMapper = new QSignalMapper(this); m_validMapper->setMapping(m_selectionPage, m_selectionPage); m_validMapper->setMapping(m_vcsPage, m_vcsPage); connect( m_selectionPage, &ProjectSelectionPage::invalid, m_invalidMapper, static_cast(&QSignalMapper::map) ); connect( m_selectionPage, &ProjectSelectionPage::valid, m_validMapper, static_cast(&QSignalMapper::map) ); connect( m_vcsPage, &ProjectVcsPage::invalid, m_invalidMapper, static_cast(&QSignalMapper::map) ); connect( m_vcsPage, &ProjectVcsPage::valid, m_validMapper, static_cast(&QSignalMapper::map) ); connect( m_validMapper, static_cast(&QSignalMapper::mapped), this, &AppWizardDialog::pageValid ); connect( m_invalidMapper, static_cast(&QSignalMapper::mapped), this, &AppWizardDialog::pageInValid ); } ApplicationInfo AppWizardDialog::appInfo() const { ApplicationInfo a; a.name = m_selectionPage->projectName(); a.location = m_selectionPage->location(); a.appTemplate = m_selectionPage->selectedTemplate(); a.vcsPluginName = m_vcsPage->pluginName(); if( !m_vcsPage->pluginName().isEmpty() ) { a.repository = m_vcsPage->destination(); a.sourceLocation = m_vcsPage->source(); a.importCommitMessage = m_vcsPage->commitMessage(); } else { a.repository = KDevelop::VcsLocation(); a.sourceLocation.clear(); a.importCommitMessage.clear(); } return a; } void AppWizardDialog::pageValid( QWidget* w ) { if( m_pageItems.contains(w) ) setValid( m_pageItems[w], true ); } void AppWizardDialog::pageInValid( QWidget* w ) { if( m_pageItems.contains(w) ) setValid( m_pageItems[w], false ); } void AppWizardDialog::next() { AppWizardPageWidget* w = qobject_cast(currentPage()->widget()); if (!w || w->shouldContinue()) { KAssistantDialog::next(); } } diff --git a/plugins/appwizard/projectselectionpage.cpp b/plugins/appwizard/projectselectionpage.cpp index c439e58f8..f802412f2 100644 --- a/plugins/appwizard/projectselectionpage.cpp +++ b/plugins/appwizard/projectselectionpage.cpp @@ -1,361 +1,361 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2011 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 "projectselectionpage.h" #include "debug.h" #include #include -#include +#include #include #include #include #include #include #include #include #include #include "ui_projectselectionpage.h" #include "projecttemplatesmodel.h" using namespace KDevelop; ProjectSelectionPage::ProjectSelectionPage(ProjectTemplatesModel *templatesModel, AppWizardDialog *wizardDialog) : AppWizardPageWidget(wizardDialog), m_templatesModel(templatesModel) { ui = new Ui::ProjectSelectionPage(); ui->setupUi(this); setContentsMargins(0,0,0,0); ui->descriptionContent->setBackgroundRole(QPalette::Base); ui->descriptionContent->setForegroundRole(QPalette::Text); ui->locationUrl->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly ); ui->locationUrl->setUrl(KDevelop::ICore::self()->projectController()->projectsBaseDirectory()); ui->locationValidWidget->hide(); ui->locationValidWidget->setMessageType(KMessageWidget::Error); ui->locationValidWidget->setCloseButtonVisible(false); connect( ui->locationUrl->lineEdit(), &KLineEdit::textEdited, this, &ProjectSelectionPage::urlEdited); connect( ui->locationUrl, &KUrlRequester::urlSelected, this, &ProjectSelectionPage::urlEdited); connect( ui->projectNameEdit, &QLineEdit::textEdited, this, &ProjectSelectionPage::nameChanged ); m_listView = new KDevelop::MultiLevelListView(this); m_listView->setLevels(2); m_listView->setHeaderLabels(QStringList() << i18n("Category") << i18n("Project Type")); m_listView->setModel(templatesModel); m_listView->setLastModelsFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); m_listView->setContentsMargins(0, 0, 0, 0); connect (m_listView, &MultiLevelListView::currentIndexChanged, this, &ProjectSelectionPage::typeChanged); ui->gridLayout->addWidget(m_listView, 0, 0, 1, 1); typeChanged(m_listView->currentIndex()); connect( ui->templateType, static_cast(&QComboBox::currentIndexChanged), this, &ProjectSelectionPage::templateChanged ); QPushButton* getMoreButton = new QPushButton(i18n("Get More Templates"), m_listView); getMoreButton->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff"))); connect (getMoreButton, &QPushButton::clicked, this, &ProjectSelectionPage::moreTemplatesClicked); m_listView->addWidget(0, getMoreButton); QPushButton* loadButton = new QPushButton(m_listView); loadButton->setText(i18n("Load Template From File")); loadButton->setIcon(QIcon::fromTheme(QStringLiteral("application-x-archive"))); connect (loadButton, &QPushButton::clicked, this, &ProjectSelectionPage::loadFileClicked); m_listView->addWidget(0, loadButton); m_wizardDialog = wizardDialog; } void ProjectSelectionPage::nameChanged() { validateData(); emit locationChanged( location() ); } ProjectSelectionPage::~ProjectSelectionPage() { delete ui; } void ProjectSelectionPage::typeChanged(const QModelIndex& idx) { if (!idx.model()) { qCDebug(PLUGIN_APPWIZARD) << "Index with no model"; return; } int children = idx.model()->rowCount(idx); ui->templateType->setVisible(children); ui->templateType->setEnabled(children > 1); if (children) { ui->templateType->setModel(m_templatesModel); ui->templateType->setRootModelIndex(idx); ui->templateType->setCurrentIndex(0); itemChanged(idx.model()->index(0, 0, idx)); } else { itemChanged(idx); } } void ProjectSelectionPage::templateChanged(int current) { QModelIndex idx=m_templatesModel->index(current, 0, ui->templateType->rootModelIndex()); itemChanged(idx); } void ProjectSelectionPage::itemChanged( const QModelIndex& current) { QString picPath = current.data( KDevelop::TemplatesModel::IconNameRole ).toString(); if( picPath.isEmpty() ) { QIcon icon(QStringLiteral("kdevelop")); ui->icon->setPixmap(icon.pixmap(128, 128)); ui->icon->setFixedHeight(128); } else { QPixmap pixmap( picPath ); ui->icon->setPixmap( pixmap ); ui->icon->setFixedHeight( pixmap.height() ); } // header name is either from this index directly or the parents if we show the combo box const QVariant headerData = ui->templateType->isVisible() ? current.parent().data() : current.data(); ui->header->setText(QStringLiteral("

%1

").arg(headerData.toString().trimmed())); ui->description->setText(current.data(KDevelop::TemplatesModel::CommentRole).toString()); validateData(); ui->propertiesBox->setEnabled(true); } QString ProjectSelectionPage::selectedTemplate() { QStandardItem *item = getCurrentItem(); if (item) return item->data().toString(); else return QString(); } QUrl ProjectSelectionPage::location() { QUrl url = ui->locationUrl->url().adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + encodedProjectName()); return url; } QString ProjectSelectionPage::projectName() { return ui->projectNameEdit->text(); } void ProjectSelectionPage::urlEdited() { validateData(); emit locationChanged( location() ); } void ProjectSelectionPage::validateData() { QUrl url = ui->locationUrl->url(); if( !url.isLocalFile() || url.isEmpty() ) { ui->locationValidWidget->setText( i18n("Invalid location") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } if (projectName().isEmpty()) { ui->locationValidWidget->setText( i18n("Empty project name") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } if (!projectName().isEmpty()) { QString projectName = this->projectName(); QString templatefile = m_wizardDialog->appInfo().appTemplate; // Read template file KConfig config(templatefile); KConfigGroup configgroup(&config, "General"); QString pattern = configgroup.readEntry( "ValidProjectName" , "^[a-zA-Z][a-zA-Z0-9_]+$" ); // Validation int pos = 0; QRegExp regex( pattern ); QRegExpValidator validator( regex ); if( validator.validate(projectName, pos) == QValidator::Invalid ) { ui->locationValidWidget->setText( i18n("Invalid project name") ); emit invalid(); return; } } QDir tDir(url.toLocalFile()); while (!tDir.exists() && !tDir.isRoot()) { if (!tDir.cdUp()) { break; } } if (tDir.exists()) { QFileInfo tFileInfo(tDir.absolutePath()); if (!tFileInfo.isWritable() || !tFileInfo.isExecutable()) { ui->locationValidWidget->setText( i18n("Unable to create subdirectories, " "missing permissions on: %1", tDir.absolutePath()) ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } } QStandardItem* item = getCurrentItem(); if( item && !item->hasChildren() ) { ui->locationValidWidget->animatedHide(); emit valid(); } else { ui->locationValidWidget->setText( i18n("Invalid project template, please choose a leaf item") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } // Check for non-empty target directory. Not an error, but need to display a warning. url.setPath( url.path() + '/' + encodedProjectName() ); QFileInfo fi( url.toLocalFile() ); if( fi.exists() && fi.isDir() ) { if( !QDir( fi.absoluteFilePath()).entryList( QDir::NoDotAndDotDot | QDir::AllEntries ).isEmpty() ) { ui->locationValidWidget->setText( i18n("Path already exists and contains files. Open it as a project.") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } } } QByteArray ProjectSelectionPage::encodedProjectName() { // : < > * ? / \ | " are invalid on windows QByteArray tEncodedName = projectName().toUtf8(); for (int i = 0; i < tEncodedName.size(); ++i) { QChar tChar(tEncodedName.at( i )); if (tChar.isDigit() || tChar.isSpace() || tChar.isLetter() || tChar == '%') continue; QByteArray tReplace = QUrl::toPercentEncoding( tChar ); tEncodedName.replace( tEncodedName.at( i ) ,tReplace ); i = i + tReplace.size() - 1; } return tEncodedName; } QStandardItem* ProjectSelectionPage::getCurrentItem() const { QStandardItem* item = m_templatesModel->itemFromIndex( m_listView->currentIndex() ); if ( item && item->hasChildren() ) { const int currect = ui->templateType->currentIndex(); const QModelIndex idx = m_templatesModel->index( currect, 0, ui->templateType->rootModelIndex() ); item = m_templatesModel->itemFromIndex(idx); } return item; } bool ProjectSelectionPage::shouldContinue() { QFileInfo fi(location().toLocalFile()); if (fi.exists() && fi.isDir()) { if (!QDir(fi.absoluteFilePath()).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()) { int res = KMessageBox::questionYesNo(this, i18n("The specified path already exists and contains files. " "Are you sure you want to proceed?")); return res == KMessageBox::Yes; } } return true; } void ProjectSelectionPage::loadFileClicked() { QString filter = QStringLiteral("application/x-desktop application/x-bzip-compressed-tar application/zip"); const QString fileName = QFileDialog::getOpenFileName(this, i18n("Load Template From File"), QString(), filter); if (!fileName.isEmpty()) { QString destination = m_templatesModel->loadTemplateFile(fileName); QModelIndexList indexes = m_templatesModel->templateIndexes(destination); if (indexes.size() > 2) { m_listView->setCurrentIndex(indexes.at(1)); ui->templateType->setCurrentIndex(indexes.at(2).row()); } } } void ProjectSelectionPage::moreTemplatesClicked() { KNS3::DownloadDialog dialog(QStringLiteral("kdevappwizard.knsrc"), this); dialog.exec(); auto entries = dialog.changedEntries(); if (entries.isEmpty()) { return; } m_templatesModel->refresh(); bool updated = false; foreach (const KNS3::Entry& entry, entries) { if (!entry.installedFiles().isEmpty()) { updated = true; setCurrentTemplate(entry.installedFiles().at(0)); break; } } if (!updated) { m_listView->setCurrentIndex(QModelIndex()); } } void ProjectSelectionPage::setCurrentTemplate (const QString& fileName) { QModelIndexList indexes = m_templatesModel->templateIndexes(fileName); if (indexes.size() > 1) { m_listView->setCurrentIndex(indexes.at(1)); } if (indexes.size() > 2) { ui->templateType->setCurrentIndex(indexes.at(2).row()); } } diff --git a/plugins/appwizard/projecttemplatesmodel.h b/plugins/appwizard/projecttemplatesmodel.h index 5cb95c612..418078bae 100644 --- a/plugins/appwizard/projecttemplatesmodel.h +++ b/plugins/appwizard/projecttemplatesmodel.h @@ -1,27 +1,24 @@ /*************************************************************************** * Copyright 2007 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. * * * ***************************************************************************/ #ifndef _PROJECTTEMPLATESMODEL_H_ #define _PROJECTTEMPLATESMODEL_H_ #include "language/codegen/templatesmodel.h" -#include -#include - class AppWizardPlugin; class ProjectTemplatesModel: public KDevelop::TemplatesModel { Q_OBJECT public: explicit ProjectTemplatesModel(AppWizardPlugin *parent); }; #endif diff --git a/plugins/appwizard/projectvcspage.h b/plugins/appwizard/projectvcspage.h index c95b86167..0c2633f25 100644 --- a/plugins/appwizard/projectvcspage.h +++ b/plugins/appwizard/projectvcspage.h @@ -1,69 +1,67 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_PROJECTVCSPAGE_H #define KDEVPLATFORM_PLUGIN_PROJECTVCSPAGE_H -#include - #include "appwizardpagewidget.h" #include namespace Ui { class ProjectVcsPage; } namespace KDevelop { class IPluginController; class VcsImportMetadataWidget; } class QUrl; class ProjectVcsPage : public AppWizardPageWidget { Q_OBJECT public: explicit ProjectVcsPage( KDevelop::IPluginController*, QWidget* parent = nullptr ); ~ProjectVcsPage() override; bool shouldContinue() override; signals: void valid(); void invalid(); public slots: void setSourceLocation( const QUrl& ); void vcsTypeChanged(int); void validateData(); public: QString pluginName() const; QUrl source() const; KDevelop::VcsLocation destination() const; QString commitMessage() const; private: QList importWidgets; QList > vcsPlugins; Ui::ProjectVcsPage* m_ui; }; #endif //kate: space-indent on; indent-width 4; replace-tabs on; auto-insert-doxygen on; indent-mode cstyle; diff --git a/plugins/bazaar/bazaarplugin.cpp b/plugins/bazaar/bazaarplugin.cpp index e4463a5ff..02a7dfb20 100644 --- a/plugins/bazaar/bazaarplugin.cpp +++ b/plugins/bazaar/bazaarplugin.cpp @@ -1,346 +1,343 @@ /*************************************************************************** * Copyright 2013-2014 Maciej Poleski * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "bazaarplugin.h" #include -#include #include #include -#include #include -#include #include #include #include #include #include #include #include "bazaarutils.h" #include "bzrannotatejob.h" #include "copyjob.h" #include "diffjob.h" using namespace KDevelop; BazaarPlugin::BazaarPlugin(QObject* parent, const QVariantList& args) : IPlugin(QStringLiteral("kdevbazaar"), parent), m_vcsPluginHelper(new KDevelop::VcsPluginHelper(this, this)) { Q_UNUSED(args); // What is this? if (QStandardPaths::findExecutable(QStringLiteral("bzr")).isEmpty()) { setErrorDescription(i18n("Unable to find Bazaar (bzr) executable Is it installed on the system?")); return; } setObjectName(QStringLiteral("Bazaar")); } BazaarPlugin::~BazaarPlugin() { } QString BazaarPlugin::name() const { return QStringLiteral("Bazaar"); } bool BazaarPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { const QString scheme = remoteLocation.scheme(); if (scheme == QLatin1String("bzr") || scheme == QLatin1String("bzr+ssh")) { return true; } return false; } VcsJob* BazaarPlugin::add(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); job->setType(VcsJob::Add); *job << "bzr" << "add"; if(recursion == NonRecursive) *job << "--no-recurse"; *job << localLocations; return job; } VcsJob* BazaarPlugin::annotate(const QUrl& localLocation, const VcsRevision& rev) { VcsJob* job = new BzrAnnotateJob(BazaarUtils::workingCopy(localLocation), BazaarUtils::getRevisionSpec(rev), localLocation, this, KDevelop::OutputJob::Silent); return job; } VcsJob* BazaarPlugin::commit(const QString& message, const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { QDir dir = BazaarUtils::workingCopy(localLocations[0]); DVcsJob* job = new DVcsJob(dir, this); job->setType(VcsJob::Commit); *job << "bzr" << "commit" << BazaarUtils::handleRecursion(localLocations, recursion) << "-m" << message; return job; } VcsJob* BazaarPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) { return new CopyJob(localLocationSrc, localLocationDstn, this); } VcsImportMetadataWidget* BazaarPlugin::createImportMetadataWidget(QWidget* parent) { return new DvcsImportMetadataWidget(parent); } VcsJob* BazaarPlugin::createWorkingCopy(const VcsLocation& sourceRepository, const QUrl& destinationDirectory, IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); // What is the purpose of recursion parameter? DVcsJob* job = new DVcsJob(BazaarUtils::toQDir(sourceRepository.localUrl()), this); job->setType(VcsJob::Import); *job << "bzr" << "branch" << sourceRepository.localUrl().url() << destinationDirectory; return job; } VcsJob* BazaarPlugin::diff(const QUrl& fileOrDirectory, const VcsRevision& srcRevision, const VcsRevision& dstRevision, VcsDiff::Type, IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); VcsJob* job = new DiffJob(BazaarUtils::workingCopy(fileOrDirectory), BazaarUtils::getRevisionSpecRange(srcRevision, dstRevision), fileOrDirectory, this); return job; } VcsJob* BazaarPlugin::init(const QUrl& localRepositoryRoot) { DVcsJob* job = new DVcsJob(BazaarUtils::toQDir(localRepositoryRoot), this); job->setType(VcsJob::Import); *job << "bzr" << "init"; return job; } bool BazaarPlugin::isVersionControlled(const QUrl& localLocation) { QDir workCopy = BazaarUtils::workingCopy(localLocation); DVcsJob* job = new DVcsJob(workCopy, this, OutputJob::Silent); job->setType(VcsJob::Unknown); job->setIgnoreError(true); *job << "bzr" << "ls" << "--from-root" << "-R" << "-V"; job->exec(); if (job->status() == VcsJob::JobSucceeded) { QList filesAndDirectoriesList; foreach (const QString& fod, job->output().split('\n')) { filesAndDirectoriesList.append(QFileInfo(workCopy.absolutePath() + QDir::separator() + fod)); } QFileInfo fi(localLocation.toLocalFile()); if (fi.isDir() || fi.isFile()) { QFileInfo file(localLocation.toLocalFile()); return filesAndDirectoriesList.contains(file); } } return false; } VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, long unsigned int limit) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this); job->setType(VcsJob::Log); *job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(rev) << "-l" << QString::number(limit); connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog); return job; } VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, const VcsRevision& limit) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this); job->setType(VcsJob::Log); *job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(limit, rev); connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog); return job; } void BazaarPlugin::parseBzrLog(DVcsJob* job) { QVariantList result; auto parts = job->output().split(QStringLiteral("------------------------------------------------------------"), QString::SkipEmptyParts); foreach (const QString& part, parts) { auto event = BazaarUtils::parseBzrLogPart(part); if (event.revision().revisionType() != VcsRevision::Invalid) result.append(QVariant::fromValue(event)); } job->setResults(result); } VcsJob* BazaarPlugin::move(const QUrl& localLocationSrc, const QUrl& localLocationDst) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocationSrc), this); job->setType(VcsJob::JobType::Move); *job << "bzr" << "move" << localLocationSrc << localLocationDst; return job; } VcsJob* BazaarPlugin::pull(const VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation) { // API describes hg pull which is git fetch equivalent // bzr has pull, but it succeeds only if fast-forward is possible // in other cases bzr merge should be used instead (bzr pull would fail) // Information about repository must be provided at least once. DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this); job->setType(VcsJob::JobType::Pull); *job << "bzr" << "pull"; if (!localOrRepoLocationSrc.localUrl().isEmpty()) { *job << localOrRepoLocationSrc.localUrl(); } // localUrl always makes sense. Even on remote repositores which are handled // transparently. return job; } VcsJob* BazaarPlugin::push(const QUrl& localRepositoryLocation, const VcsLocation& localOrRepoLocationDst) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this); job->setType(VcsJob::JobType::Push); *job << "bzr" << "push" << localOrRepoLocationDst.localUrl(); // localUrl always makes sense. Even on remote repositores which are handled // transparently. return job; } VcsJob* BazaarPlugin::remove(const QList& localLocations) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); job->setType(VcsJob::JobType::Remove); *job << "bzr" << "remove" << localLocations; return job; } VcsJob* BazaarPlugin::repositoryLocation(const QUrl& localLocation) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this); job->setType(VcsJob::JobType::Unknown); *job << "bzr" << "root" << localLocation; // It is only to make sure connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrRoot); return job; } void BazaarPlugin::parseBzrRoot(DVcsJob* job) { QString filename = job->dvcsCommand().at(2); QString rootDirectory = job->output(); QString localFilename = QFileInfo(QUrl::fromLocalFile(filename).toLocalFile()).absoluteFilePath(); QString result = localFilename.mid(localFilename.indexOf(rootDirectory) + rootDirectory.length()); job->setResults(QVariant::fromValue(result)); } VcsJob* BazaarPlugin::resolve(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { return add(localLocations, recursion); // How to provide "a conflict solving dialog to the user"? // In any case this plugin is unable to make any conflict. } VcsJob* BazaarPlugin::revert(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); job->setType(VcsJob::JobType::Revert); *job << "bzr" << "revert" << BazaarUtils::handleRecursion(localLocations, recursion); return job; } VcsJob* BazaarPlugin::status(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { Q_UNUSED(recursion); DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); job->setType(VcsJob::Status); *job << "bzr" << "status" << "--short" << "--no-pending" << "--no-classify" << localLocations; connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrStatus); return job; } void BazaarPlugin::parseBzrStatus(DVcsJob* job) { QVariantList result; QSet filesWithStatus; QDir workingCopy = job->directory(); foreach (const QString& line, job->output().split('\n')) { auto status = BazaarUtils::parseVcsStatusInfoLine(line); result.append(QVariant::fromValue(status)); filesWithStatus.insert(BazaarUtils::concatenatePath(workingCopy, status.url())); } QStringList command = job->dvcsCommand(); for (auto it = command.constBegin() + command.indexOf(QStringLiteral("--no-classify")) + 1, itEnd = command.constEnd(); it != itEnd; ++it) { QString path = QFileInfo(*it).absoluteFilePath(); if (!filesWithStatus.contains(path)) { filesWithStatus.insert(path); KDevelop::VcsStatusInfo status; status.setState(VcsStatusInfo::ItemUpToDate); status.setUrl(QUrl::fromLocalFile(*it)); result.append(QVariant::fromValue(status)); } } job->setResults(result); } VcsJob* BazaarPlugin::update(const QList& localLocations, const VcsRevision& rev, IBasicVersionControl::RecursionMode recursion) { // bzr update is stronger than API (it's effectively merge) // the best approximation is bzr pull DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this); Q_UNUSED(recursion); // recursion and file locations are ignored - we can update only whole // working copy job->setType(VcsJob::JobType::Update); *job << "bzr" << "pull" << BazaarUtils::getRevisionSpec(rev); return job; } VcsLocationWidget* BazaarPlugin::vcsLocation(QWidget* parent) const { return new KDevelop::StandardVcsLocationWidget(parent); } ContextMenuExtension BazaarPlugin::contextMenuExtension(Context* context) { m_vcsPluginHelper->setupFromContext(context); QList const& ctxUrlList = m_vcsPluginHelper->contextUrlList(); bool isWorkingDirectory = false; for (const QUrl & url : ctxUrlList) { if (BazaarUtils::isValidDirectory(url)) { isWorkingDirectory = true; break; } } if (!isWorkingDirectory) { // Not part of a repository return ContextMenuExtension(); } QMenu* menu = m_vcsPluginHelper->commonActions(); ContextMenuExtension menuExt; menuExt.addAction(ContextMenuExtension::VcsGroup, menu->menuAction()); return menuExt; } diff --git a/plugins/bazaar/bzrannotatejob.h b/plugins/bazaar/bzrannotatejob.h index 01ed446bc..0d5c26890 100644 --- a/plugins/bazaar/bzrannotatejob.h +++ b/plugins/bazaar/bzrannotatejob.h @@ -1,75 +1,74 @@ /*************************************************************************** * Copyright 2013-2014 Maciej Poleski * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef BAZAAR_BZRANNOTATEJOB_H #define BAZAAR_BZRANNOTATEJOB_H -#include #include #include #include #include #include #include class QDir; namespace KDevelop { class DVcsJob; } class BzrAnnotateJob : public KDevelop::VcsJob { Q_OBJECT public: explicit BzrAnnotateJob(const QDir& workingDir, const QString& revisionSpec, const QUrl& localLocation, KDevelop::IPlugin* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose); QVariant fetchResults() override; void start() override; JobStatus status() const override; KDevelop::IPlugin* vcsPlugin() const override; protected: bool doKill() override; private slots: void parseBzrAnnotateOutput(KDevelop::DVcsJob* job); void parseNextLine(); void prepareCommitInfo(std::size_t revision); void parseBzrLog(KDevelop::DVcsJob* job); private: QDir m_workingDir; QString m_revisionSpec; QUrl m_localLocation; KDevelop::IPlugin* m_vcsPlugin; JobStatus m_status; QPointer m_job; QStringList m_outputLines; int m_currentLine; QHash m_commits; QVariantList m_results; }; #endif // BAZAAR_BZRANNOTATEJOB_H diff --git a/plugins/bazaar/tests/test_bazaar.cpp b/plugins/bazaar/tests/test_bazaar.cpp index ae89b66b0..7ebbd6ceb 100644 --- a/plugins/bazaar/tests/test_bazaar.cpp +++ b/plugins/bazaar/tests/test_bazaar.cpp @@ -1,362 +1,362 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2007 Robert Gruber * * * * Adapted for Git * * Copyright 2008 Evgeniy Ivanov * * * * Adapted for Bazaar * * Copyright 2014 Maciej Poleski * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "test_bazaar.h" #include #include #include #include #include #include #include #include #include #include "../bazaarplugin.h" -#include +#include #define VERIFYJOB(j) \ do { QVERIFY(j); QVERIFY(j->exec()); QVERIFY((j)->status() == KDevelop::VcsJob::JobSucceeded); } while(0) const QString tempDir = QDir::tempPath(); const QString bazaarTest_BaseDir(tempDir + "/kdevBazaar_testdir/"); const QString bazaarTest_BaseDir2(tempDir + "/kdevBazaar_testdir2/"); const QString bazaarRepo(bazaarTest_BaseDir + ".bzr"); const QString bazaarSrcDir(bazaarTest_BaseDir + "src/"); const QString bazaarTest_FileName("testfile"); const QString bazaarTest_FileName2("foo"); const QString bazaarTest_FileName3("bar"); using namespace KDevelop; void TestBazaar::initTestCase() { AutoTestShell::init({QStringLiteral("kdevbazaar")}); TestCore::initialize(Core::NoUi); m_plugin = new BazaarPlugin(TestCore::self()); } void TestBazaar::cleanupTestCase() { delete m_plugin; TestCore::shutdown(); } void TestBazaar::init() { // Now create the basic directory structure QDir tmpdir(tempDir); tmpdir.mkdir(bazaarTest_BaseDir); tmpdir.mkdir(bazaarSrcDir); tmpdir.mkdir(bazaarTest_BaseDir2); } void TestBazaar::cleanup() { removeTempDirs(); } void TestBazaar::repoInit() { qDebug() << "Trying to init repo"; // make job that creates the local repository VcsJob* j = m_plugin->init(QUrl::fromLocalFile(bazaarTest_BaseDir)); VERIFYJOB(j); //check if the .bzr directory in the new local repository exists now QVERIFY(QFileInfo::exists(bazaarRepo)); } void TestBazaar::addFiles() { qDebug() << "Adding files to the repo"; //we start it after repoInit, so we still have empty bazaar repo QFile f(bazaarTest_BaseDir + bazaarTest_FileName); if (f.open(QIODevice::WriteOnly)) { QTextStream input(&f); input << "HELLO WORLD"; } f.close(); f.setFileName(bazaarTest_BaseDir + bazaarTest_FileName2); if (f.open(QIODevice::WriteOnly)) { QTextStream input(&f); input << "No, bar()!"; } f.close(); //test bzr-status exitCode (see DVcsJob::setExitCode). VcsJob* j = m_plugin->status(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir)); VERIFYJOB(j); // /tmp/kdevBazaar_testdir/ and testfile j = m_plugin->add(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir + bazaarTest_FileName)); VERIFYJOB(j); f.setFileName(bazaarSrcDir + bazaarTest_FileName3); if (f.open(QIODevice::WriteOnly)) { QTextStream input(&f); input << "No, foo()! It's bar()!"; } f.close(); //test bzr-status exitCode again j = m_plugin->status(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir)); VERIFYJOB(j); //repository path without trailing slash and a file in a parent directory // /tmp/repo and /tmp/repo/src/bar j = m_plugin->add(QList() << QUrl::fromLocalFile(bazaarSrcDir + bazaarTest_FileName3)); VERIFYJOB(j); //let's use absolute path, because it's used in ContextMenus j = m_plugin->add(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir + bazaarTest_FileName2)); VERIFYJOB(j); //Now let's create several files and try "bzr add file1 file2 file3" QStringList files = QStringList() << "file1" << "file2" << "la la"; QList multipleFiles; foreach(const QString& file, files) { QFile f(bazaarTest_BaseDir + file); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream input(&f); input << file; f.close(); multipleFiles << QUrl::fromLocalFile(bazaarTest_BaseDir + file); } j = m_plugin->add(multipleFiles); VERIFYJOB(j); } void TestBazaar::prepareWhoamiInformations() { DVcsJob *whoamiJob = new DVcsJob(bazaarTest_BaseDir, m_plugin); *whoamiJob<<"bzr"<<"whoami"<<"--branch"<<"kdevbazaar-test identity <>"; VERIFYJOB(whoamiJob); } void TestBazaar::commitFiles() { prepareWhoamiInformations(); qDebug() << "Committing..."; //we start it after addFiles, so we just have to commit VcsJob* j = m_plugin->commit(QStringLiteral("Test commit"), QList() << QUrl::fromLocalFile(bazaarTest_BaseDir)); VERIFYJOB(j); //test bzr-status exitCode one more time. j = m_plugin->status(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir)); VERIFYJOB(j); //since we committed the file to the "pure" repository, .bzr/repository/indices should exist //TODO: maybe other method should be used QString headRefName(bazaarRepo + "/repository/indices"); QVERIFY(QFileInfo::exists(headRefName)); //Test the results of the "bzr add" DVcsJob* jobLs = new DVcsJob(bazaarTest_BaseDir, m_plugin); *jobLs << "bzr" << "ls" << "-R"; if (jobLs->exec() && jobLs->status() == KDevelop::VcsJob::JobSucceeded) { QStringList files = jobLs->output().split('\n'); QVERIFY(files.contains(bazaarTest_FileName)); QVERIFY(files.contains(bazaarTest_FileName2)); QVERIFY(files.contains("src/" + bazaarTest_FileName3)); } qDebug() << "Committing one more time"; //let's try to change the file and test "bzr commit" QFile f(bazaarTest_BaseDir + bazaarTest_FileName); if (f.open(QIODevice::WriteOnly)) { QTextStream input(&f); input << "Just another HELLO WORLD\n"; } f.close(); //add changes j = m_plugin->add(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir + bazaarTest_FileName)); VERIFYJOB(j); j = m_plugin->commit(QStringLiteral("KDevelop's Test commit2"), QList() << QUrl::fromLocalFile(bazaarTest_BaseDir)); VERIFYJOB(j); } void TestBazaar::testInit() { repoInit(); } void TestBazaar::testAdd() { repoInit(); addFiles(); } void TestBazaar::testCommit() { repoInit(); addFiles(); commitFiles(); } void TestBazaar::testAnnotation() { repoInit(); addFiles(); commitFiles(); // called after commitFiles QFile f(bazaarTest_BaseDir + bazaarTest_FileName); QVERIFY(f.open(QIODevice::Append)); QTextStream input(&f); input << "An appended line"; f.close(); VcsJob* j = m_plugin->commit(QStringLiteral("KDevelop's Test commit3"), QList() << QUrl::fromLocalFile(bazaarTest_BaseDir)); VERIFYJOB(j); j = m_plugin->annotate(QUrl::fromLocalFile(bazaarTest_BaseDir + bazaarTest_FileName), VcsRevision::createSpecialRevision(VcsRevision::Head)); VERIFYJOB(j); QList results = j->fetchResults().toList(); QCOMPARE(results.size(), 2); QVERIFY(results.at(0).canConvert()); VcsAnnotationLine annotation = results.at(0).value(); QCOMPARE(annotation.lineNumber(), 0); QCOMPARE(annotation.commitMessage(), QStringLiteral("KDevelop's Test commit2")); QVERIFY(results.at(1).canConvert()); annotation = results.at(1).value(); QCOMPARE(annotation.lineNumber(), 1); QCOMPARE(annotation.commitMessage(), QStringLiteral("KDevelop's Test commit3")); } void TestBazaar::testRemoveEmptyFolder() { repoInit(); QDir d(bazaarTest_BaseDir); d.mkdir("emptydir"); VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir+"emptydir/")); if (j) VERIFYJOB(j); QVERIFY(!d.exists("emptydir")); } void TestBazaar::testRemoveEmptyFolderInFolder() { repoInit(); QDir d(bazaarTest_BaseDir); d.mkdir("dir"); QDir d2(bazaarTest_BaseDir+"dir"); d2.mkdir("emptydir"); VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir+"dir/")); if (j) VERIFYJOB(j); QVERIFY(!d.exists("dir")); } void TestBazaar::testRemoveUnindexedFile() { repoInit(); QFile f(bazaarTest_BaseDir + bazaarTest_FileName); QVERIFY(f.open(QIODevice::Append)); QTextStream input(&f); input << "An appended line"; f.close(); VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir + bazaarTest_FileName)); if (j) VERIFYJOB(j); QVERIFY(!QFile::exists(bazaarTest_BaseDir + bazaarTest_FileName)); } void TestBazaar::testRemoveFolderContainingUnversionedFiles() { repoInit(); QDir d(bazaarTest_BaseDir); d.mkdir("dir"); { QFile f(bazaarTest_BaseDir + "dir/foo"); QVERIFY(f.open(QIODevice::Append)); QTextStream input(&f); input << "An appended line"; f.close(); } VcsJob* j = m_plugin->add(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir+"dir"), IBasicVersionControl::NonRecursive); VERIFYJOB(j); prepareWhoamiInformations(); j = m_plugin->commit("initial commit", QList() << QUrl::fromLocalFile(bazaarTest_BaseDir)); VERIFYJOB(j); { QFile f(bazaarTest_BaseDir + "dir/bar"); QVERIFY(f.open(QIODevice::Append)); QTextStream input(&f); input << "An appended line"; f.close(); } j = m_plugin->remove(QList() << QUrl::fromLocalFile(bazaarTest_BaseDir + "dir")); if (j) VERIFYJOB(j); QVERIFY(!QFile::exists(bazaarTest_BaseDir + "dir")); } void TestBazaar::removeTempDirs() { if (QFileInfo::exists(bazaarTest_BaseDir)) if (!(KIO::del(QUrl::fromLocalFile(bazaarTest_BaseDir))->exec())) qDebug() << "KIO::del(" << bazaarTest_BaseDir << ") returned false"; if (QFileInfo::exists(bazaarTest_BaseDir2)) if (!(KIO::del(QUrl::fromLocalFile(bazaarTest_BaseDir2))->exec())) qDebug() << "KIO::del(" << bazaarTest_BaseDir2 << ") returned false"; } QTEST_MAIN(TestBazaar) diff --git a/plugins/bazaar/tests/test_bazaar.h b/plugins/bazaar/tests/test_bazaar.h index a03d60ba8..a555e7147 100644 --- a/plugins/bazaar/tests/test_bazaar.h +++ b/plugins/bazaar/tests/test_bazaar.h @@ -1,71 +1,70 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2007 Robert Gruber * * * * Adapted for Git * * Copyright 2008 Evgeniy Ivanov * * * * Adapted for Bazaar * * Copyright 2014 Maciej Poleski * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_TEST_BAZAAR_H #define KDEVPLATFORM_PLUGIN_TEST_BAZAAR_H #include -#include class BazaarPlugin; namespace KDevelop { class TestCore; } class TestBazaar: public QObject { Q_OBJECT private: void repoInit(); void addFiles(); void prepareWhoamiInformations(); void commitFiles(); private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testInit(); void testAdd(); void testCommit(); void testAnnotation(); void testRemoveEmptyFolder(); void testRemoveEmptyFolderInFolder(); void testRemoveUnindexedFile(); void testRemoveFolderContainingUnversionedFiles(); private: BazaarPlugin* m_plugin; void removeTempDirs(); }; #endif // KDEVPLATFORM_PLUGIN_TEST_BAZAAR_H diff --git a/plugins/codeutils/codeutilsplugin.cpp b/plugins/codeutils/codeutilsplugin.cpp index ad9c0ec35..75cf90ecc 100644 --- a/plugins/codeutils/codeutilsplugin.cpp +++ b/plugins/codeutils/codeutilsplugin.cpp @@ -1,159 +1,156 @@ /* * 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 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 "codeutilsplugin.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 Q_LOGGING_CATEGORY(PLUGIN_CODEUTILS, "kdevplatform.plugins.codeutils") using namespace KDevelop; using namespace KTextEditor; K_PLUGIN_FACTORY_WITH_JSON(CodeUtilsPluginFactory, "kdevcodeutils.json", registerPlugin(); ) CodeUtilsPlugin::CodeUtilsPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( QStringLiteral("kdevcodeutils"), parent ) { setXMLFile( QStringLiteral("kdevcodeutils.rc") ); QAction* action = actionCollection()->addAction( QStringLiteral("document_declaration") ); // i18n: action name; 'Document' is a verb action->setText( i18n( "Document Declaration" ) ); actionCollection()->setDefaultShortcut(action, i18n( "Alt+Shift+d" )); connect( action, SIGNAL(triggered(bool)), this, SLOT(documentDeclaration()) ); action->setToolTip( i18n( "Add Doxygen skeleton for declaration under cursor." ) ); // i18n: translate title same as the action name action->setWhatsThis( i18n( "Adds a basic Doxygen comment skeleton in front of " "the declaration under the cursor, e.g. with all the " "parameter of a function." ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("documentinfo") ) ); } void CodeUtilsPlugin::documentDeclaration() { View* view = ICore::self()->documentController()->activeTextDocumentView(); if ( !view ) { return; } DUChainReadLocker lock; TopDUContext* topCtx = DUChainUtils::standardContextForUrl(view->document()->url()); if ( !topCtx ) { return; } Declaration* dec = DUChainUtils::declarationInLine( KTextEditor::Cursor( view->cursorPosition() ), topCtx ); if ( !dec || dec->isForwardDeclaration() ) { return; } // finally - we found the declaration :) int line = dec->range().start.line; Cursor insertPos( line, 0 ); TemplateRenderer renderer; renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); renderer.addVariable(QStringLiteral("brief"), i18n( "..." )); /* QString indentation = textDoc->line( insertPos.line() ); if (!indentation.isEmpty()) { int lastSpace = 0; while (indentation.at(lastSpace).isSpace()) { ++lastSpace; } indentation.truncate(lastSpace); } */ if (dec->isFunctionDeclaration()) { FunctionDescription description = FunctionDescription(DeclarationPointer(dec)); renderer.addVariable(QStringLiteral("function"), QVariant::fromValue(description)); qCDebug(PLUGIN_CODEUTILS) << "Found function" << description.name << "with" << description.arguments.size() << "arguments"; } lock.unlock(); // TODO: Choose the template based on the language QString templateName = QStringLiteral("doxygen_cpp"); auto languages = core()->languageController()->languagesForUrl(view->document()->url()); if (!languages.isEmpty()) { QString languageName = languages.first()->name(); if (languageName == QLatin1String("Php")) { templateName = QStringLiteral("phpdoc_php"); } else if (languageName == QLatin1String("Python")) { templateName = QStringLiteral("rest_python"); // Python docstrings appear inside functions and classes, not above them insertPos = Cursor(line+1, 0); } } QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevcodeutils/templates/" + templateName + ".txt"); if (fileName.isEmpty()) { qWarning() << "No suitable template found" << fileName; return; } const QString comment = renderer.renderFile(QUrl::fromLocalFile(fileName)); view->insertTemplate(insertPos, comment); } CodeUtilsPlugin::~CodeUtilsPlugin() { } #include "codeutilsplugin.moc" diff --git a/plugins/contextbrowser/browsemanager.h b/plugins/contextbrowser/browsemanager.h index 0c9f480db..8ed48f7e4 100644 --- a/plugins/contextbrowser/browsemanager.h +++ b/plugins/contextbrowser/browsemanager.h @@ -1,123 +1,122 @@ /* * 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. */ #ifndef KDEVPLATFORM_PLUGIN_BROWSEMANAGER_H #define KDEVPLATFORM_PLUGIN_BROWSEMANAGER_H #include -#include #include #include #include #include #include class QWidget; namespace KTextEditor { class View; class Document; } namespace KDevelop { class IDocument; } class EditorViewWatcher : public QObject { Q_OBJECT public: explicit EditorViewWatcher(QObject* parent = nullptr); QList allViews(); private: ///Called for every added view. Reimplement this to catch them. virtual void viewAdded(KTextEditor::View*); private slots: void viewDestroyed(QObject* view); void viewCreated(KTextEditor::Document*, KTextEditor::View*); void documentCreated( KDevelop::IDocument* document ); private: void addViewInternal(KTextEditor::View* view); QList m_views; }; class ContextBrowserPlugin; class BrowseManager; class Watcher : public EditorViewWatcher { Q_OBJECT public: explicit Watcher(BrowseManager* manager); void viewAdded(KTextEditor::View*) override; private: BrowseManager* m_manager; }; /** * Integrates the context-browser with the editor views, by listening for navigation events, and implementing html-like source browsing */ class BrowseManager : public QObject { Q_OBJECT public: explicit BrowseManager(ContextBrowserPlugin* controller); void viewAdded(KTextEditor::View* view); ///Installs/uninstalls the event-filter void applyEventFilter(QWidget* object, bool install); Q_SIGNALS: ///Emitted when browsing was started using the magic-modifier void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); void invokeAction(int index); public slots: ///Enabled/disables the browsing mode void setBrowsing(bool); private slots: void eventuallyStartDelayedBrowsing(); private: struct JumpLocation { QUrl url; KTextEditor::Cursor cursor; bool isValid() const { return url.isValid() && cursor.isValid(); } }; void resetChangedCursor(); JumpLocation determineJumpLoc(KTextEditor::Cursor textCursor, const QUrl& viewUrl) const; void setHandCursor(QWidget* widget); void avoidMenuAltFocus(); bool eventFilter(QObject * watched, QEvent * event) override ; ContextBrowserPlugin* m_plugin; bool m_browsing; int m_browsingByKey; //Whether the browsing was started because of a key Watcher m_watcher; //Maps widgets to their previously set cursors QMap, QCursor> m_oldCursors; QTimer* m_delayedBrowsingTimer; QPointer m_browsingStartedInView; KTextEditor::Cursor m_buttonPressPosition; }; #endif diff --git a/plugins/contextbrowser/contextbrowserview.cpp b/plugins/contextbrowser/contextbrowserview.cpp index d4e3a74fa..4ceae2a3d 100644 --- a/plugins/contextbrowser/contextbrowserview.cpp +++ b/plugins/contextbrowser/contextbrowserview.cpp @@ -1,391 +1,390 @@ /* * 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 #include #include "contextbrowser.h" #include "debug.h" #include #include #include #include "browsemanager.h" #include #include #include #include #include #include using namespace KDevelop; namespace { enum Direction { NextUse, PreviousUse }; void selectUse(ContextBrowserView* view, Direction direction) { auto abstractNaviWidget = dynamic_cast(view->navigationWidget()); if (!abstractNaviWidget) { return; } auto usesWidget = dynamic_cast(abstractNaviWidget->context()->widget()); if (!usesWidget) { return; } OneUseWidget* first = nullptr, *previous = nullptr, *current = nullptr; for (auto item : usesWidget->items()) { auto topContext = dynamic_cast(item); if (!topContext) { continue; } for (auto item : topContext->items()) { auto navigationList = dynamic_cast(item); if (!navigationList) { continue; } for (auto item : navigationList->items()) { auto use = dynamic_cast(item); if (!use) { continue; } if (!first) { first = use; } current = use; if (direction == PreviousUse && current->isHighlighted() && previous) { previous->setHighlighted(true); previous->activateLink(); current->setHighlighted(false); return; } if (direction == NextUse && previous && previous->isHighlighted()) { current->setHighlighted(true); current->activateLink(); previous->setHighlighted(false); return; } previous = current; } } } if (direction == NextUse && first) { first->setHighlighted(true); first->activateLink(); if (current && current->isHighlighted()) current->setHighlighted(false); return; } if (direction == PreviousUse && current) { current->setHighlighted(true); current->activateLink(); if (first && first->isHighlighted()) { first->setHighlighted(false); } } } } QWidget* ContextBrowserView::createWidget(KDevelop::DUContext* context) { m_context = IndexedDUContext(context); if(m_context.data()) { return m_context.data()->createNavigationWidget(nullptr, nullptr, {}, {}, AbstractNavigationWidget::EmbeddableWidget); } 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 = 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()); } } } ContextBrowserView::ContextBrowserView( ContextBrowserPlugin* plugin, QWidget* parent ) : QWidget(parent), m_plugin(plugin), m_navigationWidget(new QTextBrowser()), m_autoLocked(false) { setWindowTitle(i18n("Code Browser")); setWindowIcon( QIcon::fromTheme(QStringLiteral("code-context"), windowIcon()) ); m_allowLockedUpdate = false; m_declarationMenuAction = new QAction(QIcon::fromTheme(QStringLiteral("code-class")), QString(), this); m_declarationMenuAction->setToolTip(i18n("Declaration menu")); connect(m_declarationMenuAction, &QAction::triggered, this, &ContextBrowserView::declarationMenu); addAction(m_declarationMenuAction); m_lockAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("object-unlocked")), i18n("Lock Current View"), this); m_lockAction->setToolTip(i18n("Lock current view")); m_lockAction->setCheckedState(KGuiItem(i18n("Unlock Current View"), QIcon::fromTheme(QStringLiteral("object-locked")), i18n("Unlock current view"))); m_lockAction->setChecked(false); addAction(m_lockAction); m_layout = new QVBoxLayout; m_layout->setSpacing(0); m_layout->setMargin(0); 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_lockAction->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()) { //Update the navigation-widget Declaration* decl = m_navigationWidgetDeclaration.getDeclaration(top); if(decl) setDeclaration(decl, top, true); } QWidget::showEvent(event); } bool ContextBrowserView::isLocked() const { bool isLocked; if (m_allowLockedUpdate) { isLocked = false; } else { isLocked = m_lockAction->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_lockAction->isChecked()) { m_autoLocked = true; m_lockAction->setChecked(true); }else if(!wasInitial && isInitial && m_autoLocked) { m_autoLocked = false; m_lockAction->setChecked(false); }else if(isInitial) { m_autoLocked = false; } } void ContextBrowserView::selectNextItem() { selectUse(this, NextUse); } void ContextBrowserView::selectPreviousItem() { selectUse(this, PreviousUse); } 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_lockAction->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_lockAction->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/contextbrowser/contextbrowserview.h b/plugins/contextbrowser/contextbrowserview.h index ea25173b7..50d55b5ae 100644 --- a/plugins/contextbrowser/contextbrowserview.h +++ b/plugins/contextbrowser/contextbrowserview.h @@ -1,118 +1,114 @@ /* * 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. */ #ifndef KDEVPLATFORM_PLUGIN_CONTEXTBROWSERVIEW_H #define KDEVPLATFORM_PLUGIN_CONTEXTBROWSERVIEW_H #include -#include #include #include #include #include #include class ContextBrowserPlugin; class QVBoxLayout; class QHBoxLayout; class QAction; -class QCheckBox; -class QMenu; -class KComboBox; class KToggleAction; namespace KDevelop { class IDocument; } class ContextBrowserView : public QWidget, public KDevelop::IToolViewActionListener { Q_OBJECT Q_INTERFACES(KDevelop::IToolViewActionListener) public: ContextBrowserView( ContextBrowserPlugin*, QWidget* parent ); ~ContextBrowserView() override; //duchain must be locked void setContext(KDevelop::DUContext* context); void setDeclaration(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext, bool force = false); void setSpecialNavigationWidget(QWidget*); void updateMainWidget(QWidget*); /** Allows a single update of the view even if in locked state. * This is needed to browse while the locked button is checked. */ void allowLockedUpdate(); ///Returns the currently locked declaration, or invalid of none is locked atm. KDevelop::IndexedDeclaration lockedDeclaration() const; void setNavigationWidget(QWidget* widget); QWidget* navigationWidget() { return m_navigationWidget; } //duchain must be locked QWidget* createWidget(KDevelop::DUContext* context); //duchain must be locked QWidget* createWidget(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext); KDevelop::IndexedDeclaration declaration() const; ///Returns whether the view is currently locked bool isLocked() const; private Q_SLOTS: void declarationMenu(); void navigationContextChanged(bool wasInitial, bool isInitial); void selectNextItem() override; void selectPreviousItem() override; private: void showEvent(QShowEvent* event) override; bool event(QEvent* event) override; void focusInEvent(QFocusEvent* event) override; void focusOutEvent(QFocusEvent* event) override; void resetWidget(); private: KDevelop::IndexedDeclaration m_declaration; ContextBrowserPlugin* m_plugin; QVBoxLayout* m_layout; KToggleAction* m_lockAction; QAction* m_declarationMenuAction; QHBoxLayout* m_buttons; QPointer m_navigationWidget; KDevelop::DeclarationId m_navigationWidgetDeclaration; bool m_allowLockedUpdate; KDevelop::IndexedTopDUContext m_lastUsedTopContext; KDevelop::IndexedDUContext m_context; // Whether the lock-button was activated automatically due to user navigation bool m_autoLocked; }; #endif diff --git a/plugins/documentswitcher/documentswitcherplugin.h b/plugins/documentswitcher/documentswitcherplugin.h index 534333dbc..7e34e0857 100644 --- a/plugins/documentswitcher/documentswitcherplugin.h +++ b/plugins/documentswitcher/documentswitcherplugin.h @@ -1,81 +1,79 @@ /*************************************************************************** * Copyright 2009 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERPLUGIN_H #define KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERPLUGIN_H #include #include #include Q_DECLARE_LOGGING_CATEGORY(PLUGIN_DOCUMENTSWITCHER) class QStandardItemModel; namespace Sublime { class View; class MainWindow; class AreaIndex; class Area; class MainWindow; } class DocumentSwitcherTreeView; class QModelIndex; -class QStringListModel; -class QEvent; class QAction; class DocumentSwitcherPlugin: public KDevelop::IPlugin { Q_OBJECT public: explicit DocumentSwitcherPlugin( QObject *parent, const QVariantList &args = QVariantList() ); ~DocumentSwitcherPlugin() override; void unload() override; public slots: void itemActivated( const QModelIndex& ); void switchToClicked(const QModelIndex& ); private slots: void addView( Sublime::View* ); void changeView( Sublime::View* ); void addMainWindow( Sublime::MainWindow* ); void changeArea( Sublime::Area* ); void removeView( Sublime::View* ); void removeMainWindow(QObject*); void walkForward(); void walkBackward(); protected: bool eventFilter( QObject*, QEvent* ) override; private: void setViewGeometry(Sublime::MainWindow* window); void storeAreaViewList( Sublime::MainWindow* mainwindow, Sublime::Area* area ); void enableActions(); void fillModel( Sublime::MainWindow* window ); void walk(const int from, const int to); // Need to use QObject here as we only have a QObject* in // the removeMainWindow method and cannot cast it to the mainwindow anymore QMap > > documentLists; DocumentSwitcherTreeView* view; QStandardItemModel* model; QAction* forwardAction; QAction* backwardAction; }; #endif diff --git a/plugins/documentswitcher/documentswitchertreeview.cpp b/plugins/documentswitcher/documentswitchertreeview.cpp index afb5e6bbb..a690e1a8c 100644 --- a/plugins/documentswitcher/documentswitchertreeview.cpp +++ b/plugins/documentswitcher/documentswitchertreeview.cpp @@ -1,70 +1,67 @@ /*************************************************************************** * Copyright 2009 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 "documentswitchertreeview.h" #include -#include -#include - #include #include #include #include #include "documentswitcherplugin.h" using namespace KDevelop; DocumentSwitcherTreeView::DocumentSwitcherTreeView(DocumentSwitcherPlugin* plugin_ ) : QTreeView(nullptr) , plugin(plugin_) { setWindowFlags(Qt::Popup | Qt::FramelessWindowHint); } void DocumentSwitcherTreeView::keyPressEvent(QKeyEvent* event) { QTreeView::keyPressEvent(event); } void DocumentSwitcherTreeView::keyReleaseEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Control) { plugin->itemActivated(selectionModel()->currentIndex()); event->accept(); hide(); } else { QTreeView::keyReleaseEvent(event); } } void DocumentSwitcherTreeView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const { if (WidgetColorizer::colorizeByProject()) { if (const auto project = index.data(ProjectRole).value()) { const auto projectPath = project->path(); const QColor color = WidgetColorizer::colorForId(qHash(projectPath), palette(), true); WidgetColorizer::drawBranches(this, painter, rect, index, color); } } // don't call the base implementation, as we only want to paint the colorization above // this means that for people who have the feature disabled, we get some padding on the left, // but that is OK imo } diff --git a/plugins/documentview/kdevdocumentviewdelegate.cpp b/plugins/documentview/kdevdocumentviewdelegate.cpp index 39352ba2d..98ed92aea 100644 --- a/plugins/documentview/kdevdocumentviewdelegate.cpp +++ b/plugins/documentview/kdevdocumentviewdelegate.cpp @@ -1,49 +1,47 @@ /* 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 "kdevdocumentviewdelegate.h" -#include - KDevDocumentViewDelegate::KDevDocumentViewDelegate( QObject *parent ) : QItemDelegate( parent ) {} KDevDocumentViewDelegate::~KDevDocumentViewDelegate() {} void KDevDocumentViewDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const { const QAbstractItemModel * model = index.model(); Q_ASSERT( model ); if ( !model->parent( index ).isValid() ) { // this is a top-level item. QStyleOptionViewItem o = option; o.textElideMode = Qt::ElideLeft; QItemDelegate::paint( painter, o, index ); } else { QItemDelegate::paint( painter, option, index ); } } diff --git a/plugins/documentview/kdevdocumentviewdelegate.h b/plugins/documentview/kdevdocumentviewdelegate.h index 86b401e4d..98fefc265 100644 --- a/plugins/documentview/kdevdocumentviewdelegate.h +++ b/plugins/documentview/kdevdocumentviewdelegate.h @@ -1,36 +1,35 @@ /* This file is part of KDevelop Copyright 2005 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. */ #ifndef KDEVPLATFORM_PLUGIN_KDEVDOCUMENTVIEWDELEGATE_H #define KDEVPLATFORM_PLUGIN_KDEVDOCUMENTVIEWDELEGATE_H #include -class QTreeView; class KDevDocumentViewDelegate: public QItemDelegate { Q_OBJECT public: explicit KDevDocumentViewDelegate( QObject *parent = nullptr ); ~KDevDocumentViewDelegate() override; void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const override; }; #endif // KDEVPLATFORM_PLUGIN_KDEVCLASSVIEWDELEGATE_H diff --git a/plugins/documentview/kdevdocumentviewplugin.cpp b/plugins/documentview/kdevdocumentviewplugin.cpp index f9d392c4f..82dea8095 100644 --- a/plugins/documentview/kdevdocumentviewplugin.cpp +++ b/plugins/documentview/kdevdocumentviewplugin.cpp @@ -1,108 +1,105 @@ /* * 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: explicit KDevDocumentViewPluginFactory( KDevDocumentViewPlugin *plugin ): m_plugin( plugin ) {} 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.h b/plugins/execute/executeplugin.h index 71ca9c7f9..c5b1fb5b4 100644 --- a/plugins/execute/executeplugin.h +++ b/plugins/execute/executeplugin.h @@ -1,73 +1,72 @@ /* * 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. */ #ifndef KDEVPLATFORM_PLUGIN_EXECUTEPLUGIN_H #define KDEVPLATFORM_PLUGIN_EXECUTEPLUGIN_H #include #include -#include #include "iexecuteplugin.h" class QUrl; class KJob; class NativeAppConfigType; class ExecutePlugin : public KDevelop::IPlugin, public IExecutePlugin { Q_OBJECT Q_INTERFACES( IExecutePlugin ) public: explicit ExecutePlugin(QObject *parent, const QVariantList & = QVariantList() ); ~ExecutePlugin() override; static QString _nativeAppConfigTypeId; static QString workingDirEntry; static QString executableEntry; static QString argumentsEntry; static QString isExecutableEntry; static QString dependencyEntry; static QString environmentProfileEntry; static QString useTerminalEntry; static QString terminalEntry; static QString userIdToRunEntry; static QString dependencyActionEntry; static QString projectTargetEntry; void unload() override; QUrl executable( KDevelop::ILaunchConfiguration*, QString& err ) const override; QStringList arguments( KDevelop::ILaunchConfiguration*, QString& err ) const override; QUrl workingDirectory( KDevelop::ILaunchConfiguration* ) const override; KJob* dependencyJob( KDevelop::ILaunchConfiguration* ) const override; QString environmentProfileName(KDevelop::ILaunchConfiguration*) const override; bool useTerminal( KDevelop::ILaunchConfiguration* ) const override; QString terminal( KDevelop::ILaunchConfiguration* ) const override; QString nativeAppConfigTypeId() const override; NativeAppConfigType* m_configType; }; #endif // KDEVPLATFORM_PLUGIN_EXECUTEPLUGIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/execute/nativeappjob.h b/plugins/execute/nativeappjob.h index 45a287aa5..62ba718f7 100644 --- a/plugins/execute/nativeappjob.h +++ b/plugins/execute/nativeappjob.h @@ -1,47 +1,43 @@ /* 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. */ #ifndef KDEVPLATFORM_PLUGIN_NATIVEAPPJOB_H #define KDEVPLATFORM_PLUGIN_NATIVEAPPJOB_H #include -#include - namespace KDevelop { class ILaunchConfiguration; } -class KProcess; - class NativeAppJob : public KDevelop::OutputExecuteJob { Q_OBJECT public: NativeAppJob( QObject* parent, KDevelop::ILaunchConfiguration* cfg ); void start() override; private: QString m_cfgname; }; #endif diff --git a/plugins/executescript/executescriptplugin.h b/plugins/executescript/executescriptplugin.h index de5f34af7..b12ad092a 100644 --- a/plugins/executescript/executescriptplugin.h +++ b/plugins/executescript/executescriptplugin.h @@ -1,77 +1,75 @@ /* * 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. */ #ifndef KDEVPLATFORM_PLUGIN_EXECUTESCRIPTPLUGIN_H #define KDEVPLATFORM_PLUGIN_EXECUTESCRIPTPLUGIN_H #include #include -#include #include "iexecutescriptplugin.h" class ScriptAppConfigType; class QUrl; -class KJob; class ExecuteScriptPlugin : public KDevelop::IPlugin, public IExecuteScriptPlugin { Q_OBJECT Q_INTERFACES( IExecuteScriptPlugin ) public: explicit ExecuteScriptPlugin(QObject *parent, const QVariantList & = QVariantList() ); ~ExecuteScriptPlugin() override; static QString _scriptAppConfigTypeId; static QString interpreterEntry; static QString workingDirEntry; static QString executableEntry; static QString executeOnRemoteHostEntry; static QString remoteHostEntry; static QString runCurrentFileEntry; static QString argumentsEntry; static QString isExecutableEntry; static QString environmentProfileEntry; //static QString useTerminalEntry; static QString userIdToRunEntry; static QString projectTargetEntry; static QString outputFilteringEntry; void unload() override; QString interpreter( KDevelop::ILaunchConfiguration*, QString& err ) const override; QUrl script( KDevelop::ILaunchConfiguration*, QString& err ) const override; QString remoteHost(KDevelop::ILaunchConfiguration* , QString& err) const override; QStringList arguments( KDevelop::ILaunchConfiguration*, QString& err ) const override; QUrl workingDirectory( KDevelop::ILaunchConfiguration* ) const override; QString environmentProfileName(KDevelop::ILaunchConfiguration*) const override; //bool useTerminal( KDevelop::ILaunchConfiguration* ) const; QString scriptAppConfigTypeId() const override; int outputFilterModeId( KDevelop::ILaunchConfiguration* ) const override; bool runCurrentFile(KDevelop::ILaunchConfiguration*) const override; ScriptAppConfigType* m_configType; }; #endif // KDEVPLATFORM_PLUGIN_EXECUTESCRIPTPLUGIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/executescript/iexecutescriptplugin.h b/plugins/executescript/iexecutescriptplugin.h index 20371f105..50e6361c6 100644 --- a/plugins/executescript/iexecutescriptplugin.h +++ b/plugins/executescript/iexecutescriptplugin.h @@ -1,62 +1,61 @@ /* KDevelop * * Copyright 2009 Andreas Pakulat * 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. */ #ifndef KDEVPLATFORM_PLUGIN_IEXECUTESCRIPTPLUGIN_H #define KDEVPLATFORM_PLUGIN_IEXECUTESCRIPTPLUGIN_H #include -#include namespace KDevelop { class ILaunchConfiguration; } -class KJob; class QUrl; +class QString; class QStringList; /** @author Andreas Pakulat @author Niko Sams */ class IExecuteScriptPlugin { public: virtual ~IExecuteScriptPlugin() {} virtual QString interpreter( KDevelop::ILaunchConfiguration*, QString& ) const = 0; virtual QUrl script( KDevelop::ILaunchConfiguration*, QString& ) const = 0; virtual QString remoteHost( KDevelop::ILaunchConfiguration*, QString& ) const = 0; virtual QStringList arguments( KDevelop::ILaunchConfiguration*, QString& ) const = 0; virtual QUrl workingDirectory( KDevelop::ILaunchConfiguration* ) const = 0; virtual QString environmentProfileName(KDevelop::ILaunchConfiguration*) const = 0; //virtual bool useTerminal( KDevelop::ILaunchConfiguration* ) const = 0; virtual QString scriptAppConfigTypeId() const = 0; virtual int outputFilterModeId( KDevelop::ILaunchConfiguration* ) const = 0; virtual bool runCurrentFile( KDevelop::ILaunchConfiguration* ) const = 0; }; Q_DECLARE_INTERFACE( IExecuteScriptPlugin, "org.kdevelop.IExecuteScriptPlugin" ) #endif diff --git a/plugins/externalscript/externalscriptitem.h b/plugins/externalscript/externalscriptitem.h index 98c2b0296..3a548d9ca 100644 --- a/plugins/externalscript/externalscriptitem.h +++ b/plugins/externalscript/externalscriptitem.h @@ -1,217 +1,217 @@ /* 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 */ #ifndef KDEVPLATFORM_PLUGIN_EXTERNALSCRIPTITEM_H #define KDEVPLATFORM_PLUGIN_EXTERNALSCRIPTITEM_H -#include +#include class QAction; /** * NOTE: use @c text() and @c setText() to define the label/name of the external script. */ class ExternalScriptItem : public QStandardItem { public: ExternalScriptItem(); /** * @return The command to execute. */ QString command() const; /** * Sets the command to execute. */ void setCommand( const QString& command ); /** * @return The working directory where to execute the command. * If this is empty (default), it should be derived from the active document. */ QString workingDirectory() const; /** * Specify the working directory where the command should be executed */ void setWorkingDirectory( const QString& workingDirectory ); /** * Whether placeholders like %b etc. in the command should be substituted. Default is true. * */ bool performParameterReplacement() const; /** * Set whether placeholders like %b etc. in the command should be substituted. Default is true. * */ void setPerformParameterReplacement(bool perform); enum SaveMode { /// Nothing needs to be saved. SaveNone, /// Currently active document gets saved. SaveCurrentDocument, /// All opened documents get saved. SaveAllDocuments }; /** * @return @c SaveMode that decides what document should be saved before executing this script. */ SaveMode saveMode() const; /** * Sets the @c SaveMode that decides what document should be saved before executing this script. */ void setSaveMode( SaveMode mode ); /** * @return what type of filter should be applied to the execution of the external script **/ int filterMode() const; /** * Sets the filtering mode **/ void setFilterMode( int mode ); /// Defines what should be done with the @c STDOUT of a script run. enum OutputMode { /// Ignore output and do nothing. OutputNone, /// Output gets inserted at the cursor position of the current document. OutputInsertAtCursor, /// Current selection gets replaced in the active document. /// If no selection exists, the output will get inserted at the /// current cursor position in the active document view. OutputReplaceSelectionOrInsertAtCursor, /// Current selection gets replaced in the active document. /// If no selection exists, the whole document gets replaced. OutputReplaceSelectionOrDocument, /// The whole contents of the active document gets replaced. OutputReplaceDocument, /// Create a new file from the output. OutputCreateNewFile }; /** * @return @c OutputMode that decides what parts of the active document should be replaced by the * @c STDOUT of the @c command() execution. */ OutputMode outputMode() const; /** * Sets the @c OutputMode that decides what parts of the active document should be replaced by the * @c STDOUT of the @c command() execution. */ void setOutputMode( OutputMode mode ); /// Defines what should be done with the @c STDERR of a script run. enum ErrorMode { /// Ignore errors and do nothing. ErrorNone, /// Merge with @c STDOUT and use @c OutputMode. ErrorMergeOutput, /// Errors get inserted at the cursor position of the current document. ErrorInsertAtCursor, /// Current selection gets replaced in the active document. /// If no selection exists, the output will get inserted at the /// current cursor position in the active document view. ErrorReplaceSelectionOrInsertAtCursor, /// Current selection gets replaced in the active document. /// If no selection exists, the whole document gets replaced. ErrorReplaceSelectionOrDocument, /// The whole contents of the active document gets replaced. ErrorReplaceDocument, /// Create a new file from the errors. ErrorCreateNewFile }; /** * @return @c ErrorMode that decides what parts of the active document should be replaced by the * @c STDERR of the @c command() execution. */ ErrorMode errorMode() const; /** * Sets the @c ErrorMode that decides what parts of the active document should be replaced by the * @c STDERR of the @c command() execution. */ void setErrorMode( ErrorMode mode ); enum InputMode { /// Nothing gets streamed to the @c STDIN of the external script. InputNone, /// Current selection gets streamed into the @c STDIN of the external script. /// If no selection exists, nothing gets streamed. InputSelectionOrNone, /// Current selection gets streamed into the @c STDIN of the external script. /// If no selection exists, the whole document gets streamed. InputSelectionOrDocument, /// The whole contents of the active document get streamed into the @c STDIN of the external script. InputDocument, }; /** * @return @c InputMode that decides what parts of the active document should be streamded into * the @c STDIN of the external script. */ InputMode inputMode() const; /** * Sets the @c InputMode that decides what parts of the active document should be streamded into * the @c STDIN of the external script. */ void setInputMode( InputMode mode ); /** * Action to trigger insertion of this snippet. */ QAction* action(); /** * @return True when this command should have its output shown, false otherwise. */ bool showOutput() const; /** * Set @p show to true when the output of this command shout be shown, false otherwise. */ void setShowOutput( bool show ); ///TODO: custom icon ///TODO: mimetype / language filter ///TODO: kate commandline integration ///TODO: filter for local/remote files /** * Saves this item after changes. */ void save() const; private: QString m_command; QString m_workingDirectory; SaveMode m_saveMode; OutputMode m_outputMode; ErrorMode m_errorMode; InputMode m_inputMode; QAction* m_action; bool m_showOutput; int m_filterMode; bool m_performReplacements; }; Q_DECLARE_METATYPE(ExternalScriptItem*) #endif // KDEVPLATFORM_PLUGIN_EXTERNALSCRIPTITEM_H // 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 5ac62e014..b79179527 100644 --- a/plugins/externalscript/externalscriptplugin.cpp +++ b/plugins/externalscript/externalscriptplugin.cpp @@ -1,369 +1,368 @@ /* 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: explicit ExternalScriptViewFactory( ExternalScriptPlugin *plugin ): m_plugin( plugin ) {} 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 = 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 = 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/externalscriptplugin.h b/plugins/externalscript/externalscriptplugin.h index ed2a9856f..de8b00678 100644 --- a/plugins/externalscript/externalscriptplugin.h +++ b/plugins/externalscript/externalscriptplugin.h @@ -1,104 +1,104 @@ /* 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 */ #ifndef KDEVPLATFORM_PLUGIN_EXTERNALSCRIPTPLUGIN_H #define KDEVPLATFORM_PLUGIN_EXTERNALSCRIPTPLUGIN_H #include #include #include -#include #include class ExternalScriptItem; class QStandardItem; class QStandardItemModel; +class QModelIndex; class ExternalScriptPlugin : public KDevelop::IPlugin { Q_OBJECT Q_CLASSINFO( "D-Bus Interface", "org.kdevelop.ExternalScriptPlugin" ) public: explicit ExternalScriptPlugin( QObject *parent, const QVariantList &args = QVariantList() ); ~ExternalScriptPlugin() override; void unload() override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override; static ExternalScriptPlugin* self(); /** * @return The model storing all external scripts managed by this plugin. * @NOTE: always append() items, never insert in the middle! */ QStandardItemModel* model() const; /** * Executes @p script. */ void execute(ExternalScriptItem* item, const QUrl &url) const; /** * Executes @p script. */ void execute(ExternalScriptItem* item) const; /** * Returns config group to store all settings for this plugin in. */ KConfigGroup getConfig() const; void saveItem(const ExternalScriptItem* item); public slots: void executeScriptFromActionData() const; /** * Executes the command (Used by the shell-integration) * */ Q_SCRIPTABLE bool executeCommand(QString command, QString workingDirectory) const; /** * Executes the command synchronously and returns the output text (Used by the shell-integration) * */ Q_SCRIPTABLE QString executeCommandSync(QString command, QString workingDirectory) const; private slots: void rowsRemoved( const QModelIndex& parent, int start, int end ); void rowsInserted( const QModelIndex& parent, int start, int end ); void executeScriptFromContextMenu() const; private: /// @param row row in the model for the item to save void saveItemForRow( int row ); QStandardItemModel* m_model; QList m_urls; static ExternalScriptPlugin* m_self; class ExternalScriptViewFactory *m_factory; }; #endif // KDEVPLATFORM_PLUGIN_EXTERNALSCRIPTPLUGIN_H // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/filemanager/filemanager.cpp b/plugins/filemanager/filemanager.cpp index 5199903a7..423242f98 100644 --- a/plugins/filemanager/filemanager.cpp +++ b/plugins/filemanager/filemanager.cpp @@ -1,215 +1,214 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * Copyright 2006 Andreas Pakulat * * Copyright 2016 Imran Tatriev * * * * 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 "filemanager.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 "../openwith/iopenwith.h" #include "kdevfilemanagerplugin.h" #include "bookmarkhandler.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_FILEMANAGER, "kdevplatform.plugins.filemanager") FileManager::FileManager(KDevFileManagerPlugin *plugin, QWidget* parent) : QWidget(parent), m_plugin(plugin) { setObjectName(QStringLiteral("FileManager")); setWindowIcon(QIcon::fromTheme(QStringLiteral("folder-sync"), windowIcon())); setWindowTitle(i18n("File System")); KConfigGroup cg = KDevelop::ICore::self()->activeSession()->config()->group( "Filesystem" ); QVBoxLayout *l = new QVBoxLayout(this); l->setMargin(0); l->setSpacing(0); KFilePlacesModel* model = new KFilePlacesModel( this ); urlnav = new KUrlNavigator(model, QUrl(cg.readEntry( "LastLocation", QUrl::fromLocalFile( QDir::homePath() ) )), this ); connect(urlnav, &KUrlNavigator::urlChanged, this, &FileManager::gotoUrl); l->addWidget(urlnav); dirop = new KDirOperator( urlnav->locationUrl(), this); dirop->setView( KFile::Tree ); dirop->setupMenu( KDirOperator::SortActions | KDirOperator::FileActions | KDirOperator::NavActions | KDirOperator::ViewActions ); connect(dirop, &KDirOperator::urlEntered, this, &FileManager::updateNav); connect(dirop, &KDirOperator::contextMenuAboutToShow, this, &FileManager::fillContextMenu); l->addWidget(dirop); connect( dirop, &KDirOperator::fileSelected, this, &FileManager::openFile ); setFocusProxy(dirop); // includes some actions, but not hooked into the shortcut dialog atm m_actionCollection = new KActionCollection(this); m_actionCollection->addAssociatedWidget(this); setupActions(); // Connect the bookmark handler connect(m_bookmarkHandler, &BookmarkHandler::openUrl, this, &FileManager::gotoUrl); connect(m_bookmarkHandler, &BookmarkHandler::openUrl, this, &FileManager::updateNav); } FileManager::~FileManager() { KConfigGroup cg = KDevelop::ICore::self()->activeSession()->config()->group( "Filesystem" ); cg.writeEntry( "LastLocation", urlnav->locationUrl() ); cg.sync(); } void FileManager::fillContextMenu(KFileItem item, QMenu* menu) { foreach(QAction* a, contextActions){ if(menu->actions().contains(a)){ menu->removeAction(a); } } contextActions.clear(); contextActions.append(menu->addSeparator()); menu->addAction(newFileAction); contextActions.append(newFileAction); KDevelop::FileContext context(QList() << item.url()); QList extensions = KDevelop::ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); KDevelop::ContextMenuExtension::populateMenu(menu, extensions); QMenu* tmpMenu = new QMenu(); KDevelop::ContextMenuExtension::populateMenu(tmpMenu, extensions); contextActions.append(tmpMenu->actions()); delete tmpMenu; } void FileManager::openFile(const KFileItem& file) { KDevelop::IOpenWith::openFiles(QList() << file.url()); } void FileManager::gotoUrl( const QUrl& url ) { dirop->setUrl( url, true ); } void FileManager::updateNav( const QUrl& url ) { urlnav->setLocationUrl( url ); } void FileManager::setupActions() { KActionMenu *acmBookmarks = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), this); acmBookmarks->setDelayed(false); m_bookmarkHandler = new BookmarkHandler(this, acmBookmarks->menu()); acmBookmarks->setShortcutContext(Qt::WidgetWithChildrenShortcut); QAction* action = new QAction(this); action->setShortcutContext(Qt::WidgetWithChildrenShortcut); action->setText(i18n("Current Document Directory")); action->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); connect(action, &QAction::triggered, this, &FileManager::syncCurrentDocumentDirectory); tbActions << (dirop->actionCollection()->action(QStringLiteral("back"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("up"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("home"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("forward"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("reload"))); tbActions << acmBookmarks; tbActions << action; tbActions << (dirop->actionCollection()->action(QStringLiteral("sorting menu"))); tbActions << (dirop->actionCollection()->action(QStringLiteral("show hidden"))); newFileAction = new QAction(this); newFileAction->setText(i18n("New File...")); newFileAction->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); connect(newFileAction, &QAction::triggered, this, &FileManager::createNewFile); } void FileManager::createNewFile() { QUrl destUrl = QFileDialog::getSaveFileUrl(KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Create New File")); if (destUrl.isEmpty()) { return; } KJob* job = KIO::storedPut(QByteArray(), destUrl, -1); KJobWidgets::setWindow(job, this); connect(job, &KJob::result, this, &FileManager::fileCreated); } void FileManager::fileCreated(KJob* job) { auto transferJob = qobject_cast(job); Q_ASSERT(transferJob); if (!transferJob->error()) { KDevelop::ICore::self()->documentController()->openDocument( transferJob->url() ); } else { KMessageBox::error(KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Unable to create file '%1'", transferJob->url().toDisplayString(QUrl::PreferLocalFile))); } } void FileManager::syncCurrentDocumentDirectory() { if( KDevelop::IDocument* activeDoc = KDevelop::ICore::self()->documentController()->activeDocument() ) updateNav( activeDoc->url().adjusted(QUrl::RemoveFilename) ); } QList FileManager::toolBarActions() const { return tbActions; } KActionCollection* FileManager::actionCollection() const { return m_actionCollection; } KDirOperator* FileManager::dirOperator() const { return dirop; } KDevFileManagerPlugin* FileManager::plugin() const { return m_plugin; } #include "moc_filemanager.cpp" diff --git a/plugins/filemanager/kdevfilemanagerplugin.cpp b/plugins/filemanager/kdevfilemanagerplugin.cpp index 8f10124b0..04d14f930 100644 --- a/plugins/filemanager/kdevfilemanagerplugin.cpp +++ b/plugins/filemanager/kdevfilemanagerplugin.cpp @@ -1,94 +1,92 @@ /*************************************************************************** * 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: explicit KDevFileManagerViewFactory(KDevFileManagerPlugin *plugin): m_plugin(plugin) {} 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/filemanager/kdevfilemanagerplugin.h b/plugins/filemanager/kdevfilemanagerplugin.h index 9e93ac729..f88384e2a 100644 --- a/plugins/filemanager/kdevfilemanagerplugin.h +++ b/plugins/filemanager/kdevfilemanagerplugin.h @@ -1,43 +1,43 @@ /*************************************************************************** * Copyright 2006 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 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_KDEVFILEMANAGERPLUGIN_H #define KDEVPLATFORM_PLUGIN_KDEVFILEMANAGERPLUGIN_H #include -#include +#include class FileManager; class KDevFileManagerPlugin: public KDevelop::IPlugin { Q_OBJECT public: explicit KDevFileManagerPlugin(QObject *parent, const QVariantList &args = QVariantList() ); ~KDevFileManagerPlugin() override; void unload() override; private slots: void init(); private: class KDevFileManagerViewFactory *m_factory; }; #endif diff --git a/plugins/filetemplates/templateoptionspage.cpp b/plugins/filetemplates/templateoptionspage.cpp index 0f8e1cd86..d66361774 100644 --- a/plugins/filetemplates/templateoptionspage.cpp +++ b/plugins/filetemplates/templateoptionspage.cpp @@ -1,175 +1,174 @@ /* 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 #include #include using namespace KDevelop; class KDevelop::TemplateOptionsPagePrivate { public: QVector entries; QHash controls; QHash typeProperties; QWidget *firstEditWidget; QList groupBoxes; }; TemplateOptionsPage::TemplateOptionsPage(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new TemplateOptionsPagePrivate) { d->firstEditWidget = nullptr; d->typeProperties.insert(QStringLiteral("String"), "text"); d->typeProperties.insert(QStringLiteral("Enum"), "currentText"); d->typeProperties.insert(QStringLiteral("Int"), "value"); d->typeProperties.insert(QStringLiteral("Bool"), "checked"); } TemplateOptionsPage::~TemplateOptionsPage() { delete d; } void TemplateOptionsPage::load(const SourceFileTemplate& fileTemplate, TemplateRenderer* renderer) { // TODO: keep any old changed values, as it comes by surprise to have them lost // when going back and forward // clear anything as there is on reentering the page d->entries.clear(); d->controls.clear(); // clear any old option group boxes & the base layout d->firstEditWidget = nullptr; qDeleteAll(d->groupBoxes); d->groupBoxes.clear(); delete layout(); QVBoxLayout* layout = new QVBoxLayout(); for (const auto& optionGroup : fileTemplate.customOptions(renderer)) { QGroupBox* box = new QGroupBox(this); d->groupBoxes.append(box); box->setTitle(optionGroup.name); QFormLayout* formLayout = new QFormLayout; d->entries << optionGroup.options; for (const auto& entry : optionGroup.options) { QWidget* control = nullptr; const QString type = entry.type; if (type == QLatin1String("String")) { control = new QLineEdit(entry.value.toString(), box); } else if (type == QLatin1String("Enum")) { auto input = new QComboBox(box); input->addItems(entry.values); input->setCurrentText(entry.value.toString()); control = input; } 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(box); checkBox->setCheckState(checked ? Qt::Checked : Qt::Unchecked); control = checkBox; } else { qCDebug(PLUGIN_FILETEMPLATES) << "Unrecognized option type" << entry.type; } if (control) { const QString entryLabelText = i18n("%1:", entry.label); QLabel* label = new QLabel(entryLabelText, box); formLayout->addRow(label, control); d->controls.insert(entry.name, control); if (d->firstEditWidget == nullptr) { d->firstEditWidget = control; } } } box->setLayout(formLayout); layout->addWidget(box); } layout->addStretch(); 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; } void TemplateOptionsPage::setFocusToFirstEditWidget() { if (d->firstEditWidget) { d->firstEditWidget->setFocus(); } } diff --git a/plugins/filetemplates/templatepreview.cpp b/plugins/filetemplates/templatepreview.cpp index a9d706a9e..21e5af60d 100644 --- a/plugins/filetemplates/templatepreview.cpp +++ b/plugins/filetemplates/templatepreview.cpp @@ -1,153 +1,152 @@ /* * 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 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 "templatepreview.h" #include #include #include #include -#include #include #include #include #include #include #include using namespace KDevelop; TemplatePreviewRenderer::TemplatePreviewRenderer() { QVariantHash vars; vars[QStringLiteral("name")] = "Example"; vars[QStringLiteral("license")] = "This file is licensed under the ExampleLicense 3.0"; // TODO: More variables, preferably the ones from TemplateClassGenerator VariableDescriptionList publicMembers; VariableDescriptionList protectedMembers; VariableDescriptionList privateMembers; publicMembers << VariableDescription(QStringLiteral("int"), QStringLiteral("number")); protectedMembers << VariableDescription(QStringLiteral("string"), QStringLiteral("name")); privateMembers << VariableDescription(QStringLiteral("float"), QStringLiteral("variable")); vars[QStringLiteral("members")] = CodeDescription::toVariantList(publicMembers + protectedMembers + privateMembers); vars[QStringLiteral("public_members")] = CodeDescription::toVariantList(publicMembers); vars[QStringLiteral("protected_members")] = CodeDescription::toVariantList(protectedMembers); vars[QStringLiteral("private_members")] = CodeDescription::toVariantList(privateMembers); FunctionDescriptionList publicFunctions; FunctionDescriptionList protectedFunctions; FunctionDescriptionList privateFunctions; FunctionDescription complexFunction(QStringLiteral("doBar"), VariableDescriptionList(), VariableDescriptionList()); complexFunction.arguments << VariableDescription(QStringLiteral("bool"), QStringLiteral("really")); complexFunction.arguments << VariableDescription(QStringLiteral("int"), QStringLiteral("howMuch")); complexFunction.returnArguments << VariableDescription(QStringLiteral("double"), QString()); publicFunctions << FunctionDescription(QStringLiteral("doFoo"), VariableDescriptionList(), VariableDescriptionList()); publicFunctions << complexFunction; protectedFunctions << FunctionDescription(QStringLiteral("onUpdate"), VariableDescriptionList(), VariableDescriptionList()); vars[QStringLiteral("functions")] = CodeDescription::toVariantList(publicFunctions + protectedFunctions + privateFunctions); vars[QStringLiteral("public_functions")] = CodeDescription::toVariantList(publicFunctions); vars[QStringLiteral("protected_functions")] = CodeDescription::toVariantList(protectedFunctions); vars[QStringLiteral("private_functions")] = CodeDescription::toVariantList(privateFunctions); vars[QStringLiteral("testCases")] = QStringList { QStringLiteral("doFoo"), QStringLiteral("doBar"), QStringLiteral("doMore") }; addVariables(vars); } TemplatePreviewRenderer::~TemplatePreviewRenderer() { } TemplatePreview::TemplatePreview(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) { m_variables[QStringLiteral("APPNAME")] = QStringLiteral("Example"); m_variables[QStringLiteral("APPNAMELC")] = QStringLiteral("example"); m_variables[QStringLiteral("APPNAMEUC")] = QStringLiteral("EXAMPLE"); m_variables[QStringLiteral("APPNAMEID")] = QStringLiteral("Example"); m_variables[QStringLiteral("PROJECTDIR")] = QDir::homePath() + "/projects/ExampleProjectDir"; m_variables[QStringLiteral("PROJECTDIRNAME")] = QStringLiteral("ExampleProjectDir"); m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = QStringLiteral("kdevgit"); KTextEditor::Document* doc = KTextEditor::Editor::instance()->createDocument(this); m_preview.reset(doc); m_preview->setReadWrite(false); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); m_view = m_preview->createView(this); m_view->setStatusBarEnabled(false); if (KTextEditor::ConfigInterface* config = qobject_cast(m_view)) { config->setConfigValue(QStringLiteral("icon-bar"), false); config->setConfigValue(QStringLiteral("folding-bar"), false); config->setConfigValue(QStringLiteral("line-numbers"), false); config->setConfigValue(QStringLiteral("dynamic-word-wrap"), true); } layout->addWidget(m_view); } TemplatePreview::~TemplatePreview() { } QString TemplatePreview::setText(const QString& text, bool isProject, TemplateRenderer::EmptyLinesPolicy policy) { QString rendered; QString errorString; if (!text.isEmpty()) { if (isProject) { rendered = KMacroExpander::expandMacros(text, m_variables); } else { TemplatePreviewRenderer renderer; renderer.setEmptyLinesPolicy(policy); rendered = renderer.render(text); errorString = renderer.errorString(); } } m_preview->setReadWrite(true); m_preview->setText(rendered); m_view->setCursorPosition(KTextEditor::Cursor(0, 0)); m_preview->setReadWrite(false); return errorString; } KTextEditor::Document* TemplatePreview::document() const { return m_preview.data(); } diff --git a/plugins/filetemplates/templatepreviewtoolview.cpp b/plugins/filetemplates/templatepreviewtoolview.cpp index f7ee90ea0..0f43a1808 100644 --- a/plugins/filetemplates/templatepreviewtoolview.cpp +++ b/plugins/filetemplates/templatepreviewtoolview.cpp @@ -1,181 +1,178 @@ /* * 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(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() : 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.h b/plugins/filetemplates/templateselectionpage.h index 5b4667578..2449ddea5 100644 --- a/plugins/filetemplates/templateselectionpage.h +++ b/plugins/filetemplates/templateselectionpage.h @@ -1,69 +1,66 @@ /* 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. */ #ifndef KDEVPLATFORM_PLUGIN_TEMPLATESELECTIONPAGE_H #define KDEVPLATFORM_PLUGIN_TEMPLATESELECTIONPAGE_H #include - -class QModelIndex; - namespace KDevelop { class TemplateClassAssistant; /** * An assistant page for selecting a class template **/ class TemplateSelectionPage : public QWidget { Q_OBJECT Q_PROPERTY(QString selectedTemplate READ selectedTemplate) public: explicit TemplateSelectionPage (TemplateClassAssistant* parent, Qt::WindowFlags f = nullptr); ~TemplateSelectionPage() override; /** * @property selectedTemplate * * The class template, selected by the user. * This property stores the full path to the template description (.desktop) file **/ QString selectedTemplate() const; QSize minimumSizeHint() const override; public Q_SLOTS: /** * Saves the selected template setting into the current project's configuration. * * If the assistant's base URL does not point to any project, this function does nothing. */ void saveConfig(); private: class TemplateSelectionPagePrivate* const d; }; } #endif // KDEVPLATFORM_PLUGIN_TEMPLATESELECTIONPAGE_H diff --git a/plugins/filetemplates/testcasespage.cpp b/plugins/filetemplates/testcasespage.cpp index 2ea703ffe..ef06df9ad 100644 --- a/plugins/filetemplates/testcasespage.cpp +++ b/plugins/filetemplates/testcasespage.cpp @@ -1,90 +1,88 @@ /* * 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 "testcasespage.h" #include "ui_testcases.h" #include #include -#include - using namespace KDevelop; class KDevelop::TestCasesPagePrivate { public: Ui::TestCasesPage* ui; }; TestCasesPage::TestCasesPage(QWidget* parent, Qt::WindowFlags f) : QWidget (parent, f) , d(new TestCasesPagePrivate) { d->ui = new Ui::TestCasesPage(); d->ui->setupUi(this); d->ui->testCasesLabel->setBuddy(d->ui->keditlistwidget->lineEdit()); #if KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5,32,0) // workaround for KEditListWidget bug: // ensure keyboard focus is returned to edit line connect(d->ui->keditlistwidget, &KEditListWidget::added, d->ui->keditlistwidget->lineEdit(), static_cast(&QWidget::setFocus)); connect(d->ui->keditlistwidget, &KEditListWidget::removed, d->ui->keditlistwidget->lineEdit(), static_cast(&QWidget::setFocus)); #endif connect(d->ui->identifierLineEdit, &QLineEdit::textChanged, this, &TestCasesPage::identifierChanged); } TestCasesPage::~TestCasesPage() { delete d->ui; delete d; } QString TestCasesPage::name() const { return d->ui->identifierLineEdit->text(); } void TestCasesPage::setTestCases(const QStringList& testCases) { d->ui->keditlistwidget->setItems(testCases); } QStringList TestCasesPage::testCases() const { return d->ui->keditlistwidget->items(); } void TestCasesPage::setFocusToFirstEditWidget() { d->ui->identifierLineEdit->setFocus(); } void TestCasesPage::identifierChanged(const QString& identifier) { emit isValid(!identifier.isEmpty()); } diff --git a/plugins/filetemplates/tests/data/testgenerationtest/templates/test_qtestlib/class.h b/plugins/filetemplates/tests/data/testgenerationtest/templates/test_qtestlib/class.h index 49c00aaba..92e0dc352 100644 --- a/plugins/filetemplates/tests/data/testgenerationtest/templates/test_qtestlib/class.h +++ b/plugins/filetemplates/tests/data/testgenerationtest/templates/test_qtestlib/class.h @@ -1,33 +1,33 @@ {% load kdev_filters %} /* {{ license|lines_prepend:" * " }} */ #ifndef {{ name|upper }}_H #define {{ name|upper }}_H -#include +#include class {{ name }} : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); {% for case in testCases %} void {{ case }}(); {% endfor %} }; #endif // {{ name|upper }}_H \ No newline at end of file diff --git a/plugins/filetemplates/tests/expected/testname.h b/plugins/filetemplates/tests/expected/testname.h index c07fc6bed..066b4a6bb 100644 --- a/plugins/filetemplates/tests/expected/testname.h +++ b/plugins/filetemplates/tests/expected/testname.h @@ -1,26 +1,26 @@ /* * Test license header * In two lines */ #ifndef TESTNAME_H #define TESTNAME_H -#include +#include class TestName : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void firstTestCase(); void secondTestCase(); void thirdTestCase(); }; #endif // TESTNAME_H diff --git a/plugins/filetemplates/tests/test_generationtest.cpp b/plugins/filetemplates/tests/test_generationtest.cpp index 666ef2f7d..c06e165ee 100644 --- a/plugins/filetemplates/tests/test_generationtest.cpp +++ b/plugins/filetemplates/tests/test_generationtest.cpp @@ -1,128 +1,130 @@ /* * This file is part of KDevelop * * 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_generationtest.h" #include "tests_config.h" #include #include #include #include #include #include #include +#include + using namespace KDevelop; #define COMPARE_FILES(name) \ do { \ QFile actualFile(Path(Path(baseUrl), QStringLiteral(name)).toLocalFile()); \ QVERIFY(actualFile.open(QIODevice::ReadOnly)); \ QFile expectedFile(QStringLiteral(TESTS_EXPECTED_DIR "/" name)); \ QVERIFY(expectedFile.open(QIODevice::ReadOnly)); \ QCOMPARE(actualFile.size(), expectedFile.size()); \ QCOMPARE(QString(actualFile.readAll()), QString(expectedFile.readAll())); \ } while(0) void TestGenerationTest::initTestCase() { QByteArray xdgData = qgetenv("XDG_DATA_DIRS"); xdgData.prepend(TESTS_DATA_DIR ":"); bool addedDir = qputenv("XDG_DATA_DIRS", xdgData); QVERIFY(addedDir); // avoid translated desktop entries, tests use untranslated strings QLocale::setDefault(QLocale::c()); AutoTestShell::init(); TestCore::initialize (Core::NoUi); TemplatesModel model(QStringLiteral("testgenerationtest")); model.refresh(); renderer = new TemplateRenderer; renderer->setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); renderer->addVariable(QStringLiteral("name"), "TestName"); renderer->addVariable(QStringLiteral("license"), "Test license header\nIn two lines"); QStringList testCases; testCases << QStringLiteral("firstTestCase"); testCases << QStringLiteral("secondTestCase"); testCases << QStringLiteral("thirdTestCase"); renderer->addVariable(QStringLiteral("testCases"), testCases); } void TestGenerationTest::cleanupTestCase() { delete renderer; TestCore::shutdown(); } void TestGenerationTest::init() { dir.reset(new QTemporaryDir); baseUrl = QUrl::fromLocalFile(dir->path()); } void TestGenerationTest::yamlTemplate() { QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("testgenerationtest/template_descriptions/test_yaml2.desktop")); QVERIFY(!description.isEmpty()); SourceFileTemplate file; file.addAdditionalSearchLocation(QStringLiteral(TESTS_DATA_DIR "/testgenerationtest/templates")); file.setTemplateDescription(description); QCOMPARE(file.name(), QStringLiteral("Testing YAML Template")); DocumentChangeSet changes = renderer->renderFileTemplate(file, baseUrl, urls(file)); changes.applyAllChanges(); COMPARE_FILES("testname.yaml"); } void TestGenerationTest::cppTemplate() { QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("testgenerationtest/template_descriptions/test_qtestlib.desktop")); QVERIFY(!description.isEmpty()); SourceFileTemplate file; file.addAdditionalSearchLocation(QStringLiteral(TESTS_DATA_DIR "/testgenerationtest/templates")); file.setTemplateDescription(description); QCOMPARE(file.name(), QStringLiteral("Testing C++ Template")); DocumentChangeSet changes = renderer->renderFileTemplate(file, baseUrl, urls(file)); changes.applyAllChanges(); COMPARE_FILES("testname.h"); COMPARE_FILES("testname.cpp"); } QHash< QString, QUrl > TestGenerationTest::urls (const SourceFileTemplate& file) { QHash ret; foreach (const SourceFileTemplate::OutputFile& output, file.outputFiles()) { QUrl url = Path(Path(baseUrl), renderer->render(output.outputName).toLower()).toUrl(); ret.insert(output.identifier, url); } return ret; } QTEST_GUILESS_MAIN(TestGenerationTest); diff --git a/plugins/filetemplates/tests/test_generationtest.h b/plugins/filetemplates/tests/test_generationtest.h index 53211f15e..849dd02d8 100644 --- a/plugins/filetemplates/tests/test_generationtest.h +++ b/plugins/filetemplates/tests/test_generationtest.h @@ -1,38 +1,38 @@ /* * */ #ifndef KDEVPLATFORM_PLUGIN_TEST_GENERATIONTEST_H #define KDEVPLATFORM_PLUGIN_TEST_GENERATIONTEST_H -#include +#include #include #include namespace KDevelop { class TemplateRenderer; class SourceFileTemplate; } class TestGenerationTest : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void init(); void yamlTemplate(); void cppTemplate(); private: KDevelop::TemplateRenderer* renderer; QScopedPointer dir; QUrl baseUrl; QHash urls(const KDevelop::SourceFileTemplate& file); }; #endif // KDEVPLATFORM_PLUGIN_TEST_GENERATIONTEST_H diff --git a/plugins/genericprojectmanager/genericmanager.cpp b/plugins/genericprojectmanager/genericmanager.cpp index 748cce7a1..e6e5d9236 100644 --- a/plugins/genericprojectmanager/genericmanager.cpp +++ b/plugins/genericprojectmanager/genericmanager.cpp @@ -1,36 +1,33 @@ /* This file is part of KDevelop CopyRight 2010 Milian Wolff Copyright 2004 Roberto Raggi 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 "genericmanager.h" -#include #include -#include -#include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(GenericSupportFactory, "kdevgenericmanager.json", registerPlugin();) GenericProjectManager::GenericProjectManager( QObject* parent, const QVariantList& args ) : AbstractFileManagerPlugin( QStringLiteral( "kdevgenericmanager" ), parent, args ) { } #include "genericmanager.moc" diff --git a/plugins/git/gitplugin.cpp b/plugins/git/gitplugin.cpp index 20c780915..6d65958f8 100644 --- a/plugins/git/gitplugin.cpp +++ b/plugins/git/gitplugin.cpp @@ -1,1560 +1,1561 @@ /*************************************************************************** * 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 #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; } 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, 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); QFile dotGitPotentialFile(dir.filePath(".git")); // if .git is a file, we may be in a git worktree QFileInfo dotGitPotentialFileInfo(dotGitPotentialFile); if (!dotGitPotentialFileInfo.isDir() && dotGitPotentialFile.exists()) { QString gitWorktreeFileContent; if (dotGitPotentialFile.open(QFile::ReadOnly)) { // the content should be gitdir: /path/to/the/.git/worktree gitWorktreeFileContent = QString::fromUtf8(dotGitPotentialFile.readAll()); dotGitPotentialFile.close(); } else { return false; } const auto items = gitWorktreeFileContent.split(' '); if (items.size() == 2 && items.at(0) == "gitdir:") { qCDebug(PLUGIN_GIT) << "we are in a git worktree" << items.at(1); return true; } } return dir.exists(QStringLiteral(".git/HEAD")); } bool GitPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { if (remoteLocation.isLocalFile()) { QFileInfo fileInfo(remoteLocation.toLocalFile()); if (fileInfo.isDir()) { QDir dir(fileInfo.filePath()); if (dir.exists(QStringLiteral(".git/HEAD"))) { return true; } // TODO: check also for bare repo } } else { const QString scheme = remoteLocation.scheme(); if (scheme == QLatin1String("git")) { return true; } // heuristic check, anything better we can do here without talking to server? if ((scheme == QLatin1String("http") || scheme == QLatin1String("https")) && remoteLocation.path().endsWith(QLatin1String(".git"))) { return true; } } return false; } 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 (dstRevision.revisionType() == VcsRevision::Special && dstRevision.specialType() == VcsRevision::Working) { if (srcRevision.revisionType() == VcsRevision::Special && srcRevision.specialType() == VcsRevision::Base) { *job << "HEAD"; } else { *job << "--cached" << srcRevision.revisionValue().toString(); } } 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 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 = 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(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) { if (job->error() == 0) { m_status = JobSucceeded; setError(NoError); } else { m_status = JobFailed; setError(UserDefinedError); } 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: explicit 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/gitplugin.h b/plugins/git/gitplugin.h index e8b3191a7..f5b73466c 100644 --- a/plugins/git/gitplugin.h +++ b/plugins/git/gitplugin.h @@ -1,225 +1,223 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2009 Hugo Parente Lima * * * * 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 . * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GIT_PLUGIN_H #define KDEVPLATFORM_PLUGIN_GIT_PLUGIN_H #include #include #include -#include -#include #include #include #include class KDirWatch; class QDir; namespace KDevelop { class VcsJob; class VcsRevision; } class StandardJob : public KDevelop::VcsJob { Q_OBJECT public: StandardJob(KDevelop::IPlugin* parent, KJob* job, OutputJobVerbosity verbosity); QVariant fetchResults() override { return QVariant(); } void start() override; JobStatus status() const override { return m_status; } KDevelop::IPlugin* vcsPlugin() const override { return m_plugin; } public slots: void result(KJob*); private: KJob* m_job; KDevelop::IPlugin* m_plugin; JobStatus m_status; }; /** * This is the main class of KDevelop's Git plugin. * * It implements the DVCS dependent things not implemented in KDevelop::DistributedVersionControlPlugin * @author Evgeniy Ivanov */ class GitPlugin: public KDevelop::DistributedVersionControlPlugin, public KDevelop::IContentAwareVersionControl { Q_OBJECT Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::IDistributedVersionControl KDevelop::IContentAwareVersionControl) friend class GitInitTest; public: explicit GitPlugin(QObject *parent, const QVariantList & args = QVariantList() ); ~GitPlugin() override; QString name() const override; bool isValidRemoteRepositoryUrl(const QUrl& remoteLocation) override; bool isVersionControlled(const QUrl &path) override; KDevelop::VcsJob* copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) override; KDevelop::VcsJob* move(const QUrl& localLocationSrc, const QUrl& localLocationDst) override; //TODO KDevelop::VcsJob* pull(const KDevelop::VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation) override; KDevelop::VcsJob* push(const QUrl& localRepositoryLocation, const KDevelop::VcsLocation& localOrRepoLocationDst) override; KDevelop::VcsJob* repositoryLocation(const QUrl& localLocation) override; KDevelop::VcsJob* resolve(const QList& localLocations, RecursionMode recursion) override; KDevelop::VcsJob* update(const QList& localLocations, const KDevelop::VcsRevision& rev, RecursionMode recursion) override; KDevelop::VcsLocationWidget* vcsLocation(QWidget* parent) const override; void setupCommitMessageEditor(const QUrl& localLocation, KTextEdit* editor) const override; //End of KDevelop::VcsJob* add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* createWorkingCopy(const KDevelop::VcsLocation & localOrRepoLocationSrc, const QUrl& localRepositoryRoot, KDevelop::IBasicVersionControl::RecursionMode) override; KDevelop::VcsJob* remove(const QList& files) override; KDevelop::VcsJob* status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type, RecursionMode recursion) override; KDevelop::VcsJob* log( const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long limit) override; KDevelop::VcsJob* log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) override; KDevelop::VcsJob* annotate(const QUrl &localLocation, const KDevelop::VcsRevision &rev) override; KDevelop::VcsJob* revert(const QList& localLocations, RecursionMode recursion) override; // Begin: KDevelop::IDistributedVersionControl KDevelop::VcsJob* init(const QUrl & directory) override; // Branch management KDevelop::VcsJob* tag(const QUrl& repository, const QString& commitMessage, const KDevelop::VcsRevision& rev, const QString& tagName) override; KDevelop::VcsJob* branch(const QUrl& repository, const KDevelop::VcsRevision& rev, const QString& branchName) override; KDevelop::VcsJob* branches(const QUrl& repository) override; KDevelop::VcsJob* currentBranch(const QUrl& repository) override; KDevelop::VcsJob* deleteBranch(const QUrl& repository, const QString& branchName) override; KDevelop::VcsJob* switchBranch(const QUrl& repository, const QString& branchName) override; KDevelop::VcsJob* renameBranch(const QUrl& repository, const QString& oldBranchName, const QString& newBranchName) override; KDevelop::VcsJob* mergeBranch(const QUrl& repository, const QString& branchName) override; //graph helpers QList getAllCommits(const QString &repo) override; //used in log void parseLogOutput(const KDevelop::DVcsJob * job, QList& commits) const override; void additionalMenuEntries(QMenu* menu, const QList& urls) override; KDevelop::DVcsJob* gitStash(const QDir& repository, const QStringList& args, KDevelop::OutputJob::OutputJobVerbosity verbosity); bool hasStashes(const QDir& repository); bool hasModifications(const QDir& repository); bool hasModifications(const QDir& repo, const QUrl& file); void registerRepositoryForCurrentBranchChanges(const QUrl& repository) override; KDevelop::CheckInRepositoryJob* isInRepository(KTextEditor::Document* document) override; KDevelop::DVcsJob* setConfigOption(const QUrl& repository, const QString& key, const QString& value, bool global = false); QString readConfigOption(const QUrl& repository, const QString& key); // this indicates whether the diff() function will generate a diff (patch) which // includes the working copy directory name or not (in which case git diff is called // with --no-prefix). bool usePrefix() const { return m_usePrefix; } void setUsePrefix(bool p) { m_usePrefix = p; } protected: QUrl repositoryRoot(const QUrl& path); bool isValidDirectory(const QUrl &dirPath) override; KDevelop::DVcsJob* lsFiles(const QDir &repository, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose); KDevelop::DVcsJob* gitRevList(const QString &directory, const QStringList &args); KDevelop::DVcsJob* gitRevParse(const QString &repository, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Silent); private slots: void parseGitBlameOutput(KDevelop::DVcsJob *job); void parseGitLogOutput(KDevelop::DVcsJob *job); void parseGitDiffOutput(KDevelop::DVcsJob* job); void parseGitRepoLocationOutput(KDevelop::DVcsJob* job); void parseGitStatusOutput(KDevelop::DVcsJob* job); void parseGitStatusOutput_old(KDevelop::DVcsJob* job); void parseGitVersionOutput(KDevelop::DVcsJob* job); void parseGitBranchOutput(KDevelop::DVcsJob* job); void parseGitCurrentBranch(KDevelop::DVcsJob* job); void ctxPushStash(); void ctxPopStash(); void ctxStashManager(); void fileChanged(const QString& file); void delayedBranchChanged(); signals: void repositoryBranchChanged(const QUrl& repository); private: bool ensureValidGitIdentity(const QDir& dir); void addNotVersionedFiles(const QDir& dir, const QList& files); //commit dialog "main" helper QStringList getLsFiles(const QDir &directory, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity); KDevelop::DVcsJob* errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity); void initBranchHash(const QString &repo); static KDevelop::VcsStatusInfo::State messageToState(const QStringRef& ch); QList branchesShas; QList m_urls; /** Tells if it's older than 1.7.0 or not */ bool m_oldVersion; KDirWatch* m_watcher; QList m_branchesChange; bool m_usePrefix; }; QVariant runSynchronously(KDevelop::VcsJob* job); #endif diff --git a/plugins/git/tests/test_git.cpp b/plugins/git/tests/test_git.cpp index 01202aedf..24935e839 100644 --- a/plugins/git/tests/test_git.cpp +++ b/plugins/git/tests/test_git.cpp @@ -1,588 +1,588 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2007 Robert Gruber * * * * Adapted for Git * * 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) 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 "test_git.h" -#include +#include #include #include #include #include #include #include #include "../gitplugin.h" #define VERIFYJOB(j) \ do { QVERIFY(j); QVERIFY(j->exec()); QVERIFY((j)->status() == KDevelop::VcsJob::JobSucceeded); } while(0) inline QString tempDir() { return QDir::tempPath(); } inline QString gitTest_BaseDir() { return tempDir() + "/kdevGit_testdir/"; } inline QString gitTest_BaseDir2() { return tempDir() + "/kdevGit_testdir2/"; } inline QString gitRepo() { return gitTest_BaseDir() + ".git"; } inline QString gitSrcDir() { return gitTest_BaseDir() + "src/"; } inline QString gitTest_FileName() { return QStringLiteral("testfile"); } inline QString gitTest_FileName2() { return QStringLiteral("foo"); } inline QString gitTest_FileName3() { return QStringLiteral("bar"); } using namespace KDevelop; bool writeFile(const QString &path, const QString& content, QIODevice::OpenModeFlag mode = QIODevice::WriteOnly) { QFile f(path); if (!f.open(mode)) { return false; } QTextStream input(&f); input << content; return true; } void GitInitTest::initTestCase() { AutoTestShell::init({QStringLiteral("kdevgit")}); TestCore::initialize(); m_plugin = new GitPlugin(TestCore::self()); } void GitInitTest::cleanupTestCase() { delete m_plugin; TestCore::shutdown(); } void GitInitTest::init() { // Now create the basic directory structure QDir tmpdir(tempDir()); tmpdir.mkdir(gitTest_BaseDir()); tmpdir.mkdir(gitSrcDir()); tmpdir.mkdir(gitTest_BaseDir2()); } void GitInitTest::cleanup() { removeTempDirs(); } void GitInitTest::repoInit() { qDebug() << "Trying to init repo"; // make job that creates the local repository VcsJob* j = m_plugin->init(QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); //check if the CVSROOT directory in the new local repository exists now QVERIFY(QFileInfo::exists(gitRepo())); //check if isValidDirectory works QVERIFY(m_plugin->isValidDirectory(QUrl::fromLocalFile(gitTest_BaseDir()))); //and for non-git dir, I hope nobody has /tmp under git QVERIFY(!m_plugin->isValidDirectory(QUrl::fromLocalFile(QStringLiteral("/tmp")))); //we have nothing, so output should be empty DVcsJob * j2 = m_plugin->gitRevParse(gitRepo(), QStringList(QStringLiteral("--branches"))); QVERIFY(j2); QVERIFY(j2->exec()); QVERIFY(j2->output().isEmpty()); // Make sure to set the Git identity so unit tests don't depend on that auto j3 = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()), QStringLiteral("user.email"), QStringLiteral("me@example.com")); VERIFYJOB(j3); auto j4 = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()), QStringLiteral("user.name"), QStringLiteral("My Name")); VERIFYJOB(j4); } void GitInitTest::addFiles() { qDebug() << "Adding files to the repo"; //we start it after repoInit, so we still have empty git repo QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("HELLO WORLD"))); QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName2(), QStringLiteral("No, bar()!"))); //test git-status exitCode (see DVcsJob::setExitCode). VcsJob* j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); // /tmp/kdevGit_testdir/ and testfile j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName())); VERIFYJOB(j); QVERIFY(writeFile(gitSrcDir() + gitTest_FileName3(), QStringLiteral("No, foo()! It's bar()!"))); //test git-status exitCode again j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); //repository path without trailing slash and a file in a parent directory // /tmp/repo and /tmp/repo/src/bar j = m_plugin->add(QList() << QUrl::fromLocalFile(gitSrcDir() + gitTest_FileName3())); VERIFYJOB(j); //let's use absolute path, because it's used in ContextMenus j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName2())); VERIFYJOB(j); //Now let's create several files and try "git add file1 file2 file3" QStringList files = QStringList() << QStringLiteral("file1") << QStringLiteral("file2") << QStringLiteral("la la"); QList multipleFiles; foreach(const QString& file, files) { QVERIFY(writeFile(gitTest_BaseDir() + file, file)); multipleFiles << QUrl::fromLocalFile(gitTest_BaseDir() + file); } j = m_plugin->add(multipleFiles); VERIFYJOB(j); } void GitInitTest::commitFiles() { qDebug() << "Committing..."; //we start it after addFiles, so we just have to commit VcsJob* j = m_plugin->commit(QStringLiteral("Test commit"), QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); //test git-status exitCode one more time. j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); //since we committed the file to the "pure" repository, .git/refs/heads/master should exist //TODO: maybe other method should be used QString headRefName(gitRepo() + "/refs/heads/master"); QVERIFY(QFileInfo::exists(headRefName)); //Test the results of the "git add" DVcsJob* jobLs = new DVcsJob(gitTest_BaseDir(), m_plugin); *jobLs << "git" << "ls-tree" << "--name-only" << "-r" << "HEAD"; if (jobLs->exec() && jobLs->status() == KDevelop::VcsJob::JobSucceeded) { QStringList files = jobLs->output().split('\n'); QVERIFY(files.contains(gitTest_FileName())); QVERIFY(files.contains(gitTest_FileName2())); QVERIFY(files.contains("src/" + gitTest_FileName3())); } QString firstCommit; QFile headRef(headRefName); if (headRef.open(QIODevice::ReadOnly)) { QTextStream output(&headRef); output >> firstCommit; } headRef.close(); QVERIFY(!firstCommit.isEmpty()); qDebug() << "Committing one more time"; //let's try to change the file and test "git commit -a" QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("Just another HELLO WORLD\n"))); //add changes j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName())); VERIFYJOB(j); j = m_plugin->commit(QStringLiteral("KDevelop's Test commit2"), QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); QString secondCommit; if (headRef.open(QIODevice::ReadOnly)) { QTextStream output(&headRef); output >> secondCommit; } headRef.close(); QVERIFY(!secondCommit.isEmpty()); QVERIFY(firstCommit != secondCommit); } void GitInitTest::testInit() { repoInit(); } static QString runCommand(const QString& cmd, const QStringList& args) { QProcess proc; proc.setWorkingDirectory(gitTest_BaseDir()); proc.start(cmd, args); proc.waitForFinished(); return proc.readAllStandardOutput().trimmed(); } void GitInitTest::testReadAndSetConfigOption() { repoInit(); { qDebug() << "read non-existing config option"; QString nameFromPlugin = m_plugin->readConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()), QStringLiteral("notexisting.asdads")); QVERIFY(nameFromPlugin.isEmpty()); } { qDebug() << "write user.name = \"John Tester\""; auto job = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()), QStringLiteral("user.name"), QStringLiteral("John Tester")); VERIFYJOB(job); const auto name = runCommand("git", {"config", "--get", QStringLiteral("user.name")}); QCOMPARE(name, QStringLiteral("John Tester")); } { qDebug() << "read user.name"; const QString nameFromPlugin = m_plugin->readConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()), QStringLiteral("user.name")); QCOMPARE(nameFromPlugin, QStringLiteral("John Tester")); const auto name = runCommand("git", {"config", "--get", QStringLiteral("user.name")}); QCOMPARE(name, QStringLiteral("John Tester")); } } void GitInitTest::testAdd() { repoInit(); addFiles(); } void GitInitTest::testCommit() { repoInit(); addFiles(); commitFiles(); } void GitInitTest::testBranch(const QString& newBranch) { //Already tested, so I assume that it works const QUrl baseUrl = QUrl::fromLocalFile(gitTest_BaseDir()); QString oldBranch = runSynchronously(m_plugin->currentBranch(baseUrl)).toString(); VcsRevision rev; rev.setRevisionValue(oldBranch, KDevelop::VcsRevision::GlobalNumber); VcsJob* j = m_plugin->branch(baseUrl, rev, newBranch); VERIFYJOB(j); QVERIFY(runSynchronously(m_plugin->branches(baseUrl)).toStringList().contains(newBranch)); // switch branch j = m_plugin->switchBranch(baseUrl, newBranch); VERIFYJOB(j); QCOMPARE(runSynchronously(m_plugin->currentBranch(baseUrl)).toString(), newBranch); // get into detached head state j = m_plugin->switchBranch(baseUrl, QStringLiteral("HEAD~1")); VERIFYJOB(j); QCOMPARE(runSynchronously(m_plugin->currentBranch(baseUrl)).toString(), QString()); // switch back j = m_plugin->switchBranch(baseUrl, newBranch); VERIFYJOB(j); QCOMPARE(runSynchronously(m_plugin->currentBranch(baseUrl)).toString(), newBranch); j = m_plugin->deleteBranch(baseUrl, oldBranch); VERIFYJOB(j); QVERIFY(!runSynchronously(m_plugin->branches(baseUrl)).toStringList().contains(oldBranch)); } void GitInitTest::testMerge() { const QString branchNames[] = {QStringLiteral("aBranchToBeMergedIntoMaster"), QStringLiteral("AnotherBranch")}; const QString files[]={QStringLiteral("First File to Appear after merging"),QStringLiteral("Second File to Appear after merging"), QStringLiteral("Another_File.txt")}; const QString content=QStringLiteral("Testing merge."); repoInit(); addFiles(); commitFiles(); const QUrl baseUrl = QUrl::fromLocalFile(gitTest_BaseDir()); VcsJob* j = m_plugin->branches(baseUrl); VERIFYJOB(j); QString curBranch = runSynchronously(m_plugin->currentBranch(baseUrl)).toString(); QCOMPARE(curBranch, QStringLiteral("master")); VcsRevision rev; rev.setRevisionValue("master", KDevelop::VcsRevision::GlobalNumber); j = m_plugin->branch(baseUrl, rev, branchNames[0]); VERIFYJOB(j); qDebug() << "Adding files to the new branch"; //we start it after repoInit, so we still have empty git repo QVERIFY(writeFile(gitTest_BaseDir() + files[0], content)); QVERIFY(writeFile(gitTest_BaseDir() + files[1], content)); QList listOfAddedFiles{QUrl::fromLocalFile(gitTest_BaseDir() + files[0]), QUrl::fromLocalFile(gitTest_BaseDir() + files[1])}; j = m_plugin->add(listOfAddedFiles); VERIFYJOB(j); j = m_plugin->commit("Commiting to the new branch", QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); j = m_plugin->switchBranch(baseUrl, QStringLiteral("master")); VERIFYJOB(j); j = m_plugin->mergeBranch(baseUrl, branchNames[0]); VERIFYJOB(j); auto jobLs = new DVcsJob(gitTest_BaseDir(), m_plugin); *jobLs << "git" << "ls-tree" << "--name-only" << "-r" << "HEAD" ; if (jobLs->exec() && jobLs->status() == KDevelop::VcsJob::JobSucceeded) { QStringList files = jobLs->output().split('\n'); qDebug() << "Files in this Branch: " << files; QVERIFY(files.contains(files[0])); QVERIFY(files.contains(files[1])); } //Testing one more time. j = m_plugin->switchBranch(baseUrl, branchNames[0]); VERIFYJOB(j); rev.setRevisionValue(branchNames[0], KDevelop::VcsRevision::GlobalNumber); j = m_plugin->branch(baseUrl, rev, branchNames[1]); VERIFYJOB(j); QVERIFY(writeFile(gitTest_BaseDir() + files[2], content)); j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + files[2])); VERIFYJOB(j); j = m_plugin->commit(QStringLiteral("Commiting to AnotherBranch"), QList() << baseUrl); VERIFYJOB(j); j = m_plugin->switchBranch(baseUrl, branchNames[0]); VERIFYJOB(j); j = m_plugin->mergeBranch(baseUrl, branchNames[1]); VERIFYJOB(j); qDebug() << j->errorString() ; jobLs = new DVcsJob(gitTest_BaseDir(), m_plugin); *jobLs << "git" << "ls-tree" << "--name-only" << "-r" << "HEAD" ; if (jobLs->exec() && jobLs->status() == KDevelop::VcsJob::JobSucceeded) { QStringList files = jobLs->output().split('\n'); QVERIFY(files.contains(files[2])); qDebug() << "Files in this Branch: " << files; } j = m_plugin->switchBranch(baseUrl, QStringLiteral("master")); VERIFYJOB(j); j = m_plugin->mergeBranch(baseUrl, branchNames[1]); VERIFYJOB(j); qDebug() << j->errorString() ; jobLs = new DVcsJob(gitTest_BaseDir(), m_plugin); *jobLs << "git" << "ls-tree" << "--name-only" << "-r" << "HEAD" ; if (jobLs->exec() && jobLs->status() == KDevelop::VcsJob::JobSucceeded) { QStringList files = jobLs->output().split('\n'); QVERIFY(files.contains(files[2])); qDebug() << "Files in this Branch: " << files; } } void GitInitTest::testBranching() { repoInit(); addFiles(); commitFiles(); const QUrl baseUrl = QUrl::fromLocalFile(gitTest_BaseDir()); VcsJob* j = m_plugin->branches(baseUrl); VERIFYJOB(j); QString curBranch = runSynchronously(m_plugin->currentBranch(baseUrl)).toString(); QCOMPARE(curBranch, QStringLiteral("master")); testBranch(QStringLiteral("new")); testBranch(QStringLiteral("averylongbranchnamejusttotestlongnames")); testBranch(QStringLiteral("KDE/4.10")); } void GitInitTest::revHistory() { repoInit(); addFiles(); commitFiles(); QList commits = m_plugin->getAllCommits(gitTest_BaseDir()); QVERIFY(!commits.isEmpty()); QStringList logMessages; for (int i = 0; i < commits.count(); ++i) logMessages << commits[i].getLog(); QCOMPARE(commits.count(), 2); QCOMPARE(logMessages[0], QStringLiteral("KDevelop's Test commit2")); //0 is later than 1! QCOMPARE(logMessages[1], QStringLiteral("Test commit")); QVERIFY(commits[1].getParents().isEmpty()); //0 is later than 1! QVERIFY(!commits[0].getParents().isEmpty()); //initial commit is on the top QVERIFY(commits[1].getCommit().contains(QRegExp("^\\w{,40}$"))); QVERIFY(commits[0].getCommit().contains(QRegExp("^\\w{,40}$"))); QVERIFY(commits[0].getParents()[0].contains(QRegExp("^\\w{,40}$"))); } void GitInitTest::testAnnotation() { repoInit(); addFiles(); commitFiles(); // called after commitFiles QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("An appended line"), QIODevice::Append)); VcsJob* j = m_plugin->commit(QStringLiteral("KDevelop's Test commit3"), QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); j = m_plugin->annotate(QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName()), VcsRevision::createSpecialRevision(VcsRevision::Head)); VERIFYJOB(j); QList results = j->fetchResults().toList(); QCOMPARE(results.size(), 2); QVERIFY(results.at(0).canConvert()); VcsAnnotationLine annotation = results.at(0).value(); QCOMPARE(annotation.lineNumber(), 0); QCOMPARE(annotation.commitMessage(), QStringLiteral("KDevelop's Test commit2")); QVERIFY(results.at(1).canConvert()); annotation = results.at(1).value(); QCOMPARE(annotation.lineNumber(), 1); QCOMPARE(annotation.commitMessage(), QStringLiteral("KDevelop's Test commit3")); } void GitInitTest::testRemoveEmptyFolder() { repoInit(); QDir d(gitTest_BaseDir()); d.mkdir(QStringLiteral("emptydir")); VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir()+"emptydir/")); if (j) VERIFYJOB(j); QVERIFY(!d.exists(QStringLiteral("emptydir"))); } void GitInitTest::testRemoveEmptyFolderInFolder() { repoInit(); QDir d(gitTest_BaseDir()); d.mkdir(QStringLiteral("dir")); QDir d2(gitTest_BaseDir()+"dir"); d2.mkdir(QStringLiteral("emptydir")); VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir()+"dir/")); if (j) VERIFYJOB(j); QVERIFY(!d.exists(QStringLiteral("dir"))); } void GitInitTest::testRemoveUnindexedFile() { repoInit(); QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("An appended line"), QIODevice::Append)); VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName())); if (j) VERIFYJOB(j); QVERIFY(!QFile::exists(gitTest_BaseDir() + gitTest_FileName())); } void GitInitTest::testRemoveFolderContainingUnversionedFiles() { repoInit(); QDir d(gitTest_BaseDir()); d.mkdir(QStringLiteral("dir")); QVERIFY(writeFile(gitTest_BaseDir() + "dir/foo", QStringLiteral("An appended line"), QIODevice::Append)); VcsJob* j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir()+"dir")); VERIFYJOB(j); j = m_plugin->commit(QStringLiteral("initial commit"), QList() << QUrl::fromLocalFile(gitTest_BaseDir())); VERIFYJOB(j); QVERIFY(writeFile(gitTest_BaseDir() + "dir/bar", QStringLiteral("An appended line"), QIODevice::Append)); j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + "dir")); if (j) VERIFYJOB(j); QVERIFY(!QFile::exists(gitTest_BaseDir() + "dir")); } void GitInitTest::removeTempDirs() { for (const auto& dirPath : {gitTest_BaseDir(), gitTest_BaseDir2()}) { QDir dir(dirPath); if (dir.exists() && !dir.removeRecursively()) { qDebug() << "QDir::removeRecursively(" << dirPath << ") returned false"; } } } void GitInitTest::testDiff() { repoInit(); addFiles(); commitFiles(); QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("something else"))); VcsRevision srcrev = VcsRevision::createSpecialRevision(VcsRevision::Base); VcsRevision dstrev = VcsRevision::createSpecialRevision(VcsRevision::Working); VcsJob* j = m_plugin->diff(QUrl::fromLocalFile(gitTest_BaseDir()), srcrev, dstrev, VcsDiff::DiffUnified, IBasicVersionControl::Recursive); VERIFYJOB(j); KDevelop::VcsDiff d = j->fetchResults().value(); QVERIFY(d.baseDiff().isLocalFile()); QString path = d.baseDiff().toLocalFile(); QVERIFY(QDir().exists(path+"/.git")); } QTEST_MAIN(GitInitTest) // #include "gittest.moc" diff --git a/plugins/git/tests/test_git.h b/plugins/git/tests/test_git.h index 9ec7f8976..faab55e26 100644 --- a/plugins/git/tests/test_git.h +++ b/plugins/git/tests/test_git.h @@ -1,73 +1,72 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2007 Robert Gruber * * * * Adapted for Git * * 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) 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 . * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GIT_INIT_H #define KDEVPLATFORM_PLUGIN_GIT_INIT_H #include -#include class GitPlugin; namespace KDevelop { class TestCore; } class GitInitTest: public QObject { Q_OBJECT private: void repoInit(); void addFiles(); void commitFiles(); private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testInit(); void testReadAndSetConfigOption(); void testAdd(); void testCommit(); void testBranching(); void testBranch(const QString &branchName); void testMerge(); void revHistory(); void testAnnotation(); void testRemoveEmptyFolder(); void testRemoveEmptyFolderInFolder(); void testRemoveUnindexedFile(); void testRemoveFolderContainingUnversionedFiles(); void testDiff(); private: GitPlugin* m_plugin; void removeTempDirs(); }; #endif diff --git a/plugins/grepview/grepdialog.h b/plugins/grepview/grepdialog.h index 26e7af0b9..9752b9642 100644 --- a/plugins/grepview/grepdialog.h +++ b/plugins/grepview/grepdialog.h @@ -1,77 +1,72 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPDIALOG_H #define KDEVPLATFORM_PLUGIN_GREPDIALOG_H #include #include #include "grepjob.h" #include "ui_grepwidget.h" class GrepViewPlugin; -class KConfig; -class KUrlRequester; - -class QLineEdit; - class GrepDialog : public QDialog, private Ui::GrepWidget { Q_OBJECT public: explicit GrepDialog( GrepViewPlugin * plugin, QWidget *parent=nullptr ); ~GrepDialog() override; void setSettings(const GrepJobSettings &settings); GrepJobSettings settings() const; public Q_SLOTS: void startSearch(); ///Sets directory(ies)/files to search in. Also it can be semicolon separated list of directories/files or one of special strings: allOpenFilesString, allOpenProjectsString void setSearchLocations(const QString &dir); private Q_SLOTS: void templateTypeComboActivated(int); void patternComboEditTextChanged( const QString& ); void directoryChanged(const QString &dir); QMenu* createSyncButtonMenu(); void addUrlToMenu(QMenu* ret, const QUrl& url); void addStringToMenu(QMenu* ret, QString string); void synchronizeDirActionTriggered(bool); ///Opens the dialog to select a directory to search in, and inserts it into Location(s) field. void selectDirectoryDialog(); protected: void closeEvent(QCloseEvent* closeEvent) override; private: // Returns the chosen directories or files (only the top directories, not subfiles) QList< QUrl > getDirectoryChoice() const; // Returns whether the given url is a subfile/subdirectory of one of the chosen directories/files // This is slow, so don't call it too often bool isPartOfChoice(QUrl url) const; // Checks what a user has entered into the dialog and saves the data in m_settings void updateSettings(); GrepViewPlugin * m_plugin; GrepJobSettings m_settings; }; #endif diff --git a/plugins/grepview/grepoutputmodel.cpp b/plugins/grepview/grepoutputmodel.cpp index b3c169c58..a54746bfc 100644 --- a/plugins/grepview/grepoutputmodel.cpp +++ b/plugins/grepview/grepoutputmodel.cpp @@ -1,494 +1,493 @@ /*************************************************************************** * 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) { #if QT_VERSION >= 0x050600 setAutoTristate(true); #else setTristate(true); #endif 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(); // show replaced version in tooltip if we are in replace mode const QString match = isCheckable() ? grepModel->replacementFor(m_change->m_oldText) : m_change->m_oldText; const QString repl = QLatin1String("") + match.toHtmlEscaped() + QLatin1String(""); 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(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 = 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 = 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() != 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 = 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() == 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) #if QT_VERSION >= 0x050600 item->setAutoTristate(true); #else item->setTristate(true); #endif } 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()) #if QT_VERSION >= 0x050600 copy->setAutoTristate(true); #else copy->setTristate(true); #endif } 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 ac2412a56..6cb95d323 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,396 +1,395 @@ /************************************************************************** * 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(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); QAction *refreshAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Refresh"), this); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); addAction(refreshAction); 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->setRootIsDecorated(false); 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); connect(refreshAction, &QAction::triggered, this, &GrepOutputView::refresh); 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); resultsTreeView->expandAll(); 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::refresh() { m_plugin->showDialog(true, QString(), false); } 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/greputil.h b/plugins/grepview/greputil.h index 026cc2f01..55c72664f 100644 --- a/plugins/grepview/greputil.h +++ b/plugins/grepview/greputil.h @@ -1,26 +1,25 @@ /*************************************************************************** * 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. * * * ***************************************************************************/ // some utility functions used at various places #ifndef KDEVPLATFORM_PLUGIN_GREPUTIL_H #define KDEVPLATFORM_PLUGIN_GREPUTIL_H -#include -#include - class QComboBox; +class QStringList; +class QString; /// Returns the contents of a QComboBox as a QStringList QStringList qCombo2StringList( QComboBox* combo, bool allowEmpty = false ); /// Replaces each occurrence of "%s" in pattern by searchString (and "%%" by "%") QString substitudePattern(const QString& pattern, const QString& searchString); #endif diff --git a/plugins/openwith/openwithplugin.cpp b/plugins/openwith/openwithplugin.cpp index 867698b3f..b92d11136 100644 --- a/plugins/openwith/openwithplugin.cpp +++ b/plugins/openwith/openwithplugin.cpp @@ -1,290 +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 #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( nullptr ) { } 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 = 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 b0c60e849..e81e1b37c 100644 --- a/plugins/outlineview/outlineviewplugin.cpp +++ b/plugins/outlineview/outlineviewplugin.cpp @@ -1,76 +1,75 @@ /* * 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: explicit OutlineViewFactory(OutlineViewPlugin *plugin) : m_plugin(plugin) {} 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/outlineview/outlinewidget.cpp b/plugins/outlineview/outlinewidget.cpp index 020c7fd53..7b33ba282 100644 --- a/plugins/outlineview/outlinewidget.cpp +++ b/plugins/outlineview/outlinewidget.cpp @@ -1,107 +1,103 @@ /* * 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 "outlinewidget.h" #include -#include -#include #include -#include #include #include -#include #include #include #include #include #include #include "outlineviewplugin.h" #include "outlinemodel.h" using namespace KDevelop; OutlineWidget::OutlineWidget(QWidget* parent, OutlineViewPlugin* plugin) : QWidget(parent) , m_plugin(plugin) , m_model(new OutlineModel(this)) , m_tree(new QTreeView(this)) , m_proxy(new KRecursiveFilterProxyModel(this)) , m_filter(new QLineEdit(this)) { setObjectName(QStringLiteral("Outline View")); setWindowTitle(i18n("Outline")); setWhatsThis(i18n("Outline View")); setWindowIcon(QIcon::fromTheme(QStringLiteral("code-class"), windowIcon())); //TODO: better icon? m_proxy->setSourceModel(m_model); m_proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); m_proxy->setDynamicSortFilter(false); m_tree->setModel(m_proxy); m_tree->setHeaderHidden(true); // sort action m_sortAlphabeticallyAction = new QAction(QIcon::fromTheme(QStringLiteral("view-sort-ascending")), QString(), this); m_sortAlphabeticallyAction->setToolTip(i18n("Sort alphabetically")); m_sortAlphabeticallyAction->setCheckable(true); connect(m_sortAlphabeticallyAction, &QAction::triggered, this, [this](bool sort) { // calling sort with -1 will restore the original order m_proxy->sort(sort ? 0 : -1, Qt::AscendingOrder); m_sortAlphabeticallyAction->setChecked(sort); }); addAction(m_sortAlphabeticallyAction); // filter connect(m_filter, &QLineEdit::textChanged, m_proxy, &KRecursiveFilterProxyModel::setFilterFixedString); connect(m_tree, &QTreeView::activated, this, &OutlineWidget::activated); m_filter->setPlaceholderText(i18n("Filter...")); auto filterAction = new QWidgetAction(this); filterAction->setDefaultWidget(m_filter); addAction(filterAction); setFocusProxy(m_filter); QVBoxLayout* vbox = new QVBoxLayout(this); vbox->setMargin(0); vbox->addWidget(m_tree); setLayout(vbox); expandFirstLevel(); connect(m_model, &QAbstractItemModel::modelReset, this, &OutlineWidget::expandFirstLevel); } void OutlineWidget::activated(QModelIndex index) { QModelIndex realIndex = m_proxy->mapToSource(index); m_model->activate(realIndex); } OutlineWidget::~OutlineWidget() { } void OutlineWidget::expandFirstLevel() { for (int i = 0; i < m_proxy->rowCount(); i++) { m_tree->expand(m_proxy->index(i, 0)); } } diff --git a/plugins/outlineview/outlinewidget.h b/plugins/outlineview/outlinewidget.h index 91eefb306..1b01271a0 100644 --- a/plugins/outlineview/outlinewidget.h +++ b/plugins/outlineview/outlinewidget.h @@ -1,52 +1,52 @@ /* * 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 */ #pragma once #include -#include class KRecursiveFilterProxyModel; +class QModelIndex; class QAction; class QTreeView; class QLineEdit; class OutlineModel; class OutlineViewPlugin; class OutlineWidget : public QWidget { Q_OBJECT public: OutlineWidget(QWidget* parent, OutlineViewPlugin* plugin); ~OutlineWidget() override; private: OutlineViewPlugin* m_plugin; OutlineModel* m_model; QTreeView* m_tree; KRecursiveFilterProxyModel* m_proxy; QLineEdit* m_filter; QAction* m_sortAlphabeticallyAction; Q_DISABLE_COPY(OutlineWidget) public slots: void activated(QModelIndex); void expandFirstLevel(); }; diff --git a/plugins/patchreview/localpatchsource.cpp b/plugins/patchreview/localpatchsource.cpp index bfb90ea22..d332f1c58 100644 --- a/plugins/patchreview/localpatchsource.cpp +++ b/plugins/patchreview/localpatchsource.cpp @@ -1,130 +1,129 @@ /*************************************************************************** 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(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, 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/patchreview.h b/plugins/patchreview/patchreview.h index 92090e124..80ee51257 100644 --- a/plugins/patchreview/patchreview.h +++ b/plugins/patchreview/patchreview.h @@ -1,144 +1,143 @@ /*************************************************************************** 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_PATCHREVIEW_H #define KDEVPLATFORM_PLUGIN_PATCHREVIEW_H -#include #include #include #include #include class PatchHighlighter; class PatchReviewToolViewFactory; class QTimer; namespace KDevelop { class IDocument; } namespace Sublime { class Area; } namespace Diff2 { class KompareModelList; class DiffModel; } namespace Kompare { struct Info; } class DiffSettings; class PatchReviewPlugin; class PatchReviewPlugin : public KDevelop::IPlugin, public KDevelop::IPatchReview, public KDevelop::ILanguageSupport { Q_OBJECT Q_INTERFACES( KDevelop::IPatchReview ) Q_INTERFACES( KDevelop::ILanguageSupport ) public : explicit PatchReviewPlugin( QObject *parent, const QVariantList & = QVariantList() ); ~PatchReviewPlugin() override; void unload() override; KDevelop::IPatchSource::Ptr patch() const { return m_patch; } Diff2::KompareModelList* modelList() const { return m_modelList.data(); } QString name() const override { return "diff"; } KDevelop::ParseJob *createParseJob(const KDevelop::IndexedString &) override { return nullptr; } void seekHunk( bool forwards, const QUrl& file = QUrl() ); void setPatch( KDevelop::IPatchSource* patch ); void startReview( KDevelop::IPatchSource* patch, ReviewMode mode ) override; void finishReview( QList< QUrl > selection ); QUrl urlForFileModel( const Diff2::DiffModel* model ); QAction* finishReviewAction() const { return m_finishReview; } KDevelop::ContextMenuExtension contextMenuExtension( KDevelop::Context* context ) override; Q_SIGNALS: void startingNewReview(); void patchChanged(); public Q_SLOTS : //Does parts of the review-starting that are problematic to do directly in startReview, as they may open dialogs etc. void updateReview(); void cancelReview(); void clearPatch( QObject* patch ); void notifyPatchChanged(); void highlightPatch(); void updateKompareModel(); void forceUpdate(); void areaChanged(Sublime::Area* area); void executeFileReviewAction(); private Q_SLOTS : void documentClosed( KDevelop::IDocument* ); void textDocumentCreated( KDevelop::IDocument* ); void documentSaved( KDevelop::IDocument* ); void closeReview(); private: void switchToEmptyReviewArea(); /// Makes sure that this working set is active only in the @p area, and that its name starts with "review". void setUniqueEmptyWorkingSet(Sublime::Area* area); void addHighlighting( const QUrl& file, KDevelop::IDocument* document = nullptr ); void removeHighlighting( const QUrl& file = QUrl() ); KDevelop::IPatchSource::Ptr m_patch; QTimer* m_updateKompareTimer; PatchReviewToolViewFactory* m_factory; QAction* m_finishReview; #if 0 void determineState(); #endif QPointer< DiffSettings > m_diffSettings; QScopedPointer< Kompare::Info > m_kompareInfo; QScopedPointer< Diff2::KompareModelList > m_modelList; uint m_depth = 0; // depth of the patch represented by m_modelList typedef QMap< QUrl, QPointer< PatchHighlighter > > HighlightMap; HighlightMap m_highlighters; friend class PatchReviewToolView; // to access slot exporterSelected(); }; #endif // kate: space-indent on; indent-width 2; tab-width 2; replace-tabs on diff --git a/plugins/perforce/perforceplugin.cpp b/plugins/perforce/perforceplugin.cpp index fb16f9daa..1523270ef 100644 --- a/plugins/perforce/perforceplugin.cpp +++ b/plugins/perforce/perforceplugin.cpp @@ -1,699 +1,695 @@ /*************************************************************************** * Copyright 2010 Morten Danielsen Volden * * * * 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 "perforceplugin.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 using namespace KDevelop; namespace { QString toRevisionName(const KDevelop::VcsRevision& rev, QString currentRevision=QString()) { bool ok; int previous = currentRevision.toInt(&ok); previous--; QString tmp; switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return QStringLiteral("#head"); case VcsRevision::Base: return QStringLiteral("#have"); case VcsRevision::Working: return QStringLiteral("#have"); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); tmp.setNum(previous); tmp.prepend("#"); return tmp; 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: tmp.append("#"); tmp.append(rev.revisionValue().toString()); return tmp; case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserSpecialType: Q_ASSERT(false); } return QString(); } VcsItemEvent::Actions actionsFromString(QString const& changeDescription) { if(changeDescription == "add") return VcsItemEvent::Added; if(changeDescription == "delete") return VcsItemEvent::Deleted; return VcsItemEvent::Modified; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } } Q_LOGGING_CATEGORY(PLUGIN_PERFORCE, "kdevplatform.plugins.perforce") PerforcePlugin::PerforcePlugin(QObject* parent, const QVariantList&): KDevelop::IPlugin("kdevperforce", parent) , m_common(new KDevelop::VcsPluginHelper(this, this)) , m_perforcemenu(nullptr) , m_perforceConfigName("p4config.txt") , m_perforceExecutable("p4") , m_edit_action(nullptr) { QProcessEnvironment currentEviron(QProcessEnvironment::systemEnvironment()); QString tmp(currentEviron.value("P4CONFIG")); if (tmp.isEmpty()) { // We require the P4CONFIG variable to be set because the perforce command line client will need it setErrorDescription(i18n("The variable P4CONFIG is not set. Is perforce installed on the system?")); return; } else { m_perforceConfigName = tmp; } qCDebug(PLUGIN_PERFORCE) << "The value of P4CONFIG is : " << tmp; } PerforcePlugin::~PerforcePlugin() { } QString PerforcePlugin::name() const { return i18n("Perforce"); } KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* /*parent*/) { return nullptr; } bool PerforcePlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { Q_UNUSED(remoteLocation); // TODO return false; } bool PerforcePlugin::isValidDirectory(const QUrl & dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(dirPath.toLocalFile()) : finfo.absoluteDir(); do { if (dir.exists(m_perforceConfigName)) { return true; } } while (dir.cdUp()); return false; } bool PerforcePlugin::isVersionControlled(const QUrl& localLocation) { QFileInfo fsObject(localLocation.toLocalFile()); if (fsObject.isDir()) { return isValidDirectory(localLocation); } return parseP4fstat(fsObject, KDevelop::OutputJob::Silent); } DVcsJob* PerforcePlugin::p4fstatJob(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(curFile.absolutePath(), this, verbosity); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); return job; } bool PerforcePlugin::parseP4fstat(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(p4fstatJob(curFile, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { qCDebug(PLUGIN_PERFORCE) << "Perforce returned: " << job->output(); if (!job->output().isEmpty()) return true; } return false; } QString PerforcePlugin::getRepositoryName(const QFileInfo& curFile) { static const QString DEPOT_FILE_STR("... depotFile "); QString ret; QScopedPointer job(p4fstatJob(curFile, KDevelop::OutputJob::Silent)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); foreach(const QString & line, outputLines) { int idx(line.indexOf(DEPOT_FILE_STR)); if (idx != -1) { ret = line.right(line.size() - DEPOT_FILE_STR.size()); return ret; } } } } return ret; } KDevelop::VcsJob* PerforcePlugin::repositoryLocation(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "add" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::remove(const QList& /*localLocations*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::copy(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDstn*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::move(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDst*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4StatusOutput); return job; } KDevelop::VcsJob* PerforcePlugin::revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "revert" << curFile.fileName(); return job; } KDevelop::VcsJob* PerforcePlugin::update(const QList& localLocations, const KDevelop::VcsRevision& /*rev*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); //*job << m_perforceExecutable << "-p" << "127.0.0.1:1666" << "info"; - Let's keep this for now it's very handy for debugging QString fileOrDirectory; if (curFile.isDir()) fileOrDirectory = curFile.absolutePath() + "/..."; else fileOrDirectory = curFile.fileName(); *job << m_perforceExecutable << "sync" << fileOrDirectory; return job; } KDevelop::VcsJob* PerforcePlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "submit" << "-d" << message << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type , KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(fileOrDirectory.toLocalFile()); QString depotSrcFileName = getRepositoryName(curFile); QString depotDstFileName = depotSrcFileName; depotSrcFileName.append(toRevisionName(srcRevision, dstRevision.prettyValue())); // dstRevision acutally contains the number that we want to take previous of DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); switch (dstRevision.revisionType()) { case VcsRevision::FileNumber: case VcsRevision::GlobalNumber: depotDstFileName.append("#"); depotDstFileName.append(dstRevision.prettyValue()); *job << m_perforceExecutable << "diff2" << "-u" << depotSrcFileName << depotDstFileName; break; case VcsRevision::Special: switch (dstRevision.revisionValue().value()) { case VcsRevision::Working: *job << m_perforceExecutable << "diff" << "-du" << depotSrcFileName; break; case VcsRevision::Start: case VcsRevision::UserSpecialType: default: break; } default: break; } connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4DiffOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, long unsigned int limit) { static QString lastSeenChangeList; QFileInfo curFile(localLocation.toLocalFile()); QString localLocationAndRevStr = localLocation.toLocalFile(); DVcsJob* job = new DVcsJob(urlDir(localLocation), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit"; if(limit > 0) *job << QStringLiteral("-m %1").arg(limit); if (curFile.isDir()) { localLocationAndRevStr.append("/..."); } QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) { // This is not too nice, but perforce argument for restricting output from filelog does not Work :-( // So putting this in so we do not end up in infinite loop calling log, if(revStr == lastSeenChangeList) { localLocationAndRevStr.append("#none"); lastSeenChangeList.clear(); } else { localLocationAndRevStr.append(revStr); lastSeenChangeList = revStr; } } *job << localLocationAndRevStr; qCDebug(PLUGIN_PERFORCE) << "Issuing the following command to p4: " << job->dvcsCommand(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/, const KDevelop::VcsRevision& /*limit*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::annotate(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "annotate" << "-qi" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4AnnotateOutput); return job; } KDevelop::VcsJob* PerforcePlugin::resolve(const QList& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::createWorkingCopy(const KDevelop::VcsLocation& /*sourceRepository*/, const QUrl& /*destinationDirectory*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsLocationWidget* PerforcePlugin::vcsLocation(QWidget* parent) const { return new StandardVcsLocationWidget(parent); } KDevelop::VcsJob* PerforcePlugin::edit(const QList& localLocations) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "edit" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::edit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::unedit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::localRevision(const QUrl& /*localLocation*/, KDevelop::VcsRevision::RevisionType) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::import(const QString& /*commitMessage*/, const QUrl& /*sourceDirectory*/, const KDevelop::VcsLocation& /*destinationRepository*/) { return nullptr; } KDevelop::ContextMenuExtension PerforcePlugin::contextMenuExtension(KDevelop::Context* context) { m_common->setupFromContext(context); const QList & ctxUrlList = m_common->contextUrlList(); bool hasVersionControlledEntries = false; for( const QUrl& url : ctxUrlList) { if (isValidDirectory(url)) { hasVersionControlledEntries = true; break; } } if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context); QMenu * perforceMenu = m_common->commonActions(); perforceMenu->addSeparator(); perforceMenu->addSeparator(); if (!m_edit_action) { m_edit_action = new QAction(i18n("Edit"), this); connect(m_edit_action, &QAction::triggered, this, & PerforcePlugin::ctxEdit); } perforceMenu->addAction(m_edit_action); ContextMenuExtension menuExt; menuExt.addAction(ContextMenuExtension::VcsGroup, perforceMenu->menuAction()); return menuExt; } void PerforcePlugin::ctxEdit() { QList const & ctxUrlList = m_common->contextUrlList(); KDevelop::ICore::self()->runController()->registerJob(edit(ctxUrlList)); } void PerforcePlugin::setEnvironmentForJob(DVcsJob* job, const QFileInfo& curFile) { KProcess* jobproc = job->process(); jobproc->setEnv("P4CONFIG", m_perforceConfigName); if (curFile.isDir()) { jobproc->setEnv("PWD", curFile.filePath()); } else { jobproc->setEnv("PWD", curFile.absolutePath()); } } QList PerforcePlugin::getQvariantFromLogOutput(QStringList const& outputLines) { static const QString LOGENTRY_START("... #"); static const QString DEPOTMESSAGE_START("... ."); QMap changes; QList commits; QString currentFileName; QString changeNumberStr, author,changeDescription, commitMessage; VcsEvent currentVcsEvent; VcsItemEvent currentRepoFile; VcsRevision rev; int indexofAt; int changeNumber = 0; foreach(const QString & line, outputLines) { if (!line.startsWith(LOGENTRY_START) && !line.startsWith(DEPOTMESSAGE_START) && !line.startsWith('\t')) { currentFileName = line; } if(line.indexOf(LOGENTRY_START) != -1) { // expecting the Logentry line to be of the form: //... #5 change 10 edit on 2010/12/06 12:07:31 by mvo@testbed (text) changeNumberStr = line.section(' ', 3, 3 ); // We use global change number changeNumber = changeNumberStr.toInt(); author = line.section(' ', 9, 9); changeDescription = line.section(' ' , 4, 4 ); indexofAt = author.indexOf('@'); author.remove(indexofAt, author.size()); // Only keep the username itself rev.setRevisionValue(changeNumberStr, KDevelop::VcsRevision::GlobalNumber); changes[changeNumber].setRevision(rev); changes[changeNumber].setAuthor(author); changes[changeNumber].setDate(QDateTime::fromString(line.section(' ', 6, 7), "yyyy/MM/dd hh:mm:ss")); currentRepoFile.setRepositoryLocation(currentFileName); currentRepoFile.setActions( actionsFromString(changeDescription) ); changes[changeNumber].addItem(currentRepoFile); commitMessage.clear(); // We have a new entry, clear message } if (line.startsWith('\t') || line.startsWith(DEPOTMESSAGE_START)) { commitMessage += line.trimmed() + '\n'; changes[changeNumber].setMessage(commitMessage); } } for(auto item : changes) { commits.prepend(QVariant::fromValue(item)); } return commits; } void PerforcePlugin::parseP4StatusOutput(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QVariantList statuses; QList processedFiles; static const QString ACTION_STR("... action "); static const QString CLIENT_FILE_STR("... clientFile "); VcsStatusInfo status; status.setState(VcsStatusInfo::ItemUserState); foreach(const QString & line, outputLines) { int idx(line.indexOf(ACTION_STR)); if (idx != -1) { QString curr = line.right(line.size() - ACTION_STR.size()); if (curr == "edit") { status.setState(VcsStatusInfo::ItemModified); } else if (curr == "add") { status.setState(VcsStatusInfo::ItemAdded); } else { status.setState(VcsStatusInfo::ItemUserState); } continue; } idx = line.indexOf(CLIENT_FILE_STR); if (idx != -1) { QUrl fileUrl = QUrl::fromLocalFile(line.right(line.size() - CLIENT_FILE_STR.size())); status.setUrl(fileUrl); } } statuses.append(qVariantFromValue(status)); job->setResults(statuses); } void PerforcePlugin::parseP4LogOutput(KDevelop::DVcsJob* job) { QList commits(getQvariantFromLogOutput(job->output().split('\n', QString::SkipEmptyParts))); job->setResults(commits); } void PerforcePlugin::parseP4DiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); QDir dir(job->directory()); do { if (dir.exists(m_perforceConfigName)) { break; } } while (dir.cdUp()); diff.setBaseDiff(QUrl::fromLocalFile(dir.absolutePath())); job->setResults(qVariantFromValue(diff)); } void PerforcePlugin::parseP4AnnotateOutput(DVcsJob *job) { QVariantList results; /// First get the changelists for this file QStringList strList(job->dvcsCommand()); QString localLocation(strList.last()); /// ASSUMPTION WARNING - localLocation is the last in the annotate command KDevelop::VcsRevision dummyRev; QScopedPointer logJob(new DVcsJob(job->directory(), this, OutputJob::Silent)); QFileInfo curFile(localLocation); setEnvironmentForJob(logJob.data(), curFile); *logJob << m_perforceExecutable << "filelog" << "-lit" << localLocation; QList commits; if (logJob->exec() && logJob->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { commits = getQvariantFromLogOutput(logJob->output().split('\n', QString::SkipEmptyParts)); } } VcsEvent item; QMap globalCommits; /// Move the VcsEvents to a more suitable data strucure for (QList::const_iterator commitsIt = commits.constBegin(), commitsEnd = commits.constEnd(); commitsIt != commitsEnd; ++commitsIt) { if(commitsIt->canConvert()) { item = commitsIt->value(); } globalCommits.insert(item.revision().revisionValue().toLongLong(), item); } VcsAnnotationLine* annotation; QStringList lines = job->output().split('\n'); size_t lineNumber(0); QMap definedRevisions; QMap::iterator currentEvent; bool convertToIntOk(false); int globalRevisionInt(0); QString globalRevision; for (QStringList::const_iterator it = lines.constBegin(), itEnd = lines.constEnd(); it != itEnd; ++it) { if (it->isEmpty()) { continue; } globalRevision = it->left(it->indexOf(':')); annotation = new VcsAnnotationLine; annotation->setLineNumber(lineNumber); VcsRevision rev; rev.setRevisionValue(globalRevision, KDevelop::VcsRevision::GlobalNumber); annotation->setRevision(rev); // Find the other info in the commits list globalRevisionInt = globalRevision.toLongLong(&convertToIntOk); if(convertToIntOk) { currentEvent = globalCommits.find(globalRevisionInt); annotation->setAuthor(currentEvent->author()); annotation->setCommitMessage(currentEvent->message()); annotation->setDate(currentEvent->date()); } results += qVariantFromValue(*annotation); ++lineNumber; } job->setResults(results); } KDevelop::VcsJob* PerforcePlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } diff --git a/plugins/perforce/perforceplugin.h b/plugins/perforce/perforceplugin.h index dcb2560ec..17369fb7e 100644 --- a/plugins/perforce/perforceplugin.h +++ b/plugins/perforce/perforceplugin.h @@ -1,177 +1,176 @@ /*************************************************************************** * Copyright 2010 Morten Danielsen Volden * * * * 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 . * ***************************************************************************/ #ifndef KDEVPERFORCEPLUGIN_H #define KDEVPERFORCEPLUGIN_H #include #include #include #include #include #include #include class QMenu; class QFileInfo; -class QDir; namespace KDevelop { class ContextMenuExtension; class VcsPluginHelper; class DVcsJob; } class PerforcePlugin : public KDevelop::IPlugin, public KDevelop::ICentralizedVersionControl { Q_OBJECT Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::ICentralizedVersionControl) friend class PerforcePluginTest; public: explicit PerforcePlugin(QObject* parent, const QVariantList & = QVariantList()); ~PerforcePlugin() override; //@{ /** Methods inherited from KDevelop::IBasicVersionControl */ QString name() const override; KDevelop::VcsImportMetadataWidget* createImportMetadataWidget(QWidget* parent) override; bool isValidRemoteRepositoryUrl(const QUrl& remoteLocation) override; bool isVersionControlled(const QUrl& localLocation) override; KDevelop::VcsJob* repositoryLocation(const QUrl& localLocation) override; KDevelop::VcsJob* add(const QList& localLocations, RecursionMode recursion = IBasicVersionControl::Recursive) override; KDevelop::VcsJob* remove(const QList& localLocations) override; KDevelop::VcsJob* copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) override; KDevelop::VcsJob* move(const QUrl& localLocationSrc, const QUrl& localLocationDst) override; KDevelop::VcsJob* status(const QList& localLocations, RecursionMode recursion = IBasicVersionControl::Recursive) override; KDevelop::VcsJob* revert(const QList& localLocations, RecursionMode recursion = IBasicVersionControl::Recursive) override; KDevelop::VcsJob* update(const QList& localLocations, const KDevelop::VcsRevision& rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Head), KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type = KDevelop::VcsDiff::DiffUnified, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long limit = 0) override; KDevelop::VcsJob* log(const QUrl& localLocation, const KDevelop::VcsRevision& rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Base), const KDevelop::VcsRevision& limit = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Start)) override; KDevelop::VcsJob* annotate(const QUrl& localLocation, const KDevelop::VcsRevision& rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Head)) override; KDevelop::VcsJob* resolve(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) override; KDevelop::VcsJob* createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl & destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion = IBasicVersionControl::Recursive) override; KDevelop::VcsLocationWidget* vcsLocation(QWidget* parent) const override; //@} //@{ /** Methods inherited from KDevelop::ICentralizedVersionControl */ KDevelop::VcsJob* edit(const QUrl& localLocation) override; KDevelop::VcsJob* unedit(const QUrl& localLocation) override; KDevelop::VcsJob* localRevision(const QUrl& localLocation, KDevelop::VcsRevision::RevisionType) override; KDevelop::VcsJob* import(const QString & commitMessage, const QUrl & sourceDirectory, const KDevelop::VcsLocation & destinationRepository) override; //@} /// This plugin implements its own edit method KDevelop::VcsJob* edit(const QList& localLocations); KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override; public Q_SLOTS: /// invoked by context-menu void ctxEdit(); // void ctxUnedit(); // void ctxLocalRevision(); // void ctxImport(); private slots: void parseP4StatusOutput(KDevelop::DVcsJob* job); void parseP4DiffOutput(KDevelop::DVcsJob* job); void parseP4LogOutput(KDevelop::DVcsJob* job); void parseP4AnnotateOutput(KDevelop::DVcsJob* job); private: bool isValidDirectory(const QUrl & dirPath); KDevelop::DVcsJob* p4fstatJob(const QFileInfo& curFile, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose); bool parseP4fstat(const QFileInfo& curFile, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose); KDevelop::VcsJob* errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose); QString getRepositoryName(const QFileInfo& curFile); void setEnvironmentForJob(KDevelop::DVcsJob* job, QFileInfo const& fsObject); QList getQvariantFromLogOutput(QStringList const& outputLines); std::unique_ptr m_common; QMenu* m_perforcemenu; QString m_perforceConfigName; QString m_perforceExecutable; QAction* m_edit_action; }; #endif // PERFORCEPLUGIN_H diff --git a/plugins/perforce/test/test_perforce.cpp b/plugins/perforce/test/test_perforce.cpp index ee8d2e4cf..5dd3d2996 100644 --- a/plugins/perforce/test/test_perforce.cpp +++ b/plugins/perforce/test/test_perforce.cpp @@ -1,190 +1,190 @@ /*************************************************************************** * This file was inspired by KDevelop's git plugin * * Copyright 2008 Evgeniy Ivanov * * * * Adapted for Perforce * * Copyright 2011 Morten Danielsen Volden * * * * 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 "test_perforce.h" -#include +#include #include #include #include #include #include #include #define VERIFYJOB(j) \ QVERIFY(j); QVERIFY(j->exec()); QVERIFY((j)->status() == VcsJob::JobSucceeded) const QString tempDir = QDir::tempPath(); const QString perforceTestBaseDirNoSlash(tempDir + "/kdevPerforce_testdir"); const QString perforceTestBaseDir(tempDir + "/kdevPerforce_testdir/"); const QString perforceTestBaseDir2(tempDir + "/kdevPerforce_testdir2/"); const QString perforceConfigFileName("p4config.txt"); const QString perforceSrcDir(perforceTestBaseDir + "src/"); const QString perforceTest_FileName("testfile"); const QString perforceTest_FileName2("foo"); const QString perforceTest_FileName3("bar"); using namespace KDevelop; void PerforcePluginTest::init() { AutoTestShell::init({"kdevperforce"}); m_core = new TestCore(); m_core->initialize(); m_plugin = new PerforcePlugin(m_core); /// During test we are setting the executable the plugin uses to our own stub m_plugin->m_perforceExecutable = P4_CLIENT_STUB_EXE; removeTempDirsIfAny(); createNewTempDirs(); } void PerforcePluginTest::createNewTempDirs() { // Now create the basic directory structure QDir tmpdir(tempDir); tmpdir.mkdir(perforceTestBaseDir); //we start it after repoInit, so we still have empty repo QFile f(perforceTestBaseDir + perforceConfigFileName); if (f.open(QIODevice::WriteOnly)) { QTextStream input(&f); input << "P4PORT=127.0.0.1:1666\n"; input << "P4USER=mvo\n"; input << "P4CLIENT=testbed\n"; } f.close(); //Put a file here because the annotate and update function will check for that QFile g(perforceTestBaseDir + perforceTest_FileName); if (g.open(QIODevice::WriteOnly)) { QTextStream input(&g); input << "HELLO WORLD"; } g.close(); tmpdir.mkdir(perforceSrcDir); tmpdir.mkdir(perforceTestBaseDir2); } void PerforcePluginTest::removeTempDirsIfAny() { QDir dir(perforceTestBaseDir); if (dir.exists() && !dir.removeRecursively()) qDebug() << "QDir::removeRecursively(" << perforceTestBaseDir << ") returned false"; QDir dir2(perforceTestBaseDir); if (dir2.exists() && !dir2.removeRecursively()) qDebug() << "QDir::removeRecursively(" << perforceTestBaseDir2 << ") returned false"; } void PerforcePluginTest::cleanup() { m_core->cleanup(); delete m_core; removeTempDirsIfAny(); } void PerforcePluginTest::testAdd() { VcsJob* j = m_plugin->add(QList({ QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) } )); VERIFYJOB(j); } void PerforcePluginTest::testEdit() { VcsJob* j = m_plugin->edit(QList( { QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) } )); VERIFYJOB(j); } void PerforcePluginTest::testEditMultipleFiles() { QList filesForEdit; filesForEdit.push_back(QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName)); filesForEdit.push_back(QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName2)); filesForEdit.push_back(QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName3)); VcsJob* j = m_plugin->edit(filesForEdit); VERIFYJOB(j); } void PerforcePluginTest::testStatus() { VcsJob* j = m_plugin->status(QList( { QUrl::fromLocalFile(perforceTestBaseDirNoSlash) } )); VERIFYJOB(j); } void PerforcePluginTest::testAnnotate() { VcsJob* j = m_plugin->annotate(QUrl( QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) )); VERIFYJOB(j); } void PerforcePluginTest::testHistory() { VcsJob* j = m_plugin->log(QUrl( QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) )); VERIFYJOB(j); } void PerforcePluginTest::testRevert() { VcsJob* j = m_plugin->revert(QList( { QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) } )); VERIFYJOB(j); } void PerforcePluginTest::testUpdateFile() { VcsJob* j = m_plugin->update(QList( { QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) } )); VERIFYJOB(j); } void PerforcePluginTest::testUpdateDir() { VcsJob* j = m_plugin->update(QList( { QUrl::fromLocalFile(perforceTestBaseDirNoSlash) } )); VERIFYJOB(j); } void PerforcePluginTest::testCommit() { QString commitMsg("this is the commit message"); VcsJob* j = m_plugin->commit(commitMsg, QList( { QUrl::fromLocalFile(perforceTestBaseDirNoSlash) } )); VERIFYJOB(j); } void PerforcePluginTest::testDiff() { VcsRevision srcRevision; srcRevision.setRevisionValue(QVariant(1), VcsRevision::GlobalNumber); VcsRevision dstRevision; dstRevision.setRevisionValue(QVariant(2), VcsRevision::GlobalNumber); VcsJob* j = m_plugin->diff( QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName), srcRevision, dstRevision, VcsDiff::Type::DiffUnified ); VERIFYJOB(j); } QTEST_MAIN(PerforcePluginTest) diff --git a/plugins/problemreporter/problemreporterplugin.cpp b/plugins/problemreporter/problemreporterplugin.cpp index 115edff83..15c69b929 100644 --- a/plugins/problemreporter/problemreporterplugin.cpp +++ b/plugins/problemreporter/problemreporterplugin.cpp @@ -1,254 +1,252 @@ /* * 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 = 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"), i18n("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(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemReporterPlugin::documentActivated); 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); connect(pms, &ProblemModelSet::problemsChanged, this, &ProblemReporterPlugin::updateOpenedDocumentsHighlight); } 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); m_reHighlightNeeded.remove(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::documentActivated(KDevelop::IDocument* document) { IndexedString documentUrl(document->url()); if (m_reHighlightNeeded.contains(documentUrl)) { m_reHighlightNeeded.remove(documentUrl); updateHighlight(documentUrl); } } 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) return; KDevelop::ProblemModelSet* pms(core()->languageController()->problemModelSet()); QVector documentProblems; foreach (const ModelData& modelData, pms->models()) { documentProblems += modelData.model->problems({url}); } ph->setProblems(documentProblems); } void ProblemReporterPlugin::showModel(const QString& id) { auto w = dynamic_cast(core()->uiController()->findToolView(i18n("Problems"), m_factory)); if (w) w->showModel(id); } 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, nullptr); QMenu* menu(new QMenu(text, nullptr)); menuAction->setMenu(menu); foreach (QAction* action, actions) menu->addAction(action); extension.addAction(ContextMenuExtension::ExtensionGroup, menuAction); } } return extension; } void ProblemReporterPlugin::updateOpenedDocumentsHighlight() { foreach(auto document, core()->documentController()->openDocuments()) { // Skip non-text documents. // This also fixes crash caused by calling updateOpenedDocumentsHighlight() method without // any opened documents. In this case documentController()->openDocuments() returns single // (non-text) document with url like file:///tmp/kdevelop_QW2530.patch which has fatal bug: // if we call isActive() method from this document the crash will happens. if (!document->isTextDocument()) continue; IndexedString documentUrl(document->url()); if (document->isActive()) updateHighlight(documentUrl); else m_reHighlightNeeded.insert(documentUrl); } } #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.h b/plugins/problemreporter/problemsview.h index b7b00fb2c..5d84f7b64 100644 --- a/plugins/problemreporter/problemsview.h +++ b/plugins/problemreporter/problemsview.h @@ -1,118 +1,117 @@ /* * 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. */ #ifndef PROBLEMSVIEW_H #define PROBLEMSVIEW_H #include #include class ProblemTreeView; class KActionMenu; class QAction; class QActionGroup; class QLineEdit; -class QMenu; class QTabWidget; namespace KDevelop { struct ModelData; /** * @brief Provides a tabbed view for models in the ProblemModelSet. * * * Also provides a toolbar for actions for the models and shows the number of messages in each tab's text. * When the load() method is called it looks up the models in the ProblemModelSet. * For each model it creates a treeview, which is then added to the tabbed view and a new tab. * The tab's text will be the name of the model + the number of items in the treeview. */ class ProblemsView : public QWidget, public IToolViewActionListener { Q_OBJECT Q_INTERFACES(KDevelop::IToolViewActionListener) public: explicit ProblemsView(QWidget* parent = nullptr); ~ProblemsView() override; /// Load all the current models and create tabs for them void load(); public Q_SLOTS: /// Triggered when a new model is added to the ModelSet void onModelAdded(const ModelData& data); /// Triggered when a model is removed from the ModelSet void onModelRemoved(const QString& id); /// Triggered when the user (or program) selects a new tab void onCurrentChanged(int idx); /// Triggered when a view changes (happens when the model data changes) void onViewChanged(); /// Open tab for selected model void showModel(const QString& id); void selectNextItem() override; void selectPreviousItem() override; private: ProblemTreeView* currentView() const; void setupActions(); void updateActions(); void handleSeverityActionToggled(); void setScope(int scope); /// Create a view for the model and add to the tabbed widget void addModel(const ModelData& data); /// Update the tab's text (name + number of problems in that tab) void updateTab(int idx, int rows); QTabWidget* m_tabWidget; KActionMenu* m_scopeMenu = nullptr; KActionMenu* m_groupingMenu = nullptr; QAction* m_fullUpdateAction = nullptr; QAction* m_showImportsAction = nullptr; QActionGroup* m_severityActions = nullptr; QAction* m_currentDocumentAction = nullptr; QAction* m_showAllAction = nullptr; QAction* m_errorSeverityAction = nullptr; QAction* m_warningSeverityAction = nullptr; QAction* m_hintSeverityAction = nullptr; void setFilter(const QString& filterText); void setFilter(const QString& filterText, int tabIdx); QLineEdit* m_filterEdit; int m_prevTabIdx; QVector m_models; }; } #endif diff --git a/plugins/problemreporter/problemtreeview.cpp b/plugins/problemreporter/problemtreeview.cpp index 9eb50f36a..ca0ab459a 100644 --- a/plugins/problemreporter/problemtreeview.cpp +++ b/plugins/problemreporter/problemtreeview.cpp @@ -1,223 +1,222 @@ /* * KDevelop Problem Reporter * * Copyright (c) 2006-2007 Hamish Rodda * 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 "problemtreeview.h" #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include "problemreporterplugin.h" #include #include #include //#include "modeltest.h" using namespace KDevelop; namespace KDevelop { class ProblemTreeViewItemDelegate : public QItemDelegate { Q_OBJECT public: explicit ProblemTreeViewItemDelegate(QObject* parent = nullptr); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; } ProblemTreeViewItemDelegate::ProblemTreeViewItemDelegate(QObject* parent) : QItemDelegate(parent) { } void ProblemTreeViewItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem newOption(option); newOption.textElideMode = index.column() == ProblemModel::File ? Qt::ElideMiddle : Qt::ElideRight; QItemDelegate::paint(painter, newOption, index); } ProblemTreeView::ProblemTreeView(QWidget* parent, QAbstractItemModel* itemModel) : QTreeView(parent) , m_proxy(new QSortFilterProxyModel(this)) { setObjectName(QStringLiteral("Problem Reporter Tree")); setWhatsThis(i18n("Problems")); setItemDelegate(new ProblemTreeViewItemDelegate(this)); setSelectionBehavior(QAbstractItemView::SelectRows); m_proxy->setSortRole(ProblemModel::SeverityRole); m_proxy->setDynamicSortFilter(true); m_proxy->sort(0, Qt::AscendingOrder); ProblemModel* problemModel = dynamic_cast(itemModel); Q_ASSERT(problemModel); setModel(problemModel); header()->setStretchLastSection(false); if (!problemModel->features().testFlag(ProblemModel::ShowSource)) { hideColumn(ProblemModel::Source); } connect(this, &ProblemTreeView::clicked, this, &ProblemTreeView::itemActivated); connect(model(), &QAbstractItemModel::rowsInserted, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::rowsRemoved, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::modelReset, this, &ProblemTreeView::changed); m_proxy->setFilterKeyColumn(-1); m_proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); } ProblemTreeView::~ProblemTreeView() { } void ProblemTreeView::openDocumentForCurrentProblem() { itemActivated(currentIndex()); } void ProblemTreeView::itemActivated(const QModelIndex& index) { if (!index.isValid()) return; KTextEditor::Cursor start; QUrl url; { // TODO: is this really necessary? DUChainReadLocker lock(DUChain::lock()); const auto problem = index.data(ProblemModel::ProblemRole).value(); if (!problem) return; url = problem->finalLocation().document.toUrl(); start = problem->finalLocation().start(); } if (QFile::exists(url.toLocalFile())) { ICore::self()->documentController()->openDocument(url, start); } } void ProblemTreeView::resizeColumns() { for (int i = 0; i < model()->columnCount(); ++i) resizeColumnToContents(i); } void ProblemTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) { QTreeView::dataChanged(topLeft, bottomRight, roles); resizeColumns(); } void ProblemTreeView::reset() { QTreeView::reset(); resizeColumns(); } int ProblemTreeView::setFilter(const QString& filterText) { m_proxy->setFilterFixedString(filterText); return m_proxy->rowCount(); } ProblemModel* ProblemTreeView::model() const { return static_cast(m_proxy->sourceModel()); } void ProblemTreeView::setModel(QAbstractItemModel* model) { Q_ASSERT(qobject_cast(model)); m_proxy->setSourceModel(model); QTreeView::setModel(m_proxy); } void ProblemTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (!index.isValid()) return; const auto problem = index.data(ProblemModel::ProblemRole).value(); if (!problem) { return; } QExplicitlySharedDataPointer solution = problem->solutionAssistant(); if (!solution) { return; } QList actions; foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) { actions << action->toKAction(); } if (actions.isEmpty()) { return; } QString title = solution->title(); title = KDevelop::htmlToPlainText(title); title.replace(QLatin1String("'"), QLatin1String("\'")); QPointer m = new QMenu(this); m->addSection(title); m->addActions(actions); m->exec(event->globalPos()); delete m; } void ProblemTreeView::showEvent(QShowEvent* event) { Q_UNUSED(event) resizeColumns(); } #include "problemtreeview.moc" diff --git a/plugins/problemreporter/tests/test_problemsview.cpp b/plugins/problemreporter/tests/test_problemsview.cpp index 1a26e7a99..a4dc8d909 100644 --- a/plugins/problemreporter/tests/test_problemsview.cpp +++ b/plugins/problemreporter/tests/test_problemsview.cpp @@ -1,213 +1,211 @@ /* * 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 +#include #include -#include #include -#include #include "../problemsview.h" #include #include #include #include #include #include using namespace KDevelop; class TestProblemsView : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testLoad(); void testAddModel(); void testSwitchTab(); void testRemoveModel(); void testAddRemoveProblems(); void testSetProblems(); private: QTabWidget* tabWidget(); QScopedPointer m_view; }; void TestProblemsView::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); ProblemModel* model = new ProblemModel(pms); IProblem::Ptr p(new DetectedProblem()); model->addProblem(p); pms->addModel(QStringLiteral("MODEL1_ID"), QStringLiteral("MODEL1"), model); m_view.reset(new ProblemsView()); } void TestProblemsView::cleanupTestCase() { TestCore::shutdown(); } void TestProblemsView::testLoad() { m_view->load(); // Check that the inital model's tab shows up QTabWidget* tab = tabWidget(); QVERIFY(tab); QCOMPARE(tab->count(), 1); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL1 (1)")); } void TestProblemsView::testAddModel() { ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); pms->addModel(QStringLiteral("MODEL2_ID"), QStringLiteral("MODEL2"), new ProblemModel(pms)); QTabWidget* tab = tabWidget(); QVERIFY(tab); QCOMPARE(tab->count(), 2); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL1 (1)")); QCOMPARE(tab->tabText(1), QStringLiteral("MODEL2 (0)")); } QVector visibilites(const QList actions) { QVector visibilites; foreach (auto action, actions) { visibilites << action->isVisible(); } return visibilites; } void TestProblemsView::testSwitchTab() { QTabWidget* tab = tabWidget(); QVERIFY(tab); // Check that the current widget's actions are in the toolbar QWidget* oldWidget = tab->currentWidget(); QVERIFY(oldWidget); const auto oldVisibilites = visibilites(m_view->actions()); tab->setCurrentIndex(1); // Check that the new widget's actions are in the toolbar QWidget* newWidget = tab->currentWidget(); QVERIFY(newWidget); QVERIFY(newWidget != oldWidget); const auto newVisibilites = visibilites(m_view->actions()); QCOMPARE(oldVisibilites, newVisibilites); } void TestProblemsView::testRemoveModel() { // Remove the model ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); ProblemModel* model = pms->findModel(QStringLiteral("MODEL1_ID")); QVERIFY(model); pms->removeModel(QStringLiteral("MODEL1_ID")); delete model; model = nullptr; // Now let's see if the view has been updated! QTabWidget* tab = tabWidget(); QVERIFY(tab); QCOMPARE(tab->count(), 1); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (0)")); } void TestProblemsView::testAddRemoveProblems() { ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); ProblemModel* model = pms->findModel(QStringLiteral("MODEL2_ID")); QVERIFY(model); QTabWidget* tab = tabWidget(); QVERIFY(tab); // Make sure there are no problems right now model->clearProblems(); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (0)")); // Let's add some problems int c = 0; for (int i = 0; i < 3; i++) { IProblem::Ptr p(new DetectedProblem()); model->addProblem(p); c++; // Check if the view has noticed the addition QString label = QStringLiteral("MODEL2 (%1)").arg(c); QCOMPARE(tab->tabText(0), label); } // Clear the problems model->clearProblems(); // Check if the view has noticed the clear QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (0)")); } void TestProblemsView::testSetProblems() { ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); ProblemModel* model = pms->findModel(QStringLiteral("MODEL2_ID")); QVERIFY(model); QTabWidget* tab = tabWidget(); QVERIFY(tab); // Make sure there are no problems right now model->clearProblems(); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (0)")); // Build a problem vector and set the problems QVector problems; for (int i = 0; i < 3; i++) { IProblem::Ptr p(new DetectedProblem()); problems.push_back(p); } model->setProblems(problems); // Check if the view has noticed QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (3)")); } ////////////////////////////////////////////////////////////////////////////////////////////////////////// QTabWidget* TestProblemsView::tabWidget() { QTabWidget* tab = m_view->findChild(); return tab; } QTEST_MAIN(TestProblemsView) #include "test_problemsview.moc" diff --git a/plugins/projectfilter/projectfilter.cpp b/plugins/projectfilter/projectfilter.cpp index 6e2598232..30549f4da 100644 --- a/plugins/projectfilter/projectfilter.cpp +++ b/plugins/projectfilter/projectfilter.cpp @@ -1,96 +1,95 @@ /* 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 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 "projectfilter.h" #include -#include #include using namespace KDevelop; ProjectFilter::ProjectFilter( const IProject* const project, const QVector& filters ) : m_filters( filters ) , m_projectFile( project->projectFile() ) , m_project( project->path() ) { } ProjectFilter::~ProjectFilter() { } bool ProjectFilter::isValid( const Path &path, const bool isFolder ) const { if (!isFolder && path == m_projectFile) { // do not show the project file ///TODO: enable egain once the project page is ready for consumption return false; } else if (isFolder && path == m_project) { // always show the project root return true; } if (isFolder && path.isLocalFile() && QFile::exists(path.toLocalFile() + "/.kdev_ignore")) { return false; } // from here on the user can configure what he wants to see or not. // we operate on the path relative to the project base // by prepending a slash we can filter hidden files with the pattern "*/.*" const QString relativePath = makeRelative(path); if (isFolder && relativePath.endsWith(QLatin1String("/.kdev4"))) { return false; } bool isValid = true; foreach( const Filter& filter, m_filters ) { if (isFolder && !(filter.targets & Filter::Folders)) { continue; } else if (!isFolder && !(filter.targets & Filter::Files)) { continue; } if ((!isValid && filter.type == Filter::Inclusive) || (isValid && filter.type == Filter::Exclusive)) { const bool match = filter.pattern.exactMatch( relativePath ); if (filter.type == Filter::Inclusive) { isValid = match; } else { isValid = !match; } } } return isValid; } QString ProjectFilter::makeRelative(const Path& path) const { if (!m_project.isParentOf(path)) { return path.path(); } return '/' + m_project.relativePath(path); } diff --git a/plugins/projectfilter/projectfilter.h b/plugins/projectfilter/projectfilter.h index 8ce449152..ecaa4f50b 100644 --- a/plugins/projectfilter/projectfilter.h +++ b/plugins/projectfilter/projectfilter.h @@ -1,54 +1,52 @@ /* 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 Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PROJECTFILTER_H #define PROJECTFILTER_H -#include - #include #include #include "filter.h" namespace KDevelop { class IProject; class ProjectFilter : public IProjectFilter { public: ProjectFilter(const IProject* const project, const Filters& filters); virtual ~ProjectFilter(); bool isValid(const Path& path, bool isFolder) const override; private: QString makeRelative(const Path& path) const; Filters m_filters; Path m_projectFile; Path m_project; }; } #endif // PROJECTFILTER_H diff --git a/plugins/projectfilter/projectfilterconfigpage.cpp b/plugins/projectfilter/projectfilterconfigpage.cpp index b796f65ef..7d314ae61 100644 --- a/plugins/projectfilter/projectfilterconfigpage.cpp +++ b/plugins/projectfilter/projectfilterconfigpage.cpp @@ -1,220 +1,219 @@ /* This file is part of KDevelop Copyright 2008 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 "projectfilterconfigpage.h" -#include #include #include #include #include #include #include #include #include #include #include "ui_projectfiltersettings.h" #include "projectfilterdebug.h" #include "filtermodel.h" #include "comboboxdelegate.h" #include "projectfilterprovider.h" using namespace KDevelop; ProjectFilterConfigPage::ProjectFilterConfigPage(ProjectFilterProvider* provider, const ProjectConfigOptions& options, QWidget* parent) : ProjectConfigPage(provider, options, parent) , m_model(new FilterModel(this)) , m_projectFilterProvider(provider) , m_ui(new Ui::ProjectFilterSettings) { m_ui->setupUi(this); m_ui->messageWidget->hide(); m_ui->filters->setSelectionMode(QAbstractItemView::SingleSelection); m_ui->filters->setModel(m_model); m_ui->filters->setRootIsDecorated(false); m_ui->filters->header()->setSectionResizeMode(FilterModel::Pattern, QHeaderView::Stretch); m_ui->filters->header()->setSectionResizeMode(FilterModel::Targets, QHeaderView::ResizeToContents); m_ui->filters->header()->setSectionResizeMode(FilterModel::Inclusive, QHeaderView::ResizeToContents); m_ui->filters->setItemDelegateForColumn(FilterModel::Targets, new ComboBoxDelegate(QVector() << ComboBoxDelegate::Item(i18n("Files"), static_cast(Filter::Files)) << ComboBoxDelegate::Item(i18n("Folders"), static_cast(Filter::Folders)) << ComboBoxDelegate::Item(i18n("Files and Folders"), static_cast(Filter::Folders | Filter::Files)) , this)); m_ui->filters->setItemDelegateForColumn(FilterModel::Inclusive, new ComboBoxDelegate(QVector() << ComboBoxDelegate::Item(i18n("Exclude"), false) << ComboBoxDelegate::Item(i18n("Include"), true) , this)); m_ui->filters->installEventFilter(this); m_ui->filters->setDragEnabled(true); m_ui->filters->setDragDropMode(QAbstractItemView::InternalMove); m_ui->filters->setAutoScroll(true); reset(); selectionChanged(); connect(m_ui->filters->selectionModel(), &QItemSelectionModel::currentChanged, this, &ProjectFilterConfigPage::selectionChanged); connect(this, &ProjectFilterConfigPage::changed, this, &ProjectFilterConfigPage::selectionChanged); connect(m_model, &FilterModel::dataChanged, this, &ProjectFilterConfigPage::emitChanged); connect(m_model, &FilterModel::rowsInserted, this, &ProjectFilterConfigPage::emitChanged); connect(m_model, &FilterModel::rowsRemoved, this, &ProjectFilterConfigPage::emitChanged); connect(m_model, &FilterModel::modelReset, this, &ProjectFilterConfigPage::emitChanged); connect(m_model, &FilterModel::rowsMoved, this, &ProjectFilterConfigPage::emitChanged); connect(m_ui->add, &QPushButton::clicked, this, &ProjectFilterConfigPage::add); connect(m_ui->remove, &QPushButton::clicked, this, &ProjectFilterConfigPage::remove); connect(m_ui->moveUp, &QPushButton::clicked, this, &ProjectFilterConfigPage::moveUp); connect(m_ui->moveDown, &QPushButton::clicked, this, &ProjectFilterConfigPage::moveDown); } ProjectFilterConfigPage::~ProjectFilterConfigPage() { } void ProjectFilterConfigPage::apply() { ProjectConfigPage::apply(); writeFilters(m_model->filters(), project()->projectConfiguration()); m_projectFilterProvider->updateProjectFilters(project()); } void ProjectFilterConfigPage::reset() { ProjectConfigPage::reset(); m_model->setFilters(readFilters(project()->projectConfiguration())); } void ProjectFilterConfigPage::defaults() { ProjectConfigPage::defaults(); m_model->setFilters(defaultFilters()); } bool ProjectFilterConfigPage::eventFilter(QObject* object, QEvent* event) { Q_ASSERT(object == m_ui->filters); Q_UNUSED(object); if (event->type() == QEvent::KeyRelease) { QKeyEvent* key = static_cast(event); if (key->key() == Qt::Key_Delete && key->modifiers() == Qt::NoModifier && m_ui->filters->currentIndex().isValid()) { // workaround https://bugs.kde.org/show_bug.cgi?id=324451 // there is no other way I see to figure out whether an editor is showing... QWidget* editor = m_ui->filters->viewport()->findChild(); if (editor && editor->isVisible()) { // editor is showing return false; } remove(); return true; } } return false; } void ProjectFilterConfigPage::selectionChanged() { bool hasSelection = m_ui->filters->currentIndex().isValid(); int row = -1; if (hasSelection) { row = m_ui->filters->currentIndex().row(); } m_ui->remove->setEnabled(hasSelection); m_ui->moveDown->setEnabled(hasSelection && row != m_model->rowCount() - 1); m_ui->moveUp->setEnabled(hasSelection && row != 0); } void ProjectFilterConfigPage::add() { m_model->insertRows(m_model->rowCount(), 1); const QModelIndex index = m_model->index(m_model->rowCount() - 1, FilterModel::Pattern, QModelIndex()); m_ui->filters->setCurrentIndex(index); m_ui->filters->edit(index); } void ProjectFilterConfigPage::remove() { Q_ASSERT(m_ui->filters->currentIndex().isValid()); m_model->removeRows(m_ui->filters->currentIndex().row(), 1); } void ProjectFilterConfigPage::moveUp() { Q_ASSERT(m_ui->filters->currentIndex().isValid()); m_model->moveFilterUp(m_ui->filters->currentIndex().row()); } void ProjectFilterConfigPage::moveDown() { Q_ASSERT(m_ui->filters->currentIndex().isValid()); m_model->moveFilterDown(m_ui->filters->currentIndex().row()); } void ProjectFilterConfigPage::checkFilters() { // check for errors, only show one error at once QString errorText; foreach(const Filter& filter, m_model->filters()) { const QString &pattern = filter.pattern.pattern(); if (pattern.isEmpty()) { errorText = i18n("A filter with an empty pattern will match all items. Use \"*\" to make this explicit."); break; } else if (pattern.endsWith('/') && filter.targets == Filter::Files) { errorText = i18n("A filter ending on \"/\" can never match a file."); break; } } if (!errorText.isEmpty()) { m_ui->messageWidget->setMessageType(KMessageWidget::Error); m_ui->messageWidget->setText(errorText); m_ui->messageWidget->animatedShow(); } else { m_ui->messageWidget->animatedHide(); } } void ProjectFilterConfigPage::emitChanged() { checkFilters(); emit changed(); } QString ProjectFilterConfigPage::fullName() const { return i18n("Configure Project Filter"); } QIcon ProjectFilterConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("view-filter")); } QString ProjectFilterConfigPage::name() const { return i18n("Project Filter"); } diff --git a/plugins/projectfilter/projectfilterprovider.cpp b/plugins/projectfilter/projectfilterprovider.cpp index 4fc7923ad..f871ec9b5 100644 --- a/plugins/projectfilter/projectfilterprovider.cpp +++ b/plugins/projectfilter/projectfilterprovider.cpp @@ -1,166 +1,164 @@ /* 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 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 "projectfilterprovider.h" -#include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include "projectfilterdebug.h" #include "projectfilterconfigpage.h" #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(ProjectFilterProviderFactory, "kdevprojectfilter.json", registerPlugin();) ProjectFilterProvider::ProjectFilterProvider( QObject* parent, const QVariantList& /*args*/ ) : IPlugin( QStringLiteral( "kdevprojectfilter" ), parent ) { connect(core()->projectController(), &IProjectController::projectClosing, this, &ProjectFilterProvider::projectClosing); connect(core()->projectController(), &IProjectController::projectAboutToBeOpened, this, &ProjectFilterProvider::projectAboutToBeOpened); // initialize the filters for each project foreach(IProject* project, core()->projectController()->projects()) { updateProjectFilters(project); } } QSharedPointer ProjectFilterProvider::createFilter(IProject* project) const { return QSharedPointer(new ProjectFilter(project, m_filters[project])); } ContextMenuExtension ProjectFilterProvider::contextMenuExtension(Context* context) { ContextMenuExtension ret; if (!context->hasType(Context::ProjectItemContext)) { return ret; } ProjectItemContext* ctx = static_cast( context ); QList items = ctx->items(); // filter out project roots and items in targets QList< ProjectBaseItem* >::iterator it = items.begin(); while (it != items.end()) { if ((*it)->isProjectRoot() || !(*it)->parent()->folder()) { it = items.erase(it); } else { ++it; } } if (items.isEmpty()) { return ret; } QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), i18np("Exclude Item From Project", "Exclude Items From Project", items.size()), this); action->setData(QVariant::fromValue(items)); connect(action, &QAction::triggered, this, &ProjectFilterProvider::addFilterFromContextMenu); ret.addAction(ContextMenuExtension::FileGroup, action); return ret; } void ProjectFilterProvider::addFilterFromContextMenu() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); QList items = action->data().value >(); QHash changedProjectFilters; foreach(ProjectBaseItem* item, items) { if (!changedProjectFilters.contains(item->project())) { changedProjectFilters[item->project()] = readFilters(item->project()->projectConfiguration()); } SerializedFilters& filters = changedProjectFilters[item->project()]; Path path; if (item->target()) { path = Path(item->parent()->path(), item->text()); } else { path = item->path(); } filters << SerializedFilter('/' + item->project()->path().relativePath(path), item->folder() ? Filter::Folders : Filter::Files); } QHash< IProject*, SerializedFilters >::const_iterator it = changedProjectFilters.constBegin(); while (it != changedProjectFilters.constEnd()) { writeFilters(it.value(), it.key()->projectConfiguration()); m_filters[it.key()] = deserialize(it.value()); emit filterChanged(this, it.key()); ++it; } KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18np("A filter for the item was added. To undo, use the project filter settings.", "A filter for the items was added. To undo, use the project filter settings.", items.size()), i18n("Project Filter Added"), QStringLiteral("projectfilter-addfromctxmenu")); } void ProjectFilterProvider::updateProjectFilters(IProject* project) { Filters newFilters = deserialize(readFilters(project->projectConfiguration())); Filters& filters = m_filters[project]; if (filters != newFilters) { projectFilterDebug() << "project filter changed:" << project->name(); filters = newFilters; emit filterChanged(this, project); } } void ProjectFilterProvider::projectAboutToBeOpened(IProject* project) { m_filters[project] = deserialize(readFilters(project->projectConfiguration())); } void ProjectFilterProvider::projectClosing(IProject* project) { m_filters.remove(project); } int ProjectFilterProvider::perProjectConfigPages() const { return 1; } ConfigPage* ProjectFilterProvider::perProjectConfigPage(int i, const ProjectConfigOptions& options, QWidget* parent) { return i == 0 ? new ProjectFilterConfigPage(this, options, parent) : nullptr; } #include "projectfilterprovider.moc" diff --git a/plugins/projectmanagerview/projectbuildsetwidget.cpp b/plugins/projectmanagerview/projectbuildsetwidget.cpp index bde3f7fa2..8ecabc0f6 100644 --- a/plugins/projectmanagerview/projectbuildsetwidget.cpp +++ b/plugins/projectmanagerview/projectbuildsetwidget.cpp @@ -1,271 +1,266 @@ /*************************************************************************** * 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( 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/projectbuildsetwidget.h b/plugins/projectmanagerview/projectbuildsetwidget.h index 2b4f88faf..3c31e0368 100644 --- a/plugins/projectmanagerview/projectbuildsetwidget.h +++ b/plugins/projectmanagerview/projectbuildsetwidget.h @@ -1,68 +1,64 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_PROJECTBUILDSETWIDGET_H #define KDEVPLATFORM_PLUGIN_PROJECTBUILDSETWIDGET_H #include -#include -class QTreeView; -class QToolButton; class ProjectManagerViewPlugin; -class QStringListModel; class ProjectManagerView; namespace KDevelop { class ProjectBaseItem; } namespace Ui { class ProjectBuildSetWidget; } class ProjectBuildSetWidget : public QWidget { Q_OBJECT public: explicit ProjectBuildSetWidget( QWidget* parent = nullptr ); ~ProjectBuildSetWidget() override; void setProjectView( ProjectManagerView* view ); public slots: void selectionChanged(); private slots: void addItems(); void removeItems(); void moveUp(); void moveDown(); void moveToBottom(); void moveToTop(); void showContextMenu( const QPoint& p ); private: ProjectManagerView* m_view; Ui::ProjectBuildSetWidget* m_ui; }; #endif //kate: space-indent on; indent-width 4; replace-tabs on; auto-insert-doxygen on; indent-mode cstyle; diff --git a/plugins/projectmanagerview/projectmanagerview.cpp b/plugins/projectmanagerview/projectmanagerview.cpp index db9072b2c..63dbef610 100644 --- a/plugins/projectmanagerview/projectmanagerview.cpp +++ b/plugins/projectmanagerview/projectmanagerview.cpp @@ -1,284 +1,285 @@ /* 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 #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 ); setFocusProxy(m_ui->projectTreeView); 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(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/projectmanagerview.h b/plugins/projectmanagerview/projectmanagerview.h index 1bec6f104..1ca23a504 100644 --- a/plugins/projectmanagerview/projectmanagerview.h +++ b/plugins/projectmanagerview/projectmanagerview.h @@ -1,95 +1,95 @@ /* This file is part of KDevelop Copyright 2005 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. */ #ifndef KDEVPLATFORM_PLUGIN_PROJECTMANAGERVIEW_H #define KDEVPLATFORM_PLUGIN_PROJECTMANAGERVIEW_H -#include #include #include #include class QModelIndex; +class QAction; namespace Ui { class ProjectManagerView; } class ProjectProxyModel; class VcsOverlayProxyModel; namespace KDevelop { class ProjectBaseItem; class Path; } class ProjectManagerView; class ProjectManagerViewPlugin; //own subclass to the current view can be passed from ProjectManagetView to ProjectManagerViewPlugin class ProjectManagerViewItemContext : public KDevelop::ProjectItemContextImpl { public: ProjectManagerViewItemContext(const QList< KDevelop::ProjectBaseItem* >& items, ProjectManagerView *view); ProjectManagerView *view() const; private: ProjectManagerView *m_view; }; class ProjectManagerView: public QWidget { Q_OBJECT public: ProjectManagerView( ProjectManagerViewPlugin*, QWidget *parent ); ~ProjectManagerView() override; ProjectManagerViewPlugin* plugin() const { return m_plugin; } QList selectedItems() const; void selectItems(const QList &items); void expandItem(KDevelop::ProjectBaseItem *item); protected: bool eventFilter(QObject* obj, QEvent* event) override; private slots: void selectionChanged(); void locateCurrentDocument(); void updateSyncAction(); void open( const KDevelop::Path& ); void toggleHideTargets(bool hidden); private: QModelIndex indexFromView(const QModelIndex& index) const; QModelIndex indexToView(const QModelIndex& index) const; QAction* m_syncAction; QAction* m_toggleTargetsAction; Ui::ProjectManagerView* m_ui; QStringList m_cachedFileList; ProjectProxyModel* m_modelFilter; VcsOverlayProxyModel* m_overlayProxy; ProjectManagerViewPlugin* m_plugin; }; #endif // KDEVPLATFORM_PLUGIN_PROJECTMANAGERVIEW_H diff --git a/plugins/projectmanagerview/projectmanagerviewplugin.h b/plugins/projectmanagerview/projectmanagerviewplugin.h index 5c50286e5..058f1a50c 100644 --- a/plugins/projectmanagerview/projectmanagerviewplugin.h +++ b/plugins/projectmanagerview/projectmanagerviewplugin.h @@ -1,98 +1,97 @@ /* 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. */ #ifndef KDEVPLATFORM_PLUGIN_PROJECTMANAGERVIEWPLUGIN_H #define KDEVPLATFORM_PLUGIN_PROJECTMANAGERVIEWPLUGIN_H -#include #include -#include +#include #include class ProjectBuildSetModel; namespace KDevelop { class ProjectBaseItem; class ProjectBuilder; class ProjectFileItem; class ProjectFolderItem; class ProjectTargetItem; class IProjectBuilder; class IProject; class ContextMenuExtension; class Context; } class ProjectManagerView; class ProjectManagerViewPlugin: public KDevelop::IPlugin { Q_OBJECT public: public: explicit ProjectManagerViewPlugin(QObject *parent, const QVariantList & = QVariantList() ); ~ProjectManagerViewPlugin() override; // Plugin methods void unload() override; KDevelop::ContextMenuExtension contextMenuExtension( KDevelop::Context* ) override; void removeItems(const QList& items); void renameItems(const QList< KDevelop::ProjectBaseItem* >& items); public Q_SLOTS: void buildProjectItems(); void installProjectItems(); void cleanProjectItems(); void copyFromContextMenu(); void pasteFromContextMenu(); protected Q_SLOTS: void closeProjects(); void buildItemsFromContextMenu(); void installItemsFromContextMenu(); void cleanItemsFromContextMenu(); void configureProjectItems(); void pruneProjectItems(); void buildAllProjects(); void addItemsFromContextMenuToBuildset(); void projectConfiguration(); void runTargetsFromContextMenu(); void reloadFromContextMenu(); void createFolderFromContextMenu(); void createFileFromContextMenu(); void removeFromContextMenu(); void removeTargetFilesFromContextMenu(); void renameItemFromContextMenu(); void updateActionState( KDevelop::Context* ctx ); void updateFromBuildSetChange(); private: QList recurseAndFetchCheckedItems( KDevelop::ProjectBaseItem* item ); QList collectItems(); QList collectAllProjects(); void runBuilderJob( KDevelop::BuilderJob::BuildType type, QList items ); class ProjectManagerViewPluginPrivate* const d; }; #endif diff --git a/plugins/projectmanagerview/projecttreeview.cpp b/plugins/projectmanagerview/projecttreeview.cpp index b9d15aefa..d651186dd 100644 --- a/plugins/projectmanagerview/projecttreeview.cpp +++ b/plugins/projectmanagerview/projecttreeview.cpp @@ -1,536 +1,536 @@ /* 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 #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_previousSelection ( 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::projectClosed, this, &ProjectTreeView::projectClosed ); } 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 = 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 = 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::projectClosed(KDevelop::IProject* project) { if ( project == m_previousSelection ) m_previousSelection = nullptr; } QList ProjectTreeView::selectedProjects() { QList itemlist; if ( selectionModel()->hasSelection() ) { QModelIndexList indexes = selectionModel()->selectedRows(); for ( const QModelIndex& index: indexes ) { ProjectBaseItem* item = index.data( ProjectModel::ProjectItemRole ).value(); if ( item ) { itemlist << item; m_previousSelection = item->project(); } } } // add previous selection if nothing is selected right now if ( itemlist.isEmpty() && m_previousSelection ) { itemlist << m_previousSelection->projectItem(); } return itemlist; } KDevelop::IProject* ProjectTreeView::getCurrentProject() { auto itemList = selectedProjects(); if ( !itemList.isEmpty() ) { return itemList.at( 0 )->project(); } return nullptr; } void ProjectTreeView::popupContextMenu( const QPoint &pos ) { QList itemlist; if ( indexAt( pos ).isValid() ) { itemlist = selectedProjects(); } 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 ( IProject* project = getCurrentProject() ) { IProjectController* ip = ICore::self()->projectController(); ip->configureProject( project ); } } void ProjectTreeView::saveState( IProject* project ) { // nullptr won't create a usable saved state, so spare the effort if ( !project ) { return; } KConfigGroup configGroup( ICore::self()->activeSession()->config(), QString( settingsConfigGroup ).append( project->name() ) ); ProjectModelSaver saver; saver.setProject( project ); saver.setView( this ); saver.saveState( configGroup ); } void ProjectTreeView::restoreState( IProject* project ) { if ( !project ) { return; } KConfigGroup configGroup( ICore::self()->activeSession()->config(), QString( settingsConfigGroup ).append( project->name() ) ); ProjectModelSaver saver; saver.setProject( project ); saver.setView( this ); saver.restoreState( configGroup ); } void ProjectTreeView::rowsInserted( const QModelIndex& parent, int start, int end ) { QTreeView::rowsInserted( parent, start, end ); // automatically select row if there is only one if ( model()->rowCount() == 1 ) { selectionModel()->select( model()->index( 0, 0 ), QItemSelectionModel::Select ); } if ( !parent.model() ) { for ( const auto& project: selectedProjects() ) { restoreState( project->project() ); } } } void ProjectTreeView::rowsAboutToBeRemoved( const QModelIndex& parent, int start, int end ) { // automatically select row if there is only one if ( model()->rowCount() == 1 ) { selectionModel()->select( model()->index( 0, 0 ), QItemSelectionModel::Select ); } if ( !parent.model() ) { for ( const auto& project: selectedProjects() ) { saveState( project->project() ); } } QTreeView::rowsAboutToBeRemoved( parent, start, end ); } void ProjectTreeView::aboutToShutdown() { // save all projects, not just the selected ones const auto projects = ICore::self()->projectController()->projects(); for ( const auto& project: projects ) { saveState( project ); } } 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/projectmanagerview/projecttreeview.h b/plugins/projectmanagerview/projecttreeview.h index ae42ffb38..b067f8710 100644 --- a/plugins/projectmanagerview/projecttreeview.h +++ b/plugins/projectmanagerview/projecttreeview.h @@ -1,87 +1,84 @@ /* This file is part of KDevelop Copyright 2005 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. */ #ifndef KDEVPLATFORM_PLUGIN_PROJECTTREEVIEW_H #define KDEVPLATFORM_PLUGIN_PROJECTTREEVIEW_H #include #include class QAbstractProxyModel; -class QAction; -class QItemSelectionModel; -class QMouseEvent; namespace KDevelop { class IProject; class ProjectModel; class ProjectBaseItem; class ProjectFolderItem; class ProjectFileItem; class ProjectTargetItem; class ProjectBaseItem; class NavigationToolTip; class Path; } class ProjectTreeView: public QTreeView { Q_OBJECT public: explicit ProjectTreeView( QWidget *parent = nullptr ); ~ProjectTreeView() override; static QModelIndex mapFromSource(const QAbstractProxyModel* proxy, const QModelIndex& sourceIdx); bool event(QEvent* event) override; Q_SIGNALS: void activate( const KDevelop::Path &url ); protected Q_SLOTS: void slotActivated( const QModelIndex &index ); void popupContextMenu( const QPoint &pos ); void openProjectConfig(); void saveState(KDevelop::IProject* project = nullptr); void restoreState(KDevelop::IProject* project = nullptr); void aboutToShutdown(); void projectClosed(KDevelop::IProject* project); void rowsInserted(const QModelIndex& parent, int start, int end) override; void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; protected: void keyPressEvent(QKeyEvent *event) override; void dropEvent(QDropEvent* event) override; void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override; private: QModelIndex mapFromItem(const KDevelop::ProjectBaseItem* item); KDevelop::ProjectBaseItem* itemAtPos(QPoint pos); QList selectedProjects(); KDevelop::IProject* getCurrentProject(); QPointer m_previousSelection; QPointer m_tooltip; QPersistentModelIndex m_idx; }; #endif // KDEVPLATFORM_PLUGIN_PROJECTTREEVIEW_H diff --git a/plugins/quickopen/actionsquickopenprovider.cpp b/plugins/quickopen/actionsquickopenprovider.cpp index 833850cb1..248848959 100644 --- a/plugins/quickopen/actionsquickopenprovider.cpp +++ b/plugins/quickopen/actionsquickopenprovider.cpp @@ -1,138 +1,137 @@ /* * This file is part of KDevelop * * Copyright 2015 Aleix Pol Gonzalez * * 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 "actionsquickopenprovider.h" -#include + #include #include #include -#include #include #include #include using namespace KDevelop; class ActionsQuickOpenItem : public QuickOpenDataBase { public: ActionsQuickOpenItem(const QString& display, QAction* action) : QuickOpenDataBase() , m_action(action) , m_display(display) {} QString text() const override { return m_display; } QString htmlDescription() const override { QString desc = m_action->toolTip(); const QKeySequence shortcut = m_action->shortcut(); if (!shortcut.isEmpty()) { desc = i18nc("description (shortcut)", "%1 (%2)", desc, shortcut.toString()); } return desc; } bool execute(QString&) override { m_action->trigger(); return true; } QIcon icon() const override { // note: not the best icon, but can't find anything better static const QIcon fallbackIcon = QIcon::fromTheme("system-run"); const QIcon icon = m_action->icon(); if (icon.isNull()) { return fallbackIcon; } return icon; } private: QAction* m_action; ///needed because it won't have the "E&xit" ampersand QString m_display; }; ActionsQuickOpenProvider::ActionsQuickOpenProvider() { } void ActionsQuickOpenProvider::setFilterText(const QString& text) { if (text.size() < 2) { return; } m_results.clear(); const QList collections = KActionCollection::allCollections(); QRegularExpression mnemonicRx(QStringLiteral("^(.*)&(.+)$")); for (KActionCollection* c : collections) { QList actions = c->actions(); foreach (QAction* action, actions) { if (!action->isEnabled()) { continue; } QString display = action->text(); QRegularExpressionMatch match = mnemonicRx.match(display); if (match.hasMatch()) { display = match.captured(1) + match.captured(2); } if (display.contains(text, Qt::CaseInsensitive)) { m_results += QuickOpenDataPointer(new ActionsQuickOpenItem(display, action)); } } } } uint ActionsQuickOpenProvider::unfilteredItemCount() const { uint ret = 0; QList collections = KActionCollection::allCollections(); foreach (KActionCollection* c, collections) { ret += c->count(); } return ret; } QuickOpenDataPointer ActionsQuickOpenProvider::data(uint row) const { return m_results.at(row); } uint ActionsQuickOpenProvider::itemCount() const { return m_results.count(); } void ActionsQuickOpenProvider::reset() { m_results.clear(); } diff --git a/plugins/quickopen/documentationquickopenprovider.cpp b/plugins/quickopen/documentationquickopenprovider.cpp index 6d0e72d6a..43c3e55a4 100644 --- a/plugins/quickopen/documentationquickopenprovider.cpp +++ b/plugins/quickopen/documentationquickopenprovider.cpp @@ -1,150 +1,149 @@ /* * This file is part of KDevelop * * Copyright 2013 Aleix Pol Gonzalez * * 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 "documentationquickopenprovider.h" #include #include #include #include #include -#include #include using namespace KDevelop; class DocumentationQuickOpenItem : public QuickOpenDataBase { public: DocumentationQuickOpenItem(const QModelIndex& data, IDocumentationProvider* p) : QuickOpenDataBase() , m_data(data) , m_provider(p) {} QString text() const override { return m_data.data().toString(); } QString htmlDescription() const override { return i18n("Documentation in the %1", m_provider->name()); } bool execute(QString&) override { IDocumentation::Ptr docu = m_provider->documentationForIndex(m_data); if (docu) { ICore::self()->documentationController()->showDocumentation(docu); } return docu; } QIcon icon() const override { return m_provider->icon(); } private: QModelIndex m_data; IDocumentationProvider* m_provider; }; namespace { uint recursiveRowCount(const QAbstractItemModel* m, const QModelIndex& idx) { uint rows = m->rowCount(idx); uint ret = rows; for (uint i = 0; i < rows; i++) { ret += recursiveRowCount(m, m->index(i, 0, idx)); } return ret; } void matchingIndexes(const QAbstractItemModel* m, const QString& match, const QModelIndex& idx, QList& ret, int& preferred) { if (m->hasChildren(idx)) { for (int i = 0, rows = m->rowCount(); i < rows; i++) { matchingIndexes(m, match, m->index(i, 0, idx), ret, preferred); } } else { int index = idx.data().toString().indexOf(match, 0, Qt::CaseInsensitive); if (index == 0) { ret.insert(preferred++, idx); } else if (index > 0) { ret.append(idx); } } } } DocumentationQuickOpenProvider::DocumentationQuickOpenProvider() { connect(ICore::self()->documentationController(), &IDocumentationController::providersChanged, this, &DocumentationQuickOpenProvider::reset); } void DocumentationQuickOpenProvider::setFilterText(const QString& text) { if (text.size() < 2) { return; } m_results.clear(); int split = 0; QList providers = ICore::self()->documentationController()->documentationProviders(); foreach (IDocumentationProvider* p, providers) { QList idxs; int internalSplit = 0; int i = 0; matchingIndexes(p->indexModel(), text, QModelIndex(), idxs, internalSplit); foreach (const QModelIndex& idx, idxs) { m_results.insert(split + i, QuickOpenDataPointer(new DocumentationQuickOpenItem(idx, p))); i++; } split += internalSplit; } } uint DocumentationQuickOpenProvider::unfilteredItemCount() const { uint ret = 0; QList providers = ICore::self()->documentationController()->documentationProviders(); foreach (IDocumentationProvider* p, providers) { ret += recursiveRowCount(p->indexModel(), QModelIndex()); } return ret; } QuickOpenDataPointer DocumentationQuickOpenProvider::data(uint row) const { return m_results.at(row); } uint DocumentationQuickOpenProvider::itemCount() const { return m_results.size(); } void DocumentationQuickOpenProvider::reset() { m_results.clear(); } diff --git a/plugins/quickopen/duchainitemquickopen.cpp b/plugins/quickopen/duchainitemquickopen.cpp index 80d867320..258523c21 100644 --- a/plugins/quickopen/duchainitemquickopen.cpp +++ b/plugins/quickopen/duchainitemquickopen.cpp @@ -1,252 +1,251 @@ /* 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 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/expandingdelegate.cpp b/plugins/quickopen/expandingtree/expandingdelegate.cpp index 2cedb3879..db12e8f29 100644 --- a/plugins/quickopen/expandingtree/expandingdelegate.cpp +++ b/plugins/quickopen/expandingtree/expandingdelegate.cpp @@ -1,370 +1,369 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * 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 "expandingdelegate.h" #include #include #include -#include #include #include #include "expandingwidgetmodel.h" #include "../debug.h" ExpandingDelegate::ExpandingDelegate(ExpandingWidgetModel* model, QObject* parent) : QItemDelegate(parent) , m_model(model) { } //Gets the background-color in the way QItemDelegate does it static QColor getUsedBackgroundColor(const QStyleOptionViewItem& option, const QModelIndex& index) { if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } return option.palette.brush(cg, QPalette::Highlight).color(); } else { QVariant value = index.data(Qt::BackgroundRole); if ((value).canConvert()) { return qvariant_cast(value).color(); } } return QApplication::palette().base().color(); } static void dampColors(QColor& col) { //Reduce the colors that are less visible to the eye, because they are closer to black when it comes to contrast //The most significant color to the eye is green. Then comes red, and then blue, with blue _much_ less significant. col.setBlue(0); col.setRed(col.red() / 2); } //A hack to compute more eye-focused contrast values static double readabilityContrast(QColor foreground, QColor background) { dampColors(foreground); dampColors(background); return abs(foreground.green() - background.green()) + abs(foreground.red() - background.red()) + abs(foreground.blue() - background.blue()); } void ExpandingDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optionOld, const QModelIndex& index) const { QStyleOptionViewItem option(optionOld); m_currentIndex = index; adjustStyle(index, option); const QModelIndex sourceIndex = model()->mapToSource(index); if (index.column() == 0) { model()->placeExpandingWidget(sourceIndex); } //Make sure the decorations are painted at the top, because the center of expanded items will be filled with the embedded widget. if (model()->isPartiallyExpanded(sourceIndex) == ExpandingWidgetModel::ExpandUpwards) { m_cachedAlignment = Qt::AlignBottom; } else { m_cachedAlignment = Qt::AlignTop; } option.decorationAlignment = m_cachedAlignment; option.displayAlignment = m_cachedAlignment; //qCDebug( PLUGIN_QUICKOPEN ) << "Painting row " << index.row() << ", column " << index.column() << ", internal " << index.internalPointer() << ", drawselected " << option.showDecorationSelected << ", selected " << (option.state & QStyle::State_Selected); m_cachedHighlights.clear(); m_backgroundColor = getUsedBackgroundColor(option, index); if (model()->indexIsItem(sourceIndex)) { m_currentColumnStart = 0; m_cachedHighlights = createHighlighting(index, option); } /*qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for line:"; foreach (const QTextLayout::FormatRange& fr, m_cachedHighlights) qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << " format ";*/ QItemDelegate::paint(painter, option, index); ///This is a bug workaround for the Qt raster paint engine: It paints over widgets embedded into the viewport when updating due to mouse events ///@todo report to Qt Software if (model()->isExpanded(sourceIndex) && model()->expandingWidget(sourceIndex)) { model()->expandingWidget(sourceIndex)->update(); } } QList ExpandingDelegate::createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const { Q_UNUSED(index); Q_UNUSED(option); return QList(); } QSize ExpandingDelegate::basicSizeHint(const QModelIndex& index) const { return QItemDelegate::sizeHint(QStyleOptionViewItem(), index); } QSize ExpandingDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { const QModelIndex sourceIndex = model()->mapToSource(index); QSize s = QItemDelegate::sizeHint(option, index); if (model()->isExpanded(sourceIndex) && model()->expandingWidget(sourceIndex)) { QWidget* widget = model()->expandingWidget(sourceIndex); QSize widgetSize = widget->size(); s.setHeight(widgetSize.height() + s.height() + 10); //10 is the sum that must match exactly the offsets used in ExpandingWidgetModel::placeExpandingWidgets } else if (model()->isPartiallyExpanded(sourceIndex) != ExpandingWidgetModel::ExpansionType::NotExpanded) { s.setHeight(s.height() + 30 + 10); } return s; } void ExpandingDelegate::adjustStyle(const QModelIndex& index, QStyleOptionViewItem& option) const { Q_UNUSED(index) Q_UNUSED(option) } void ExpandingDelegate::adjustRect(QRect& rect) const { const QModelIndex sourceIndex = model()->mapToSource(m_currentIndex); if (!model()->indexIsItem(sourceIndex) /*&& m_currentIndex.column() == 0*/) { rect.setLeft(model()->treeView()->columnViewportPosition(0)); int columnCount = model()->columnCount(sourceIndex.parent()); if (!columnCount) { return; } rect.setRight(model()->treeView()->columnViewportPosition(columnCount - 1) + model()->treeView()->columnWidth(columnCount - 1)); } } void ExpandingDelegate::drawDisplay(QPainter* painter, const QStyleOptionViewItem& option, const QRect& _rect, const QString& text) const { QRect rect(_rect); adjustRect(rect); QTextLayout layout(text, option.font, painter->device()); #if QT_VERSION < 0x050600 QList additionalFormats; #else QVector additionalFormats; #endif int missingFormats = text.length(); for (int i = 0; i < m_cachedHighlights.count(); ++i) { if (m_cachedHighlights[i].start + m_cachedHighlights[i].length <= m_currentColumnStart) { continue; } if (additionalFormats.isEmpty()) { if (i != 0 && m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length > m_currentColumnStart) { QTextLayout::FormatRange before; before.start = 0; before.length = m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length - m_currentColumnStart; before.format = m_cachedHighlights[i - 1].format; additionalFormats.append(before); } } QTextLayout::FormatRange format; format.start = m_cachedHighlights[i].start - m_currentColumnStart; format.length = m_cachedHighlights[i].length; format.format = m_cachedHighlights[i].format; additionalFormats.append(format); } if (!additionalFormats.isEmpty()) { missingFormats = text.length() - (additionalFormats.back().length + additionalFormats.back().start); } if (missingFormats > 0) { QTextLayout::FormatRange format; format.start = text.length() - missingFormats; format.length = missingFormats; QTextCharFormat fm; fm.setForeground(option.palette.text()); format.format = fm; additionalFormats.append(format); } if (m_backgroundColor.isValid()) { QColor background = m_backgroundColor; // qCDebug(PLUGIN_QUICKOPEN) << text << "background:" << background.name(); //Now go through the formats, and make sure the contrast background/foreground is readable for (int a = 0; a < additionalFormats.size(); ++a) { QColor currentBackground = background; if (additionalFormats[a].format.hasProperty(QTextFormat::BackgroundBrush)) { currentBackground = additionalFormats[a].format.background().color(); } QColor currentColor = additionalFormats[a].format.foreground().color(); double currentContrast = readabilityContrast(currentColor, currentBackground); QColor invertedColor(0xffffffff - additionalFormats[a].format.foreground().color().rgb()); double invertedContrast = readabilityContrast(invertedColor, currentBackground); // qCDebug(PLUGIN_QUICKOPEN) << "values:" << invertedContrast << currentContrast << invertedColor.name() << currentColor.name(); if (invertedContrast > currentContrast) { // qCDebug(PLUGIN_QUICKOPEN) << text << additionalFormats[a].length << "switching from" << currentColor.name() << "to" << invertedColor.name(); QBrush b(additionalFormats[a].format.foreground()); b.setColor(invertedColor); additionalFormats[a].format.setForeground(b); } } } for (int a = additionalFormats.size() - 1; a >= 0; --a) { if (additionalFormats[a].length == 0) { additionalFormats.removeAt(a); } else { ///For some reason the text-formats seem to be invalid in some way, sometimes ///@todo Fix this properly, it sucks not copying everything over QTextCharFormat fm; fm.setForeground(QBrush(additionalFormats[a].format.foreground().color())); fm.setBackground(additionalFormats[a].format.background()); fm.setUnderlineStyle(additionalFormats[a].format.underlineStyle()); fm.setUnderlineColor(additionalFormats[a].format.underlineColor()); fm.setFontWeight(additionalFormats[a].format.fontWeight()); additionalFormats[a].format = fm; } } // qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for text [" << text << "] col start " << m_currentColumnStart << ":"; // foreach (const QTextLayout::FormatRange& fr, additionalFormats) // qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << "foreground" << fr.format.foreground() << "background" << fr.format.background(); #if QT_VERSION < 0x050600 layout.setAdditionalFormats(additionalFormats); #else layout.setFormats(additionalFormats); #endif QTextOption to; to.setAlignment(m_cachedAlignment); to.setWrapMode(QTextOption::WrapAnywhere); layout.setTextOption(to); layout.beginLayout(); QTextLine line = layout.createLine(); line.setLineWidth(rect.width()); layout.endLayout(); //We need to do some hand layouting here if (to.alignment() & Qt::AlignBottom) { layout.draw(painter, QPoint(rect.left(), rect.bottom() - ( int )line.height())); } else { layout.draw(painter, rect.topLeft()); } return; //if (painter->fontMetrics().width(text) > textRect.width() && !text.contains(QLatin1Char('\n'))) //str = elidedText(option.fontMetrics, textRect.width(), option.textElideMode, text); //qt_format_text(option.font, textRect, option.displayAlignment, str, 0, 0, 0, 0, painter); } void ExpandingDelegate::drawDecoration(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QPixmap& pixmap) const { const QModelIndex sourceIndex = model()->mapToSource(m_currentIndex); if (model()->indexIsItem(sourceIndex)) { QItemDelegate::drawDecoration(painter, option, rect, pixmap); } } void ExpandingDelegate::drawBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index) QStyleOptionViewItem opt = option; //initStyleOption(&opt, index); //Problem: This isn't called at all, because drawBrackground is not virtual :-/ QStyle* style = model()->treeView()->style() ? model()->treeView()->style() : QApplication::style(); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); } ExpandingWidgetModel* ExpandingDelegate::model() const { return m_model; } void ExpandingDelegate::heightChanged() const { } bool ExpandingDelegate::editorEvent(QEvent* event, QAbstractItemModel* /*model*/, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) { if (event->type() == QEvent::MouseButtonRelease) { const QModelIndex sourceIndex = model()->mapToSource(index); event->accept(); model()->setExpanded(sourceIndex, !model()->isExpanded(sourceIndex)); heightChanged(); return true; } else { event->ignore(); } return false; } QList ExpandingDelegate::highlightingFromVariantList(const QList& customHighlights) const { QList ret; for (int i = 0; i + 2 < customHighlights.count(); i += 3) { if (!customHighlights[i].canConvert(QVariant::Int) || !customHighlights[i + 1].canConvert(QVariant::Int) || !customHighlights[i + 2].canConvert()) { qWarning() << "Unable to convert triple to custom formatting."; continue; } QTextLayout::FormatRange format; format.start = customHighlights[i].toInt(); format.length = customHighlights[i + 1].toInt(); format.format = customHighlights[i + 2].value().toCharFormat(); if (!format.format.isValid()) { qWarning() << "Format is not valid"; } ret << format; } return ret; } diff --git a/plugins/quickopen/expandingtree/expandingdelegate.h b/plugins/quickopen/expandingtree/expandingdelegate.h index cbb23321b..c9388ea2d 100644 --- a/plugins/quickopen/expandingtree/expandingdelegate.h +++ b/plugins/quickopen/expandingtree/expandingdelegate.h @@ -1,92 +1,91 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * 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. */ #ifndef KDEVPLATFORM_PLUGIN_EXPANDINGDELEGATE_H #define KDEVPLATFORM_PLUGIN_EXPANDINGDELEGATE_H #include -#include +#include #include -#include class KateRenderer; class KateCompletionWidget; class KateDocument; class KateTextLine; class ExpandingWidgetModel; class QVariant; class QStyleOptionViewItem; /** * This is a delegate that cares, together with ExpandingWidgetModel, about embedded widgets in tree-view. * */ class ExpandingDelegate : public QItemDelegate { Q_OBJECT public: explicit ExpandingDelegate(ExpandingWidgetModel* model, QObject* parent = nullptr); // Overridden to create highlighting for current index void paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; // Returns the basic size-hint as reported by QItemDelegate QSize basicSizeHint(const QModelIndex& index) const; ExpandingWidgetModel* model() const; protected: //Called right before paint to allow last-minute changes to the style virtual void adjustStyle(const QModelIndex& index, QStyleOptionViewItem& option) const; void drawDisplay (QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QString& text) const override; QSize sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const override; bool editorEvent (QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override; virtual void drawBackground (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; void drawDecoration(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QPixmap& pixmap) const override; //option can be changed virtual QList createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const; void adjustRect(QRect& rect) const; /** * Creates a list of FormatRanges as should be returned by createHighlighting from a list of QVariants as described in the kde header ktexteditor/codecompletionmodel.h * */ QList highlightingFromVariantList(const QList& customHighlights) const; //Called when an item was expanded/unexpanded and the height changed virtual void heightChanged() const; //Initializes the style options from the index void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const; mutable int m_currentColumnStart; //Text-offset for custom highlighting, will be applied to m_cachedHighlights(Only highlights starting after this will be used). Shoult be zero of the highlighting is not taken from kate. mutable QList m_currentColumnStarts; mutable QList m_cachedHighlights; mutable Qt::Alignment m_cachedAlignment; mutable QColor m_backgroundColor; mutable QModelIndex m_currentIndex; private: ExpandingWidgetModel* m_model; }; #endif // KDEVPLATFORM_PLUGIN_EXPANDINGDELEGATE_H diff --git a/plugins/quickopen/expandingtree/expandingtree.cpp b/plugins/quickopen/expandingtree/expandingtree.cpp index 5b8fd4958..7db1ed93c 100644 --- a/plugins/quickopen/expandingtree/expandingtree.cpp +++ b/plugins/quickopen/expandingtree/expandingtree.cpp @@ -1,90 +1,88 @@ /* 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 "expandingtree.h" -#include -#include #include #include #include #include "expandingwidgetmodel.h" #include #include using namespace KDevelop; ExpandingTree::ExpandingTree(QWidget* parent) : QTreeView(parent) { m_drawText.documentLayout()->setPaintDevice(this); setUniformRowHeights(false); } void ExpandingTree::setModel(QAbstractItemModel* model) { Q_ASSERT(!model || qobject_cast( qobject_cast(model)->sourceModel()) ); QTreeView::setModel(model); } void ExpandingTree::drawRow(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QTreeView::drawRow(painter, option, index); const ExpandingWidgetModel* eModel = qobject_cast(qobject_cast(model())->sourceModel()); Q_ASSERT(eModel); const QModelIndex sourceIndex = eModel->mapToSource(index); if (eModel->isPartiallyExpanded(sourceIndex) != ExpandingWidgetModel::ExpansionType::NotExpanded) { QRect rect = eModel->partialExpandRect(sourceIndex); if (rect.isValid()) { painter->fillRect(rect, QBrush(0xffffffff)); QAbstractTextDocumentLayout::PaintContext ctx; // since arbitrary HTML can be shown use a black on white color scheme here ctx.palette = QPalette(Qt::black, Qt::white); ctx.clip = QRectF(0, 0, rect.width(), rect.height());; painter->setViewTransformEnabled(true); painter->translate(rect.left(), rect.top()); m_drawText.setHtml(eModel->partialExpandText(sourceIndex)); m_drawText.setPageSize(QSizeF(rect.width(), rect.height())); m_drawText.documentLayout()->draw(painter, ctx); painter->translate(-rect.left(), -rect.top()); } } } int ExpandingTree::sizeHintForColumn(int column) const { return columnWidth(column); } void ExpandingTree::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const { const auto& path = index.data(ProjectPathRole).value(); if (path.isValid()) { const auto color = WidgetColorizer::colorForId(qHash(path), palette(), true); WidgetColorizer::drawBranches(this, painter, rect, index, color); } QTreeView::drawBranches(painter, rect, index); } diff --git a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp index a166465ed..2023667fd 100644 --- a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp +++ b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp @@ -1,592 +1,592 @@ /* 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 #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(); } QModelIndex ExpandingWidgetModel::mapFromSource(const QModelIndex& index) const { const auto proxyModel = qobject_cast(treeView()->model()); Q_ASSERT(proxyModel); Q_ASSERT(!index.isValid() || index.model() == this); return proxyModel->mapFromSource(index); } QModelIndex ExpandingWidgetModel::mapToSource(const QModelIndex& index) const { const auto proxyModel = qobject_cast(treeView()->model()); Q_ASSERT(proxyModel); Q_ASSERT(!index.isValid() || index.model() == proxyModel); return proxyModel->mapToSource(index); } 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_) { Q_ASSERT(idx_.model() == this); 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) { const QModelIndex viewIndex = mapFromSource(idx); //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(viewIndex); QRect frameRect = treeView()->frameRect(); if (selectedRect.bottom() > frameRect.bottom()) { int diff = selectedRect.bottom() - frameRect.bottom(); //We need to scroll down QModelIndex newTopIndex = viewIndex; QModelIndex nextTopIndex = viewIndex; 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 { Q_ASSERT(idx.model() == this); if (!idx.isValid()) { return QString(); } return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); } QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); 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: const QModelIndex viewIndex = mapFromSource(idx); QModelIndex rightMostIndex = viewIndex; QModelIndex tempIndex = viewIndex; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) rightMostIndex = tempIndex; QRect rect = treeView()->visualRect(viewIndex); 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(viewIndex); } else { bottom -= basicRowHeight(viewIndex); } rect.setTop(top); rect.setBottom(bottom); return rect; } bool ExpandingWidgetModel::isExpandable(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); 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 { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); return m_expandState.contains(idx) && m_expandState[idx] == Expanded; } void ExpandingWidgetModel::setExpanded(QModelIndex idx_, bool expanded) { Q_ASSERT(idx_.model() == this); 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] = nullptr; } } //Eventually partially expand the row if (!expanded && firstColumn(mapToSource(treeView()->currentIndex())) == idx && (isPartiallyExpanded(idx) == ExpandingWidgetModel::ExpansionType::NotExpanded)) { rowSelected(idx); //Partially expand the row. } emit dataChanged(idx, idx); if (treeView()) { treeView()->scrollTo(mapFromSource(idx)); } } } int ExpandingWidgetModel::basicRowHeight(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == treeView()->model()); 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_) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); QWidget* w = nullptr; if (m_expandingWidgets.contains(idx)) { w = m_expandingWidgets[idx]; } if (w && isExpanded(idx)) { if (!idx.isValid()) { return; } const QModelIndex viewIndex = mapFromSource(idx_); QRect rect = treeView()->visualRect(viewIndex); if (!rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height()) { //The item is currently not visible w->hide(); return; } QModelIndex rightMostIndex = viewIndex; QModelIndex tempIndex = viewIndex; 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(viewIndex) + 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 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/expandingtree/expandingwidgetmodel.h b/plugins/quickopen/expandingtree/expandingwidgetmodel.h index f18f6c04d..6720fbe6b 100644 --- a/plugins/quickopen/expandingtree/expandingwidgetmodel.h +++ b/plugins/quickopen/expandingtree/expandingwidgetmodel.h @@ -1,171 +1,167 @@ /* 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. */ #ifndef KDEVPLATFORM_PLUGIN_EXPANDING_WIDGET_MODEL_H #define KDEVPLATFORM_PLUGIN_EXPANDING_WIDGET_MODEL_H #include -#include #include -#include #include -#include class ExpandingDelegate; class ExpandingTree; class KWidget; class QTreeView; -class QTextEdit; /** * Cares about expanding/un-expanding items in a tree-view together with ExpandingDelegate */ class ExpandingWidgetModel : public QAbstractTableModel { Q_OBJECT public: explicit ExpandingWidgetModel(QWidget* parent); virtual ~ExpandingWidgetModel(); enum ExpandingType { NotExpandable = 0, Expandable, Expanded }; ///The following three are convenience-functions for the current item that could be replaced by the later ones ///@return whether the current item can be expanded bool canExpandCurrentItem() const; ///@return whether the current item can be collapsed bool canCollapseCurrentItem() const; ///Expand/collapse the current item void setCurrentItemExpanded(bool); void clearMatchQualities(); ///Unexpand all rows and clear all cached information about them(this includes deleting the expanding-widgets) void clearExpanding(); ///@return whether the row given through index is expandable bool isExpandable(const QModelIndex& index) const; enum ExpansionType { NotExpanded = 0, ExpandDownwards, //The additional(expanded) information is shown UNDER the original information ExpandUpwards //The additional(expanded) information is shown ABOVE the original information }; ///Returns whether the given index is currently partially expanded. Does not do any other checks like calling models for data. ExpansionType isPartiallyExpanded(const QModelIndex& index) const; ///@return whether row is currently expanded bool isExpanded(const QModelIndex& row) const; ///Change the expand-state of the row given through index. The display will be updated. void setExpanded(QModelIndex index, bool expanded); ///Returns the total height added through all open expanding-widgets int expandingWidgetsHeight() const; ///@return the expanding-widget for the given row, if available. Expanding-widgets are in best case available for all expanded rows. ///This does not return the partially-expand widget. QWidget* expandingWidget(const QModelIndex& row) const; ///Amount by which the height of a row increases when it is partially expanded int partiallyExpandWidgetHeight() const; /** * Notifies underlying models that the item was selected, collapses any previous partially expanded line, * checks whether this line should be partially expanded, and eventually does it. * Does nothing when nothing needs to be done. * Does NOT show the expanding-widget. That is done immediately when painting by ExpandingDelegate, * to reduce flickering. @see showPartialExpandWidget() * @param row The row * */ /// virtual void rowSelected(const QModelIndex& row); /// Returns the rectangle for the partially expanded part of the given row /// TODO: Do this via QAIM roles? QRect partialExpandRect(const QModelIndex& row) const; /// TODO: Do this via QAIM roles? QString partialExpandText(const QModelIndex& row) const; ///Places and shows the expanding-widget for the given row, if it should be visible and is valid. ///Also shows the partial-expanding-widget when it should be visible. void placeExpandingWidget(const QModelIndex& row); virtual QTreeView* treeView() const = 0; ///Should return true if the given row should be painted like a contained item(as opposed to label-rows etc.) virtual bool indexIsItem(const QModelIndex& index) const = 0; ///Does not request data from index, this only returns local data like highlighting for expanded rows and similar QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const override; ///Returns the first row that is currently partially expanded. QModelIndex partiallyExpandedRow() const; ///Returns the match-color for the given index, or zero if match-quality could not be computed. uint matchColor(const QModelIndex& index) const; public slots: ///Place or hides all expanding-widgets to the correct positions. Should be called after the view was scrolled. void placeExpandingWidgets(); protected: /** * @return the context-match quality from 0 to 10 if it could be determined, else -1 * */ virtual int contextMatchQuality(const QModelIndex& index) const = 0; QModelIndex mapFromSource(const QModelIndex& index) const; QModelIndex mapToSource(const QModelIndex& index) const; //Makes sure m_expandedIcon and m_collapsedIcon are loaded void cacheIcons() const; static QIcon m_expandedIcon; static QIcon m_collapsedIcon; //Does not update the view void partiallyUnExpand(const QModelIndex& index); //Finds out the basic height of the row represented by the given index. Basic means without respecting any expansion. int basicRowHeight(const QModelIndex& index) const; private: friend ExpandingDelegate; friend ExpandingTree; QMap m_partiallyExpanded; // Store expanding-widgets and cache whether items can be expanded mutable QMap m_expandState; QMap > m_expandingWidgets; //Map rows to their expanding-widgets QMap m_contextMatchQualities; //Map rows to their context-match qualities(undefined if unknown, else 0 to 10). Not used yet, eventually remove. }; /** * Helper-function to merge custom-highlighting variant-lists. * * @param strings A list of strings that should be merged * @param highlights One variant-list for highlighting, as described in the kde header ktextedtor/codecompletionmodel.h * @param gapBetweenStrings How many signs are inserted between 2 strings? * */ QList mergeCustomHighlighting(QStringList strings, QList highlights, int gapBetweenStrings = 0); #endif diff --git a/plugins/quickopen/projectfilequickopen.h b/plugins/quickopen/projectfilequickopen.h index a26755b14..5b46bab2b 100644 --- a/plugins/quickopen/projectfilequickopen.h +++ b/plugins/quickopen/projectfilequickopen.h @@ -1,149 +1,147 @@ /* 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. */ #ifndef PROJECT_FILE_QUICKOPEN #define PROJECT_FILE_QUICKOPEN #include #include #include #include namespace KDevelop { class IProject; class ProjectFileItem; } -class QIcon; - /** * Internal data class for the BaseFileDataProvider and ProjectFileData. */ struct ProjectFile { ProjectFile() : outsideOfProject(false) {} KDevelop::Path path; // project root folder url KDevelop::Path projectPath; // indexed url - only set for project files // currently open documents don't use this! KDevelop::IndexedString indexedPath; // true for files which reside outside of the project root // this happens e.g. for generated files in out-of-source build folders bool outsideOfProject; }; inline bool operator<(const ProjectFile& left, const ProjectFile& right) { if (left.outsideOfProject != right.outsideOfProject) { return !left.outsideOfProject; } return left.path < right.path; } Q_DECLARE_TYPEINFO(ProjectFile, Q_MOVABLE_TYPE); /** * The shared data class that is used by the quick open model. */ class ProjectFileData : public KDevelop::QuickOpenDataBase { public: explicit ProjectFileData(const ProjectFile& file); QString text() const override; QString htmlDescription() const override; bool execute(QString& filterText) override; bool isExpandable() const override; QWidget* expandingWidget() const override; QIcon icon() const override; QList highlighting() const override; QString project() const; KDevelop::Path projectPath() const; private: ProjectFile m_file; }; class BaseFileDataProvider : public KDevelop::QuickOpenDataProviderBase , public KDevelop::PathFilter , public KDevelop::QuickOpenFileSetInterface { Q_OBJECT public: BaseFileDataProvider(); void setFilterText(const QString& text) override; uint itemCount() const override; uint unfilteredItemCount() const override; KDevelop::QuickOpenDataPointer data(uint row) const override; inline KDevelop::Path itemPath(const ProjectFile& data) const { return data.path; } }; /** * QuickOpen data provider for file-completion using project-files. * * It provides all files from all open projects except currently opened ones. */ class ProjectFileDataProvider : public BaseFileDataProvider { Q_OBJECT public: ProjectFileDataProvider(); void reset() override; QSet files() const override; private slots: void projectClosing(KDevelop::IProject*); void projectOpened(KDevelop::IProject*); void fileAddedToSet(KDevelop::ProjectFileItem*); void fileRemovedFromSet(KDevelop::ProjectFileItem*); private: // project files sorted by their url // this is done so we can limit ourselves to a relatively fast // filtering without any expensive sorting in reset(). QList m_projectFiles; }; /** * Quick open data provider for currently opened documents. */ class OpenFilesDataProvider : public BaseFileDataProvider { Q_OBJECT public: void reset() override; QSet files() const override; }; #endif diff --git a/plugins/quickopen/quickopenmodel.h b/plugins/quickopen/quickopenmodel.h index 65198d4f1..5d5f52963 100644 --- a/plugins/quickopen/quickopenmodel.h +++ b/plugins/quickopen/quickopenmodel.h @@ -1,134 +1,133 @@ /* 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. */ #ifndef KDEVPLATFORM_PLUGIN_QUICKOPENMODEL_H #define KDEVPLATFORM_PLUGIN_QUICKOPENMODEL_H #include #include -#include #include #include #include #include "expandingtree/expandingwidgetmodel.h" class QuickOpenModel : public ExpandingWidgetModel { Q_OBJECT public: explicit QuickOpenModel(QWidget* parent); void registerProvider(const QStringList& scopes, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider); /** * Remove provider. * @param provider The provider to remove * @return Whether a provider was removed. If false, the provider was not attached. * */ bool removeProvider(KDevelop::QuickOpenDataProviderBase* provider); ///Returns a list of all scopes that a registered through some providers QStringList allScopes() const; ///Returns a list of all types that a registered through some providers QStringList allTypes() const; /** * @param items The list of items that should be used. * @param scopes The list of scopes that should be used. * When this is called, the state is restart()ed. * */ void enableProviders(const QStringList& items, const QStringList& scopes); ///Reset all providers, unexpand everything, empty caches. void restart(bool keepFilterText = false); QModelIndex index(int, int, const QModelIndex& parent) const override; QModelIndex parent(const QModelIndex&) const override; int rowCount(const QModelIndex&) const override; int unfilteredRowCount() const; int columnCount() const; int columnCount(const QModelIndex&) const override; QVariant data(const QModelIndex&, int) const override; /** * Tries to execute the item currently selected. * Returns true if the quickopen-dialog should be closed. * @param filterText Should be the current content of the filter line-edit. * * If this returns false, and filterText was changed, the change must be put * into the line-edit. That way items may execute by changing the content * of the line-edit. * */ bool execute(const QModelIndex& index, QString& filterText); //The expandingwidgetmodel needs access to the tree-view void setTreeView(QTreeView* view); QTreeView* treeView() const override; virtual QSet fileSet() const; ///This value will be added to the height of all created expanding-widgets void setExpandingWidgetHeightIncrease(int pixels); public slots: void textChanged(const QString& str); private slots: void destroyed(QObject* obj); void resetTimer(); void restart_internal(bool keepFilterText); private: bool indexIsItem(const QModelIndex& index) const override; int contextMatchQuality(const QModelIndex& index) const override; KDevelop::QuickOpenDataPointer getItem(int row, bool noReset = false) const; typedef QHash DataList; mutable DataList m_cachedData; QTreeView* m_treeView; QTimer* m_resetTimer; struct ProviderEntry { ProviderEntry() : enabled(false) { } bool enabled; QSet scopes; QSet types; KDevelop::QuickOpenDataProviderBase* provider; }; //typedef QMultiMap< QString, ProviderEntry > ProviderMap; typedef QList ProviderList; QList m_providers; QString m_filterText; int m_expandingWidgetHeightIncrease; mutable int m_resetBehindRow; QSet m_enabledItems; QSet m_enabledScopes; }; #endif diff --git a/plugins/quickopen/quickopenplugin.cpp b/plugins/quickopen/quickopenplugin.cpp index b71b2a757..b17e999c9 100644 --- a/plugins/quickopen/quickopenplugin.cpp +++ b/plugins/quickopen/quickopenplugin.cpp @@ -1,1185 +1,1174 @@ /* * 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 }; explicit 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 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 nullptr; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url()); if (!ctx) { return nullptr; } KTextEditor::Cursor cursor(view->cursorPosition()); DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor)); while (subCtx && !subCtx->owner()) subCtx = subCtx->parentContext(); Declaration* definition = nullptr; if (!subCtx || !subCtx->owner()) { definition = DUChainUtils::declarationInLine(cursor, ctx); } else { definition = subCtx->owner(); } if (!definition) { 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 lines = ICore::self()->uiController()->activeMainWindow()->findChildren(name); foreach (QuickOpenLineEdit* line, lines) { if (line->isVisible()) { return line; } } return nullptr; } 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; 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::NavigationGroup, m_quickOpenDeclaration); } if (isDef) { menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, 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 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 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 = 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 = nullptr; int distanceBefore = INT_MIN; 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(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(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); dialog->widget()->setSortingEnabled(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(nullptr) { } ~OutlineQuickopenWidgetCreator() override { delete m_creator; } QuickOpenWidget* createWidget() override { delete m_creator; m_creator = new CreateOutlineDialog; m_creator->start(); if (!m_creator->dialog) { 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 = 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(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(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 = 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 98f7f90a4..02c4597b6 100644 --- a/plugins/quickopen/quickopenwidget.cpp +++ b/plugins/quickopen/quickopenwidget.cpp @@ -1,557 +1,558 @@ /* * 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 "debug.h" #include "expandingtree/expandingdelegate.h" #include "quickopenmodel.h" #include #include #include +#include #include #include #include #include #include #include using namespace KDevelop; class QuickOpenDelegate : public ExpandingDelegate { Q_OBJECT public: explicit 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(); } } bool QuickOpenWidget::sortingEnabled() const { return m_sortingEnabled; } void QuickOpenWidget::setSortingEnabled(bool enabled) { m_sortingEnabled = enabled; } 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(nullptr); ui.list->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_model->setTreeView(ui.list); // set up proxy filter delete m_proxy; m_proxy = nullptr; if (sortingEnabled()) { auto sortFilterProxyModel = new QSortFilterProxyModel(this); sortFilterProxyModel->setDynamicSortFilter(true); m_proxy = sortFilterProxyModel; } else { m_proxy = new QIdentityProxyModel(this); } m_proxy->setSourceModel(m_model); if (sortingEnabled()) { m_proxy->sort(1); } ui.list->setModel(m_proxy); 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(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(m_proxy->mapFromSource(currentIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current); callRowSelected(); } void QuickOpenWidget::callRowSelected() { const QModelIndex currentIndex = ui.list->currentIndex(); if (currentIndex.isValid()) { m_model->rowSelected(m_proxy->mapToSource(currentIndex)); } else { qCDebug(PLUGIN_QUICKOPEN) << "current index is not valid"; } } void QuickOpenWidget::accept() { QString filterText = ui.searchLine->text(); m_model->execute(m_proxy->mapToSource(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 = m_proxy->mapToSource(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 = m_proxy->mapToSource(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) { const QModelIndex index = m_proxy->mapToSource(ui.list->currentIndex()); QWidget* w = m_model->expandingWidget(index); 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 const QModelIndex index = m_proxy->mapToSource(ui.list->currentIndex()); QWidget* w = m_model->expandingWidget(index); if (KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast(w)) { interface->previous(); return true; } } else { QModelIndex row = m_proxy->mapToSource(ui.list->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 const QModelIndex index = m_proxy->mapToSource(ui.list->currentIndex()); QWidget* w = m_model->expandingWidget(index); if (KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast(w)) { interface->next(); return true; } } else { QModelIndex row = m_proxy->mapToSource(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 const QModelIndex index = m_proxy->mapToSource(ui.list->currentIndex()); QWidget* w = m_model->expandingWidget(index); 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(m_proxy->mapToSource(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/quickopenwidget.h b/plugins/quickopen/quickopenwidget.h index 26131fd0d..78d44f0cb 100644 --- a/plugins/quickopen/quickopenwidget.h +++ b/plugins/quickopen/quickopenwidget.h @@ -1,120 +1,120 @@ /* * 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. */ #ifndef KDEVPLATFORM_PLUGIN_QUICKOPENWIDGET_H #define KDEVPLATFORM_PLUGIN_QUICKOPENWIDGET_H #include "ui_quickopenwidget.h" #include #include #include -#include class QuickOpenModel; +class QAbstractProxyModel; class QLineEdit; /// Will delete itself once the dialog is closed, so use QPointer when referencing it permanently class QuickOpenWidget : public QMenu { Q_OBJECT public: /** * @param initialItems List of items that should initially be enabled in the quickopen-list. If empty, all are enabled. * @param initialScopes List of scopes that should initially be enabled in the quickopen-list. If empty, all are enabled. * @param listOnly when this is true, the given items will be listed, but all filtering using checkboxes is disabled. * @param noSearchField when this is true, no search-line is shown. * */ QuickOpenWidget(QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly = false, bool noSearchField = false); ~QuickOpenWidget() override; void setPreselectedText(const QString& text); void prepareShow(); void setAlternativeSearchField(QLineEdit* alterantiveSearchField); bool sortingEnabled() const; void setSortingEnabled(bool enabled); //Shows OK + Cancel. By default they are hidden void showStandardButtons(bool show); void showSearchField(bool show); signals: void scopesChanged(const QStringList& scopes); void itemsChanged(const QStringList& scopes); void ready(); private slots: void callRowSelected(); void updateTimerInterval(bool cheapFilterChange); void accept(); void textChanged(const QString& str); void updateProviders(); void doubleClicked (const QModelIndex& index); void applyFilter(); private: void showEvent(QShowEvent*) override; bool eventFilter (QObject* watched, QEvent* event) override; void avoidMenuAltFocus(); QuickOpenModel* m_model; QAbstractProxyModel* m_proxy = nullptr; bool m_sortingEnabled = false; bool m_expandedTemporary, m_hadNoCommandSinceAlt; QTime m_altDownTime; QString m_preselectedText; QTimer m_filterTimer; QString m_filter; public: Ui::QuickOpenWidget ui; friend class QuickOpenWidgetDialog; friend class QuickOpenPlugin; friend class QuickOpenLineEdit; }; class QuickOpenWidgetDialog : public QObject { Q_OBJECT public: QuickOpenWidgetDialog(QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly = false, bool noSearchField = false); ~QuickOpenWidgetDialog() override; /// Shows the dialog void run(); QuickOpenWidget* widget() const { return m_widget; } private: QDialog* m_dialog; /// @warning m_dialog is also the parent QuickOpenWidget* m_widget; }; #endif diff --git a/plugins/standardoutputview/standardoutputview.h b/plugins/standardoutputview/standardoutputview.h index 8382dbbae..5dc8139b9 100644 --- a/plugins/standardoutputview/standardoutputview.h +++ b/plugins/standardoutputview/standardoutputview.h @@ -1,94 +1,94 @@ /* 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. */ #ifndef KDEVPLATFORM_PLUGIN_STANDARDOUTPUTVIEW_H #define KDEVPLATFORM_PLUGIN_STANDARDOUTPUTVIEW_H #include #include -#include +#include template class QList; class QAbstractItemModel; class QString; class QModelIndex; class QAbstractItemDelegate; class OutputWidget; class ToolViewData; /** @author Andreas Pakulat */ namespace Sublime { class View; } class StandardOutputView : public KDevelop::IPlugin, public KDevelop::IOutputView { Q_OBJECT Q_INTERFACES( KDevelop::IOutputView ) public: explicit StandardOutputView(QObject *parent = nullptr, const QVariantList &args = QVariantList()); ~StandardOutputView() override; int standardToolView( KDevelop::IOutputView::StandardToolView view ) override; int registerToolView( const QString& title, KDevelop::IOutputView::ViewType type = KDevelop::IOutputView::OneView, const QIcon& icon = QIcon(), KDevelop::IOutputView::Options option = ShowItemsButton, const QList& actionList = QList()) override; int registerOutputInToolView( int toolviewId, const QString& title, KDevelop::IOutputView::Behaviours behaviour = KDevelop::IOutputView::AllowUserClose ) override; void raiseOutput( int id ) override; void setModel( int outputId, QAbstractItemModel* model ) override; void setDelegate( int outputId, QAbstractItemDelegate* delegate ) override; OutputWidget* outputWidgetForId( int outputId ) const; void removeToolView( int toolviewId ) override; void removeOutput( int outputId ) override; void scrollOutputTo( int outputId, const QModelIndex& idx ) override; void setTitle(int outputId, const QString& title) override; public Q_SLOTS: void removeSublimeView( Sublime::View* ); Q_SIGNALS: void activated( const QModelIndex& ); void outputRemoved( int toolviewId, int outputId ); void toolViewRemoved( int toolviewId ); private: QMap m_toolviews; QList m_ids; QMap m_standardViews; }; #endif // KDEVPLATFORM_PLUGIN_STANDARDOUTPUTVIEW_H diff --git a/plugins/standardoutputview/standardoutputviewmetadata.cpp b/plugins/standardoutputview/standardoutputviewmetadata.cpp index e1a9954e0..a7df11c3b 100644 --- a/plugins/standardoutputview/standardoutputviewmetadata.cpp +++ b/plugins/standardoutputview/standardoutputviewmetadata.cpp @@ -1,29 +1,28 @@ /* KDevelop Standard OutputView * * Copyright 2014 Alex Richardson * * 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 -#include // this is split out to a separate file so that compiling the test doesn't need the json file K_PLUGIN_FACTORY_WITH_JSON(StandardOutputViewFactory, "kdevstandardoutputview.json", registerPlugin(); ) #include "standardoutputviewmetadata.moc" diff --git a/plugins/standardoutputview/tests/test_standardoutputview.cpp b/plugins/standardoutputview/tests/test_standardoutputview.cpp index 8b56ecbd4..7d3b82064 100644 --- a/plugins/standardoutputview/tests/test_standardoutputview.cpp +++ b/plugins/standardoutputview/tests/test_standardoutputview.cpp @@ -1,234 +1,230 @@ /* 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 #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 = 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 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(), 11); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); QList addedActions; 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(), 16); 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->m_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->m_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->m_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->m_views.value(outputId[0])->model(), model); QCOMPARE(outputWidget->m_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.h b/plugins/standardoutputview/toolviewdata.h index bf6b7e744..29e70f57b 100644 --- a/plugins/standardoutputview/toolviewdata.h +++ b/plugins/standardoutputview/toolviewdata.h @@ -1,81 +1,78 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_TOOLVIEWDATA_H #define KDEVPLATFORM_PLUGIN_TOOLVIEWDATA_H #include #include #include namespace Sublime { class View; } -class QItemDelegate; -class QString; class QAbstractItemModel; -class QListView; class StandardOutputView; class ToolViewData; class OutputData : public QObject { Q_OBJECT public: explicit OutputData( ToolViewData* tv ); QAbstractItemDelegate* delegate; QAbstractItemModel* model; ToolViewData* toolView; KDevelop::IOutputView::Behaviours behaviour; QString title; int id; void setModel( QAbstractItemModel* model ); void setDelegate( QAbstractItemDelegate* delegate ); signals: void modelChanged( int ); void delegateChanged( int ); }; class ToolViewData : public QObject { Q_OBJECT public: explicit ToolViewData( QObject* parent ); ~ToolViewData() override; OutputData* addOutput( int id, const QString& title, KDevelop::IOutputView::Behaviours behave ); // If we would adhere to model-view-separation strictly, then this member would move into standardoutputview, but it is more convenient this way. // TODO: move into standardoutputview mutable QList views; StandardOutputView* plugin; QMap outputdata; KDevelop::IOutputView::ViewType type; QString title; QIcon icon; int toolViewId; KDevelop::IOutputView::Options option; QList actionList; signals: void outputAdded( int ); }; #endif diff --git a/plugins/subversion/kdevsvnplugin.h b/plugins/subversion/kdevsvnplugin.h index 87ed0e8b9..99b9508dd 100644 --- a/plugins/subversion/kdevsvnplugin.h +++ b/plugins/subversion/kdevsvnplugin.h @@ -1,159 +1,158 @@ /*************************************************************************** * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_KDEVSVNPLUGIN_H #define KDEVPLATFORM_PLUGIN_KDEVSVNPLUGIN_H #include #include #include #include -class QMenu; class QUrl; class SvnStatusHolder; class SvnCommitDialog; namespace ThreadWeaver { class Queue; } namespace KDevelop { class VcsCommitDialog; class ContextMenuExtension; class VcsPluginHelper; } class KDevSvnPlugin: public KDevelop::IPlugin, public KDevelop::ICentralizedVersionControl { Q_OBJECT Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::ICentralizedVersionControl) public: explicit KDevSvnPlugin(QObject *parent, const QVariantList & = QVariantList()); virtual ~KDevSvnPlugin(); QString name() const override; KDevelop::VcsImportMetadataWidget* createImportMetadataWidget(QWidget* parent) override; // Begin: KDevelop::IBasicVersionControl bool isValidRemoteRepositoryUrl(const QUrl& remoteLocation) override; bool isVersionControlled(const QUrl &localLocation) override; KDevelop::VcsJob* repositoryLocation(const QUrl &localLocation) override; KDevelop::VcsJob* status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* remove(const QList& localLocations) override; KDevelop::VcsJob* copy(const QUrl &localLocationSrc, const QUrl &localLocationDstn) override; KDevelop::VcsJob* move(const QUrl &localLocationSrc, const QUrl &localLocationDst) override; KDevelop::VcsJob* revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* update(const QList& localLocations, const KDevelop::VcsRevision& rev, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* diff(const QUrl &fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type = KDevelop::VcsDiff::DiffUnified, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; /** * Retrieves a diff between the two locations at the given revisions * * The diff is in unified diff format for text files by default */ KDevelop::VcsJob* diff2(const KDevelop::VcsLocation& localOrRepoLocationSrc, const KDevelop::VcsLocation& localOrRepoLocationDst, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type = KDevelop::VcsDiff::DiffDontCare, KDevelop::IBasicVersionControl::RecursionMode = KDevelop::IBasicVersionControl::Recursive); KDevelop::VcsJob* log(const QUrl &localLocation, const KDevelop::VcsRevision& rev, unsigned long limit) override; KDevelop::VcsJob* log(const QUrl &localLocation, const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) override; KDevelop::VcsJob* annotate(const QUrl &localLocation, const KDevelop::VcsRevision& rev) override; KDevelop::VcsJob* merge(const KDevelop::VcsLocation& localOrRepoLocationSrc, const KDevelop::VcsLocation& localOrRepoLocationDst, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, const QUrl &localLocation); KDevelop::VcsJob* resolve(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) override; KDevelop::VcsLocationWidget* vcsLocation(QWidget* parent) const override; // End: KDevelop::IBasicVersionControl // Begin: KDevelop::ICentralizedVersionControl KDevelop::VcsJob* import(const QString & commitMessage, const QUrl &sourceDirectory, const KDevelop::VcsLocation & destinationRepository) override; KDevelop::VcsJob* createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl &destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* edit(const QUrl &localLocation) override; KDevelop::VcsJob* unedit(const QUrl &localLocation) override; KDevelop::VcsJob* localRevision(const QUrl &localLocation, KDevelop::VcsRevision::RevisionType) override; // End: KDevelop::ICentralizedVersionControl KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context*) override; ThreadWeaver::Queue* jobQueue() const; public Q_SLOTS: // invoked by context-menu void ctxInfo(); void ctxStatus(); void ctxCopy(); void ctxMove(); void ctxCat(); void ctxImport(); void ctxCheckout(); private: QScopedPointer m_common; QAction* copy_action; QAction* move_action; ThreadWeaver::Queue* m_jobQueue; }; #endif diff --git a/plugins/subversion/svnaddjob.h b/plugins/subversion/svnaddjob.h index 4f8426721..64c55cf4a 100644 --- a/plugins/subversion/svnaddjob.h +++ b/plugins/subversion/svnaddjob.h @@ -1,45 +1,43 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNADDJOB_H #define KDEVPLATFORM_PLUGIN_SVNADDJOB_H #include "svnjobbase.h" -#include - #include class SvnInternalAddJob; class SvnAddJob : public SvnJobBaseImpl { Q_OBJECT public: explicit SvnAddJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setLocations( const QList& locations ); void setRecursive( bool ); }; #endif diff --git a/plugins/subversion/svncatjob.h b/plugins/subversion/svncatjob.h index a58735bc1..bb27f0a0e 100644 --- a/plugins/subversion/svncatjob.h +++ b/plugins/subversion/svncatjob.h @@ -1,58 +1,54 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNCATJOB_H #define KDEVPLATFORM_PLUGIN_SVNCATJOB_H #include "svnjobbase.h" -#include - -#include - #include namespace KDevelop { class VcsRevision; } class SvnInternalCatJob; class SvnCatJob : public SvnJobBaseImpl { Q_OBJECT public: explicit SvnCatJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setSource( const KDevelop::VcsLocation& ); void setPegRevision( const KDevelop::VcsRevision& ); void setSrcRevision( const KDevelop::VcsRevision& ); public slots: void setContent( const QString& ); private: QString m_content; }; #endif diff --git a/plugins/subversion/svncatjob_p.h b/plugins/subversion/svncatjob_p.h index da2d3aa5b..dff4fd67b 100644 --- a/plugins/subversion/svncatjob_p.h +++ b/plugins/subversion/svncatjob_p.h @@ -1,53 +1,52 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNCATJOB_P_H #define KDEVPLATFORM_PLUGIN_SVNCATJOB_P_H #include "svninternaljobbase.h" -#include #include class SvnInternalCatJob : public SvnInternalJobBase { Q_OBJECT public: explicit SvnInternalCatJob( SvnJobBase* parent = nullptr ); void setSource( const KDevelop::VcsLocation& ); void setSrcRevision( const KDevelop::VcsRevision& ); void setPegRevision( const KDevelop::VcsRevision& ); KDevelop::VcsLocation source() const; KDevelop::VcsRevision srcRevision() const; KDevelop::VcsRevision pegRevision() const; signals: void gotContent( const QString& ); protected: void run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread* thread) override; private: KDevelop::VcsLocation m_source; KDevelop::VcsRevision m_srcRevision; KDevelop::VcsRevision m_pegRevision; }; #endif diff --git a/plugins/subversion/svncheckoutmetadatawidget.cpp b/plugins/subversion/svncheckoutmetadatawidget.cpp index bca74000f..a65d9b274 100644 --- a/plugins/subversion/svncheckoutmetadatawidget.cpp +++ b/plugins/subversion/svncheckoutmetadatawidget.cpp @@ -1,52 +1,51 @@ /*************************************************************************** * 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 "svncheckoutmetadatawidget.h" #include "ui_checkoutmetadatawidget.h" #include -#include #include SvnCheckoutMetadataWidget::SvnCheckoutMetadataWidget( QWidget *parent ) : QWidget( parent ), m_ui( new Ui::SvnCheckoutMetadataWidget ) { m_ui->setupUi( this ); } SvnCheckoutMetadataWidget::~SvnCheckoutMetadataWidget() { delete m_ui; } KDevelop::VcsLocation SvnCheckoutMetadataWidget::source() const { KDevelop::VcsLocation src; src.setRepositoryServer( m_ui->src->url().url() ); return src; } QUrl SvnCheckoutMetadataWidget::destination() const { return m_ui->dest->url(); } void SvnCheckoutMetadataWidget::setDestinationLocation( const QUrl &url ) { m_ui->dest->setUrl( url ); } KDevelop::IBasicVersionControl::RecursionMode SvnCheckoutMetadataWidget::recursionMode() const { return m_ui->recurse->isChecked() ? KDevelop::IBasicVersionControl::Recursive : KDevelop::IBasicVersionControl::NonRecursive ; } diff --git a/plugins/subversion/svnclient.cpp b/plugins/subversion/svnclient.cpp index 5889678d4..f40b90815 100644 --- a/plugins/subversion/svnclient.cpp +++ b/plugins/subversion/svnclient.cpp @@ -1,335 +1,334 @@ /*************************************************************************** * 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, 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 != nullptr ) { apr_file_close( outfile ); } if( errfile != nullptr ) { apr_file_close( outfile ); } if( outfileName != nullptr ) { svn_error_clear( svn_io_remove_file ( outfileName, pool ) ); } if( errfileName != nullptr ) { svn_error_clear( svn_io_remove_file ( errfileName, pool ) ); } } SvnClient::SvnClient( svn::Context* 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 = 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 != 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 != 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 != 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 = 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 != 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 != 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 != 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 != nullptr) { for (apr_hash_index_t *hi = apr_hash_first (pool, changedPaths); hi != nullptr; hi = apr_hash_next (hi)) { char *path; void *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 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 != nullptr) { throw svn::ClientException (error); } } void SvnClient::emitLogEventReceived( const KDevelop::VcsEvent& ev ) { emit logEventReceived( ev ); } diff --git a/plugins/subversion/svnclient.h b/plugins/subversion/svnclient.h index 77388bf7a..68ad8fc34 100644 --- a/plugins/subversion/svnclient.h +++ b/plugins/subversion/svnclient.h @@ -1,71 +1,70 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNCLIENT_H #define KDEVPLATFORM_PLUGIN_SVNCLIENT_H -#include #include #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/revision.hpp" #include "kdevsvncpp/client.hpp" #include namespace KDevelop { class VcsAnnotationLine; } class SvnClient : public QObject, public svn::Client { Q_OBJECT public: explicit SvnClient( svn::Context* = nullptr ); QString 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); QString 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); void log( const char* path, const svn::Revision& start, const svn::Revision& end, int limit, bool discoverChangedPaths = false, bool strictNodeHistory = true ) throw (svn::ClientException); void emitLogEventReceived( const KDevelop::VcsEvent& ); signals: void logEventReceived( const KDevelop::VcsEvent& ); private: svn::Context* m_ctxt; }; #endif diff --git a/plugins/subversion/svncommitjob.cpp b/plugins/subversion/svncommitjob.cpp index 7ac3fdd45..7032ce124 100644 --- a/plugins/subversion/svncommitjob.cpp +++ b/plugins/subversion/svncommitjob.cpp @@ -1,175 +1,174 @@ /*************************************************************************** * 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 "svncommitjob.h" #include "svncommitjob_p.h" #include #include #include #include -#include #include #include #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/targets.hpp" #include #include "svninternaljobbase.h" #include "kdevsvnplugin.h" SvnInternalCommitJob::SvnInternalCommitJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ), m_recursive( true ), m_keepLock( false ) { } void SvnInternalCommitJob::setRecursive( bool recursive ) { QMutexLocker l( &m_mutex ); m_recursive = recursive; } void SvnInternalCommitJob::setCommitMessage( const QString& msg ) { QMutexLocker l( &m_mutex ); m_commitMessage = msg; } void SvnInternalCommitJob::setUrls( const QList& urls ) { QMutexLocker l( &m_mutex ); m_urls = urls; } void SvnInternalCommitJob::setKeepLock( bool lock ) { QMutexLocker l( &m_mutex ); m_keepLock = lock; } QList SvnInternalCommitJob::urls() const { QMutexLocker l( &m_mutex ); return m_urls; } QString SvnInternalCommitJob::commitMessage() const { QMutexLocker l( &m_mutex ); return m_commitMessage; } bool SvnInternalCommitJob::recursive() const { QMutexLocker l( &m_mutex ); return m_recursive; } bool SvnInternalCommitJob::keepLock() const { QMutexLocker l( &m_mutex ); return m_keepLock; } void SvnInternalCommitJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { initBeforeRun(); svn::Client cli(m_ctxt); std::vector targets; QList l = urls(); foreach( const QUrl &u, l ) { QByteArray path = u.toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); targets.push_back( svn::Path( path.data() ) ); } QByteArray ba = commitMessage().toUtf8(); try { cli.commit( svn::Targets(targets), ba.data(), recursive(), keepLock() ); }catch( svn::ClientException ce ) { qCDebug(PLUGIN_SVN) << "Couldn't commit:" << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } SvnCommitJob::SvnCommitJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl(parent, KDevelop::OutputJob::Verbose ) { setType( KDevelop::VcsJob::Commit ); setObjectName(i18n("Subversion Commit")); } QVariant SvnCommitJob::fetchResults() { return QVariant(); } void SvnCommitJob::start() { setTitle(QStringLiteral("commit")); setBehaviours( KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); startOutput(); QStandardItemModel *m = qobject_cast(model()); m->setColumnCount(1); m->appendRow(new QStandardItem(i18n("Committing..."))); if( m_job->urls().isEmpty() ) { internalJobFailed(); setErrorText( i18n( "Not enough information to execute commit" ) ); m->appendRow(new QStandardItem(errorText())); } else { startInternalJob(); } } void SvnCommitJob::setCommitMessage( const QString& msg ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setCommitMessage( msg ); } void SvnCommitJob::setKeepLock( bool keepLock ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setKeepLock( keepLock ); } void SvnCommitJob::setUrls( const QList& urls ) { qCDebug(PLUGIN_SVN) << "Setting urls?" << status() << urls; if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setUrls( urls ); } void SvnCommitJob::setRecursive( bool recursive ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRecursive( recursive ); } diff --git a/plugins/subversion/svncommitjob.h b/plugins/subversion/svncommitjob.h index ed7a4282e..44189deda 100644 --- a/plugins/subversion/svncommitjob.h +++ b/plugins/subversion/svncommitjob.h @@ -1,47 +1,46 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNCOMMITJOB_H #define KDEVPLATFORM_PLUGIN_SVNCOMMITJOB_H #include "svnjobbase.h" #include -#include #include class SvnInternalCommitJob; class SvnCommitDialog; class SvnCommitJob : public SvnJobBaseImpl { Q_OBJECT public: explicit SvnCommitJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setCommitMessage( const QString& msg ); void setKeepLock( bool ); void setUrls( const QList& urls ); void setRecursive( bool ); }; #endif diff --git a/plugins/subversion/svncommitjob_p.h b/plugins/subversion/svncommitjob_p.h index 7f9b24ab7..5f852af1a 100644 --- a/plugins/subversion/svncommitjob_p.h +++ b/plugins/subversion/svncommitjob_p.h @@ -1,56 +1,55 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNCOMMITJOB_P_H #define KDEVPLATFORM_PLUGIN_SVNCOMMITJOB_P_H -#include #include #include "svninternaljobbase.h" class SvnJobBase; class SvnInternalCommitJob : public SvnInternalJobBase { Q_OBJECT public: explicit SvnInternalCommitJob( SvnJobBase* parent = nullptr ); void setRecursive( bool ); void setCommitMessage( const QString& ); void setUrls( const QList& ); void setKeepLock( bool ); QList urls() const; QString commitMessage() const; bool recursive() const; bool keepLock() const; protected: void run(ThreadWeaver::JobPointer job, ThreadWeaver::Thread* thread) override; private: QList m_urls; bool m_recursive; bool m_keepLock; }; #endif diff --git a/plugins/subversion/svncopyjob.h b/plugins/subversion/svncopyjob.h index 584351394..906ae373d 100644 --- a/plugins/subversion/svncopyjob.h +++ b/plugins/subversion/svncopyjob.h @@ -1,44 +1,42 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNCOPYJOB_H #define KDEVPLATFORM_PLUGIN_SVNCOPYJOB_H #include "svnjobbase.h" -#include - #include class SvnInternalCopyJob; class SvnCopyJob : public SvnJobBaseImpl { Q_OBJECT public: explicit SvnCopyJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setSourceLocation( const QUrl &location ); void setDestinationLocation( const QUrl &location ); }; #endif diff --git a/plugins/subversion/svndiffjob.cpp b/plugins/subversion/svndiffjob.cpp index 707be797b..1c141b9ac 100644 --- a/plugins/subversion/svndiffjob.cpp +++ b/plugins/subversion/svndiffjob.cpp @@ -1,441 +1,440 @@ /*************************************************************************** * 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 "svndiffjob.h" #include "svndiffjob_p.h" #include -#include #include #include #include #include #include #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/revision.hpp" #include "icore.h" #include "iruncontroller.h" #include "svnclient.h" #include "svncatjob.h" ///@todo The subversion library returns borked diffs, where the headers are at the end. This function /// takes those headers, and moves them into the correct place to create a valid working diff. /// Find the source of this problem. QString repairDiff(QString diff) { qCDebug(PLUGIN_SVN) << "diff before repair:" << diff; QStringList lines = diff.split('\n'); QMap headers; for(int a = 0; a < lines.size()-1; ++a) { if(lines[a].startsWith(QLatin1String("Index: ")) && lines[a+1].startsWith(QLatin1String("====="))) { QString fileName = lines[a].mid(strlen("Index: ")).trimmed(); headers[fileName] = lines[a]; qCDebug(PLUGIN_SVN) << "found header for" << fileName; lines[a] = QString(); if(lines[a+1].startsWith(QLatin1String("======"))) { headers[fileName] += '\n' + lines[a+1]; lines[a+1] = QString(); } } } QRegExp spaceRegExp("\\s"); for(int a = 0; a < lines.size()-1; ++a) { if(lines[a].startsWith(QLatin1String("--- "))) { QString tail = lines[a].mid(strlen("--- ")); if(tail.indexOf(spaceRegExp) != -1) { QString file = tail.left(tail.indexOf(spaceRegExp)); qCDebug(PLUGIN_SVN) << "checking for" << file; if(headers.contains(file)) { qCDebug(PLUGIN_SVN) << "adding header for" << file << ":" << headers[file]; lines[a] = headers[file] + '\n' + lines[a]; } } } } QString ret = lines.join(QLatin1Char('\n')); qCDebug(PLUGIN_SVN) << "repaired diff:" << ret; return ret; } //@TODO: Handle raw diffs by using SvnCatJob to fetch both files/revisions SvnInternalDiffJob::SvnInternalDiffJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ), m_recursive( true ), m_ignoreAncestry( false ), m_ignoreContentType( false ), m_noDiffOnDelete( false ) { m_pegRevision.setRevisionValue( KDevelop::VcsRevision::Head, KDevelop::VcsRevision::Special ); } void SvnInternalDiffJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { initBeforeRun(); SvnClient cli(m_ctxt); try { QString diff; if( destination().isValid() ) { QByteArray srcba; if( source().type() == KDevelop::VcsLocation::LocalLocation ) { srcba = source().localUrl().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); }else { srcba = source().repositoryServer().toUtf8(); } QByteArray dstba; if( destination().type() == KDevelop::VcsLocation::LocalLocation ) { dstba = destination().localUrl().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); }else { dstba = destination().repositoryServer().toUtf8(); } svn::Revision srcRev = createSvnCppRevisionFromVcsRevision( srcRevision() ); svn::Revision dstRev = createSvnCppRevisionFromVcsRevision( dstRevision() ); if( srcba.isEmpty() || ( dstba.isEmpty() && srcRev.kind() == svn_opt_revision_unspecified && dstRev.kind() == svn_opt_revision_unspecified ) ) { throw svn::ClientException( "Not enough information for a diff"); } diff = cli.diff( svn::Path( srcba.data() ), srcRev, svn::Path( dstba.data() ), dstRev, recursive(), ignoreAncestry(), noDiffOnDelete(), ignoreContentType() ); }else { QByteArray srcba; if( source().type() == KDevelop::VcsLocation::LocalLocation ) { srcba = source().localUrl().toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); }else { srcba = source().repositoryServer().toUtf8(); } svn::Revision pegRev = createSvnCppRevisionFromVcsRevision( pegRevision() ); svn::Revision srcRev = createSvnCppRevisionFromVcsRevision( srcRevision() ); svn::Revision dstRev = createSvnCppRevisionFromVcsRevision( dstRevision() ); if( srcba.isEmpty() || pegRev.kind() == svn_opt_revision_unspecified || dstRev.kind() == svn_opt_revision_unspecified || srcRev.kind() == svn_opt_revision_unspecified) { throw svn::ClientException( "Not enough information for a diff"); } diff = cli.diff( svn::Path( srcba.data() ), pegRev, srcRev, dstRev, recursive(), ignoreAncestry(), noDiffOnDelete(), ignoreContentType() ); } diff = repairDiff(diff); emit gotDiff( diff ); }catch( svn::ClientException ce ) { qCDebug(PLUGIN_SVN) << "Exception while doing a diff: " << m_source.localUrl() << m_source.repositoryServer() << m_srcRevision.prettyValue() << m_destination.localUrl() << m_destination.repositoryServer() << m_dstRevision.prettyValue() << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } void SvnInternalDiffJob::setSource( const KDevelop::VcsLocation& src ) { QMutexLocker l( &m_mutex ); m_source = src; } void SvnInternalDiffJob::setDestination( const KDevelop::VcsLocation& dst ) { QMutexLocker l( &m_mutex ); m_destination = dst; } void SvnInternalDiffJob::setSrcRevision( const KDevelop::VcsRevision& srcRev ) { QMutexLocker l( &m_mutex ); m_srcRevision = srcRev; } void SvnInternalDiffJob::setDstRevision( const KDevelop::VcsRevision& dstRev ) { QMutexLocker l( &m_mutex ); m_dstRevision = dstRev; } void SvnInternalDiffJob::setPegRevision( const KDevelop::VcsRevision& pegRev ) { QMutexLocker l( &m_mutex ); m_pegRevision = pegRev; } void SvnInternalDiffJob::setRecursive( bool recursive ) { QMutexLocker l( &m_mutex ); m_recursive = recursive; } void SvnInternalDiffJob::setIgnoreAncestry( bool ignoreAncestry ) { QMutexLocker l( &m_mutex ); m_ignoreAncestry = ignoreAncestry; } void SvnInternalDiffJob::setIgnoreContentType( bool ignoreContentType ) { QMutexLocker l( &m_mutex ); m_ignoreContentType = ignoreContentType; } void SvnInternalDiffJob::setNoDiffOnDelete( bool noDiffOnDelete ) { QMutexLocker l( &m_mutex ); m_noDiffOnDelete = noDiffOnDelete; } bool SvnInternalDiffJob::recursive() const { QMutexLocker l( &m_mutex ); return m_recursive; } bool SvnInternalDiffJob::ignoreAncestry() const { QMutexLocker l( &m_mutex ); return m_ignoreAncestry; } bool SvnInternalDiffJob::ignoreContentType() const { QMutexLocker l( &m_mutex ); return m_ignoreContentType; } bool SvnInternalDiffJob::noDiffOnDelete() const { QMutexLocker l( &m_mutex ); return m_noDiffOnDelete; } KDevelop::VcsLocation SvnInternalDiffJob::source() const { QMutexLocker l( &m_mutex ); return m_source; } KDevelop::VcsLocation SvnInternalDiffJob::destination() const { QMutexLocker l( &m_mutex ); return m_destination; } KDevelop::VcsRevision SvnInternalDiffJob::srcRevision() const { QMutexLocker l( &m_mutex ); return m_srcRevision; } KDevelop::VcsRevision SvnInternalDiffJob::dstRevision() const { QMutexLocker l( &m_mutex ); return m_dstRevision; } KDevelop::VcsRevision SvnInternalDiffJob::pegRevision() const { QMutexLocker l( &m_mutex ); return m_pegRevision; } SvnDiffJob::SvnDiffJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl( parent, KDevelop::OutputJob::Silent ) { setType( KDevelop::VcsJob::Add ); connect( m_job, &SvnInternalDiffJob::gotDiff, this, &SvnDiffJob::setDiff, Qt::QueuedConnection ); setObjectName(i18n("Subversion Diff")); } QVariant SvnDiffJob::fetchResults() { return qVariantFromValue( m_diff ); } void SvnDiffJob::start() { if( !m_job->source().isValid() || ( !m_job->destination().isValid() && ( m_job->srcRevision().revisionType() == KDevelop::VcsRevision::Invalid || m_job->dstRevision().revisionType() == KDevelop::VcsRevision::Invalid ) ) ) { internalJobFailed(); setErrorText( i18n( "Not enough information given to execute diff" ) ); } else { startInternalJob(); } } void SvnDiffJob::setSource( const KDevelop::VcsLocation& source ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setSource( source ); } void SvnDiffJob::setDestination( const KDevelop::VcsLocation& destination ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setDestination( destination ); } void SvnDiffJob::setPegRevision( const KDevelop::VcsRevision& pegRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setPegRevision( pegRevision ); } void SvnDiffJob::setSrcRevision( const KDevelop::VcsRevision& srcRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setSrcRevision( srcRevision ); } void SvnDiffJob::setDstRevision( const KDevelop::VcsRevision& dstRevision ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setDstRevision( dstRevision ); } void SvnDiffJob::setRecursive( bool recursive ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRecursive( recursive ); } void SvnDiffJob::setIgnoreAncestry( bool ignoreAncestry ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setIgnoreAncestry( ignoreAncestry ); } void SvnDiffJob::setIgnoreContentType( bool ignoreContentType ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setIgnoreContentType( ignoreContentType ); } void SvnDiffJob::setNoDiffOnDelete( bool noDiffOnDelete ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setNoDiffOnDelete( noDiffOnDelete ); } void SvnDiffJob::setDiff( const QString& diff ) { m_diff = KDevelop::VcsDiff(); m_diff.setBaseDiff(QUrl::fromLocalFile(QStringLiteral("/"))); m_diff.setType( KDevelop::VcsDiff::DiffUnified ); m_diff.setContentType( KDevelop::VcsDiff::Text ); m_diff.setDiff( diff ); QRegExp fileRe("(?:^|\n)Index: ([^\n]+)\n"); QStringList paths; int pos = 0; while( ( pos = fileRe.indexIn( diff, pos ) ) != -1 ) { paths << fileRe.cap(1); pos += fileRe.matchedLength(); } if (paths.isEmpty()) { internalJobDone(); emit resultsReady( this ); return; } foreach( const QString &s, paths ) { if( !s.isEmpty() ) { SvnCatJob* job = new SvnCatJob( m_part ); KDevelop::VcsLocation l = m_job->source(); if( l.type() == KDevelop::VcsLocation::LocalLocation ) { l.setLocalUrl( QUrl::fromLocalFile( s ) ); }else { QString repoLocation = QUrl( l.repositoryServer() ).toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ); QFileInfo fi( repoLocation ); if( s == fi.fileName() ) { l.setRepositoryServer( l.repositoryServer() ); }else { l.setRepositoryServer( l.repositoryServer() + '/' + s ); } } job->setSource( l ); job->setPegRevision( m_job->pegRevision() ); job->setSrcRevision( m_job->srcRevision() ); m_catJobMap[job] = l; connect( job, SIGNAL(resultsReady(KDevelop::VcsJob*)), this, SLOT(addLeftText(KDevelop::VcsJob*)) ); connect( job, SIGNAL(result(KJob*)), this, SLOT(removeJob(KJob*)) ); KDevelop::ICore::self()->runController()->registerJob(job); } } } void SvnDiffJob::addLeftText( KDevelop::VcsJob* job ) { if( m_catJobMap.contains( job ) ) { QVariant v = job->fetchResults(); m_diff.addLeftText( m_catJobMap[job], v.toString() ); m_catJobMap.remove(job); // KJobs delete themselves when finished } if( m_catJobMap.isEmpty() ) { internalJobDone(); emit resultsReady( this ); } } void SvnDiffJob::removeJob( KJob* job ) { if( job->error() != 0 ) { KDevelop::VcsJob* j = dynamic_cast( job ); if( j ) { if( m_catJobMap.contains( j ) ) { m_catJobMap.remove(j); // KJobs delete themselves when finished } } } if( m_catJobMap.isEmpty() ) { internalJobDone(); emit resultsReady( this ); } } void SvnDiffJob::setDiffType( KDevelop::VcsDiff::Type type ) { m_diffType = type; } diff --git a/plugins/subversion/svndiffjob.h b/plugins/subversion/svndiffjob.h index ddc81193f..1a4e5ae78 100644 --- a/plugins/subversion/svndiffjob.h +++ b/plugins/subversion/svndiffjob.h @@ -1,73 +1,71 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNDIFFJOB_H #define KDEVPLATFORM_PLUGIN_SVNDIFFJOB_H #include "svnjobbase.h" #include -#include - #include class SvnCatJob; namespace KDevelop { class VcsRevision; } class SvnInternalDiffJob; class SvnDiffJob : public SvnJobBaseImpl { Q_OBJECT public: explicit SvnDiffJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setSource( const KDevelop::VcsLocation& ); void setDestination( const KDevelop::VcsLocation& ); void setPegRevision( const KDevelop::VcsRevision& ); void setSrcRevision( const KDevelop::VcsRevision& ); void setDstRevision( const KDevelop::VcsRevision& ); void setRecursive( bool ); void setIgnoreAncestry( bool ); void setIgnoreContentType( bool ); void setNoDiffOnDelete( bool ); void setDiffType( KDevelop::VcsDiff::Type type ); public slots: void setDiff( const QString& ); void addLeftText( KDevelop::VcsJob* job ); void removeJob( KJob* job ); private: KDevelop::VcsDiff m_diff; KDevelop::VcsDiff::Type m_diffType; QMap m_catJobMap; }; #endif diff --git a/plugins/subversion/svndiffjob_p.h b/plugins/subversion/svndiffjob_p.h index f2926a676..80f7a9b93 100644 --- a/plugins/subversion/svndiffjob_p.h +++ b/plugins/subversion/svndiffjob_p.h @@ -1,71 +1,70 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNDIFFJOB_P_H #define KDEVPLATFORM_PLUGIN_SVNDIFFJOB_P_H #include "svninternaljobbase.h" -#include #include class SvnInternalDiffJob : public SvnInternalJobBase { Q_OBJECT public: explicit SvnInternalDiffJob( SvnJobBase* parent = nullptr ); void setSource( const KDevelop::VcsLocation& ); void setDestination( const KDevelop::VcsLocation& ); void setSrcRevision( const KDevelop::VcsRevision& ); void setDstRevision( const KDevelop::VcsRevision& ); void setPegRevision( const KDevelop::VcsRevision& ); void setRecursive( bool ); void setIgnoreAncestry( bool ); void setIgnoreContentType( bool ); void setNoDiffOnDelete( bool ); bool recursive() const; bool ignoreAncestry() const; bool ignoreContentType() const; bool noDiffOnDelete() const; KDevelop::VcsLocation source() const; KDevelop::VcsLocation destination() const; KDevelop::VcsRevision srcRevision() const; KDevelop::VcsRevision dstRevision() const; KDevelop::VcsRevision pegRevision() const; signals: void gotDiff( const QString& ); protected: void run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread* thread) override; private: KDevelop::VcsLocation m_source; KDevelop::VcsLocation m_destination; KDevelop::VcsRevision m_srcRevision; KDevelop::VcsRevision m_dstRevision; KDevelop::VcsRevision m_pegRevision; bool m_recursive; bool m_ignoreAncestry; bool m_ignoreContentType; bool m_noDiffOnDelete; }; #endif diff --git a/plugins/subversion/svninfojob.h b/plugins/subversion/svninfojob.h index d75ed3e1b..bb18edf2d 100644 --- a/plugins/subversion/svninfojob.h +++ b/plugins/subversion/svninfojob.h @@ -1,92 +1,91 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNINFOJOB_H #define KDEVPLATFORM_PLUGIN_SVNINFOJOB_H #include "svnjobbase.h" -#include #include #include #include class SvnInternalInfoJob; /// A structure which describes various system-generated metadata about /// a working-copy path or URL. class SvnInfoHolder { public: QString name; QUrl url; qlonglong rev; int kind; QUrl repoUrl; QString repouuid; qlonglong lastChangedRev; QDateTime lastChangedDate; QString lastChangedAuthor; int scheduled; QUrl copyFromUrl; qlonglong copyFromRevision; QDateTime textTime; QDateTime propertyTime; QString oldFileConflict; QString newFileConflict; QString workingCopyFileConflict; QString propertyRejectFile; }; /// This is an svn internal class, it shouldn't be used outside of the svn /// plugin class SvnInfoJob : public SvnJobBaseImpl { Q_OBJECT public: enum ProvideInformationType { AllInfo, RevisionOnly, RepoUrlOnly }; explicit SvnInfoJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setLocation( const QUrl &location ); void setProvideInformation( ProvideInformationType ); void setProvideRevisionType( KDevelop::VcsRevision::RevisionType ); public slots: void setInfo( const SvnInfoHolder& ); private: SvnInfoHolder m_info; ProvideInformationType m_provideInfo; KDevelop::VcsRevision::RevisionType m_provideRevisionType; }; Q_DECLARE_METATYPE( SvnInfoHolder ) #endif diff --git a/plugins/subversion/svninternaljobbase.cpp b/plugins/subversion/svninternaljobbase.cpp index 65af2fc1a..334d7cae7 100644 --- a/plugins/subversion/svninternaljobbase.cpp +++ b/plugins/subversion/svninternaljobbase.cpp @@ -1,388 +1,387 @@ /*************************************************************************** * 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(nullptr); delete m_ctxt; 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/svninternaljobbase.h b/plugins/subversion/svninternaljobbase.h index 7ab71f926..8d4085662 100644 --- a/plugins/subversion/svninternaljobbase.h +++ b/plugins/subversion/svninternaljobbase.h @@ -1,129 +1,127 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNINTERNALJOBBASE_H #define KDEVPLATFORM_PLUGIN_SVNINTERNALJOBBASE_H #include -#include #include #include extern "C" { #include } #include "kdevsvncpp/context_listener.hpp" namespace KDevelop { class VcsRevision; } namespace svn { class Context; class Revision; } class SvnJobBase; -class QMutex; -class QSemaphore; + class SvnInternalJobBase : public QObject, public ThreadWeaver::Job, public svn::ContextListener { Q_OBJECT public: explicit SvnInternalJobBase( SvnJobBase* parent = nullptr ); ~SvnInternalJobBase() override; bool success() const override; bool contextGetLogin( const std::string& realm, std::string& username, std::string& password, bool& maySave ) override; void 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 ) override; bool contextCancel() override; bool contextGetLogMessage( std::string& msg ) override; svn::ContextListener::SslServerTrustAnswer contextSslServerTrustPrompt( const svn::ContextListener::SslServerTrustData& data, apr_uint32_t& acceptedFailures ) override; bool contextSslClientCertPrompt( std::string& cert ) override; bool contextSslClientCertPwPrompt( std::string& pw, const std::string& realm, bool& maySave ) override; void initBeforeRun(); void kill(); bool wasKilled(); QString errorMessage() const; svn::Context* m_ctxt; QSemaphore m_guiSemaphore; QString m_login_username; QString m_login_password; bool m_maySave; QString m_commitMessage; svn::ContextListener::SslServerTrustAnswer m_trustAnswer; static svn::Revision createSvnCppRevisionFromVcsRevision( const KDevelop::VcsRevision& ); signals: void needLogin( const QString& ); void showNotification( const QString&, const QString& ); void needCommitMessage(); void needSslServerTrust( const QStringList&, const QString&, const QString&, const QString&, const QString&, const QString&, const QString& ); void needSslClientCert( const QString& ); void needSslClientCertPassword( const QString& ); /** This signal is emitted when this job is being processed by a thread. */ void started(); /** This signal is emitted when the job has been finished (no matter if it succeeded or not). */ void done(); /** This job has failed. * * This signal is emitted when success() returns false after the job is executed. */ void failed(); protected: void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; mutable QMutex m_mutex; mutable QMutex m_killMutex; bool m_success; void setErrorMessage( const QString& ); private: bool sendFirstDelta; bool killed; QString m_errorMessage; }; #endif diff --git a/plugins/subversion/svnjobbase.cpp b/plugins/subversion/svnjobbase.cpp index 670b85611..c04df5e6f 100644 --- a/plugins/subversion/svnjobbase.cpp +++ b/plugins/subversion/svnjobbase.cpp @@ -1,212 +1,211 @@ /*************************************************************************** * 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( 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( 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/svnjobbase.h b/plugins/subversion/svnjobbase.h index e50902565..05ae31c28 100644 --- a/plugins/subversion/svnjobbase.h +++ b/plugins/subversion/svnjobbase.h @@ -1,92 +1,90 @@ /*************************************************************************** * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNJOBBASE_H #define KDEVPLATFORM_PLUGIN_SVNJOBBASE_H #include #include "kdevsvncpp/context_listener.hpp" #include "kdevsvnplugin.h" #include "debug.h" #include extern "C" { #include } - -class QEvent; class SvnInternalJobBase; namespace ThreadWeaver { class Job; } class KDevSvnPlugin; class SvnJobBase : public KDevelop::VcsJob { Q_OBJECT public: explicit SvnJobBase( KDevSvnPlugin*, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose ); ~SvnJobBase() override; virtual SvnInternalJobBase* internalJob() const = 0; KDevelop::VcsJob::JobStatus status() const override; KDevelop::IPlugin* vcsPlugin() const override; public slots: void askForLogin( const QString& ); void showNotification( const QString&, const QString& ); void askForCommitMessage(); void askForSslServerTrust( const QStringList&, const QString&, const QString&, const QString&, const QString&, const QString&, const QString& ); void askForSslClientCert( const QString& ); void askForSslClientCertPassword( const QString& ); protected slots: void internalJobStarted(); void internalJobDone(); void internalJobFailed(); protected: void startInternalJob(); bool doKill() override; KDevSvnPlugin* m_part; private: void outputMessage(const QString &message); KDevelop::VcsJob::JobStatus m_status; }; template class SvnJobBaseImpl : public SvnJobBase { public: explicit SvnJobBaseImpl(KDevSvnPlugin* plugin, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose) : SvnJobBase(plugin, verbosity) { m_job = new InternalJobClass(this); } SvnInternalJobBase* internalJob() const override { return m_job; } protected: InternalJobClass* m_job = nullptr; }; #endif diff --git a/plugins/subversion/svnmovejob.h b/plugins/subversion/svnmovejob.h index ce5312205..b4d30a0e5 100644 --- a/plugins/subversion/svnmovejob.h +++ b/plugins/subversion/svnmovejob.h @@ -1,44 +1,43 @@ /*************************************************************************** * This file is part of KDevelop * * Moveright 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNMOVEJOB_H #define KDEVPLATFORM_PLUGIN_SVNMOVEJOB_H #include "svnjobbase.h" -#include -#include +class QUrl; class SvnInternalMoveJob; class SvnMoveJob : public SvnJobBaseImpl { Q_OBJECT public: explicit SvnMoveJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setSourceLocation( const QUrl &location ); void setDestinationLocation( const QUrl &location ); void setForce( bool ); }; #endif diff --git a/plugins/subversion/svnremovejob.h b/plugins/subversion/svnremovejob.h index 323b650e3..55a4aa785 100644 --- a/plugins/subversion/svnremovejob.h +++ b/plugins/subversion/svnremovejob.h @@ -1,44 +1,43 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNREMOVEJOB_H #define KDEVPLATFORM_PLUGIN_SVNREMOVEJOB_H #include "svnjobbase.h" -#include #include class SvnInternalRemoveJob; class SvnRemoveJob : public SvnJobBaseImpl { Q_OBJECT public: explicit SvnRemoveJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setLocations( const QList& locations ); void setForce( bool force ); }; #endif diff --git a/plugins/subversion/svnrevertjob.h b/plugins/subversion/svnrevertjob.h index baac80268..72c0c5805 100644 --- a/plugins/subversion/svnrevertjob.h +++ b/plugins/subversion/svnrevertjob.h @@ -1,43 +1,42 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNREVERTJOB_H #define KDEVPLATFORM_PLUGIN_SVNREVERTJOB_H #include "svnjobbase.h" -#include #include class SvnInternalRevertJob; class SvnRevertJob : public SvnJobBaseImpl { Q_OBJECT public: explicit SvnRevertJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setLocations( const QList& locations ); void setRecursive( bool ); }; #endif diff --git a/plugins/subversion/svnssldialog.cpp b/plugins/subversion/svnssldialog.cpp index 0a7b7667a..d7cac3324 100644 --- a/plugins/subversion/svnssldialog.cpp +++ b/plugins/subversion/svnssldialog.cpp @@ -1,85 +1,85 @@ /*************************************************************************** * 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 "svnssldialog.h" +#include #include -#include #include #include "ui_ssltrustdialog.h" class SvnSSLTrustDialogPrivate { public: Ui::SvnSSLTrustDialog ui; bool temporarily; }; SvnSSLTrustDialog::SvnSSLTrustDialog( QWidget *parent ) : QDialog( parent ), d( new SvnSSLTrustDialogPrivate ) { d->ui.setupUi( this ); d->temporarily = true; setWindowTitle( i18n( "Ssl Server Certificate" ) ); buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel); buttonBox->addButton(i18n("Trust Permanently"), QDialogButtonBox::YesRole); buttonBox->addButton(i18n("Trust Temporarily"), QDialogButtonBox::AcceptRole)->setDefault(true); auto layout = new QVBoxLayout(); setLayout(layout); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::clicked, this, &SvnSSLTrustDialog::buttonClicked); } SvnSSLTrustDialog::~SvnSSLTrustDialog() { delete d; } void SvnSSLTrustDialog::setCertInfos( const QString& hostname, const QString& fingerPrint, const QString& validfrom, const QString& validuntil, const QString& issuerName, const QString& realm, const QStringList& failures ) { QString txt = QStringLiteral("
    "); foreach( const QString &fail, failures ) { txt += "
  • "+fail+"
  • "; } d->ui.reasons->setHtml( txt ); d->ui.hostname->setText( hostname ); d->ui.fingerprint->setText( fingerPrint ); d->ui.validUntil->setText( validuntil ); d->ui.validFrom->setText( validfrom ); d->ui.issuer->setText( issuerName ); setWindowTitle( i18n( "Ssl Server Certificate: %1", realm ) ); } bool SvnSSLTrustDialog::useTemporarily() { return d->temporarily; } void SvnSSLTrustDialog::buttonClicked(QAbstractButton *button) { if (buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { d->temporarily = true; } else { d->temporarily = false; } accept(); } diff --git a/plugins/subversion/svnssldialog.h b/plugins/subversion/svnssldialog.h index f5f9ad280..50d51f645 100644 --- a/plugins/subversion/svnssldialog.h +++ b/plugins/subversion/svnssldialog.h @@ -1,36 +1,38 @@ /*************************************************************************** * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVN_SSLDIALOG_H #define KDEVPLATFORM_PLUGIN_SVN_SSLDIALOG_H #include -#include + +class QDialogButtonBox; +class QAbstractButton; class SvnSSLTrustDialog: public QDialog { Q_OBJECT public: explicit SvnSSLTrustDialog( QWidget *parent = nullptr ); ~SvnSSLTrustDialog() override; void setCertInfos( const QString& hostname, const QString& fingerPrint, const QString& validfrom, const QString& validuntil, const QString& issuerName, const QString& realm, const QStringList& failures ); bool useTemporarily(); private slots: void buttonClicked(QAbstractButton *button); private: class SvnSSLTrustDialogPrivate *d; QDialogButtonBox *buttonBox; }; #endif diff --git a/plugins/subversion/svnstatusjob.cpp b/plugins/subversion/svnstatusjob.cpp index 65d8ce30b..e27993b03 100644 --- a/plugins/subversion/svnstatusjob.cpp +++ b/plugins/subversion/svnstatusjob.cpp @@ -1,179 +1,178 @@ /*************************************************************************** * 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 "svnstatusjob.h" #include "svnstatusjob_p.h" -#include #include #include #include extern "C" { #include } #include #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/status.hpp" KDevelop::VcsStatusInfo::State getState( svn::Status st ) { KDevelop::VcsStatusInfo::State state; if( st.isVersioned() ) { if( st.textStatus() == svn_wc_status_added ) { state = KDevelop::VcsStatusInfo::ItemAdded; }else if( st.textStatus() == svn_wc_status_modified || st.propStatus() == svn_wc_status_modified ) { state = KDevelop::VcsStatusInfo::ItemModified; }else if( st.textStatus() == svn_wc_status_deleted ) { state = KDevelop::VcsStatusInfo::ItemDeleted; }else if( st.textStatus() == svn_wc_status_conflicted || st.propStatus() == svn_wc_status_conflicted ) { state = KDevelop::VcsStatusInfo::ItemHasConflicts; }else { state = KDevelop::VcsStatusInfo::ItemUpToDate; } }else { state = KDevelop::VcsStatusInfo::ItemUnknown; } return state; } SvnInternalStatusJob::SvnInternalStatusJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ) , m_recursive( false ) { } void SvnInternalStatusJob::setRecursive( bool recursive ) { QMutexLocker l( &m_mutex ); m_recursive = recursive; } void SvnInternalStatusJob::setLocations( const QList& urls ) { QMutexLocker l( &m_mutex ); m_locations = urls; } QList SvnInternalStatusJob::locations() const { QMutexLocker l( &m_mutex ); return m_locations; } bool SvnInternalStatusJob::recursive() const { QMutexLocker l( &m_mutex ); return m_recursive; } void SvnInternalStatusJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { qCDebug(PLUGIN_SVN) << "Running internal status job with urls:" << m_locations; initBeforeRun(); svn::Client cli(m_ctxt); QList l = locations(); foreach( const QUrl &url, l ) { //qCDebug(PLUGIN_SVN) << "Fetching status info for:" << url; try { QByteArray ba = url.toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); svn::StatusEntries se = cli.status( ba.data(), recursive() ); for( svn::StatusEntries::const_iterator it = se.begin(); it != se.end() ; ++it ) { KDevelop::VcsStatusInfo info; info.setUrl( QUrl::fromLocalFile( QString::fromUtf8( (*it).path() ) ) ); info.setState( getState( *it ) ); emit gotNewStatus( info ); } }catch( svn::ClientException ce ) { qCDebug(PLUGIN_SVN) << "Couldn't get status: " << url << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } } SvnStatusJob::SvnStatusJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl( parent, KDevelop::OutputJob::Silent ) { setType( KDevelop::VcsJob::Status ); connect(m_job, &SvnInternalStatusJob::gotNewStatus, this, &SvnStatusJob::addToStats, Qt::QueuedConnection); setObjectName(i18n("Subversion Status")); } QVariant SvnStatusJob::fetchResults() { QList temp = m_stats; m_stats.clear(); return QVariant(temp); } void SvnStatusJob::start() { if( m_job->locations().isEmpty() ) { internalJobFailed(); setErrorText( i18n( "Not enough information to execute status job" ) ); } else { qCDebug(PLUGIN_SVN) << "Starting status job"; startInternalJob(); } } void SvnStatusJob::setLocations( const QList& urls ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setLocations( urls ); } void SvnStatusJob::setRecursive( bool recursive ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRecursive( recursive ); } void SvnStatusJob::addToStats( const KDevelop::VcsStatusInfo& info ) { //qCDebug(PLUGIN_SVN) << "new status info:" << info.url() << info.state(); if( !m_stats.contains( qVariantFromValue( info ) ) ) { m_stats << qVariantFromValue( info ); emit resultsReady( this ); }else { qCDebug(PLUGIN_SVN) << "Already have this info:"; } } diff --git a/plugins/subversion/svnstatusjob_p.h b/plugins/subversion/svnstatusjob_p.h index 488f98d4e..7df29b016 100644 --- a/plugins/subversion/svnstatusjob_p.h +++ b/plugins/subversion/svnstatusjob_p.h @@ -1,51 +1,49 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNSTATUSJOB_P_H #define KDEVPLATFORM_PLUGIN_SVNSTATUSJOB_P_H #include "svninternaljobbase.h" -#include - #include #include class SvnInternalStatusJob : public SvnInternalJobBase { Q_OBJECT public: explicit SvnInternalStatusJob( SvnJobBase* parent = nullptr ); void setLocations( const QList& ); void setRecursive( bool ); bool recursive() const; QList locations() const; signals: void gotNewStatus( const KDevelop::VcsStatusInfo& ); protected: void run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread* thread) override; private: QList m_locations; bool m_recursive; }; #endif diff --git a/plugins/subversion/svnupdatejob.cpp b/plugins/subversion/svnupdatejob.cpp index 39bc079da..adbe13f6d 100644 --- a/plugins/subversion/svnupdatejob.cpp +++ b/plugins/subversion/svnupdatejob.cpp @@ -1,168 +1,167 @@ /*************************************************************************** * 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 "svnupdatejob.h" #include "svnupdatejob_p.h" #include -#include #include #include "kdevsvncpp/client.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/targets.hpp" SvnInternalUpdateJob::SvnInternalUpdateJob( SvnJobBase* parent ) : SvnInternalJobBase( parent ) , m_recursive( false ) , m_ignoreExternals( false ) { } void SvnInternalUpdateJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { initBeforeRun(); svn::Client cli(m_ctxt); std::vector targets; QList l = locations(); foreach( const QUrl &url, l ) { QByteArray ba = url.toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ).toUtf8(); targets.push_back( svn::Path( ba.data() ) ); } try { svn::Revision rev = createSvnCppRevisionFromVcsRevision( m_revision ); if( rev.kind() == svn_opt_revision_unspecified ) { m_success = false; return; } cli.update( svn::Targets( targets ), rev, recursive(), ignoreExternals() ); }catch( svn::ClientException ce ) { qCDebug(PLUGIN_SVN) << "Exception while updating files: " << m_locations << QString::fromUtf8( ce.message() ); setErrorMessage( QString::fromUtf8( ce.message() ) ); m_success = false; } } void SvnInternalUpdateJob::setRecursive( bool recursive ) { QMutexLocker l( &m_mutex ); m_recursive = recursive; } void SvnInternalUpdateJob::setLocations( const QList& urls ) { QMutexLocker l( &m_mutex ); m_locations = urls; } void SvnInternalUpdateJob::setIgnoreExternals( bool ignore ) { QMutexLocker l( &m_mutex ); m_ignoreExternals = ignore; } bool SvnInternalUpdateJob::ignoreExternals() const { QMutexLocker l( &m_mutex ); return m_ignoreExternals; } void SvnInternalUpdateJob::setRevision( const KDevelop::VcsRevision& rev ) { QMutexLocker l( &m_mutex ); m_revision = rev; } QList SvnInternalUpdateJob::locations() const { QMutexLocker l( &m_mutex ); return m_locations; } KDevelop::VcsRevision SvnInternalUpdateJob::revision() const { QMutexLocker l( &m_mutex ); return m_revision; } bool SvnInternalUpdateJob::recursive() const { QMutexLocker l( &m_mutex ); return m_recursive; } SvnUpdateJob::SvnUpdateJob( KDevSvnPlugin* parent ) : SvnJobBaseImpl(parent, KDevelop::OutputJob::Verbose ) { setType( KDevelop::VcsJob::Add ); setObjectName(i18n("Subversion Update")); } QVariant SvnUpdateJob::fetchResults() { return QVariant(); } void SvnUpdateJob::start() { if( m_job->locations().isEmpty() ) { internalJobFailed(); setErrorText( i18n( "Not enough Information to execute update" ) ); }else { qCDebug(PLUGIN_SVN) << "updating urls:" << m_job->locations(); startInternalJob(); } } void SvnUpdateJob::setLocations( const QList& urls ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setLocations( urls ); } void SvnUpdateJob::setRecursive( bool recursive ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRecursive( recursive ); } void SvnUpdateJob::setRevision( const KDevelop::VcsRevision& rev ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setRevision( rev ); } void SvnUpdateJob::setIgnoreExternals( bool ignore ) { if( status() == KDevelop::VcsJob::JobNotStarted ) m_job->setIgnoreExternals( ignore ); } diff --git a/plugins/subversion/svnupdatejob.h b/plugins/subversion/svnupdatejob.h index cd04bec6b..4b39f295c 100644 --- a/plugins/subversion/svnupdatejob.h +++ b/plugins/subversion/svnupdatejob.h @@ -1,47 +1,46 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_SVNUPDATEJOB_H #define KDEVPLATFORM_PLUGIN_SVNUPDATEJOB_H #include "svnjobbase.h" -#include #include #include class SvnInternalUpdateJob; class SvnUpdateJob : public SvnJobBaseImpl { Q_OBJECT public: explicit SvnUpdateJob( KDevSvnPlugin* parent ); QVariant fetchResults() override; void start() override; void setLocations( const QList& locations ); void setRecursive( bool ); void setRevision( const KDevelop::VcsRevision& ); void setIgnoreExternals( bool ); }; #endif diff --git a/plugins/subversion/tests/svnimport.cpp b/plugins/subversion/tests/svnimport.cpp index d4a6ef8ca..ebc19ca8e 100644 --- a/plugins/subversion/tests/svnimport.cpp +++ b/plugins/subversion/tests/svnimport.cpp @@ -1,164 +1,164 @@ /*************************************************************************** * 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 "svnimport.h" -#include +#include #include +#include #include #include -#include #include #include #include #include #include #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); if (!j->exec()) { qDebug() << j->errorString(); // On error, wait for key in order to allow manual state inspection } QCOMPARE(j->status(), status); } void setupLocalRepository( const QString& name, VcsLocation & reposLoc ) { KProcess cmd; cmd.setWorkingDirectory(name); cmd << QStringLiteral("svnadmin") << QStringLiteral("create") << name; QCOMPARE(cmd.execute(10000), 0); reposLoc.setRepositoryServer("file://" + name ); } void setupSampleProject( const QString& name, const QString& content ) { QFile sampleFile( name + "/sample.file" ); sampleFile.open( QIODevice::WriteOnly ); sampleFile.write( content.toUtf8() ); sampleFile.close(); } void SvnImport::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevplatform.plugins.svn.debug=true\n")); AutoTestShell::init({QStringLiteral("kdevsubversion")}); TestCore::initialize(); QList plugins = Core::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IBasicVersionControl")); 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); } void SvnImport::cleanupTestCase() { TestCore::shutdown(); } void SvnImport::testBasic() { QTemporaryDir reposDir; VcsLocation reposLoc; setupLocalRepository( reposDir.path(), reposLoc ); QTemporaryDir projectDir; QString origcontent = QStringLiteral("This is a Test"); setupSampleProject( projectDir.path(), origcontent ); VcsJob* job = vcs->import( QStringLiteral("import test"), QUrl::fromLocalFile( projectDir.path() ), reposLoc ); validatingExecJob(job); QTemporaryDir checkoutDir; validateImport( reposLoc.repositoryServer(), checkoutDir, origcontent ); } void SvnImport::testImportWithMissingDirs() { QTemporaryDir reposDir; VcsLocation reposLoc; setupLocalRepository( reposDir.path(), reposLoc ); QTemporaryDir projectDir; QString origcontent = QStringLiteral("This is a Test"); setupSampleProject( projectDir.path(), origcontent ); reposLoc.setRepositoryServer( reposLoc.repositoryServer() + "/foobar/" + QDir( projectDir.path() ).dirName() ); VcsJob* job = vcs->import( QStringLiteral("import test"), QUrl::fromLocalFile( projectDir.path() ), reposLoc ); validatingExecJob(job); QTemporaryDir checkoutDir; validateImport( reposLoc.repositoryServer(), checkoutDir, origcontent ); } void SvnImport::testImportIntoDir() { QTemporaryDir reposDir; VcsLocation reposLoc; setupLocalRepository( reposDir.path(), reposLoc ); QTemporaryDir projectDir; QString origcontent = QStringLiteral("This is a Test"); setupSampleProject( projectDir.path(), origcontent ); reposLoc.setRepositoryServer( reposLoc.repositoryServer() + '/' + QDir( projectDir.path() ).dirName() ); VcsJob* job = vcs->import( QStringLiteral("import test"), QUrl::fromLocalFile( projectDir.path() ), reposLoc ); validatingExecJob(job); QTemporaryDir checkoutDir; validateImport( reposLoc.repositoryServer(), checkoutDir, origcontent ); } void SvnImport::validateImport( const QString& repourl, QTemporaryDir& checkoutdir, const QString& origcontent ) { VcsLocation reposLoc; reposLoc.setRepositoryServer( repourl ); VcsJob* job = vcs->createWorkingCopy( reposLoc, QUrl::fromLocalFile(checkoutdir.path()) ); validatingExecJob(job); QFile newfile( checkoutdir.path() + "/sample.file" ); QVERIFY(newfile.exists()); QVERIFY(newfile.open(QIODevice::ReadOnly)); QCOMPARE(QString::fromUtf8( newfile.readAll() ), origcontent); } QTEST_MAIN(SvnImport) diff --git a/plugins/subversion/tests/svnrecursiveadd.cpp b/plugins/subversion/tests/svnrecursiveadd.cpp index c63c8f672..1f8fe77a2 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 #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 = 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 a6c4c915e..18edb7d4e 100644 --- a/plugins/switchtobuddy/switchtobuddyplugin.cpp +++ b/plugins/switchtobuddy/switchtobuddyplugin.cpp @@ -1,319 +1,317 @@ /* * 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 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(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::NavigationGroup, 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 = 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 = 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 = 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 e39085f0f..576566e10 100644 --- a/plugins/testview/testview.cpp +++ b/plugins/testview/testview.cpp @@ -1,411 +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); setFocusProxy(edit); 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 nullptr; } QStandardItem* TestView::itemForProject(IProject* project) { QList itemsForProject = m_model->findItems(project->name()); if (!itemsForProject.isEmpty()) { return itemsForProject.first(); } 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() == 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() == 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() == nullptr) { // No sense in finding source code for projects. return; } 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 204f53596..36302e66f 100644 --- a/plugins/testview/testviewplugin.cpp +++ b/plugins/testview/testviewplugin.cpp @@ -1,155 +1,154 @@ /* 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: explicit TestToolViewFactory( TestViewPlugin *plugin ): mplugin( plugin ) {} 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.h b/plugins/vcschangesview/vcschangesviewplugin.h index 2cb9f01e1..eb3ea0d9c 100644 --- a/plugins/vcschangesview/vcschangesviewplugin.h +++ b/plugins/vcschangesview/vcschangesviewplugin.h @@ -1,55 +1,53 @@ /* 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. */ #ifndef KDEVPLATFORM_PLUGIN_VCSPROJECTINTEGRATIONPLUGIN_H #define KDEVPLATFORM_PLUGIN_VCSPROJECTINTEGRATIONPLUGIN_H #include #include #include -#include class QModelIndex; -class KJob; namespace KDevelop { class IProject; class IDocument; class VcsStatusInfo; class ProjectChangesModel; } class VcsProjectIntegrationPlugin : public KDevelop::IPlugin { Q_OBJECT public: VcsProjectIntegrationPlugin(QObject *parent, const QVariantList & args); KDevelop::ProjectChangesModel* model(); private slots: void activated(const QModelIndex& idx); private: KDevelop::ProjectChangesModel* m_model; friend class VCSProjectToolViewFactory; // to access activated() slot }; #endif diff --git a/plugins/welcomepage/uihelper.cpp b/plugins/welcomepage/uihelper.cpp index 54f85bca3..cdf26075e 100644 --- a/plugins/welcomepage/uihelper.cpp +++ b/plugins/welcomepage/uihelper.cpp @@ -1,86 +1,84 @@ /* 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 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/plugins/welcomepage/uihelper.h b/plugins/welcomepage/uihelper.h index ab9fa8a90..4fddaf7eb 100644 --- a/plugins/welcomepage/uihelper.h +++ b/plugins/welcomepage/uihelper.h @@ -1,41 +1,40 @@ /* 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. */ #ifndef UIHELPER_H #define UIHELPER_H #include -#include -class QUrl; +class QString; class QAction; class UiHelper : public QObject { Q_OBJECT public: explicit UiHelper(QObject* parent); public slots: QAction* retrieveMenuAction(const QString& name); void showMenu(const QString& name); void setArea(const QString& name); void raiseToolView(const QString& id); }; #endif // UIHELPER_H diff --git a/plugins/welcomepage/welcomepageview.cpp b/plugins/welcomepage/welcomepageview.cpp index 79d060564..943c4b6a1 100644 --- a/plugins/welcomepage/welcomepageview.cpp +++ b/plugins/welcomepage/welcomepageview.cpp @@ -1,74 +1,73 @@ /* This file is part of KDevelop Copyright 2011 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 "welcomepageview.h" #include "uihelper.h" #include "sessionsmodel.h" #include #include #include #include #include #include #include #include #include #include #include -#include using namespace KDevelop; WelcomePageWidget::WelcomePageWidget(const QList & /*projects*/, QWidget* parent) : QQuickWidget(parent) { qRegisterMetaType("KDevelop::IProjectController*"); qRegisterMetaType("KDevelop::IPluginController*"); qRegisterMetaType("PatchReviewPlugin*"); qRegisterMetaType(); qmlRegisterType("org.kdevelop.welcomepage", 4, 3, "SessionsModel"); //setup kdeclarative library KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.setTranslationDomain("kdevwelcomepage"); kdeclarative.setupBindings(); setResizeMode(QQuickWidget::SizeRootObjectToView); UiHelper* helper = new UiHelper(this); rootContext()->setContextProperty(QStringLiteral("kdev"), helper); rootContext()->setContextProperty(QStringLiteral("ICore"), KDevelop::ICore::self()); areaChanged(ICore::self()->uiController()->activeArea()); setSource(QUrl(QStringLiteral("qrc:/qml/main.qml"))); if(!errors().isEmpty()) { qWarning() << "welcomepage errors:" << errors(); } connect(Core::self()->uiControllerInternal()->activeSublimeWindow(), &Sublime::MainWindow::areaChanged, this, &WelcomePageWidget::areaChanged); } void WelcomePageWidget::areaChanged(Sublime::Area* area) { rootContext()->setContextProperty(QStringLiteral("area"), area->objectName()); } diff --git a/project/abstractfilemanagerplugin.cpp b/project/abstractfilemanagerplugin.cpp index 1f3169279..f0018348c 100644 --- a/project/abstractfilemanagerplugin.cpp +++ b/project/abstractfilemanagerplugin.cpp @@ -1,657 +1,656 @@ /*************************************************************************** * 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)) { 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(), 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 = 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 = 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, nullptr ); } //END Plugin #include "moc_abstractfilemanagerplugin.cpp" diff --git a/project/builderjob.h b/project/builderjob.h index 1f534d1b2..1ad98466d 100644 --- a/project/builderjob.h +++ b/project/builderjob.h @@ -1,121 +1,117 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_BUILDERJOB_H #define KDEVPLATFORM_BUILDERJOB_H -#include - #include "projectexport.h" #include -class KConfigGroup; - namespace KDevelop { class ProjectBaseItem; class IProject; /** * Allows to build a list of project items or projects sequentially, where * failing to build one item in the list will fail the whole job. */ class KDEVPLATFORMPROJECT_EXPORT BuilderJob : public ExecuteCompositeJob { Q_OBJECT public: /** * Defines what action to do on the Project builder */ enum BuildType { Build /**< Build the selected items */, Prune /**< Prune the selected items */, Configure /**< Configure the selected items */, Install /**< Install the selected items */, Clean /**< Clean the selected items */ }; /** * Creates a Builder job */ BuilderJob(); ~BuilderJob() override; /** * Allows to easily schedule building a couple of @p items using the * method identified by @p type * * @param type the build method to use * @param items the project items to add */ void addItems( BuildType type, const QList& items ); /** * Allows to easily schedule building a couple of @p projects using the * method identified by @p type * * @param type the build method to use * @param projects the projects to add */ void addProjects( BuildType type, const QList& projects ); /** * Allows to add a single @p item to the end of the list. The item will be * built using the method identified by @p type * * @param item The item to add to the list * @param type The build method to be used for the item */ void addItem( BuildType type, ProjectBaseItem* item ); /** * Allows to add a custom @p job to the end of the list. The build method specified by @p type * and (optionally) an item specified by @p item are needed to create a human-readable job name. * * @param type The build method which is represented by the @p job * @param job The job to add to the list * @param item The item which is build by the @p job */ void addCustomJob( BuildType type, KJob* job, ProjectBaseItem* item = nullptr ); /** * Updates the job's name. * * Shall be called before registering this job in the run controller, but after * adding all required tasks to the job. */ void updateJobName(); /** * Starts this job */ void start() override; private: class BuilderJobPrivate* const d; friend class BuilderJobPrivate; }; } #endif diff --git a/project/helper.cpp b/project/helper.cpp index 61e1ff1f2..af6fda15d 100644 --- a/project/helper.cpp +++ b/project/helper.cpp @@ -1,233 +1,232 @@ /* 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 "helper.h" #include "path.h" #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include using namespace KDevelop; bool KDevelop::removeUrl(const KDevelop::IProject* project, const QUrl& url, const bool isFolder) { QWidget* window = QApplication::activeWindow(); auto job = KIO::stat(url, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(job, window); if (!job->exec()) { qWarning() << "tried to remove non-existing url:" << url << project << isFolder; return true; } IPlugin* vcsplugin=project->versionControlPlugin(); if(vcsplugin) { IBasicVersionControl* vcs=vcsplugin->extension(); // We have a vcs and the file/folder is controller, need to make the rename through vcs if(vcs->isVersionControlled(url)) { VcsJob* job=vcs->remove(QList() << url); if(job) { return job->exec(); } } } //if we didn't find a VCS, we remove using KIO (if the file still exists, the vcs plugin might have simply deleted the url without returning a job auto deleteJob = KIO::del(url); KJobWidgets::setWindow(deleteJob, window); if (!deleteJob->exec() && url.isLocalFile() && (QFileInfo::exists(url.toLocalFile()))) { KMessageBox::error( window, isFolder ? i18n( "Cannot remove folder %1.", url.toDisplayString(QUrl::PreferLocalFile) ) : i18n( "Cannot remove file %1.", url.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } return true; } bool KDevelop::removePath(const KDevelop::IProject* project, const KDevelop::Path& path, const bool isFolder) { return removeUrl(project, path.toUrl(), isFolder); } bool KDevelop::createFile(const QUrl& file) { auto statJob = KIO::stat(file, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); if (statJob->exec()) { KMessageBox::error( QApplication::activeWindow(), i18n( "The file %1 already exists.", file.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } { auto uploadJob = KIO::storedPut(QByteArray("\n"), file, -1); KJobWidgets::setWindow(uploadJob, QApplication::activeWindow()); if (!uploadJob->exec()) { KMessageBox::error( QApplication::activeWindow(), i18n( "Cannot create file %1.", file.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } } return true; } bool KDevelop::createFile(const KDevelop::Path& file) { return createFile(file.toUrl()); } bool KDevelop::createFolder(const QUrl& folder) { auto mkdirJob = KIO::mkdir(folder); KJobWidgets::setWindow(mkdirJob, QApplication::activeWindow()); if (!mkdirJob->exec()) { KMessageBox::error( QApplication::activeWindow(), i18n( "Cannot create folder %1.", folder.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } return true; } bool KDevelop::createFolder(const KDevelop::Path& folder) { return createFolder(folder.toUrl()); } bool KDevelop::renameUrl(const KDevelop::IProject* project, const QUrl& oldname, const QUrl& newname) { bool wasVcsMoved = false; IPlugin* vcsplugin = project->versionControlPlugin(); if (vcsplugin) { IBasicVersionControl* vcs = vcsplugin->extension(); // We have a vcs and the file/folder is controller, need to make the rename through vcs if (vcs->isVersionControlled(oldname)) { VcsJob* job = vcs->move(oldname, newname); if (job && !job->exec()) { return false; } wasVcsMoved = true; } } // Fallback for the case of no vcs, or not-vcs-managed file/folder // try to save-as the text document, so users can directly continue to work // on the renamed url as well as keeping the undo-stack intact IDocument* document = ICore::self()->documentController()->documentForUrl(oldname); if (document && document->textDocument()) { if (!document->textDocument()->saveAs(newname)) { return false; } if (!wasVcsMoved) { // unlink the old file removeUrl(project, oldname, false); } return true; } else if (!wasVcsMoved) { // fallback for non-textdocuments (also folders e.g.) KIO::CopyJob* job = KIO::move(oldname, newname); KJobWidgets::setWindow(job, QApplication::activeWindow()); bool success = job->exec(); if (success) { // save files that where opened in this folder under the new name Path oldBasePath(oldname); Path newBasePath(newname); foreach (auto doc, ICore::self()->documentController()->openDocuments()) { auto textDoc = doc->textDocument(); if (textDoc && oldname.isParentOf(doc->url())) { const auto path = Path(textDoc->url()); const auto relativePath = oldBasePath.relativePath(path); const auto newPath = Path(newBasePath, relativePath); textDoc->saveAs(newPath.toUrl()); } } } return success; } else { return true; } } bool KDevelop::renamePath(const KDevelop::IProject* project, const KDevelop::Path& oldName, const KDevelop::Path& newName) { return renameUrl(project, oldName.toUrl(), newName.toUrl()); } bool KDevelop::copyUrl(const KDevelop::IProject* project, const QUrl& source, const QUrl& target) { IPlugin* vcsplugin=project->versionControlPlugin(); if(vcsplugin) { IBasicVersionControl* vcs=vcsplugin->extension(); // We have a vcs and the file/folder is controller, need to make the rename through vcs if(vcs->isVersionControlled(source)) { VcsJob* job=vcs->copy(source, target); if(job) { return job->exec(); } } } // Fallback for the case of no vcs, or not-vcs-managed file/folder auto job = KIO::copy(source, target); KJobWidgets::setWindow(job, QApplication::activeWindow()); return job->exec(); } bool KDevelop::copyPath(const KDevelop::IProject* project, const KDevelop::Path& source, const KDevelop::Path& target) { return copyUrl(project, source.toUrl(), target.toUrl()); } Path KDevelop::proposedBuildFolder(const Path& sourceFolder) { Path proposedBuildFolder; if (sourceFolder.path().contains(QStringLiteral("/src/"))) { const QString srcBuildPath = sourceFolder.path().replace(QStringLiteral("/src/"), QStringLiteral("/build/")); Q_ASSERT(!srcBuildPath.isEmpty()); if (QDir(srcBuildPath).exists()) { proposedBuildFolder = Path(srcBuildPath); } } if (!proposedBuildFolder.isValid()) { proposedBuildFolder = Path(sourceFolder, QStringLiteral("build")); } return proposedBuildFolder; } diff --git a/project/interfaces/iprojectbuilder.h b/project/interfaces/iprojectbuilder.h index 285ec217b..a75e2dfe0 100644 --- a/project/interfaces/iprojectbuilder.h +++ b/project/interfaces/iprojectbuilder.h @@ -1,133 +1,133 @@ /* 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. */ #ifndef KDEVPLATFORM_IPROJECTBUILDER_H #define KDEVPLATFORM_IPROJECTBUILDER_H #include -#include +#include #include class KJob; namespace KDevelop { class IProject; class ProjectBaseItem; /** @author Roberto Raggi @short IProjectBuilder Base class for the Project Builders Provides an interface that will bre useful to perform tasks upon projects, inspired on the unix-style way to build projects: configure -> make -> make install It will provide a job for each of the tasks and a signal to make sure it completed successfully. */ class KDEVPLATFORMPROJECT_EXPORT IProjectBuilder { public: virtual ~IProjectBuilder(); /** * Installs the given project @p item, exact behaviour depends * on the implementation. * * @param specificPrefix defines where the project will be installed. */ virtual KJob* install(ProjectBaseItem* item, const QUrl &specificPrefix = {}) = 0; /** * Builds the given project @p item, exact behaviour depends * on the implementation */ virtual KJob* build(ProjectBaseItem *item) = 0; /** * Cleans the given project @p item, exact behaviour depends * on the implementation. The cleaning should only include * output files, like C/C++ object files, binaries, files * that the builder needs shouldn't be removed. */ virtual KJob* clean(ProjectBaseItem *item) = 0; /** * Configures the given @p project, exact behaviour depends * on the implementation. After calling this a build() call should * succeed (given the code doesn't have errors). * * This function is optional, the default implementation does nothing. */ virtual KJob* configure(IProject* project); /** * Prunes the given @p project, exact behaviour depends * on the implementation. Additionally to what clean() does this may * also remove files used for the builder (or a "subbuilder"), for example * generated Makefiles in QMake/CMake/Automake projects * * This function is optional, the default implementation does nothing. */ virtual KJob* prune(IProject* project); /** * Provide access to the builders related to the @p project. * The list returned by this method is used to select the appropriate config pages for a project. * This method may safely return an empty list, as does the default implementation. */ virtual QList additionalBuilderPlugins( IProject* project ) const; Q_SIGNALS: /** * Emitted when the build for the given item was finished */ void built(ProjectBaseItem *dom); /** * Emitted when the install for the given item was finished */ void installed(ProjectBaseItem*); /** * Emitted when the clean for the given item was finished */ void cleaned(ProjectBaseItem*); /** * Emitted when any of the scheduled actions for the given item was failed */ void failed(ProjectBaseItem *dom); /** * Emitted when the configure for the given item was finished */ void configured(IProject*); /** * Emitted when the pruning for the given item was finished */ void pruned(IProject*); }; } Q_DECLARE_INTERFACE( KDevelop::IProjectBuilder, "org.kdevelop.IProjectBuilder" ) #endif diff --git a/project/interfaces/iprojectfilemanager.h b/project/interfaces/iprojectfilemanager.h index d6590b213..ab49b63db 100644 --- a/project/interfaces/iprojectfilemanager.h +++ b/project/interfaces/iprojectfilemanager.h @@ -1,180 +1,179 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2006 Matt Rogers Copyright 2006 Hamish Rodda 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. */ #ifndef KDEVPLATFORM_IPROJECTFILEMANAGER_H #define KDEVPLATFORM_IPROJECTFILEMANAGER_H #include -#include #include #include class KJob; namespace KDevelop { class IProject; class ProjectBaseItem; class ProjectFolderItem; class ProjectFileItem; /** * @short An interface to project file management * * FileManager is the class you want to implement for integrating * a project manager in KDevelop. For build systems, implement its * child class, BuildManager. * * These classes \e do \e not cause files, folders etc. to be created * or removed on disk. They simply read from and write to the file(s) * which describe the structure (eg. CMakeLists.txt for cmake, Makefile.am for automake, etc). * * @author Roberto Raggi, Matt Rogers, Hamish Rodda, Milian Wolff */ class KDEVPLATFORMPROJECT_EXPORT IProjectFileManager { public: virtual ~IProjectFileManager(); /** Features the file manager supports */ enum Feature { None = 0 , ///< This manager supports nothing Folders = 1 << 0, ///< Folders are supported by the manager Targets = 1 << 1, ///< Targets are supported by the manager Files = 1 << 2 ///< Files are supported by the manager }; Q_DECLARE_FLAGS( Features, Feature ) /** * @return the Features supported by the filemanager */ virtual Features features() const = 0; /** * This method initialize the model item @arg dom * @return The list of the sub folders */ virtual QList parse(ProjectFolderItem *dom) = 0; /** * This method creates the root item from the file @arg fileName * @return The created item */ virtual ProjectFolderItem *import(IProject *project) = 0; /** * @brief This method creates an import job for the given @p item * * @details The default implementation should be suitable for most needs, * it'll create an instance of class @ref ImportProjectJob * * @return a job that imports the project */ virtual KJob* createImportJob(ProjectFolderItem* item); /** * Add a folder to the project and create it on disk. * * Adds the folder specified by @p folder to @p parent and modifies the * underlying build system if needed */ virtual ProjectFolderItem* addFolder(const Path& folder, ProjectFolderItem* parent) = 0; /** * Add a file to a folder and create it on disk. * * Adds the file specified by @p file to the folder @p parent and modifies * the underlying build system if needed. The file is not added to a target */ virtual ProjectFileItem* addFile(const Path& file, ProjectFolderItem *parent) = 0; /** * Remove files or folders from the project and delete them from disk * * Removes the files or folders specified by @p items and * modifies the underlying build system if needed. * * Note: Do not attempt to remove subitems along with their parents */ virtual bool removeFilesAndFolders(const QList &items) = 0; /** * Move files and folders within a given project * * Moves the files or folders specified by @p items to @p newParent and * modifies the underlying build system as needed * * Note: Do not attempt to move subitems along with their parents */ virtual bool moveFilesAndFolders(const QList< KDevelop::ProjectBaseItem* > &items, KDevelop::ProjectFolderItem* newParent) = 0; /** * Copy files and folders within a given project * * Copies the files or folders specified by @p items to @p newParent and * modifies the underlying build system as needed * * Note: Do not attempt to copy subitems along with their parents */ virtual bool copyFilesAndFolders(const Path::List &items, KDevelop::ProjectFolderItem* newParent) = 0; /** * Rename a file in the project * * Renames the file specified by @p oldFile to @p newPath */ virtual bool renameFile(ProjectFileItem* file, const Path& newPath) = 0; /** * Rename a folder in the project * * Renames the folder specified by @p oldFile to @p newPath */ virtual bool renameFolder(ProjectFolderItem* oldFolder, const Path& newPath) = 0; /** * Reload an item in the project * * Reloads the item specified by @p item */ virtual bool reload(ProjectFolderItem* item) = 0; Q_SIGNALS: void folderAdded(KDevelop::ProjectFolderItem* folder); void folderRemoved(KDevelop::ProjectFolderItem* folder); void folderRenamed(const KDevelop::Path& oldFolder, KDevelop::ProjectFolderItem* newFolder); void fileAdded(KDevelop::ProjectFileItem* file); void fileRemoved(KDevelop::ProjectFileItem* file); void fileRenamed(const KDevelop::Path& oldFile, KDevelop::ProjectFileItem* newFile); }; } Q_DECLARE_OPERATORS_FOR_FLAGS( KDevelop::IProjectFileManager::Features ) Q_DECLARE_INTERFACE( KDevelop::IProjectFileManager, "org.kdevelop.IProjectFileManager") #endif diff --git a/project/interfaces/iprojectfilter.h b/project/interfaces/iprojectfilter.h index 63d613f05..c3e37bb34 100644 --- a/project/interfaces/iprojectfilter.h +++ b/project/interfaces/iprojectfilter.h @@ -1,60 +1,58 @@ /* 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 Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_IPROJECTFILTER_H #define KDEVPLATFORM_IPROJECTFILTER_H #include -#include - namespace KDevelop { class Path; /** * @short The actual project filter object. * * Instances of this class should allow threadsafe filtering of project contents. * * @author Milian Wolff */ class KDEVPLATFORMPROJECT_EXPORT IProjectFilter { public: virtual ~IProjectFilter(); /** * Check whether the given @p path should be included in a project. * * @param path is the path that you want to be checked. * @param isFolder distinguishes between files and folders. * * @return true when the given path should be included in the project, * false otherwise, i.e. when the path should be hidden. */ virtual bool isValid(const Path& path, bool isFolder) const = 0; }; } #endif // KDEVPLATFORM_IPROJECTFILTER_H diff --git a/project/projectbuildsetmodel.h b/project/projectbuildsetmodel.h index bce29fac7..c00fca12a 100644 --- a/project/projectbuildsetmodel.h +++ b/project/projectbuildsetmodel.h @@ -1,95 +1,93 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PROJECTBUILDSETMODEL_H #define KDEVPLATFORM_PROJECTBUILDSETMODEL_H #include "projectexport.h" #include #include -class KConfigGroup; - namespace KDevelop { class ProjectBaseItem; class ICore; class IProject; class ISession; class KDEVPLATFORMPROJECT_EXPORT BuildItem { public: BuildItem(); explicit BuildItem( const QStringList& itemPath ); BuildItem( const BuildItem& rhs ); explicit BuildItem( KDevelop::ProjectBaseItem* ); void initializeFromItem( KDevelop::ProjectBaseItem* item ); KDevelop::ProjectBaseItem* findItem() const; BuildItem& operator=( const BuildItem& ); QString itemName() const; QStringList itemPath() const { return m_itemPath; } QString itemProject() const; private: QStringList m_itemPath; }; bool operator==( const BuildItem& rhs, const BuildItem& lhs ); class KDEVPLATFORMPROJECT_EXPORT ProjectBuildSetModel : public QAbstractTableModel { Q_OBJECT public: explicit ProjectBuildSetModel( QObject* parent ); QVariant data( const QModelIndex&, int role = Qt::DisplayRole ) const override; QVariant headerData( int, Qt::Orientation, int role = Qt::DisplayRole ) const override; int rowCount( const QModelIndex& = QModelIndex() ) const override; int columnCount( const QModelIndex& = QModelIndex() ) const override; void loadFromSession( ISession* session ); void storeToSession( ISession* session ); void addProjectItem( KDevelop::ProjectBaseItem* ); bool removeRows( int row, int count, const QModelIndex& parent = QModelIndex() ) override; void moveRowsUp( int row, int count ); void moveRowsDown( int row, int count ); void moveRowsToTop( int row, int count ); void moveRowsToBottom( int row, int count ); QList items(); public slots: void saveToProject( KDevelop::IProject* ) const; void loadFromProject( KDevelop::IProject* ); void projectClosed( KDevelop::IProject* ); private: QList m_items; QList< QStringList > m_orderingCache; int findInsertionPlace( const QStringList& itemPath ); void removeItemsWithCache( const QList& itemIndices ); void insertItemWithCache( const KDevelop::BuildItem& item ); void insertItemsOverrideCache( int index, const QList& items ); }; } #endif //kate: space-indent on; indent-width 4; replace-tabs on; auto-insert-doxygen on; indent-mode cstyle; diff --git a/project/projectconfigpage.h b/project/projectconfigpage.h index a9477defb..35ee9d563 100644 --- a/project/projectconfigpage.h +++ b/project/projectconfigpage.h @@ -1,92 +1,91 @@ /* KDevelop * * 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 General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef KDEVPLATFORM_PROJECTCONFIGPAGE_H #define KDEVPLATFORM_PROJECTCONFIGPAGE_H #include #include #include #include "projectconfigskeleton.h" class KComponentData; class QWidget; -class QStringList; namespace KDevelop { /** This is needed because IProject does not expose these methods */ struct ProjectConfigOptions { QString developerTempFile; Path developerFile; QString projectTempFile; KDevelop::IProject* project; }; } /** * @tparam T a class derived from KDevelop::ProjectConfigSkeleton. */ template class ProjectConfigPage : public KDevelop::ConfigPage { public: ProjectConfigPage(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent) : KDevelop::ConfigPage(plugin, initConfigSkeleton(options), parent) , m_project(options.project) { static_assert(std::is_base_of::value, "T must inherit from KDevelop::ProjectConfigSkeleton"); KDevelop::ProjectConfigSkeleton* conf = T::self(); conf->setDeveloperTempFile(options.developerTempFile); conf->setDeveloperFile(options.developerFile); conf->setProjectTempFile(options.projectTempFile); conf->setProjectFile(m_project->projectFile()); } virtual ~ProjectConfigPage() { // we have to delete T::self otherwise we get the following message on the // next call to T::intance(QString): // "T::instance called after the first use - ignoring" // which means that we will continue using the old file delete T::self(); } KDevelop::IProject* project() const { return m_project; } private: static inline KDevelop::ProjectConfigSkeleton* initConfigSkeleton(const KDevelop::ProjectConfigOptions& options) { T::instance(options.developerTempFile); return T::self(); } private: KDevelop::IProject* m_project; }; #endif diff --git a/project/projectmodel.h b/project/projectmodel.h index 89e037fd0..20beecf57 100644 --- a/project/projectmodel.h +++ b/project/projectmodel.h @@ -1,491 +1,490 @@ /* 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. */ #ifndef KDEVPLATFORM_PROJECTMODEL_H #define KDEVPLATFORM_PROJECTMODEL_H #include #include #include "projectexport.h" -#include template struct QPair; template class QList; namespace KDevelop { class IProject; class ProjectFolderItem; class ProjectBuildFolderItem; class ProjectFileItem; class ProjectTargetItem; class ProjectExecutableTargetItem; class ProjectLibraryTargetItem; class ProjectModel; class IndexedString; class Path; /** * Base class to implement the visitor pattern for the project item tree. * * Feel free to subclass it and add overloads for the methods corresponding * to the items you are interested in. * * Start visiting using one of the visit methods. */ class KDEVPLATFORMPROJECT_EXPORT ProjectVisitor { public: ProjectVisitor(); virtual ~ProjectVisitor(); /** * Visit the whole project model tree. */ virtual void visit( ProjectModel* ); /** * Visit the tree starting from the project root item. */ virtual void visit( IProject* ); /** * Visit the folder and anything it contains. */ virtual void visit( ProjectFolderItem* ); /** * Visit the file. */ virtual void visit( ProjectFileItem* ); /** * Visit the build folder and anything it contains. */ virtual void visit( ProjectBuildFolderItem* ); /** * Visit the target and all children it may contain. */ virtual void visit( ProjectExecutableTargetItem* ); /** * Visit the target and all children it may contain. */ virtual void visit( ProjectLibraryTargetItem* ); }; /** * Interface that allows a developer to implement the three basic types of * items you would see in a multi-project * \li Folder * \li Project * \li Custom Target * \li Library Target * \li Executable Target * \li File */ class KDEVPLATFORMPROJECT_EXPORT ProjectBaseItem { public: ProjectBaseItem( IProject*, const QString &name, ProjectBaseItem *parent = nullptr ); virtual ~ProjectBaseItem(); enum ProjectItemType { BaseItem = 0 /** item is a base item */, BuildFolder = 1 /** item is a buildable folder */, Folder = 2 /** item is a folder */, ExecutableTarget = 3 /** item is an executable target */, LibraryTarget = 4 /** item is a library target */, Target = 5 /** item is a target */, File = 6 /** item is a file */, CustomProjectItemType = 100 /** type which should be used as base for custom types */ }; enum RenameStatus { RenameOk = 0, ExistingItemSameName = 1, ProjectManagerRenameFailed = 2, InvalidNewName = 3 }; /** @returns Returns the project that the item belongs to. */ IProject* project() const; /** @returns If this item is a folder, it returns a pointer to the folder, otherwise returns a 0 pointer. */ virtual ProjectFolderItem *folder() const; /** @returns If this item is a target, it returns a pointer to the target, otherwise returns a 0 pointer. */ virtual ProjectTargetItem *target() const; /** @returns If this item is a file, it returns a pointer to the file, otherwise returns a 0 pointer. */ virtual ProjectFileItem *file() const; /** @returns If this item is a file, it returns a pointer to the file, otherwise returns a 0 pointer. */ virtual ProjectExecutableTargetItem *executable() const; /** @returns Returns a list of the folders that have this object as the parent. */ QList folderList() const; /** @returns Returns a list of the targets that have this object as the parent. */ QList targetList() const; /** @returns Returns a list of the files that have this object as the parent. */ QList fileList() const; virtual bool lessThan( const KDevelop::ProjectBaseItem* ) const; static bool pathLessThan(KDevelop::ProjectBaseItem* item1, KDevelop::ProjectBaseItem* item2); /** @returns the @p row item in the list of children of this item or 0 if there is no such child. */ ProjectBaseItem* child( int row ) const; /** @returns the list of children of this item. */ QList children() const; /** @returns a valid QModelIndex for usage with the model API for this item. */ QModelIndex index() const; /** @returns The parent item if this item has one, else it return 0. */ virtual ProjectBaseItem* parent() const; /** @returns the displayed text of this item. */ QString text() const; /** @returns the row in the list of children of this items parent, or -1. */ int row() const; /** @returns the number of children of this item, or 0 if there are none. */ int rowCount() const; /** @returns the model to which this item belongs, or 0 if its not associated to a model. */ ProjectModel* model() const; /** * Adds a new child item to this item. */ void appendRow( ProjectBaseItem* item ); /** * Removes and deletes the item at the given @p row if there is one. */ void removeRow( int row ); /** * Removes and deletes the @p count items after the given @p row if there is one. */ void removeRows( int row, int count ); /** * Returns and removes the item at the given @p row if there is one. */ ProjectBaseItem* takeRow( int row ); /** @returns RTTI info, allows one to know the type of item */ virtual int type() const; /** @returns a string to pass to QIcon::fromTheme() as icon-name suitable to represent this item. */ virtual QString iconName() const; /** * Set the path of this item. * * @note This function never renames the item in the project manager or * on the filesystem, it only changes the path and possibly the text nothing else. */ virtual void setPath( const Path& ); /** * @returns the path of this item. */ Path path() const; /** * @returns the basename of this items path (if any) * * Convenience function, returns the same as @c text() for most items. */ QString baseName() const; /** * Renames the item to the new name. * @returns status information whether the renaming succeeded. */ virtual RenameStatus rename( const QString& newname ); bool isProjectRoot() const; /** * Default flags: Qt::ItemIsEnabled | Qt::ItemIsSelectable * * @returns the flags supported by the item */ virtual Qt::ItemFlags flags(); /** * Sets what flags should be returned by ::flags() method. */ void setFlags(Qt::ItemFlags flags); protected: /** * Allows to change the displayed text of this item. * * Most items assume text == baseName so this is *not* public. * * @param text the new text */ void setText( const QString& text ); class ProjectBaseItemPrivate* const d_ptr; void setRow( int row ); void setModel( ProjectModel* model ); private: Q_DECLARE_PRIVATE(ProjectBaseItem) friend class ProjectModel; }; /** * Implementation of the ProjectBaseItem interface that is specific to a * folder */ class KDEVPLATFORMPROJECT_EXPORT ProjectFolderItem: public ProjectBaseItem { public: /** * Create a new ProjectFolderItem with the given @p path and an optional @p parent * in the given @p project. */ ProjectFolderItem( IProject* project, const Path& path, ProjectBaseItem* parent = nullptr ); /** * Create a child ProjectFolderItem of @p parent with @p name. * * The path is set automatically. */ ProjectFolderItem( const QString& name, ProjectBaseItem *parent ); ~ProjectFolderItem() override; void setPath(const Path& ) override; ProjectFolderItem *folder() const override; ///Reimplemented from QStandardItem int type() const override; /** * Get the folder name, equal to path().fileName() or text(). */ QString folderName() const; /** @returns Returns whether this folder directly contains the specified file or folder. */ bool hasFileOrFolder(const QString& name) const; QString iconName() const override; RenameStatus rename(const QString& newname) override; private: void propagateRename( const Path& newBase ) const; }; /** * Folder which contains buildable targets as part of a buildable project */ class KDEVPLATFORMPROJECT_EXPORT ProjectBuildFolderItem: public ProjectFolderItem { public: /** * Create a new ProjectBuildFolderItem with the given @p path with the optional * parent @p parent in the given @p project. */ ProjectBuildFolderItem( IProject* project, const Path &path, ProjectBaseItem* parent = nullptr ); /** * Create a child ProjectBuildFolderItem of @p parent with @p name. * * The path is set automatically. */ ProjectBuildFolderItem( const QString &name, ProjectBaseItem *parent ); ///Reimplemented from QStandardItem int type() const override; QString iconName() const override; }; /** * Object which represents a target in a build system. * * This object contains all properties specific to a target. */ class KDEVPLATFORMPROJECT_EXPORT ProjectTargetItem: public ProjectBaseItem { public: ProjectTargetItem( IProject*, const QString &name, ProjectBaseItem *parent = nullptr ); ///Reimplemented from QStandardItem int type() const override; ProjectTargetItem *target() const override; QString iconName() const override; void setPath(const Path& path ) override; }; /** * Object which represents an executable target in a build system. * * This object contains all properties specific to an executable. */ class KDEVPLATFORMPROJECT_EXPORT ProjectExecutableTargetItem: public ProjectTargetItem { public: ProjectExecutableTargetItem( IProject*, const QString &name, ProjectBaseItem *parent = nullptr ); ProjectExecutableTargetItem *executable() const override; int type() const override; virtual QUrl builtUrl() const=0; virtual QUrl installedUrl() const=0; }; /** * Object which represents a library target in a build system. * * This object contains all properties specific to a library. */ class KDEVPLATFORMPROJECT_EXPORT ProjectLibraryTargetItem: public ProjectTargetItem { public: ProjectLibraryTargetItem(IProject* project, const QString &name, ProjectBaseItem *parent = nullptr ); int type() const override; }; /** * Object which represents a file. */ class KDEVPLATFORMPROJECT_EXPORT ProjectFileItem: public ProjectBaseItem { public: /** * Create a new ProjectFileItem with the given @p path and an optional @p parent * in the given @p project. */ ProjectFileItem( IProject* project, const Path& path, ProjectBaseItem* parent = nullptr ); /** * Create a child ProjectFileItem of @p parent with the given @p name. * * The path is set automatically. */ ProjectFileItem( const QString& name, ProjectBaseItem *parent ); ~ProjectFileItem() override; ///Reimplemented from QStandardItem int type() const override; ProjectFileItem *file() const override; /** * @returns the file name, equal to path().fileName() or text() */ QString fileName() const; void setPath( const Path& ) override; QString iconName() const override; RenameStatus rename(const QString& newname) override; /** * @return the items indexed path, which is often required for performant * lookups or memory efficient storage. */ IndexedString indexedPath() const; }; /** * Class providing some convenience methods for accessing the project model * @todo: maybe switch to QAbstractItemModel, would make the implementation * for at least the checkbox-behaviour easier */ class KDEVPLATFORMPROJECT_EXPORT ProjectModel: public QAbstractItemModel { Q_OBJECT public: enum Roles { ProjectRole = Qt::UserRole+1 , ProjectItemRole , UrlRole , LastRole }; explicit ProjectModel( QObject *parent = nullptr ); ~ProjectModel() override; void clear(); void appendRow( ProjectBaseItem* item ); void removeRow( int row ); ProjectBaseItem* takeRow( int row ); ProjectBaseItem* itemAt( int row ) const; QList topItems() const; QModelIndex pathToIndex(const QStringList& tofetch) const; QStringList pathFromIndex(const QModelIndex& index) const; QModelIndex indexFromItem( const ProjectBaseItem* item ) const; ProjectBaseItem* itemFromIndex( const QModelIndex& ) const; int columnCount( const QModelIndex& parent = QModelIndex() ) const override; QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const override; QModelIndex parent( const QModelIndex& child ) const override; int rowCount( const QModelIndex& parent = QModelIndex() ) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; bool insertColumns(int column, int count, const QModelIndex& parent = QModelIndex()) override; bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; Qt::ItemFlags flags(const QModelIndex& index) const override; Qt::DropActions supportedDropActions() const override; /** * @return all items for the given indexed path. */ QList itemsForPath(const IndexedString& path) const; /** * Returns the first item for the given indexed path. */ ProjectBaseItem* itemForPath(const IndexedString& path) const; private: class ProjectModelPrivate* const d; friend class ProjectBaseItem; }; KDEVPLATFORMPROJECT_EXPORT QStringList joinProjectBasePath( const QStringList& partialpath, KDevelop::ProjectBaseItem* item ); KDEVPLATFORMPROJECT_EXPORT QStringList removeProjectBasePath( const QStringList& fullpath, KDevelop::ProjectBaseItem* item ); } Q_DECLARE_METATYPE(KDevelop::ProjectBaseItem*) Q_DECLARE_METATYPE(KDevelop::ProjectFolderItem*) Q_DECLARE_METATYPE(KDevelop::ProjectFileItem*) Q_DECLARE_METATYPE(KDevelop::ProjectLibraryTargetItem*) Q_DECLARE_METATYPE(KDevelop::ProjectExecutableTargetItem*) Q_DECLARE_METATYPE(KDevelop::ProjectTargetItem*) Q_DECLARE_METATYPE(KDevelop::ProjectBuildFolderItem*) Q_DECLARE_METATYPE(QList) #endif // KDEVPLATFORM_PROJECTMODEL_H diff --git a/project/projectproxymodel.h b/project/projectproxymodel.h index 398e3db9e..eb92749b6 100644 --- a/project/projectproxymodel.h +++ b/project/projectproxymodel.h @@ -1,57 +1,52 @@ /* This file is part of KDevelop 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. */ #ifndef KDEVPLATFORM_PROJECTPROXYMODEL_H #define KDEVPLATFORM_PROJECTPROXYMODEL_H #include #include "projectexport.h" -#include -#include -#include -#include - namespace KDevelop { class ProjectModel; class ProjectBaseItem; } class KDEVPLATFORMPROJECT_EXPORT ProjectProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit ProjectProxyModel(QObject *parent); bool lessThan (const QModelIndex & left, const QModelIndex & right) const override; QModelIndex proxyIndexFromItem(KDevelop::ProjectBaseItem* item) const; KDevelop::ProjectBaseItem* itemFromProxyIndex(const QModelIndex&) const; void showTargets(bool visible); protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; private: KDevelop::ProjectModel* projectModel() const; bool m_showTargets; }; #endif diff --git a/project/tests/projectmodelperformancetest.cpp b/project/tests/projectmodelperformancetest.cpp index b535acc1c..7fd426b4f 100644 --- a/project/tests/projectmodelperformancetest.cpp +++ b/project/tests/projectmodelperformancetest.cpp @@ -1,207 +1,205 @@ /*************************************************************************** * 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( 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( 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 = 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 = nullptr; if( currentParent.size() < BIG_DEPTH ) { item = new ProjectFolderItem(nullptr, path, parent); } else { 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( 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 a7b48f814..79cc7f229 100644 --- a/project/tests/test_projectmodel.cpp +++ b/project/tests/test_projectmodel.cpp @@ -1,546 +1,545 @@ /*************************************************************************** * 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 = nullptr; switch( itemType ) { case ProjectBaseItem::Folder: newitem = new ProjectFolderItem( nullptr, itemPath ); break; case ProjectBaseItem::BuildFolder: newitem = new ProjectBuildFolderItem( nullptr, itemPath ); break; case ProjectBaseItem::File: 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 = nullptr; switch( itemType ) { case ProjectBaseItem::Target: newitem = new ProjectTargetItem( nullptr, itemText ); break; case ProjectBaseItem::LibraryTarget: 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( 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( nullptr, Path(QUrl::fromLocalFile("/"+folderName)) ); QCOMPARE(rootFolder->baseName(), folderName); ProjectFileItem* file = new ProjectFileItem( fileName, rootFolder ); QCOMPARE(file->baseName(), fileName); ProjectTargetItem* target = new ProjectTargetItem( nullptr, targetName ); rootFolder->appendRow( 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 = 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( 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( 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(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, nullptr); proj->setProjectItem( rootItem ); new ProjectFileItem(QStringLiteral("existing"), rootItem); 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"))), 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(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(nullptr, Path(QUrl::fromLocalFile(QStringLiteral("/tmp/")))); ProjectFolderItem* folder = new ProjectFolderItem(QStringLiteral("a"), root); ProjectFileItem* file = new ProjectFileItem(QStringLiteral("foo"), folder); 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(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(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.h b/serialization/abstractitemrepository.h index 2f75b8bb6..d5982e2ce 100644 --- a/serialization/abstractitemrepository.h +++ b/serialization/abstractitemrepository.h @@ -1,67 +1,68 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ABSTRACTITEMREPOSITORY_H #define ABSTRACTITEMREPOSITORY_H -#include #include #include "serializationexport.h" +class QString; + namespace KDevelop { /// Returns a version-number that is used to reset the item-repository after incompatible layout changes. KDEVPLATFORMSERIALIZATION_EXPORT uint staticItemRepositoryVersion(); /// The interface class for an item-repository object. class KDEVPLATFORMSERIALIZATION_EXPORT AbstractItemRepository { public: virtual ~AbstractItemRepository(); /// @param path A shared directory-name that the item-repository is to be loaded from. /// @returns Whether the repository has been opened successfully. virtual bool open(const QString& path) = 0; virtual void close(bool doStore = false) = 0; /// Stores the repository contents to disk, eventually unloading unused data to save memory. virtual void store() = 0; /// Does a big cleanup, removing all non-persistent items in the repositories. /// @returns Count of bytes of data that have been removed. virtual int finalCleanup() = 0; virtual QString repositoryName() const = 0; virtual QString printStatistics() const = 0; }; /// Internal helper class that wraps around a repository object and manages its lifetime. class KDEVPLATFORMSERIALIZATION_EXPORT AbstractRepositoryManager { public: AbstractRepositoryManager(); virtual ~AbstractRepositoryManager(); void deleteRepository(); virtual QMutex* repositoryMutex() const = 0; protected: mutable AbstractItemRepository* m_repository; }; } #endif // ABSTRACTITEMREPOSITORY_H diff --git a/serialization/itemrepositoryregistry.h b/serialization/itemrepositoryregistry.h index 43aa5f1f3..a8a3f4a3e 100644 --- a/serialization/itemrepositoryregistry.h +++ b/serialization/itemrepositoryregistry.h @@ -1,114 +1,113 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ITEMREPOSITORYREGISTRY_H #define ITEMREPOSITORYREGISTRY_H -#include -#include -#include -#include - #include "serializationexport.h" #include +class QString; +class QMutex; +class QAtomicInt; + namespace KDevelop { class ISession; class AbstractRepositoryManager; class AbstractItemRepository; struct ItemRepositoryRegistryPrivate; /** * Manages a set of item-repositores and allows loading/storing them all at once from/to disk. * Does not automatically store contained repositories on destruction. * For the global registry, the storing is triggered from within duchain, so you don't need to care about it. */ class KDEVPLATFORMSERIALIZATION_EXPORT ItemRepositoryRegistry { public: ~ItemRepositoryRegistry(); /** * Initialize the global item-repository registry for the given @p session. */ static void initialize(const ISessionLock::Ptr& session); /// @returns The global item-repository registry. static ItemRepositoryRegistry* self(); /// Deletes the item-repository of a specified session; or, if it is currently used, marks it for deletion at exit. static void deleteRepositoryFromDisk(const ISessionLock::Ptr& sessionLock); /// Add a new repository. /// It will automatically be opened with the current path, if one is set. void registerRepository(AbstractItemRepository* repository, AbstractRepositoryManager* manager); /// Remove a repository. /// It will automatically be closed (if it was open). void unRegisterRepository(AbstractItemRepository* repository); /// @returns The path to item-repositories. QString path() const; /// Stores all repositories to disk, eventually unloading unused data to save memory. /// @note Should be called on a regular basis. void store(); /// Indicates that the application has been closed gracefully. /// @note Must be called somewhere at the end of the shutdown sequence. void shutdown(); /// Does a big cleanup, removing all non-persistent items in the repositories. /// @returns Count of bytes of data that have been removed. int finalCleanup(); /// Prints the statistics of all registered item-repositories to the command line using qDebug(). void printAllStatistics() const; /// Marks the directory as inconsistent, so it will be discarded /// on next startup if the application crashes during the write process. void lockForWriting(); /// Removes the inconsistency mark set by @ref lockForWriting(). void unlockForWriting(); /// Returns a custom counter persistently stored as part of item-repositories in the /// same directory, possibly creating it. /// @param identity The string used to identify a counter. /// @param initialValue Value to initialize a previously inexistent counter with. QAtomicInt& getCustomCounter(const QString& identity, int initialValue); /// @returns The global item-repository mutex. /// @note Can be used to protect the initialization. QMutex& mutex(); private: explicit ItemRepositoryRegistry(const ISessionLock::Ptr& session); ItemRepositoryRegistryPrivate* d; static ItemRepositoryRegistry* m_self; }; /// @returns The global item-repository registry (now it is @ref ItemRepositoryRegistry::self()). KDEVPLATFORMSERIALIZATION_EXPORT ItemRepositoryRegistry& globalItemRepositoryRegistry(); } #endif // ITEMREPOSITORYREGISTRY_H diff --git a/serialization/tests/test_itemrepository.cpp b/serialization/tests/test_itemrepository.cpp index c512cbcf9..963ad1581 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 #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(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/serialization/tests/test_itemrepositoryregistry_automatic.cpp b/serialization/tests/test_itemrepositoryregistry_automatic.cpp index abe7dade1..28016bf6e 100644 --- a/serialization/tests/test_itemrepositoryregistry_automatic.cpp +++ b/serialization/tests/test_itemrepositoryregistry_automatic.cpp @@ -1,51 +1,51 @@ -#include +#include #include #include #include #include class TestItemRepositoryRegistryAutomaticDeletion : public QObject { Q_OBJECT void initCore(const QString& sessionName = QString()) { KDevelop::TestCore* core = new KDevelop::TestCore(); core->initialize(KDevelop::Core::NoUi, sessionName); } void destroyCore() { KDevelop::TestCore::shutdown(); } private slots: void initTestCase() { KDevelop::AutoTestShell::init(); } void testTemporarySessionDeletion() { // Create and shutdown a TestCore. The session created by it is temporary // and thus shall be deleted upon core shutdown together with its // item-repository directory. { initCore(); // The session created by TestCore shall be temporary QVERIFY(KDevelop::Core::self()->activeSession()->isTemporary()); // The repository shall exist QString repositoryPath = KDevelop::globalItemRepositoryRegistry().path(); QVERIFY(QFile::exists(repositoryPath)); // The repository shall die with the core shutdown destroyCore(); QVERIFY(!QFile::exists(repositoryPath)); } } void cleanupTestCase() { } }; #include "test_itemrepositoryregistry_automatic.moc" QTEST_MAIN(TestItemRepositoryRegistryAutomaticDeletion) diff --git a/serialization/tests/test_itemrepositoryregistry_deferred.cpp b/serialization/tests/test_itemrepositoryregistry_deferred.cpp index b03a3549c..ab21ab52e 100644 --- a/serialization/tests/test_itemrepositoryregistry_deferred.cpp +++ b/serialization/tests/test_itemrepositoryregistry_deferred.cpp @@ -1,58 +1,58 @@ -#include +#include #include #include #include #include class TestItemRepositoryRegistryDeferredDeletion : public QObject { Q_OBJECT void initCore(const QString& sessionName = QString()) { KDevelop::TestCore* core = new KDevelop::TestCore(); core->initialize(KDevelop::Core::NoUi, sessionName); } void destroyCore() { KDevelop::TestCore::shutdown(); } private slots: void initTestCase() { KDevelop::AutoTestShell::init(); } void testDeferredDeletion() { // Create and shutdown a TestCore, giving the session a custom name // so it will stay (won't be temporary) and request the session deletion // from the same core instance. static const char sessionName[] = "test-itemrepositoryregistry-deferreddeletion"; QString repositoryPath; { initCore(sessionName); // The session with a custom name shall not be temporary QVERIFY(!KDevelop::Core::self()->activeSession()->isTemporary()); // The repository shall exist repositoryPath = KDevelop::globalItemRepositoryRegistry().path(); QVERIFY(QFile::exists(repositoryPath)); // The repository shall survive session deletion request KDevelop::Core::self()->sessionController()->deleteSession(KDevelop::Core::self()->sessionController()->activeSessionLock()); QVERIFY(QFile::exists(repositoryPath)); // The repository shall die together with the core shutdown destroyCore(); QVERIFY(!QFile::exists(repositoryPath)); } } void cleanupTestCase() { } }; #include "test_itemrepositoryregistry_deferred.moc" QTEST_MAIN(TestItemRepositoryRegistryDeferredDeletion) diff --git a/shell/colorschemechooser.cpp b/shell/colorschemechooser.cpp index 5aa04fe11..8cc7d9fee 100644 --- a/shell/colorschemechooser.cpp +++ b/shell/colorschemechooser.cpp @@ -1,106 +1,99 @@ /************************************************************************************* * This file is part of KDevPlatform * * Copyright 2016 Zhigalin Alexander * * * * 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 "colorschemechooser.h" -#include -#include -#include -#include #include -#include -#include #include #include #include #include #include -#include #include #include #include #include "mainwindow.h" #include "core.h" #include "debug.h" namespace KDevelop { ColorSchemeChooser::ColorSchemeChooser(QObject* parent) : QAction(parent) { auto manager = new KColorSchemeManager(parent); const auto scheme(currentSchemeName()); qCDebug(SHELL) << "Color scheme : " << scheme; auto selectionMenu = manager->createSchemeSelectionMenu(scheme, this); connect(selectionMenu->menu(), &QMenu::triggered, this, &ColorSchemeChooser::slotSchemeChanged); manager->activateScheme(manager->indexForScheme(scheme)); setMenu(selectionMenu->menu()); menu()->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color"))); menu()->setTitle(i18n("&Color Theme")); } QString ColorSchemeChooser::loadCurrentScheme() const { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup cg(config, "UiSettings"); return cg.readEntry("ColorScheme", currentDesktopDefaultScheme()); } void ColorSchemeChooser::saveCurrentScheme(const QString &name) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup cg(config, "UiSettings"); cg.writeEntry("ColorScheme", name); cg.sync(); } QString ColorSchemeChooser::currentDesktopDefaultScheme() const { KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("kdeglobals")); KConfigGroup group(config, "General"); return group.readEntry("ColorScheme", QStringLiteral("Breeze")); } QString ColorSchemeChooser::currentSchemeName() const { if(!menu()) return loadCurrentScheme(); QAction* const action = menu()->activeAction(); if(action) return KLocalizedString::removeAcceleratorMarker(action->text()); return currentDesktopDefaultScheme(); } void ColorSchemeChooser::slotSchemeChanged(QAction* triggeredAction) { saveCurrentScheme(KLocalizedString::removeAcceleratorMarker(triggeredAction->text())); } } // namespace KDevelop diff --git a/shell/colorschemechooser.h b/shell/colorschemechooser.h index 72c3bb3b7..4c879f7e6 100644 --- a/shell/colorschemechooser.h +++ b/shell/colorschemechooser.h @@ -1,60 +1,53 @@ /************************************************************************************* * This file is part of KDevPlatform * * Copyright 2016 Zhigalin Alexander * * * * 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 . * *************************************************************************************/ #ifndef COLORSCHEMECHOOSER_H #define COLORSCHEMECHOOSER_H -#include #include -#include -#include - -#include #include "mainwindow.h" -class KActionCollection; - namespace KDevelop { /** * Provides a menu that will offer to change the color scheme * * Furthermore, it will save the selection in the user configuration. */ class ColorSchemeChooser : public QAction { public: explicit ColorSchemeChooser(QObject* parent); QString currentSchemeName() const; private Q_SLOTS: void slotSchemeChanged(QAction* triggeredAction); private: QString loadCurrentScheme() const; void saveCurrentScheme(const QString &name); QString currentDesktopDefaultScheme() const; }; } // namespace KDevelop #endif // COLORSCHEMECHOOSER_H diff --git a/shell/completionsettings.cpp b/shell/completionsettings.cpp index 121adec70..d7b8d9e9c 100644 --- a/shell/completionsettings.cpp +++ b/shell/completionsettings.cpp @@ -1,108 +1,109 @@ /*************************************************************************** * Copyright 2008 David Nolden * * Copyright 2013 Vlas Puhov * * * * 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 "completionsettings.h" #include +#include namespace KDevelop { static QString completionLevelToString(ICompletionSettings::CompletionLevel l) { if (l < 0 || l >= ICompletionSettings::LAST_LEVEL) { return QString(); } const static QString levels[ICompletionSettings::LAST_LEVEL] = {QStringLiteral("Minimal"), QStringLiteral("MinimalWhenAutomatic"), QStringLiteral("AlwaysFull")}; return levels[l]; } CompletionSettings& CompletionSettings::self() { static CompletionSettings settings; return settings; } QStringList CompletionSettings::todoMarkerWords() const { const QString markers = m_languageGroup.readEntry("todoMarkerWords", m_todoMarkerWords); return KShell::splitArgs(markers); } int CompletionSettings::minFilesForSimplifiedParsing() const { return m_languageGroup.readEntry("minFilesForSimplifiedParsing", m_minFilesForSimplifiedParsing); } bool CompletionSettings::showMultiLineSelectionInformation() const { return m_languageGroup.readEntry("showMultiLineSelectionInformation", m_showMultiLineInformation); } bool CompletionSettings::highlightProblematicLines() const { return m_languageGroup.readEntry("highlightProblematicLines", m_highlightProblematicLines); } bool CompletionSettings::highlightSemanticProblems() const { return m_languageGroup.readEntry("highlightSemanticProblems", m_highlightSemanticProblems); } bool CompletionSettings::boldDeclarations() const { return m_languageGroup.readEntry("boldDeclarations", m_boldDeclarations); } int CompletionSettings::globalColorizationLevel() const { return m_languageGroup.readEntry("globalColorization", m_globalColorizationLevel); } int CompletionSettings::localColorizationLevel() const { return m_languageGroup.readEntry("localColorization", m_localColorizationLevel); } bool CompletionSettings::automaticCompletionEnabled() const { return m_languageGroup.readEntry("Automatic Invocation", m_automatic); } ICompletionSettings::CompletionLevel CompletionSettings::completionLevel() const { const QString level = m_languageGroup.readEntry("completionDetail", completionLevelToString(m_level)); for (int i = 0; i < ICompletionSettings::LAST_LEVEL; i++) { if (completionLevelToString(static_cast(i)) == level) { return static_cast(i); } } return m_level; } CompletionSettings::CompletionSettings() : m_level(MinimalWhenAutomatic), m_automatic(true), m_highlightSemanticProblems(true), m_highlightProblematicLines(false), m_showMultiLineInformation(false), m_boldDeclarations(true), m_localColorizationLevel(170), m_globalColorizationLevel(255), m_minFilesForSimplifiedParsing(100000), m_todoMarkerWords(QStringLiteral("TODO FIXME")), m_languageGroup(KSharedConfig::openConfig(), "Language Support"){} } diff --git a/shell/completionsettings.h b/shell/completionsettings.h index 66a05b18c..7f9ad3f03 100644 --- a/shell/completionsettings.h +++ b/shell/completionsettings.h @@ -1,75 +1,74 @@ /*************************************************************************** * 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 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_COMPLETIONSETTINGS_H #define KDEVPLATFORM_COMPLETIONSETTINGS_H #include #include -#include namespace KDevelop { class CompletionSettings : public KDevelop::ICompletionSettings { Q_OBJECT public: CompletionLevel completionLevel() const override; bool automaticCompletionEnabled() const override; void emitChanged() { emit settingsChanged(this); } int localColorizationLevel() const override; int globalColorizationLevel() const override; bool highlightSemanticProblems() const override; bool highlightProblematicLines() const override; bool boldDeclarations() const override; bool showMultiLineSelectionInformation() const override; int minFilesForSimplifiedParsing() const override; QStringList todoMarkerWords() const override; static CompletionSettings& self(); private: CompletionSettings(); const CompletionLevel m_level; const bool m_automatic; const bool m_highlightSemanticProblems; const bool m_highlightProblematicLines; const bool m_showMultiLineInformation; const bool m_boldDeclarations; const int m_localColorizationLevel; const int m_globalColorizationLevel; const int m_minFilesForSimplifiedParsing; const QString m_todoMarkerWords; const KConfigGroup m_languageGroup; }; } #endif diff --git a/shell/debugcontroller.h b/shell/debugcontroller.h index eb018d30a..2a51cb20f 100644 --- a/shell/debugcontroller.h +++ b/shell/debugcontroller.h @@ -1,124 +1,123 @@ /* This file is part of KDevelop * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * 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. */ #ifndef KDEVPLATFORM_DEBUGCONTROLLER_H #define KDEVPLATFORM_DEBUGCONTROLLER_H -#include #include #include #include #include "../interfaces/idebugcontroller.h" #include "../debugger/interfaces/idebugsession.h" class QAction; namespace Sublime { class Area; } namespace KParts { class Part; } namespace KDevelop { class Context; class ContextMenuExtension; class DebugController : public IDebugController, public KXMLGUIClient { Q_OBJECT public: explicit DebugController(QObject *parent = nullptr); ~DebugController() override; void initialize(); void cleanup(); /// Must be called by debugger plugin that needs debugger actions and toolviews. void initializeUi() override; void addSession(IDebugSession* session) override; IDebugSession* currentSession() override; ContextMenuExtension contextMenuExtension( Context* context ); BreakpointModel* breakpointModel() override; VariableCollection* variableCollection() override; private Q_SLOTS: //void restartDebugger(); void stopDebugger(); void interruptDebugger(); void run(); void runToCursor(); void jumpToCursor(); void stepOver(); void stepIntoInstruction(); void stepInto(); void stepOverInstruction(); void stepOut(); void toggleBreakpoint(); void debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state); void showStepInSource(const QUrl &file, int line); void clearExecutionPoint(); void partAdded(KParts::Part* part); void areaChanged(Sublime::Area* newArea); Q_SIGNALS: void raiseFramestackViews(); private: void setupActions(); void updateDebuggerState(KDevelop::IDebugSession::DebuggerState state, KDevelop::IDebugSession* session); void setContinueStartsDebug(bool startsDebug); static const QPixmap* executionPointPixmap(); QAction* m_continueDebugger; //QAction* m_restartDebugger; QAction* m_stopDebugger; QAction* m_interruptDebugger; QAction* m_runToCursor; QAction* m_jumpToCursor; QAction* m_stepOver; QAction* m_stepIntoInstruction; QAction* m_stepInto; QAction* m_stepOverInstruction; QAction* m_stepOut; QAction* m_toggleBreakpoint; QPointer m_currentSession; BreakpointModel *m_breakpointModel; VariableCollection *m_variableCollection; bool m_uiInitialized; }; } #endif diff --git a/shell/ktexteditorpluginintegration.h b/shell/ktexteditorpluginintegration.h index b0f65af6f..b3279fd68 100644 --- a/shell/ktexteditorpluginintegration.h +++ b/shell/ktexteditorpluginintegration.h @@ -1,128 +1,127 @@ /* Copyright 2015 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 . */ #ifndef KDEVPLATFORM_KTEXTEDITOR_PLUGIN_INTEGRATION_H #define KDEVPLATFORM_KTEXTEDITOR_PLUGIN_INTEGRATION_H #include -#include #include #include #include #include class QStackedLayout; namespace KDevelop { class ObjectListTracker; class MainWindow; } namespace KTextEditorIntegration { /** * Class mimicking the KTextEditor::Application interface */ class Application : public QObject { Q_OBJECT public: explicit Application(QObject *parent = nullptr); ~Application() override; public slots: KTextEditor::MainWindow *activeMainWindow() const; QList mainWindows() const; KTextEditor::Plugin *plugin(const QString &id) const; bool closeDocument(KTextEditor::Document *document) const; }; class MainWindow : public QObject { Q_OBJECT public: explicit MainWindow(KDevelop::MainWindow *mainWindow); ~MainWindow() override; public slots: QWidget *createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text); KXMLGUIFactory *guiFactory() const; QWidget *window() const; QList views() const; KTextEditor::View *activeView() const; QObject *pluginView(const QString &id) const; void splitView(Qt::Orientation orientation); QWidget *createViewBar(KTextEditor::View *view); void deleteViewBar(KTextEditor::View *view); void showViewBar(KTextEditor::View *view); void hideViewBar(KTextEditor::View *view); void addWidgetToViewBar(KTextEditor::View *view, QWidget *widget); public: KTextEditor::MainWindow *interface() const; void addPluginView(const QString &id, QObject *pluginView); void removePluginView(const QString &id); private: KDevelop::MainWindow *m_mainWindow; KTextEditor::MainWindow *m_interface; QHash> m_pluginViews; QStackedLayout *m_viewBarContainerLayout; QHash m_viewBars; }; class Plugin : public KDevelop::IPlugin { Q_OBJECT public: explicit Plugin(KTextEditor::Plugin *plugin, QObject *parent = nullptr); ~Plugin() override; KXMLGUIClient* createGUIForMainWindow(Sublime::MainWindow *window) override; void unload() override; KTextEditor::Plugin *interface() const; QString pluginId() const; private: QPointer m_plugin; // view objects and toolviews that should get deleted when the plugin gets unloaded KDevelop::ObjectListTracker *m_tracker; }; void initialize(); } #endif diff --git a/shell/languagecontroller.h b/shell/languagecontroller.h index af80ba26c..da7180ed7 100644 --- a/shell/languagecontroller.h +++ b/shell/languagecontroller.h @@ -1,81 +1,77 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_LANGUAGECONTROLLER_H #define KDEVPLATFORM_LANGUAGECONTROLLER_H #include #include "shellexport.h" -namespace KParts { -class Part; -} - namespace KDevelop { class ILanguageSupport; class KDEVPLATFORMSHELL_EXPORT LanguageController : public ILanguageController { Q_OBJECT public: explicit LanguageController(QObject *parent); ~LanguageController() override; void initialize(); //After this was called, no more languages will be returned void cleanup(); /** @copydoc ILanguageController::activeLanguages() */ QList activeLanguages() override; /** @copydoc ILanguageController::language() */ ILanguageSupport* language(const QString &name) const override; /** @copydoc ILanguageController::languageForUrl() */ QList languagesForUrl(const QUrl &url) override; /** @copydoc ILanguageController::backgroundParser() */ Q_SCRIPTABLE BackgroundParser *backgroundParser() const override; StaticAssistantsManager *staticAssistantsManager() const override; QList loadedLanguages() const override; ICompletionSettings *completionSettings() const override; ProblemModelSet* problemModelSet() const override; QList languagesForMimetype(const QString& mime); QList mimetypesForLanguageName(const QString& languageName); protected: /** * functions for unit tests * @see TestLanguageController */ void addLanguageSupport(KDevelop::ILanguageSupport* languageSupport, const QStringList& mimetypes); private: struct LanguageControllerPrivate *d; }; } #endif diff --git a/shell/mainwindow.cpp b/shell/mainwindow.cpp index b7dd1b9c2..e36ad292c 100644 --- a/shell/mainwindow.cpp +++ b/shell/mainwindow.cpp @@ -1,525 +1,525 @@ /* 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 #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 #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::split(Qt::Orientation orientation) { d->split(orientation); } 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 = 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); } bool KDevelop::MainWindow::event( QEvent* ev ) { if ( ev->type() == QEvent::PaletteChange ) updateAllTabColors(); return Sublime::MainWindow::event(ev); } void MainWindow::dragEnterEvent( QDragEnterEvent* ev ) { const QMimeData* mimeData = ev->mimeData(); if (mimeData->hasUrls()) { ev->acceptProposedAction(); } else if (mimeData->hasText()) { // also take text which contains a URL const QUrl url = QUrl::fromUserInput(mimeData->text()); if (url.isValid()) { ev->acceptProposedAction(); } } } void MainWindow::dropEvent( QDropEvent* ev ) { Sublime::View* dropToView = viewForPosition(mapToGlobal(ev->pos())); if(dropToView) activateView(dropToView); QList urls; const QMimeData* mimeData = ev->mimeData(); if (mimeData->hasUrls()) { urls = mimeData->urls(); } else if (mimeData->hasText()) { const QUrl url = QUrl::fromUserInput(mimeData->text()); if (url.isValid()) { urls << url; } } bool eventUsed = false; if (urls.size() == 1) { const QUrl& url = urls.at(0); // TODO: query also projectprovider plugins, and that before plain vcs plugins // e.g. KDE provider plugin could catch URLs from mirror or pickup kde:repo things auto* pluginController = Core::self()->pluginController(); const auto& plugins = pluginController->allPluginsForExtension(QStringLiteral("org.kdevelop.IBasicVersionControl")); for (auto* plugin : plugins) { auto* iface = plugin->extension(); if (iface->isValidRemoteRepositoryUrl(url)) { Core::self()->projectControllerInternal()->fetchProjectFromUrl(url, plugin); eventUsed = true; break; } } } if (!eventUsed) { for(const auto& url : urls) { Core::self()->documentController()->openDocument(url); } } 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); connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateTabColor); 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.h b/shell/mainwindow.h index e2e9e2aba..84e9ed787 100644 --- a/shell/mainwindow.h +++ b/shell/mainwindow.h @@ -1,120 +1,118 @@ /* This file is part of the KDevelop project Copyright 2003 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2003 Amilcar do Carmo Lucas Copyright 2004, 2007 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. */ #ifndef KDEVPLATFORM_MAINWINDOW_H #define KDEVPLATFORM_MAINWINDOW_H -#include - #include #include "shellexport.h" namespace KTextEditor { class View; } namespace KTextEditorIntegration { class MainWindow; } namespace KDevelop { class IDocument; /** KDevelop main window. Provides methods to control the main window of an application. */ class KDEVPLATFORMSHELL_EXPORT MainWindow: public Sublime::MainWindow { friend class UiController; Q_OBJECT Q_CLASSINFO( "D-Bus Interface", "org.kdevelop.MainWindow" ) public: explicit MainWindow( Sublime::Controller *parent = nullptr, Qt::WindowFlags flags = KDE_DEFAULT_WINDOWFLAGS ); ~MainWindow() override; /*! @p status must implement KDevelop::IStatus */ void registerStatus(QObject* status); KTextEditorIntegration::MainWindow *kateWrapper() const; void split(Qt::Orientation orientation); public Q_SLOTS: /*! Shows an error message in the status bar. @p message The message @p timeout The timeout in milliseconds how long to show the message */ void showErrorMessage(const QString& message, int timeout); virtual Q_SCRIPTABLE void ensureVisible(); virtual Q_SCRIPTABLE QString windowTitle() { return Sublime::MainWindow::windowTitle(); } void setVisible( bool visible ) override; void configureShortcuts(); void loadSettings() override; Q_SIGNALS: void finishedLoading(); protected: //FIXME DOCUMENT!!! queryClose() must call all of the Core cleanup() methods! bool queryClose() override; //reimplemented from KXMLGUIBuilder to support visible menubar separators QAction *createCustomElement(QWidget *parent, int index, const QDomElement &element) override; virtual void initialize(); virtual void cleanup(); void initializeStatusBar() override; bool event( QEvent* ) override; void dragEnterEvent( QDragEnterEvent* ) override; void dropEvent( QDropEvent* ) override; void applyMainWindowSettings(const KConfigGroup& config) override; void createGUI(KParts::Part* part); protected Q_SLOTS: void tabContextMenuRequested(Sublime::View* , QMenu* ) override; void tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) override; void dockBarContextMenuRequested(Qt::DockWidgetArea, const QPoint&) override; void newTabRequested() override; private Q_SLOTS: void updateCaption(); void updateActiveDocumentConnection(IDocument* document); void updateTabColor(IDocument* doc); void updateAllTabColors(); void shortcutsChanged(); private: void initializeCorners(); class MainWindowPrivate *d; friend class MainWindowPrivate; }; } #endif diff --git a/shell/mainwindow_p.cpp b/shell/mainwindow_p.cpp index bfc872289..750302900 100644 --- a/shell/mainwindow_p.cpp +++ b/shell/mainwindow_p.cpp @@ -1,474 +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_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(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, nullptr); 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"))); 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* lockAction = new QAction(this); lockAction->setCheckable(true); lockAction->setText(i18n("Lock the Panel from Hiding")); KConfigGroup config = KSharedConfig::openConfig()->group("UI"); lockAction->setChecked(config.readEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), false)); menu.addSeparator(); menu.addAction(lockAction); QAction* triggered = menu.exec(position); if ( !triggered ) { return; } if (triggered == lockAction) { KConfigGroup config = KSharedConfig::openConfig()->group("UI"); config.writeEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), lockAction->isChecked()); 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/openprojectpage.h b/shell/openprojectpage.h index 1e0ff606e..865650e6b 100644 --- a/shell/openprojectpage.h +++ b/shell/openprojectpage.h @@ -1,52 +1,51 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat -#include class QUrl; class KFileWidget; namespace KDevelop { class OpenProjectPage : public QWidget { Q_OBJECT public: explicit OpenProjectPage( const QUrl& startUrl, const QStringList& filters, QWidget* parent = nullptr ); void setUrl(const QUrl& url); signals: void urlSelected(const QUrl&); void accepted(); protected: void showEvent(QShowEvent*) override; private slots: void highlightFile(const QUrl&); void opsEntered(const QUrl& item); void comboTextChanged(const QString&); void dirChanged(const QUrl& url); private: QUrl getAbsoluteUrl(const QString&) const; KFileWidget* fileWidget; }; } #endif diff --git a/shell/partcontroller.cpp b/shell/partcontroller.cpp index fef0d89dc..28b44492c 100644 --- a/shell/partcontroller.cpp +++ b/shell/partcontroller.cpp @@ -1,327 +1,326 @@ /*************************************************************************** * 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 #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( nullptr, this, className.toLatin1() ); } 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 nullptr; else mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); KParts::Part* part = createPart( mimeType, preferredPart ); if( part ) { readOnly( part ) ->openUrl( url ); return part; } 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 nullptr; } KTextEditor::Document *PartController::createDocument() { // NOTE: not implemented qWarning() << "WARNING: interface call not implemented"; 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 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/partcontroller.h b/shell/partcontroller.h index 0acf2f2f6..96371aef0 100644 --- a/shell/partcontroller.h +++ b/shell/partcontroller.h @@ -1,111 +1,111 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef __KDEVPARTCONTROLLER_H__ #define __KDEVPARTCONTROLLER_H__ #include #include -#include #include #include "core.h" +class QWidget; + namespace KParts { class Part; -class PartManager; class ReadOnlyPart; class ReadWritePart; } namespace KTextEditor { class Document; class Editor; class View; } class QMimeType; Q_DECLARE_METATYPE(KSharedConfigPtr) namespace KDevelop { class KDEVPLATFORMSHELL_EXPORT PartController : public IPartController { friend class Core; friend class CorePrivate; Q_OBJECT public: PartController(Core *core, QWidget *toplevel); ~PartController() override; bool showTextEditorStatusBar() const; KTextEditor::Document* createTextPart( const QString &encoding = QString() ); KTextEditor::Editor* editorPart() const override; bool canCreatePart( const QUrl &url ); using IPartController::createPart; KParts::Part* createPart( const QUrl &url, const QString& prefName = QString() ); KParts::Part* createPart( const QString &mimeType, const QString &partType, const QString &className, const QString &preferredName = QString() ); KParts::ReadOnlyPart* activeReadOnly() const; KParts::ReadWritePart* activeReadWrite() const; KParts::ReadOnlyPart* readOnly( KParts::Part *part ) const; KParts::ReadWritePart* readWrite( KParts::Part *part ) const; bool isTextType(const QMimeType& mimeType); virtual void setActiveView( KTextEditor::View * view ); virtual KTextEditor::View * activeView(); virtual KTextEditor::Document * createDocument(); virtual bool closeDocument( KTextEditor::Document * doc ); virtual KTextEditor::View * createView( KTextEditor::Document * doc ); virtual bool closeView( KTextEditor::View * view ); public Q_SLOTS: void setShowTextEditorStatusBar(bool show); protected: virtual void loadSettings( bool projectIsLoaded ); virtual void saveSettings( bool projectIsLoaded ); virtual void initialize(); virtual void cleanup(); private: void setupActions(); class PartControllerPrivate* const d; }; } #endif diff --git a/shell/plugincontroller.h b/shell/plugincontroller.h index cdcf16ca9..ade096894 100644 --- a/shell/plugincontroller.h +++ b/shell/plugincontroller.h @@ -1,185 +1,182 @@ /* This file is part of the KDE project Copyright 2007 Andreas Pakulat Copyright 2004, 2007 Alexander Dymo Copyright 2006 Matt Rogers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGINCONTROLLER_H #define KDEVPLATFORM_PLUGINCONTROLLER_H - -#include - #include #include "shellexport.h" namespace KDevelop { class Core; class CorePrivate; class IPlugin; class PluginControllerPrivate; /** * The KDevelop plugin controller. * The Plugin controller is responsible for querying, loading and unloading * available plugins. */ class KDEVPLATFORMSHELL_EXPORT PluginController: public IPluginController { Q_OBJECT friend class Core; friend class CorePrivate; public: explicit PluginController(Core *core); ~PluginController() override; /** * Get the plugin instance based on the ID. The ID should be whatever is * in X-KDE-PluginInfo-Name */ IPlugin* plugin( const QString& ); /** * Get the plugin info for a loaded plugin */ KPluginMetaData pluginInfo( const IPlugin* ) const override; /** * Find the KPluginMetaData structure for the given @p pluginId. */ KPluginMetaData infoForPluginId(const QString &pluginId) const override; /** * Get a list of currently loaded plugins */ QList loadedPlugins() const override; /** * Returns a uniquely specified plugin. If it isn't already loaded, it will be. * @param pluginName the name of the plugin, as given in the X-KDE-PluginInfo-Name property * @returns a pointer to the plugin instance or 0 */ IPlugin * loadPlugin( const QString & pluginName ) override; /** * @brief Unloads the plugin specified by @p plugin * * @param plugin The name of the plugin as specified by the * X-KDE-PluginInfo-Name key of the .desktop file for the plugin */ bool unloadPlugin( const QString & plugin ) override; enum PluginDeletion { Now, Later }; /** * retrieve all plugin infos */ QVector allPluginInfos() const; /** * loads not-yet-loaded plugins and unloads plugins * depending on the configuration in the session\ */ void updateLoadedPlugins(); /** * Queries for the plugin which supports given extension interface. * * All already loaded plugins will be queried and the first one to support the extension interface * will be returned. Any plugin can be an extension, only "ServiceTypes=..." entry is * required in .desktop file for that plugin. * * @param extension The extension interface * @param pluginName The name of the plugin to load if multiple plugins for the extension exist, corresponds to the X-KDE-PluginInfo-Name * @return A KDevelop extension plugin for given service type or 0 if no plugin supports it */ IPlugin *pluginForExtension(const QString &extension, const QString &pluginName = {}, const QVariantMap& constraints = QVariantMap()) override; QList allPluginsForExtension(const QString &extension, const QVariantMap& constraints = QVariantMap()) override; QStringList allPluginNames(); QVector queryExtensionPlugins(const QString& extension, const QVariantMap& constraints = QVariantMap()) const override; QList queryPluginsForContextMenuExtensions( KDevelop::Context* context ) const override; QStringList projectPlugins(); void loadProjectPlugins(); void unloadProjectPlugins(); void resetToDefaults(); bool isEnabled(const KPluginMetaData& info) const; private: /** * Directly unload the given \a plugin, either deleting it now or \a deletion. * * \param plugin plugin to unload * \param deletion if true, delete the plugin later, if false, delete it now. */ bool unloadPlugin(IPlugin* plugin, PluginDeletion deletion); /** * @internal * * The internal method for loading plugins. * Called by @ref loadPlugin directly or through the queue for async plugin * loading. */ IPlugin* loadPluginInternal( const QString &pluginId ); /** * Check whether the plugin identified by @p info has unresolved dependencies. * * Assume a plugin depends on the interfaces Foo and Bar. Then, all available enabled * plugins are queried to check whether any fulfills the interfaces. If any of the * interfaces is not found, then it is inserted into @p missing and this method returns * true. Otherwise, @p missing is empty and this method returns true, indicating that all * dependencies can be fulfilled. * * @return true when there are unresolved dependencies, false otherwise. */ bool hasUnresolvedDependencies( const KPluginMetaData& info, QStringList& missing ) const; bool loadDependencies(const KPluginMetaData&, QString& failedPlugin); void loadOptionalDependencies(const KPluginMetaData& info); void cleanup(); virtual void initialize(); private: class PluginControllerPrivate* const d; }; } #endif diff --git a/shell/problem.h b/shell/problem.h index d5fdd40f0..a13b80d59 100644 --- a/shell/problem.h +++ b/shell/problem.h @@ -1,112 +1,111 @@ /* * 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. */ #ifndef PROBLEM_H #define PROBLEM_H #include #include #include #include -#include struct DetectedProblemPrivate; namespace KDevelop { /** * @brief Represents a problem as one unit with the IProblem interface so can be used with anything that can handle IProblem. * * You should have it wrapped in an IProblem::Ptr which is a shared pointer for it. * It is basically a mirror of DUChain's Problem class. * However that class is strongly coupled with DUChain's internals due to DUChain's needs (special serialization). * * Usage example: * @code * IProblem::Ptr problem(new DetectedProblem()); * problem->setSource(IProblem::Plugin); * problem->setSeverity(IProblem::Warning); * problem->setDescription(QStringLiteral("Warning message")); * problem->setExplanation(QStringLiteral("Warning explanation")); * * DocumentRange range; * range.document = IndexedString("/path/to/source/file"); * range.setBothLines(1337); * range.setBothColumns(12); * problem->setFinalLocation(range); * @endcode * */ class KDEVPLATFORMSHELL_EXPORT DetectedProblem : public IProblem { public: /// Creates new empty DetectedProblem with default properties: /// severity - KDevelop::IProblem::Error; /// source - KDevelop::IProblem::Unknown; /// finalLocationMode - KDevelop::IProblem::Range. DetectedProblem(); /// Creates new DetectedProblem, produced by some plugin, for example by analyzer plugins /// like cppcheck/clang-tydy/valgrind/etc. /// Default values of problem properties are: /// severity - KDevelop::IProblem::Error; /// source - KDevelop::IProblem::Plugin; /// sourceString - passed pluginName parameter; /// finalLocationMode - KDevelop::IProblem::Range. explicit DetectedProblem(const QString& pluginName); ~DetectedProblem() override; Source source() const override; void setSource(Source source) override; QString sourceString() const override; DocumentRange finalLocation() const override; void setFinalLocation(const DocumentRange& location) override; FinalLocationMode finalLocationMode() const override; void setFinalLocationMode(FinalLocationMode mode) override; QString description() const override; void setDescription(const QString& description) override; QString explanation() const override; void setExplanation(const QString& explanation) override; Severity severity() const override; void setSeverity(Severity severity) override; QString severityString() const override; QVector diagnostics() const override; void setDiagnostics(const QVector &diagnostics) override; void addDiagnostic(const Ptr &diagnostic) override; void clearDiagnostics() override; QExplicitlySharedDataPointer solutionAssistant() const override; private: QScopedPointer d; }; } #endif diff --git a/shell/problemmodel.h b/shell/problemmodel.h index 287c85893..ef21946d2 100644 --- a/shell/problemmodel.h +++ b/shell/problemmodel.h @@ -1,211 +1,210 @@ /* * KDevelop Problem Reporter * * 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. */ #ifndef PROBLEMMODEL_H #define PROBLEMMODEL_H #include #include #include -#include struct ProblemModelPrivate; namespace KDevelop { class IDocument; class ProblemStore; /** * @brief Wraps a ProblemStore and adds the QAbstractItemModel interface, so the it can be used in a model/view architecture. * * By default ProblemModel instantiates a FilteredProblemStore, with the following features on: * \li ScopeFilter * \li SeverityFilter * \li Grouping * \li CanByPassScopeFilter * * Has to following columns: * \li Error * \li Source * \li File * \li Line * \li Column * \li LastColumn * * Possible ProblemModel features * \li NoFeatures * \li CanDoFullUpdate * \li CanShowImports * \li ScopeFilter * \li SeverityFilter * \li Grouping * \li CanByPassScopeFilter * * Scope, severity, grouping, imports can be set using the slots named after these features. * * Usage example: * @code * IProblem::Ptr problem(new DetectedProblem); * problem->setDescription(QStringLiteral("Problem")); * ProblemModel *model = new ProblemModel(nullptr); * model->addProblem(problem); * model->rowCount(); // returns 1 * QModelIndex idx = model->index(0, 0); * model->data(index); // "Problem" * @endcode * */ class KDEVPLATFORMSHELL_EXPORT ProblemModel : public QAbstractItemModel { Q_OBJECT public: /// List of supportable features enum FeatureCode { NoFeatures = 0, /// No features :( CanDoFullUpdate = 1, /// Reload/Reparse problems CanShowImports = 2, /// Show problems from imported files. E.g.: Header files in C/C++ ScopeFilter = 4, /// Filter problems by scope. E.g.: current document, open documents, etc SeverityFilter = 8, /// Filter problems by severity. E.g.: hint, warning, error, etc Grouping = 16, /// Can group problems CanByPassScopeFilter = 32, /// Can bypass scope filter ShowSource = 64 /// Show problem's source. Set if problems can have different sources. }; Q_DECLARE_FLAGS(Features, FeatureCode) explicit ProblemModel(QObject *parent, ProblemStore *store = nullptr); ~ProblemModel() override; enum Columns { Error, Source, File, Line, Column, LastColumn }; enum Roles { ProblemRole = Qt::UserRole + 1, SeverityRole }; int columnCount(const QModelIndex & parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex & index) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override; IProblem::Ptr problemForIndex(const QModelIndex& index) const; /// Adds a new problem to the model void addProblem(const IProblem::Ptr &problem); /// Clears the problems, then adds a new set of them void setProblems(const QVector &problems); /// Clears the problems void clearProblems(); /// Retrieve problems for selected document QVector problems(const KDevelop::IndexedString& document); /// Retrieve the supported features Features features() const; /// Retrieve 'show imports' filter setting bool showImports(); /// Set the supported features void setFeatures(Features features); /// Tooltip for "Force Full Update" action in the Problems View when the model /// is active (correspondent tab is selected) QString fullUpdateTooltip() const; /// Set the "Force Full Update" action tooltip void setFullUpdateTooltip(const QString& tooltip); signals: /// Emitted when the stored problems are changed with addProblem(), setProblems() and /// clearProblems() methods. This signal emitted only when internal problems storage is /// really changed: for example, it is not emitted when we call clearProblems() method /// for empty model. void problemsChanged(); /// Emitted when the "Force Full Update" action tooltip is changed with setFullUpdateTooltip(). /// This signal emitted only when tooltip is really changed. void fullUpdateTooltipChanged(); public slots: /// Show imports void setShowImports(bool showImports); /// Sets the scope filter. Uses int to be able to use QSignalMapper void setScope(int scope); /// Sets the severity filter. Uses int to be able to use QSignalMapper void setSeverity(int severity);///old-style severity filtering void setSeverities(KDevelop::IProblem::Severities severities);///new-style severity filtering void setGrouping(int grouping); /** * Force a full problem update. * E.g.: Reparse the source code. * Obviously it doesn't make sense for run-time problem checkers. */ virtual void forceFullUpdate(){} protected slots: /// Triggered when problems change virtual void onProblemsChanged(){} private slots: /// Triggered when the current document changes virtual void setCurrentDocument(IDocument* doc); virtual void closedDocument(IDocument* doc); /// Triggered before the problems are rebuilt void onBeginRebuild(); /// Triggered once the problems have been rebuilt void onEndRebuild(); protected: ProblemStore *store() const; private: QScopedPointer d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProblemModel::Features) } #endif // PROBLEMMODEL_H diff --git a/shell/problemmodelset.cpp b/shell/problemmodelset.cpp index 833392389..43373a85f 100644 --- a/shell/problemmodelset.cpp +++ b/shell/problemmodelset.cpp @@ -1,97 +1,96 @@ /* * 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 "problemmodelset.h" #include "problemmodel.h" -#include namespace KDevelop { struct ProblemModelSetPrivate { QVector data; }; ProblemModelSet::ProblemModelSet(QObject *parent) : QObject(parent) , d(new ProblemModelSetPrivate()) { } ProblemModelSet::~ProblemModelSet() = default; void ProblemModelSet::addModel(const QString &id, const QString &name, ProblemModel *model) { ModelData m{id, name, model}; d->data.push_back(m); connect(model, &ProblemModel::problemsChanged, this, &ProblemModelSet::problemsChanged); emit added(m); } ProblemModel* ProblemModelSet::findModel(const QString &id) const { ProblemModel *model = nullptr; foreach (const ModelData &data, d->data) { if (data.id == id) { model = data.model; break; } } return model; } void ProblemModelSet::removeModel(const QString &id) { QVector::iterator itr = d->data.begin(); while (itr != d->data.end()) { if(itr->id == id) break; ++itr; } if(itr != d->data.end()) { (*itr).model->disconnect(this); d->data.erase(itr); emit removed(id); } } void ProblemModelSet::showModel(const QString &id) { for (const ModelData &data : d->data) { if (data.id == id) { emit showRequested(data.id); return; } } } QVector ProblemModelSet::models() const { return d->data; } } diff --git a/shell/problemmodelset.h b/shell/problemmodelset.h index c45b7aba4..54cbfad64 100644 --- a/shell/problemmodelset.h +++ b/shell/problemmodelset.h @@ -1,106 +1,103 @@ /* * 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. */ #ifndef PROBLEMMODELSET_H #define PROBLEMMODELSET_H #include #include -#include - -class QAbstractItemModel; namespace KDevelop { class ProblemModel; /// Struct that handles the model and it's name as one unit, stored in ProblemModelSet struct ModelData { QString id; QString name; ProblemModel *model; }; struct ProblemModelSetPrivate; /** * @brief Stores name/model pairs and emits signals when they are added/removed. * * Typically it's used from plugins, which maintains a reference to the model added. * Therefore It assumes that models get removed, so it doesn't delete! * * Usage example: * @code * ProblemModelSet *set = new ProblemModelSet(); * ProblemModel *model = new ProblemModel(nullptr); * set->addModel(QStringLiteral("MODEL_ID"), QStringLiteral("MODEL"), model); // added() signal is emitted * set->models().count(); // returns 1 * set->findModel(QStringLiteral("MODEL_ID")); // returns the model just added * set->removeModel(QStringLiteral("MODEL_ID")); // removed() signal is emitted * @endcode * */ class KDEVPLATFORMSHELL_EXPORT ProblemModelSet : public QObject { Q_OBJECT public: explicit ProblemModelSet(QObject *parent = nullptr); ~ProblemModelSet() override; /// Adds a model void addModel(const QString &id, const QString &name, ProblemModel *model); /// Finds a model ProblemModel* findModel(const QString &id) const; /// Removes a model void removeModel(const QString &id); /// Show model in ProblemsView void showModel(const QString &id); /// Retrieves a list of models stored QVector models() const; signals: /// Emitted when a new model is added void added(const ModelData &model); /// Emitted when a model is removed void removed(const QString &id); /// Emitted when showModel() is called void showRequested(const QString &id); /// Emitted when any model emits problemsChanged() void problemsChanged(); private: QScopedPointer d; }; } Q_DECLARE_TYPEINFO(KDevelop::ModelData, Q_MOVABLE_TYPE); #endif diff --git a/shell/problemstore.h b/shell/problemstore.h index 17fd18d72..ad0bda8d2 100644 --- a/shell/problemstore.h +++ b/shell/problemstore.h @@ -1,163 +1,162 @@ /* * 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. */ #ifndef PROBLEMSTORE_H #define PROBLEMSTORE_H #include -#include #include #include #include struct ProblemStorePrivate; namespace KDevelop { class WatchedDocumentSet; class ProblemStoreNode; /** * @brief Stores and handles problems. Does no ordering or filtering, those should be done in subclasses. * * Used to store problems that are ordered, filtered somewhere else. For example: DUChain problems gathered by ProblemReporter. * Stores the problems in ProblemStoreNodes. * When implementing a subclass, first and foremost the rebuild method needs to be implemented, which is called every time there's a change in scope and severity filter. * If grouping is desired then also the setGrouping method must be implemented. * ProblemStore depending on settings uses CurrentDocumentSet, OpenDocumentSet, CurrentProjectSet, or AllProjectSet for scope support (NOTE: Filtering still has to be implemented in either a subclass, or somewhere else). * When the scope changes it emits the changed() signal. * * Scope set / query methods: * \li setScope() * \li scope() * * Valid scope settings: * \li CurrentDocument * \li OpenDocuments * \li CurrentProject * \li AllProjects * \li BypassScopeFilter * * Usage example: * @code * QVector problems; * // Add 4 problems * ... * ProblemStore *store = new ProblemStore(); * store->setProblems(problems); * store->count(); // Returns 4 * * ProblemStoreNode *node = store->findNode(0); // Returns the node with the first problem * @endcode * */ class KDEVPLATFORMSHELL_EXPORT ProblemStore : public QObject { Q_OBJECT public: explicit ProblemStore(QObject *parent = nullptr); ~ProblemStore() override; /// Adds a problem virtual void addProblem(const IProblem::Ptr &problem); /// Clears the current problems, and adds new ones from a list virtual void setProblems(const QVector &problems); /// Retrieve problems for selected document QVector problems(const KDevelop::IndexedString& document) const; /// Finds the specified node virtual const ProblemStoreNode* findNode(int row, ProblemStoreNode *parent = nullptr) const; /// Returns the number of problems virtual int count(ProblemStoreNode *parent = nullptr) const; /// Clears the problems virtual void clear(); /// Rebuild the problems list, if applicable. It does nothing in the base class. virtual void rebuild(); /// Specifies the severity filter virtual void setSeverity(int severity);///old-style severity access virtual void setSeverities(KDevelop::IProblem::Severities severities);///new-style severity access /// Retrives the severity filter settings int severity() const;///old-style severity access KDevelop::IProblem::Severities severities() const;//new-style severity access /// Retrieves the currently watched document set WatchedDocumentSet* documents() const; /// Sets the scope filter void setScope(int scope); /// Returns the current scope int scope() const; /// Sets the grouping method virtual void setGrouping(int grouping); /// Set 'show imports' filter value void setShowImports(bool showImports); /// Retrieve 'show imports' filter setting int showImports() const; /// Sets the currently shown document (in the editor, it's triggered by the IDE) void setCurrentDocument(const IndexedString &doc); /// Retrives the path of the current document const KDevelop::IndexedString& currentDocument() const; signals: /// Emitted when any store setting (grouping, scope, severity, document) is changed void changed(); /// Emitted when the stored problems are changed with clear(), addProblem() and setProblems() /// methods. This signal emitted only when internal problems storage is really changed: /// for example, it is not emitted when we call clear() method for empty storage. void problemsChanged(); /// Emitted before the problemlist is rebuilt void beginRebuild(); /// Emitted once the problemlist has been rebuilt void endRebuild(); private slots: /// Triggered when the watched document set changes. E.g.:document closed, new one added, etc virtual void onDocumentSetChanged(); protected: ProblemStoreNode* rootNode(); private: QScopedPointer d; }; } #endif diff --git a/shell/problemstorenode.h b/shell/problemstorenode.h index 19aba61c6..cff85c823 100644 --- a/shell/problemstorenode.h +++ b/shell/problemstorenode.h @@ -1,222 +1,221 @@ /* * 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. */ #ifndef PROBLEMNODE_H #define PROBLEMNODE_H #include -#include #include namespace KDevelop { /** * @brief Base class for ProblemStoreNode classes, which together make up a tree structure with label or problem leaves. * * When adding a child the node is automatically reparented. * * Usage: * @code * ProblemStoreNode *root = new ProblemStoreNode(); * root->addChild(new ProblemStoreNode()); * root->addChild(new ProblemStoreNode()); * root->addChild(new ProblemStoreNode()); * root->count(); // Returns 3 * @endcode * */ class ProblemStoreNode { public: explicit ProblemStoreNode(ProblemStoreNode *parent = nullptr) { m_parent = parent; } virtual ~ProblemStoreNode() { clear(); } /// Clear the children nodes void clear() { qDeleteAll(m_children); m_children.clear(); } /// Tells if the node is a root node. /// A node is considered a root node (in this context), when it has no parent bool isRoot() const { if(!m_parent) return true; else return false; } /// Returns the index of this node in the parent's child list. int index() { if(!m_parent) return -1; const QVector &children = m_parent->children(); return children.indexOf(this); } /// Returns the parent of this node ProblemStoreNode* parent() const{ return m_parent; } /// Sets the parent of this node void setParent(ProblemStoreNode *parent) { m_parent = parent; } /// Returns the number of children nodes int count() const { return m_children.count(); } /// Returns a particular child node ProblemStoreNode* child(int row) const { return m_children[row]; } /// Returns the list of children nodes const QVector& children() const{ return m_children; } /// Adds a child node, and reparents the child void addChild(ProblemStoreNode *child) { m_children.push_back(child); child->setParent(this); } /// Returns the label of this node, if there's one virtual QString label() const{ return QString(); } /// Returns the node's stored problem, if there's such virtual IProblem::Ptr problem() const{ return IProblem::Ptr(nullptr); } private: /// The parent node ProblemStoreNode *m_parent; /// Children nodes QVector m_children; }; /////////////////////////////////////////////////////////////////////////////////////////////////////// /** * @brief A ProblemStoreNode that contains a label. For example: Label for severity or path grouping of problem nodes. * * Usage: * @code * ProblemStoreNode *root = new ProblemStoreNode(); * ... * root->addChild(new LabelNode(root, QStringLiteral("ERROR"))); * root->children().last()->label(); // "ERROR" * @endcode * */ class LabelNode : public ProblemStoreNode { public: explicit LabelNode(ProblemStoreNode *parent = nullptr, const QString &l = QString()) : ProblemStoreNode(parent) , m_label(l) { } ~LabelNode() { } QString label() const{ return m_label; } /// Sets the label void setLabel(const QString &s){ m_label = s; } private: /// The label QString m_label; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * @brief A ProblemStoreNode that contains a problem. For example: as part of a problem list in a severity or path group. * * Usage: * @code * IProblem::Ptr problem1(new DetectedProblem()); * ... * label->addChild(new ProblemNode(label, problem1)); * label->children().last()->problem(); // Provides problem1 * @endcode * */ class ProblemNode : public ProblemStoreNode { public: explicit ProblemNode(ProblemStoreNode *parent = nullptr, const IProblem::Ptr &problem = IProblem::Ptr(nullptr)) : ProblemStoreNode(parent) , m_problem(problem) { } ~ProblemNode() { } IProblem::Ptr problem() const{ return m_problem; } /// Sets the problem void setProblem(const IProblem::Ptr &problem){ m_problem = problem; } private: /// The problem IProblem::Ptr m_problem; }; } #endif diff --git a/shell/progresswidget/overlaywidget.h b/shell/progresswidget/overlaywidget.h index d2402739c..6b28fc877 100644 --- a/shell/progresswidget/overlaywidget.h +++ b/shell/progresswidget/overlaywidget.h @@ -1,60 +1,57 @@ /*************************************************************************** * Copyright (c) 2004 David Faure * * * * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_OVERLAYWIDGET_H #define KDEVPLATFORM_OVERLAYWIDGET_H #include -class QResizeEvent; -class QEvent; - namespace KDevelop { /** * This is a widget that can align itself with another one, without using a layout, * so that it can actually be on top of other widgets. * Currently the only supported type of alignment is "right aligned, on top of the other widget". */ class OverlayWidget : public QWidget { Q_OBJECT public: OverlayWidget( QWidget* alignWidget, QWidget* parent, const char* name = nullptr ); ~OverlayWidget() override; QWidget * alignWidget() { return mAlignWidget; } void setAlignWidget( QWidget * alignWidget ); protected: void resizeEvent( QResizeEvent* ev ) override; bool eventFilter( QObject* o, QEvent* e) override; private: void reposition(); private: QWidget * mAlignWidget; }; } // namespace #endif /* OVERLAYWIDGET_H */ diff --git a/shell/progresswidget/progressdialog.cpp b/shell/progresswidget/progressdialog.cpp index 786320824..4b596fab7 100644 --- a/shell/progresswidget/progressdialog.cpp +++ b/shell/progresswidget/progressdialog.cpp @@ -1,412 +1,408 @@ /** -*- 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( 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.h b/shell/progresswidget/progressmanager.h index b08bfcfde..ce55f2dd9 100644 --- a/shell/progresswidget/progressmanager.h +++ b/shell/progresswidget/progressmanager.h @@ -1,478 +1,477 @@ /*************************************************************************** * (C) 2004 Till 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 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PROGRESSMANAGER_H #define KDEVPLATFORM_PROGRESSMANAGER_H #include #include #include #include -#include #include namespace Akonadi { class AgentInstance; } namespace KDevelop { class ProgressItem; class ProgressManager; typedef QMap ProgressItemMap; class ProgressItem : public QObject { Q_OBJECT friend class ProgressManager; public: /** * @return The id string which uniquely identifies the operation * represented by this item. */ const QString &id() const { return mId; } /** * @return The parent item of this one, if there is one. */ ProgressItem *parent() const { return mParent.data(); } /** * @return The user visible string to be used to represent this item. */ const QString &label() const { return mLabel; } /** * @param v Set the user visible string identifying this item. */ void setLabel( const QString &v ); /** * @return The string to be used for showing this item's current status. */ const QString &status() const { return mStatus; } /** * Set the string to be used for showing this item's current status. * @param v The status string. */ void setStatus( const QString &v ); /** * @return Whether this item can be canceled. */ bool canBeCanceled() const { return mCanBeCanceled; } /** * @return Whether this item uses secure communication * (Account uses ssl, for example.). */ bool usesCrypto() const { return mUsesCrypto; } /** * Set whether this item uses crypted communication, so listeners * can display a nice crypto icon. * @param v The value. */ void setUsesCrypto( bool v ); /** * @return whether this item uses a busy indicator instead of real progress display */ bool usesBusyIndicator() const { return mUsesBusyIndicator; } /** * Sets whether this item uses a busy indicator instead of real progress for its progress bar. * If it uses a busy indicator, you are still responsible for calling setProgress() from time to * time to update the busy indicator. */ void setUsesBusyIndicator( bool useBusyIndicator ); /** * @return The current progress value of this item in percent. */ unsigned int progress() const { return mProgress; } /** * Set the progress (percentage of completion) value of this item. * @param v The percentage value. */ void setProgress( unsigned int v ); /** * Tell the item it has finished. This will emit progressItemCompleted() * result in the destruction of the item after all slots connected to this * signal have executed. This is the only way to get rid of an item and * needs to be called even if the item is canceled. Don't use the item * after this has been called on it. */ void setComplete(); /** * Reset the progress value of this item to 0 and the status string to * the empty string. */ void reset() { setProgress( 0 ); setStatus( QString() ); mCompleted = 0; } void cancel(); // Often needed values for calculating progress. void setTotalItems( unsigned int v ) { mTotal = v; } unsigned int totalItems() const { return mTotal; } void setCompletedItems( unsigned int v ) { mCompleted = v; } void incCompletedItems( unsigned int v = 1 ) { mCompleted += v; } unsigned int completedItems() const { return mCompleted; } /** * Recalculate progress according to total/completed items and update. */ void updateProgress() { setProgress( mTotal? mCompleted * 100 / mTotal : 0 ); } void addChild( ProgressItem *kiddo ); void removeChild( ProgressItem *kiddo ); bool canceled() const { return mCanceled; } void setBusy( bool busy ); Q_SIGNALS: /** * Emitted when a new ProgressItem is added. * @param item The ProgressItem that was added. */ void progressItemAdded( KDevelop::ProgressItem* item ); /** * Emitted when the progress value of an item changes. * @param item The item which got a new value. * @param value The value, for convenience. */ void progressItemProgress( KDevelop::ProgressItem* item, unsigned int value ); /** * Emitted when a progress item was completed. The item will be * deleted afterwards, so slots connected to this are the last * chance to work with this item. * @param item The completed item. */ void progressItemCompleted( KDevelop::ProgressItem* item ); /** * Emitted when an item was canceled. It will _not_ go away immediately, * only when the owner sets it complete, which will usually happen. Can be * used to visually indicate the canceled status of an item. Should be used * by the owner of the item to make sure it is set completed even if it is * canceled. There is a ProgressManager::slotStandardCancelHandler which * simply sets the item completed and can be used if no other work needs to * be done on cancel. * @param item The canceled item; */ void progressItemCanceled( KDevelop::ProgressItem* item ); /** * Emitted when the status message of an item changed. Should be used by * progress dialogs to update the status message for an item. * @param item The updated item. * @param message The new message. */ void progressItemStatus( KDevelop::ProgressItem* item, const QString& message ); /** * Emitted when the label of an item changed. Should be used by * progress dialogs to update the label of an item. * @param item The updated item. * @param label The new label. */ void progressItemLabel( KDevelop::ProgressItem* item, const QString& label ); /** * Emitted when the crypto status of an item changed. Should be used by * progress dialogs to update the crypto indicator of an item. * @param item The updated item. * @param state The new state. */ void progressItemUsesCrypto( KDevelop::ProgressItem* item, bool state ); /** * Emitted when the busy indicator state of an item changes. Should be used * by progress dialogs so that they can adjust the display of the progress bar * to the new mode. * @param item The updated item * @param value True if the item uses a busy indicator now, false otherwise */ void progressItemUsesBusyIndicator( KDevelop::ProgressItem *item, bool value ); protected: /* Only to be used by our good friend the ProgressManager */ ProgressItem( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool isCancellable, bool usesCrypto ); ~ProgressItem() override; private: QString mId; QString mLabel; QString mStatus; QPointer mParent; bool mCanBeCanceled; unsigned int mProgress; ProgressItemMap mChildren; unsigned int mTotal; unsigned int mCompleted; bool mWaitingForKids; bool mCanceled; bool mUsesCrypto; bool mUsesBusyIndicator; bool mCompletedCalled; }; struct ProgressManagerPrivate; /** * The ProgressManager singleton keeps track of all ongoing transactions * and notifies observers (progress dialogs) when their progress percent value * changes, when they are completed (by their owner), and when they are canceled. * Each ProgressItem emits those signals individually and the singleton * broadcasts them. Use the ::createProgressItem() statics to acquire an item * and then call ->setProgress( int percent ) on it every time you want to * update the item and ->setComplete() when the operation is done. This will * delete the item. Connect to the item's progressItemCanceled() signal to be * notified when the user cancels the transaction using one of the observing * progress dialogs or by calling item->cancel() in some other way. The owner * is responsible for calling setComplete() on the item, even if it is canceled. * Use the standardCancelHandler() slot if that is all you want to do on cancel. * * Note that if you request an item with a certain id and there is already * one with that id, there will not be a new one created but the existing * one will be returned. This is convenient for accessing items that are * needed regularly without the to store a pointer to them or to add child * items to parents by id. */ class ProgressManager : public QObject { Q_OBJECT friend struct ProgressManagerPrivate; public: ~ProgressManager() override; /** * @return The singleton instance of this class. */ static ProgressManager *instance(); /** * @return a unique id number which can be used to discern * an operation from all others going on at the same time. Use that * number as the id string for your progressItem to ensure it is unique. */ static QString getUniqueID() { return QString::number( ++uID ); } /** * Creates a ProgressItem with a unique id and the given label. * This is the simplest way to acquire a progress item. It will not * have a parent and will be set to be cancellable and not using crypto. */ static ProgressItem *createProgressItem( const QString &label ) { return instance()->createProgressItemImpl( nullptr, getUniqueID(), label, QString(), true, false ); } /** * Creates a new progressItem with the given parent, id, label and initial * status. * * @param parent Specify an already existing item as the parent of this one. * @param id Used to identify this operation for cancel and progress info. * @param label The text to be displayed by progress handlers * @param status Additional text to be displayed for the item. * @param canBeCanceled can the user cancel this operation? * @param usesCrypto does the operation use secure transports (SSL) * Cancelling the parent will cancel the children as well (if they can be * canceled) and ongoing children prevent parents from finishing. * @return The ProgressItem representing the operation. */ static ProgressItem *createProgressItem( ProgressItem *parent, const QString &id, const QString &label, const QString &status = QString(), bool canBeCanceled = true, bool usesCrypto = false ) { return instance()->createProgressItemImpl( parent, id, label, status, canBeCanceled, usesCrypto ); } /** * Use this version if you have the id string of the parent and want to * add a subjob to it. */ static ProgressItem *createProgressItem( const QString &parent, const QString &id, const QString &label, const QString &status = QString(), bool canBeCanceled = true, bool usesCrypto = false ) { return instance()->createProgressItemImpl( parent, id, label, status, canBeCanceled, usesCrypto ); } /** * Version without a parent. */ static ProgressItem *createProgressItem( const QString &id, const QString &label, const QString &status = QString(), bool canBeCanceled = true, bool usesCrypto = false ) { return instance()->createProgressItemImpl( nullptr, id, label, status, canBeCanceled, usesCrypto ); } /** * Version for Akonadi agents. * This connects all the proper signals so that you do not have to * worry about updating the progress or reacting to progressItemCanceled(). */ static ProgressItem *createProgressItem( ProgressItem *parent, const Akonadi::AgentInstance &agent, const QString &id, const QString &label, const QString &status = QString(), bool canBeCanceled = true, bool usesCrypto = false ) { return instance()->createProgressItemForAgent( parent, agent, id, label, status, canBeCanceled, usesCrypto ); } /** * @return true when there are no more progress items. */ bool isEmpty() const { return mTransactions.isEmpty(); } /** * @return the only top level progressitem when there's only one. * Returns 0 if there is no item, or more than one top level item. * Since this is used to calculate the overall progress, it will also return * 0 if there is an item which uses a busy indicator, since that will invalidate * the overall progress. */ ProgressItem *singleItem() const; /** * Ask all listeners to show the progress dialog, because there is * something that wants to be shown. */ static void emitShowProgressDialog() { instance()->emitShowProgressDialogImpl(); } Q_SIGNALS: /** @see ProgressItem::progressItemAdded() */ void progressItemAdded( KDevelop::ProgressItem * ); /** @see ProgressItem::progressItemProgress() */ void progressItemProgress( KDevelop::ProgressItem *, unsigned int ); /** @see ProgressItem::progressItemCompleted() */ void progressItemCompleted( KDevelop::ProgressItem * ); /** @see ProgressItem::progressItemCanceled() */ void progressItemCanceled( KDevelop::ProgressItem * ); /** @see ProgressItem::progressItemStatus() */ void progressItemStatus( KDevelop::ProgressItem *, const QString & ); /** @see ProgressItem::progressItemLabel() */ void progressItemLabel( KDevelop::ProgressItem *, const QString & ); /** @see ProgressItem::progressItemUsesCrypto() */ void progressItemUsesCrypto( KDevelop::ProgressItem *, bool ); /** @see ProgressItem::progressItemUsesBusyIndicator */ void progressItemUsesBusyIndicator( KDevelop::ProgressItem*, bool ); /** * Emitted when an operation requests the listeners to be shown. * Use emitShowProgressDialog() to trigger it. */ void showProgressDialog(); public Q_SLOTS: /** * Calls setCompleted() on the item, to make sure it goes away. * Provided for convenience. * @param item the canceled item. */ void slotStandardCancelHandler( KDevelop::ProgressItem *item ); /** * Aborts all running jobs. Bound to "Esc" */ void slotAbortAll(); private Q_SLOTS: void slotTransactionCompleted( KDevelop::ProgressItem *item ); private: ProgressManager(); // prevent unsolicited copies ProgressManager( const ProgressManager & ); virtual ProgressItem *createProgressItemImpl( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool cancellable, bool usesCrypto ); virtual ProgressItem *createProgressItemImpl( const QString &parent, const QString &id, const QString &label, const QString &status, bool cancellable, bool usesCrypto ); ProgressItem *createProgressItemForAgent( ProgressItem *parent, const Akonadi::AgentInstance &instance, const QString &id, const QString &label, const QString &status, bool cancellable, bool usesCrypto ); void emitShowProgressDialogImpl(); QHash< QString, ProgressItem* > mTransactions; static unsigned int uID; }; } #endif // __KDevelop_PROGRESSMANAGER_H__ diff --git a/shell/progresswidget/statusbarprogresswidget.h b/shell/progresswidget/statusbarprogresswidget.h index 4aa0973df..aae03e623 100644 --- a/shell/progresswidget/statusbarprogresswidget.h +++ b/shell/progresswidget/statusbarprogresswidget.h @@ -1,97 +1,95 @@ #ifndef KDEVPLATFORM_STATUSBARPROGRESSWIDGET_H #define KDEVPLATFORM_STATUSBARPROGRESSWIDGET_H /*************************************************************************** * (C) 2004 Till Adam * * Don Sanders * * David Faure * * Copyright 2004 David Faure * * * * 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. * ***************************************************************************/ /** * A specialized progress widget class, heavily based on * kio_littleprogress_dlg (it looks similar) */ #include -class QBoxLayout; -class QEvent; class QProgressBar; class QToolButton; class QStackedWidget; class QBoxLayout; class QLabel; class QTimer; namespace KDevelop { class ProgressItem; class ProgressDialog; class StatusbarProgressWidget : public QFrame { Q_OBJECT public: StatusbarProgressWidget( ProgressDialog* progressDialog, QWidget* parent, bool button = true ); public Q_SLOTS: void slotClean(); void slotProgressItemAdded( KDevelop::ProgressItem *i ); void slotProgressItemCompleted( KDevelop::ProgressItem *i ); void slotProgressItemProgress( KDevelop::ProgressItem *i, unsigned int value ); protected Q_SLOTS: void slotProgressDialogVisible( bool ); void slotShowItemDelayed(); void updateBusyMode(); protected: void setMode(); void connectSingleItem(); void activateSingleItemMode(); void activateSingleItemMode( unsigned int progress ); bool eventFilter( QObject *, QEvent * ) override; private: QProgressBar* m_pProgressBar; union { QLabel* label; QToolButton* button; } m_pPlaceHolder; QToolButton* m_pButton; enum Mode { None, /*Label,*/ Progress }; uint mode; bool m_bShowButton; QBoxLayout *box; QStackedWidget *stack; ProgressItem *mCurrentItem; ProgressDialog* mProgressDialog; QTimer *mDelayTimer; QTimer *mCleanTimer; }; } // namespace #endif diff --git a/shell/project.cpp b/shell/project.cpp index 064483669..f61e857bc 100644 --- a/shell/project.cpp +++ b/shell/project.cpp @@ -1,653 +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 #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::HideProgressInfo | 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 = 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 = 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 = nullptr; return nullptr; } if (iface == nullptr) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "project importing plugin (%1) does not support the IProjectFileManager interface.", managerSetting ) ); delete manager; 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 = 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 = 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(), -1, KIO::HideProgressInfo); 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/projectsourcepage.cpp b/shell/projectsourcepage.cpp index 859c3c269..35f5f6520 100644 --- a/shell/projectsourcepage.cpp +++ b/shell/projectsourcepage.cpp @@ -1,315 +1,315 @@ /*************************************************************************** * 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 #include #include #include using namespace KDevelop; static const int FROM_FILESYSTEM_SOURCE_INDEX = 0; ProjectSourcePage::ProjectSourcePage(const QUrl& initial, const QUrl& repoUrl, IPlugin* preSelectPlugin, 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(nullptr); int preselectIndex = -1; IPluginController* pluginManager = ICore::self()->pluginController(); QList plugins = pluginManager->allPluginsForExtension( QStringLiteral("org.kdevelop.IBasicVersionControl") ); foreach( IPlugin* p, plugins ) { if (p == preSelectPlugin) { preselectIndex = m_plugins.count(); } 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 ) { if (p == preSelectPlugin) { preselectIndex = m_plugins.count(); } m_plugins.append(p); m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension()->name()); } if (preselectIndex == -1) { // "From File System" is quite unlikely to be what the user wants, so default to first real plugin... const int defaultIndex = (m_plugins.count() > 1) ? 1 : 0; KConfigGroup configGroup = KSharedConfig::openConfig()->group("Providers"); preselectIndex = configGroup.readEntry("LastProviderIndex", defaultIndex); } preselectIndex = qBound(0, preselectIndex, m_ui->sources->count() - 1); m_ui->sources->setCurrentIndex(preselectIndex); setSourceWidget(preselectIndex, repoUrl); // connect as last step, otherwise KMessageWidget could get both animatedHide() and animatedShow() // during setup and due to a bug will ignore any but the first call // Only fixed for KF5 5.32 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); } ProjectSourcePage::~ProjectSourcePage() { KConfigGroup configGroup = KSharedConfig::openConfig()->group("Providers"); configGroup.writeEntry("LastProviderIndex", m_ui->sources->currentIndex()); delete m_ui; } void ProjectSourcePage::setSourceIndex(int index) { setSourceWidget(index, QUrl()); } void ProjectSourcePage::setSourceWidget(int index, const QUrl& repoUrl) { m_locationWidget = nullptr; m_providerWidget = nullptr; QLayout* remoteWidgetLayout = m_ui->remoteWidget->layout(); QLayoutItem *child; 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); // set after connect, to trigger handler if (!repoUrl.isEmpty()) { m_locationWidget->setLocation(repoUrl); } 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 nullptr; else return p->extension(); } IProjectProvider* ProjectSourcePage::providerPerIndex(int index) { IPlugin* p = m_plugins.value(index); if(!p) 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=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(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()); const bool validWidget = ((m_locationWidget && m_locationWidget->isCorrect()) || (m_providerWidget && m_providerWidget->isCorrect())); const bool isFolderEmpty = (correct && cwd.isLocalFile() && dir.exists() && dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty()); const bool validToCheckout = correct && validWidget && (!dir.exists() || isFolderEmpty); 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() && !validWidget) setStatus(i18n("You need to specify the source for your remote project")); else if(!m_ui->get->isEnabled() && m_ui->workingDir->isEnabled() && !isFolderEmpty) setStatus(i18n("You need to specify an empty folder as your project destination")); 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() { #if KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5,32,0) // workaround for KMessageWidget bug: // animatedHide() will not explicitely hide the widget if it is not yet shown. // So if it has never been explicitely hidden otherwise, // if show() is called on the parent later the KMessageWidget will be shown as well. // As this method is sometimes called when the page is created and thus not yet shown, // we have to ensure the hidden state ourselves here. if (!m_ui->status->isVisible()) { m_ui->status->hide(); return; } #endif m_ui->status->animatedHide(); } QUrl ProjectSourcePage::workingDir() const { return m_ui->workingDir->url(); } diff --git a/shell/runcontroller.cpp b/shell/runcontroller.cpp index 436bb14c9..13390100b 100644 --- a/shell/runcontroller.cpp +++ b/shell/runcontroller.cpp @@ -1,1053 +1,1052 @@ /* 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("debug-run")); } 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(); } } 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 = 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 = 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 = 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 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 = nullptr; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->launchChangeMapper = new QSignalMapper( this ); 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 = nullptr; delete d->profileMode; d->profileMode = nullptr; delete d->debugMode; 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(), 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 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 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, &RunController::showConfigurationDialog); 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()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("debug") ); } } void RunController::slotProfile() { if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("profile") ); } } void RunController::slotExecute() { if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("execute") ); } } void KDevelop::RunController::showConfigurationDialog() const { LaunchConfigurationDialog dlg; dlg.exec(); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); 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 = 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(), 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 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) { if (auto dl = defaultLaunch()) { execute(runMode, dl); } else { qWarning() << "no default launch!"; } } 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 = 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/runcontroller.h b/shell/runcontroller.h index a77c5af62..4ccf72b18 100644 --- a/shell/runcontroller.h +++ b/shell/runcontroller.h @@ -1,167 +1,166 @@ /* 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. */ #ifndef KDEVPLATFORM_RUNCONTROLLER_H #define KDEVPLATFORM_RUNCONTROLLER_H #include #include #include #include #include "shellexport.h" class QStyleOptionViewItem; -class QPainter; class QModelIndex; class KStatefulBrush; namespace KDevelop { class Context; class ContextMenuExtension; class IPlugin; class IProject; class LaunchConfiguration; class LaunchConfigurationType; class KDEVPLATFORMSHELL_EXPORT RunController : public IRunController { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kdevelop.RunController") public: explicit RunController(QObject *parent); ~RunController() override; void registerJob(KJob *job) override; void unregisterJob(KJob *job) override; QList currentJobs() const override; KJob* execute(const QString& launchMode, ILaunchConfiguration* launch) override; QList launchModes() const override; /** * @copydoc IRunController::addLaunchMode */ void addLaunchMode( ILaunchMode* mode ) override; /** * @copydoc IRunController::removeLaunchMode */ void removeLaunchMode( ILaunchMode* mode ) override; /** * @copydoc IRunController::launchModeForId() */ KDevelop::ILaunchMode* launchModeForId(const QString& id) const override; void initialize(); void cleanup(); QItemDelegate* delegate() const; void addLaunchConfiguration( LaunchConfiguration* l ); void removeLaunchConfiguration( LaunchConfiguration* l ); QList launchConfigurationsInternal() const; QList launchConfigurations() const override; /** * @copydoc IRunController::launchConfigurationTypes() */ QList launchConfigurationTypes() const override; /** * @copydoc IRunController::addConfigurationType() */ void addConfigurationType( LaunchConfigurationType* type ) override; /** * @copydoc IRunController::removeConfigurationType() */ void removeConfigurationType( LaunchConfigurationType* type ) override; /** * Find the launch configuration type for the given @p id. * @returns the launch configuration type having the id, or 0 if no such type is known */ LaunchConfigurationType* launchConfigurationTypeForId( const QString& ) override; ILaunchConfiguration* createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project = nullptr, const QString& name = QString() ) override; void setDefaultLaunch(ILaunchConfiguration* l); LaunchConfiguration* defaultLaunch() const; /** * @copydoc IRunController::showConfigurationDialog() */ void showConfigurationDialog() const override; ContextMenuExtension contextMenuExtension( KDevelop::Context* ctx ); public Q_SLOTS: Q_SCRIPTABLE void executeDefaultLaunch(const QString& runMode) override; Q_SCRIPTABLE void stopAllProcesses() override; protected Q_SLOTS: void finished(KJob *job) override; void suspended(KJob *job) override; void resumed(KJob *job) override; private Q_SLOTS: void slotRefreshProject(KDevelop::IProject* project); void slotExecute(); void slotDebug(); void slotProfile(); void slotProjectOpened(KDevelop::IProject* project); void slotProjectClosing(KDevelop::IProject* project); void slotKillJob(); void launchChanged(LaunchConfiguration*); void jobDestroyed(QObject* job); void jobPercentChanged(); private: void setupActions(); void checkState(); void removeLaunchConfigurationInternal( LaunchConfiguration* l ); class RunControllerPrivate; RunControllerPrivate* const d; }; class RunDelegate : public QItemDelegate { Q_OBJECT public: explicit RunDelegate( QObject* = nullptr ); void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const override; private: KStatefulBrush runProviderBrush; KStatefulBrush errorBrush; }; } #endif diff --git a/shell/savedialog.cpp b/shell/savedialog.cpp index ff52cfd19..6c2430d7e 100644 --- a/shell/savedialog.cpp +++ b/shell/savedialog.cpp @@ -1,99 +1,97 @@ /* This file is part of the KDE project Copyright (C) 2002 Harald Fernengel Copyright (C) 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 "savedialog.h" #include #include #include #include #include #include -#include #include #include -#include using namespace KDevelop; class DocumentItem : public QListWidgetItem { public: DocumentItem( IDocument* doc, QListWidget* parent ) : QListWidgetItem(parent) , m_doc( doc ) { setFlags(Qt::ItemIsUserCheckable | flags()); setData(Qt::CheckStateRole, Qt::Checked); setText(m_doc->url().toDisplayString(QUrl::PreferLocalFile)); } IDocument* doc() const { return m_doc; } private: IDocument* m_doc; }; KSaveSelectDialog::KSaveSelectDialog( const QList& files, QWidget * parent ) : QDialog( parent ) { setWindowTitle( i18n("Save Modified Files?") ); auto mainLayout = new QVBoxLayout(this); mainLayout->addWidget(new QLabel( i18n("The following files have been modified. Save them?"), this )); m_listWidget = new QListWidget(this); mainLayout->addWidget(m_listWidget); // m_listWidget->addColumn( "" ); // m_listWidget->header()->hide(); // m_listWidget->setSectionResizeMode( QListView::LastColumn ); foreach (IDocument* doc, files) new DocumentItem( doc, m_listWidget ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Save|QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Save); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &KSaveSelectDialog::save); connect(buttonBox, &QDialogButtonBox::rejected, this, &KSaveSelectDialog::reject); auto user1Button = buttonBox->addButton(i18n("Save &None" ), QDialogButtonBox::ActionRole); user1Button->setToolTip(i18n("Discard all modifications" )); connect(user1Button, &QPushButton::clicked, this, &KSaveSelectDialog::accept); mainLayout->addWidget(buttonBox); } KSaveSelectDialog::~KSaveSelectDialog() { } void KSaveSelectDialog::save( ) { for (int i = 0; i < m_listWidget->count(); ++i) { DocumentItem* item = static_cast(m_listWidget->item(i)); if (item->data(Qt::CheckStateRole).toBool()) item->doc()->save(IDocument::Silent); } accept(); } diff --git a/shell/session.cpp b/shell/session.cpp index 21448b4e5..37ad2b052 100644 --- a/shell/session.cpp +++ b/shell/session.cpp @@ -1,235 +1,232 @@ /* 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 = 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/session.h b/shell/session.h index 918baffa7..304084a68 100644 --- a/shell/session.h +++ b/shell/session.h @@ -1,90 +1,89 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_SESSION_H #define KDEVPLATFORM_SESSION_H #include "shellexport.h" -#include #include #include #include #include namespace KDevelop { struct SessionInfo { QString name; QUuid uuid; QString description; QList projects; QString path; KSharedConfigPtr config; }; using SessionInfos = QVector; class KDEVPLATFORMSHELL_EXPORT Session : public ISession { Q_OBJECT public: static const QString cfgSessionNameEntry; static const QString cfgSessionDescriptionEntry; static const QString cfgSessionProjectsEntry; static const QString cfgSessionOptionsGroup; explicit Session( const QString& id, QObject * parent = nullptr ); ~Session() override; QUrl pluginDataArea( const IPlugin* ) override; KSharedConfigPtr config() override; QList containedProjects() const override; void setContainedProjects( const QList& projects ) override; QString name() const override; void setName( const QString& ); QUuid id() const override; QString description() const override; bool isTemporary() const override; void setTemporary(bool temp) override; QString path() const; /** * Generates a @ref SessionInfo by a session @p id. * @param mkdir Whether to create a session directory if one does not exist. */ static SessionInfo parse( const QString& id, bool mkdir = false ); private: class SessionPrivate* const d; friend class SessionPrivate; }; } Q_DECLARE_METATYPE( KDevelop::Session* ) #endif diff --git a/shell/sessioncontroller.cpp b/shell/sessioncontroller.cpp index dae683216..8345d2974 100644 --- a/shell/sessioncontroller.cpp +++ b/shell/sessioncontroller.cpp @@ -1,664 +1,661 @@ /* 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 #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 = 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: explicit SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(nullptr) , grp(nullptr) { } ~SessionControllerPrivate() override { } Session* findSessionForName( const QString& name ) const { foreach( Session* s, sessionActions.keys() ) { if( s->name() == name ) return s; } return nullptr; } Session* findSessionForId(QString idString) { QUuid id(idString); foreach( Session* s, sessionActions.keys() ) { if( s->id() == id) return s; } return nullptr; } void newSession() { qsrand(QDateTime::currentDateTimeUtc().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->executableFilePath(), 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()->executableFilePath(), 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 = 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] = 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"), i18n("Session Manager")); 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 = 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 = 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/settings/editstyledialog.cpp b/shell/settings/editstyledialog.cpp index ddc694a4f..5f96a9cdc 100644 --- a/shell/settings/editstyledialog.cpp +++ b/shell/settings/editstyledialog.cpp @@ -1,125 +1,124 @@ /* 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 "editstyledialog.h" #include #include #include -#include +#include #include #include #include #include #include #include -#include using namespace KDevelop; EditStyleDialog::EditStyleDialog(ISourceFormatter* formatter, const QMimeType& mime, const SourceFormatterStyle& style, QWidget* parent) : QDialog(parent) , m_sourceFormatter(formatter) , m_mimeType(mime) , m_style(style) { m_content = new QWidget(); m_ui.setupUi(m_content); QVBoxLayout* mainLayout = new QVBoxLayout(this); mainLayout->addWidget(m_content); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); connect(buttonBox, &QDialogButtonBox::accepted, this, &EditStyleDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &EditStyleDialog::reject); mainLayout->addWidget(buttonBox); m_settingsWidget = m_sourceFormatter->editStyleWidget(mime); init(); if (m_settingsWidget) { m_settingsWidget->load(style); } } EditStyleDialog::~EditStyleDialog() { } void EditStyleDialog::init() { // add plugin settings widget if (m_settingsWidget) { QVBoxLayout* layout = new QVBoxLayout(m_ui.settingsWidgetParent); layout->addWidget(m_settingsWidget); m_ui.settingsWidgetParent->setLayout(layout); connect(m_settingsWidget, &SettingsWidget::previewTextChanged, this, &EditStyleDialog::updatePreviewText); } m_document = KTextEditor::Editor::instance()->createDocument(this); m_document->setReadWrite(false); m_document->setHighlightingMode(m_style.modeForMimetype(m_mimeType)); m_view = m_document->createView(m_ui.textEditor); QVBoxLayout* layout2 = new QVBoxLayout(m_ui.textEditor); layout2->addWidget(m_view); m_ui.textEditor->setLayout(layout2); m_view->setStatusBarEnabled(false); 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); } if (m_sourceFormatter) { QString text = m_sourceFormatter->previewText(m_style, m_mimeType); updatePreviewText(text); } } void EditStyleDialog::updatePreviewText(const QString &text) { m_document->setReadWrite(true); m_style.setContent(content()); if (m_sourceFormatter) { m_document->setText(m_sourceFormatter->formatSourceWithStyle(m_style, text, QUrl(), m_mimeType)); } else { m_document->setText(i18n("No Source Formatter available")); } m_view->setCursorPosition(KTextEditor::Cursor(0, 0)); m_document->setReadWrite(false); } QString EditStyleDialog::content() { if (m_settingsWidget) { return m_settingsWidget->save(); } return QString(); } diff --git a/shell/settings/environmentpreferences.cpp b/shell/settings/environmentpreferences.cpp index c83f20606..b564b69f6 100644 --- a/shell/settings/environmentpreferences.cpp +++ b/shell/settings/environmentpreferences.cpp @@ -1,100 +1,99 @@ /* This file is part of KDevelop 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 "environmentpreferences.h" #include #include -#include #include #include #include "environmentwidget.h" namespace KDevelop { class EnvironmentPreferencesPrivate { public: EnvironmentWidget *preferencesDialog; KConfigSkeleton* skel; QString preselectedProfileName; }; EnvironmentPreferences::EnvironmentPreferences(const QString& preselectedProfileName, QWidget* parent) : ConfigPage(nullptr, nullptr, parent), d(new EnvironmentPreferencesPrivate) { QVBoxLayout * l = new QVBoxLayout( this ); l->setMargin(0); d->preferencesDialog = new EnvironmentWidget( this ); l->addWidget( d->preferencesDialog ); connect(d->preferencesDialog, &EnvironmentWidget::changed, this, &EnvironmentPreferences::changed); d->skel = new KConfigSkeleton(KSharedConfig::openConfig()); setConfigSkeleton(d->skel); d->preselectedProfileName = preselectedProfileName; } EnvironmentPreferences::~EnvironmentPreferences( ) { delete d; } void EnvironmentPreferences::apply() { d->preferencesDialog->saveSettings(d->skel->config()); ConfigPage::apply(); } void EnvironmentPreferences::reset() { d->preferencesDialog->loadSettings(d->skel->config()); if (!d->preselectedProfileName.isEmpty()) { d->preferencesDialog->selectProfile(d->preselectedProfileName); } ConfigPage::reset(); } void EnvironmentPreferences::defaults() { d->preferencesDialog->defaults(d->skel->config()); ConfigPage::defaults(); } QString EnvironmentPreferences::name() const { return i18n("Environment"); } QString EnvironmentPreferences::fullName() const { return i18n("Configure Environment Variables"); } QIcon EnvironmentPreferences::icon() const { return QIcon::fromTheme(QStringLiteral("utilities-terminal")); } } diff --git a/shell/settings/environmentprofilelistmodel.h b/shell/settings/environmentprofilelistmodel.h index 809d190c6..46bc8b956 100644 --- a/shell/settings/environmentprofilelistmodel.h +++ b/shell/settings/environmentprofilelistmodel.h @@ -1,67 +1,65 @@ /* This file is part of KDevelop Copyright 2007 Andreas Pakulat Copyright 2017 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ENVIRONMENTPROFILELISTMODEL_H #define KDEVPLATFORM_ENVIRONMENTPROFILELISTMODEL_H #include #include "util/environmentprofilelist.h" -class QStringList; - namespace KDevelop { // Subclassing EnvironmentProfileList instead of having as a member, to have access to protected API class EnvironmentProfileListModel : public QAbstractItemModel, protected EnvironmentProfileList { Q_OBJECT public: explicit EnvironmentProfileListModel(QObject* parent = nullptr); int rowCount(const QModelIndex& parent = {}) const override; int columnCount(const QModelIndex& parent = {}) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex& parent = {}) const override; QModelIndex parent(const QModelIndex& index) const override; int profileIndex(const QString& profileName) const; int defaultProfileIndex() const; QString profileName(int profileIndex) const; bool hasProfile(const QString& profileName) const; QMap& variables(const QString& profileName); int addProfile(const QString& newProfileName); int cloneProfile(const QString& newProfileName, const QString& sourceProfileName); void removeProfile(int profileIndex); void setDefaultProfile(int profileIndex); void loadFromConfig(KConfig* config); void saveToConfig(KConfig* config); Q_SIGNALS: void profileAboutToBeRemoved(const QString& profileName); void defaultProfileChanged(int defaultProfileIndex); }; } #endif diff --git a/shell/settings/languagepreferences.cpp b/shell/settings/languagepreferences.cpp index b98f9691e..103f58b9f 100644 --- a/shell/settings/languagepreferences.cpp +++ b/shell/settings/languagepreferences.cpp @@ -1,98 +1,96 @@ /* KDevelop Project Settings * * Copyright 2006 Matt Rogers * Copyright 2007 Hamish Rodda * Copyright 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. * * 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 "languagepreferences.h" -#include - #include #include #include #include #include #include #include #include "../completionsettings.h" #include "../core.h" #include "languageconfig.h" #include "ui_languagepreferences.h" using namespace KTextEditor; namespace KDevelop { LanguagePreferences::LanguagePreferences(QWidget* parent) : ConfigPage(nullptr, LanguageConfig::self(), parent) { preferencesDialog = new Ui::LanguagePreferences; preferencesDialog->setupUi(this); preferencesDialog->kcfg_minFilesForSimplifiedParsing->setSuffix(ki18np(" file", " files")); } void LanguagePreferences::notifySettingsChanged() { CompletionSettings& settings(static_cast(*ICore::self()->languageController()->completionSettings())); settings.emitChanged(); } LanguagePreferences::~LanguagePreferences( ) { delete preferencesDialog; } void LanguagePreferences::apply() { ConfigPage::apply(); foreach (KDevelop::IDocument* doc, Core::self()->documentController()->openDocuments()) { if (Document* textDoc = doc->textDocument()) { foreach (View* view, textDoc->views()) { if (CodeCompletionInterface* cc = dynamic_cast(view)) { cc->setAutomaticInvocationEnabled(preferencesDialog->kcfg_automaticInvocation->isChecked()); } } } } notifySettingsChanged(); } QString LanguagePreferences::name() const { return i18n("Language Support"); } QString LanguagePreferences::fullName() const { return i18n("Configure Code-Completion and Semantic Highlighting"); } QIcon LanguagePreferences::icon() const { return QIcon::fromTheme(QStringLiteral("page-zoom")); } } diff --git a/shell/settings/sourceformattersettings.cpp b/shell/settings/sourceformattersettings.cpp index 500d3a345..36e8d5d63 100644 --- a/shell/settings/sourceformattersettings.cpp +++ b/shell/settings/sourceformattersettings.cpp @@ -1,540 +1,538 @@ /* 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(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(); } } } 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 ); { QSignalBlocker blocker(cbFormatters); 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())); } 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 = 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 == 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 ) != 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/settings/sourceformattersettings.h b/shell/settings/sourceformattersettings.h index e1e2e25a0..68cfb42c6 100644 --- a/shell/settings/sourceformattersettings.h +++ b/shell/settings/sourceformattersettings.h @@ -1,98 +1,97 @@ /* 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. */ #ifndef KDEVPLATFORM_SOURCEFORMATTERSETTINGS_H #define KDEVPLATFORM_SOURCEFORMATTERSETTINGS_H -#include #include #include #include "sourceformattercontroller.h" #include "ui_sourceformattersettings.h" class QListWidgetItem; namespace KTextEditor { class Document; class View; } namespace KDevelop { class ISourceFormatter; class SourceFormatterStyle; } struct LanguageSettings { LanguageSettings(); QList mimetypes; QSet formatters; // weak pointers to selected formatter and style, no ownership KDevelop::SourceFormatter* selectedFormatter; // Should never be zero KDevelop::SourceFormatterStyle* selectedStyle; // TODO: can this be zero? Assume that not }; /** \short The settings modulefor the Source formatter plugin. * It supports predefined and custom styles. A live preview of the style * is shown on the right side of the page.s */ class SourceFormatterSettings : public KDevelop::ConfigPage, public Ui::SourceFormatterSettingsUI { Q_OBJECT public: explicit SourceFormatterSettings(QWidget* parent = nullptr); ~SourceFormatterSettings() override; QString name() const override; QString fullName() const override; QIcon icon() const override; public slots: void reset() override; void apply() override; void defaults() override; private slots: void deleteStyle(); void editStyle(); void newStyle(); void selectLanguage( int ); void selectFormatter( int ); void selectStyle( int ); void styleNameChanged( QListWidgetItem* ); void somethingChanged(); private: void updatePreview(); QListWidgetItem* addStyle( const KDevelop::SourceFormatterStyle& s ); void enableStyleButtons(); // Language name -> language settings typedef QMap LanguageMap; LanguageMap languages; // formatter name -> formatter. Formatters owned by this typedef QMap FormatterMap; FormatterMap formatters; KTextEditor::Document* m_document; KTextEditor::View* m_view; }; #endif // KDEVPLATFORM_SOURCEFORMATTERSETTINGS_H diff --git a/shell/settings/templatepage.cpp b/shell/settings/templatepage.cpp index 6685422de..21bc6983d 100644 --- a/shell/settings/templatepage.cpp +++ b/shell/settings/templatepage.cpp @@ -1,128 +1,127 @@ /* * 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; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "templatepage.h" #include "ui_templatepage.h" #include #include #include #include #include #include #include #include -#include TemplatePage::TemplatePage (KDevelop::ITemplateProvider* provider, QWidget* parent) : QWidget (parent), m_provider(provider) { ui = new Ui::TemplatePage; ui->setupUi(this); ui->getNewButton->setVisible(!m_provider->knsConfigurationFile().isEmpty()); connect(ui->getNewButton, &QPushButton::clicked, this, &TemplatePage::getMoreTemplates); ui->shareButton->setVisible(!m_provider->knsConfigurationFile().isEmpty()); connect(ui->shareButton, &QPushButton::clicked, this, &TemplatePage::shareTemplates); ui->loadButton->setVisible(!m_provider->supportedMimeTypes().isEmpty()); connect(ui->loadButton, &QPushButton::clicked, this, &TemplatePage::loadFromFile); ui->extractButton->setEnabled(false); connect(ui->extractButton, &QPushButton::clicked, this, &TemplatePage::extractTemplate); provider->reload(); ui->treeView->setModel(provider->templatesModel()); ui->treeView->expandAll(); connect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TemplatePage::currentIndexChanged); } TemplatePage::~TemplatePage() { delete ui; } void TemplatePage::loadFromFile() { const QString filter = m_provider->supportedMimeTypes().join(QStringLiteral(";;")); const QString filename = QFileDialog::getOpenFileName(this, QString(), QString(), filter); if (!filename.isEmpty()) { m_provider->loadTemplate(filename); } m_provider->reload(); } void TemplatePage::getMoreTemplates() { KNS3::DownloadDialog dialog(m_provider->knsConfigurationFile(), this); dialog.exec(); if (!dialog.changedEntries().isEmpty()) { m_provider->reload(); } } void TemplatePage::shareTemplates() { KNS3::UploadDialog dialog(m_provider->knsConfigurationFile(), this); dialog.exec(); } void TemplatePage::currentIndexChanged(const QModelIndex& index) { QString archive = ui->treeView->model()->data(index, KDevelop::TemplatesModel::ArchiveFileRole).toString(); ui->extractButton->setEnabled(QFileInfo::exists(archive)); } void TemplatePage::extractTemplate() { QModelIndex index = ui->treeView->currentIndex(); QString archiveName= ui->treeView->model()->data(index, KDevelop::TemplatesModel::ArchiveFileRole).toString(); QFileInfo info(archiveName); if (!info.exists()) { ui->extractButton->setEnabled(false); return; } QScopedPointer archive; if (info.suffix() == QLatin1String("zip")) { archive.reset(new KZip(archiveName)); } else { archive.reset(new KTar(archiveName)); } archive->open(QIODevice::ReadOnly); const QString destination = QFileDialog::getExistingDirectory() + '/' + info.baseName(); archive->directory()->copyTo(destination); } diff --git a/shell/sourceformattercontroller.h b/shell/sourceformattercontroller.h index 9424efdd0..5db2e80db 100644 --- a/shell/sourceformattercontroller.h +++ b/shell/sourceformattercontroller.h @@ -1,165 +1,164 @@ /* 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. */ #ifndef KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #define KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #include #include -#include #include #include #include #include #include #include #include "shellexport.h" namespace KTextEditor { class Document; } class QAction; namespace KDevelop { class Context; class ContextMenuExtension; class ProjectBaseItem; class IDocument; class ISourceFormatter; class IPlugin; struct SourceFormatter { KDevelop::ISourceFormatter* formatter; // style name -> style. style objects owned by this typedef QMap StyleMap; StyleMap styles; // Get a list of supported mime types from the style map. QSet supportedMimeTypes() const { QSet supported; for ( auto style: styles ) { foreach ( auto& item, style->mimeTypes() ) { supported.insert(item.mimeType); } } return supported; } ~SourceFormatter() { qDeleteAll(styles); }; }; /** \short A singleton class managing all source formatter plugins */ class KDEVPLATFORMSHELL_EXPORT SourceFormatterController : public ISourceFormatterController, public KXMLGUIClient { Q_OBJECT public: static QString kateModeLineConfigKey(); static QString kateOverrideIndentationConfigKey(); static QString styleCaptionKey(); static QString styleContentKey(); static QString styleMimeTypesKey(); static QString styleSampleKey(); explicit SourceFormatterController(QObject *parent = nullptr); ~SourceFormatterController() override; void initialize(); void cleanup(); //----------------- Public API defined in interfaces ------------------- /** \return The formatter corresponding to the language * of the document corresponding to the \arg url. */ ISourceFormatter* formatterForUrl(const QUrl &url) override; /** Loads and returns a source formatter for this mime type. * The language is then activated and the style is loaded. * The source formatter is then ready to use on a file. */ ISourceFormatter* formatterForMimeType(const QMimeType& mime) override; /** \return Whether this mime type is supported by any plugin. */ bool isMimeTypeSupported(const QMimeType& mime) override; /** * @brief Instantiate a Formatter for the given plugin and load its configuration. * * @param ifmt The ISourceFormatter interface of the plugin * @return KDevelop::SourceFormatter* the SourceFormatter instance for the plugin, including config items */ SourceFormatter* createFormatterForPlugin(KDevelop::ISourceFormatter* ifmt) const; /** * @brief Find the first formatter which supports a given mime type. */ ISourceFormatter* findFirstFormatterForMimeType(const QMimeType& mime) const; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context); KDevelop::SourceFormatterStyle styleForMimeType(const QMimeType& mime) override; KConfigGroup sessionConfig() const; KConfigGroup globalConfig() const; void settingsChanged(); void disableSourceFormatting(bool disable) override; bool sourceFormattingEnabled() override; private Q_SLOTS: void updateFormatTextAction(); void beautifySource(); void beautifyLine(); void formatFiles(); void documentLoaded( KDevelop::IDocument* ); private: /** \return A modeline string (to add at the end or the beginning of a file) * corresponding to the settings of the active language. */ QString addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType&); /** \return The name of kate indentation mode for the mime type. * examples are cstyle, python, etc. */ QString indentationMode(const QMimeType& mime); void formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime); // Adapts the mode of the editor regarding indentation-style void adaptEditorIndentationMode(KTextEditor::Document* doc, KDevelop::ISourceFormatter* formatter, bool ignoreModeline = false); void formatFiles(QList &list); // GUI actions QAction* m_formatTextAction; QAction* m_formatFilesAction; QAction* m_formatLine; QList m_prjItems; QList m_urls; bool m_enabled; }; } #endif // KDEVPLATFORM_SOURCEFORMATTERMANAGER_H diff --git a/shell/statusbar.cpp b/shell/statusbar.cpp index 1f818730d..9de8185a5 100644 --- a/shell/statusbar.cpp +++ b/shell/statusbar.cpp @@ -1,266 +1,264 @@ /* 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(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/statusbar.h b/shell/statusbar.h index ae441529f..3863fa098 100644 --- a/shell/statusbar.h +++ b/shell/statusbar.h @@ -1,101 +1,99 @@ /* 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. */ #ifndef KDEVPLATFORM_STATUSBAR_H #define KDEVPLATFORM_STATUSBAR_H #include #include #include -class QProgressBar; class QSignalMapper; class QTimer; -class QLabel; namespace Sublime { class View; } namespace KDevelop { class IStatus; class IPlugin; class ProgressItem; class ProgressDialog; class StatusbarProgressWidget; class ProgressManager; /** * Status bar */ class StatusBar : public QStatusBar { Q_OBJECT public: /** * Constructs a status bar. */ explicit StatusBar(QWidget* parent); void registerStatus(QObject* status); void updateMessage(); void viewChanged(Sublime::View* view); public Q_SLOTS: void showErrorMessage(const QString& message, int timeout); private Q_SLOTS: void clearMessage( KDevelop::IStatus* ); void showMessage( KDevelop::IStatus*, const QString & message, int timeout); void hideProgress( KDevelop::IStatus* ); void showProgress( KDevelop::IStatus*, int minimum, int maximum, int value); void slotTimeout(); void viewStatusChanged(Sublime::View* view); void pluginLoaded(KDevelop::IPlugin*); void removeError(QWidget*); private: QTimer* errorTimeout(QWidget* error, int timeout); private: struct Message { QString text; int timeout; }; QMap m_messages; QTimer* m_timer; QTime m_time; Sublime::View* m_currentView; QSignalMapper* m_errorRemovalMapper; QMap m_progressItems; StatusbarProgressWidget* m_progressWidget; // embedded in the statusbar, shows a single progressbar & button to expand the overlay widget ProgressDialog* m_progressDialog; // the actual overlay widget that contains multiple progressbars and status messages ProgressManager* m_progressController; // progress item model }; } #endif // KDEVPLATFORM_STATUSBAR_H diff --git a/shell/tests/nonguiinterfaceplugin.h b/shell/tests/nonguiinterfaceplugin.h index d08f6f81e..54881a1c2 100644 --- a/shell/tests/nonguiinterfaceplugin.h +++ b/shell/tests/nonguiinterfaceplugin.h @@ -1,48 +1,47 @@ /* * 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. */ #ifndef NONGUIINTERFACEPLUGIN #define NONGUIINTERFACEPLUGIN #include #include -#include class ITestNonGuiInterface { public: virtual ~ITestNonGuiInterface() {} }; Q_DECLARE_INTERFACE( ITestNonGuiInterface, "org.kdevelop.ITestNonGuiInterface" ) class NonGuiInterfacePlugin : public KDevelop::IPlugin, ITestNonGuiInterface { Q_OBJECT Q_INTERFACES(ITestNonGuiInterface) public: explicit NonGuiInterfacePlugin( QObject* parent, const QVariantList& = QVariantList() ); }; #endif diff --git a/shell/tests/test_checkerstatus.cpp b/shell/tests/test_checkerstatus.cpp index 27da9cfb2..707da7f90 100644 --- a/shell/tests/test_checkerstatus.cpp +++ b/shell/tests/test_checkerstatus.cpp @@ -1,108 +1,108 @@ /* * 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 +#include #include #include #include using namespace KDevelop; #define MYCOMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ return false class TestCheckerStatus : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testName(); void testStart(); void testItemChecked(); void testStop(); private: QScopedPointer m_status; }; void TestCheckerStatus::initTestCase() { m_status.reset(new CheckerStatus()); } void TestCheckerStatus::cleanupTestCase() { } void TestCheckerStatus::testName() { QString name = QStringLiteral("TESTNAME"); QString s = QStringLiteral("Running ") + name; m_status->setCheckerName(name); QCOMPARE(m_status->statusName(), i18nc("@info:progress", s.toUtf8().data())); } bool checkValues(const QList &signal, int min, int max, int value) { MYCOMPARE(qvariant_cast(signal[1]), min); MYCOMPARE(qvariant_cast(signal[2]), max); MYCOMPARE(qvariant_cast(signal[3]), value); return true; } void TestCheckerStatus::testStart() { QSignalSpy spy(m_status.data(), &CheckerStatus::showProgress); m_status->setMaxItems(100); m_status->start(); QCOMPARE(spy.count(), 1); QVERIFY(checkValues(spy.takeFirst(), 0, 100, 0)); } void TestCheckerStatus::testItemChecked() { QSignalSpy spy(m_status.data(), &CheckerStatus::showProgress); m_status->itemChecked(); QCOMPARE(spy.count(), 1); QVERIFY(checkValues(spy.takeFirst(), 0, 100, 1)); } void TestCheckerStatus::testStop() { QSignalSpy spy(m_status.data(), &CheckerStatus::showProgress); m_status->stop(); QCOMPARE(spy.count(), 1); QVERIFY(checkValues(spy.takeFirst(), 0, 100, 100)); } QTEST_MAIN(TestCheckerStatus) #include "test_checkerstatus.moc" diff --git a/shell/tests/test_detectedproblem.cpp b/shell/tests/test_detectedproblem.cpp index ae45400b3..61c457f4b 100644 --- a/shell/tests/test_detectedproblem.cpp +++ b/shell/tests/test_detectedproblem.cpp @@ -1,221 +1,221 @@ /* * 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 +#include #include #include #include #include #include #include using namespace KDevelop; class TestDetectedProblem : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testSource(); void testSeverity(); void testDescription(); void testExplanation(); void testFinalLocation(); void testDiagnostics(); void testPluginName(); private: IProblem::Ptr m_problem; }; void TestDetectedProblem::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_problem = new DetectedProblem(); } void TestDetectedProblem::cleanupTestCase() { TestCore::shutdown(); } struct Source { IProblem::Source source; QString sourceString; }; void TestDetectedProblem::testSource() { static Source sources[] = { { IProblem::Unknown, i18n("Unknown") }, { IProblem::Disk, i18n("Disk") }, { IProblem::Preprocessor, i18n("Preprocessor") }, { IProblem::Lexer, i18n("Lexer") }, { IProblem::Parser, i18n("Parser") }, { IProblem::DUChainBuilder, i18n("DuchainBuilder") }, { IProblem::SemanticAnalysis, i18n("Semantic analysis") }, { IProblem::ToDo, i18n("Todo") }, { IProblem::Plugin, i18n("Plugin") } }; int c = sizeof(sources) / sizeof(Source); for (int i = 0; i < c; i++) { m_problem->setSource(sources[i].source); QCOMPARE(sources[i].source, m_problem->source()); QCOMPARE(sources[i].sourceString, m_problem->sourceString()); } } struct Severity { IProblem::Severity severity; QString severityString; }; void TestDetectedProblem::testSeverity() { static Severity severities[] = { { IProblem::Error, i18n("Error") }, { IProblem::Warning, i18n("Warning") }, { IProblem::Hint, i18n("Hint") }, }; int c = sizeof(severities) / sizeof(Severity); for (int i = 0; i < c; i++) { m_problem->setSeverity(severities[i].severity); QCOMPARE(severities[i].severity, m_problem->severity()); QCOMPARE(severities[i].severityString, m_problem->severityString()); } } void TestDetectedProblem::testDescription() { QString TESTDESCRIPTION = QStringLiteral("Just a test description"); m_problem->setDescription(TESTDESCRIPTION); QCOMPARE(TESTDESCRIPTION, m_problem->description()); } void TestDetectedProblem::testExplanation() { QString TESTEXPLANATION = QStringLiteral("Just a test explanation"); m_problem->setExplanation(TESTEXPLANATION); QCOMPARE(TESTEXPLANATION, m_problem->explanation()); } void TestDetectedProblem::testFinalLocation() { QString TESTPATH = QStringLiteral("/just/a/bogus/path/to/a/fake/document"); int TESTLINE = 9001; int TESTCOLUMN = 1337; DocumentRange range; range.document = IndexedString(TESTPATH); range.setBothLines(TESTLINE); range.setBothColumns(TESTCOLUMN); m_problem->setFinalLocation(range); QCOMPARE(TESTPATH, m_problem->finalLocation().document.str()); QCOMPARE(TESTLINE, m_problem->finalLocation().start().line()); QCOMPARE(TESTLINE, m_problem->finalLocation().end().line()); QCOMPARE(TESTCOLUMN, m_problem->finalLocation().start().column()); QCOMPARE(TESTCOLUMN, m_problem->finalLocation().end().column()); } void TestDetectedProblem::testDiagnostics() { QString one = QStringLiteral("One"); QString two = QStringLiteral("Two"); QString three = QStringLiteral("Three"); IProblem::Ptr p1(new DetectedProblem()); IProblem::Ptr p2(new DetectedProblem()); IProblem::Ptr p3(new DetectedProblem()); p1->setDescription(one); p2->setDescription(two); p3->setDescription(three); QCOMPARE(m_problem->diagnostics().size(), 0); m_problem->addDiagnostic(p1); m_problem->addDiagnostic(p2); m_problem->addDiagnostic(p3); QCOMPARE(m_problem->diagnostics().size(), 3); QCOMPARE(m_problem->diagnostics().at(0)->description(), one); QCOMPARE(m_problem->diagnostics().at(1)->description(), two); QCOMPARE(m_problem->diagnostics().at(2)->description(), three); m_problem->clearDiagnostics(); QCOMPARE(0, m_problem->diagnostics().size()); QVector diags; diags.push_back(p3); diags.push_back(p2); diags.push_back(p1); m_problem->setDiagnostics(diags); QCOMPARE(m_problem->diagnostics().size(), 3); QCOMPARE(m_problem->diagnostics().at(2)->description(), one); QCOMPARE(m_problem->diagnostics().at(1)->description(), two); QCOMPARE(m_problem->diagnostics().at(0)->description(), three); m_problem->clearDiagnostics(); QCOMPARE(m_problem->diagnostics().size(), 0); } void TestDetectedProblem::testPluginName() { DetectedProblem p1(QStringLiteral("Plugin1")); DetectedProblem p2(QStringLiteral("Plugin2")); DetectedProblem p3(QStringLiteral("")); DetectedProblem p4; QCOMPARE(p1.source(), IProblem::Plugin); QCOMPARE(p2.source(), IProblem::Plugin); QCOMPARE(p3.source(), IProblem::Plugin); QCOMPARE(p4.source(), IProblem::Unknown); QCOMPARE(p1.sourceString(), QStringLiteral("Plugin1")); QCOMPARE(p2.sourceString(), QStringLiteral("Plugin2")); QCOMPARE(p3.sourceString(), QStringLiteral("")); QCOMPARE(p4.sourceString(), i18n("Unknown")); p4.setSource(IProblem::Plugin); QCOMPARE(p4.source(), IProblem::Plugin); QCOMPARE(p4.sourceString(), i18n("Plugin")); } QTEST_MAIN(TestDetectedProblem) #include "test_detectedproblem.moc" diff --git a/shell/tests/test_filteredproblemstore.cpp b/shell/tests/test_filteredproblemstore.cpp index faf1c2054..e8ac05725 100644 --- a/shell/tests/test_filteredproblemstore.cpp +++ b/shell/tests/test_filteredproblemstore.cpp @@ -1,681 +1,681 @@ /* * 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 +#include #include #include #include #include #include #include #include #include namespace { const int ErrorCount = 1; const int WarningCount = 2; const int HintCount = 3; const int ProblemsCount = ErrorCount + WarningCount + HintCount; const int ErrorFilterProblemCount = ErrorCount; const int WarningFilterProblemCount = ErrorCount + WarningCount; const int HintFilterProblemCount = ErrorCount + WarningCount + HintCount; } using namespace KDevelop; class TestFilteredProblemStore : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testSeverity(); void testSeverities(); void testGrouping(); void testNoGrouping(); void testPathGrouping(); void testSeverityGrouping(); private: // Severity grouping testing bool checkCounts(int error, int warning, int hint); bool checkNodeLabels(); // --------------------------- void generateProblems(); QScopedPointer m_store; QVector m_problems; IProblem::Ptr m_diagnosticTestProblem; }; #define MYCOMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ return false #define MYVERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return false void TestFilteredProblemStore::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_store.reset(new FilteredProblemStore()); generateProblems(); } void TestFilteredProblemStore::cleanupTestCase() { TestCore::shutdown(); } void TestFilteredProblemStore::testSeverity() { QVERIFY(m_store->severity() == IProblem::Hint); QSignalSpy changedSpy(m_store.data(), &FilteredProblemStore::changed); QSignalSpy beginRebuildSpy(m_store.data(), &FilteredProblemStore::beginRebuild); QSignalSpy endRebuildSpy(m_store.data(), &FilteredProblemStore::endRebuild); m_store->setSeverity(IProblem::Error); QVERIFY(m_store->severity() == IProblem::Error); QCOMPARE(changedSpy.count(), 1); QCOMPARE(beginRebuildSpy.count(), 1); QCOMPARE(endRebuildSpy.count(), 1); m_store->setSeverity(IProblem::Hint); } void TestFilteredProblemStore::testSeverities() { QVERIFY(m_store->severities() == (IProblem::Error | IProblem::Warning | IProblem::Hint)); QSignalSpy changedSpy(m_store.data(), &FilteredProblemStore::changed); QSignalSpy beginRebuildSpy(m_store.data(), &FilteredProblemStore::beginRebuild); QSignalSpy endRebuildSpy(m_store.data(), &FilteredProblemStore::endRebuild); m_store->setSeverities(IProblem::Error | IProblem::Hint); QVERIFY(m_store->severities() == (IProblem::Error | IProblem::Hint)); QCOMPARE(changedSpy.count(), 1); QCOMPARE(beginRebuildSpy.count(), 1); QCOMPARE(endRebuildSpy.count(), 1); m_store->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); } void TestFilteredProblemStore::testGrouping() { QVERIFY(m_store->grouping() == NoGrouping); QSignalSpy changedSpy(m_store.data(), &FilteredProblemStore::changed); QSignalSpy beginRebuildSpy(m_store.data(), &FilteredProblemStore::beginRebuild); QSignalSpy endRebuildSpy(m_store.data(), &FilteredProblemStore::endRebuild); m_store->setGrouping(PathGrouping); QVERIFY(m_store->grouping() == PathGrouping); QCOMPARE(changedSpy.count(), 1); QCOMPARE(beginRebuildSpy.count(), 1); QCOMPARE(endRebuildSpy.count(), 1); m_store->setGrouping(NoGrouping); } // Compares the node and it's children to a reference problem and it's diagnostics bool checkDiagnodes(const ProblemStoreNode *node, const IProblem::Ptr &reference) { const ProblemNode *problemNode = dynamic_cast(node); MYVERIFY(problemNode); MYCOMPARE(problemNode->problem()->description(), reference->description()); MYCOMPARE(problemNode->problem()->finalLocation().document.str(), reference->finalLocation().document.str()); MYCOMPARE(problemNode->count(), 1); const IProblem::Ptr diag = reference->diagnostics().at(0); const ProblemNode *diagNode = dynamic_cast(problemNode->child(0)); MYVERIFY(diagNode); MYCOMPARE(diagNode->problem()->description(), diag->description()); MYCOMPARE(diagNode->count(), 1); const IProblem::Ptr diagdiag = diag->diagnostics().at(0); const ProblemNode *diagdiagNode = dynamic_cast(diagNode->child(0)); MYVERIFY(diagdiagNode); MYCOMPARE(diagdiagNode->problem()->description(), diagdiag->description()); MYCOMPARE(diagdiagNode->count(), 0); return true; } void TestFilteredProblemStore::testNoGrouping() { // Add problems int c = 0; foreach (const IProblem::Ptr &p, m_problems) { m_store->addProblem(p); c++; QCOMPARE(m_store->count(), c); } for (int i = 0; i < c; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } // Check if clear works m_store->clear(); QCOMPARE(m_store->count(), 0); // Set problems m_store->setProblems(m_problems); QCOMPARE(m_problems.count(), m_store->count()); for (int i = 0; i < c; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } // Check old style severity filtering // old-style setSeverity // Error filter m_store->setSeverity(IProblem::Error); QCOMPARE(m_store->count(), ErrorFilterProblemCount); for (int i = 0; i < ErrorFilterProblemCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(0)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } // Warning filter m_store->setSeverity(IProblem::Warning); QCOMPARE(m_store->count(), WarningFilterProblemCount); for (int i = 0; i < WarningFilterProblemCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } // Hint filter m_store->setSeverity(IProblem::Hint); QCOMPARE(m_store->count(), HintFilterProblemCount); for (int i = 0; i < HintFilterProblemCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } // Check new severity filtering // Error filter m_store->setSeverities(IProblem::Error); QCOMPARE(m_store->count(), ErrorCount); for (int i = 0; i < ErrorCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } // Warning filter m_store->setSeverities(IProblem::Warning); QCOMPARE(m_store->count(), WarningCount); for (int i = 0; i < WarningCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i+ErrorCount]->description()); } // Hint filter m_store->setSeverities(IProblem::Hint); QCOMPARE(m_store->count(), HintCount); for (int i = 0; i < HintCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i+ErrorCount+WarningCount]->description()); } //Error + Hint filter m_store->setSeverities(IProblem::Error | IProblem::Hint); QCOMPARE(m_store->count(), HintCount + ErrorCount); for (int i = 0; i < ErrorCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } for (int i = ErrorCount; i < ErrorCount+HintCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QCOMPARE(node->problem()->description(), m_problems[i+WarningCount]->description()); } m_store->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); m_store->clear(); // Check if diagnostics are added properly m_store->addProblem(m_diagnosticTestProblem); QCOMPARE(m_store->count(), 1); QVERIFY(checkDiagnodes(m_store->findNode(0), m_diagnosticTestProblem)); } bool checkNodeLabel(const ProblemStoreNode *node, const QString &label) { const LabelNode *parent = dynamic_cast(node); MYVERIFY(parent); MYCOMPARE(parent->label(), label); return true; } bool checkNodeDescription(const ProblemStoreNode *node, const QString &descr) { const ProblemNode *n = dynamic_cast(node); MYVERIFY(n); MYCOMPARE(n->problem()->description(), descr); return true; } void TestFilteredProblemStore::testPathGrouping() { m_store->clear(); // Rebuild the problem list with grouping m_store->setGrouping(PathGrouping); // Add problems foreach (const IProblem::Ptr &p, m_problems) { m_store->addProblem(p); } QCOMPARE(m_store->count(), ProblemsCount); for (int i = 0; i < 3; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } // Now add a new problem IProblem::Ptr p(new DetectedProblem()); p->setDescription(QStringLiteral("PROBLEM4")); p->setFinalLocation(m_problems[2]->finalLocation()); m_store->addProblem(p); QCOMPARE(m_store->count(), ProblemsCount); // Check the first 2 top-nodes for (int i = 0; i < 2; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } // check the last one, and check the added problem is at the right place { const ProblemStoreNode *node = m_store->findNode(2); checkNodeLabel(node, m_problems[2]->finalLocation().document.str()); QCOMPARE(node->count(), 2); checkNodeDescription(node->child(1), p->description()); } m_store->clear(); m_store->setProblems(m_problems); // Check filters // old-style setSeverity // Error filter m_store->setSeverity(IProblem::Error); QCOMPARE(m_store->count(), ErrorFilterProblemCount); { const ProblemStoreNode *node = m_store->findNode(0); checkNodeLabel(node, m_problems[0]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[0]->description()); } // Warning filter m_store->setSeverity(IProblem::Warning); QCOMPARE(m_store->count(), WarningFilterProblemCount); for (int i = 0; i < WarningFilterProblemCount; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } // Hint filter m_store->setSeverity(IProblem::Hint); QCOMPARE(m_store->count(), HintFilterProblemCount); for (int i = 0; i < HintFilterProblemCount; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } // Check new severity filtering // Error filter m_store->setSeverities(IProblem::Error); for (int i = 0; i < ErrorCount; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } // Warning filter m_store->setSeverities(IProblem::Warning); for (int i = 0; i < WarningCount; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i+ErrorCount]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i+ErrorCount]->description()); } // Hint filter m_store->setSeverities(IProblem::Hint); for (int i = 0; i < HintCount; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i+ErrorCount+WarningCount]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i+ErrorCount+WarningCount]->description()); } //Error + Hint filter m_store->setSeverities(IProblem::Error | IProblem::Hint); QCOMPARE(m_store->count(), HintCount + ErrorCount); for (int i = 0; i < ErrorCount; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } for (int i = ErrorCount; i < ErrorCount+HintCount; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i+WarningCount]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i+WarningCount]->description()); } m_store->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); m_store->clear(); // Check if the diagnostics are added properly m_store->addProblem(m_diagnosticTestProblem); QCOMPARE(m_store->count(), 1); const LabelNode *node = dynamic_cast(m_store->findNode(0)); QVERIFY(node); QCOMPARE(node->label(), m_diagnosticTestProblem->finalLocation().document.str()); QVERIFY(checkDiagnodes(node->child(0), m_diagnosticTestProblem)); } void TestFilteredProblemStore::testSeverityGrouping() { m_store->clear(); m_store->setGrouping(SeverityGrouping); QCOMPARE(m_store->count(), 3); const ProblemStoreNode *errorNode = m_store->findNode(0); const ProblemStoreNode *warningNode = m_store->findNode(1); const ProblemStoreNode *hintNode = m_store->findNode(2); // Add problems for (int i=0;iaddProblem(m_problems[i]); int severityType = 0; //error int addedCountOfCurrentSeverityType = i + 1; if (i>=ErrorCount) { severityType = 1; //warning addedCountOfCurrentSeverityType = i - ErrorCount + 1; } if (i>=ErrorCount+WarningCount) { severityType = 2; //hint addedCountOfCurrentSeverityType = i - (ErrorCount + WarningCount) + 1; } QCOMPARE(m_store->findNode(severityType)->count(), addedCountOfCurrentSeverityType); } QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, WarningCount, HintCount)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); checkNodeDescription(warningNode->child(0), m_problems[1]->description()); checkNodeDescription(hintNode->child(0), m_problems[3]->description()); // Clear m_store->clear(); QCOMPARE(m_store->count(), 3); QVERIFY(checkCounts(0,0,0)); // Set problems m_store->setProblems(m_problems); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, WarningCount, HintCount)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); checkNodeDescription(warningNode->child(0), m_problems[1]->description()); checkNodeDescription(hintNode->child(0), m_problems[3]->description()); // Check severity filter // old-style setSeverity // Error filter m_store->setSeverity(IProblem::Error); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, 0, 0)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); // Warning filter m_store->setSeverity(IProblem::Warning); QCOMPARE(m_store->count(), 3); checkNodeLabels(); QVERIFY(checkCounts(ErrorCount, WarningCount, 0)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); checkNodeDescription(warningNode->child(0), m_problems[1]->description()); // Hint filter m_store->setSeverity(IProblem::Hint); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, WarningCount, HintCount)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); checkNodeDescription(warningNode->child(0), m_problems[1]->description()); checkNodeDescription(hintNode->child(0), m_problems[3]->description()); // Check severity filter // Error filter m_store->setSeverities(IProblem::Error); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, 0, 0)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); // Warning filter m_store->setSeverities(IProblem::Warning); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(0, WarningCount, 0)); checkNodeDescription(warningNode->child(0), m_problems[1]->description()); // Hint filter m_store->setSeverities(IProblem::Hint); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(0, 0, HintCount)); checkNodeDescription(hintNode->child(0), m_problems[3]->description()); // Error + Hint filter m_store->setSeverities(IProblem::Error | IProblem::Hint); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, 0, HintCount)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); checkNodeDescription(hintNode->child(0), m_problems[3]->description()); m_store->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); m_store->clear(); // Check if diagnostics are added properly m_store->addProblem(m_diagnosticTestProblem); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(1, 0, 0)); QVERIFY(checkDiagnodes(m_store->findNode(0)->child(0), m_diagnosticTestProblem)); } bool TestFilteredProblemStore::checkCounts(int error, int warning, int hint) { const ProblemStoreNode *errorNode = m_store->findNode(0); const ProblemStoreNode *warningNode = m_store->findNode(1); const ProblemStoreNode *hintNode = m_store->findNode(2); MYVERIFY(errorNode); MYVERIFY(warningNode); MYVERIFY(hintNode); MYCOMPARE(errorNode->count(), error); MYCOMPARE(warningNode->count(), warning); MYCOMPARE(hintNode->count(), hint); return true; } bool TestFilteredProblemStore::checkNodeLabels() { const ProblemStoreNode *errorNode = m_store->findNode(0); const ProblemStoreNode *warningNode = m_store->findNode(1); const ProblemStoreNode *hintNode = m_store->findNode(2); MYCOMPARE(checkNodeLabel(errorNode, i18n("Error")), true); MYCOMPARE(checkNodeLabel(warningNode, i18n("Warning")), true); MYCOMPARE(checkNodeLabel(hintNode, i18n("Hint")), true); return true; } // Generate 3 problems, all with different paths, different severity // Also generates a problem with diagnostics void TestFilteredProblemStore::generateProblems() { IProblem::Ptr p1(new DetectedProblem()); IProblem::Ptr p2(new DetectedProblem()); IProblem::Ptr p3(new DetectedProblem()); IProblem::Ptr p4(new DetectedProblem()); IProblem::Ptr p5(new DetectedProblem()); IProblem::Ptr p6(new DetectedProblem()); DocumentRange r1; r1.document = IndexedString("/just/a/random/path"); p1->setDescription(QStringLiteral("PROBLEM1")); p1->setSeverity(IProblem::Error); p1->setFinalLocation(r1); DocumentRange r2; r2.document = IndexedString("/just/another/path"); p2->setDescription(QStringLiteral("PROBLEM2")); p2->setSeverity(IProblem::Warning); p2->setFinalLocation(r2); DocumentRange r3; r3.document = IndexedString("/just/another/pathy/patha"); p3->setDescription(QStringLiteral("PROBLEM3")); p3->setSeverity(IProblem::Warning); p3->setFinalLocation(r3); DocumentRange r4; r4.document = IndexedString("/yet/another/test/path"); p4->setDescription(QStringLiteral("PROBLEM4")); p4->setSeverity(IProblem::Hint); p4->setFinalLocation(r4); DocumentRange r5; r5.document = IndexedString("/yet/another/pathy/test/path"); p5->setDescription(QStringLiteral("PROBLEM5")); p5->setSeverity(IProblem::Hint); p5->setFinalLocation(r5); DocumentRange r6; r6.document = IndexedString("/yet/another/test/pathy/path"); p6->setDescription(QStringLiteral("PROBLEM6")); p6->setSeverity(IProblem::Hint); p6->setFinalLocation(r6); m_problems.push_back(p1); m_problems.push_back(p2); m_problems.push_back(p3); m_problems.push_back(p4); m_problems.push_back(p5); m_problems.push_back(p6); // Problem for diagnostic testing IProblem::Ptr p(new DetectedProblem()); DocumentRange r; r.document = IndexedString("DIAGTEST"); p->setFinalLocation(r); p->setDescription(QStringLiteral("PROBLEM")); p->setSeverity(IProblem::Error); IProblem::Ptr d(new DetectedProblem()); d->setDescription(QStringLiteral("DIAG")); IProblem::Ptr dd(new DetectedProblem()); dd->setDescription(QStringLiteral("DIAGDIAG")); d->addDiagnostic(dd); p->addDiagnostic(d); m_diagnosticTestProblem = p; } QTEST_MAIN(TestFilteredProblemStore) #include "test_filteredproblemstore.moc" diff --git a/shell/tests/test_plugincontroller.cpp b/shell/tests/test_plugincontroller.cpp index ffedc86cd..e969b2ef9 100644 --- a/shell/tests/test_plugincontroller.cpp +++ b/shell/tests/test_plugincontroller.cpp @@ -1,110 +1,108 @@ /*************************************************************************** * 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 "test_plugincontroller.h" #include "nonguiinterfaceplugin.h" #include "testfilepaths.h" -#include -#include #include #include #include #include #include "../core.h" #include "../plugincontroller.h" using namespace KDevelop; void TestPluginController::initTestCase() { qApp->addLibraryPath(QStringLiteral(TEST_PLUGIN_DIR)); AutoTestShell::init({QStringLiteral("kdevnonguiinterface")}); TestCore::initialize( Core::NoUi ); m_pluginCtrl = Core::self()->pluginControllerInternal(); } void TestPluginController::cleanupTestCase() { TestCore::shutdown(); } void TestPluginController::init() { } void TestPluginController::cleanup() { } void TestPluginController::pluginInfo() { IPlugin* plugin = m_pluginCtrl->loadPlugin( QStringLiteral("kdevnonguiinterface") ); QVERIFY(plugin); KPluginMetaData pluginInfo = m_pluginCtrl->pluginInfo(plugin); QCOMPARE(pluginInfo.pluginId(), QStringLiteral("kdevnonguiinterface")); } void TestPluginController::loadUnloadPlugin() { QSignalSpy spy(m_pluginCtrl, SIGNAL(pluginLoaded(KDevelop::IPlugin*))); QSignalSpy spyloading(m_pluginCtrl, SIGNAL(loadingPlugin(QString))); QVERIFY(spy.isValid()); QVERIFY(spyloading.isValid()); m_pluginCtrl->loadPlugin( QStringLiteral( "kdevnonguiinterface" ) ); QVERIFY( m_pluginCtrl->plugin( QStringLiteral( "kdevnonguiinterface" ) ) ); QCOMPARE(spy.size(), 1); QCOMPARE(spyloading.size(), 1); QList args = spyloading.takeFirst(); QCOMPARE( args.at(0).toString(), QStringLiteral( "kdevnonguiinterface" ) ); QSignalSpy spy2(m_pluginCtrl, SIGNAL(pluginUnloaded(KDevelop::IPlugin*)) ); QSignalSpy spy3(m_pluginCtrl, SIGNAL(unloadingPlugin(KDevelop::IPlugin*)) ); QVERIFY(spy2.isValid()); QVERIFY(spy3.isValid()); m_pluginCtrl->unloadPlugin( QStringLiteral("kdevnonguiinterface") ); QVERIFY( !m_pluginCtrl->plugin( QStringLiteral( "kdevnonguiinterface" ) ) ); QCOMPARE(spy2.size(), 1); QCOMPARE(spy3.size(), 1); } void TestPluginController::loadFromExtension() { IPlugin* plugin = m_pluginCtrl->pluginForExtension( QStringLiteral("org.kdevelop.ITestNonGuiInterface") ); QVERIFY( plugin ); QVERIFY( plugin->inherits("org.kdevelop.ITestNonGuiInterface") ); QVERIFY( plugin->extension()); } void TestPluginController::benchPluginForExtension() { QBENCHMARK { IPlugin* plugin = m_pluginCtrl->pluginForExtension( QStringLiteral("org.kdevelop.ITestNonGuiInterface") ); QVERIFY( plugin ); } } QTEST_MAIN( TestPluginController) diff --git a/shell/tests/test_problemmodel.cpp b/shell/tests/test_problemmodel.cpp index 70dc1c1ae..c1eaad91d 100644 --- a/shell/tests/test_problemmodel.cpp +++ b/shell/tests/test_problemmodel.cpp @@ -1,515 +1,514 @@ /* * 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 -#include +#include #include #include #include #include #include #include #define MYCOMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ return false #define MYVERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return false using namespace KDevelop; class TestProblemModel : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testNoGrouping(); void testPathGrouping(); void testSeverityGrouping(); private: void generateProblems(); bool checkIsSame(int row, const QModelIndex &parent, const IProblem::Ptr &problem); bool checkDiagnostics(int row, const QModelIndex &parent); bool checkDisplay(int row, const QModelIndex &parent, const IProblem::Ptr &problem); bool checkLabel(int row, const QModelIndex &parent, const QString &label); bool checkPathGroup(int row, const IProblem::Ptr &problem); bool checkSeverityGroup(int row, const IProblem::Ptr &problem); QScopedPointer m_model; QVector m_problems; IProblem::Ptr m_diagnosticTestProblem; }; void TestProblemModel::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_model.reset(new ProblemModel(nullptr)); m_model->setScope(BypassScopeFilter); generateProblems(); } void TestProblemModel::cleanupTestCase() { TestCore::shutdown(); } void TestProblemModel::testNoGrouping() { m_model->setGrouping(NoGrouping); m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 0); // Check if setting the problems works m_model->setProblems(m_problems); QCOMPARE(m_model->rowCount(), 3); for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkIsSame(i, QModelIndex(), m_problems[i])); } // Check if displaying various data parts works QVERIFY(checkDisplay(0, QModelIndex(), m_problems[0])); // Check if clearing the problems works m_model->clearProblems(); QCOMPARE(m_model->rowCount(), 0); // Check if adding the problems works int c = 0; foreach (const IProblem::Ptr &p, m_problems) { m_model->addProblem(p); c++; QCOMPARE(m_model->rowCount(), c); } for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkIsSame(i, QModelIndex(), m_problems[i])); } // Check if filtering works // old-style setSeverity // Error filter m_model->setSeverity(IProblem::Error); QCOMPARE(m_model->rowCount(), 1); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); // Warning filter m_model->setSeverity(IProblem::Warning); QCOMPARE(m_model->rowCount(), 2); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); QVERIFY(checkIsSame(1, QModelIndex(), m_problems[1])); // Hint filter m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); QVERIFY(checkIsSame(1, QModelIndex(), m_problems[1])); QVERIFY(checkIsSame(2, QModelIndex(), m_problems[2])); // Check if filtering works // new style // Error filter m_model->setSeverities(IProblem::Error); QCOMPARE(m_model->rowCount(), 1); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); // Warning filter m_model->setSeverities(IProblem::Warning); QCOMPARE(m_model->rowCount(), 1); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[1])); // Hint filter m_model->setSeverities(IProblem::Hint); QCOMPARE(m_model->rowCount(), 1); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[2])); // Error + Hint filter m_model->setSeverities(IProblem::Error | IProblem::Hint); QCOMPARE(m_model->rowCount(), 2); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); QVERIFY(checkIsSame(1, QModelIndex(), m_problems[2])); m_model->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); // Check if diagnostics are added properly m_model->clearProblems(); m_model->addProblem(m_diagnosticTestProblem); QVERIFY(checkDiagnostics(0, QModelIndex())); m_model->clearProblems(); } void TestProblemModel::testPathGrouping() { m_model->setGrouping(PathGrouping); m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 0); // Check if setting problems works m_model->setProblems(m_problems); QCOMPARE(m_model->rowCount(), 3); for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkLabel(i, QModelIndex(), m_problems[i]->finalLocation().document.str())); QModelIndex idx = m_model->index(i, 0); QVERIFY(idx.isValid()); QVERIFY(checkIsSame(0, idx, m_problems[i])); } // Check if displaying various data parts works { QModelIndex idx = m_model->index(0, 0); QVERIFY(idx.isValid()); QVERIFY(checkDisplay(0, idx, m_problems[0])); } // Check if clearing works m_model->clearProblems(); QCOMPARE(m_model->rowCount(), 0); // Check if add problems works int c = 0; foreach (const IProblem::Ptr &p, m_problems) { m_model->addProblem(p); c++; QCOMPARE(m_model->rowCount(), c); } for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkLabel(i, QModelIndex(), m_problems[i]->finalLocation().document.str())); QModelIndex idx = m_model->index(i, 0); QVERIFY(idx.isValid()); QVERIFY(checkIsSame(0, idx, m_problems[i])); } // Check if filtering works // old-style setSeverity // Error filtering m_model->setSeverity(IProblem::Error); QCOMPARE(m_model->rowCount(), 1); QVERIFY(checkPathGroup(0, m_problems[0])); // Warning filtering m_model->setSeverity(IProblem::Warning); QCOMPARE(m_model->rowCount(), 2); QVERIFY(checkPathGroup(0, m_problems[0])); QVERIFY(checkPathGroup(1, m_problems[1])); // Hint filtering m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); QVERIFY(checkPathGroup(0, m_problems[0])); QVERIFY(checkPathGroup(1, m_problems[1])); QVERIFY(checkPathGroup(2, m_problems[2])); // Check if filtering works // new style // Error filtering m_model->setSeverities(IProblem::Error); QCOMPARE(m_model->rowCount(), 1); QVERIFY(checkPathGroup(0, m_problems[0])); // Warning filtering m_model->setSeverities(IProblem::Warning); QCOMPARE(m_model->rowCount(), 1); QVERIFY(checkPathGroup(0, m_problems[1])); // Hint filtering m_model->setSeverities(IProblem::Hint); QCOMPARE(m_model->rowCount(), 1);; QVERIFY(checkPathGroup(0, m_problems[2])); // Error + Hint filtering m_model->setSeverities(IProblem::Error | IProblem::Hint); QCOMPARE(m_model->rowCount(), 2); QVERIFY(checkPathGroup(0, m_problems[0])); QVERIFY(checkPathGroup(1, m_problems[2])); m_model->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); // Check if diagnostics get to the right place m_model->clearProblems(); m_model->addProblem(m_diagnosticTestProblem); { QModelIndex parent = m_model->index(0, 0); QVERIFY(parent.isValid()); checkDiagnostics(0, parent); } m_model->clearProblems(); } void TestProblemModel::testSeverityGrouping() { m_model->setGrouping(SeverityGrouping); m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); // Check if setting problems works m_model->setProblems(m_problems); QCOMPARE(m_model->rowCount(), 3); for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkSeverityGroup(i, m_problems[i])); } // Check if displaying works for (int i = 0; i < m_model->rowCount(); i++) { QModelIndex parent = m_model->index(i, 0); QVERIFY(parent.isValid()); QVERIFY(checkDisplay(0, parent, m_problems[i])); } // Check if clearing works m_model->clearProblems(); QCOMPARE(m_model->rowCount(), 3); // Check if adding problems works int c = 0; foreach (const IProblem::Ptr &p, m_problems) { m_model->addProblem(p); QVERIFY(checkSeverityGroup(c, m_problems[c])); c++; } // Check if filtering works // old-style setSeverity // Error filtering m_model->setSeverity(IProblem::Error); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(0, m_problems[0]); // Warning filtering m_model->setSeverity(IProblem::Warning); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(0, m_problems[0]); checkSeverityGroup(1, m_problems[1]); // Hint filtering m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(0, m_problems[0]); checkSeverityGroup(1, m_problems[1]); checkSeverityGroup(2, m_problems[2]); // Check if filtering works // Error filtering m_model->setSeverities(IProblem::Error); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(0, m_problems[0]); // Warning filtering m_model->setSeverities(IProblem::Warning); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(1, m_problems[1]); // Hint filtering m_model->setSeverities(IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(2, m_problems[2]); // Error + Hint filtering m_model->setSeverities(IProblem::Error | IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(0, m_problems[0]); checkSeverityGroup(2, m_problems[2]); m_model->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); // Check if diagnostics get to the right place m_model->clearProblems(); m_model->addProblem(m_diagnosticTestProblem); { QModelIndex parent = m_model->index(0, 0); QVERIFY(parent.isValid()); checkDiagnostics(0, parent); } m_model->clearProblems(); } // Generate 3 problems, all with different paths, different severity // Also generates a problem with diagnostics void TestProblemModel::generateProblems() { IProblem::Ptr p1(new DetectedProblem()); IProblem::Ptr p2(new DetectedProblem()); IProblem::Ptr p3(new DetectedProblem()); DocumentRange r1; r1.document = IndexedString("/just/a/random/path"); p1->setDescription(QStringLiteral("PROBLEM1")); p1->setSeverity(IProblem::Error); p1->setFinalLocation(r1); DocumentRange r2; r2.document = IndexedString("/just/another/path"); p2->setDescription(QStringLiteral("PROBLEM2")); p2->setSeverity(IProblem::Warning); p2->setFinalLocation(r2); DocumentRange r3; r3.document = IndexedString("/yet/another/test/path"); p2->setDescription(QStringLiteral("PROBLEM3")); p3->setSeverity(IProblem::Hint); p3->setFinalLocation(r3); m_problems.push_back(p1); m_problems.push_back(p2); m_problems.push_back(p3); // Problem for diagnostic testing IProblem::Ptr p(new DetectedProblem()); DocumentRange r; r.document = IndexedString("DIAGTEST"); p->setFinalLocation(r); p->setDescription(QStringLiteral("PROBLEM")); p->setSeverity(IProblem::Error); IProblem::Ptr d(new DetectedProblem()); d->setDescription(QStringLiteral("DIAG")); IProblem::Ptr dd(new DetectedProblem()); dd->setDescription(QStringLiteral("DIAGDIAG")); d->addDiagnostic(dd); p->addDiagnostic(d); m_diagnosticTestProblem = p; } bool TestProblemModel::checkIsSame(int row, const QModelIndex &parent, const IProblem::Ptr &problem) { QModelIndex idx; idx = m_model->index(row, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->description()); return true; } bool TestProblemModel::checkDiagnostics(int row, const QModelIndex &parent) { MYCOMPARE(m_model->rowCount(parent), 1); QModelIndex idx; idx = m_model->index(row, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), m_diagnosticTestProblem->description()); QModelIndex diagidx; IProblem::Ptr diag = m_diagnosticTestProblem->diagnostics().at(0); diagidx = m_model->index(0, 0, idx); MYVERIFY(diagidx.isValid()); MYCOMPARE(m_model->data(diagidx).toString(), diag->description()); QModelIndex diagdiagidx; IProblem::Ptr diagdiag = diag->diagnostics().at(0); diagdiagidx = m_model->index(0, 0, diagidx); MYVERIFY(diagdiagidx.isValid()); MYCOMPARE(m_model->data(diagdiagidx).toString(), diagdiag->description()); return true; } bool TestProblemModel::checkDisplay(int row, const QModelIndex &parent, const IProblem::Ptr &problem) { QModelIndex idx; idx = m_model->index(row, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->description()); idx = m_model->index(row, 1, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->sourceString()); idx = m_model->index(row, 2, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->finalLocation().document.str()); idx = m_model->index(row, 3, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), QString::number(problem->finalLocation().start().line() + 1)); idx = m_model->index(row, 4, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), QString::number(problem->finalLocation().start().column() + 1)); return true; } bool TestProblemModel::checkLabel(int row, const QModelIndex &parent, const QString &label) { QModelIndex idx = m_model->index(row, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), label); return true; } bool TestProblemModel::checkPathGroup(int row, const IProblem::Ptr &problem) { QModelIndex parent = m_model->index(row, 0); MYVERIFY(parent.isValid()); MYCOMPARE(m_model->data(parent).toString(), problem->finalLocation().document.str()); QModelIndex idx = m_model->index(0, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->description()); return true; } bool TestProblemModel::checkSeverityGroup(int row, const IProblem::Ptr &problem) { QModelIndex parent = m_model->index(row, 0); MYVERIFY(parent.isValid()); MYCOMPARE(m_model->data(parent).toString(), problem->severityString()); QModelIndex idx = m_model->index(0, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->description()); return true; } QTEST_MAIN(TestProblemModel) #include "test_problemmodel.moc" diff --git a/shell/tests/test_problemmodelset.cpp b/shell/tests/test_problemmodelset.cpp index 83d031045..33ea45fa8 100644 --- a/shell/tests/test_problemmodelset.cpp +++ b/shell/tests/test_problemmodelset.cpp @@ -1,159 +1,159 @@ /* * 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 +#include #include #include #include "shell/problemmodelset.h" #include "shell/problemmodel.h" #include "tests/testcore.h" #include "tests/autotestshell.h" using namespace KDevelop; namespace { const int testModelCount = 3; const QString testModelIds[] = { QStringLiteral("MODEL1_ID"), QStringLiteral("MODEL2_ID"), QStringLiteral("MODEL3_ID") }; const QString testModelNames[] = { QStringLiteral("MODEL1"), QStringLiteral("MODEL2"), QStringLiteral("MODEL3") }; struct TestModelData { QString id; QString name; ProblemModel* model; }; } class TestProblemModelSet : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testAddModel(); void testFindModel(); void testModelListing(); void testRemoveModel(); private: QScopedPointer m_set; QVector m_testData; }; void TestProblemModelSet::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_set.reset(new ProblemModelSet()); for (int i = 0; i < testModelCount; i++) { m_testData.push_back(TestModelData({testModelIds[i], testModelNames[i], new ProblemModel(nullptr)})); } } void TestProblemModelSet::cleanupTestCase() { for (int i = 0; i < m_testData.size(); i++) { delete m_testData[i].model; } m_testData.clear(); TestCore::shutdown(); } void TestProblemModelSet::testAddModel() { QSignalSpy spy(m_set.data(), &ProblemModelSet::added); int c = 0; for (int i = 0; i < testModelCount; i++) { m_set->addModel(m_testData[i].id, m_testData[i].name, m_testData[i].model); c++; QCOMPARE(spy.count(), c); QCOMPARE(m_set->models().count(), c); } } void TestProblemModelSet::testFindModel() { ProblemModel *model = nullptr; for (int i = 0; i < testModelCount; i++) { model = m_set->findModel(m_testData[i].id); QVERIFY(model); QVERIFY(model == m_testData[i].model); } model = m_set->findModel(QStringLiteral("")); QVERIFY(model == nullptr); model = m_set->findModel(QStringLiteral("RandomName")); QVERIFY(model == nullptr); } void TestProblemModelSet::testModelListing() { QVector models = m_set->models(); QCOMPARE(models.size(), testModelCount); for (int i = 0; i < testModelCount; i++) { QCOMPARE(models[i].name, m_testData[i].name); QCOMPARE(models[i].model, m_testData[i].model); } } void TestProblemModelSet::testRemoveModel() { QSignalSpy spy(m_set.data(), &ProblemModelSet::removed); int c = 0; foreach (const TestModelData &data, m_testData) { m_set->removeModel(data.id); c++; QCOMPARE(spy.count(), c); QVERIFY(testModelCount >= c); QCOMPARE(m_set->models().count(), testModelCount - c); } } QTEST_MAIN(TestProblemModelSet) #include "test_problemmodelset.moc" diff --git a/shell/tests/test_problemstore.cpp b/shell/tests/test_problemstore.cpp index 017da78ff..266b8cd27 100644 --- a/shell/tests/test_problemstore.cpp +++ b/shell/tests/test_problemstore.cpp @@ -1,158 +1,158 @@ /* * 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 +#include #include #include #include #include #include #include #include using namespace KDevelop; class TestProblemStore : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testAddProblems(); void testClearProblems(); void testSetProblems(); void testFindNode(); void testSeverity(); void testSeverities(); void testScope(); private: void generateProblems(); QScopedPointer m_store; QVector m_problems; }; void TestProblemStore::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_store.reset(new ProblemStore()); m_store->setScope(CurrentDocument); QVERIFY(m_store->scope() == CurrentDocument); generateProblems(); } void TestProblemStore::cleanupTestCase() { TestCore::shutdown(); } void TestProblemStore::testAddProblems() { QCOMPARE(m_store->count(), 0); int c = 0; foreach (const IProblem::Ptr &problem, m_problems) { m_store->addProblem(problem); c++; QCOMPARE(m_store->count(), c); } } void TestProblemStore::testClearProblems() { m_store->clear(); QCOMPARE(m_store->count(), 0); } void TestProblemStore::testSetProblems() { m_store->setProblems(m_problems); QCOMPARE(m_store->count(), m_problems.count()); } void TestProblemStore::testFindNode() { for (int i = 0; i < m_problems.count(); i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node); QVERIFY(node->problem().data()); QCOMPARE(node->problem().data()->description(), m_problems[i]->description()); } } void TestProblemStore::testSeverity() { IProblem::Severity severity = IProblem::Error; QVERIFY(severity != m_store->severity()); QSignalSpy spy(m_store.data(), &ProblemStore::changed); m_store->setSeverity(severity); QVERIFY(m_store->severity() == severity); QCOMPARE(spy.count(), 1); } void TestProblemStore::testSeverities() { IProblem::Severities severities = IProblem::Error | IProblem::Hint; QVERIFY(severities != m_store->severities()); QSignalSpy spy(m_store.data(), &ProblemStore::changed); m_store->setSeverities(severities); QVERIFY(m_store->severities() == severities); QCOMPARE(spy.count(), 1); } void TestProblemStore::testScope() { QSignalSpy spy(m_store.data(), &ProblemStore::changed); ProblemScope scope = AllProjects; m_store->setScope(scope); QVERIFY(m_store->scope() == scope); QCOMPARE(spy.count(), 1); } void TestProblemStore::generateProblems() { for (int i = 0; i < 5; i++) { IProblem::Ptr problem(new DetectedProblem()); problem->setDescription(QStringLiteral("PROBLEM") + QString::number(i + 1)); m_problems.push_back(problem); } } QTEST_MAIN(TestProblemStore) #include "test_problemstore.moc" diff --git a/shell/tests/test_problemstorenode.cpp b/shell/tests/test_problemstorenode.cpp index bc6b4303c..ce96ae60a 100644 --- a/shell/tests/test_problemstorenode.cpp +++ b/shell/tests/test_problemstorenode.cpp @@ -1,129 +1,129 @@ /* * 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 +#include #include #include using namespace KDevelop; class TestProblemStoreNode : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testRootNode(); void testChildren(); void testLabelNode(); void testProblemNode(); private: QScopedPointer m_root; }; void TestProblemStoreNode::initTestCase() { m_root.reset(new ProblemStoreNode()); } void TestProblemStoreNode::cleanupTestCase() { } void TestProblemStoreNode::testRootNode() { QVERIFY(m_root->isRoot()); QVERIFY(m_root->parent() == nullptr); QCOMPARE(m_root->index(), -1); QCOMPARE(m_root->count(), 0); QCOMPARE(m_root->children().count(), 0); QVERIFY(m_root->label().isEmpty()); QVERIFY(m_root->problem().constData() == nullptr); } void TestProblemStoreNode::testChildren() { QVector nodes; nodes.push_back(new ProblemStoreNode()); nodes.push_back(new ProblemStoreNode()); nodes.push_back(new ProblemStoreNode()); int c = 0; foreach (ProblemStoreNode *node, nodes) { m_root->addChild(node); c++; QCOMPARE(m_root->count(), c); QCOMPARE(m_root->children().count(), c); QVERIFY(node->parent() == m_root.data()); QVERIFY(!node->isRoot()); } for (int i = 0; i < c; i++) { ProblemStoreNode *node = m_root->child(i); QVERIFY(node); QVERIFY(node == nodes[i]); QCOMPARE(node->index(), i); } nodes.clear(); m_root->clear(); QCOMPARE(m_root->count(), 0); } void TestProblemStoreNode::testLabelNode() { QString s1 = QStringLiteral("TEST1"); QString s2 = QStringLiteral("TEST2"); LabelNode *node = new LabelNode(nullptr, s1); QCOMPARE(node->label(), s1); node->setLabel(s2); QCOMPARE(node->label(), s2); } void TestProblemStoreNode::testProblemNode() { IProblem::Ptr p1(new DetectedProblem()); IProblem::Ptr p2(new DetectedProblem()); QString s1 = QStringLiteral("PROBLEM1"); QString s2 = QStringLiteral("PROBLEM2"); p1->setDescription(s1); p2->setDescription(s2); QScopedPointer node; node.reset(new ProblemNode(nullptr, p1)); QCOMPARE(node->problem()->description(), p1->description()); node->setProblem(p2); QCOMPARE(node->problem()->description(), p2->description()); } QTEST_MAIN(TestProblemStoreNode) #include "test_problemstorenode.moc" diff --git a/shell/tests/test_shellbuddy.cpp b/shell/tests/test_shellbuddy.cpp index 8298ec460..a0b466b8f 100644 --- a/shell/tests/test_shellbuddy.cpp +++ b/shell/tests/test_shellbuddy.cpp @@ -1,424 +1,422 @@ /*************************************************************************** * 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 #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 = 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 fbda04c44..5b4c63847 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 #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*)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*)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/textdocument.cpp b/shell/textdocument.cpp index 9cf1b32b8..41835c984 100644 --- a/shell/textdocument.cpp +++ b/shell/textdocument.cpp @@ -1,795 +1,794 @@ /*************************************************************************** * 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 "ktexteditorpluginintegration.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 { explicit TextDocumentPrivate(TextDocument *textDocument) : document(nullptr), state(IDocument::Clean), encoding(), q(textDocument) , m_loaded(false), m_addedContextMenu(nullptr) { } ~TextDocumentPrivate() { delete m_addedContextMenu; 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 = 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 { explicit 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 = 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, 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, Core::self()->uiControllerInternal()->defaultMainWindow()->kateWrapper()->interface()); // 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 nullptr; } // KDevelop::IDocument implementation void TextDocument::reload() { if (!d->document) return; 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/textdocument.h b/shell/textdocument.h index 499b2dd12..e4888aa87 100644 --- a/shell/textdocument.h +++ b/shell/textdocument.h @@ -1,128 +1,127 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_TEXTDOCUMENT_H #define KDEVPLATFORM_TEXTDOCUMENT_H #include -#include #include #include "partdocument.h" #include "shellexport.h" class QMenu; namespace KTextEditor { class View; } namespace KDevelop { /** Text document which represents KTextEditor documents. Usually Kate documents are represented by this class but TextDocument is not limited to Kate. Each conforming text editor will work. */ class KDEVPLATFORMSHELL_EXPORT TextDocument: public PartDocument { Q_OBJECT public: TextDocument(const QUrl &url, ICore*, const QString& encoding ); ~TextDocument() override; QWidget *createViewWidget(QWidget *parent = nullptr) override; KParts::Part *partForView(QWidget *view) const override; bool close(DocumentSaveMode mode = Default) override; bool save(DocumentSaveMode mode = Default) override; DocumentState state() const override; KTextEditor::Cursor cursorPosition() const override; void setCursorPosition(const KTextEditor::Cursor &cursor) override; KTextEditor::Range textSelection() const override; void setTextSelection(const KTextEditor::Range &range) override; QString text(const KTextEditor::Range &range) const override; QString textLine() const override; QString textWord() const override; bool isTextDocument() const override; KTextEditor::Document* textDocument() const override; QString documentType() const override; QIcon defaultIcon() const override; KTextEditor::View* activeTextView() const override; public Q_SLOTS: void reload() override; protected: Sublime::View *newView(Sublime::Document *doc) override; private: Q_PRIVATE_SLOT(d, void saveSessionConfig()); Q_PRIVATE_SLOT(d, void modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)); void newDocumentStatus(KTextEditor::Document*); void populateContextMenu(KTextEditor::View*, QMenu*); void textChanged(KTextEditor::Document*); void documentUrlChanged(KTextEditor::Document*); void slotDocumentLoaded(); void documentSaved(KTextEditor::Document*,bool); void repositoryCheckFinished(bool); struct TextDocumentPrivate * const d; friend struct TextDocumentPrivate; }; class KDEVPLATFORMSHELL_EXPORT TextView : public Sublime::View { Q_OBJECT public: explicit TextView(TextDocument* doc); ~TextView() override; QWidget *createWidget(QWidget *parent = nullptr) override; KTextEditor::View *textView() const; QString viewStatus() const override; QString viewState() const override; void setState(const QString& state) override; void setInitialRange(const KTextEditor::Range& range); KTextEditor::Range initialRange() const; private: void sendStatusChanged(); struct TextViewPrivate* const d; }; } #endif diff --git a/shell/workingsets/workingset.cpp b/shell/workingsets/workingset.cpp index eab936a59..64c73ddbe 100644 --- a/shell/workingsets/workingset.cpp +++ b/shell/workingsets/workingset.cpp @@ -1,565 +1,564 @@ /* 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(WorkingSetIconParameters(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 { explicit 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(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() ? 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/workingsetfilelabel.h b/shell/workingsets/workingsetfilelabel.h index 513acfd0c..473e6f7a0 100644 --- a/shell/workingsets/workingsetfilelabel.h +++ b/shell/workingsets/workingsetfilelabel.h @@ -1,49 +1,47 @@ /* 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. */ #ifndef KDEVPLATFORM_WORKINGSETFILELABEL_H #define KDEVPLATFORM_WORKINGSETFILELABEL_H #include -class QMouseEvent; - namespace KDevelop { class WorkingSetFileLabel : public QLabel { Q_OBJECT public: WorkingSetFileLabel(); void mouseReleaseEvent(QMouseEvent* ev) override; void setIsActiveFile(bool active); bool isActive() const; void emitClicked(); Q_SIGNALS: void clicked(); private: bool m_isActive; }; } #endif // KDEVPLATFORM_WORKINGSETFILELABEL_H diff --git a/shell/workingsets/workingsettoolbutton.cpp b/shell/workingsets/workingsettoolbutton.cpp index 79f9fa683..ddb9aeb2f 100644 --- a/shell/workingsets/workingsettoolbutton.cpp +++ b/shell/workingsets/workingsettoolbutton.cpp @@ -1,175 +1,174 @@ /* 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 "workingsettoolbutton.h" #include -#include #include #include #include "core.h" #include "mainwindow.h" #include "workingset.h" #include "workingsetcontroller.h" #include "workingsethelpers.h" #include "documentcontroller.h" #include #include #include using namespace KDevelop; WorkingSetToolButton::WorkingSetToolButton(QWidget* parent, WorkingSet* set) : QToolButton(parent), m_set(set), m_toolTipEnabled(true) { setFocusPolicy(Qt::NoFocus); setWorkingSet(set); setAutoRaise(true); connect(this, &WorkingSetToolButton::clicked, this, &WorkingSetToolButton::buttonTriggered); } WorkingSet* WorkingSetToolButton::workingSet() const { return m_set; } void WorkingSetToolButton::setWorkingSet(WorkingSet* set) { m_set = set; setIcon(set ? set->icon() : QIcon()); } void WorkingSetToolButton::contextMenuEvent(QContextMenuEvent* ev) { showTooltip(); ev->accept(); } void WorkingSetToolButton::intersectSet() { Q_ASSERT(m_set); m_set->setPersistent(true); filterViews(Core::self()->workingSetControllerInternal()->getWorkingSet(mainWindow()->area()->workingSet())->fileList().toSet() & m_set->fileList().toSet()); } void WorkingSetToolButton::subtractSet() { Q_ASSERT(m_set); m_set->setPersistent(true); filterViews(Core::self()->workingSetControllerInternal()->getWorkingSet(mainWindow()->area()->workingSet())->fileList().toSet() - m_set->fileList().toSet()); } void WorkingSetToolButton::mergeSet() { Q_ASSERT(m_set); QSet< QString > loadFiles = m_set->fileList().toSet() - Core::self()->workingSetControllerInternal()->getWorkingSet(mainWindow()->area()->workingSet())->fileList().toSet(); foreach(const QString& file, loadFiles) Core::self()->documentController()->openDocument(QUrl::fromUserInput(file)); } void WorkingSetToolButton::duplicateSet() { Q_ASSERT(m_set); if(!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(mainWindow(), KDevelop::IDocument::Default, true)) return; WorkingSet* set = Core::self()->workingSetControllerInternal()->newWorkingSet(QStringLiteral("clone")); set->setPersistent(true); set->saveFromArea(mainWindow()->area(), mainWindow()->area()->rootIndex()); mainWindow()->area()->setWorkingSet(set->id()); } void WorkingSetToolButton::loadSet() { Q_ASSERT(m_set); m_set->setPersistent(true); if(!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(mainWindow(), KDevelop::IDocument::Default, true)) return; mainWindow()->area()->setWorkingSet(QString(m_set->id())); } void WorkingSetToolButton::closeSet(bool ask) { Q_ASSERT(m_set); m_set->setPersistent(true); m_set->saveFromArea(mainWindow()->area(), mainWindow()->area()->rootIndex()); if(ask && !Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(mainWindow(), KDevelop::IDocument::Default, true)) return; mainWindow()->area()->setWorkingSet(QString()); } bool WorkingSetToolButton::event(QEvent* e) { if(m_toolTipEnabled && e->type() == QEvent::ToolTip) { showTooltip(); e->accept(); return true; } return QToolButton::event(e); } void WorkingSetToolButton::showTooltip() { Q_ASSERT(m_set); static WorkingSetToolButton* oldTooltipButton; WorkingSetController* controller = Core::self()->workingSetControllerInternal(); if(controller->tooltip() && oldTooltipButton == this) return; oldTooltipButton = this; controller->showToolTip(m_set, QCursor::pos() + QPoint(10, 20)); QRect extended(parentWidget()->mapToGlobal(geometry().topLeft()), parentWidget()->mapToGlobal(geometry().bottomRight())); controller->tooltip()->setHandleRect(extended); } void WorkingSetToolButton::buttonTriggered() { Q_ASSERT(m_set); if(mainWindow()->area()->workingSet() == m_set->id()) { showTooltip(); }else{ //Only close the working-set if the file was saved before if(!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(mainWindow(), KDevelop::IDocument::Default, true)) return; m_set->setPersistent(true); mainWindow()->area()->setWorkingSet(m_set->id()); } } diff --git a/sublime/aggregatemodel.h b/sublime/aggregatemodel.h index fae9dbdc8..cc5d4edce 100644 --- a/sublime/aggregatemodel.h +++ b/sublime/aggregatemodel.h @@ -1,99 +1,97 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMEAGGREGATEMODEL_H #define KDEVPLATFORM_SUBLIMEAGGREGATEMODEL_H -#include -#include #include #include "sublimeexport.h" class QStandardItemModel; namespace Sublime { /** @short A model to combine several QStandardItemModel's into one. Combine standard models into the aggregate model to display them in the one view. Each new model gets its own parent item to differentiate items between different models, for example: Tea Model: @code - Black - Green - White @endcode Coffee Model: @code - Arabica - Robusta @endcode When aggregated with @code AggregateModel model; model->addModel("Tea", teaModel); model->addModel("Coffee", coffeeModel); @endcode they will look as: @code - Tea - Black - Green - White - Coffee - Arabica - Robusta @endcode @note It is impossible to aggregate any model, aggregation works only for standard models. @note Currently aggregate model displays only 1 column. */ class KDEVPLATFORMSUBLIME_EXPORT AggregateModel: public QAbstractItemModel { Q_OBJECT public: explicit AggregateModel(QObject *parent = nullptr); ~AggregateModel() override; /**Adds the model and creates a parent item with given @p name in the aggregated model.*/ void addModel(const QString &name, QStandardItemModel *model); /**Removes the model from aggregation.*/ void removeModel(QStandardItemModel *model); //reimplemented methods from QAbstractItemModel Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex parent(const QModelIndex &index) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; private: struct AggregateModelPrivate *d; }; } #endif diff --git a/sublime/areaindex.h b/sublime/areaindex.h index 47582bb03..7b0ff6acd 100644 --- a/sublime/areaindex.h +++ b/sublime/areaindex.h @@ -1,196 +1,194 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMEAREAINDEX_H #define KDEVPLATFORM_SUBLIMEAREAINDEX_H #include #include -#include - #include "sublimeexport.h" namespace Sublime { class View; /** @short Index denotes the position of the view in the split area. B-Tree alike structure is used to represent an area with split views. Area has a root index which can either contain one view or contain two child nodes (@p first and @p second). In the later case area is considered to be split into two parts. Each of those parts can in turn contain a view or be split (with first/second children). When a view at given index is split, then its index becomes an index of the splitter and the original view goes into the @p first part of the splitter. The new view goes as @p second part. For example, consider an area which was split once horizontally and then the second part of it was split vertically: @code 1. initial state: one view in the area |----------------| | | | 1 | | | |----------------| Indices: root_index (view 1) 2. the view is split horizontally |----------------| | | | | 1 | 2 | | | | |----------------| Indices: root_index (no view) | ---------------- | | view 1 view 2 3. the second view is split vertically |----------------| | | 2 | | 1 |--------| | | 3 | |----------------| Indices: root_index (horizontal splitter) | ---------------- | | view 1 vertical_splitter | ----------------- | | view 2 view 3 @endcode It is possible that several "stacked" views will have the same area index. Those views can be considered as the view stack from which only one view is visible at the time. @code |----------------| | | | 1,2,3,4 | | | |----------------| Indices: root_index (view1, view2, view3, view4) @endcode */ class KDEVPLATFORMSUBLIME_EXPORT AreaIndex { public: ~AreaIndex(); AreaIndex(const AreaIndex &index); /**@return the parent index, returns 0 for root index.*/ AreaIndex *parent() const; /**@return the first child index if there're any.*/ AreaIndex *first() const; /**@return the second child index if there're any.*/ AreaIndex *second() const; /**@return true if the index is split.*/ bool isSplit() const; /**@return the orientation of the splitter for this index.*/ Qt::Orientation orientation() const; /**Set the orientation of the splitter for this index.*/ void setOrientation(Qt::Orientation orientation) const; /**Adds view to the list of views in this position. Does nothing if the view is already split. @param after if not 0, new view will be placed after this one. @param view the view to be added.*/ void add(View *view, View *after = nullptr); /**Removes view and unsplits the parent index when no views are left at the current index.*/ void remove(View *view); /**Splits the view in this position by given @p orientation and adds the @p newView into the splitter. Does nothing if the view is already split. @p newView will be in the second child index.*/ void split(View *newView, Qt::Orientation orientation); /**Splits the view in this position by given @p orientation. * @p moveViewsToSecondChild Normally, the existing views in this index are moved to the first sub-index. * If this is true, the views are moved to the _second_ sub-index instead. * Does nothing if the view is already split.*/ void split(Qt::Orientation orientation, bool moveViewsToSecondChild = false); /**Unsplits the index removing the given @p childToRemove and moving the contents of another child to this index.*/ void unsplit(AreaIndex *childToRemove); /** Returns a text-representation of the architecture of this area index and sub-indices. */ QString print() const; /**@return the stacked view in @p position, returns 0 for splitter's indices and when there's no view at the @p position.*/ View *viewAt(int position) const; /**@return the number of stacked views.*/ int viewCount() const; /**@return true if there's a stacked @p view at this index.*/ bool hasView(View *view) const; /**@return the list of views at this index.*/ QList &views() const; protected: /**Constructor for Root index.*/ AreaIndex(); private: /**Constructor for indices other than root.*/ explicit AreaIndex(AreaIndex *parent); /**Sets the parent for this index.*/ void setParent(AreaIndex *parent); /**Copies the data from this index to @p target.*/ void moveViewsTo(AreaIndex *target); /**Copies the children indices from this index to @p target.*/ void copyChildrenTo(AreaIndex *target); struct AreaIndexPrivate * const d; }; /** @short Root Area Index This is the special index class returned by @ref Area::rootIndex(). Doesn't provide any additional functionality beyond AreaIndex. */ class KDEVPLATFORMSUBLIME_EXPORT RootAreaIndex: public AreaIndex { public: RootAreaIndex(); private: class RootAreaIndexPrivate* const d; }; } #endif diff --git a/sublime/container.cpp b/sublime/container.cpp index cf1c0183f..c4b8b91d0 100644 --- a/sublime/container.cpp +++ b/sublime/container.cpp @@ -1,688 +1,687 @@ /*************************************************************************** * 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 #include "view.h" #include "document.h" #include namespace Sublime { // struct ContainerPrivate class ContainerTabBar : public QTabBar { Q_OBJECT public: explicit ContainerTabBar(Container* container) : QTabBar(container), m_container(container) { if (QApplication::style()->objectName() == QLatin1String("macintosh")) { static QPointer qTabBarStyle = QStyleFactory::create("fusion"); if (qTabBarStyle) { setStyle(qTabBarStyle); } } // configure the QTabBar style so it behaves as appropriately as possible, // even if we end up using the native Macintosh style because the user's // Qt doesn't have the Fusion style installed. setDocumentMode(true); setUsesScrollButtons(true); setElideMode(Qt::ElideNone); 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: explicit UnderlinedLabel(QTabBar *tabBar, QWidget* parent = nullptr) :KSqueezedTextLabel(parent), m_tabBar(tabBar) { } protected: void paintEvent(QPaintEvent *ev) override { #ifndef Q_OS_OSX // getting the underlining right on OS X is tricky; omitting // the underlining attracts the eye less than not getting it // exactly right. 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); } } #endif KSqueezedTextLabel::paintEvent(ev); } QTabBar *m_tabBar; }; class StatusLabel: public UnderlinedLabel { Q_OBJECT public: explicit 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(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 { QSignalBlocker blocker(d->tabBar); d->tabBar->setCurrentIndex(d->stack->indexOf(w)); } 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/container.h b/sublime/container.h index 580c1d587..a7b96ff79 100644 --- a/sublime/container.h +++ b/sublime/container.h @@ -1,119 +1,118 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMECONTAINER_H #define KDEVPLATFORM_SUBLIMECONTAINER_H #include #include "urldocument.h" #include "sublimeexport.h" class QMenu; -class QBoxLayout; class QPaintEvent; namespace Sublime { class View; class Document; /** @short Container for the widgets. This container is placed inside mainwindow splitters to show widgets for views in the area. */ class KDEVPLATFORMSUBLIME_EXPORT Container: public QWidget { Q_OBJECT public: explicit Container(QWidget *parent = nullptr); ~Container() override; /**Adds the widget for given @p view to the container.*/ void addWidget(Sublime::View* view, int position = -1); /**Removes the widget from the container.*/ void removeWidget(QWidget *w); /** @return true if widget is placed inside this container.*/ bool hasWidget(QWidget *w); QList views() const; int count() const; QWidget *currentWidget() const; void setCurrentWidget(QWidget *w); QWidget *widget(int i) const; int indexOf(QWidget *w) const; View *viewForWidget(QWidget *w) const; void setTabBarHidden(bool hide); void setTabColor(const View* view, const QColor& color); void setTabColors(const QHash& colors); void resetTabColors(const QColor& color); /** Adds a corner widget to the left of this containers tab-bar. To remove it again, just delete it. * The ownership otherwise goes to the container. */ void setLeftCornerWidget(QWidget* widget); void showTooltipForTab(int tab); bool isCurrentTab(int tab) const; /// @return Rect in global position of the tab identified by index @p tab QRect tabRect(int tab) const; static bool configTabBarVisible(); Q_SIGNALS: void activateView(Sublime::View* view); void requestClose(QWidget *w); /** * This signal is emitted whenever the users double clicks on the free * space next to the tab bar. Typically, a new document should be * created. */ void newTabRequested(); void tabContextMenuRequested(Sublime::View* view, QMenu* menu); /** * @p view The view represented by the tab that was hovered * @p Container The tab container that triggered the event * @p idx The index of the tab that was hovered */ void tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int idx); void tabDoubleClicked(Sublime::View* view); private Q_SLOTS: void widgetActivated(int idx); void documentTitleChanged(Sublime::Document* doc); void statusIconChanged(Sublime::Document*); void statusChanged(Sublime::View *view); void requestClose(int idx); void tabMoved(int from, int to); void contextMenu(const QPoint&); void doubleClickTriggered(int tab); void documentListActionTriggered(QAction*); private: Sublime::View* currentView() const; struct ContainerPrivate * const d; }; } #endif diff --git a/sublime/idealbuttonbarwidget.h b/sublime/idealbuttonbarwidget.h index 6fba18d1b..e167d4f18 100644 --- a/sublime/idealbuttonbarwidget.h +++ b/sublime/idealbuttonbarwidget.h @@ -1,101 +1,100 @@ /* 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. */ #ifndef IDEALBUTTONBARWIDGET_H #define IDEALBUTTONBARWIDGET_H #include -#include class IdealToolButton; class ToolViewAction; class QAction; class KConfigGroup; namespace Sublime { class MainWindow; class IdealController; class IdealDockWidget; class View; class Area; class IdealButtonBarWidget: public QWidget { Q_OBJECT public: IdealButtonBarWidget(Qt::DockWidgetArea area, IdealController *controller, Sublime::MainWindow *parent); QAction* addWidget(IdealDockWidget *widget, Area* area, View *view); void addAction(QAction *action); void removeAction(QAction* action); Qt::Orientation orientation() const; Qt::DockWidgetArea area() const; IdealDockWidget* widgetForAction(QAction* action) const; QWidget* corner(); void showWidget(QAction *widgetAction, bool checked); bool isEmpty(); bool isShown(); void saveShowState(); bool lastShowState(); void loadOrderSettings(const KConfigGroup& configGroup); void saveOrderSettings(KConfigGroup& configGroup); bool isLocked(); signals: void emptyChanged(); private: void showWidget(bool checked); void applyOrderToLayout(); void takeOrderFromLayout(); IdealToolButton* button(const QString& id) const; QString id(const IdealToolButton* button) const; void addButtonToOrder(const IdealToolButton* button); Qt::DockWidgetArea _area; IdealController *_controller; QWidget *_corner; bool _showState; QStringList _buttonsOrder; }; } #endif // IDEALBUTTONBARWIDGET_H diff --git a/sublime/idealcontroller.cpp b/sublime/idealcontroller.cpp index dc322f2ab..9e4bd90d8 100644 --- a/sublime/idealcontroller.cpp +++ b/sublime/idealcontroller.cpp @@ -1,515 +1,516 @@ /* 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 #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() == 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->setFocusProxy(w); toolView->addToolBar(toolBar); dock->setWidget(toolView); KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings/Docks/ToolbarEnabled"); toolBar->setVisible(cg.readEntry(dockObjectName, true)); connect(toolBar->toggleViewAction(), &QAction::toggled, this, [toolBar, dockObjectName](){ KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings/Docks/ToolbarEnabled"); cg.writeEntry(dockObjectName, toolBar->toggleViewAction()->isChecked()); }); } dock->setWindowTitle(view->widget()->windowTitle()); dock->setWindowIcon(view->widget()->windowIcon()); dock->setFocusProxy(dock->widget()); if (IdealButtonBarWidget* bar = barForDockArea(area)) { QAction* action = bar->addWidget(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(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); else dock->setFeatures(QDockWidget::DockWidgetClosable | 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 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) { QSignalBlocker blocker(action); action->setChecked(checked); } } 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(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()) { QSignalBlocker blocker(action); action->setChecked(true); } 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 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() && !leftBarWidget->isLocked()) || (bottomBarWidget->isShown() && !bottomBarWidget->isLocked()) || (rightBarWidget->isShown() && !rightBarWidget->isLocked()); if (anyBarShown) { leftBarWidget->saveShowState(); bottomBarWidget->saveShowState(); rightBarWidget->saveShowState(); } if (!leftBarWidget->isLocked()) toggleDocksShown(leftBarWidget, !anyBarShown && leftBarWidget->lastShowState()); if (!bottomBarWidget->isLocked()) toggleDocksShown(bottomBarWidget, !anyBarShown && bottomBarWidget->lastShowState()); if (!rightBarWidget->isLocked()) 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/idealcontroller.h b/sublime/idealcontroller.h index 92ebd9d0a..e6dc168ce 100644 --- a/sublime/idealcontroller.h +++ b/sublime/idealcontroller.h @@ -1,133 +1,129 @@ /* 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. */ #ifndef SUBLIME_IDEAL_H #define SUBLIME_IDEAL_H #include -#include -#include -#include -#include +#include #include #include "sublimedefs.h" -class QAction; class KActionMenu; namespace Sublime { class Area; class View; class MainWindow; class IdealButtonBarWidget; class IdealDockWidget; class View; class IdealController: public QObject { Q_OBJECT public: explicit IdealController(Sublime::MainWindow *mainWindow); void addView(Qt::DockWidgetArea area, View* view); enum RaiseMode { HideOtherViews, GroupWithOtherViews }; void raiseView(View* view, RaiseMode mode = HideOtherViews); void showDockWidget(IdealDockWidget* dock, bool show); void focusEditor(); QWidget *statusBarLocation() const; QAction* actionForView(View* view) const; void setShowDockStatus(Qt::DockWidgetArea area, bool checked); /** Remove view. If nondestructive true, view->widget() is not deleted, as is left with NULL parent. Otherwise, it's deleted. */ void removeView(View* view, bool nondestructive = false); void moveView(View *view, Qt::DockWidgetArea area); void showLeftDock(bool show); void showRightDock(bool show); void showBottomDock(bool show); void toggleDocksShown(); IdealButtonBarWidget* barForDockArea(Qt::DockWidgetArea area) const; QAction* actionForArea(Qt::DockWidgetArea area) const; enum Direction { NextDock, PrevDock }; void goPrevNextDock(IdealController::Direction direction); IdealButtonBarWidget *leftBarWidget; IdealButtonBarWidget *rightBarWidget; IdealButtonBarWidget *bottomBarWidget; IdealButtonBarWidget *topBarWidget; QWidget *bottomStatusBarLocation; IdealDockWidget* currentDockWidget(); QMap > lastDockWidget; void emitWidgetResized(Qt::DockWidgetArea dockArea, int thickness); QList allDockWidgets(); Q_SIGNALS: /// Emitted, when a context menu is requested on one of the dock bars. /// When no actions gets associated to the QMenu, it won't be shown. void dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position); void dockShown(Sublime::View*, Sublime::Position pos, bool shown); void widgetResized(Qt::DockWidgetArea dockArea, int thickness); private Q_SLOTS: void slotDockBarContextMenuRequested(QPoint position); void dockLocationChanged(Qt::DockWidgetArea); void loadSettings(); private: void hideDocks(IdealButtonBarWidget *bar); void showDock(Qt::DockWidgetArea area, bool show); void toggleDocksShown(IdealButtonBarWidget *bar, bool show); Sublime::MainWindow *m_mainWindow; QSet docks; /** Map from View to an action that shows/hides the IdealDockWidget containing that view. */ QMap m_view_to_action; /** Map from IdealDockWidget to an action that shows/hides that IdealDockWidget. */ QMap m_dockwidget_to_action; KActionMenu* m_docks; QAction* m_showLeftDock; QAction* m_showRightDock; QAction* m_showBottomDock; QAction* m_showTopDock; }; } #endif diff --git a/sublime/ideallayout.cpp b/sublime/ideallayout.cpp index 68fe066dc..2b717cd39 100644 --- a/sublime/ideallayout.cpp +++ b/sublime/ideallayout.cpp @@ -1,251 +1,248 @@ /* 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, nullptr); } QLayoutItem* IdealButtonBarLayout::takeAt(int index) { if (index >= 0 && index < _items.count()) return _items.takeAt(index); invalidate(); 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/ideallayout.h b/sublime/ideallayout.h index b40328f93..3c4c45748 100644 --- a/sublime/ideallayout.h +++ b/sublime/ideallayout.h @@ -1,90 +1,85 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda 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. */ #ifndef KDEVPLATFORM_SUBLIME_IDEALLAYOUT_H #define KDEVPLATFORM_SUBLIME_IDEALLAYOUT_H #include -#include -#include #include "sublimedefs.h" #define IDEAL_LAYOUT_MARGIN 0 #define IDEAL_LAYOUT_SPACING 2 -class QAction; -class KActionCollection; - namespace Sublime { class IdealDockWidget; class IdealButtonBarLayout: public QLayout { Q_OBJECT public: explicit IdealButtonBarLayout(Qt::Orientation orientation, QWidget *parent = nullptr); ~IdealButtonBarLayout() override; void setHeight(int height); inline Qt::Orientation orientation() const; Qt::Orientations expandingDirections() const override; QSize minimumSize() const override; QSize sizeHint() const override; void setGeometry(const QRect &rect) override; void addItem(QLayoutItem *item) override; QLayoutItem* itemAt(int index) const override; QLayoutItem* takeAt(int index) override; int count() const override; void invalidate() override; protected: int doVerticalLayout(const QRect &rect, bool updateGeometry = true) const; int doHorizontalLayout(const QRect &rect, bool updateGeometry = true) const; private: QList _items; Qt::Orientation _orientation; int _height; mutable bool m_minSizeDirty : 1; mutable bool m_sizeHintDirty : 1; mutable bool m_layoutDirty : 1; mutable QSize m_min; mutable QSize m_hint; }; } #endif diff --git a/sublime/mainwindow.h b/sublime/mainwindow.h index 3072b1ee4..c02d351c6 100644 --- a/sublime/mainwindow.h +++ b/sublime/mainwindow.h @@ -1,184 +1,182 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMEMAINWINDOW_H #define KDEVPLATFORM_SUBLIMEMAINWINDOW_H #include #include #include #include "sublimeexport.h" -class QDockWidget; - namespace Sublime { class Container; class Area; class View; class Controller; class MainWindowOperator; /** @short Sublime Main Window The area-enabled mainwindow to show Sublime views and toolviews. To use, a controller and constructed areas are necessary: @code MainWindow w(controller); controller->showArea(area, &w); @endcode */ class KDEVPLATFORMSUBLIME_EXPORT MainWindow: public KParts::MainWindow { Q_OBJECT public: /**Creates a mainwindow and adds it to the controller.*/ explicit MainWindow(Controller *controller, Qt::WindowFlags flags = KDE_DEFAULT_WINDOWFLAGS); ~MainWindow() override; /**@return the list of dockwidgets that contain area's toolviews.*/ QList toolDocks() const; /**@return area which mainwindow currently shows or 0 if no area has been set.*/ Area *area() const; /**@return controller for this mainwindow.*/ Controller *controller() const; /**@return active view inside this mainwindow.*/ View *activeView() const; /**@return active toolview inside this mainwindow.*/ View *activeToolView() const; /**Enable saving of per-area UI settings (like toolbar properties and position) whenever area is changed. This should be called after all areas are restored, and main window area is set, to prevent saving a half-broken state. */ void enableAreaSettingsSave(); /** Allows setting an additional widget that will be inserted left to the document tab-bar. * The ownership goes to the target. */ void setTabBarLeftCornerWidget(QWidget* widget); /**Sets the area of main window and fills it with views. *The contents is reconstructed, even if the area equals the currently set area. */ void setArea(Area *area); /** * Reconstruct the view structure. This is required after significant untracked changes to the * area-index structure. * Views listed in topViews will be on top of their view stacks. * */ void reconstructViews(QList topViews = QList()); /**Returns a list of all views which are on top of their corresponding view stacks*/ QList getTopViews() const; QList containers() const; /**Returns the view that is closest to the given global position, or zero.*/ View* viewForPosition(QPoint globalPos) const; /**Returns true if this main-window contains this view*/ bool containsView(View* view) const; /**Returns all areas that belong to this main-window*/ QList areas() const; /** Sets a @p w widget that will be shown when there are no opened documents. * This method takes the ownership of @p w. */ void setBackgroundCentralWidget(QWidget* w); /**Returns a widget that can hold a centralized view bar*/ QWidget *viewBarContainer() const; public Q_SLOTS: /**Shows the @p view and makes it active, focusing it by default).*/ void activateView(Sublime::View *view, bool focus = true); /**Loads size/toolbar/menu/statusbar settings to the global configuration file. Reimplement in subclasses to load more and don't forget to call inherited method.*/ virtual void loadSettings(); Q_SIGNALS: /**Emitted before the area is cleared from this mainwindow.*/ void areaCleared(Sublime::Area*); /**Emitted after the new area has been shown in this mainwindow.*/ void areaChanged(Sublime::Area*); /**Emitted when the active view is changed.*/ void activeViewChanged(Sublime::View*); /**Emitted when the active toolview is changed.*/ void activeToolViewChanged(Sublime::View*); /**Emitted when the user interface settings have changed.*/ void settingsLoaded(); /**Emitted when a new view is added to the mainwindow.*/ void viewAdded(Sublime::View*); /**Emitted when a view is going to be removed from the mainwindow.*/ void aboutToRemoveView(Sublime::View*); protected: QWidget *statusBarLocation() const; virtual void initializeStatusBar(); protected Q_SLOTS: virtual void tabDoubleClicked(Sublime::View* view); virtual void tabContextMenuRequested(Sublime::View*, QMenu*); virtual void tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab); virtual void newTabRequested(); /**Called whenever the user requests a context menu on a dockwidget bar. You can then e.g. add actions to add dockwidgets. Default implementation does nothing.**/ virtual void dockBarContextMenuRequested(Qt::DockWidgetArea, const QPoint&); public: // FIXME? /**Saves size/toolbar/menu/statusbar settings to the global configuration file. Reimplement in subclasses to save more and don't forget to call inherited method.*/ virtual void saveSettings(); /**Reimplemented to save settings.*/ bool queryClose() override; /** Allow connecting to activateView without the need for a lambda for the default parameter */ void activateViewAndFocus(Sublime::View *view) { activateView(view, true); } private: QString screenKey() const; //Inherit MainWindowOperator to access four methods below /**Unsets the area clearing main window.*/ void clearArea(); /**Sets the active view.*/ void setActiveView(Sublime::View* view, bool focus = true); /**Sets the active toolview and focuses it.*/ void setActiveToolView(View *view); void resizeEvent(QResizeEvent* event) override; void saveGeometry(KConfigGroup &config); void loadGeometry(const KConfigGroup &config); class MainWindowPrivate *const d; friend class MainWindowOperator; friend class MainWindowPrivate; }; } #endif diff --git a/sublime/mainwindow_p.cpp b/sublime/mainwindow_p.cpp index a12d7115c..6782082aa 100644 --- a/sublime/mainwindow_p.cpp +++ b/sublime/mainwindow_p.cpp @@ -1,814 +1,815 @@ /*************************************************************************** * 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 #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(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; centralWidget->setObjectName(QStringLiteral("centralWidget")); QVBoxLayout* layout = new QVBoxLayout(centralWidget); layout->setMargin(0); centralWidget->setLayout(layout); splitterCentralWidget = new QSplitter(centralWidget); // take as much space as possible splitterCentralWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout->addWidget(splitterCentralWidget, 2); // this view bar container is used for the ktexteditor integration to show // all view bars at a central place, esp. for split view configurations viewBarContainer = new QWidget; viewBarContainer->setObjectName(QStringLiteral("viewBarContainer")); // hide by default viewBarContainer->setVisible(false); // only take as much as needed viewBarContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); layout->addWidget(viewBarContainer); 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(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; } 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 = 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(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(nullptr); } IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, Sublime::AllPositions); reconstructViews(); { QSignalBlocker blocker(m_mainWindow); 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); } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::clearArea() { if(m_leftTabbarCornerWidget) 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(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(nullptr); } cleanCentralWidget(); m_mainWindow->setActiveView(nullptr); m_indexSplitters.clear(); 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(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(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(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(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(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(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(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(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 = 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/mainwindow_p.h b/sublime/mainwindow_p.h index 4650f63ab..542cf3ae2 100644 --- a/sublime/mainwindow_p.h +++ b/sublime/mainwindow_p.h @@ -1,167 +1,156 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMEMAINWINDOW_P_H #define KDEVPLATFORM_SUBLIMEMAINWINDOW_P_H #include #include #include #include -#include -#include -#include #include "area.h" #include "sublimedefs.h" #include "mainwindow.h" -#include -#include -#include -#include #include -#include -class QMenu; class QAction; class QSplitter; -class QDockWidget; -class QComboBox; class IdealToolBar; namespace Sublime { class View; class Container; class Controller; class AreaIndex; class IdealMainWidget; class IdealController; class MainWindowPrivate: public QObject { Q_OBJECT public: MainWindowPrivate(MainWindow *w, Controller* controller); ~MainWindowPrivate() override; /**Use this to create tool views for an area.*/ class IdealToolViewCreator { public: explicit IdealToolViewCreator(MainWindowPrivate *_d): d(_d) {} Area::WalkerMode operator() (View *view, Sublime::Position position); private: MainWindowPrivate *d; }; /**Use this to create views for an area.*/ class ViewCreator { public: explicit ViewCreator(MainWindowPrivate *_d, QList _topViews = QList()): d(_d), topViews(_topViews.toSet()) {} Area::WalkerMode operator() (AreaIndex *index); private: MainWindowPrivate *d; QSet topViews; }; /**Reconstructs the mainwindow according to the current area.*/ void reconstruct(); /**Reconstructs the views according to the current area index.*/ void reconstructViews(QList topViews = QList()); /**Clears the area leaving mainwindow empty.*/ void clearArea(); /** Sets a @p w widget that will be shown when there are no documents on the area */ void setBackgroundCentralWidget(QWidget* w); void activateFirstVisibleView(); Controller *controller; Area *area; QList docks; QMap viewContainers; QMap widgetToView; View *activeView; View *activeToolView; QWidget *centralWidget; QWidget* bgCentralWidget; QWidget* viewBarContainer; QSplitter* splitterCentralWidget; IdealController *idealController; int ignoreDockShown; bool autoAreaSettingsSave; bool eventFilter(QObject* obj, QEvent* event) override; void disableConcentrationMode(); public slots: void toggleDocksShown(); void viewAdded(Sublime::AreaIndex *index, Sublime::View *view); void viewRemovedInternal(Sublime::AreaIndex *index, Sublime::View *view); void raiseToolView(Sublime::View* view); void aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view); void toolViewAdded(Sublime::View *toolView, Sublime::Position position); void aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position position); void toolViewMoved(Sublime::View *toolView, Sublime::Position position); void setTabBarLeftCornerWidget(QWidget* widget); private slots: void switchToArea(QAction *action); void updateAreaSwitcher(Sublime::Area *area); void slotDockShown(Sublime::View*, Sublime::Position, bool); void widgetResized(Qt::DockWidgetArea dockArea, int thickness); void widgetCloseRequest(QWidget* widget); void showLeftDock(bool b); void showRightDock(bool b); void showBottomDock(bool b); void focusEditor(); void selectNextDock(); void selectPreviousDock(); private: void restoreConcentrationMode(); void setBackgroundVisible(bool v); Qt::DockWidgetArea positionToDockArea(Position position); void cleanCentralWidget(); MainWindow *m_mainWindow; // uses QPointer to make already-deleted splitters detectable QMap > m_indexSplitters; QMap m_areaActions; QMap m_actionAreas; QPointer m_leftTabbarCornerWidget; QPointer m_concentrateToolBar; IdealToolBar* m_bottomToolBar; IdealToolBar* m_rightToolBar; IdealToolBar* m_leftToolBar; QAction* m_concentrationModeAction; }; } #endif diff --git a/sublime/tests/test_aggregatemodel.cpp b/sublime/tests/test_aggregatemodel.cpp index 7ace36e53..90bc8ea81 100644 --- a/sublime/tests/test_aggregatemodel.cpp +++ b/sublime/tests/test_aggregatemodel.cpp @@ -1,68 +1,68 @@ /*************************************************************************** * 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_aggregatemodel.h" -#include +#include #include #include #include #include using namespace Sublime; void TestAggregateModel::modelAggregationInASingleView() { AggregateModel *model = new AggregateModel(this); model->addModel(QStringLiteral("First Model"), newModel()); model->addModel(QStringLiteral("Second Model"), newModel()); //this will assert in case of model problems and the test will fail //for detailed explanation why the test failed refer to test/modeltest.cpp new ModelTest(model, this); } QStandardItemModel * TestAggregateModel::newModel() { /* construct the simple model like: cool item item 0 item 1 item 2 item 3 */ QStandardItemModel *model = new QStandardItemModel(this); QStandardItem *parentItem = model->invisibleRootItem(); QStandardItem *item = new QStandardItem(QStringLiteral("cool item")); parentItem->appendRow(item); for (int i = 0; i < 4; ++i) { QStandardItem *item = new QStandardItem(QStringLiteral("item %0").arg(i)); parentItem->appendRow(item); parentItem = item; } return model; } QTEST_MAIN(TestAggregateModel) diff --git a/sublime/tests/test_areaoperation.cpp b/sublime/tests/test_areaoperation.cpp index 98ef9aca1..2a924f3c1 100644 --- a/sublime/tests/test_areaoperation.cpp +++ b/sublime/tests/test_areaoperation.cpp @@ -1,728 +1,727 @@ /*************************************************************************** * 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 #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 = 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 != 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) != 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 != 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) != 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() != 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() != 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 != 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) != 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 nullptr; } /////////// QTEST_MAIN(TestAreaOperation) diff --git a/sublime/tests/test_areawalker.cpp b/sublime/tests/test_areawalker.cpp index debaa6865..67dc2513e 100644 --- a/sublime/tests/test_areawalker.cpp +++ b/sublime/tests/test_areawalker.cpp @@ -1,143 +1,143 @@ /*************************************************************************** * 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_areawalker.h" #include -#include +#include #include #include #include #include #include "areaprinter.h" using namespace Sublime; struct AreaStopper { explicit AreaStopper(QString stopAt): m_stopAt(stopAt) {} Area::WalkerMode operator()(AreaIndex *index) { foreach (View *view, index->views()) { list << view->objectName(); if (view->objectName() == m_stopAt) return Area::StopWalker; } return Area::ContinueWalker; } Area::WalkerMode operator()(View *view, Sublime::Position) { list << view->objectName(); if (view->objectName() == m_stopAt) return Area::StopWalker; return Area::ContinueWalker; } QStringList list; QString m_stopAt; }; void TestAreaWalker::viewWalkerModes() { Controller *controller = new Controller(this); Document *doc = new UrlDocument(controller, QUrl::fromLocalFile(QStringLiteral("~/foo.cpp"))); Area *area = new Area(controller, QStringLiteral("Area")); View *view1 = doc->createView(); view1->setObjectName(QStringLiteral("1")); area->addView(view1); View *view2 = doc->createView(); view2->setObjectName(QStringLiteral("2")); area->addView(view2, view1, Qt::Vertical); View *view3 = doc->createView(); view3->setObjectName(QStringLiteral("3")); area->addView(view3, view1, Qt::Vertical); View *view4 = doc->createView(); view4->setObjectName(QStringLiteral("4")); area->addView(view4, view1, Qt::Vertical); AreaViewsPrinter p; area->walkViews(p, area->rootIndex()); QCOMPARE(p.result, QStringLiteral("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ 1 ]\n\ [ 4 ]\n\ [ 3 ]\n\ [ 2 ]\n\ ")); AreaStopper stopper(QStringLiteral("1")); area->walkViews(stopper, area->rootIndex()); QCOMPARE(stopper.list.join(QStringLiteral(" ")), QStringLiteral("1")); AreaStopper stopper2(QStringLiteral("2")); area->walkViews(stopper2, area->rootIndex()); QCOMPARE(stopper2.list.join(QStringLiteral(" ")), QStringLiteral("1 4 3 2")); AreaStopper stopper3(QStringLiteral("3")); area->walkViews(stopper3, area->rootIndex()); QCOMPARE(stopper3.list.join(QStringLiteral(" ")), QStringLiteral("1 4 3")); AreaStopper noStopper(QStringLiteral("X")); area->walkViews(noStopper, area->rootIndex()); QCOMPARE(noStopper.list.join(QStringLiteral(" ")), QStringLiteral("1 4 3 2")); delete area; delete doc; delete controller; } void TestAreaWalker::toolViewWalkerModes() { Controller *controller = new Controller(this); Document *doc = new UrlDocument(controller, QUrl::fromLocalFile(QStringLiteral("~/foo.cpp"))); Area *area = new Area(controller, QStringLiteral("Area")); View *view = doc->createView(); view->setObjectName(QStringLiteral("1")); area->addToolView(view, Sublime::Left); view = doc->createView(); view->setObjectName(QStringLiteral("2")); area->addToolView(view, Sublime::Left); view = doc->createView(); view->setObjectName(QStringLiteral("3")); area->addToolView(view, Sublime::Bottom); AreaStopper stopper1(QStringLiteral("1")); area->walkToolViews(stopper1, Sublime::AllPositions); QCOMPARE(stopper1.list.join(QStringLiteral(" ")), QStringLiteral("1")); AreaStopper stopper2(QStringLiteral("2")); area->walkToolViews(stopper2, Sublime::AllPositions); QCOMPARE(stopper2.list.join(QStringLiteral(" ")), QStringLiteral("1 2")); AreaStopper stopper3(QStringLiteral("3")); area->walkToolViews(stopper3, Sublime::AllPositions); QCOMPARE(stopper3.list.join(QStringLiteral(" ")), QStringLiteral("1 2 3")); AreaStopper noStopper(QStringLiteral("X")); area->walkToolViews(noStopper, Sublime::AllPositions); QCOMPARE(noStopper.list.join(QStringLiteral(" ")), QStringLiteral("1 2 3")); delete area; delete doc; delete controller; } QTEST_MAIN(TestAreaWalker) diff --git a/sublime/tests/test_controller.cpp b/sublime/tests/test_controller.cpp index c2166c096..aab9681c3 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 #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 = 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_document.cpp b/sublime/tests/test_document.cpp index 6a9aa8bdc..bdf6a0416 100644 --- a/sublime/tests/test_document.cpp +++ b/sublime/tests/test_document.cpp @@ -1,43 +1,43 @@ /*************************************************************************** * 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_document.h" #include -#include +#include #include #include #include using namespace Sublime; void TestDocument::viewDeletion() { Controller controller; Document *doc = new ToolDocument(QStringLiteral("tool"), &controller, new SimpleToolWidgetFactory(QStringLiteral("tool"))); View *view = doc->createView(); view->widget(); QCOMPARE(doc->views().count(), 1); delete view; QCOMPARE(doc->views().count(), 0); } QTEST_MAIN(TestDocument) diff --git a/sublime/tests/test_toolviewtoolbar.cpp b/sublime/tests/test_toolviewtoolbar.cpp index 6b0b11363..aafa6fb99 100644 --- a/sublime/tests/test_toolviewtoolbar.cpp +++ b/sublime/tests/test_toolviewtoolbar.cpp @@ -1,134 +1,132 @@ /*************************************************************************** * 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 #include using namespace Sublime; class ToolViewToolBarFactory : public SimpleToolWidgetFactory { public: explicit ToolViewToolBarFactory(const QString &id): SimpleToolWidgetFactory(id) {} QList toolBarActions( QWidget* ) const override { 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_toolviewtoolbar.h b/sublime/tests/test_toolviewtoolbar.h index fc94ecb3b..50d5c1431 100644 --- a/sublime/tests/test_toolviewtoolbar.h +++ b/sublime/tests/test_toolviewtoolbar.h @@ -1,61 +1,60 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_TEST_TOOLVIEWTOOLBAR_H #define KDEVPLATFORM_TEST_TOOLVIEWTOOLBAR_H #include namespace Sublime { class View; class Controller; class ToolDocument; class Area; } -class QDockWidget; class QToolBar; class TestToolViewToolBar : public QObject { Q_OBJECT private slots: void init(); void cleanup(); void horizontalTool(); void verticalTool(); void toolViewMove(); private: QToolBar* fetchToolBarFor(Sublime::View*); void assertGoodBar(QToolBar*, QString actionText); private: Sublime::Controller *controller; Sublime::Area *area; Sublime::ToolDocument *tool1; Sublime::ToolDocument *tool2; Sublime::View *viewT11; Sublime::View *viewT21; QString actionTextT1; QString actionTextT2; }; #endif diff --git a/sublime/tests/test_view.cpp b/sublime/tests/test_view.cpp index f34dfcfc6..2191f7adf 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 #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: explicit Test(Document *doc): View(doc) {} }; class TestDocument: public Document { Q_OBJECT public: explicit 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 = 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) != nullptr); } QTEST_MAIN(TestView) #include "test_view.moc" diff --git a/sublime/tests/test_viewactivation.cpp b/sublime/tests/test_viewactivation.cpp index 2058a5e65..4c651ab48 100644 --- a/sublime/tests/test_viewactivation.cpp +++ b/sublime/tests/test_viewactivation.cpp @@ -1,227 +1,228 @@ /*************************************************************************** * 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 #include #include using namespace Sublime; template class SpecialWidgetFactory: public SimpleToolWidgetFactory { public: explicit SpecialWidgetFactory(const QString &id): SimpleToolWidgetFactory(id) {} 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/template/filters/kdevfilters.h b/template/filters/kdevfilters.h index c2e0a18b9..b3d648059 100644 --- a/template/filters/kdevfilters.h +++ b/template/filters/kdevfilters.h @@ -1,94 +1,93 @@ /* 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. */ #ifndef KDEVPLATFORM_KDEVFILTERS_H #define KDEVPLATFORM_KDEVFILTERS_H #include #include #include -#include namespace KDevelop { class CamelCaseFilter : public Grantlee::Filter { public: QVariant doFilter(const QVariant& input, const QVariant& argument = QVariant(), bool autoescape = false) const override; }; class LowerCamelCaseFilter : public Grantlee::Filter { public: QVariant doFilter(const QVariant& input, const QVariant& argument = QVariant(), bool autoescape = false) const override; }; class UnderscoreFilter : public Grantlee::Filter { public: QVariant doFilter(const QVariant& input, const QVariant& argument = QVariant(), bool autoescape = false) const override; }; class UpperFirstFilter : public Grantlee::Filter { public: QVariant doFilter(const QVariant& input, const QVariant& argument = QVariant(), bool autoescape = false) const override; }; class SplitLinesFilter : public Grantlee::Filter { public: QVariant doFilter(const QVariant& input, const QVariant& argument = QVariant(), bool autoescape = false) const override; }; class ArgumentTypeFilter : public Grantlee::Filter { public: QVariant doFilter(const QVariant& input, const QVariant& argument = QVariant(), bool autoescape = false) const override; }; class KDevFilters : public QObject, public Grantlee::TagLibraryInterface { Q_OBJECT Q_INTERFACES(Grantlee::TagLibraryInterface) Q_PLUGIN_METADATA(IID "org.grantlee.TagLibraryInterface") public: explicit KDevFilters(QObject* parent = nullptr, const QVariantList &args = QVariantList()); ~KDevFilters() override; QHash< QString, Grantlee::Filter* > filters(const QString& name = QString()) override; }; } #endif // KDEVPLATFORM_KDEVFILTERS_H diff --git a/tests/json/delayedoutput.cpp b/tests/json/delayedoutput.cpp index 59f0a2e02..1d68dd5f1 100644 --- a/tests/json/delayedoutput.cpp +++ b/tests/json/delayedoutput.cpp @@ -1,70 +1,71 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 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 - #include "delayedoutput.h" +#include +#include + namespace KDevelop { typedef QPair DepthedOutput; class DelayedOutputPrivate { public: void flushOutput() { while (!output.isEmpty()) { DepthedOutput curOutput = output.pop(); qDebug().nospace() << qPrintable(QString(curOutput.second -1, ' ')) << curOutput.first.toUtf8().data(); } } QStack output; int delayDepth; }; DelayedOutput::Delay::Delay(DelayedOutput* output) { m_output = output; ++m_output->d->delayDepth; } DelayedOutput::Delay::~Delay() { --m_output->d->delayDepth; if (!m_output->d->delayDepth) m_output->d->flushOutput(); } DelayedOutput::DelayedOutput() : d(new DelayedOutputPrivate()) { } DelayedOutput::~DelayedOutput() { } DelayedOutput& DelayedOutput::self() { static DelayedOutput _inst; return _inst; } void DelayedOutput::push(const QString& output) { d->output.push(DepthedOutput(output, d->delayDepth)); } } diff --git a/tests/json/delayedoutput.h b/tests/json/delayedoutput.h index ba07f3b70..f8eb917e4 100644 --- a/tests/json/delayedoutput.h +++ b/tests/json/delayedoutput.h @@ -1,55 +1,54 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 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_DELAYEDOUTPUT_H #define KDEVPLATFORM_DELAYEDOUTPUT_H #include -#include -#include +#include namespace KDevelop { ///Used to invert and visually nest error output generated by nested/recursive functions ///Used in TestSuite.h, use only at your own, singleton educated risk class DelayedOutputPrivate; class KDEVPLATFORMTESTS_EXPORT DelayedOutput { public: class KDEVPLATFORMTESTS_EXPORT Delay { public: explicit Delay(DelayedOutput* output); ~Delay(); private: DelayedOutput *m_output; }; ~DelayedOutput(); static DelayedOutput& self(); void push(const QString &output); private: DelayedOutput(); Q_DISABLE_COPY(DelayedOutput) const QScopedPointer d; }; } #endif //KDEVPLATFORM_DELAYEDOUTPUT_H diff --git a/util/foregroundlock.h b/util/foregroundlock.h index 6150ad732..c5e594c8e 100644 --- a/util/foregroundlock.h +++ b/util/foregroundlock.h @@ -1,108 +1,106 @@ /* 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. */ #ifndef KDEVPLATFORM_FOREGROUNDLOCK_H #define KDEVPLATFORM_FOREGROUNDLOCK_H #include "utilexport.h" #include #include #include -class QApplication; - namespace KDevelop { /** * A locking object that locks the resources that are associated to the main thread. When this lock is held, * you can call any thread-unsafe functions, because the foreground thread is locked in an event. * * The lock always becomes available when the foreground thread stops processing events. * * @warning There is one simple rule you must always follow to prevent deadlocks: * @em Never lock anything before locking the foreground mutex!! * That also means that you must not take this lock in contexts where * you don't know what other mutexes might be locked. * * @warning Objects that have QObject as base always get the thread they were created in assigned (see thread affinity, QObject::moveToThread), * which seriously affects the objects functionality regarding signals/slots. * The foreground lock does not change the thread affinity, so holding the foreground lock does not fully equal being in the foreground. * It may generally be unsafe to call foreground functions that create QObjects from within the background. */ class KDEVPLATFORMUTIL_EXPORT ForegroundLock { public: explicit ForegroundLock(bool lock = true); ~ForegroundLock(); void unlock(); void relock(); bool tryLock(); /// Returns whether the current thread holds the foreground lock static bool isLockedForThread(); bool isLocked() const; private: ForegroundLock(const ForegroundLock& rhs); ForegroundLock& operator=(const ForegroundLock& rhs); bool m_locked; }; /** * Use this object if you want to temporarily release the foreground lock, * for example when sleeping in the foreground thread, or when waiting in the foreground * thread for a background thread which should get the chance to lock the foreground. * * While this object is alive, you _must not_ access any non-threadsafe resources * that belong to the foreground, and you must not start an event-loop. */ class KDEVPLATFORMUTIL_EXPORT TemporarilyReleaseForegroundLock { public: TemporarilyReleaseForegroundLock(); ~TemporarilyReleaseForegroundLock(); private: TemporarilyReleaseForegroundLock(const TemporarilyReleaseForegroundLock&); TemporarilyReleaseForegroundLock& operator=(const TemporarilyReleaseForegroundLock& rhs); int m_recursion; }; #define VERIFY_FOREGROUND_LOCKED Q_ASSERT(KDevelop::ForegroundLock::isLockedForThread()); class KDEVPLATFORMUTIL_EXPORT DoInForeground : public QObject { Q_OBJECT public: DoInForeground() ; ~DoInForeground() override ; void doIt() ; private Q_SLOTS: void doInternalSlot(); private: virtual void doInternal() = 0; QMutex m_mutex; QWaitCondition m_wait; }; } #endif diff --git a/util/formattinghelpers.h b/util/formattinghelpers.h index 83a1e0ea7..0a7fb1206 100644 --- a/util/formattinghelpers.h +++ b/util/formattinghelpers.h @@ -1,46 +1,44 @@ /* This file is part of KDevelop * Copyright 2011 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_FORMATTINGHELPERS_H #define KDEVPLATFORM_FORMATTINGHELPERS_H #include "utilexport.h" #include -class QString; - namespace KDevelop { /** * Helps extracting a re-formatted version of a text fragment, within a specific left and right context. * The re-formatting must be an operation which only changes whitespace, and keeps whitespace boundaries * between identifiers intact. If this is not the case, the original text is returned. * * @param formattedMergedText The re-formatted merged text: format(leftContext + text + rightContext) * @param text The text fragment of which the re-formatted version will be returned * @param leftContext The left context of the text fragment * @param rightContext The right context of the text fragment * @param tabWidth The width of one tab, required while matching tabs vs. spaces * @param fuzzyCharacters Characters which are ignored in case of mismatches * * @return The re-formatted version of @p text * */ KDEVPLATFORMUTIL_EXPORT QString extractFormattedTextFromContext(const QString& formattedMergedText, const QString& text, const QString& leftContext, const QString& rightContext, int tabWidth = 4, const QString& fuzzyCharacters = QStringLiteral("{}()/*/")); } #endif // KDEVPLATFORM_FORMATTINGHELPERS_H diff --git a/util/kdevformatfile.cpp b/util/kdevformatfile.cpp index 2b957464d..91b714eb2 100644 --- a/util/kdevformatfile.cpp +++ b/util/kdevformatfile.cpp @@ -1,153 +1,152 @@ /* Copyright 2016 Anton Anikin 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 "kdevformatfile.h" #include #include #include #include -#include namespace KDevelop { static const QString formatFileName = QStringLiteral("format_sources"); KDevFormatFile::KDevFormatFile(const QString& origFilePath, const QString& tempFilePath) : m_origFilePath(origFilePath) , m_tempFilePath(tempFilePath) { } bool KDevFormatFile::find() { QDir srcDir(QFileInfo(m_origFilePath).canonicalPath()); while (!srcDir.isRoot()) { if (srcDir.exists(formatFileName)) { QDir::setCurrent(srcDir.canonicalPath()); qStdOut() << "found \"" << QFileInfo(srcDir.canonicalPath() + "/" + formatFileName).canonicalFilePath() << "\"\n"; return true; } srcDir.cdUp(); } return false; } bool KDevFormatFile::read() { static const QChar delimeter(':'); QFile formatFile(formatFileName); if (!formatFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qStdOut() << "unable to open \"" << formatFileName << "\"\n"; return false; } int lineNumber = 0; QString line; QStringList wildcards; QString command; while (!formatFile.atEnd()) { ++lineNumber; line = formatFile.readLine().trimmed(); if (line.isEmpty() || line.startsWith('#')) continue; if (line.indexOf(delimeter) < 0) { // We found the simple syntax without wildcards, and only with the command wildcards.clear(); m_formatLines.append({wildcards, line}); } else { // We found the correct syntax with "wildcards : command" wildcards = line.section(delimeter, 0, 0).split(' ', QString::SkipEmptyParts); command = line.section(delimeter, 1).trimmed(); if (wildcards.isEmpty()) { qStdOut() << formatFileName << ":" << lineNumber << ": error: empty wildcard, skip the line\n"; continue; } m_formatLines.append({wildcards, command}); } } if (m_formatLines.isEmpty()) { qStdOut() << formatFileName << ": error: no commands are found\n"; return false; } return true; } bool KDevFormatFile::apply() { foreach (const KDevFormatLine& formatLine, m_formatLines) { if (formatLine.wildcards.isEmpty()) { qStdOut() << "matched \"" << m_origFilePath << "\" without wildcard"; return executeCommand(formatLine.command); } foreach(const QString& wildcard, formatLine.wildcards) { if (QDir::match(QDir::current().canonicalPath() + "/" + wildcard.trimmed(), m_origFilePath)) { qStdOut() << "matched \"" << m_origFilePath << "\" with wildcard \"" << wildcard << "\""; return executeCommand(formatLine.command); } } } qStdOut() << formatFileName << ": error: no commands applicable to \"" << m_origFilePath << "\"\n"; return false; } bool KDevFormatFile::executeCommand(QString command) { qStdOut() << ", using command \"" << command << "\"\n"; command.replace(QStringLiteral("$ORIGFILE"), m_origFilePath); command.replace(QStringLiteral("$TMPFILE"), m_tempFilePath); #ifdef Q_OS_WIN int execResult = QProcess::execute("cmd", {"/c", command}); #else int execResult = QProcess::execute("sh", {"-c", command}); #endif if (execResult == -2) { qStdOut() << "command \"" + command + "\" failed to start\n"; return false; } if (execResult == -1) { qStdOut() << "command \"" + command + "\" crashed\n"; return false; } return true; } } diff --git a/util/ksharedobject.h b/util/ksharedobject.h index 115958840..5a4511ddc 100644 --- a/util/ksharedobject.h +++ b/util/ksharedobject.h @@ -1,74 +1,74 @@ /* 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. */ #ifndef KDEVPLATFORM_KSHAREDOBJECT_H #define KDEVPLATFORM_KSHAREDOBJECT_H -#include +#include #include namespace KDevelop { struct FakeAtomic { inline FakeAtomic(QObject& object, QSharedData& real) : m_object(object), m_real(real) { } inline operator int() const { const int value = m_real.ref.loadAcquire(); if(value == 0) return 1; //Always return true, because we handle the deleting by ourself using deleteLater return value; } inline bool ref() { return m_real.ref.ref(); } inline bool deref() { bool ret = m_real.ref.deref(); if(!ret) m_object.deleteLater(); return true; //Always return true, because we handle the deleting by ourself } inline int load() const { return m_real.ref.load(); } QObject& m_object; QSharedData& m_real; }; /** * Wrapper around QSharedData for use with KSharedPtr when the object is based on QObject as well. * Instead of deleting the object once the reference-count reaches zero, QObject::deleteLater() is called. * This prevents a possible crash when the reference-count reaches zero during event-processing while the queue * contains events to be delivered to that object. * * Notice however that the object will not be deleted immediately, which may lead to unintended behavior. */ struct KSharedObject : public QSharedData { inline explicit KSharedObject(QObject& object) : ref(object, *this) { } mutable FakeAtomic ref; }; } #endif diff --git a/util/multilevellistview.cpp b/util/multilevellistview.cpp index 923d474c3..04553b81e 100644 --- a/util/multilevellistview.cpp +++ b/util/multilevellistview.cpp @@ -1,455 +1,454 @@ /* 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: explicit 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 = 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: explicit 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(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 (proxy->hasIndex(0, 0, current)) { // select the first leaf node for this view QModelIndex idx = current; QModelIndex child = proxy->index(0, 0, idx); while(child.isValid()) { idx = child; child = proxy->index(0, 0, idx); } 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 = 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/path.cpp b/util/path.cpp index 0b5236b1a..edcbfbe9a 100644 --- a/util/path.cpp +++ b/util/path.cpp @@ -1,514 +1,513 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * Copyright 2015 Kevin Funk * * 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 "path.h" #include -#include #include #include using namespace KDevelop; namespace { inline bool isWindowsDriveLetter(const QString& segment) { #ifdef Q_OS_WIN return segment.size() == 2 && segment.at(0).isLetter() && segment.at(1) == ':'; #else Q_UNUSED(segment); return false; #endif } inline bool isAbsolutePath(const QString& path) { if (path.startsWith('/')) { return true; // Even on Windows: Potentially a path of a remote URL } #ifdef Q_OS_WIN return path.size() >= 2 && path.at(0).isLetter() && path.at(1) == ':'; #else return false; #endif } } QString KDevelop::toUrlOrLocalFile(const QUrl& url, QUrl::FormattingOptions options) { const auto str = url.toString(options | QUrl::PreferLocalFile); #ifdef Q_OS_WIN // potentially strip leading slash if (url.isLocalFile() && !str.isEmpty() && str[0] == '/') { return str.mid(1); // expensive copying, but we'd like toString(...) to properly format everything first } #endif return str; } Path::Path() { } Path::Path(const QString& pathOrUrl) : Path(QUrl::fromUserInput(pathOrUrl, QString(), QUrl::DefaultResolution)) { } Path::Path(const QUrl& url) { if (!url.isValid()) { // empty or invalid Path return; } // we do not support urls with: // - fragments // - sub urls // - query // nor do we support relative urls if (url.hasFragment() || url.hasQuery() || url.isRelative() || url.path().isEmpty()) { // invalid qWarning("Path::init: invalid/unsupported Path encountered: \"%s\"", qPrintable(url.toDisplayString(QUrl::PreferLocalFile))); return; } if (!url.isLocalFile()) { // handle remote urls QString urlPrefix; urlPrefix += url.scheme(); urlPrefix += QLatin1String("://"); const QString user = url.userName(); if (!user.isEmpty()) { urlPrefix += user; urlPrefix += '@'; } urlPrefix += url.host(); if (url.port() != -1) { urlPrefix += ':' + QString::number(url.port()); } m_data << urlPrefix; } addPath(url.isLocalFile() ? url.toLocalFile() : url.path()); // support for root paths, they are valid but don't really contain any data if (m_data.isEmpty() || (isRemote() && m_data.size() == 1)) { m_data << QString(); } } Path::Path(const Path& other, const QString& child) : m_data(other.m_data) { if (isAbsolutePath(child)) { // absolute path: only share the remote part of @p other m_data.resize(isRemote() ? 1 : 0); } else if (!other.isValid() && !child.isEmpty()) { qWarning("Path::Path: tried to append relative path \"%s\" to invalid base", qPrintable(child)); return; } addPath(child); } static QString generatePathOrUrl(bool onlyPath, bool isLocalFile, const QVector& data) { // more or less a copy of QtPrivate::QStringList_join const int size = data.size(); if (size == 0) { return QString(); } int totalLength = 0; // separators: '/' totalLength += size; // skip Path segment if we only want the path int start = (onlyPath && !isLocalFile) ? 1 : 0; // path and url prefix for (int i = start; i < size; ++i) { totalLength += data.at(i).size(); } // build string representation QString res; res.reserve(totalLength); #ifdef Q_OS_WIN if (start == 0 && isLocalFile) { Q_ASSERT(data.at(0).endsWith(':')); // assume something along "C:" res += data.at(0); start++; } #endif for (int i = start; i < size; ++i) { if (i || isLocalFile) { res += '/'; } res += data.at(i); } return res; } QString Path::pathOrUrl() const { return generatePathOrUrl(false, isLocalFile(), m_data); } QString Path::path() const { return generatePathOrUrl(true, isLocalFile(), m_data); } QString Path::toLocalFile() const { return isLocalFile() ? path() : QString(); } QString Path::relativePath(const Path& path) const { if (!path.isValid()) { return QString(); } if (!isValid() || remotePrefix() != path.remotePrefix()) { // different remote destinations or we are invalid, return input as-is return path.pathOrUrl(); } // while I'd love to use QUrl::relativePath here, it seems to behave pretty // strangely, and adds unexpected "./" at the start for example // so instead, do it on our own based on _relativePath in kurl.cpp // this should also be more performant I think // Find where they meet int level = isRemote() ? 1 : 0; const int maxLevel = qMin(m_data.count(), path.m_data.count()); while(level < maxLevel && m_data.at(level) == path.m_data.at(level)) { ++level; } // Need to go down out of our path to the common branch. // but keep in mind that e.g. '/' paths have an empty name int backwardSegments = m_data.count() - level; if (backwardSegments && level < maxLevel && m_data.at(level).isEmpty()) { --backwardSegments; } // Now up up from the common branch to the second path. int forwardSegmentsLength = 0; for (int i = level; i < path.m_data.count(); ++i) { forwardSegmentsLength += path.m_data.at(i).length(); // slashes if (i + 1 != path.m_data.count()) { forwardSegmentsLength += 1; } } QString relativePath; relativePath.reserve((backwardSegments * 3) + forwardSegmentsLength); for(int i = 0; i < backwardSegments; ++i) { relativePath.append(QLatin1String("../")); } for (int i = level; i < path.m_data.count(); ++i) { relativePath.append(path.m_data.at(i)); if (i + 1 != path.m_data.count()) { relativePath.append(QLatin1Char('/')); } } Q_ASSERT(relativePath.length() == ((backwardSegments * 3) + forwardSegmentsLength)); return relativePath; } static bool isParentPath(const QVector& parent, const QVector& child, bool direct) { if (direct && child.size() != parent.size() + 1) { return false; } else if (!direct && child.size() <= parent.size()) { return false; } for (int i = 0; i < parent.size(); ++i) { if (child.at(i) != parent.at(i)) { // support for trailing '/' if (i + 1 == parent.size() && parent.at(i).isEmpty()) { return true; } // otherwise we take a different branch here return false; } } return true; } bool Path::isParentOf(const Path& path) const { if (!isValid() || !path.isValid() || remotePrefix() != path.remotePrefix()) { return false; } return isParentPath(m_data, path.m_data, false); } bool Path::isDirectParentOf(const Path& path) const { if (!isValid() || !path.isValid() || remotePrefix() != path.remotePrefix()) { return false; } return isParentPath(m_data, path.m_data, true); } QString Path::remotePrefix() const { return isRemote() ? m_data.first() : QString(); } bool Path::operator<(const Path& other) const { const int size = m_data.size(); const int otherSize = other.m_data.size(); const int toCompare = qMin(size, otherSize); // compare each Path segment in turn and try to return early for (int i = 0; i < toCompare; ++i) { int comparison = m_data.at(i).compare(other.m_data.at(i)); if (comparison == 0) { // equal, try next segment continue; } else { // return whether our segment is less then the other one return comparison < 0; } } // when we reach this point, all elements that we compared where equal // thus return whether we have less items than the other Path return size < otherSize; } QUrl Path::toUrl() const { return QUrl::fromUserInput(pathOrUrl()); } bool Path::isLocalFile() const { // if the first data element contains a '/' it is a Path prefix return !m_data.isEmpty() && !m_data.first().contains(QLatin1Char('/')); } bool Path::isRemote() const { return !m_data.isEmpty() && m_data.first().contains(QLatin1Char('/')); } QString Path::lastPathSegment() const { // remote Paths are offset by one, thus never return the first item of them as file name if (m_data.isEmpty() || (!isLocalFile() && m_data.size() == 1)) { return QString(); } return m_data.last(); } void Path::setLastPathSegment(const QString& name) { // remote Paths are offset by one, thus never return the first item of them as file name if (m_data.isEmpty() || (!isLocalFile() && m_data.size() == 1)) { // append the name to empty Paths or remote Paths only containing the Path prefix m_data.append(name); } else { // overwrite the last data member m_data.last() = name; } } static void cleanPath(QVector* data, const bool isRemote) { if (data->isEmpty()) { return; } const int startOffset = isRemote ? 1 : 0; const auto start = data->begin() + startOffset; auto it = start; while(it != data->end()) { if (*it == QLatin1String("..")) { if (it == start) { it = data->erase(it); } else { if (isWindowsDriveLetter(*(it - 1))) { it = data->erase(it); // keep the drive letter } else { it = data->erase(it - 1, it + 1); } } } else if (*it == QLatin1String(".")) { it = data->erase(it); } else { ++it; } } if (data->count() == startOffset) { data->append(QString()); } } // Optimized QString::split code for the specific Path use-case static QVarLengthArray splitPath(const QString &source) { QVarLengthArray list; int start = 0; int end = 0; while ((end = source.indexOf(QLatin1Char('/'), start)) != -1) { if (start != end) { list.append(source.mid(start, end - start)); } start = end + 1; } if (start != source.size()) { list.append(source.mid(start, -1)); } return list; } void Path::addPath(const QString& path) { if (path.isEmpty()) { return; } const auto& newData = splitPath(path); if (newData.isEmpty()) { if (m_data.size() == (isRemote() ? 1 : 0)) { // this represents the root path, we just turned an invalid path into it m_data << QString(); } return; } auto it = newData.begin(); if (!m_data.isEmpty() && m_data.last().isEmpty()) { // the root item is empty, set its contents and continue appending m_data.last() = *it; ++it; } std::copy(it, newData.end(), std::back_inserter(m_data)); cleanPath(&m_data, isRemote()); } Path Path::parent() const { if (m_data.isEmpty()) { return Path(); } Path ret(*this); if (m_data.size() == (1 + (isRemote() ? 1 : 0))) { // keep the root item, but clear it, otherwise we'd make the path invalid // or a URL a local path auto& root = ret.m_data.last(); if (!isWindowsDriveLetter(root)) { root.clear(); } } else { ret.m_data.pop_back(); } return ret; } bool Path::hasParent() const { const int rootIdx = isRemote() ? 1 : 0; return m_data.size() > rootIdx && !m_data[rootIdx].isEmpty(); } void Path::clear() { m_data.clear(); } Path Path::cd(const QString& dir) const { if (!isValid()) { return Path(); } return Path(*this, dir); } namespace KDevelop { uint qHash(const Path& path) { KDevHash hash; foreach (const QString& segment, path.segments()) { hash << qHash(segment); } return hash; } template static Path::List toPathList_impl(const Container& list) { Path::List ret; ret.reserve(list.size()); foreach (const auto& entry, list) { Path path(entry); if (path.isValid()) { ret << path; } } return ret; } Path::List toPathList(const QList& list) { return toPathList_impl(list); } Path::List toPathList(const QList< QString >& list) { return toPathList_impl(list); } } QDebug operator<<(QDebug s, const Path& string) { s.nospace() << string.pathOrUrl(); return s.space(); } namespace QTest { template<> char *toString(const Path &path) { return qstrdup(qPrintable(path.pathOrUrl())); } } diff --git a/util/processlinemaker.cpp b/util/processlinemaker.cpp index 0abc23237..ed84a7aae 100644 --- a/util/processlinemaker.cpp +++ b/util/processlinemaker.cpp @@ -1,133 +1,132 @@ /* This file is part of the KDE project Copyright 2002 John Firebaugh Copyright 2007 Andreas Pakulat Copyright 2007 Oswald Buddenhagen 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 "processlinemaker.h" #include #include -#include namespace KDevelop { class ProcessLineMakerPrivate { public: QByteArray stdoutbuf; QByteArray stderrbuf; ProcessLineMaker* p; QProcess* m_proc; explicit ProcessLineMakerPrivate( ProcessLineMaker* maker ) : p(maker) { } void slotReadyReadStdout() { stdoutbuf += m_proc->readAllStandardOutput(); processStdOut(); } static QStringList streamToStrings(QByteArray &data) { QStringList lineList; int pos; while ( (pos = data.indexOf('\n')) != -1) { if (pos > 0 && data.at(pos - 1) == '\r') lineList << QString::fromLocal8Bit(data, pos - 1); else lineList << QString::fromLocal8Bit(data, pos); data.remove(0, pos+1); } return lineList; } void processStdOut() { emit p->receivedStdoutLines(streamToStrings(stdoutbuf)); } void slotReadyReadStderr() { stderrbuf += m_proc->readAllStandardError(); processStdErr(); } void processStdErr() { emit p->receivedStderrLines(streamToStrings(stderrbuf)); } }; ProcessLineMaker::ProcessLineMaker(QObject* parent) : QObject(parent) , d( new ProcessLineMakerPrivate( this ) ) { } ProcessLineMaker::ProcessLineMaker( QProcess* proc, QObject* parent ) : QObject(parent) , d( new ProcessLineMakerPrivate( this ) ) { d->m_proc = proc; connect(proc, &QProcess::readyReadStandardOutput, this, [&] { d->slotReadyReadStdout(); } ); connect(proc, &QProcess::readyReadStandardError, this, [&] { d->slotReadyReadStderr(); } ); } ProcessLineMaker::~ProcessLineMaker() { delete d; } void ProcessLineMaker::slotReceivedStdout( const QByteArray& buffer ) { d->stdoutbuf += buffer; d->processStdOut(); } void ProcessLineMaker::slotReceivedStderr( const QByteArray& buffer ) { d->stderrbuf += buffer; d->processStdErr(); } void ProcessLineMaker::discardBuffers( ) { d->stderrbuf.truncate(0); d->stdoutbuf.truncate(0); } void ProcessLineMaker::flushBuffers() { if (!d->stdoutbuf.isEmpty()) emit receivedStdoutLines(QStringList(QString::fromLocal8Bit(d->stdoutbuf))); if (!d->stderrbuf.isEmpty()) emit receivedStderrLines(QStringList(QString::fromLocal8Bit(d->stderrbuf))); discardBuffers(); } } #include "moc_processlinemaker.cpp" diff --git a/util/richtextpushbutton.cpp b/util/richtextpushbutton.cpp index 4d3384ddd..fa5b3b921 100644 --- a/util/richtextpushbutton.cpp +++ b/util/richtextpushbutton.cpp @@ -1,118 +1,119 @@ /* Copyright 2010 Unknown Author (Qt Centre) 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 "richtextpushbutton.h" #include #include #include #include #include #include #include +#include using namespace KDevelop; RichTextPushButton::RichTextPushButton(QWidget *parent) : QPushButton(parent) { } void RichTextPushButton::setHtml(const QString &text) { htmlText = text; isRichText = true; QPalette palette; palette.setBrush(QPalette::ButtonText, Qt::transparent); setPalette(palette); } void RichTextPushButton::setText(const QString &text) { isRichText = false; QPushButton::setText(text); } QSize RichTextPushButton::sizeHint() const { if(!isRichText) { return QPushButton::sizeHint(); } else{ QTextDocument richTextLabel; richTextLabel.setHtml(htmlText); return richTextLabel.size().toSize(); } } void RichTextPushButton::paintEvent(QPaintEvent *event) { if (isRichText) { QStylePainter p(this); QRect buttonRect = rect(); QPoint point; QTextDocument richTextLabel; richTextLabel.setHtml(htmlText); QPixmap richTextPixmap(richTextLabel.size().width(), richTextLabel.size().height()); richTextPixmap.fill(Qt::transparent); QPainter richTextPainter(&richTextPixmap); richTextLabel.drawContents(&richTextPainter, richTextPixmap.rect()); if (!icon().isNull()) point = QPoint(buttonRect.x() + buttonRect.width() / 2 + iconSize().width() / 2 + 2, buttonRect.y() + buttonRect.height() / 2); else point = QPoint(buttonRect.x() + buttonRect.width() / 2 - 1, buttonRect.y() + buttonRect.height() / 2); buttonRect.translate(point.x() - richTextPixmap.width() / 2, point.y() - richTextPixmap.height() / 2); p.drawControl(QStyle::CE_PushButton, getStyleOption()); p.drawPixmap(buttonRect.left(), buttonRect.top(), richTextPixmap.width(), richTextPixmap.height(),richTextPixmap); } else QPushButton::paintEvent(event); } QStyleOptionButton RichTextPushButton::getStyleOption() const { QStyleOptionButton opt; opt.initFrom(this); opt.features = QStyleOptionButton::None; if (isFlat()) opt.features |= QStyleOptionButton::Flat; if (menu()) opt.features |= QStyleOptionButton::HasMenu; if (autoDefault() || isDefault()) opt.features |= QStyleOptionButton::AutoDefaultButton; if (isDefault()) opt.features |= QStyleOptionButton::DefaultButton; if (isDown() || (menu() && menu()->isVisible())) opt.state |= QStyle::State_Sunken; if (isChecked()) opt.state |= QStyle::State_On; if (!isFlat() && !isDown()) opt.state |= QStyle::State_Raised; if (!isRichText) opt.text = QPushButton::text(); opt.icon = icon(); opt.iconSize = iconSize(); return opt; } diff --git a/util/richtextpushbutton.h b/util/richtextpushbutton.h index 16a9734b4..7f09c9326 100644 --- a/util/richtextpushbutton.h +++ b/util/richtextpushbutton.h @@ -1,57 +1,58 @@ /* Copyright 2010 Unknown Author (Qt Centre) 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. */ #ifndef KDEVPLATFORM_RICHTEXTPUSHBUTTON_H #define KDEVPLATFORM_RICHTEXTPUSHBUTTON_H #include #include -#include #include "utilexport.h" - + +class QStyleOptionButton; + namespace KDevelop { class KDEVPLATFORMUTIL_EXPORT RichTextPushButton : public QPushButton { Q_OBJECT public: explicit RichTextPushButton(QWidget *parent = nullptr); void setHtml(const QString &text); void setText(const QString &text); QString text() const; QSize sizeHint() const override; signals: public slots: protected: void paintEvent(QPaintEvent *) override; private: QString htmlText; bool isRichText; QStyleOptionButton getStyleOption() const; }; } #endif // KDEVPLATFORM_RICHTEXTPUSHBUTTON_H diff --git a/util/tests/test_embeddedfreetree.cpp b/util/tests/test_embeddedfreetree.cpp index 5dfdb5b98..409164ed9 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 #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; explicit 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(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(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/util/tests/test_environment.cpp b/util/tests/test_environment.cpp index 167b14e7a..e6f2a6bac 100644 --- a/util/tests/test_environment.cpp +++ b/util/tests/test_environment.cpp @@ -1,81 +1,82 @@ /* * This file is part of KDevelop * * Copyright 2015 Artur Puzio * * 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_environment.h" #include "util/environmentprofilelist.h" -#include +#include +#include QTEST_MAIN(TestEnvironment); using ProcEnv = QMap; void TestEnvironment::testExpandVariables_data() { QTest::addColumn("env"); QTest::addColumn("expectedEnv"); QTest::newRow("no variables") << ProcEnv({}) << ProcEnv({}); QTest::newRow("simple variables") << ProcEnv{ {"VAR1","data"}, {"Var2","some other data"} } << ProcEnv({ {"VAR1","data"}, {"Var2","some other data"} }); QTest::newRow("PATH append and prepend") << ProcEnv({ {"PATH","/home/usr/bin:$PATH:/home/user/folder"} }) << ProcEnv({ {"PATH", "/home/usr/bin:/bin:/usr/bin:/home/user/folder"} }); QTest::newRow("\\$VAR") << ProcEnv({ {"MY_VAR","\\$PATH something \\$HOME"} }) << ProcEnv({ {"MY_VAR","$PATH something $HOME"} }); QTest::newRow("spaces, \\$VAR after $VAR") << ProcEnv({ {"MY_VAR","$PATH:$HOME something \\$HOME"} }) << ProcEnv({ {"MY_VAR","/bin:/usr/bin:/home/tom something $HOME"} }); QTest::newRow("VAR2=$VAR1") << ProcEnv({ {"VAR1","/some/path"},{"VAR2","$VAR1"} }) << ProcEnv({ {"VAR1","/some/path"},{"VAR2",""} }); } void TestEnvironment::testExpandVariables() { QFETCH(ProcEnv, env); QFETCH(ProcEnv, expectedEnv); QProcessEnvironment fakeSysEnv; fakeSysEnv.insert(QStringLiteral("PATH"),QStringLiteral("/bin:/usr/bin")); fakeSysEnv.insert(QStringLiteral("HOME"),QStringLiteral("/home/tom")); KDevelop::expandVariables(env, fakeSysEnv); for (auto it = expectedEnv.cbegin(); it!= expectedEnv.cend(); ++it) { QCOMPARE(env.value(it.key()), it.value()); } } diff --git a/util/tests/test_foregroundlock.cpp b/util/tests/test_foregroundlock.cpp index 929de01e2..0cbb7d2ad 100644 --- a/util/tests/test_foregroundlock.cpp +++ b/util/tests/test_foregroundlock.cpp @@ -1,89 +1,89 @@ /* 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 "test_foregroundlock.h" -#include +#include #include #include "../foregroundlock.h" QTEST_MAIN(KDevelop::TestForegroundLock) using namespace KDevelop; //BEGIN Helper Threads class TryLockThread : public QThread { Q_OBJECT public: void run() override { ForegroundLock lock(false); for(int i = 0; i < 1000; ++i) { if (lock.tryLock()) { lock.unlock(); } QThread::usleep(qrand() % 20); } } }; void TestForegroundLock::testTryLock_data() { QTest::addColumn("numThreads"); for (int i = 1; i <= 10; ++i) { QTest::newRow(qPrintable(QString::number(i))) << i; } } void TestForegroundLock::testTryLock() { QFETCH(int, numThreads); QList threads; for (int i = 0; i < numThreads; ++i) { threads << new TryLockThread; } ForegroundLock lock(true); foreach(TryLockThread* thread, threads) { thread->start(); } lock.unlock(); while(true) { bool running = false; foreach(TryLockThread* thread, threads) { if (thread->isRunning()) { running = true; break; } } if (!running) { break; } lock.relock(); QThread::usleep(10); lock.unlock(); } } #include "moc_test_foregroundlock.cpp" #include "test_foregroundlock.moc" diff --git a/util/tests/test_kdevformatsource.cpp b/util/tests/test_kdevformatsource.cpp index 207f34b1d..f48c4c0e3 100644 --- a/util/tests/test_kdevformatsource.cpp +++ b/util/tests/test_kdevformatsource.cpp @@ -1,255 +1,256 @@ /* Copyright 2016 Anton Anikin 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_kdevformatsource.h" #include "../kdevformatfile.h" -#include - +#include +#include +#include #include QTEST_MAIN(KDevelop::TestKdevFormatSource) using namespace KDevelop; void TestKdevFormatSource::testNotFound_data() { static const QStringList formatFileData = {}; QCOMPARE(initTest(formatFileData), true); for (const Source& source : m_sources) { QTest::newRow(source.path.toUtf8()) << source.path << false << false << false << source.lines; } } void TestKdevFormatSource::testNotFound() { runTest(); } void TestKdevFormatSource::testNoCommands_data() { static const QStringList formatFileData = {QStringLiteral("# some comment")}; QCOMPARE(initTest(formatFileData), true); for (const Source& source : m_sources) { QTest::newRow(source.path.toUtf8()) << source.path << true << false << false << source.lines; } } void TestKdevFormatSource::testNoCommands() { runTest(); } void TestKdevFormatSource::testNotMatch_data() { static const QStringList formatFileData = {QStringLiteral("notmatched.cpp : unused_command")}; QCOMPARE(initTest(formatFileData), true); for (const Source& source : m_sources) { QTest::newRow(source.path.toUtf8()) << source.path << true << true << false << source.lines; } } void TestKdevFormatSource::testNotMatch() { runTest(); } void TestKdevFormatSource::testMatch1_data() { static const QStringList formatFileData({ QStringLiteral("src1/source_1.cpp : cat $ORIGFILE | sed 's/foo/FOO/' > tmp && mv tmp $ORIGFILE"), QStringLiteral("src2/source_2.cpp : cat $ORIGFILE | sed 's/sqrt/std::sqrt/' > tmp && mv tmp $ORIGFILE"), QStringLiteral("*.cpp : cat $ORIGFILE | sed 's/z/Z/' > tmp && mv tmp $ORIGFILE"), QStringLiteral("notmatched.cpp : unused_command"), }); QCOMPARE(initTest(formatFileData), true); m_sources[0].lines.replaceInStrings("foo", "FOO"); m_sources[1].lines.replaceInStrings("sqrt", "std::sqrt"); m_sources[2].lines.replaceInStrings("z", "Z"); for (const Source& source : m_sources) { QTest::newRow(source.path.toUtf8()) << source.path << true << true << true << source.lines; } } void TestKdevFormatSource::testMatch1() { runTest(); } void TestKdevFormatSource::testMatch2_data() { static const QStringList formatFileData({QStringLiteral("cat $ORIGFILE | sed 's/;/;;/' > tmp && mv tmp $ORIGFILE")}); QCOMPARE(initTest(formatFileData), true); for (Source& source : m_sources) { source.lines.replaceInStrings(";", ";;"); QTest::newRow(source.path.toUtf8()) << source.path << true << true << true << source.lines; } } void TestKdevFormatSource::testMatch2() { runTest(); } bool TestKdevFormatSource::initTest(const QStringList& formatFileData) { QTest::addColumn("path"); QTest::addColumn("isFound"); QTest::addColumn("isRead"); QTest::addColumn("isApplied"); QTest::addColumn("lines"); QString workPath = QStandardPaths::standardLocations(QStandardPaths::TempLocation).first(); workPath += "/test_kdevformatsource/"; if (QDir(workPath).exists() && !QDir(workPath).removeRecursively()) { qDebug() << "unable to remove existing directory" << workPath; return false; } if (!mkPath(workPath)) return false; if (!mkPath(workPath + "src1")) return false; if (!mkPath(workPath + "src2")) return false; if (!QDir::setCurrent(workPath)) { qDebug() << "unable to set current directory to" << workPath; return false; } m_sources.resize(3); m_sources[0].path = workPath + "src1/source_1.cpp"; m_sources[0].lines = QStringList({ QStringLiteral("void foo(int x) {"), QStringLiteral(" printf(\"squared x = %d\\n\", x * x);"), QStringLiteral("}") }); m_sources[1].path = workPath + "src2/source_2.cpp"; m_sources[1].lines = QStringList({ QStringLiteral("void bar(double x) {"), QStringLiteral(" x = sqrt(x);"), QStringLiteral(" printf(\"sqrt(x) = %e\\n\", x);"), QStringLiteral("}") }); m_sources[2].path = workPath + "source_3.cpp"; m_sources[2].lines = QStringList({ QStringLiteral("void baz(double x, double y) {"), QStringLiteral(" double z = pow(x, y);"), QStringLiteral(" printf(\"x^y = %e\\n\", z);"), QStringLiteral("}") }); for (const Source& source : m_sources) { if (!writeLines(source.path, source.lines)) return false; } if (!formatFileData.isEmpty() && !writeLines(QStringLiteral("format_sources"), formatFileData)) return false; return true; } void TestKdevFormatSource::runTest() const { QFETCH(QString, path); QFETCH(bool, isFound); QFETCH(bool, isRead); QFETCH(bool, isApplied); QFETCH(QStringList, lines); KDevFormatFile formatFile(path, path); QCOMPARE(formatFile.find(), isFound); if (isFound) QCOMPARE(formatFile.read(), isRead); if (isRead) QCOMPARE(formatFile.apply(), isApplied); QStringList processedLines; QCOMPARE(readLines(path, processedLines), true); QCOMPARE(processedLines, lines); } bool TestKdevFormatSource::mkPath(const QString& path) const { if (!QDir().exists(path) && !QDir().mkpath(path)) { qDebug() << "unable to create directory" << path; return false; } return true; } bool TestKdevFormatSource::writeLines(const QString& path, const QStringList& lines) const { QFile outFile(path); if (!outFile.open(QIODevice::WriteOnly)) { qDebug() << "unable to open file" << path << "for writing"; return false; } QTextStream outStream(&outFile); for (const QString& line : lines) { outStream << line << "\n"; } outFile.close(); return true; } bool TestKdevFormatSource::readLines(const QString& path, QStringList& lines) const { QFile inFile(path); if (!inFile.open(QIODevice::ReadOnly)) { qDebug() << "unable to open file" << path << "for reading"; return false; } lines.clear(); QTextStream inStream(&inFile); while (!inStream.atEnd()) { lines += inStream.readLine(); } inFile.close(); return true; } diff --git a/util/tests/test_kdevvarlengtharray.cpp b/util/tests/test_kdevvarlengtharray.cpp index a82f2322c..c2d18cca3 100644 --- a/util/tests/test_kdevvarlengtharray.cpp +++ b/util/tests/test_kdevvarlengtharray.cpp @@ -1,99 +1,99 @@ /* 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 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 -#include +#include #include #include "../kdevvarlengtharray.h" struct TestValue { TestValue() : m_index(0) {} TestValue(const TestValue& other) { if (other.m_index) { int mustDo = 1; ++mustDo; } m_index = other.m_index; } uint m_index; }; class TestKDevVarLengthArray : public QObject { Q_OBJECT private slots: /** * Make sure that valgrind does not report any warnings here * about uninitialized member variables. */ void appendReallocIntegrity() { KDevVarLengthArray array; QCOMPARE(array.size(), 0); QCOMPARE(array.capacity(), 2); qDebug() << "append item 1"; array << TestValue(); qDebug() << "appended index is:" << array[0].m_index; QCOMPARE(array.size(), 1); QCOMPARE(array.capacity(), 2); qDebug() << "append item 2"; array << TestValue(); // should trigger the realloc qDebug() << "appended index is:" << array[1].m_index; QCOMPARE(array.size(), 2); QCOMPARE(array.capacity(), 2); qDebug() << "append item 3"; array << TestValue(); qDebug() << "appended index is:" << array[2].m_index; QCOMPARE(array.size(), 3); QCOMPARE(array.capacity(), 4); array.clear(); } void mixed() { KDevVarLengthArray array; array.append(1); array << 2; array.insert(0, 0); QCOMPARE(array.back(), 2); array.pop_back(); QCOMPARE(array.back(), 1); array.append(1); QVERIFY(array.removeOne(1)); QCOMPARE(array.toList(), QList() << 0 << 1); QCOMPARE(array.toVector(), QVector() << 0 << 1); array.insert(0, 42); QCOMPARE(array.toVector(), QVector() << 42 << 0 << 1); array.remove(0); QCOMPARE(array.toVector(), QVector() << 0 << 1); QVERIFY(array.contains(1)); QVERIFY(!array.contains(42)); QCOMPARE(array.back(), 1); QCOMPARE(array.indexOf(1), 1); QCOMPARE(array.indexOf(42), -1); } }; QTEST_MAIN(TestKDevVarLengthArray) #include "test_kdevvarlengtharray.moc" diff --git a/util/tests/test_objectlist.cpp b/util/tests/test_objectlist.cpp index ae726d429..8f8ea8772 100644 --- a/util/tests/test_objectlist.cpp +++ b/util/tests/test_objectlist.cpp @@ -1,82 +1,82 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) 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 "test_objectlist.h" #include "objectlist.h" -#include +#include QTEST_MAIN(TestObjectList); using namespace KDevelop; void TestObjectList::testBasicInterface() { ObjectList list; QObject o; list.append(&o); QCOMPARE(list.data().size(), 1); QCOMPARE(list.data().at(0), &o); QCOMPARE(list.data().value(0), &o); QCOMPARE(list.data().size(), 1); QCOMPARE(list.data().at(0), &o); QVERIFY(list.remove(&o)); QCOMPARE(list.data().size(), 0); //QCOMPARE(list.at(0)), &o); // would crash QVERIFY(!list.data().value(0)); QVERIFY(list.data().isEmpty()); // try removing it again QVERIFY(!list.remove(&o)); } void TestObjectList::testDeleteAll() { ObjectList list; QPointer p(new QObject); list.append(p.data()); list.deleteAll(); QVERIFY(!p); } void TestObjectList::testBehaviorOnDestruction() { QPointer p(new QObject); { ObjectList list; list.append(p.data()); // nothing is supposed to happen on destruction } QVERIFY(p); { ObjectList list(ObjectListTracker::CleanupWhenDone); list.append(p.data()); } QVERIFY(!p); } diff --git a/util/tests/test_path.cpp b/util/tests/test_path.cpp index 05ecb391c..648c7b39a 100644 --- a/util/tests/test_path.cpp +++ b/util/tests/test_path.cpp @@ -1,625 +1,625 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * Copyright 2015 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) 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 "test_path.h" #include #include #include #include -#include +#include QTEST_MAIN(TestPath); using namespace KDevelop; static const int FILES_PER_FOLDER = 10; static const int FOLDERS_PER_FOLDER = 5; static const int TREE_DEPTH = 5; template T stringToUrl(const QString& path) { return T(path); } template<> QStringList stringToUrl(const QString& path) { return path.split('/'); } template T childUrl(const T& parent, const QString& child) { return T(parent, child); } template<> QStringList childUrl(const QStringList& parent, const QString& child) { QStringList ret = parent; ret << child; return ret; } template<> QUrl childUrl(const QUrl& parent, const QString& child) { QUrl ret = parent; ret.setPath(ret.path() + '/' + child); return ret; } template QVector generateData(const T& parent, int level) { QVector ret; // files per folder for (int i = 0; i < FILES_PER_FOLDER; ++i) { const QString fileName = QStringLiteral("file%1.txt").arg(i); const T file = childUrl(parent, fileName); Q_ASSERT(!ret.contains(file)); ret << file; } // nesting depth if (level < TREE_DEPTH) { // folders per folder for (int i = 0; i < FOLDERS_PER_FOLDER; ++i) { const QString folderName = QStringLiteral("folder%1").arg(i); const T folder = childUrl(parent, folderName); Q_ASSERT(!ret.contains(folder)); ret << folder; ret += generateData(folder, level + 1); } } return ret; } template void runBenchmark() { QBENCHMARK { const T base = stringToUrl("/tmp/foo/bar"); generateData(base, 0); } } void TestPath::initTestCase() { // TODO: is this really needed? It doesn't seem like any kdevelop shell is needed AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestPath::cleanupTestCase() { TestCore::shutdown(); } void TestPath::bench_qurl() { runBenchmark(); } void TestPath::bench_qstringlist() { runBenchmark(); } void TestPath::bench_path() { runBenchmark(); } void TestPath::bench_fromLocalPath() { QFETCH(int, variant); const QString input(QStringLiteral("/foo/bar/asdf/bla/blub.h")); const int repeat = 1000; if (variant == 1) { QBENCHMARK { for(int i = 0; i < repeat; ++i) { Path path = Path(QUrl::fromLocalFile(input)); Q_UNUSED(path); } } } else if (variant == 2) { QBENCHMARK { for(int i = 0; i < repeat; ++i) { Path path = Path(input); Q_UNUSED(path); } } } else { QFAIL("unexpected variant"); } } void TestPath::bench_fromLocalPath_data() { QTest::addColumn("variant"); QTest::newRow("QUrl::fromLocalFile") << 1; QTest::newRow("QString") << 2; } void TestPath::bench_hash() { const Path path(QStringLiteral("/my/very/long/path/to/a/file.cpp")); QBENCHMARK { auto hash = qHash(path); Q_UNUSED(hash); } } /// Invoke @p op on URL @p base, but preserve drive letter if @p op removes it template QUrl preserveWindowsDriveLetter(const QUrl& base, Func op) { #ifndef Q_OS_WIN return op(base); #else // only apply to local files if (!base.isLocalFile()) { return op(base); } // save drive letter const QString windowsDriveLetter = base.toLocalFile().mid(0, 2); QUrl url = op(base); // restore drive letter if (url.toLocalFile().startsWith('/')) { url = QUrl::fromLocalFile(windowsDriveLetter + url.toLocalFile()); } return url; #endif } QUrl resolvedUrl(const QUrl& base, const QUrl& relative) { return preserveWindowsDriveLetter(base, [&](const QUrl& url) { return url.resolved(relative); }); } QUrl comparableUpUrl(const QUrl& url) { QUrl ret = preserveWindowsDriveLetter(url, [&](const QUrl& url) { return KIO::upUrl(url).adjusted(QUrl::RemovePassword); }); return ret.adjusted(QUrl::StripTrailingSlash); } void TestPath::testPath() { QFETCH(QString, input); QUrl url = QUrl::fromUserInput(input); url = url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments); Path optUrl(input); if (!url.password().isEmpty()) { QUrl urlNoPass = url.adjusted(QUrl::RemovePassword); QCOMPARE(optUrl.toUrl(), urlNoPass); } else { QCOMPARE(optUrl.toUrl(), url); } QCOMPARE(optUrl.isLocalFile(), url.isLocalFile()); QCOMPARE(optUrl.pathOrUrl(), toUrlOrLocalFile(url, QUrl::RemovePassword)); QCOMPARE(optUrl.isValid(), url.isValid()); QCOMPARE(optUrl.isEmpty(), url.isEmpty()); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path()); QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url)); QCOMPARE(optUrl.toLocalFile(), url.toLocalFile()); QCOMPARE(optUrl, Path(input)); QCOMPARE(optUrl, Path(optUrl)); QVERIFY(optUrl != Path(input + "/asdf")); if (url.isLocalFile() && !input.startsWith(QLatin1String("file://"))) { QCOMPARE(optUrl, Path(QUrl::fromLocalFile(input))); } QCOMPARE(optUrl, Path(url)); if (url.isValid()) { QVERIFY(optUrl.relativePath(optUrl).isEmpty()); Path relativePath(optUrl, QStringLiteral("foo/bar")); QCOMPARE(optUrl.relativePath(relativePath), QLatin1String("foo/bar")); QCOMPARE(relativePath.relativePath(optUrl), QLatin1String("../../")); QVERIFY(optUrl.isParentOf(relativePath)); QVERIFY(!relativePath.isParentOf(optUrl)); #ifndef Q_OS_WIN Path absolutePath(optUrl, QStringLiteral("/laa/loo")); QCOMPARE(absolutePath.path(), QLatin1String("/laa/loo")); QCOMPARE(url.resolved(QUrl(QStringLiteral("/laa/loo"))).path(), QLatin1String("/laa/loo")); Path absolutePath2(optUrl, QStringLiteral("/")); QCOMPARE(absolutePath2.path(), QLatin1String("/")); QCOMPARE(url.resolved(QUrl(QStringLiteral("/"))).path(), QLatin1String("/")); #endif Path unrelatedPath(QStringLiteral("https://test@blubasdf.com:12345/")); QCOMPARE(optUrl.relativePath(unrelatedPath), unrelatedPath.pathOrUrl()); QCOMPARE(unrelatedPath.relativePath(optUrl), optUrl.pathOrUrl()); QVERIFY(!unrelatedPath.isParentOf(optUrl)); QVERIFY(!optUrl.isParentOf(unrelatedPath)); } QCOMPARE(Path().relativePath(optUrl), optUrl.pathOrUrl()); QVERIFY(optUrl.relativePath(Path()).isEmpty()); QVERIFY(Path().relativePath(Path()).isEmpty()); QVERIFY(!optUrl.isParentOf(Path())); QVERIFY(!Path().isParentOf(optUrl)); QVERIFY(!Path().isParentOf(Path())); QVERIFY(!optUrl.isParentOf(optUrl)); QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.isLocalFile()); QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.remotePrefix().isEmpty()); url.setPath(url.path() + "/test/foo/bar"); if (url.scheme().isEmpty()) { url.setScheme(QStringLiteral("file")); } optUrl.addPath(QStringLiteral("test/foo/bar")); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path()); url = url.adjusted(QUrl::RemoveFilename); url.setPath(url.path() + "lalalala_adsf.txt"); optUrl.setLastPathSegment(QStringLiteral("lalalala_adsf.txt")); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path()); QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url)); QVERIFY(optUrl.parent().isDirectParentOf(optUrl)); QVERIFY(!optUrl.parent().parent().isDirectParentOf(optUrl)); #ifndef Q_OS_WIN Path a(QStringLiteral("/foo/bar/asdf/")); Path b(QStringLiteral("/foo/bar/")); QVERIFY(b.isDirectParentOf(a)); Path c(QStringLiteral("/foo/bar")); QVERIFY(c.isDirectParentOf(a)); #endif optUrl.clear(); url.clear(); QCOMPARE(optUrl.toUrl(), url); } void TestPath::testPath_data() { QTest::addColumn("input"); #ifndef Q_OS_WIN QTest::newRow("invalid") << ""; QTest::newRow("path") << "/tmp/foo/asdf.txt"; QTest::newRow("path-folder") << "/tmp/foo/asdf/"; QTest::newRow("root") << "/"; QTest::newRow("clean-path") << "/tmp/..///asdf/"; QTest::newRow("file") << "file:///tmp/foo/asdf.txt"; QTest::newRow("file-folder") << "file:///tmp/foo/bar/"; #else QTest::newRow("path") << "C:/tmp/foo/asdf.txt"; QTest::newRow("path-folder") << "C:/tmp/foo/asdf/"; QTest::newRow("root") << "C:/"; QTest::newRow("clean-path") << "C:/tmp/..///asdf/"; QTest::newRow("file") << "file:///C:/tmp/foo/asdf.txt"; QTest::newRow("file-folder") << "file:///C:/tmp/foo/bar/"; #endif QTest::newRow("remote-root") << "http://www.test.com/"; QTest::newRow("http") << "http://www.test.com/tmp/asdf.txt"; QTest::newRow("ftps") << "ftps://user@host.com/tmp/foo/asdf.txt"; QTest::newRow("password") << "ftps://user:password@host.com/tmp/asdf.txt"; QTest::newRow("port") << "http://localhost:8080/foo/bar/test.txt"; } void TestPath::testPathInvalid() { QFETCH(QString, input); Path url(input); QVERIFY(!url.isValid()); QVERIFY(url.isEmpty()); } void TestPath::testPathInvalid_data() { QTest::addColumn("input"); QTest::newRow("empty") << ""; QTest::newRow("fragment") << "http://test.com/#hello"; QTest::newRow("query") << "http://test.com/?hello"; QTest::newRow("suburl") << "file:///home/weis/kde.tgz#gzip:/#tar:/kdebase"; QTest::newRow("relative") << "../foo/bar"; QTest::newRow("name") << "asdfasdf"; QTest::newRow("remote-nopath") << "http://www.test.com"; } void TestPath::testPathOperators() { QFETCH(Path, left); QFETCH(Path, right); QFETCH(bool, equal); QFETCH(bool, less); bool greater = !equal && !less; QVERIFY(left == left); QVERIFY(right == right); QCOMPARE(left == right, equal); QCOMPARE(right == left, equal); QCOMPARE(left < right, less); QCOMPARE(left <= right, less || equal); QCOMPARE(left > right, greater); QCOMPARE(left >= right, greater || equal); QCOMPARE(right < left, greater); QCOMPARE(right <= left, greater || equal); QCOMPARE(right > left, less); QCOMPARE(right >= left, less || equal); } void TestPath::testPathOperators_data() { QTest::addColumn("left"); QTest::addColumn("right"); QTest::addColumn("equal"); QTest::addColumn("less"); Path a(QStringLiteral("/tmp/a")); Path b(QStringLiteral("/tmp/b")); Path c(QStringLiteral("/tmp/ac")); Path d(QStringLiteral("/d")); Path e(QStringLiteral("/tmp")); Path f(QStringLiteral("/tmp/")); Path invalid; QTest::newRow("a-b") << a << b << false << true; QTest::newRow("a-copy") << a << Path(a) << true << false; QTest::newRow("c-a") << c << a << false << false; QTest::newRow("c-invalid") << c << invalid << false << false; QTest::newRow("c-d") << c << d << false << false; QTest::newRow("e-f") << e << f << true << false; } void TestPath::testPathAddData() { QFETCH(QString, pathToAdd); const QStringList bases = { #ifndef Q_OS_WIN QStringLiteral("/"), QStringLiteral("/foo/bar/asdf/"), QStringLiteral("file:///foo/bar/asdf/"), #else "C:/", "C:/foo/bar/asdf/", "file:///C:/foo/bar/asdf/", #endif QStringLiteral("http://www.asdf.com/foo/bar/asdf/"), }; foreach(const QString& base, bases) { QUrl baseUrl = QUrl::fromUserInput(base); if (QDir::isRelativePath(pathToAdd)) { baseUrl = resolvedUrl(baseUrl, QUrl(pathToAdd)); } else { baseUrl.setPath(baseUrl.path() + pathToAdd); } baseUrl = baseUrl.adjusted(QUrl::NormalizePathSegments); // QUrl::StripTrailingSlash converts file:/// to file: which is not what we want if (baseUrl.path() != QLatin1String("/")) { baseUrl = baseUrl.adjusted(QUrl::StripTrailingSlash); } Path basePath(base); basePath.addPath(pathToAdd); QCOMPARE(basePath.pathOrUrl(), toUrlOrLocalFile(baseUrl)); QCOMPARE(basePath.toUrl(), baseUrl); } } void TestPath::testPathAddData_data() { QTest::addColumn("pathToAdd"); const QStringList paths = QStringList() << QStringLiteral("file.txt") << QStringLiteral("path/file.txt") << QStringLiteral("path//file.txt") << QStringLiteral("/absolute") << QStringLiteral("../") << QStringLiteral("..") << QStringLiteral("../../../") << QStringLiteral("./foo") << QStringLiteral("../relative") << QStringLiteral("../../relative") << QStringLiteral("../foo/../bar") << QStringLiteral("../foo/./bar") << QStringLiteral("../../../../../../../invalid"); foreach(const QString &path, paths) { QTest::newRow(qstrdup(path.toUtf8().constData())) << path; } } void TestPath::testPathBaseCtor() { QFETCH(QString, base); QFETCH(QString, subPath); QFETCH(QString, expected); const Path basePath(base); const Path path(basePath, subPath); QCOMPARE(path.pathOrUrl(), expected); } void TestPath::testPathBaseCtor_data() { QTest::addColumn("base"); QTest::addColumn("subPath"); QTest::addColumn("expected"); QTest::newRow("empty") << "" << "" << ""; QTest::newRow("empty-relative") << "" << "foo" << ""; #ifndef Q_OS_WIN QTest::newRow("root-empty") << "/" << "" << "/"; QTest::newRow("root-root") << "/" << "/" << "/"; QTest::newRow("root-relative") << "/" << "bar" << "/bar"; QTest::newRow("root-relative-dirty") << "/" << "bar//foo/a/.." << "/bar/foo"; QTest::newRow("path-relative") << "/foo/bar" << "bar/foo" << "/foo/bar/bar/foo"; QTest::newRow("path-absolute") << "/foo/bar" << "/bar/foo" << "/bar/foo"; #else QTest::newRow("root-empty") << "C:/" << "" << "C:"; QTest::newRow("root1-empty") << "C:" << "" << "C:"; QTest::newRow("root-root") << "C:/" << "C:/" << "C:"; QTest::newRow("root-relative") << "C:/" << "bar" << "C:/bar"; QTest::newRow("root1-relative") << "C:" << "bar" << "C:/bar"; QTest::newRow("root-relative-dirty") << "C:/" << "bar//foo/a/.." << "C:/bar/foo"; QTest::newRow("path-relative") << "C:/foo/bar" << "bar/foo" << "C:/foo/bar/bar/foo"; QTest::newRow("path-absolute") << "C:/foo/bar" << "C:/bar/foo" << "C:/bar/foo"; #endif QTest::newRow("remote-path-absolute") << "http://foo.com/foo/bar" << "/bar/foo" << "http://foo.com/bar/foo"; QTest::newRow("remote-path-relative") << "http://foo.com/foo/bar" << "bar/foo" << "http://foo.com/foo/bar/bar/foo"; } // there is no cd() in QUrl, emulate what KUrl did static bool cdQUrl(QUrl& url, const QString& path) { if (path.isEmpty() || !url.isValid()) { return false; } // have to append slash otherwise last segment is treated as a file name and not a directory if (!url.path().endsWith('/')) { url.setPath(url.path() + '/'); } url = url.resolved(QUrl(path)).adjusted(QUrl::RemoveFragment | QUrl::RemoveQuery); return true; } void TestPath::testPathCd() { QFETCH(QString, base); QFETCH(QString, change); Path path = base.isEmpty() ? Path() : Path(base); QUrl url = QUrl::fromUserInput(base); Path changed = path.cd(change); if (cdQUrl(url, change)) { QVERIFY(changed.isValid()); } url = url.adjusted(QUrl::NormalizePathSegments); QCOMPARE(changed.pathOrUrl(), toUrlOrLocalFile(url, QUrl::StripTrailingSlash)); } void TestPath::testPathCd_data() { QTest::addColumn("base"); QTest::addColumn("change"); const QVector bases{ QLatin1String(""), #ifndef Q_OS_WIN QStringLiteral("/foo"), QStringLiteral("/foo/bar/asdf"), #else "C:/foo", "C:/foo/bar/asdf", #endif QStringLiteral("http://foo.com/"), QStringLiteral("http://foo.com/foo"), QStringLiteral("http://foo.com/foo/bar/asdf") }; foreach (const QString& base, bases) { QTest::newRow(qstrdup(qPrintable(base + "-"))) << base << ""; QTest::newRow(qstrdup(qPrintable(base + "-.."))) << base << ".."; QTest::newRow(qstrdup(qPrintable(base + "-../"))) << base << "../"; QTest::newRow(qstrdup(qPrintable(base + "v../foo"))) << base << "../foo"; QTest::newRow(qstrdup(qPrintable(base + "-."))) << base << "."; QTest::newRow(qstrdup(qPrintable(base + "-./"))) << base << "./"; QTest::newRow(qstrdup(qPrintable(base + "-./foo"))) << base << "./foo"; QTest::newRow(qstrdup(qPrintable(base + "-./foo/bar"))) << base << "./foo/bar"; QTest::newRow(qstrdup(qPrintable(base + "-foo/.."))) << base << "foo/.."; QTest::newRow(qstrdup(qPrintable(base + "-foo/"))) << base << "foo/"; QTest::newRow(qstrdup(qPrintable(base + "-foo/../bar"))) << base << "foo/../bar"; #ifdef Q_OS_WIN if (!base.startsWith("C:/") ) { // only add next rows for remote URLs on Windows #endif QTest::newRow(qstrdup(qPrintable(base + "-/foo"))) << base << "/foo"; QTest::newRow(qstrdup(qPrintable(base + "-/foo/../bar"))) << base << "/foo/../bar"; #ifdef Q_OS_WIN } #endif } } void TestPath::testHasParent_data() { QTest::addColumn("input"); QTest::addColumn("hasParent"); QTest::newRow("empty") << QString() << false; QTest::newRow("/") << QStringLiteral("/") << false; QTest::newRow("/foo") << QStringLiteral("/foo") << true; QTest::newRow("/foo/bar") << QStringLiteral("/foo/bar") << true; QTest::newRow("//foo/bar") << QStringLiteral("//foo/bar") << true; QTest::newRow("http://foo.bar") << QStringLiteral("http://foo.bar") << false; QTest::newRow("http://foo.bar/") << QStringLiteral("http://foo.bar/") << false; QTest::newRow("http://foo.bar/asdf") << QStringLiteral("http://foo.bar/asdf") << true; QTest::newRow("http://foo.bar/asdf/asdf") << QStringLiteral("http://foo.bar/asdf/asdf") << true; } void TestPath::testHasParent() { QFETCH(QString, input); Path path(input); QTEST(path.hasParent(), "hasParent"); } void TestPath::QUrl_acceptance() { const QUrl baseLocal = QUrl(QStringLiteral("file:///foo.h")); QCOMPARE(baseLocal.isValid(), true); QCOMPARE(baseLocal.isRelative(), false); QCOMPARE(baseLocal, QUrl::fromLocalFile(QStringLiteral("/foo.h"))); QCOMPARE(baseLocal, QUrl::fromUserInput(QStringLiteral("/foo.h"))); QUrl relative(QStringLiteral("bar.h")); QCOMPARE(relative.isRelative(), true); QCOMPARE(baseLocal.resolved(relative), QUrl(QStringLiteral("file:///bar.h"))); QUrl relative2(QStringLiteral("/foo/bar.h")); QCOMPARE(relative2.isRelative(), true); QCOMPARE(baseLocal.resolved(relative2), QUrl(QStringLiteral("file:///foo/bar.h"))); const QUrl baseRemote = QUrl(QStringLiteral("http://foo.org/asdf/foo/asdf")); QCOMPARE(baseRemote.resolved(relative), QUrl(QStringLiteral("http://foo.org/asdf/foo/bar.h"))); } diff --git a/util/tests/test_stringhandler.cpp b/util/tests/test_stringhandler.cpp index e7c791d6f..ae22d4381 100644 --- a/util/tests/test_stringhandler.cpp +++ b/util/tests/test_stringhandler.cpp @@ -1,75 +1,75 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) 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 "test_stringhandler.h" #include "kdevstringhandler.h" -#include +#include QTEST_MAIN(TestStringHandler); using namespace KDevelop; Q_DECLARE_METATYPE(HtmlToPlainTextMode) void TestStringHandler::testHtmlToPlainText() { QFETCH(QString, html); QFETCH(HtmlToPlainTextMode, mode); QFETCH(QString, expectedPlainText); QString plainText = htmlToPlainText(html, mode); QCOMPARE(plainText, expectedPlainText); } void TestStringHandler::testHtmlToPlainText_data() { QTest::addColumn("html"); QTest::addColumn("mode"); QTest::addColumn("expectedPlainText"); QTest::newRow("simple-fast") << "

    bar()

    a
    foo
    " << KDevelop::FastMode << "bar() a foo"; QTest::newRow("simple-complete") << "

    bar()

    a
    foo
    " << KDevelop::CompleteMode << "bar() \na\nfoo"; } void TestStringHandler::testStripAnsiSequences() { QFETCH(QString, input); QFETCH(QString, expectedOutput); const auto output = stripAnsiSequences(input); QCOMPARE(output, expectedOutput); } void TestStringHandler::testStripAnsiSequences_data() { QTest::addColumn("input"); QTest::addColumn("expectedOutput"); QTest::newRow("simple") << QStringLiteral("foo bar:") << "foo bar:"; } \ No newline at end of file diff --git a/util/tests/test_texteditorhelpers.cpp b/util/tests/test_texteditorhelpers.cpp index 87e8dc4a2..4bdd8c964 100644 --- a/util/tests/test_texteditorhelpers.cpp +++ b/util/tests/test_texteditorhelpers.cpp @@ -1,92 +1,92 @@ /* * Copyright 2016 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) 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 "test_texteditorhelpers.h" #include "texteditorhelpers.h" -#include +#include QTEST_MAIN(TestKTextEditorHelpers); using namespace KDevelop; void TestKTextEditorHelpers::testExtractCursor() { QFETCH(QString, input); QFETCH(KTextEditor::Cursor, expectedCursor); QFETCH(QString, expectedPath); int pathLen; const auto cursor = KTextEditorHelpers::extractCursor(input, &pathLen); QCOMPARE(cursor, expectedCursor); QCOMPARE(input.mid(0, pathLen), expectedPath); } void TestKTextEditorHelpers::testExtractCursor_data() { QTest::addColumn("input"); QTest::addColumn("expectedCursor"); QTest::addColumn("expectedPath"); // valid input QTest::newRow("file") << QStringLiteral("widget.cpp") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp"); QTest::newRow("file:line") << QStringLiteral("widget.cpp:12") << KTextEditor::Cursor(11, 0) << QStringLiteral("widget.cpp"); QTest::newRow("file:line:column") << QStringLiteral("widget.cpp:12:5") << KTextEditor::Cursor(11, 4) << QStringLiteral("widget.cpp"); QTest::newRow("file:line") << QStringLiteral("widget.cpp#12") << KTextEditor::Cursor(11, 0) << QStringLiteral("widget.cpp"); QTest::newRow("file:line") << QStringLiteral("widget.cpp#L12") << KTextEditor::Cursor(11, 0) << QStringLiteral("widget.cpp"); QTest::newRow("file:line") << QStringLiteral("widget.cpp#n12") << KTextEditor::Cursor(11, 0) << QStringLiteral("widget.cpp"); // partially invalid input QTest::newRow("file:") << QStringLiteral("widget.cpp:") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp:"); QTest::newRow("file:") << QStringLiteral("widget.cpp#") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp#"); QTest::newRow("file:") << QStringLiteral("widget.cpp#L") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp#L"); QTest::newRow("file:") << QStringLiteral("widget.cpp#n") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp#n"); } diff --git a/util/widgetcolorizer.cpp b/util/widgetcolorizer.cpp index 142ba0c28..b28f30e4a 100644 --- a/util/widgetcolorizer.cpp +++ b/util/widgetcolorizer.cpp @@ -1,85 +1,84 @@ /* * Copyright 2015 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) 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 "widgetcolorizer.h" #include #include -#include #include #include #include #include #include #include #include using namespace KDevelop; QColor WidgetColorizer::blendForeground(QColor color, float ratio, const QColor& foreground, const QColor& background) { if (KColorUtils::luma(foreground) > KColorUtils::luma(background)) { // for dark color schemes, produce a fitting color first color = KColorUtils::tint(foreground, color, 0.5); } // adapt contrast return KColorUtils::mix(foreground, color, ratio); } QColor WidgetColorizer::blendBackground(QColor color, float ratio, const QColor& /*foreground*/, const QColor& background) { // adapt contrast return KColorUtils::mix(background, color, ratio); } void WidgetColorizer::drawBranches(const QTreeView* treeView, QPainter* painter, const QRect& rect, const QModelIndex& /*index*/, const QColor& baseColor) { QRect newRect(rect); newRect.setWidth(treeView->indentation()); painter->fillRect(newRect, baseColor); } QColor WidgetColorizer::colorForId(uint id, const QPalette& activePalette, bool forBackground) { const int high = 255; const int low = 100; auto color = QColor(qAbs(id % (high-low)), qAbs((id / 50) % (high-low)), qAbs((id / (50 * 50)) % (high-low))); const auto& foreground = activePalette.foreground().color(); const auto& background = activePalette.background().color(); if (forBackground) { return blendBackground(color, .5, foreground, background); } else { return blendForeground(color, .5, foreground, background); } } bool WidgetColorizer::colorizeByProject() { return KSharedConfig::openConfig()->group("UiSettings").readEntry("ColorizeByProject", true); } diff --git a/util/widgetcolorizer.h b/util/widgetcolorizer.h index a168a5297..c0fac135b 100644 --- a/util/widgetcolorizer.h +++ b/util/widgetcolorizer.h @@ -1,90 +1,89 @@ /* * Copyright 2015 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) 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 . * */ #pragma once #include #include class QColor; class QModelIndex; class QPainter; class QRect; class QPalette; -class QString; class QTreeView; namespace KDevelop { namespace WidgetColorizer { /** * Generate a new color by blending the input @p color with the foreground. * * This function also works nicely on dark color schemes, in contrast to * simply setting an alpha channel value on the color. * * @p color Input color to blend. * @p ratio Ratio to decide how strong to do the blending. * When set to 0 you get the foreground color as-is, when set to 1 * you get the input color as-is. */ KDEVPLATFORMUTIL_EXPORT QColor blendForeground(QColor color, float ratio, const QColor& foreground, const QColor& background); /** * Generate a new color by blending the input @p color with the background. * * This function also works nicely on dark color schemes, in contrast to * simply setting an alpha channel value on the color. * * @p color Input color to blend. * @p ratio Ratio to decide how strong to do the blending. * When set to 0 you get the background color as-is, when set to 1 * you get the input color as-is. */ KDEVPLATFORMUTIL_EXPORT QColor blendBackground(QColor color, float ratio, const QColor& foreground, const QColor& background); KDEVPLATFORMUTIL_EXPORT void drawBranches(const QTreeView* treeView, QPainter* painter, const QRect& rect, const QModelIndex& index, const QColor& color); /** * Return a random color fit for the active palette. * * @p id An id which can be generated e.g. by qHash. Same ids will return * the same color. * @p activePalette The palette to use for generating the color. * @p background If set to true, a background color will be returned, * otherwise a foreground color will be returned by default. */ KDEVPLATFORMUTIL_EXPORT QColor colorForId(uint id, const QPalette& activePalette, bool background = false); /** * Returns true when the setting is enabled to colorize widgets representing * files belonging to projects. */ KDEVPLATFORMUTIL_EXPORT bool colorizeByProject(); }; } diff --git a/vcs/dvcs/dvcsjob.cpp b/vcs/dvcs/dvcsjob.cpp index 481febf01..11945a5d9 100644 --- a/vcs/dvcs/dvcsjob.cpp +++ b/vcs/dvcs/dvcsjob.cpp @@ -1,333 +1,331 @@ /*************************************************************************** * 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(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/dvcs/dvcsjob.h b/vcs/dvcs/dvcsjob.h index 9e55c6e60..9eeb6b159 100644 --- a/vcs/dvcs/dvcsjob.h +++ b/vcs/dvcs/dvcsjob.h @@ -1,230 +1,230 @@ /*************************************************************************** * 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 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 . * ***************************************************************************/ #ifndef KDEVPLATFORM_DVCS_JOB_H #define KDEVPLATFORM_DVCS_JOB_H -#include #include #include #include #include "../vcsjob.h" class QDir; +class QStringList; struct DVcsJobPrivate; /** * This class is capable of running our dvcs commands. * Most of all DVcsJob are created in DVCS executors, but executed in DistributedVersionControlPlugin or * any managers like BranchManager. * @note Connect to KJob::result(KJob*) to be notified when the job finished. * * How to create DVcsJob: * @code * DVcsJob* job = new DVcsJob(vcsplugin); * * job->setDirectory(workDir); * *job << "git-rev-parse"; * foreach(const QString &arg, args) // *job << args can be used instead! * *job << arg; * return job; * * return error_cmd(i18n("could not create the job")); * @endcode * * Usage example 1: * @code * VcsJob* j = add(QList() << a << b << c, IBasicVersionControl::Recursive); * DVcsJob* job = qobject_cast(j); * connect(job, SIGNAL(result(KJob*) ), * this, SIGNAL(jobFinished(KJob*) )); * ICore::self()->runController()->registerJob(job); * @endcode * * Usage example 2, asyunchronous: * @code * DVcsJob* branchJob = d->branch(repo, baseBranch, newBranch); * * if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) * return true; * else * //something, maybe even just * return false * @endcode * * @author Robert Gruber * @author Evgeniy Ivanov */ namespace KDevelop { class KDEVPLATFORMVCS_EXPORT DVcsJob : public KDevelop::VcsJob { Q_OBJECT public: explicit DVcsJob(const QDir& workingDir, KDevelop::IPlugin* parent=nullptr, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose); ~DVcsJob() override; /** * Returns current working directory. */ QDir directory() const; /** * Call this method to set command to execute and its arguments. * @note Don't forget <<"one two"; is not the same as <<"one"<<"two"; Use one word(command, arg) per one QString! */ DVcsJob& operator<<(const QString& arg); /** * Overloaded convenience function. * @see operator<<(const QString& arg). */ DVcsJob& operator<<(const char* arg); /** * Overloaded convenience function. * @see operator<<(const QString& arg). */ DVcsJob& operator<<(const QStringList& args); /** * Overloaded operator << for url's, can be used to pass files and * makes arguments absolute to the process working directory * * Override if you need to treat paths beffore adding them as parameters. */ virtual DVcsJob& operator<<(const QUrl& arg); /** * @see operator<<(const QUrl& arg). */ DVcsJob& operator<<(const QList& args); /** * Call this method to start this job. * @note Default communication mode is KProcess::AllOutput. * @see Use setCommunicationMode() to override the default communication mode. */ void start() override; /** * In some cases it's needed to specify the communication mode between the * process and the job object. This is for instance done for the "git status" * command. If stdout and stderr are processed as separate streams, their signals * do not always get emitted in correct order by KProcess, which will lead to a * screwed up output. * @note Default communication mode is KProcess::SeparateChannels. */ void setCommunicationMode(KProcess::OutputChannelMode comm); /** * @return The command that is executed when calling start(). */ QStringList dvcsCommand() const; /** * @return The whole output of the job as a string. (Might fail on binary data) */ QString output() const; /** * @return The whole binary output of the job */ QByteArray rawOutput() const; /** * @return The whole binary stderr output of the job. */ QByteArray errorOutput() const; /** * Ignore a non-zero exit code depending on @p ignore. */ void setIgnoreError(bool ignore); // Begin: KDevelop::VcsJob /** * Sets executions results. * In most cases this method is used by IDVCSexecutor * @see fetchResults() */ virtual void setResults(const QVariant &res); /** * Returns execution results stored in QVariant. * Mostly used in vcscommitdialog. * @see setResults(const QVariant &res) */ QVariant fetchResults() override; /** * Returns JobStatus * @see KDevelop::VcsJob::JobStatus */ KDevelop::VcsJob::JobStatus status() const override; /** * Returns pointer to IPlugin (which was used to create a job). */ KDevelop::IPlugin* vcsPlugin() const override; // End: KDevelop::VcsJob KProcess *process(); void displayOutput(const QString& output); public Q_SLOTS: /** * Cancel slot. */ void cancel(); Q_SIGNALS: void readyForParsing(KDevelop::DVcsJob *job); protected Q_SLOTS: virtual void slotProcessError( QProcess::ProcessError ); private Q_SLOTS: void slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus); void slotReceivedStdout(); protected: bool doKill() override; private: void jobIsReady(); DVcsJobPrivate* const d; }; } #endif diff --git a/vcs/dvcs/dvcsplugin.cpp b/vcs/dvcs/dvcsplugin.cpp index 9296957cf..d2ff6b462 100644 --- a/vcs/dvcs/dvcsplugin.cpp +++ b/vcs/dvcs/dvcsplugin.cpp @@ -1,145 +1,140 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2007 Robert Gruber * * * * Adapted for DVCS (added templates) * * 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) 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 . * ***************************************************************************/ #ifndef DVCS_PLUGIN_CC #define DVCS_PLUGIN_CC #include "dvcsplugin.h" #include -#include -#include #include #include -#include #include #include #include #include #include #include #include #include #include #include "dvcsjob.h" #include "ui/dvcsimportmetadatawidget.h" #include "ui/branchmanager.h" #include -#include - namespace KDevelop { struct DistributedVersionControlPluginPrivate { explicit DistributedVersionControlPluginPrivate(DistributedVersionControlPlugin * pThis) : m_common(new VcsPluginHelper(pThis, pThis)) {} ~DistributedVersionControlPluginPrivate() { delete m_common; } VcsPluginHelper* m_common; }; //class DistributedVersionControlPlugin DistributedVersionControlPlugin::DistributedVersionControlPlugin(QObject *parent, const QString& componentName) : IPlugin(componentName, parent) , d(new DistributedVersionControlPluginPrivate(this)) {} DistributedVersionControlPlugin::~DistributedVersionControlPlugin() { //TODO: Find out why this crashes on the svn tests delete d->m_factory; delete d; } // End: KDevelop::IBasicVersionControl // Begin: KDevelop::IDistributedVersionControl // End: KDevelop::IDistributedVersionControl KDevelop::VcsImportMetadataWidget* DistributedVersionControlPlugin::createImportMetadataWidget(QWidget* parent) { return new DvcsImportMetadataWidget(parent); } KDevelop::ContextMenuExtension DistributedVersionControlPlugin::contextMenuExtension(Context* context) { d->m_common->setupFromContext(context); QList const & ctxUrlList = d->m_common->contextUrlList(); bool isWorkingDirectory = false; foreach(const QUrl &url, ctxUrlList) { if (isValidDirectory(url)) { isWorkingDirectory = true; break; } } if (!isWorkingDirectory) { // Not part of a repository return ContextMenuExtension(); } QMenu * menu = d->m_common->commonActions(); menu->addSeparator(); menu->addAction(i18n("Branches..."), this, SLOT(ctxBranchManager()))->setEnabled(ctxUrlList.count()==1); additionalMenuEntries(menu, ctxUrlList); ContextMenuExtension menuExt; menuExt.addAction(ContextMenuExtension::VcsGroup, menu->menuAction()); return menuExt; } void DistributedVersionControlPlugin::additionalMenuEntries(QMenu* /*menu*/, const QList& /*urls*/) {} static QString stripPathToDir(const QString &path) { QFileInfo info = QFileInfo(path); return info.isDir() ? info.absoluteFilePath() : info.absolutePath(); } void DistributedVersionControlPlugin::ctxBranchManager() { QList const & ctxUrlList = d->m_common->contextUrlList(); Q_ASSERT(!ctxUrlList.isEmpty()); ICore::self()->documentController()->saveAllDocuments(); BranchManager branchManager(stripPathToDir(ctxUrlList.front().toLocalFile()), this, core()->uiController()->activeMainWindow()); branchManager.exec(); } } #endif diff --git a/vcs/dvcs/tests/test_dvcsjob.cpp b/vcs/dvcs/tests/test_dvcsjob.cpp index 0c24829b4..482838773 100644 --- a/vcs/dvcs/tests/test_dvcsjob.cpp +++ b/vcs/dvcs/tests/test_dvcsjob.cpp @@ -1,59 +1,59 @@ /*************************************************************************** * 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) 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 "test_dvcsjob.h" -#include +#include #include #include #include using namespace KDevelop; void TestDVcsJob::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestDVcsJob::cleanupTestCase() { TestCore::shutdown(); } void TestDVcsJob::testJob() { KDevelop::DVcsJob* job = new KDevelop::DVcsJob(QDir::temp()); QVERIFY(job); QVERIFY(job->status() == KDevelop::VcsJob::JobNotStarted); //try the command like "echo -n test" //should fail, because command and arg are in one string. We can change opearator<<(QString) to split, //but it will be a wrong style to work with jobs. const QString echoCommand(QStringLiteral("echo -n test")); *job << echoCommand; QVERIFY(!job->exec()); QVERIFY(job->status() == KDevelop::VcsJob::JobFailed); QCOMPARE(job->dvcsCommand().join(QStringLiteral(";;")), echoCommand); } QTEST_MAIN(TestDVcsJob) diff --git a/vcs/dvcs/ui/branchmanager.cpp b/vcs/dvcs/ui/branchmanager.cpp index cbe5c29c2..5501b30ed 100644 --- a/vcs/dvcs/ui/branchmanager.cpp +++ b/vcs/dvcs/ui/branchmanager.cpp @@ -1,240 +1,238 @@ /*************************************************************************** * 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) 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 "branchmanager.h" -#include #include #include #include #include #include "../dvcsjob.h" #include "../dvcsplugin.h" #include #include "ui_branchmanager.h" #include "../../debug.h" #include "widgets/vcsdiffpatchsources.h" #include #include -#include #include #include #include #include using namespace KDevelop; BranchManager::BranchManager(const QString& repository, KDevelop::DistributedVersionControlPlugin* executor, QWidget *parent) : QDialog(parent) , m_repository(repository) , m_dvcPlugin(executor) { setWindowTitle(i18n("Branch Manager")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); m_ui = new Ui::BranchDialogBase; QWidget* w = new QWidget(this); m_ui->setupUi(w); mainLayout->addWidget(w); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &BranchManager::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &BranchManager::reject); mainLayout->addWidget(buttonBox); m_model = new BranchesListModel(this); m_model->initialize(m_dvcPlugin, QUrl::fromLocalFile(repository)); m_ui->branchView->setModel(m_model); QString branchName = m_model->currentBranch(); // apply initial selection QList< QStandardItem* > items = m_model->findItems(branchName); if (!items.isEmpty()) { m_ui->branchView->setCurrentIndex(items.first()->index()); } m_ui->newButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); connect(m_ui->newButton, &QPushButton::clicked, this, &BranchManager::createBranch); m_ui->deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); connect(m_ui->deleteButton, &QPushButton::clicked, this, &BranchManager::deleteBranch); m_ui->renameButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); connect(m_ui->renameButton, &QPushButton::clicked, this, &BranchManager::renameBranch); m_ui->checkoutButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); connect(m_ui->checkoutButton, &QPushButton::clicked, this, &BranchManager::checkoutBranch); // checkout branch on double-click connect(m_ui->branchView, &QListView::doubleClicked, this, &BranchManager::checkoutBranch); m_ui->mergeButton->setIcon(QIcon::fromTheme(QStringLiteral("merge"))); connect(m_ui->mergeButton, &QPushButton::clicked, this, &BranchManager::mergeBranch); m_ui->diffButton->setIcon(QIcon::fromTheme(QStringLiteral("text-x-patch"))); connect(m_ui->diffButton, &QPushButton::clicked, this, &BranchManager::diffFromBranch); } BranchManager::~BranchManager() { delete m_ui; } void BranchManager::createBranch() { const QModelIndex currentBranchIdx = m_ui->branchView->currentIndex(); if (!currentBranchIdx.isValid()) { KMessageBox::messageBox(this, KMessageBox::Error, i18n("You must select a base branch from the list before creating a new branch.")); return; } QString baseBranch = currentBranchIdx.data().toString(); bool branchNameEntered = false; QString newBranch = QInputDialog::getText(this, i18n("New branch"), i18n("Name of the new branch:"), QLineEdit::Normal, QString(), &branchNameEntered); if (!branchNameEntered) return; if (!m_model->findItems(newBranch).isEmpty()) { KMessageBox::messageBox(this, KMessageBox::Sorry, i18n("Branch \"%1\" already exists.\n" "Please, choose another name.", newBranch)); } else m_model->createBranch(baseBranch, newBranch); } void BranchManager::deleteBranch() { QString baseBranch = m_ui->branchView->selectionModel()->selection().indexes().first().data().toString(); if (baseBranch == m_model->currentBranch()) { KMessageBox::messageBox(this, KMessageBox::Sorry, i18n("Currently at the branch \"%1\".\n" "To remove it, please change to another branch.", baseBranch)); return; } int ret = KMessageBox::messageBox(this, KMessageBox::WarningYesNo, i18n("Are you sure you want to irreversibly remove the branch '%1'?", baseBranch)); if (ret == KMessageBox::Yes) m_model->removeBranch(baseBranch); } void BranchManager::renameBranch() { QModelIndex currentIndex = m_ui->branchView->currentIndex(); if (!currentIndex.isValid()) return; m_ui->branchView->edit(currentIndex); } void BranchManager::checkoutBranch() { QString branch = m_ui->branchView->currentIndex().data().toString(); if (branch == m_model->currentBranch()) { KMessageBox::messageBox(this, KMessageBox::Sorry, i18n("Already on branch \"%1\"\n", branch)); return; } qCDebug(VCS) << "Switching to" << branch << "in" << m_repository; KDevelop::VcsJob *branchJob = m_dvcPlugin->switchBranch(QUrl::fromLocalFile(m_repository), branch); // connect(branchJob, SIGNAL(finished(KJob*)), m_model, SIGNAL(resetCurrent())); ICore::self()->runController()->registerJob(branchJob); close(); } void BranchManager::mergeBranch() { const QModelIndex branchToMergeIdx = m_ui->branchView->currentIndex(); if (branchToMergeIdx.isValid()) { QString branchToMerge = branchToMergeIdx.data().toString(); if (m_model->findItems(branchToMerge).isEmpty()) { KMessageBox::messageBox(this, KMessageBox::Sorry, i18n("Branch \"%1\" doesn't exists.\n" "Please, choose another name.", branchToMerge)); } else { KDevelop::VcsJob* branchJob = m_dvcPlugin->mergeBranch(QUrl::fromLocalFile(m_repository), branchToMerge); ICore::self()->runController()->registerJob(branchJob); close(); } } else { KMessageBox::messageBox(this, KMessageBox::Error, i18n("You must select a branch to merge into current one from the list.")); } } void BranchManager::diffFromBranch() { const auto dest = m_model->currentBranch(); const auto src = m_ui->branchView->currentIndex().data().toString(); if (src == dest) { KMessageBox::messageBox(this, KMessageBox::Information, i18n("Already on branch \"%1\"\n", src)); return; } VcsRevision srcRev; srcRev.setRevisionValue(src, KDevelop::VcsRevision::GlobalNumber); // We have two options here: // * create a regular VcsRevision to represent the last commit on the current branch or // * create a special branch to reflect the staging area. I choosed this one. // If the staing area is clean it automatically defaults to the first option. const auto destRev = VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Working); const auto job = m_dvcPlugin->diff(QUrl::fromLocalFile(m_repository), srcRev, destRev); connect(job, &VcsJob::finished, this, &BranchManager::diffJobFinished); m_dvcPlugin->core()->runController()->registerJob(job); } void BranchManager::diffJobFinished(KJob* job) { auto vcsjob = qobject_cast(job); Q_ASSERT(vcsjob); if (vcsjob->status() != KDevelop::VcsJob::JobSucceeded) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), vcsjob->errorString(), i18n("Unable to retrieve diff.")); return; } auto diff = vcsjob->fetchResults().value(); if(diff.isEmpty()){ KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18n("There are no committed differences."), i18n("VCS support")); return; } auto patch = new VCSDiffPatchSource(diff); showVcsDiff(patch); close(); } diff --git a/vcs/interfaces/icontentawareversioncontrol.h b/vcs/interfaces/icontentawareversioncontrol.h index 57b92498b..53700a8ab 100644 --- a/vcs/interfaces/icontentawareversioncontrol.h +++ b/vcs/interfaces/icontentawareversioncontrol.h @@ -1,80 +1,79 @@ /* This file is part of KDevelop * * Copyright 2013 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef KDEVPLATFORM_ICONTENTAWAREVERSIONCONTROL_H #define KDEVPLATFORM_ICONTENTAWAREVERSIONCONTROL_H #include -#include #include namespace KTextEditor { class Document; } namespace KDevelop { class KDEVPLATFORMVCS_EXPORT CheckInRepositoryJob : public KJob { Q_OBJECT public: explicit CheckInRepositoryJob(KTextEditor::Document* document); ~CheckInRepositoryJob() override; KTextEditor::Document* document() const; public slots: /// Abort this request. void abort(); signals: void finished(bool canRecreate); protected: struct CheckInRepositoryJobPrivate* d; }; /** * This interface is used by version control systems which can tell whether a given * blob of data is stored in the repository or not, such as git. * This information is used to reload files automatically if that involves no data loss. */ class IContentAwareVersionControl { public: virtual ~IContentAwareVersionControl() {}; /** * @brief Determines whether the given data is stored in the VCS' repository. * * @param document Document to search for in the repository * @returns CheckInRepositoryJob request object to track get notified when this finishes. * The request object deletes itself after finished() was emitted. */ virtual CheckInRepositoryJob* isInRepository(KTextEditor::Document* document) = 0; }; } Q_DECLARE_INTERFACE( KDevelop::IContentAwareVersionControl, "org.kdevelop.IContentAwareVersionControl" ) #endif diff --git a/vcs/interfaces/ipatchsource.h b/vcs/interfaces/ipatchsource.h index 6e2939d27..b0f8c8cb6 100644 --- a/vcs/interfaces/ipatchsource.h +++ b/vcs/interfaces/ipatchsource.h @@ -1,112 +1,113 @@ /* 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. */ #ifndef KDEVPLATFORM_IPATCHSOURCE_H #define KDEVPLATFORM_IPATCHSOURCE_H #include +#include #include #include #include namespace KDevelop { ///Any entity may delete an IPatchSource based object at will, so it must always be referenced through a QPointer (Just use IPatchSource::Ptr). class KDEVPLATFORMVCS_EXPORT IPatchSource : public QObject { Q_OBJECT public: typedef QPointer Ptr; ///Name of the patch, that will be shown in a combo box. Should ///describe the patch in a useful way, for example "Difference to base in kdevplatform/language" virtual QString name() const = 0; ///Icon that will be shown with the patch virtual QIcon icon() const; ///Should tell if the patch is already applied on the local version. virtual bool isAlreadyApplied() const = 0; ///Explicit updating of the patch: If it is a dynamic patch, it ///should re-compare the files or whatever needs to be done ///If the patch has changed, patchChanged needs to be emitted virtual void update() = 0; ///Name of the patch file virtual QUrl file() const = 0; ///Should return the base-dir of the patch virtual QUrl baseDir() const = 0; ///Can return a custom widget that should be shown to the user with this patch ///The ownership of the widget is shared between the caller and the patch-source (both may delete it at will) ///The default implementation returns zero virtual QWidget* customWidget() const; ///May return a custom text for the "Finish Review" action. ///The default implementation returns QString(), which means that the default is used virtual QString finishReviewCustomText() const; ///Called when the user has reviewed and accepted this patch ///If canSelectFiles() returned true, @p selection will contain the list of selected files ///If this returns false, the review is not finished. virtual bool finishReview(QList selection); ///Called when the user has rejected this patch virtual void cancelReview(); ///Should return whether the user may cancel this review (cancelReview will be called when he does) ///The default implementation returns false virtual bool canCancel() const; ///Should return whether the user should be able to select files of the patch ///The files available for selection will be all files affected by the patch, and the files ///return by additionalSelectableFiles() The default implementation returns false virtual bool canSelectFiles() const; ///May return an additional list of selectable files together with short description strings for this patch ///The default implementation returns an empty list virtual QMap additionalSelectableFiles() const; /// Depth - number of directories to left-strip from paths in the patch - see "patch -p" /// Defaults to 0 virtual uint depth() const; Q_SIGNALS: ///Should be emitted whenever the patch has changed. void patchChanged(); }; class KDEVPLATFORMVCS_EXPORT IPatchReview { public: virtual ~IPatchReview(); enum ReviewMode { OpenAndRaise //Opens the related files in the review area, switches to that area, and raises the patch-review toolview }; ///Starts a review on the patch: Opens the patch and the files within the review area virtual void startReview(IPatchSource* patch, ReviewMode mode = OpenAndRaise) = 0; }; } Q_DECLARE_INTERFACE(KDevelop::IPatchReview, "org.kdevelop.IPatchReview") #endif // KDEVPLATFORM_IPATCHSOURCE_H diff --git a/vcs/models/brancheslistmodel.cpp b/vcs/models/brancheslistmodel.cpp index e05dd457a..37f00d062 100644 --- a/vcs/models/brancheslistmodel.cpp +++ b/vcs/models/brancheslistmodel.cpp @@ -1,212 +1,211 @@ /*************************************************************************** * 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: explicit 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(nullptr, KMessageBox::Sorry, i18n("Branch \"%1\" already exists.", newBranch)); return; } 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/tests/test_models.cpp b/vcs/models/tests/test_models.cpp index fc98e03a1..b31b064ca 100644 --- a/vcs/models/tests/test_models.cpp +++ b/vcs/models/tests/test_models.cpp @@ -1,142 +1,142 @@ /*************************************************************************** * Copyright 2011 Andrey Batyiev * * * * 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 "test_models.h" -#include +#include #include #include #include using namespace KDevelop; void TestModels::initTestCase() { AutoTestShell::init({"dummy"}); TestCore::initialize(); } void TestModels::cleanupTestCase() { TestCore::shutdown(); } void TestModels::testVcsFileChangesModel() { const auto indexForUrl = [](const VcsFileChangesModel* model, const QUrl& url) { return model->match(model->index(0, 0), VcsFileChangesModel::UrlRole, url, 1, Qt::MatchExactly).value(0); }; const auto statusInfo = [](const QModelIndex& idx) { return idx.data(VcsFileChangesModel::VcsStatusInfoRole).value(); }; QScopedPointer model(new VcsFileChangesModel); // Newly created model should be empty QVERIFY(model->rowCount() == 0); // Pull some files into QUrl filenames[] = { QUrl::fromLocalFile(QStringLiteral("foo")), QUrl::fromLocalFile(QStringLiteral("bar")), QUrl::fromLocalFile(QStringLiteral("pew")), QUrl::fromLocalFile(QStringLiteral("trash")) }; VcsStatusInfo::State states[] = {VcsStatusInfo::ItemAdded, VcsStatusInfo::ItemModified, VcsStatusInfo::ItemDeleted, VcsStatusInfo::ItemUpToDate}; VcsStatusInfo status; for(int i = 0; i < 3; i++) { status.setUrl(filenames[i]); status.setState(states[i]); model->updateState(status); QCOMPARE(model->rowCount(), (i+1)); } // Pulling up-to-date file doesn't change anything { status.setUrl(filenames[3]); status.setState(states[3]); model->updateState(status); QCOMPARE(model->rowCount(), 3); } // Check that all OK for(int i = 0; i < 3; i++) { QModelIndex idx = indexForUrl(model.data(), filenames[i]); QVERIFY(idx.isValid()); VcsStatusInfo info = statusInfo(idx); QVERIFY(info.url().isValid()); QCOMPARE(info.url(), filenames[i]); QCOMPARE(info.state(), states[i]); } // Pull it all again = nothing changed for(int i = 0; i < 3; i++) { status.setUrl(filenames[i]); status.setState(states[i]); model->updateState(status); QCOMPARE(model->rowCount(), 3); } // Check that all OK for(int i = 0; i < 3; i++) { QModelIndex item = indexForUrl(model.data(), filenames[i]); QVERIFY(item.isValid()); VcsStatusInfo info = statusInfo(item); QCOMPARE(info.url(), filenames[i]); QCOMPARE(info.state(), states[i]); } // Remove one file { states[1] = VcsStatusInfo::ItemUpToDate; status.setUrl(filenames[1]); status.setState(states[1]); model->updateState(status); QCOMPARE(model->rowCount(), 2); } // Check them all for(int i = 0; i < 3; i++) { if(states[i] != VcsStatusInfo::ItemUpToDate && states[i] != VcsStatusInfo::ItemUnknown) { QModelIndex item = indexForUrl(model.data(), filenames[i]); QVERIFY(item.isValid()); VcsStatusInfo info = statusInfo(item); QCOMPARE(info.url(), filenames[i]); QCOMPARE(info.state(), states[i]); } } // Delete them all model->removeRows(0, model->rowCount()); QCOMPARE(model->rowCount(), 0); // Pull it all again for(int i = 0; i < 3; i++) { status.setUrl(filenames[i]); status.setState(states[i]); model->updateState(status); } QCOMPARE(model->rowCount(), 2); } QTEST_MAIN(TestModels); diff --git a/vcs/models/vcsannotationmodel.cpp b/vcs/models/vcsannotationmodel.cpp index 1c01b8b88..a74065723 100644 --- a/vcs/models/vcsannotationmodel.cpp +++ b/vcs/models/vcsannotationmodel.cpp @@ -1,150 +1,149 @@ /*************************************************************************** * 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 "vcsannotationmodel.h" #include "../vcsannotation.h" #include "../vcsrevision.h" #include "../vcsjob.h" #include -#include #include #include #include #include #include #include #include #include namespace KDevelop { class VcsAnnotationModelPrivate { public: explicit VcsAnnotationModelPrivate( VcsAnnotationModel* q_ ) : q(q_) {} KDevelop::VcsAnnotation m_annotation; QHash m_brushes; VcsAnnotationModel* q; VcsJob* job; QColor foreground; QColor background; void addLines( KDevelop::VcsJob* job ) { if( job == this->job ) { foreach( const QVariant& v, job->fetchResults().toList() ) { if( v.canConvert() ) { VcsAnnotationLine l = v.value(); if( !m_brushes.contains( l.revision() ) ) { const int background_y = background.red()*0.299 + 0.587*background.green() + 0.114*background.blue(); int u = ( float(qrand()) / RAND_MAX ) * 255; int v = ( float(qrand()) / RAND_MAX ) * 255; float r = qMin(255.0, qMax(0.0, background_y + 1.402*(v-128))); float g = qMin(255.0, qMax(0.0, background_y - 0.344*(u-128) - 0.714*(v-128))); float b = qMin(255.0, qMax(0.0, background_y + 1.772*(u-128))); m_brushes.insert( l.revision(), QBrush( QColor( r, g, b ) ) ); } m_annotation.insertLine( l.lineNumber(), l ); emit q->lineChanged( l.lineNumber() ); } } } } }; VcsAnnotationModel::VcsAnnotationModel(VcsJob *job, const QUrl& url, QObject* parent, const QColor &foreground, const QColor &background) : d( new VcsAnnotationModelPrivate( this ) ) { setParent( parent ); d->m_annotation.setLocation( url ); d->job = job; d->foreground = foreground; d->background = background; qsrand( QDateTime().toTime_t() ); connect( d->job, &VcsJob::resultsReady,this, [&] (VcsJob* job) { d->addLines(job); } ); ICore::self()->runController()->registerJob( d->job ); } VcsAnnotationModel::~VcsAnnotationModel() { delete d; } static QString abbreviateLastName(const QString& author) { auto parts = author.split(' '); bool onlyOneFragment = parts.size() == 1 || ( parts.size() == 2 && parts.at(1).isEmpty() ); return onlyOneFragment ? parts.first() : parts.first() + QStringLiteral(" %1.").arg(parts.last()[0]); } QVariant VcsAnnotationModel::data( int line, Qt::ItemDataRole role ) const { if( line < 0 || !d->m_annotation.containsLine( line ) ) { return QVariant(); } KDevelop::VcsAnnotationLine aline = d->m_annotation.line( line ); if( role == Qt::ForegroundRole ) { return QVariant(QPen(d->foreground)); } if( role == Qt::BackgroundRole ) { return QVariant( d->m_brushes[aline.revision()] ); } else if( role == Qt::DisplayRole ) { return QVariant( QStringLiteral("%1 ").arg(aline.date().date().year()) + abbreviateLastName(aline.author()) ); } else if( role == Qt::UserRole ) // TODO KDE5: replace by KTextEditor::AnnotationModel::GroupIdentifierRole { return aline.revision().revisionValue(); } else if( role == Qt::ToolTipRole ) { return QVariant( i18n("Author: %1\nDate: %2\nCommit Message: %3", aline.author(), QLocale().toString( aline.date() ), aline.commitMessage() ) ); } return QVariant(); } VcsRevision VcsAnnotationModel::revisionForLine( int line ) const { ///FIXME: update the annotation bar on edit/reload somehow ///BUG: https://bugs.kde.org/show_bug.cgi?id=269757 if (!d->m_annotation.containsLine(line)) { return VcsRevision(); } Q_ASSERT(line > 0 && d->m_annotation.containsLine( line )); return d->m_annotation.line( line ).revision(); } } #include "moc_vcsannotationmodel.cpp" diff --git a/vcs/models/vcsannotationmodel.h b/vcs/models/vcsannotationmodel.h index 1b04a8aec..fb6184689 100644 --- a/vcs/models/vcsannotationmodel.h +++ b/vcs/models/vcsannotationmodel.h @@ -1,60 +1,59 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_VCSANNOTATIONMODEL_H #define KDEVPLATFORM_VCSANNOTATIONMODEL_H #include #include #include "../vcsrevision.h" #include -#include -class QModelIndex; +class QUrl; template class QList; namespace KDevelop { class VcsAnnotation; class VcsAnnotationLine; class VcsJob; class KDEVPLATFORMVCS_EXPORT VcsAnnotationModel : public KTextEditor::AnnotationModel { Q_OBJECT public: VcsAnnotationModel( VcsJob* job, const QUrl&, QObject*, const QColor& foreground = QColor(Qt::black), const QColor& background = QColor(Qt::white) ); ~VcsAnnotationModel() override; VcsRevision revisionForLine(int line) const; QVariant data( int line, Qt::ItemDataRole role = Qt::DisplayRole ) const override; private: class VcsAnnotationModelPrivate* const d; friend class VcsAnnotationModelPrivate; }; } #endif diff --git a/vcs/vcsannotation.h b/vcs/vcsannotation.h index a91c8866d..dcd077acf 100644 --- a/vcs/vcsannotation.h +++ b/vcs/vcsannotation.h @@ -1,161 +1,161 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Matthew Woehlke * * 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. */ #ifndef KDEVPLATFORM_VCSANNOTATION_H #define KDEVPLATFORM_VCSANNOTATION_H #include "vcsexport.h" -#include +#include class QString; class QDateTime; class QUrl; namespace KDevelop { class VcsRevision; /** * Annotation information for a line of a version controlled file */ class KDEVPLATFORMVCS_EXPORT VcsAnnotationLine { public: VcsAnnotationLine(); VcsAnnotationLine( const VcsAnnotationLine& ); virtual ~VcsAnnotationLine(); /** * @return the line number of this annotation line */ int lineNumber() const; /** * @return the text of this line */ QString text() const; /** * @return the author that last changed this line */ QString author() const; /** * @return the revision this line was last changed */ VcsRevision revision() const; /** * @return the date of the last change to this line */ QDateTime date() const; /** * @return the commit message of the revision in this line */ QString commitMessage() const; /** * set the line number of this annotation line * @param lineno the line number */ void setLineNumber( int lineno ); /** * set the text of this annotation line * @param text the text of the line */ void setText( const QString& text ); /** * set the author of this annotation line * @param author the author of the last change */ void setAuthor( const QString& author ); /** * set the revision of this annotation line * @param revision the revision of the last change */ void setRevision( const VcsRevision& revision ); /** * set the date of this annotation line * @param date the date of the last change */ void setDate( const QDateTime& date ); /** * set the commit message of the revision in this * line * @param msg the message of the commit */ void setCommitMessage( const QString& msg ); VcsAnnotationLine& operator=( const VcsAnnotationLine& rhs); private: class VcsAnnotationLinePrivate* d; }; /** * Annotations for a local file. * * This class lets the user fetch information for each line of a local file, * including date of last change, author of last change and revision of * last change to the line. */ class KDEVPLATFORMVCS_EXPORT VcsAnnotation { public: VcsAnnotation(); VcsAnnotation(const VcsAnnotation&); virtual ~VcsAnnotation(); /** * @return the local url of the file */ QUrl location() const; /** * @return the number of lines in the file */ int lineCount() const; /** * retrieve the annotation line for the given number */ VcsAnnotationLine line( int linenumber ) const; /** * insert a new line to list of lines using * the parameters * * @param lineno the line for which to insert the content * @param line the annotation line that should be inserted * */ void insertLine( int lineno, const VcsAnnotationLine& line ); /** * @param location the location of the file */ void setLocation( const QUrl& location ); bool containsLine( int lineno ) const; VcsAnnotation& operator=( const VcsAnnotation& rhs); private: class VcsAnnotationPrivate* const d; }; } Q_DECLARE_METATYPE( KDevelop::VcsAnnotation ) Q_DECLARE_METATYPE( KDevelop::VcsAnnotationLine ) #endif diff --git a/vcs/vcsevent.cpp b/vcs/vcsevent.cpp index fc67be0d4..f7afecd79 100644 --- a/vcs/vcsevent.cpp +++ b/vcs/vcsevent.cpp @@ -1,208 +1,209 @@ /*************************************************************************** * 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 "vcsevent.h" #include #include #include +#include #include "vcsrevision.h" namespace KDevelop { class VcsItemEventPrivate { public: QString location; QString sourceLocation; VcsRevision sourceRevision; VcsItemEvent::Actions actions; }; VcsItemEvent::VcsItemEvent() : d(new VcsItemEventPrivate) { } VcsItemEvent::~VcsItemEvent() { delete d; } VcsItemEvent::VcsItemEvent(const VcsItemEvent& rhs ) : d(new VcsItemEventPrivate) { d->actions = rhs.d->actions; d->sourceRevision = rhs.d->sourceRevision; d->sourceLocation = rhs.d->sourceLocation; d->location = rhs.d->location; } QString VcsItemEvent::repositoryLocation() const { return d->location; } QString VcsItemEvent::repositoryCopySourceLocation() const { return d->sourceLocation; } VcsRevision VcsItemEvent::repositoryCopySourceRevision() const { return d->sourceRevision; } VcsItemEvent::Actions VcsItemEvent::actions() const { return d->actions; } void VcsItemEvent::setRepositoryLocation( const QString& l ) { d->location = l; } void VcsItemEvent::setRepositoryCopySourceLocation( const QString& l ) { d->sourceLocation = l; } void VcsItemEvent::setRepositoryCopySourceRevision( const KDevelop::VcsRevision& rev ) { d->sourceRevision = rev; } void VcsItemEvent::setActions( VcsItemEvent::Actions a ) { d->actions = a; } VcsItemEvent& VcsItemEvent::operator=( const VcsItemEvent& rhs) { if(this == &rhs) return *this; d->actions = rhs.d->actions; d->sourceRevision = rhs.d->sourceRevision; d->sourceLocation = rhs.d->sourceLocation; d->location = rhs.d->location; return *this; } class VcsEventPrivate { public: VcsRevision revision; QString author; QString message; QDateTime date; QList items; }; VcsEvent::VcsEvent() : d(new VcsEventPrivate) { } VcsEvent::~VcsEvent() { delete d; } VcsEvent::VcsEvent( const VcsEvent& rhs ) : d(new VcsEventPrivate) { d->revision = rhs.d->revision; d->author = rhs.d->author; d->message = rhs.d->message; d->date = rhs.d->date; d->items = rhs.d->items; } VcsRevision VcsEvent::revision() const { return d->revision; } QString VcsEvent::author() const { return d->author; } QDateTime VcsEvent::date() const { return d->date; } QString VcsEvent::message() const { return d->message; } QList VcsEvent::items() const { return d->items; } void VcsEvent::setRevision( const VcsRevision& rev ) { d->revision = rev; } void VcsEvent::setAuthor( const QString& a ) { d->author = a; } void VcsEvent::setDate( const QDateTime& date ) { d->date = date; } void VcsEvent::setMessage(const QString& m ) { d->message = m; } void VcsEvent::setItems( const QList& l ) { d->items = l; } void VcsEvent::addItem(const VcsItemEvent& item) { d->items.append(item); } VcsEvent& VcsEvent::operator=( const VcsEvent& rhs) { if(this == &rhs) return *this; d->revision = rhs.d->revision; d->message = rhs.d->message; d->items = rhs.d->items; d->date = rhs.d->date; d->author = rhs.d->author; return *this; } } diff --git a/vcs/vcsjob.h b/vcs/vcsjob.h index 7786a0028..1590a1078 100644 --- a/vcs/vcsjob.h +++ b/vcs/vcsjob.h @@ -1,148 +1,147 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Matthew Woehlke * * 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. */ #ifndef KDEVPLATFORM_VCSJOB_H #define KDEVPLATFORM_VCSJOB_H #include #include "vcsexport.h" class QVariant; -class QString; namespace KDevelop { class IPlugin; /** * This class provides an extension of KJob to get various VCS-specific * information about the job. This includes the type, the state * and the results provided by the job. * */ class KDEVPLATFORMVCS_EXPORT VcsJob : public OutputJob { Q_OBJECT public: explicit VcsJob( QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose); virtual ~VcsJob(); /** * To easily check which type of job this is. * * @TODO: Check how this can be extended via plugins, maybe use QFlag? (not * QFlags!) */ enum JobType { Unknown = -1 /**< Unknown job type (default)*/, Add = 0 /**< An add job */, Remove = 1 /**< A remove job */, Copy = 2 /**< A copy job */, Move = 3 /**< A move job */, Diff = 4 /**< A diff job */, Commit = 5 /**< A commit job */, Update = 6 /**< An update job */, Merge = 7 /**< A merge job */, Resolve = 8 /**< A resolve job */, Import = 9 /**< An import job */, Checkout = 10 /**< A checkout job */, Log = 11 /**< A log job */, Push = 12 /**< A push job */, Pull = 13 /**< A pull job */, Annotate = 14 /**< An annotate job */, Clone = 15 /**< A clone job */, Status = 16 /**< A status job */, Revert = 17 /**< A revert job */, Cat = 18 /**< A cat job */, UserType = 1000 /**< A custom job */ }; /** * Simple enum to define how the job finished. */ enum JobStatus { JobRunning = 0 /**< The job is running */, JobSucceeded = 1 /**< The job succeeded */, JobCanceled = 2 /**< The job was cancelled */, JobFailed = 3 /**< The job failed */, JobNotStarted = 4 /**< The job is not yet started */ }; /** * This method will return all new results of the job. The actual data * type that is wrapped in the QVariant depends on the type of job. * * @note Results returned by a previous call to fetchResults are not * returned. */ virtual QVariant fetchResults() = 0; /** * Find out in which state the job is. It can be running, canceled, * failed or finished * * @return the status of the job * @see JobStatus */ virtual JobStatus status() const = 0; /** * Used to find out about the type of job. * * @return the type of job */ JobType type() const; /** * Used to get at the version control plugin. The plugin * can be used to get one of the interfaces to execute * more vcs actions, depending on this job's results * (like getting a diff for an entry in a log) */ virtual KDevelop::IPlugin* vcsPlugin() const = 0; /** * This can be used to set the type of the vcs job in subclasses. */ void setType( JobType ); Q_SIGNALS: /** * This signal is emitted when new results are available. Depending on * the plugin and the operation, it may be emitted only once when all * results are ready, or several times. */ void resultsReady( KDevelop::VcsJob* ); private Q_SLOTS: void delayedModelInitialize(); private: class VcsJobPrivate* const d; }; } #endif diff --git a/vcs/vcspluginhelper.h b/vcs/vcspluginhelper.h index 14b9ce05d..e15d89331 100644 --- a/vcs/vcspluginhelper.h +++ b/vcs/vcspluginhelper.h @@ -1,87 +1,85 @@ /*************************************************************************** * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_VCSPLUGINHELPER_H #define KDEVPLATFORM_VCSPLUGINHELPER_H #include "vcsexport.h" #include #include "vcsrevision.h" class KJob; -class QAction; -class QActionGroup; class QMenu; namespace KTextEditor { class View; class Document; } using KTextEditor::View; namespace KDevelop { class VcsDiff; class IPlugin; class IBasicVersionControl; class Context; class ContextMenuExtension; class VcsCommitDialog; class ProjectBaseItem; class KDEVPLATFORMVCS_EXPORT VcsPluginHelper : public QObject { Q_OBJECT public: VcsPluginHelper(IPlugin * parent, IBasicVersionControl * vcs); ~VcsPluginHelper() override; void setupFromContext(KDevelop::Context*); void addContextDocument(const QUrl& url); QList contextUrlList() const; QMenu* commonActions(); public Q_SLOTS: void commit(); void add(); void revert(); void history(const VcsRevision& rev = VcsRevision::createSpecialRevision( VcsRevision::Base )); void annotation(); void annotationContextMenuAboutToShow( KTextEditor::View* view, QMenu* menu, int line); void diffToBase(); void diffForRev(); void diffForRevGlobal(); void update(); void pull(); void push(); void diffJobFinished(KJob* job); void revertDone(KJob* job); void disposeEventually(KTextEditor::Document*); void disposeEventually(View*, bool); private Q_SLOTS: void delayedModificationWarningOn(); private: void diffForRev(const QUrl& url); struct VcsPluginHelperPrivate; QScopedPointer d; }; } // namespace KDevelop #endif diff --git a/vcs/vcsstatusinfo.cpp b/vcs/vcsstatusinfo.cpp index 8a01ef49d..35986495c 100644 --- a/vcs/vcsstatusinfo.cpp +++ b/vcs/vcsstatusinfo.cpp @@ -1,112 +1,109 @@ /*************************************************************************** * 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 "vcsstatusinfo.h" #include -#include -#include -#include -#include +#include namespace KDevelop { class VcsStatusInfoPrivate { public: int state; QUrl url; }; VcsStatusInfo::VcsStatusInfo() : d( new VcsStatusInfoPrivate) { d->state = VcsStatusInfo::ItemUnknown; } VcsStatusInfo::~VcsStatusInfo() { delete d; } VcsStatusInfo::VcsStatusInfo( const VcsStatusInfo& rhs ) : d(new VcsStatusInfoPrivate) { d->state = rhs.d->state; d->url = rhs.d->url; } VcsStatusInfo& VcsStatusInfo::operator=( const VcsStatusInfo& rhs) { if(this == &rhs) return *this; d->state = rhs.d->state; d->url = rhs.d->url; return *this; } bool VcsStatusInfo::operator==( const KDevelop::VcsStatusInfo& rhs) const { return ( d->state == rhs.d->state && d->url == rhs.d->url ); } bool VcsStatusInfo::operator!=( const KDevelop::VcsStatusInfo& rhs) const { return !(operator==(rhs)); } void VcsStatusInfo::setUrl( const QUrl& url ) { d->url = url; } void VcsStatusInfo::setExtendedState( int newstate ) { d->state = newstate; } void VcsStatusInfo::setState( VcsStatusInfo::State state ) { d->state = state; } int VcsStatusInfo::extendedState() const { return d->state; } QUrl VcsStatusInfo::url() const { return d->url; } VcsStatusInfo::State VcsStatusInfo::state() const { return VcsStatusInfo::State(d->state); } } QDebug operator<<(QDebug s, const KDevelop::VcsStatusInfo& statusInfo) { s.nospace() << statusInfo.state() << "@" << statusInfo.url(); return s.space(); } diff --git a/vcs/vcsstatusinfo.h b/vcs/vcsstatusinfo.h index 107c69c39..d0e250aee 100644 --- a/vcs/vcsstatusinfo.h +++ b/vcs/vcsstatusinfo.h @@ -1,102 +1,99 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Matthew Woehlke * * 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. */ #ifndef KDEVPLATFORM_VCSSTATUSINFO_H #define KDEVPLATFORM_VCSSTATUSINFO_H -#include -#include - #include "vcsexport.h" +#include -class QString; -class QStringList; +class QUrl; namespace KDevelop { /** * * Class that encapsulates status information * for one local url. * * The extendedState functions allow to transport * extended status information * * Note for VCS implementations: * If you want to use this class in queued signal/slot connections * you should call qRegisterMetaType() * in the constructor of the plugin class */ class KDEVPLATFORMVCS_EXPORT VcsStatusInfo { public: /** * Status of a local file */ enum State { ItemUnknown = 0 /**< No VCS information about a file is known (or file is not under VCS control). */, ItemUpToDate = 1 /**< Item was updated or it is already at up to date version. */, ItemAdded = 2 /**< Item was added to the repository but not committed. */, ItemModified = 3 /**< Item was modified locally. */, ItemDeleted = 4 /**< Item is scheduled to be deleted. */, ItemHasConflicts = 8 /**< Local version has conflicts that need to be resolved before commit. */, ItemUserState = 1000 /**< special states for individual vcs implementations should use this as base. */ }; VcsStatusInfo(); virtual ~VcsStatusInfo(); VcsStatusInfo(const VcsStatusInfo&); /** * retrieves the url of this status information item * @return the url */ QUrl url() const; /** * Change the url of this status information item * @param url the url */ void setUrl( const QUrl& url ); VcsStatusInfo::State state() const; void setState( VcsStatusInfo::State ); int extendedState() const; void setExtendedState( int ); VcsStatusInfo& operator=( const VcsStatusInfo& rhs); bool operator==( const KDevelop::VcsStatusInfo& rhs) const; bool operator!=( const KDevelop::VcsStatusInfo& rhs) const; private: class VcsStatusInfoPrivate* d; }; } Q_DECLARE_METATYPE( KDevelop::VcsStatusInfo ) KDEVPLATFORMVCS_EXPORT QDebug operator<<(QDebug s, const KDevelop::VcsStatusInfo& statusInfo); #endif diff --git a/vcs/widgets/vcsdiffpatchsources.cpp b/vcs/widgets/vcsdiffpatchsources.cpp index 42cf1d844..ba7ed73a6 100644 --- a/vcs/widgets/vcsdiffpatchsources.cpp +++ b/vcs/widgets/vcsdiffpatchsources.cpp @@ -1,318 +1,319 @@ /* 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 #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(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(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(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(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() { } diff --git a/vcs/widgets/vcsdiffpatchsources.h b/vcs/widgets/vcsdiffpatchsources.h index 796eb9eb0..0e162bdab 100644 --- a/vcs/widgets/vcsdiffpatchsources.h +++ b/vcs/widgets/vcsdiffpatchsources.h @@ -1,134 +1,137 @@ /* 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. */ /** * This is an internal header */ #ifndef KDEVPLATFORM_VCSDIFFPATCHSOURCES_H #define KDEVPLATFORM_VCSDIFFPATCHSOURCES_H #include #include "vcs/vcsstatusinfo.h" #include "vcs/vcsjob.h" #include +#include +#include + class KTextEdit; class KComboBox; namespace KDevelop { class VcsCommitDialog; class IBasicVersionControl; class VcsDiff; } class QWidget; class VCSDiffUpdater { public: virtual ~VCSDiffUpdater(); virtual KDevelop::VcsDiff update() const = 0; virtual KDevelop::IBasicVersionControl* vcs() const = 0; virtual QUrl url() const = 0; }; class KDEVPLATFORMVCS_EXPORT VCSStandardDiffUpdater : public VCSDiffUpdater { public: VCSStandardDiffUpdater(KDevelop::IBasicVersionControl* vcs, QUrl url); ~VCSStandardDiffUpdater() override; KDevelop::VcsDiff update() const override; KDevelop::IBasicVersionControl* vcs() const override { return m_vcs; } QUrl url() const override { return m_url; } private: KDevelop::IBasicVersionControl* m_vcs; QUrl m_url; }; class KDEVPLATFORMVCS_EXPORT VCSDiffPatchSource : public KDevelop::IPatchSource { Q_OBJECT public: /// The ownership of the updater is taken explicit VCSDiffPatchSource(VCSDiffUpdater* updater); explicit VCSDiffPatchSource(const KDevelop::VcsDiff& diff); ~VCSDiffPatchSource() override; QUrl baseDir() const override ; QUrl file() const override ; QString name() const override ; uint depth() const override ; void update() override ; bool isAlreadyApplied() const override { return true; } QMap additionalSelectableFiles() const override ; QUrl m_base, m_file; QString m_name; VCSDiffUpdater* m_updater; QList m_infos; QMap m_selectable; private: void updateFromDiff(KDevelop::VcsDiff diff); uint m_depth = 0; }; class KDEVPLATFORMVCS_EXPORT VCSCommitDiffPatchSource : public VCSDiffPatchSource { Q_OBJECT public: /// The ownership of the updater is taken explicit VCSCommitDiffPatchSource(VCSDiffUpdater* updater); ~VCSCommitDiffPatchSource() override ; QStringList oldMessages() const; bool canSelectFiles() const override ; QWidget* customWidget() const override ; QString finishReviewCustomText() const override ; bool canCancel() const override; void cancelReview() override; bool finishReview(QList< QUrl > selection) override ; QList infos() const { return m_infos; } Q_SIGNALS: void reviewFinished(QString message, QList selection); void reviewCancelled(QString message); public: QPointer m_commitMessageWidget; QPointer m_commitMessageEdit; KDevelop::IBasicVersionControl* m_vcs; KComboBox* m_oldMessages; public slots: void addMessageToHistory(const QString& message); void oldMessageChanged(QString); void jobFinished(KJob*); }; ///Sends the diff to the patch-review plugin. ///Returns whether the diff was shown successfully. bool KDEVPLATFORMVCS_EXPORT showVcsDiff(KDevelop::IPatchSource* vcsDiff); #endif // KDEVPLATFORM_VCSDIFFPATCHSOURCES_H diff --git a/vcs/widgets/vcseventwidget.h b/vcs/widgets/vcseventwidget.h index 9de4b83ec..8198ea310 100644 --- a/vcs/widgets/vcseventwidget.h +++ b/vcs/widgets/vcseventwidget.h @@ -1,52 +1,50 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Dukju Ahn * * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_VCSEVENTWIDGET_H #define KDEVPLATFORM_VCSEVENTWIDGET_H #include #include -class QPoint; -class QModelIndex; class QUrl; namespace KDevelop { class VcsRevision; class IBasicVersionControl; class KDEVPLATFORMVCS_EXPORT VcsEventWidget : public QWidget { Q_OBJECT public: VcsEventWidget( const QUrl& url, const VcsRevision& rev, KDevelop::IBasicVersionControl* iface, QWidget* parent = nullptr ); ~VcsEventWidget() override; private: Q_PRIVATE_SLOT(d, void diffToPrevious()) Q_PRIVATE_SLOT(d, void diffRevisions()) class VcsEventWidgetPrivate* const d; }; } #endif