diff --git a/debugger/breakpoint/breakpointmodel.cpp b/debugger/breakpoint/breakpointmodel.cpp index 654a72b24c..f0db4c2280 100644 --- a/debugger/breakpoint/breakpointmodel.cpp +++ b/debugger/breakpoint/breakpointmodel.cpp @@ -1,640 +1,640 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, see . */ #include "breakpointmodel.h" #include #include #include #include #include #include #include "../interfaces/icore.h" #include "../interfaces/idebugcontroller.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/idocument.h" #include "../interfaces/ipartcontroller.h" #include #include #include #include "util/debug.h" #include "breakpoint.h" #include #include #include #include #include #define IF_DEBUG(x) using namespace KDevelop; using namespace KTextEditor; namespace { IBreakpointController* breakpointController() { KDevelop::ICore* core = KDevelop::ICore::self(); if (!core) { return nullptr; } IDebugController* controller = core->debugController(); if (!controller) { return nullptr; } IDebugSession* session = controller->currentSession(); return session ? session->breakpointController() : nullptr; } } // anonymous namespace BreakpointModel::BreakpointModel(QObject* parent) : QAbstractTableModel(parent), m_dontUpdateMarks(false) { connect(this, &BreakpointModel::dataChanged, this, &BreakpointModel::updateMarks); if (KDevelop::ICore::self()->partController()) { //TODO remove if foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts()) slotPartAdded(p); connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &BreakpointModel::slotPartAdded); } connect (KDevelop::ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &BreakpointModel::textDocumentCreated); connect (KDevelop::ICore::self()->documentController(), &IDocumentController::documentSaved, this, &BreakpointModel::documentSaved); } BreakpointModel::~BreakpointModel() { qDeleteAll(m_breakpoints); } void BreakpointModel::slotPartAdded(KParts::Part* part) { - if (KTextEditor::Document* doc = dynamic_cast(part)) + if (auto doc = qobject_cast(part)) { MarkInterface *iface = dynamic_cast(doc); if( !iface ) return; iface->setMarkDescription((MarkInterface::MarkTypes)BreakpointMark, i18n("Breakpoint")); iface->setMarkPixmap((MarkInterface::MarkTypes)BreakpointMark, *breakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)PendingBreakpointMark, *pendingBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)ReachedBreakpointMark, *reachedBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)DisabledBreakpointMark, *disabledBreakpointPixmap()); iface->setEditableMarks( MarkInterface::Bookmark | BreakpointMark ); updateMarks(); } } void BreakpointModel::textDocumentCreated(KDevelop::IDocument* doc) { KTextEditor::MarkInterface *iface = qobject_cast(doc->textDocument()); if (iface) { // can't use new signal slot syntax here, MarkInterface is not a QObject connect(doc->textDocument(), SIGNAL( markChanged(KTextEditor::Document*, KTextEditor::Mark, KTextEditor::MarkInterface::MarkChangeAction)), this, SLOT(markChanged(KTextEditor::Document*, KTextEditor::Mark, KTextEditor::MarkInterface::MarkChangeAction))); connect(doc->textDocument(), SIGNAL(markContextMenuRequested(KTextEditor::Document*, KTextEditor::Mark, QPoint, bool&)), SLOT(markContextMenuRequested(KTextEditor::Document*, KTextEditor::Mark, QPoint, bool&))); } } void BreakpointModel::markContextMenuRequested(Document* document, Mark mark, const QPoint &pos, bool& handled) { int type = mark.type; qCDebug(DEBUGGER) << type; /* Is this a breakpoint mark, to begin with? */ if (!(type & AllBreakpointMarks)) return; Breakpoint *b = breakpoint(document->url(), mark.line); if (!b) { QMessageBox::critical(nullptr, i18n("Breakpoint not found"), i18n("Couldn't find breakpoint at %1:%2", document->url().toString(), mark.line)); return; } QMenu menu; QAction deleteAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Delete Breakpoint"), 0); QAction disableAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Disable Breakpoint"), 0); QAction enableAction(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("&Enable Breakpoint"), 0); menu.addAction(&deleteAction); if (b->enabled()) { menu.addAction(&disableAction); } else { menu.addAction(&enableAction); } QAction *a = menu.exec(pos); if (a == &deleteAction) { b->setDeleted(); } else if (a == &disableAction) { b->setData(Breakpoint::EnableColumn, Qt::Unchecked); } else if (a == &enableAction) { b->setData(Breakpoint::EnableColumn, Qt::Checked); } handled = true; } QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) return QVariant(); if (role == Qt::DecorationRole ) { if (section == 0) return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); else if (section == 1) return QIcon::fromTheme(QStringLiteral("system-switch-user")); } if (role == Qt::DisplayRole) { if (section == 0 || section == 1) return ""; if (section == 2) return i18n("Type"); if (section == 3) return i18n("Location"); if (section == 4) return i18n("Condition"); } if (role == Qt::ToolTipRole) { if (section == 0) return i18n("Active status"); if (section == 1) return i18n("State"); return headerData(section, orientation, Qt::DisplayRole); } return QVariant(); } Qt::ItemFlags BreakpointModel::flags(const QModelIndex &index) const { /* FIXME: all this logic must be in item */ if (!index.isValid()) return 0; if (index.column() == 0) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); if (index.column() == Breakpoint::LocationColumn || index.column() == Breakpoint::ConditionColumn) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); return static_cast(Qt::ItemIsEnabled | Qt::ItemIsSelectable); } QModelIndex BreakpointModel::breakpointIndex(KDevelop::Breakpoint* b, int column) { int row = m_breakpoints.indexOf(b); if (row == -1) return QModelIndex(); return index(row, column); } bool KDevelop::BreakpointModel::removeRows(int row, int count, const QModelIndex& parent) { if (count < 1 || (row < 0) || (row + count) > rowCount(parent)) return false; IBreakpointController* controller = breakpointController(); beginRemoveRows(parent, row, row+count-1); for (int i=0; i < count; ++i) { Breakpoint* b = m_breakpoints.at(row); b->m_deleted = true; if (controller) controller->breakpointAboutToBeDeleted(row); m_breakpoints.removeAt(row); b->m_model = 0; // 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("script-error")).pixmap(QSize(22,22), QIcon::Active, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::pendingBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("script-error")).pixmap(QSize(22,22), QIcon::Normal, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::reachedBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("script-error")).pixmap(QSize(22,22), QIcon::Selected, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::disabledBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("script-error")).pixmap(QSize(22,22), QIcon::Disabled, QIcon::Off); return &pixmap; } void BreakpointModel::toggleBreakpoint(const QUrl& url, const KTextEditor::Cursor& cursor) { Breakpoint *b = breakpoint(url, cursor.line()); if (b) { b->setDeleted(); } else { addCodeBreakpoint(url, cursor.line()); } } void BreakpointModel::reportChange(Breakpoint* breakpoint, Breakpoint::Column column) { // note: just a portion of Breakpoint::Column is displayed in this model! if (column >= 0 && column < columnCount()) { QModelIndex idx = breakpointIndex(breakpoint, column); Q_ASSERT(idx.isValid()); // make sure we don't pass invalid indices to dataChanged() emit dataChanged(idx, idx); } if (IBreakpointController* controller = breakpointController()) { int row = m_breakpoints.indexOf(breakpoint); Q_ASSERT(row != -1); controller->breakpointModelChanged(row, ColumnFlags(1 << column)); } scheduleSave(); } uint BreakpointModel::breakpointType(Breakpoint *breakpoint) { uint type = BreakpointMark; if (!breakpoint->enabled()) { type = DisabledBreakpointMark; } else if (breakpoint->hitCount() > 0) { type = ReachedBreakpointMark; } else if (breakpoint->state() == Breakpoint::PendingState) { type = PendingBreakpointMark; } return type; } void KDevelop::BreakpointModel::updateMarks() { if (m_dontUpdateMarks) return; //add marks foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (breakpoint->line() == -1) continue; IDocument *doc = ICore::self()->documentController()->documentForUrl(breakpoint->url()); if (!doc) continue; KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; uint type = breakpointType(breakpoint); IF_DEBUG( qCDebug(DEBUGGER) << type << breakpoint->url() << mark->mark(breakpoint->line()); ) doc->textDocument()->blockSignals(true); if (mark->mark(breakpoint->line()) & AllBreakpointMarks) { if (!(mark->mark(breakpoint->line()) & type)) { mark->removeMark(breakpoint->line(), AllBreakpointMarks); mark->addMark(breakpoint->line(), type); } } else { mark->addMark(breakpoint->line(), type); } doc->textDocument()->blockSignals(false); } //remove marks foreach (IDocument *doc, ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; doc->textDocument()->blockSignals(true); foreach (KTextEditor::Mark *m, mark->marks()) { if (!(m->type & AllBreakpointMarks)) continue; IF_DEBUG( qCDebug(DEBUGGER) << m->line << m->type; ) foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (doc->url() == breakpoint->url() && m->line == breakpoint->line()) { goto continueNextMark; } } mark->removeMark(m->line, AllBreakpointMarks); continueNextMark:; } doc->textDocument()->blockSignals(false); } } void BreakpointModel::documentSaved(KDevelop::IDocument* doc) { IF_DEBUG( qCDebug(DEBUGGER); ) foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->movingCursor()) { if (breakpoint->movingCursor()->document() != doc->textDocument()) continue; if (breakpoint->movingCursor()->line() == breakpoint->line()) continue; m_dontUpdateMarks = true; breakpoint->setLine(breakpoint->movingCursor()->line()); m_dontUpdateMarks = false; } } } void BreakpointModel::aboutToDeleteMovingInterfaceContent(KTextEditor::Document* document) { foreach (Breakpoint *breakpoint, m_breakpoints) { if (breakpoint->movingCursor() && breakpoint->movingCursor()->document() == document) { breakpoint->setMovingCursor(0); } } } void BreakpointModel::load() { KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); int count = breakpoints.readEntry("number", 0); if (count == 0) return; beginInsertRows(QModelIndex(), 0, count - 1); for (int i = 0; i < count; ++i) { if (!breakpoints.group(QString::number(i)).readEntry("kind", "").isEmpty()) { new Breakpoint(this, breakpoints.group(QString::number(i))); } } endInsertRows(); } void BreakpointModel::save() { m_dirty = false; KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); breakpoints.writeEntry("number", m_breakpoints.count()); int i = 0; foreach (Breakpoint *b, m_breakpoints) { KConfigGroup g = breakpoints.group(QString::number(i)); b->save(g); ++i; } breakpoints.sync(); } void BreakpointModel::scheduleSave() { if (m_dirty) return; m_dirty = true; QTimer::singleShot(0, this, SLOT(save())); } QList KDevelop::BreakpointModel::breakpoints() const { return m_breakpoints; } Breakpoint* BreakpointModel::breakpoint(int row) { if (row >= m_breakpoints.count()) return 0; return m_breakpoints.at(row); } Breakpoint* BreakpointModel::addCodeBreakpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::CodeBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QUrl& url, int line) { Breakpoint* n = addCodeBreakpoint(); n->setLocation(url, line); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QString& expression) { Breakpoint* n = addCodeBreakpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addWatchpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::WriteBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addWatchpoint(const QString& expression) { Breakpoint* n = addWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addReadWatchpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::ReadBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addReadWatchpoint(const QString& expression) { Breakpoint* n = addReadWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint() { beginInsertRows(QModelIndex(), m_breakpoints.count(), m_breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::AccessBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint(const QString& expression) { Breakpoint* n = addAccessWatchpoint(); n->setExpression(expression); return n; } void BreakpointModel::registerBreakpoint(Breakpoint* breakpoint) { Q_ASSERT(!m_breakpoints.contains(breakpoint)); int row = m_breakpoints.size(); m_breakpoints << breakpoint; if (IBreakpointController* controller = breakpointController()) { controller->breakpointAdded(row); } scheduleSave(); } Breakpoint* BreakpointModel::breakpoint(const QUrl& url, int line) { foreach (Breakpoint *b, m_breakpoints) { if (b->url() == url && b->line() == line) { return b; } } return 0; } diff --git a/debugger/interfaces/ivariablecontroller.cpp b/debugger/interfaces/ivariablecontroller.cpp index b53f38cb8f..270ca242e4 100644 --- a/debugger/interfaces/ivariablecontroller.cpp +++ b/debugger/interfaces/ivariablecontroller.cpp @@ -1,129 +1,129 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ivariablecontroller.h" #include "idebugsession.h" #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../variable/variablecollection.h" #include "util/debug.h" #include "iframestackmodel.h" namespace KDevelop { IVariableController::IVariableController(IDebugSession* parent) : QObject(parent), m_activeThread(-1), m_activeFrame(-1) { connect(parent, &IDebugSession::stateChanged, this, &IVariableController::stateChanged); } VariableCollection* IVariableController::variableCollection() { if (!ICore::self()) return 0; return ICore::self()->debugController()->variableCollection(); } IDebugSession* IVariableController::session() const { return static_cast(parent()); } void IVariableController::stateChanged(IDebugSession::DebuggerState state) { if (!ICore::self() || ICore::self()->shuttingDown()) { return; } if (state == IDebugSession::ActiveState) { //variables are now outdated, update them m_activeThread = -1; m_activeFrame = -1; } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { // Remove all locals. foreach (Locals *l, variableCollection()->allLocals()) { l->deleteChildren(); l->setHasMore(false); } for (int i=0; i < variableCollection()->watches()->childCount(); ++i) { - Variable *var = dynamic_cast(variableCollection()->watches()->child(i)); + Variable *var = qobject_cast(variableCollection()->watches()->child(i)); if (var) { var->setInScope(false); } } } } void IVariableController::updateIfFrameOrThreadChanged() { IFrameStackModel *sm = session()->frameStackModel(); if (sm->currentThread() != m_activeThread || sm->currentFrame() != m_activeFrame) { m_activeThread = sm->currentThread(); m_activeFrame = sm->currentFrame(); variableCollection()->root()->resetChanged(); update(); } } void IVariableController::handleEvent(IDebugSession::event_t event) { if (!variableCollection()) return; switch (event) { case IDebugSession::thread_or_frame_changed: qCDebug(DEBUGGER) << m_autoUpdate; if (!(m_autoUpdate & UpdateLocals)) { foreach (Locals *l, variableCollection()->allLocals()) { if (!l->isExpanded() && !l->childCount()) { l->setHasMore(true); } } } if (m_autoUpdate != UpdateNone) { updateIfFrameOrThreadChanged(); } break; default: break; } } void IVariableController::setAutoUpdate(QFlags autoUpdate) { IDebugSession::DebuggerState state = session()->state(); m_autoUpdate = autoUpdate; qCDebug(DEBUGGER) << m_autoUpdate; if (m_autoUpdate != UpdateNone && state == IDebugSession::PausedState) { update(); } } QFlags IVariableController::autoUpdate() { return m_autoUpdate; } } diff --git a/debugger/variable/variablecollection.cpp b/debugger/variable/variablecollection.cpp index 031cb9dd64..c1eba9dcae 100644 --- a/debugger/variable/variablecollection.cpp +++ b/debugger/variable/variablecollection.cpp @@ -1,544 +1,544 @@ /* * KDevelop Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "variablecollection.h" #include #include #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/idocumentcontroller.h" #include "../../interfaces/iuicontroller.h" #include "../../sublime/controller.h" #include "../../sublime/view.h" #include "../../interfaces/idebugcontroller.h" #include "../interfaces/idebugsession.h" #include "../interfaces/ivariablecontroller.h" #include "util/debug.h" #include "util/texteditorhelpers.h" #include "variabletooltip.h" #include namespace KDevelop { IDebugSession* currentSession() { return ICore::self()->debugController()->currentSession(); } IDebugSession::DebuggerState currentSessionState() { if (!currentSession()) return IDebugSession::NotStartedState; return currentSession()->state(); } bool hasStartedSession() { IDebugSession::DebuggerState s = currentSessionState(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; } Variable::Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : TreeItem(model, parent), - inScope_(true), topLevel_(true), changed_(false), showError_(false), m_format(Natural) + m_inScope(true), m_topLevel(true), m_changed(false), m_showError(false), m_format(Natural) { - expression_ = expression; + m_expression = expression; // FIXME: should not duplicate the data, instead overload 'data' // and return expression_ directly. if (display.isEmpty()) setData(QVector() << expression << QString() << QString()); else setData(QVector() << display << QString() << QString()); } QString Variable::expression() const { - return expression_; + return m_expression; } bool Variable::inScope() const { - return inScope_; + 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) { - topLevel_ = v; + m_topLevel = v; } void Variable::setInScope(bool v) { - inScope_ = v; + m_inScope = v; for (int i=0; i < childCount(); ++i) { - if (Variable *var = dynamic_cast(child(i))) { + if (Variable *var = qobject_cast(child(i))) { var->setInScope(v); } } reportChange(); } void Variable::setShowError (bool v) { - showError_ = v; + m_showError = v; reportChange(); } bool Variable::showError() { - return showError_; + return m_showError; } Variable::~Variable() { } void Variable::die() { removeSelf(); deleteLater(); } void Variable::setChanged(bool c) { - changed_=c; + m_changed=c; reportChange(); } void Variable::resetChanged() { setChanged(false); for (int i=0; i(childItem)) { + if (qobject_cast(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 (showError_) { + 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 (!inScope_) { + if (!m_inScope) { return scheme.foreground(KColorScheme::InactiveText).color(); } else if (isPotentialProblematicValue()) { return scheme.foreground(KColorScheme::NegativeText).color(); - } else if (changed_) { + } else if (m_changed) { return scheme.foreground(KColorScheme::NeutralText).color(); } } if (role == Qt::ToolTipRole) { return TreeItem::data(column, Qt::DisplayRole); } return TreeItem::data(column, role); } Watches::Watches(TreeModel* model, TreeItem* parent) : TreeItem(model, parent), finishResult_(0) { setData(QVector() << i18n("Auto") << QString()); } Variable* Watches::add(const QString& expression) { if (!hasStartedSession()) return 0; Variable* v = currentSession()->variableController()->createVariable( model(), this, expression); appendChild(v); v->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return v; } Variable *Watches::addFinishResult(const QString& convenienceVarible) { if( finishResult_ ) { removeFinishResult(); } finishResult_ = currentSession()->variableController()->createVariable( model(), this, convenienceVarible, QStringLiteral("$ret")); appendChild(finishResult_); finishResult_->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return finishResult_; } void Watches::removeFinishResult() { if (finishResult_) { finishResult_->die(); finishResult_ = 0; } } void Watches::resetChanged() { for (int i=0; i(childItem)) { + if (qobject_cast(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(dynamic_cast(child(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(dynamic_cast(i)); + Q_ASSERT(qobject_cast(i)); ret << static_cast(i); } return ret; } void Locals::resetChanged() { for (int i=0; i(childItem)) { + if (qobject_cast(childItem)) { static_cast(childItem)->resetChanged(); } } } VariablesRoot::VariablesRoot(TreeModel* model) -: TreeItem(model) + : TreeItem(model) + , m_watches(new Watches(model, this)) { - watches_ = new Watches(model, this); - appendChild(watches_, true); + appendChild(m_watches, true); } Locals* VariablesRoot::locals(const QString& name) { - if (!locals_.contains(name)) { - locals_[name] = new Locals(model(), this, name); - appendChild(locals_[name]); + if (!m_locals.contains(name)) { + m_locals[name] = new Locals(model(), this, name); + appendChild(m_locals[name]); } - return locals_[name]; + return m_locals[name]; } QHash VariablesRoot::allLocals() const { - return locals_; + return m_locals; } void VariablesRoot::resetChanged() { - watches_->resetChanged(); - foreach (Locals *l, locals_) { + 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) { - universe_ = new VariablesRoot(this); - setRootItem(universe_); + 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->activeTooltip_ && m_collection->activeTooltip_->variable()->expression() == expression) + 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->activeTooltip_ = new VariableToolTip(w, global+QPoint(30,30), expression); - m_collection->activeTooltip_->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, expressionRange)); + 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 237776e5a6..dc3801485d 100644 --- a/debugger/variable/variablecollection.h +++ b/debugger/variable/variablecollection.h @@ -1,254 +1,254 @@ /* * 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 GDBDebugger { class GdbTest; } namespace KDevelop { class VariableToolTip; class KDEVPLATFORMDEBUGGER_EXPORT Variable : public TreeItem { Q_OBJECT friend class GDBDebugger::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 changed_; } + bool isChanged() const { return m_changed; } void setChanged(bool c); void resetChanged(); public slots: void die(); protected: - bool topLevel() const { return topLevel_; } + bool topLevel() const { return m_topLevel; } private: // TreeItem overrides QVariant data(int column, int role) const override; private: bool isPotentialProblematicValue() const; - QString expression_; - bool inScope_; - bool topLevel_; - bool changed_; - bool showError_; + 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 GDBDebugger::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 watches_; } + Watches *watches() const { return m_watches; } Locals *locals(const QString &name = QStringLiteral("Locals")); QHash allLocals() const; void fetchMoreChildren() override {} void resetChanged(); private: - Watches *watches_; - QHash locals_; + 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 universe_; } - Watches* watches() const { return universe_->watches(); } - Locals* locals(const QString &name = i18n("Locals")) const { return universe_->locals(name); } - QHash allLocals() const { return universe_->allLocals(); } + 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* universe_; - QPointer activeTooltip_; + VariablesRoot* m_universe; + QPointer m_activeTooltip; bool m_widgetVisible; friend class VariableProvider; VariableProvider m_textHintProvider; }; } #endif diff --git a/debugger/variable/variabletooltip.cpp b/debugger/variable/variabletooltip.cpp index fb1a141951..6f9e3fe575 100644 --- a/debugger/variable/variabletooltip.cpp +++ b/debugger/variable/variabletooltip.cpp @@ -1,220 +1,221 @@ /* * 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: 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, QPoint position, const QString& identifier) : ActiveToolTip(parent, position) { setPalette( QApplication::palette() ); - model_ = new TreeModel(QVector() << i18n("Name") << i18n("Value") << i18n("Type"), + m_model = new TreeModel(QVector() << i18n("Name") << i18n("Value") << i18n("Type"), this); - TooltipRoot* tr = new TooltipRoot(model_); - model_->setRootItem(tr); - var_ = ICore::self()->debugController()->currentSession()-> + TooltipRoot* tr = new TooltipRoot(m_model); + m_model->setRootItem(tr); + m_var = ICore::self()->debugController()->currentSession()-> variableController()->createVariable( - model_, tr, identifier); - tr->init(var_); - var_->attachMaybe(this, "variableCreated"); + 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 - proxy_ = new QSortFilterProxyModel; - view_ = new AsyncTreeView(model_, proxy_, this); - proxy_->setSourceModel(model_); - view_->setModel(proxy_); - view_->header()->resizeSection(0, 150); - view_->header()->resizeSection(1, 90); - view_->setSelectionBehavior(QAbstractItemView::SelectRows); - view_->setSelectionMode(QAbstractItemView::SingleSelection); - view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - view_->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); - l->addWidget(view_); - - itemHeight_ = view_->indexRowSizeHint(model_->indexForItem(var_, 0)); - connect(view_->verticalScrollBar(), + 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); - selection_ = view_->selectionModel(); - selection_->select(model_->indexForItem(var_, 0), + 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) { - view_->resizeColumns(); + m_view->resizeColumns(); if (hasValue) { ActiveToolTip::showToolTip(this, 0.0); } else { close(); } } void VariableToolTip::slotLinkActivated(const QString& link) { - Variable* v = var_; - QItemSelection s = selection_->selection(); + Variable* v = m_var; + QItemSelection s = m_selection->selection(); if (!s.empty()) { QModelIndex index = s.front().topLeft(); - TreeItem *item = model_->itemForIndex(index); + const auto sourceIndex = m_proxy->mapToSource(index); + TreeItem *item = m_model->itemForIndex(sourceIndex); if (item) { - Variable* v2 = dynamic_cast(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*itemHeight_ < rect.bottom()) - resize(width(), height() + max*itemHeight_); + 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 - view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } } #include "variabletooltip.moc" diff --git a/debugger/variable/variabletooltip.h b/debugger/variable/variabletooltip.h index 070dd45079..1b23201e46 100644 --- a/debugger/variable/variabletooltip.h +++ b/debugger/variable/variabletooltip.h @@ -1,64 +1,64 @@ /* * 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, QPoint position, const QString& identifier); - Variable* variable() const { return var_; }; + 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* model_; - Variable* var_; - QItemSelectionModel* selection_; - int itemHeight_; - AsyncTreeView* view_; - QSortFilterProxyModel* proxy_; + 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 c0c1a21559..fb81082fc4 100644 --- a/debugger/variable/variablewidget.cpp +++ b/debugger/variable/variablewidget.cpp @@ -1,512 +1,512 @@ // ************************************************************************** // begin : Sun Aug 8 1999 // copyright : (C) 1999 by John Birch // email : jbb@kdevelop.org // ************************************************************************** // * Copyright 2006 Vladimir Prus // ************************************************************************** // * * // * This program is free software; you can redistribute it and/or modify * // * it under the terms of the GNU General Public License as published by * // * the Free Software Foundation; either version 2 of the License, or * // * (at your option) any later version. * // * * // ************************************************************************** #include "variablewidget.h" #include #include #include #include #include #include #include #include #include #include "../util/treemodel.h" #include "../../interfaces/icore.h" #include #include "../interfaces/ivariablecontroller.h" #include "variablecollection.h" #include "variablesortmodel.h" #include "util/debug.h" /** The variables widget is passive, and is invoked by the rest of the code via two main Q_SLOTS: - slotDbgStatus - slotCurrentFrame The first is received the program status changes and the second is received after current frame in the debugger can possibly changes. The widget has a list item for each frame/thread combination, with variables as children. However, at each moment only one item is shown. When handling the slotCurrentFrame, we check if variables for the current frame are available. If yes, we simply show the corresponding item. Otherwise, we fetch the new data from debugger. Fetching the data is done by emitting the produceVariablesInfo signal. In response, we get slotParametersReady and slotLocalsReady signal, in that order. The data is parsed and changed variables are highlighted. After that, we 'trim' variable items that were not reported by gdb -- that is, gone out of scope. */ // ************************************************************************** // ************************************************************************** // ************************************************************************** namespace KDevelop { VariableCollection *variableCollection() { return ICore::self()->debugController()->variableCollection(); } VariableWidget::VariableWidget(IDebugController* controller, QWidget *parent) -: QWidget(parent), variablesRoot_(controller->variableCollection()->root()) +: 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); - varTree_ = new VariableTree(controller, this, m_proxy); - setFocusProxy(varTree_); + m_varTree = new VariableTree(controller, this, m_proxy); + setFocusProxy(m_varTree); - watchVarEditor_ = new KHistoryComboBox( this ); + m_watchVarEditor = new KHistoryComboBox( this ); QVBoxLayout *topLayout = new QVBoxLayout(this); - topLayout->addWidget(varTree_, 10); - topLayout->addWidget(watchVarEditor_); + topLayout->addWidget(m_varTree, 10); + topLayout->addWidget(m_watchVarEditor); topLayout->setMargin(0); - connect(watchVarEditor_, static_cast(&KHistoryComboBox::returnPressed), + 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.
")); - watchVarEditor_->setWhatsThis( + m_watchVarEditor->setWhatsThis( i18n("Expression entry" "Type in expression to watch.")); } void VariableWidget::slotAddWatch(const QString &expression) { if (!expression.isEmpty()) { - watchVarEditor_->addToHistory(expression); + m_watchVarEditor->addToHistory(expression); qCDebug(DEBUGGER) << "Trying to add watch"; - Variable* v = variablesRoot_->watches()->add(expression); + 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); } - watchVarEditor_->clearEditText(); + m_watchVarEditor->clearEditText(); } } void VariableWidget::hideEvent(QHideEvent* e) { QWidget::hideEvent(e); variableCollection()->variableWidgetHidden(); } void VariableWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); variableCollection()->variableWidgetShown(); } // ************************************************************************** // ************************************************************************** // ************************************************************************** VariableTree::VariableTree(IDebugController *controller, VariableWidget *parent, QSortFilterProxyModel *proxy) : AsyncTreeView(controller->variableCollection(), proxy, parent) , m_proxy(proxy) #if 0 , activePopup_(0), toggleWatch_(0) #endif { setRootIsDecorated(true); setAllColumnsShowFocus(true); // setting proxy model m_model = static_cast(controller->variableCollection()); m_proxy->setSourceModel(m_model); setModel(m_proxy); setSortingEnabled(true); sortByColumn(VariableCollection::NameColumn, Qt::AscendingOrder); QModelIndex index = controller->variableCollection()->indexForItem( controller->variableCollection()->watches(), 0); setExpanded(index, true); m_signalMapper = new QSignalMapper(this); setupActions(); } VariableCollection* VariableTree::collection() const { Q_ASSERT(qobject_cast(static_cast(model())->sourceModel())); return static_cast(model()); } VariableTree::~VariableTree() { } void VariableTree::setupActions() { // TODO decorate this properly to make nice menu title m_contextMenuTitle = new QAction(this); m_contextMenuTitle->setEnabled(false); // make Format menu action group m_formatMenu = new QMenu(i18n("&Format"), this); QActionGroup *ag= new QActionGroup(m_formatMenu); QAction* act; act = new QAction(i18n("&Natural"), ag); act->setData(Variable::Natural); act->setShortcut(Qt::Key_N); m_formatMenu->addAction(act); act = new QAction(i18n("&Binary"), ag); act->setData(Variable::Binary); act->setShortcut(Qt::Key_B); m_formatMenu->addAction(act); act = new QAction(i18n("&Octal"), ag); act->setData(Variable::Octal); act->setShortcut(Qt::Key_O); m_formatMenu->addAction(act); act = new QAction(i18n("&Decimal"), ag); act->setData(Variable::Decimal); act->setShortcut(Qt::Key_D); m_formatMenu->addAction(act); act = new QAction(i18n("&Hexadecimal"), ag); act->setData(Variable::Hexadecimal); act->setShortcut(Qt::Key_H); m_formatMenu->addAction(act); foreach(QAction* act, m_formatMenu->actions()) { act->setCheckable(true); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_signalMapper->setMapping(act, act->data().toInt()); connect(act, &QAction::triggered, m_signalMapper, static_cast(&QSignalMapper::map)); addAction(act); } connect(m_signalMapper, static_cast(&QSignalMapper::mapped), this, &VariableTree::changeVariableFormat); m_watchDelete = new QAction( QIcon::fromTheme(QStringLiteral("edit-delete")), i18n( "Remove Watch Variable" ), this); m_watchDelete->setShortcut(Qt::Key_Delete); m_watchDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(m_watchDelete); connect(m_watchDelete, &QAction::triggered, this, &VariableTree::watchDelete); m_copyVariableValue = new QAction(i18n("&Copy Value"), this); m_copyVariableValue->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_copyVariableValue->setShortcut(QKeySequence::Copy); connect(m_copyVariableValue, &QAction::triggered, this, &VariableTree::copyVariableValue); m_stopOnChange = new QAction(i18n("&Stop on Change"), this); connect(m_stopOnChange, &QAction::triggered, this, &VariableTree::stopOnChange); } Variable* VariableTree::selectedVariable() const { if (selectionModel()->selectedRows().isEmpty()) return 0; auto item = selectionModel()->currentIndex().data(TreeModel::ItemRole).value(); if (!item) return 0; - return dynamic_cast(item); + 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 (dynamic_cast(selectedVariable()->parent())) { + 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 (!dynamic_cast(selectedVariable()->parent())) 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 e4c6a421a7..22ecc9b86f 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; 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 *varTree_; - KHistoryComboBox *watchVarEditor_; - VariablesRoot *variablesRoot_; + 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/language/duchain/duchainutils.cpp b/language/duchain/duchainutils.cpp index 66b1ce9c50..55136fadff 100644 --- a/language/duchain/duchainutils.cpp +++ b/language/duchain/duchainutils.cpp @@ -1,632 +1,634 @@ /* * DUChain Utilities * * Copyright 2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchainutils.h" +#include + #include #include #include "../interfaces/ilanguagesupport.h" #include "util/debug.h" #include "declaration.h" #include "classfunctiondeclaration.h" #include "ducontext.h" #include "duchain.h" #include "use.h" #include "duchainlock.h" #include "classmemberdeclaration.h" #include "functiondefinition.h" #include "specializationstore.h" #include "persistentsymboltable.h" #include "classdeclaration.h" #include "parsingenvironment.h" #include using namespace KDevelop; using namespace KTextEditor; CodeCompletionModel::CompletionProperties DUChainUtils::completionProperties(const Declaration* dec) { CodeCompletionModel::CompletionProperties p; if(dec->context()->type() == DUContext::Class) { if (const ClassMemberDeclaration* member = dynamic_cast(dec)) { switch (member->accessPolicy()) { case Declaration::Public: p |= CodeCompletionModel::Public; break; case Declaration::Protected: p |= CodeCompletionModel::Protected; break; case Declaration::Private: p |= CodeCompletionModel::Private; break; default: break; } if (member->isStatic()) p |= CodeCompletionModel::Static; if (member->isAuto()) {}//TODO if (member->isFriend()) p |= CodeCompletionModel::Friend; if (member->isRegister()) {}//TODO if (member->isExtern()) {}//TODO if (member->isMutable()) {}//TODO } } if (const AbstractFunctionDeclaration* function = dynamic_cast(dec)) { p |= CodeCompletionModel::Function; if (function->isVirtual()) p |= CodeCompletionModel::Virtual; if (function->isInline()) p |= CodeCompletionModel::Inline; if (function->isExplicit()) {}//TODO } if( dec->isTypeAlias() ) p |= CodeCompletionModel::TypeAlias; if (dec->abstractType()) { switch (dec->abstractType()->whichType()) { case AbstractType::TypeIntegral: p |= CodeCompletionModel::Variable; break; case AbstractType::TypePointer: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeReference: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeFunction: p |= CodeCompletionModel::Function; break; case AbstractType::TypeStructure: p |= CodeCompletionModel::Class; break; case AbstractType::TypeArray: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeEnumeration: p |= CodeCompletionModel::Enum; break; case AbstractType::TypeEnumerator: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeAbstract: case AbstractType::TypeDelayed: case AbstractType::TypeUnsure: case AbstractType::TypeAlias: // TODO break; } if( dec->abstractType()->modifiers() & AbstractType::ConstModifier ) p |= CodeCompletionModel::Const; if( dec->kind() == Declaration::Instance && !dec->isFunctionDeclaration() ) p |= CodeCompletionModel::Variable; } if (dec->context()) { if( dec->context()->type() == DUContext::Global ) p |= CodeCompletionModel::GlobalScope; else if( dec->context()->type() == DUContext::Namespace ) p |= CodeCompletionModel::NamespaceScope; else if( dec->context()->type() != DUContext::Class && dec->context()->type() != DUContext::Enum ) p |= CodeCompletionModel::LocalScope; } return p; } /**We have to construct the item from the pixmap, else the icon will be marked as "load on demand", * and for some reason will be loaded every time it's used(this function returns a QIcon marked "load on demand" * each time this is called). And the loading is very slow. Seems like a bug somewhere, it cannot be ment to be that slow. */ #define RETURN_CACHED_ICON(name) {static QIcon icon(QIcon( \ QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/pics/" name ".png"))\ ).pixmap(QSize(16, 16)));\ return icon;} QIcon DUChainUtils::iconForProperties(KTextEditor::CodeCompletionModel::CompletionProperties p) { if( (p & CodeCompletionModel::Variable) ) if( (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("CVprotected_var") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_var") else RETURN_CACHED_ICON("CVpublic_var") else if( (p & CodeCompletionModel::Union) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_union") else if( p & CodeCompletionModel::Enum ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_enum") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_enum") else RETURN_CACHED_ICON("enum") else if( p & CodeCompletionModel::Struct ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_struct") else RETURN_CACHED_ICON("struct") else if( p & CodeCompletionModel::Slot ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_slot") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_slot") else if(p & CodeCompletionModel::Public ) RETURN_CACHED_ICON("CVpublic_slot") else RETURN_CACHED_ICON("slot") else if( p & CodeCompletionModel::Signal ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_signal") else RETURN_CACHED_ICON("signal") else if( p & CodeCompletionModel::Class ) if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_class") else if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Private) ) RETURN_CACHED_ICON("private_class") else RETURN_CACHED_ICON("code-class") else if( p & CodeCompletionModel::Union ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_union") else RETURN_CACHED_ICON("union") else if( p & CodeCompletionModel::TypeAlias ) if ((p & CodeCompletionModel::Const) /*|| (p & CodeCompletionModel::Volatile)*/) RETURN_CACHED_ICON("CVtypedef") else RETURN_CACHED_ICON("typedef") else if( p & CodeCompletionModel::Function ) { if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_function") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_function") else RETURN_CACHED_ICON("code-function") } if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_field") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_field") else RETURN_CACHED_ICON("field") return QIcon(); } QIcon DUChainUtils::iconForDeclaration(const Declaration* dec) { return iconForProperties(completionProperties(dec)); } TopDUContext* DUChainUtils::contentContextFromProxyContext(TopDUContext* top) { if(!top) return 0; if(top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->isProxyContext()) { if(!top->importedParentContexts().isEmpty()) { DUContext* ctx = top->importedParentContexts().at(0).context(0); if(!ctx) return 0; TopDUContext* ret = ctx->topContext(); if(!ret) return 0; if(ret->url() != top->url()) qCDebug(LANGUAGE) << "url-mismatch between content and proxy:" << top->url().toUrl() << ret->url().toUrl(); if(ret->url() == top->url() && !ret->parsingEnvironmentFile()->isProxyContext()) return ret; } else { qCDebug(LANGUAGE) << "Proxy-context imports no content-context"; } } else return top; return 0; } TopDUContext* DUChainUtils::standardContextForUrl(const QUrl& url, bool preferProxyContext) { KDevelop::TopDUContext* chosen = 0; auto languages = ICore::self()->languageController()->languagesForUrl(url); foreach(const auto language, languages) { if(!chosen) { chosen = language->standardContext(url, preferProxyContext); } } if(!chosen) chosen = DUChain::self()->chainForDocument(IndexedString(url), preferProxyContext); if(!chosen && preferProxyContext) return standardContextForUrl(url, false); // Fall back to a normal context return chosen; } Declaration* declarationUnderCursor(const CursorInRevision& c, DUContext* ctx) { foreach( Declaration* decl, ctx->localDeclarations() ) if( decl->range().contains(c, RangeInRevision::IncludeBackEdge) ) return decl; //Search all collapsed sub-contexts. In C++, those can contain declarations that have ranges out of the context foreach( DUContext* subCtx, ctx->childContexts() ) { //This is a little hacky, but we need it in case of foreach macros and similar stuff if(subCtx->range().isEmpty() || subCtx->range().start.line == c.line || subCtx->range().end.line == c.line) { Declaration* decl = declarationUnderCursor(c, subCtx); if(decl) return decl; } } return 0; } Declaration* DUChainUtils::itemUnderCursor(const QUrl& url, const KTextEditor::Cursor& _c) { KDevelop::TopDUContext* chosen = standardContextForUrl(url); if( chosen ) { CursorInRevision c = chosen->transformToLocalRevision(_c); DUContext* ctx = chosen->findContextAt(c); while( ctx ) { //Try finding a declaration under the cursor Declaration* decl = declarationUnderCursor(c, ctx); if(decl) return decl; //Try finding a use under the cursor for(int a = 0; a < ctx->usesCount(); ++a) if( ctx->uses()[a].m_range.contains(c, RangeInRevision::IncludeBackEdge) ) return ctx->topContext()->usedDeclarationForIndex(ctx->uses()[a].m_declarationIndex); ctx = ctx->parentContext(); //It may happen, for example in the case of function-declarations, that the use is one context higher. } } return 0; } KTextEditor::Range DUChainUtils::itemRangeUnderCursor(const QUrl& url, const KTextEditor::Cursor& cursor) { KDevelop::TopDUContext* chosen = standardContextForUrl(url); if( chosen ) { CursorInRevision c = chosen->transformToLocalRevision(cursor); DUContext* ctx = chosen->findContextAt(c); if (ctx) { Declaration* decl = declarationUnderCursor(c, ctx); // Some declarations need to be searched in parent context if (!decl && ctx->parentContext()) { decl = declarationUnderCursor(c, ctx->parentContext()); } if (decl && decl->range().contains(c, RangeInRevision::IncludeBackEdge) ) { return decl->rangeInCurrentRevision(); } for(int a = 0; a < ctx->usesCount(); ++a) { if( ctx->uses()[a].m_range.contains(c, RangeInRevision::IncludeBackEdge) ) { return ctx->transformFromLocalRevision(ctx->uses()[a].m_range); } } } } return KTextEditor::Range(); } Declaration* DUChainUtils::declarationForDefinition(Declaration* definition, TopDUContext* topContext) { if(!definition) return 0; if(!topContext) topContext = definition->topContext(); if(dynamic_cast(definition)) { Declaration* ret = static_cast(definition)->declaration(); if(ret) return ret; } return definition; } Declaration* DUChainUtils::declarationInLine(const KTextEditor::Cursor& _cursor, DUContext* ctx) { if(!ctx) return 0; CursorInRevision cursor = ctx->transformToLocalRevision(_cursor); foreach(Declaration* decl, ctx->localDeclarations()) { if(decl->range().start.line == cursor.line) return decl; DUContext* funCtx = getFunctionContext(decl); if(funCtx && funCtx->range().contains(cursor)) return decl; } foreach(DUContext* child, ctx->childContexts()){ Declaration* decl = declarationInLine(_cursor, child); if(decl) return decl; } return 0; } DUChainUtils::DUChainItemFilter::~DUChainItemFilter() { } void DUChainUtils::collectItems( DUContext* context, DUChainItemFilter& filter ) { QVector children = context->childContexts(); QVector localDeclarations = context->localDeclarations(); QVector::const_iterator childIt = children.constBegin(); QVector::const_iterator declIt = localDeclarations.constBegin(); while(childIt != children.constEnd() || declIt != localDeclarations.constEnd()) { DUContext* child = 0; if(childIt != children.constEnd()) child = *childIt; Declaration* decl = 0; if(declIt != localDeclarations.constEnd()) decl = *declIt; if(decl) { if(child && child->range().start.line >= decl->range().start.line) child = 0; } if(child) { if(decl && decl->range().start >= child->range().start) decl = 0; } if(decl) { if( filter.accept(decl) ) { //Action is done in the filter } ++declIt; continue; } if(child) { if( filter.accept(child) ) collectItems(child, filter); ++childIt; continue; } } } KDevelop::DUContext* DUChainUtils::getArgumentContext(KDevelop::Declaration* decl) { DUContext* internal = decl->internalContext(); if( !internal ) return 0; if( internal->type() == DUContext::Function ) return internal; foreach( const DUContext::Import &ctx, internal->importedParentContexts() ) { if( ctx.context(decl->topContext()) ) if( ctx.context(decl->topContext())->type() == DUContext::Function ) return ctx.context(decl->topContext()); } return 0; } QList DUChainUtils::collectAllVersions(Declaration* decl) { QList ret; ret << IndexedDeclaration(decl); if(decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) if(!(allDeclarations[a] == IndexedDeclaration(decl))) ret << allDeclarations[a]; } return ret; } static QList getInheritersInternal(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions) { QList ret; if(!dynamic_cast(decl)) return ret; if(maxAllowedSteps == 0) return ret; if(decl->internalContext() && decl->internalContext()->type() == DUContext::Class) { foreach (const IndexedDUContext importer, decl->internalContext()->indexedImporters()) { DUContext* imp = importer.data(); if(!imp) continue; if(imp->type() == DUContext::Class && imp->owner()) ret << imp->owner(); --maxAllowedSteps; if(maxAllowedSteps == 0) return ret; } } if(collectVersions && decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) { ++maxAllowedSteps; if(allDeclarations[a].data() && allDeclarations[a].data() != decl) { ret += getInheritersInternal(allDeclarations[a].data(), maxAllowedSteps, false); } if(maxAllowedSteps == 0) return ret; } } return ret; } QList DUChainUtils::getInheriters(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions) { auto inheriters = getInheritersInternal(decl, maxAllowedSteps, collectVersions); // remove duplicates std::sort(inheriters.begin(), inheriters.end()); inheriters.erase(std::unique(inheriters.begin(), inheriters.end()), inheriters.end()); return inheriters; } QList DUChainUtils::getOverriders(const Declaration* currentClass, const Declaration* overriddenDeclaration, uint& maxAllowedSteps) { QList ret; if(maxAllowedSteps == 0) return ret; if(currentClass != overriddenDeclaration->context()->owner() && currentClass->internalContext()) ret += currentClass->internalContext()->findLocalDeclarations(overriddenDeclaration->identifier(), CursorInRevision::invalid(), currentClass->topContext(), overriddenDeclaration->abstractType()); foreach(Declaration* inheriter, getInheriters(currentClass, maxAllowedSteps)) ret += getOverriders(inheriter, overriddenDeclaration, maxAllowedSteps); return ret; } static bool hasUse(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return false; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) return true; foreach(DUContext* child, context->childContexts()) if(hasUse(child, usedDeclarationIndex)) return true; return false; } bool DUChainUtils::contextHasUse(DUContext* context, Declaration* declaration) { return hasUse(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } static uint countUses(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return 0; uint ret = 0; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) ++ret; foreach(DUContext* child, context->childContexts()) ret += countUses(child, usedDeclarationIndex); return ret; } uint DUChainUtils::contextCountUses(DUContext* context, Declaration* declaration) { return countUses(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } Declaration* DUChainUtils::getOverridden(const Declaration* decl) { const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl); if(!classFunDecl || !classFunDecl->isVirtual()) return 0; QList decls; foreach(const DUContext::Import &import, decl->context()->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx) decls += ctx->findDeclarations(QualifiedIdentifier(decl->identifier()), CursorInRevision::invalid(), decl->abstractType(), decl->topContext(), DUContext::DontSearchInParent); } foreach(Declaration* found, decls) { const ClassFunctionDeclaration* foundClassFunDecl = dynamic_cast(found); if(foundClassFunDecl && foundClassFunDecl->isVirtual()) return found; } return 0; } DUContext* DUChainUtils::getFunctionContext(Declaration* decl) { DUContext* functionContext = decl->internalContext(); if(functionContext && functionContext->type() != DUContext::Function) { foreach(const DUContext::Import& import, functionContext->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx && ctx->type() == DUContext::Function) functionContext = ctx; } } if(functionContext && functionContext->type() == DUContext::Function) return functionContext; return 0; } diff --git a/language/editor/persistentmovingrangeprivate.cpp b/language/editor/persistentmovingrangeprivate.cpp index 8d11e2709e..91bb313e91 100644 --- a/language/editor/persistentmovingrangeprivate.cpp +++ b/language/editor/persistentmovingrangeprivate.cpp @@ -1,95 +1,95 @@ /* Copyright 2010 David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "persistentmovingrangeprivate.h" #include #include #include #include void KDevelop::PersistentMovingRangePrivate::connectTracker() { Q_ASSERT(m_tracker == 0); Q_ASSERT(m_movingRange == 0); m_tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(m_document); if(m_tracker) { // Create a moving range m_movingRange = m_tracker->documentMovingInterface()->newMovingRange(m_range); if (m_shouldExpand) m_movingRange->setInsertBehaviors(KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); // can't use new connect syntax here, MovingInterface is not a QObject connect(m_tracker->document(), SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent())); connect(m_tracker->document(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent())); m_movingRange->setAttribute(m_attribte); m_movingRange->setZDepth(m_zDepth); } } void KDevelop::PersistentMovingRangePrivate::disconnectTracker() { Q_ASSERT(m_tracker); Q_ASSERT(m_movingRange); // can't use new connect syntax here, MovingInterface is not a QObject disconnect(m_tracker->document(), SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent())); disconnect(m_tracker->document(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent())); delete m_movingRange; - m_tracker = 0; + m_tracker.clear(); m_movingRange = 0; } void KDevelop::PersistentMovingRangePrivate::aboutToInvalidateMovingInterfaceContent() { if(m_movingRange) { Q_ASSERT(m_tracker); Q_ASSERT(m_movingRange); m_valid = false; /// @todo More precise tracking: Why is the document being invalidated? Try /// keeping the range alive. DocumentChangeTracker to the rescue. delete m_movingRange; m_movingRange = 0; m_range = KTextEditor::Range::invalid(); } } void KDevelop::PersistentMovingRangePrivate::aboutToDeleteMovingInterfaceContent() { // The whole document is being closed. Map the range back to the last saved revision, and use that. updateRangeFromMoving(); - if(m_tracker->diskRevision()) + if(m_tracker && m_tracker->diskRevision()) { if(m_movingRange) m_range = m_tracker->diskRevision()->transformFromCurrentRevision(m_range).castToSimpleRange(); }else{ m_valid = false; m_range = KTextEditor::Range::invalid(); } // No need to disconnect, as the document is being deleted. Simply set the referenes to zero. delete m_movingRange; m_movingRange = 0; - m_tracker = 0; + m_tracker.clear(); } diff --git a/language/editor/persistentmovingrangeprivate.h b/language/editor/persistentmovingrangeprivate.h index 91f25bc0c8..c182ecf464 100644 --- a/language/editor/persistentmovingrangeprivate.h +++ b/language/editor/persistentmovingrangeprivate.h @@ -1,68 +1,73 @@ /* 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_PERSISTENTMOVINGRANGEPRIVATE_H #define KDEVPLATFORM_PERSISTENTMOVINGRANGEPRIVATE_H #include #include #include #include "documentrange.h" #include #include namespace KDevelop { class PersistentMovingRangePrivate : public QObject { Q_OBJECT public: - PersistentMovingRangePrivate() : m_valid(false), m_shouldExpand(false), m_movingRange(nullptr), m_tracker(nullptr), m_zDepth(0) { + PersistentMovingRangePrivate() + : m_valid(false) + , m_shouldExpand(false) + , m_movingRange(nullptr) + , m_zDepth(0) + { moveToThread(QApplication::instance()->thread()); } void connectTracker(); void disconnectTracker(); bool m_valid; bool m_shouldExpand; KTextEditor::Range m_range; IndexedString m_document; KTextEditor::Attribute::Ptr m_attribte; KTextEditor::MovingRange* m_movingRange; - DocumentChangeTracker* m_tracker; + QPointer m_tracker; float m_zDepth; void updateRangeFromMoving() { if(m_movingRange) { m_range = m_movingRange->toRange(); } } private Q_SLOTS: void aboutToDeleteMovingInterfaceContent(); void aboutToInvalidateMovingInterfaceContent(); }; } #endif // KDEVPLATFORM_PERSISTENTMOVINGRANGEPRIVATE_H diff --git a/outputview/outputfilteringstrategies.cpp b/outputview/outputfilteringstrategies.cpp index a650b95999..27e8aca01b 100644 --- a/outputview/outputfilteringstrategies.cpp +++ b/outputview/outputfilteringstrategies.cpp @@ -1,453 +1,452 @@ /* 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 . */ #include "outputfilteringstrategies.h" #include "outputformats.h" #include "filtereditem.h" #include #include #include #include namespace KDevelop { void initializeFilteredItem(FilteredItem& item, const ErrorFormat& filter, const QRegularExpressionMatch& match) { item.lineNo = match.captured( filter.lineGroup ).toInt() - 1; item.columnNo = filter.columnNumber(match); } template FilteredItem match(const ErrorFormats& errorFormats, const QString& line) { FilteredItem item(line); for( const ErrorFormat& curErrFilter : errorFormats ) { const auto match = curErrFilter.expression.match(line); if( match.hasMatch() ) { initializeFilteredItem(item, curErrFilter, match); item.url = QUrl::fromUserInput(match.captured( curErrFilter.fileGroup )); item.type = FilteredItem::ErrorItem; // Make the item clickable if it comes with the necessary file & line number information if (curErrFilter.fileGroup > 0 && curErrFilter.lineGroup > 0) { item.isActivatable = true; } break; } } return item; } /// --- No filter strategy --- NoFilterStrategy::NoFilterStrategy() { } FilteredItem NoFilterStrategy::actionInLine(const QString& line) { return FilteredItem( line ); } FilteredItem NoFilterStrategy::errorInLine(const QString& line) { return FilteredItem( line ); } /// --- Compiler error filter strategy --- /// Impl. of CompilerFilterStrategy. struct CompilerFilterStrategyPrivate { CompilerFilterStrategyPrivate(const QUrl& buildDir); Path pathForFile( const QString& ) const; bool isMultiLineCase(ErrorFormat curErrFilter) const; void putDirAtEnd(const Path& pathToInsert); QVector m_currentDirs; Path m_buildDir; using PositionMap = QHash; PositionMap m_positionInCurrentDirs; }; - CompilerFilterStrategyPrivate::CompilerFilterStrategyPrivate(const QUrl& buildDir) : m_buildDir(buildDir) { } Path CompilerFilterStrategyPrivate::pathForFile(const QString& filename) const { QFileInfo fi( filename ); Path currentPath; if( fi.isRelative() ) { if( m_currentDirs.isEmpty() ) { return Path(m_buildDir, filename ); } auto it = m_currentDirs.constEnd() - 1; do { currentPath = Path(*it, filename); } while( (it-- != m_currentDirs.constBegin()) && !QFileInfo::exists(currentPath.toLocalFile()) ); return currentPath; } else { currentPath = Path(filename); } return currentPath; } bool CompilerFilterStrategyPrivate::isMultiLineCase(KDevelop::ErrorFormat curErrFilter) const { if(curErrFilter.compiler == QLatin1String("gfortran") || curErrFilter.compiler == QLatin1String("cmake")) { return true; } return false; } void CompilerFilterStrategyPrivate::putDirAtEnd(const Path& pathToInsert) { CompilerFilterStrategyPrivate::PositionMap::iterator it = m_positionInCurrentDirs.find( pathToInsert ); // Encountered new build directory? if (it == m_positionInCurrentDirs.end()) { m_currentDirs.push_back( pathToInsert ); m_positionInCurrentDirs.insert( pathToInsert, m_currentDirs.size() - 1 ); } else { // Build dir already in currentDirs, but move it to back of currentDirs list // (this gives us most-recently-used semantics in pathForFile) std::rotate(m_currentDirs.begin() + it.value(), m_currentDirs.begin() + it.value() + 1, m_currentDirs.end() ); it.value() = m_currentDirs.size() - 1; } } CompilerFilterStrategy::CompilerFilterStrategy(const QUrl& buildDir) : d(new CompilerFilterStrategyPrivate( buildDir )) { } CompilerFilterStrategy::~CompilerFilterStrategy() { delete d; } QVector CompilerFilterStrategy::getCurrentDirs() { QVector ret; ret.reserve(d->m_currentDirs.size()); foreach (const auto& path, d->m_currentDirs) { ret << path.pathOrUrl(); } return ret; } FilteredItem CompilerFilterStrategy::actionInLine(const QString& line) { // A list of filters for possible compiler, linker, and make actions static const ActionFormat ACTION_FILTERS[] = { ActionFormat( 2, QStringLiteral("(?:^|[^=])\\b(gcc|CC|cc|distcc|c\\+\\+|g\\+\\+|clang(?:\\+\\+)|mpicc|icc|icpc)\\s+.*-c.*[/ '\\\\]+(\\w+\\.(?:cpp|CPP|c|C|cxx|CXX|cs|java|hpf|f|F|f90|F90|f95|F95))")), //moc and uic ActionFormat( 2, QStringLiteral("/(moc|uic)\\b.*\\s-o\\s([^\\s;]+)")), //libtool linking ActionFormat( QStringLiteral("libtool"), QStringLiteral("/bin/sh\\s.*libtool.*--mode=link\\s.*\\s-o\\s([^\\s;]+)"), 1 ), //unsermake ActionFormat( 1, QStringLiteral("^compiling (.*)") ), ActionFormat( 2, QStringLiteral("^generating (.*)") ), ActionFormat( 2, QStringLiteral("(gcc|cc|c\\+\\+|g\\+\\+|clang(?:\\+\\+)|mpicc|icc|icpc)\\S* (?:\\S* )*-o ([^\\s;]+)")), ActionFormat( 2, QStringLiteral("^linking (.*)") ), //cmake ActionFormat( 1, QStringLiteral("\\[.+%\\] Built target (.*)") ), ActionFormat( QStringLiteral("cmake"), QStringLiteral("\\[.+%\\] Building .* object (.*)"), 1 ), ActionFormat( 1, QStringLiteral("\\[.+%\\] Generating (.*)") ), ActionFormat( 1, QStringLiteral("^Linking (.*)") ), ActionFormat( QStringLiteral("cmake"), QStringLiteral("(-- Configuring (done|incomplete)|-- Found|-- Adding|-- Enabling)"), -1 ), ActionFormat( 1, QStringLiteral("-- Installing (.*)") ), //libtool install ActionFormat( {}, QStringLiteral("/(?:bin/sh\\s.*mkinstalldirs).*\\s([^\\s;]+)"), 1 ), ActionFormat( {}, QStringLiteral("/(?:usr/bin/install|bin/sh\\s.*mkinstalldirs|bin/sh\\s.*libtool.*--mode=install).*\\s([^\\s;]+)"), 1 ), //dcop ActionFormat( QStringLiteral("dcopidl"), QStringLiteral("dcopidl .* > ([^\\s;]+)"), 1 ), ActionFormat( QStringLiteral("dcopidl2cpp"), QStringLiteral("dcopidl2cpp (?:\\S* )*([^\\s;]+)"), 1 ), // match against Entering directory to update current build dir ActionFormat( QStringLiteral("cd"), QStringLiteral("make\\[\\d+\\]: Entering directory (\\`|\\')(.+)'"), 2), // waf and scons use the same basic convention as make ActionFormat( QStringLiteral("cd"), QStringLiteral("(Waf|scons): Entering directory (\\`|\\')(.+)'"), 3) }; FilteredItem item(line); for (const auto& curActFilter : ACTION_FILTERS) { const auto match = curActFilter.expression.match(line); if( match.hasMatch() ) { item.type = FilteredItem::ActionItem; if( curActFilter.tool == QLatin1String("cd") ) { const Path path(match.captured(curActFilter.fileGroup)); d->m_currentDirs.push_back( path ); d->m_positionInCurrentDirs.insert( path , d->m_currentDirs.size() - 1 ); } // Special case for cmake: we parse the "Compiling " expression // and use it to find out about the build paths encountered during a build. // They are later searched by pathForFile to find source files corresponding to // compiler errors. // Note: CMake objectfile has the format: "/path/to/four/CMakeFiles/file.o" if ( curActFilter.fileGroup != -1 && curActFilter.tool == QLatin1String("cmake") && line.contains(QStringLiteral("Building"))) { const auto objectFile = match.captured(curActFilter.fileGroup); const auto dir = objectFile.section(QStringLiteral("CMakeFiles/"), 0, 0); d->putDirAtEnd(Path(d->m_buildDir, dir)); } break; } } return item; } FilteredItem CompilerFilterStrategy::errorInLine(const QString& line) { // All the possible string that indicate an error if we via Regex have been able to // extract file and linenumber from a given outputline // TODO: This seems clumsy -- and requires another scan of the line. // Merge this information into ErrorFormat? --Kevin using Indicator = QPair; static const Indicator INDICATORS[] = { // ld Indicator(QStringLiteral("undefined reference"), FilteredItem::ErrorItem), Indicator(QStringLiteral("undefined symbol"), FilteredItem::ErrorItem), Indicator(QStringLiteral("ld: cannot find"), FilteredItem::ErrorItem), Indicator(QStringLiteral("no such file"), FilteredItem::ErrorItem), // gcc Indicator(QStringLiteral("error"), FilteredItem::ErrorItem), // generic Indicator(QStringLiteral("warning"), FilteredItem::WarningItem), Indicator(QStringLiteral("info"), FilteredItem::InformationItem), Indicator(QStringLiteral("note"), FilteredItem::InformationItem), }; // A list of filters for possible compiler, linker, and make errors static const ErrorFormat ERROR_FILTERS[] = { #ifdef Q_OS_WIN // MSVC ErrorFormat( QStringLiteral("^([a-zA-Z]:\\\\.+)\\(([1-9][0-9]*)\\): ((?:error|warning) .+\\:).*$"), 1, 2, 3 ), #endif // GCC - another case, eg. for #include "pixmap.xpm" which does not exists ErrorFormat( QStringLiteral("^([^:\t]+):([0-9]+):([0-9]+):([^0-9]+)"), 1, 2, 4, 3 ), // ant ErrorFormat( QStringLiteral("\\[javac\\][\\s]+([^:\t]+):([0-9]+): (warning: .*|error: .*)"), 1, 2, 3, QStringLiteral("javac")), // GCC ErrorFormat( QStringLiteral("^([^:\t]+):([0-9]+):([^0-9]+)"), 1, 2, 3 ), // GCC ErrorFormat( QStringLiteral("^(In file included from |[ ]+from )([^: \\t]+):([0-9]+)(:|,)(|[0-9]+)"), 2, 3, 5 ), // ICC ErrorFormat( QStringLiteral("^([^: \\t]+)\\(([0-9]+)\\):([^0-9]+)"), 1, 2, 3, QStringLiteral("intel") ), //libtool link ErrorFormat( QStringLiteral("^(libtool):( link):( warning): "), 0, 0, 0 ), // make ErrorFormat( QStringLiteral("No rule to make target"), 0, 0, 0 ), // cmake ErrorFormat( QStringLiteral("^([^: \\t]+):([0-9]+):"), 1, 2, 0, QStringLiteral("cmake") ), // cmake ErrorFormat( QStringLiteral("CMake (Error|Warning) (|\\([a-zA-Z]+\\) )(in|at) ([^:]+):($|[0-9]+)"), 4, 5, 1, QStringLiteral("cmake") ), // cmake/automoc // example: AUTOMOC: error: /foo/bar.cpp The file includes (...), // example: AUTOMOC: error: /foo/bar.cpp: The file includes (...) // note: ':' after file name isn't always appended, see http://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=317d8498aa02c9f486bf5071963bb2034777cdd6 // example: AUTOGEN: error: /foo/bar.cpp: The file includes (...) // note: AUTOMOC got renamed to AUTOGEN at some point ErrorFormat( QStringLiteral("^(AUTOMOC|AUTOGEN): error: ([^:]+):? (The file .*)$"), 2, 0, 0 ), // via qt4_automoc // example: automoc4: The file "/foo/bar.cpp" includes the moc file "bar1.moc", but ... ErrorFormat( QStringLiteral("^automoc4: The file \"([^\"]+)\" includes the moc file"), 1, 0, 0 ), // Fortran ErrorFormat( QStringLiteral("\"(.*)\", line ([0-9]+):(.*)"), 1, 2, 3 ), // GFortran ErrorFormat( QStringLiteral("^(.*):([0-9]+)\\.([0-9]+):(.*)"), 1, 2, 4, QStringLiteral("gfortran"), 3 ), // Jade ErrorFormat( QStringLiteral("^[a-zA-Z]+:([^: \t]+):([0-9]+):[0-9]+:[a-zA-Z]:(.*)"), 1, 2, 3 ), // ifort ErrorFormat( QStringLiteral("^fortcom: (.*): (.*), line ([0-9]+):(.*)"), 2, 3, 1, QStringLiteral("intel") ), // PGI ErrorFormat( QStringLiteral("PGF9(.*)-(.*)-(.*)-(.*) \\((.*): ([0-9]+)\\)"), 5, 6, 4, QStringLiteral("pgi") ), // PGI (2) ErrorFormat( QStringLiteral("PGF9(.*)-(.*)-(.*)-Symbol, (.*) \\((.*)\\)"), 5, 5, 4, QStringLiteral("pgi") ), }; FilteredItem item(line); for (const auto& curErrFilter : ERROR_FILTERS) { const auto match = curErrFilter.expression.match(line); if( match.hasMatch() && !( line.contains( QLatin1String("Each undeclared identifier is reported only once") ) || line.contains( QLatin1String("for each function it appears in.") ) ) ) { if(curErrFilter.fileGroup > 0) { if( curErrFilter.compiler == QLatin1String("cmake") ) { // Unfortunately we cannot know if an error or an action comes first in cmake, and therefore we need to do this if( d->m_currentDirs.empty() ) { d->putDirAtEnd( d->m_buildDir.parent() ); } } item.url = d->pathForFile( match.captured( curErrFilter.fileGroup ) ).toUrl(); } initializeFilteredItem(item, curErrFilter, match); const QString txt = match.captured(curErrFilter.textGroup); // Find the indicator which happens most early. int earliestIndicatorIdx = txt.length(); for (const auto& curIndicator : INDICATORS) { int curIndicatorIdx = txt.indexOf(curIndicator.first, 0, Qt::CaseInsensitive); if((curIndicatorIdx >= 0) && (earliestIndicatorIdx > curIndicatorIdx)) { earliestIndicatorIdx = curIndicatorIdx; item.type = curIndicator.second; } } // Make the item clickable if it comes with the necessary file information if (item.url.isValid()) { item.isActivatable = true; if(item.type == FilteredItem::InvalidItem) { // If there are no error indicators in the line // maybe this is a multiline case if(d->isMultiLineCase(curErrFilter)) { item.type = FilteredItem::ErrorItem; } else { // Okay so we couldn't find anything to indicate an error, but we have file and lineGroup // Lets keep this item clickable and indicate this to the user. item.type = FilteredItem::InformationItem; } } } break; } } return item; } /// --- Script error filter strategy --- ScriptErrorFilterStrategy::ScriptErrorFilterStrategy() { } FilteredItem ScriptErrorFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem ScriptErrorFilterStrategy::errorInLine(const QString& line) { // A list of filters for possible Python and PHP errors static const ErrorFormat SCRIPT_ERROR_FILTERS[] = { ErrorFormat( QStringLiteral("^ File \"(.*)\", line ([0-9]+)(.*$|, in(.*)$)"), 1, 2, -1 ), ErrorFormat( QStringLiteral("^.*(/.*):([0-9]+).*$"), 1, 2, -1 ), ErrorFormat( QStringLiteral("^.* in (/.*) on line ([0-9]+).*$"), 1, 2, -1 ) }; return match(SCRIPT_ERROR_FILTERS, line); } /// --- Native application error filter strategy --- NativeAppErrorFilterStrategy::NativeAppErrorFilterStrategy() { } FilteredItem NativeAppErrorFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem NativeAppErrorFilterStrategy::errorInLine(const QString& line) { static const ErrorFormat NATIVE_APPLICATION_ERROR_FILTERS[] = { // BEGIN: C++ // a.out: test.cpp:5: int main(): Assertion `false' failed. ErrorFormat(QStringLiteral("^.+: (.+):([1-9][0-9]*): .*: Assertion `.*' failed\\.$"), 1, 2, -1), // END: C++ // BEGIN: Qt // Note: Scan the whole line for catching Qt runtime warnings (-> no ^$) // Reasoning: With QT_MESSAGE_PATTERN you can configure the output to your desire, // which means the output could be arbitrarily prefixed or suffixed with other content // QObject::connect related errors, also see err_method_notfound() in qobject.cpp // QObject::connect: No such slot Foo::bar() in /foo/bar.cpp:313 ErrorFormat(QStringLiteral("QObject::connect: (?:No such|Parentheses expected,) (?:slot|signal) [^ ]* in (.*):([0-9]+)"), 1, 2, -1), // ASSERT: "errors().isEmpty()" in file /foo/bar.cpp, line 49 ErrorFormat(QStringLiteral("ASSERT: \"(.*)\" in file (.*), line ([0-9]+)"), 2, 3, -1), // Catch: // FAIL! : FooTest::testBar() Compared pointers are not the same // Actual ... // Expected ... // Loc: [/foo/bar.cpp(33)] // // Do *not* catch: // ... // Loc: [Unknown file(0)] ErrorFormat(QStringLiteral(" Loc: \\[(.*)\\(([1-9][0-9]*)\\)\\]"), 1, 2, -1), // file:///path/to/foo.qml:7:1: Bar is not a type ErrorFormat(QStringLiteral("(file:\\/\\/(?:.+)):([1-9][0-9]*):([1-9][0-9]*): (.+) (?:is not a type|is ambiguous\\.|is instantiated recursively)"), 1, 2, -1, 3) // END: Qt }; return match(NATIVE_APPLICATION_ERROR_FILTERS, line); } /// --- Static Analysis filter strategy --- StaticAnalysisFilterStrategy::StaticAnalysisFilterStrategy() { } FilteredItem StaticAnalysisFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem StaticAnalysisFilterStrategy::errorInLine(const QString& line) { // A list of filters for static analysis tools (krazy2, cppcheck) static const ErrorFormat STATIC_ANALYSIS_FILTERS[] = { // CppCheck ErrorFormat( QStringLiteral("^\\[(.*):([0-9]+)\\]:(.*)"), 1, 2, 3 ), // krazy2 ErrorFormat( QStringLiteral("^\\t([^:]+).*line#([0-9]+).*"), 1, 2, -1 ), // krazy2 without line info ErrorFormat( QStringLiteral("^\\t(.*): missing license"), 1, -1, -1 ) }; return match(STATIC_ANALYSIS_FILTERS, line); } } diff --git a/plugins/welcomepage/qml/GettingStarted.qml b/plugins/welcomepage/qml/GettingStarted.qml index f820901556..3902503063 100644 --- a/plugins/welcomepage/qml/GettingStarted.qml +++ b/plugins/welcomepage/qml/GettingStarted.qml @@ -1,106 +1,105 @@ /* KDevelop * * Copyright 2011 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. */ import QtQuick 2.0 import QtQuick.Layouts 1.2 import QtQuick.Controls 1.2 import QtWebKit 3.0 StandardPage { ColumnLayout { id: options anchors.top: parent.top anchors.left: parent.left anchors.margins: 30 spacing: 10 width: 200 Link { Layout.fillWidth: true text: i18n("Official Website") onClicked: info.state="kdevelop.org" } Link { Layout.fillWidth: true text: i18n("Userbase") onClicked: info.state="userbase" } Link { Layout.fillWidth: true text: i18n("Techbase") onClicked: info.state="techbase" } Link { Layout.fillWidth: true text: i18n("Handbook") onClicked: kdev.retrieveMenuAction("help/help_contents").trigger() } } Column { id: info anchors { top: parent.top left: options.right right: parent.right margins: 30 } spacing: 10 WebView { id: webview width: parent.width height: 200 MouseArea { anchors.fill: parent; hoverEnabled: true } } Label { id: description width: parent.width } Button { text: i18n("Go!") onClicked: Qt.openUrlExternally(webview.url) } + state: "kdevelop.org" states: [ State { name: "userbase" PropertyChanges { target: webview; url: "http://userbase.kde.org/KDevelop" } PropertyChanges { target: description; text: i18n("Documentation for KDevelop users") } }, State { name: "kdevelop.org" PropertyChanges { target: webview; url: "http://kdevelop.org" } PropertyChanges { target: description; text: i18n("Keep up with KDevelop's development") } }, State { name: "techbase" PropertyChanges { target: webview; url: "http://techbase.kde.org/KDevelop" } PropertyChanges { target: description; text: i18n("Help us improve KDevelop") } } ] } - - Component.onCompleted: info.state="kdevelop.org" } diff --git a/plugins/welcomepage/qml/StandardBackground.qml b/plugins/welcomepage/qml/StandardBackground.qml index fc26ccb321..c76d5de637 100644 --- a/plugins/welcomepage/qml/StandardBackground.qml +++ b/plugins/welcomepage/qml/StandardBackground.qml @@ -1,69 +1,72 @@ /* KDevelop * * Copyright 2011 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. */ import QtQuick 2.0 Rectangle { id: bg property Component tools: null property string pageIcon property real marginLeft: toolbar.x+toolbar.width property real margins: 5 + color: pal.base + + SystemPalette { id: pal } Rectangle { id: toolbar radius: 5 color: Qt.rgba(0.8, 0.8, 0.8, 0.4) anchors { top: parent.top left: parent.left margins: parent.margins } width: toolsLoader.width + 2*toolsLoader.margins height: toolsLoader.height + 2*toolsLoader.margins Loader { id: toolsLoader property int margins: 20 anchors { top: parent.top left: parent.left margins: toolsLoader.margins } sourceComponent: tools } } Image { id: theIcon anchors { bottom: parent.bottom left: parent.left margins: 5 } source: bg.pageIcon !== "" ? "image://icon/" + bg.pageIcon : "" width: 64 height: width } } diff --git a/plugins/welcomepage/qml/StandardPage.qml b/plugins/welcomepage/qml/StandardPage.qml index 4a5b581d84..f9642cb389 100644 --- a/plugins/welcomepage/qml/StandardPage.qml +++ b/plugins/welcomepage/qml/StandardPage.qml @@ -1,38 +1,38 @@ /* KDevelop * * Copyright 2011 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. */ import QtQuick 2.1 Rectangle { radius: 5 - color: Qt.rgba(0.8, 0.8, 0.8, 0.4) + color: Qt.rgba(0.8, 0.9, 0.8, 0.4) Image { anchors { bottom: parent.bottom right: parent.right margins: 5 } opacity: 0.3 width: 128 height: width source: "image://icon/kdevelop" } } diff --git a/plugins/welcomepage/qml/area_code.qml b/plugins/welcomepage/qml/area_code.qml index 5f18c4fae2..686c2e3803 100644 --- a/plugins/welcomepage/qml/area_code.qml +++ b/plugins/welcomepage/qml/area_code.qml @@ -1,76 +1,72 @@ /* KDevelop * * Copyright 2011 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. */ import QtQuick 2.0 import QtQuick.Layouts 1.2 StandardBackground { id: root - gradient: Gradient { - GradientStop { position: 0.0; color: "#61B056" } - GradientStop { position: 1.0; color: "#A3D69B" } - } state: "develop" tools: ColumnLayout { spacing: 10 Link { Layout.fillWidth: true iconName: "applications-development" text: i18n("Develop") onClicked: root.state = "develop" } Link { Layout.fillWidth: true iconName: "project-development" text: i18n("Projects") onClicked: root.state = "projects" visible: false //FIXME: removed until it makes sense } Link { Layout.fillWidth: true iconName: "help-contents" text: i18n("Getting Started") onClicked: root.state = "gettingstarted" } } Loader { id: codeContents anchors { fill: parent leftMargin: root.marginLeft+root.margins margins: root.margins } } states: [ State { name: "gettingstarted" PropertyChanges { target: codeContents; source: "qrc:/qml/GettingStarted.qml"} }, State { name: "develop" PropertyChanges { target: codeContents; source: "qrc:/qml/Develop.qml"} }, State { name: "projects" PropertyChanges { target: codeContents; source: "qrc:/qml/ProjectsDashboard.qml"} } ] } diff --git a/plugins/welcomepage/qml/area_debug.qml b/plugins/welcomepage/qml/area_debug.qml index 6040874921..5c2b24bcbb 100644 --- a/plugins/welcomepage/qml/area_debug.qml +++ b/plugins/welcomepage/qml/area_debug.qml @@ -1,72 +1,68 @@ /* KDevelop * * Copyright 2011 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. */ import QtQuick 2.0 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.2 StandardBackground { id: root - gradient: Gradient { - GradientStop { position: 0.0; color: "#21257C" } - GradientStop { position: 1.0; color: "#62B6F1" } - } tools: Link { id: goCode iconName: "go-previous" text: i18n("Back to code") onClicked: kdev.setArea("code") } pageIcon: "tools-report-bug" StandardPage { id: startingPage anchors { fill: parent leftMargin: root.marginLeft+root.margins margins: root.margins } Column { anchors.margins: 30 anchors.fill: parent spacing: 30 RowLayout { Link { iconName: "configure"; text: i18n("Configure a new Launcher"); onClicked: kdev.retrieveMenuAction("run/configure_launches").trigger() } Link { iconName: "audio-input-line"; text: i18n("Attach to Process"); onClicked: kdev.retrieveMenuAction("run/debug_attach").trigger() } Link { iconName: "debug-run"; text: i18n("Debug your program"); onClicked: kdev.retrieveMenuAction("run/run_debug").trigger() } } Heading { text: i18n("Debug Area") } Label { width: parent.width text: i18n("On the Debug area you will be able to see and analyze how your program works on execution. "+ "On the Run menu you will find all the possible options.

"+ "As you can see, here you can just execute your application or debug it if you need "+ "further runtime information. You can select what is going to be run by configuring "+ "the launches and selecting the one you want to use in the Current Launch Configuration sub-menu.") wrapMode: Text.WordWrap horizontalAlignment: Text.AlignJustify } } } } diff --git a/plugins/welcomepage/qml/area_review.qml b/plugins/welcomepage/qml/area_review.qml index 50cf02189d..e0933dd550 100644 --- a/plugins/welcomepage/qml/area_review.qml +++ b/plugins/welcomepage/qml/area_review.qml @@ -1,77 +1,73 @@ /* KDevelop * * Copyright 2011 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. */ import QtQuick 2.0 StandardBackground { id: root - gradient: Gradient { - GradientStop { position: 0.0; color: "#B85B5B" } - GradientStop { position: 1.0; color: "#EEBABA" } - } tools: Link { id: goCode iconName: "go-previous" text: i18n("Back to code") onClicked: kdev.setArea("code") } pageIcon: "applications-engineering" StandardPage { id: startingPage anchors { fill: parent leftMargin: root.marginLeft+root.margins margins: root.margins } Column { anchors.margins: 30 anchors.fill: parent spacing: 30 Item { tools: Flow { Link { iconName: "kompare" text: i18n("Review a Patch") onClicked: { kdev.raiseToolView("EditPatch") } } } } Heading { text: i18n("Review Area") } Label { width: parent.width text: i18n("On the Review area you will be able to find the tools you need "+ "to review changes in your projects, either the ones you made or some external patch.
Also it will help you send "+ "the changes to the community you're contributing to, either by committing the changes, sending them by e-mail or "+ "putting them on a ReviewBoard service.") wrapMode: Text.WordWrap horizontalAlignment: Text.AlignJustify } } } } diff --git a/shell/openprojectdialog.cpp b/shell/openprojectdialog.cpp index ef3fde3267..9ccca43fa9 100644 --- a/shell/openprojectdialog.cpp +++ b/shell/openprojectdialog.cpp @@ -1,246 +1,322 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat +#include #include #include #include #include #include #include "core.h" #include "uicontroller.h" +#include "plugincontroller.h" #include "mainwindow.h" #include "shellextension.h" #include "projectsourcepage.h" #include +namespace +{ +struct URLInfo +{ + bool isValid; + bool isDir; + QString extension; +}; + +URLInfo getUrlInfo(const QUrl& url) +{ + URLInfo ret; + ret.isValid = false; + + if (url.isLocalFile()) { + QFileInfo info(url.toLocalFile()); + ret.isValid = info.exists(); + if (ret.isValid) { + ret.isDir = info.isDir(); + ret.extension = info.suffix(); + } + } else if (url.isValid()) { + KIO::StatJob* statJob = KIO::stat(url, KIO::HideProgressInfo); + KJobWidgets::setWindow(statJob, KDevelop::Core::self()->uiControllerInternal()->defaultMainWindow()); + ret.isValid = statJob->exec(); // TODO: do this asynchronously so that the user isn't blocked while typing every letter of the hostname in sftp://hostname + if (ret.isValid) { + KIO::UDSEntry entry = statJob->statResult(); + ret.isDir = entry.isDir(); + ret.extension = QFileInfo(entry.stringValue(KIO::UDSEntry::UDS_NAME)).suffix(); + } + } + return ret; +} +} + namespace KDevelop { OpenProjectDialog::OpenProjectDialog( bool fetch, const QUrl& startUrl, QWidget* parent ) : KAssistantDialog( parent ) , sourcePage(nullptr) , openPage(nullptr) , projectInfoPage(nullptr) { resize(QSize(700, 500)); + const bool useKdeFileDialog = qEnvironmentVariableIsSet("KDE_FULL_SESSION"); + QStringList filters, allEntry; + QString filterFormat = useKdeFileDialog + ? QStringLiteral("%1|%2 (%1)") + : QStringLiteral("%2 (%1)"); + allEntry << "*." + ShellExtension::getInstance()->projectFileExtension(); + filters << filterFormat.arg("*." + ShellExtension::getInstance()->projectFileExtension(), ShellExtension::getInstance()->projectFileDescription()); + QVector plugins = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); + foreach(const KPluginMetaData& info, plugins) + { + QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); + QString desc = info.value(QStringLiteral("X-KDevelop-ProjectFilesFilterDescription")); + + if (!filter.isEmpty() && !desc.isEmpty()) { + m_projectFilters.insert(info.name(), filter); + allEntry += filter; + filters << filterFormat.arg(filter.join(QStringLiteral(" ")), desc); + } + } + + if (useKdeFileDialog) + filters.prepend(i18n("%1|All Project Files (%1)", allEntry.join(QStringLiteral(" ")))); + else + filters.prepend(i18n("All Project Files (%1)", allEntry.join(QStringLiteral(" ")))); + QUrl start = startUrl.isValid() ? startUrl : Core::self()->projectController()->projectsBaseDirectory(); start = start.adjusted(QUrl::NormalizePathSegments); KPageWidgetItem* currentPage = 0; if( fetch ) { sourcePageWidget = new ProjectSourcePage( start, this ); connect( sourcePageWidget, &ProjectSourcePage::isCorrect, this, &OpenProjectDialog::validateSourcePage ); sourcePage = addPage( sourcePageWidget, i18n("Select Source") ); currentPage = sourcePage; } - openPageWidget = new OpenProjectPage( start, this ); - connect( openPageWidget, &OpenProjectPage::urlSelected, this, &OpenProjectDialog::validateOpenUrl ); - connect( openPageWidget, &OpenProjectPage::accepted, this, &OpenProjectDialog::openPageAccepted ); - openPage = addPage( openPageWidget, i18n("Select a build system setup file, existing KDevelop project, " - "or any folder to open as a project") ); + if (useKdeFileDialog) { + openPageWidget = new OpenProjectPage( start, filters, this ); + connect( openPageWidget, &OpenProjectPage::urlSelected, this, &OpenProjectDialog::validateOpenUrl ); + connect( openPageWidget, &OpenProjectPage::accepted, this, &OpenProjectDialog::openPageAccepted ); + openPage = addPage( openPageWidget, i18n("Select a build system setup file, existing KDevelop project, " + "or any folder to open as a project") ); - if( !fetch ) { - currentPage = openPage; + if (!currentPage) { + currentPage = openPage; + } + } else { + nativeDialog = new QFileDialog(this, i18n("Open Project")); + nativeDialog->setDirectoryUrl(start); + nativeDialog->setFileMode(QFileDialog::ExistingFile); + nativeDialog->setNameFilters(filters); } ProjectInfoPage* page = new ProjectInfoPage( this ); connect( page, &ProjectInfoPage::projectNameChanged, this, &OpenProjectDialog::validateProjectName ); connect( page, &ProjectInfoPage::projectManagerChanged, this, &OpenProjectDialog::validateProjectManager ); projectInfoPage = addPage( page, i18n("Project Information") ); + if (!currentPage) { + currentPage = projectInfoPage; + } + setValid( sourcePage, false ); setValid( openPage, false ); setValid( projectInfoPage, false); setAppropriate( projectInfoPage, false ); setCurrentPage( currentPage ); setWindowTitle(i18n("Open Project")); } +bool OpenProjectDialog::execNativeDialog() +{ + while (true) + { + if (nativeDialog->exec()) { + QUrl selectedUrl = nativeDialog->selectedUrls()[0]; + if (getUrlInfo(selectedUrl).isValid) { + // validate directory first to populate m_projectName and m_projectManager + validateOpenUrl(selectedUrl.adjusted(QUrl::RemoveFilename)); + validateOpenUrl(selectedUrl); + return true; + } + } + else { + return false; + } + } +} + +int OpenProjectDialog::exec() +{ + if (nativeDialog && !execNativeDialog()) { + reject(); + return QDialog::Rejected; + } + return KAssistantDialog::exec(); +} + void OpenProjectDialog::validateSourcePage(bool valid) { setValid(sourcePage, valid); openPageWidget->setUrl(sourcePageWidget->workingDir()); } void OpenProjectDialog::validateOpenUrl( const QUrl& url_ ) { - bool isDir = false; - QString extension; - bool isValid = false; + URLInfo urlInfo = getUrlInfo(url_); const QUrl url = url_.adjusted(QUrl::StripTrailingSlash); - if( url.isLocalFile() ) - { - QFileInfo info( url.toLocalFile() ); - isValid = info.exists(); - if ( isValid ) { - isDir = info.isDir(); - extension = info.suffix(); + // openPage is used only in KDE + if (openPage) { + if ( urlInfo.isValid ) { + // reset header + openPage->setHeader(i18n("Open \"%1\" as project", url.fileName())); + } else { + // report error + KColorScheme scheme(palette().currentColorGroup()); + const QString errorMsg = i18n("Selected URL is invalid"); + openPage->setHeader(QStringLiteral("%2") + .arg(scheme.foreground(KColorScheme::NegativeText).color().name(), errorMsg) + ); + setAppropriate( projectInfoPage, false ); + setAppropriate( openPage, true ); + setValid( openPage, false ); + return; } - } else if ( url.isValid() ) - { - KIO::StatJob* statJob = KIO::stat( url, KIO::HideProgressInfo ); - KJobWidgets::setWindow(statJob, Core::self()->uiControllerInternal()->defaultMainWindow() ); - isValid = statJob->exec(); // TODO: do this asynchronously so that the user isn't blocked while typing every letter of the hostname in sftp://hostname - if ( isValid ) { - KIO::UDSEntry entry = statJob->statResult(); - isDir = entry.isDir(); - extension = QFileInfo( entry.stringValue( KIO::UDSEntry::UDS_NAME ) ).suffix(); - } - } - - if ( isValid ) { - // reset header - openPage->setHeader(i18n("Open \"%1\" as project", url.fileName())); - } else { - // report error - KColorScheme scheme(palette().currentColorGroup()); - const QString errorMsg = i18n("Selected URL is invalid"); - openPage->setHeader(QStringLiteral("%2") - .arg(scheme.foreground(KColorScheme::NegativeText).color().name(), errorMsg) - ); - setAppropriate( projectInfoPage, false ); - setAppropriate( openPage, true ); - setValid( openPage, false ); - return; } m_selected = url; - if( isDir || extension != ShellExtension::getInstance()->projectFileExtension() ) + if( urlInfo.isDir || urlInfo.extension != ShellExtension::getInstance()->projectFileExtension() ) { setAppropriate( projectInfoPage, true ); m_url = url; - if( !isDir ) { + if( !urlInfo.isDir ) { m_url = m_url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename); } ProjectInfoPage* page = qobject_cast( projectInfoPage->widget() ); if( page ) { page->setProjectName( m_url.fileName() ); - OpenProjectPage* page2 = qobject_cast( openPage->widget() ); - if( page2 ) + // Default manager + page->setProjectManager( QStringLiteral("Generic Project Manager") ); + // clear the filelist + m_fileList.clear(); + + if( urlInfo.isDir ) { + // If a dir was selected fetch all files in it + KIO::ListJob* job = KIO::listDir( m_url ); + connect( job, &KIO::ListJob::entries, + this, &OpenProjectDialog::storeFileList); + KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); + job->exec(); + } else { + // Else we'lll just take the given file + m_fileList << url.fileName(); + } + // Now find a manager for the file(s) in our filelist. + bool managerFound = false; + foreach( const QString& manager, m_projectFilters.keys() ) { - // Default manager - page->setProjectManager( QStringLiteral("Generic Project Manager") ); - // clear the filelist - m_fileList.clear(); - - if( isDir ) { - // If a dir was selected fetch all files in it - KIO::ListJob* job = KIO::listDir( m_url ); - connect( job, &KIO::ListJob::entries, - this, &OpenProjectDialog::storeFileList); - KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); - job->exec(); - } else { - // Else we'lll just take the given file - m_fileList << url.fileName(); - } - // Now find a manager for the file(s) in our filelist. - bool managerFound = false; - foreach( const QString& manager, page2->projectFilters().keys() ) + foreach( const QString& filterexp, m_projectFilters.value(manager) ) { - foreach( const QString& filterexp, page2->projectFilters().value(manager) ) - { - if( !m_fileList.filter( QRegExp( filterexp, Qt::CaseSensitive, QRegExp::Wildcard ) ).isEmpty() ) - { - managerFound = true; - break; - } - } - if( managerFound ) + if( !m_fileList.filter( QRegExp( filterexp, Qt::CaseSensitive, QRegExp::Wildcard ) ).isEmpty() ) { - page->setProjectManager( manager ); + managerFound = true; break; } } + if( managerFound ) { + page->setProjectManager( manager ); + break; + } } } m_url.setPath( m_url.path() + '/' + m_url.fileName() + '.' + ShellExtension::getInstance()->projectFileExtension() ); - } else - { + } else { setAppropriate( projectInfoPage, false ); m_url = url; } validateProjectInfo(); setValid( openPage, true ); } void OpenProjectDialog::openPageAccepted() { if ( isValid( openPage ) ) { next(); } } void OpenProjectDialog::validateProjectName( const QString& name ) { m_projectName = name; validateProjectInfo(); } void OpenProjectDialog::validateProjectInfo() { setValid( projectInfoPage, (!projectName().isEmpty() && !projectManager().isEmpty()) ); } void OpenProjectDialog::validateProjectManager( const QString& manager ) { m_projectManager = manager; validateProjectInfo(); } QUrl OpenProjectDialog::projectFileUrl() const { return m_url; } QUrl OpenProjectDialog::selectedUrl() const { return m_selected; } QString OpenProjectDialog::projectName() const { return m_projectName; } QString OpenProjectDialog::projectManager() const { return m_projectManager; } void OpenProjectDialog::storeFileList(KIO::Job*, const KIO::UDSEntryList& list) { foreach( const KIO::UDSEntry& entry, list ) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if( name != QLatin1String(".") && name != QLatin1String("..") && !entry.isDir() ) { m_fileList << name; } } } } diff --git a/shell/openprojectdialog.h b/shell/openprojectdialog.h index 7aeae56030..d39ff8e08b 100644 --- a/shell/openprojectdialog.h +++ b/shell/openprojectdialog.h @@ -1,75 +1,82 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat #include #include class KPageWidgetItem; +class QFileDialog; namespace KIO { class Job; } namespace KDevelop { class ProjectSourcePage; class OpenProjectPage; class OpenProjectDialog : public KAssistantDialog { Q_OBJECT public: OpenProjectDialog( bool fetch, const QUrl& startUrl, QWidget* parent = nullptr ); /** * Return a QUrl pointing to the project's .kdev file. */ QUrl projectFileUrl() const; /** * Return a QUrl pointing to the file, that was selected by the user. * Unlike projectFileUrl(), this can be a .kdev file, as well * as build system file (e.g. CMakeLists.txt). */ QUrl selectedUrl() const; QString projectName() const; QString projectManager() const; + int exec() override; + private slots: void validateSourcePage( bool ); void validateOpenUrl( const QUrl& ); void validateProjectName( const QString& ); void validateProjectManager( const QString& ); void storeFileList(KIO::Job*, const KIO::UDSEntryList&); void openPageAccepted(); private: + bool execNativeDialog(); void validateProjectInfo(); QUrl m_url; QUrl m_selected; QString m_projectName; QString m_projectManager; + /// Used to select files when we aren't in KDE + QFileDialog* nativeDialog = nullptr; KPageWidgetItem* sourcePage; KPageWidgetItem* openPage; KPageWidgetItem* projectInfoPage; QStringList m_fileList; + QMap m_projectFilters; - KDevelop::OpenProjectPage* openPageWidget; - KDevelop::ProjectSourcePage* sourcePageWidget; + KDevelop::OpenProjectPage* openPageWidget = nullptr; + KDevelop::ProjectSourcePage* sourcePageWidget = nullptr; }; } #endif diff --git a/shell/openprojectpage.cpp b/shell/openprojectpage.cpp index dabde9727f..42d836fc92 100644 --- a/shell/openprojectpage.cpp +++ b/shell/openprojectpage.cpp @@ -1,129 +1,105 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat #include #include #include #include #include #include "shellextension.h" #include "core.h" -#include "plugincontroller.h" namespace KDevelop { -OpenProjectPage::OpenProjectPage( const QUrl& startUrl, QWidget* parent ) +OpenProjectPage::OpenProjectPage( const QUrl& startUrl, const QStringList& filters, + QWidget* parent ) : QWidget( parent ) { QHBoxLayout* layout = new QHBoxLayout( this ); fileWidget = new KFileWidget( startUrl, this); - QStringList filters; - QStringList allEntry; - allEntry << "*."+ShellExtension::getInstance()->projectFileExtension(); - filters << QStringLiteral( "%1|%2 (%1)").arg("*."+ShellExtension::getInstance()->projectFileExtension(), ShellExtension::getInstance()->projectFileDescription()); - QVector plugins = ICore::self()->pluginController()->queryExtensionPlugins( QStringLiteral( "org.kdevelop.IProjectFileManager" ) ); - foreach(const KPluginMetaData& info, plugins) - { - QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); - QString desc = info.value(QStringLiteral("X-KDevelop-ProjectFilesFilterDescription")); - QString filterline; - if(!filter.isEmpty() && !desc.isEmpty()) { - m_projectFilters.insert(info.name(), filter); - allEntry += filter; - filters << QStringLiteral("%1|%2 (%1)").arg(filter.join(QStringLiteral(" ")), desc); - } - } - - filters.prepend( i18n( "%1|All Project Files (%1)", allEntry.join( QStringLiteral(" ") ) ) ); - fileWidget->setFilter( filters.join(QStringLiteral("\n")) ); fileWidget->setMode( KFile::Modes( KFile::File | KFile::Directory | KFile::ExistingOnly ) ); layout->addWidget( fileWidget ); KDirOperator* ops = fileWidget->dirOperator(); // Emitted for changes in the places view, the url navigator and when using the back/forward/up buttons connect(ops, &KDirOperator::urlEntered, this, &OpenProjectPage::opsEntered); // Emitted when selecting an entry from the "Name" box or editing in there connect( fileWidget->locationEdit(), &KUrlComboBox::editTextChanged, this, &OpenProjectPage::comboTextChanged); // Emitted when clicking on a file in the fileview area connect( fileWidget, &KFileWidget::fileHighlighted, this, &OpenProjectPage::highlightFile ); connect( fileWidget->dirOperator()->dirLister(), static_cast(&KDirLister::completed), this, &OpenProjectPage::dirChanged); connect( fileWidget, &KFileWidget::accepted, this, &OpenProjectPage::accepted); } QUrl OpenProjectPage::getAbsoluteUrl( const QString& file ) const { QUrl u(file); if( u.isRelative() ) { u = fileWidget->baseUrl().resolved( u ); } return u; } void OpenProjectPage::setUrl(const QUrl& url) { fileWidget->setUrl(url, false); } void OpenProjectPage::dirChanged(const QUrl& /*url*/) { if(fileWidget->selectedFiles().isEmpty()) { KFileItemList items=fileWidget->dirOperator()->dirLister()->items(); foreach(const KFileItem& item, items) { if(item.url().path().endsWith(ShellExtension::getInstance()->projectFileExtension()) && item.isFile()) fileWidget->setSelection(item.url().url()); } } } void OpenProjectPage::showEvent(QShowEvent* ev) { fileWidget->locationEdit()->setFocus(); QWidget::showEvent(ev); } void OpenProjectPage::highlightFile(const QUrl& file) { emit urlSelected(file); } void OpenProjectPage::opsEntered(const QUrl& url) { emit urlSelected(url); } void OpenProjectPage::comboTextChanged( const QString& file ) { emit urlSelected( getAbsoluteUrl( file ) ); } -QMap OpenProjectPage::projectFilters() const -{ - return m_projectFilters; -} - } diff --git a/shell/openprojectpage.h b/shell/openprojectpage.h index 93deb49634..1e0ff606eb 100644 --- a/shell/openprojectpage.h +++ b/shell/openprojectpage.h @@ -1,53 +1,52 @@ /*************************************************************************** * 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, QWidget* parent = nullptr ); - QMap projectFilters() const; + 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; - QMap m_projectFilters; }; } #endif diff --git a/shell/sessioncontroller.cpp b/shell/sessioncontroller.cpp index 605dbfbd7d..adf8314d5d 100644 --- a/shell/sessioncontroller.cpp +++ b/shell/sessioncontroller.cpp @@ -1,676 +1,678 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessioncontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" #include "sessiondialog.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.h" #include "debug.h" #include #include #include #include #include namespace KDevelop { namespace { int argc = 0; char** argv = 0; }; void SessionController::setArguments(int _argc, char** _argv) { argc = _argc; argv = _argv; } static QStringList standardArguments() { QStringList ret; for(int a = 0; a < argc; ++a) { QString arg = QString::fromLocal8Bit(argv[a]); if(arg.startsWith(QLatin1String("-graphicssystem")) || arg.startsWith(QLatin1String("-style"))) { ret << '-' + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(0) , grp(0) { } ~SessionControllerPrivate() override { } Session* findSessionForName( const QString& name ) const { foreach( Session* s, sessionActions.keys() ) { if( s->name() == name ) return s; } return 0; } Session* findSessionForId(QString idString) { QUuid id(idString); foreach( Session* s, sessionActions.keys() ) { if( s->id() == id) return s; } return 0; } void newSession() { qsrand(QDateTime::currentDateTimeUtc().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << QStringLiteral("-s") << session->id().toString() << standardArguments()); delete session; #if 0 //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); #endif } void configureSessions() { SessionDialog dlg(ICore::self()->uiController()-> activeMainWindow()); dlg.exec(); } void deleteCurrentSession() { int choice = KMessageBox::warningContinueCancel(Core::self()->uiController()->activeMainWindow(), i18n("The current session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?")); if(choice == KMessageBox::Continue) { q->deleteSessionFromDisk(sessionLock); q->emitQuitSession(); } } void renameSession() { bool ok; auto newSessionName = QInputDialog::getText(Core::self()->uiController()->activeMainWindow(), i18n("Rename Session"), i18n("New Session Name:"), QLineEdit::Normal, q->activeSession()->name(), &ok); if (ok) { static_cast(q->activeSession())->setName(newSessionName); } q->updateXmlGuiActionList(); // resort } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << QStringLiteral("-s") << s->id().toString() << standardArguments()); return true; } TryLockSessionResult activateSession( Session* s ) { Q_ASSERT( s ); activeSession = s; TryLockSessionResult result = SessionController::tryLockSession( s->id().toString()); if( !result.lock ) { activeSession = 0; return result; } Q_ASSERT(s->id().toString() == result.lock->id()); sessionLock = result.lock; KConfigGroup grp = KSharedConfig::openConfig()->group( SessionController::cfgSessionGroup() ); grp.writeEntry( SessionController::cfgActiveSessionEntry(), s->id().toString() ); grp.sync(); if (Core::self()->setupFlags() & Core::NoUi) return result; QHash::iterator it = sessionActions.find(s); Q_ASSERT( it != sessionActions.end() ); (*it)->setCheckable(true); (*it)->setChecked(true); for(it = sessionActions.begin(); it != sessionActions.end(); ++it) { if(it.key() != s) (*it)->setCheckable(false); } return result; } void loadSessionFromAction(QAction* action) { auto session = action->data().value(); loadSessionExternally(session); } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { sessionActions[s] = 0; return; } QAction* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData(QVariant::fromValue(s)); sessionActions[s] = a; q->actionCollection()->addAction( "session_"+s->id().toString(), a ); connect( s, &Session::sessionUpdated, this, &SessionControllerPrivate::sessionUpdated ); sessionUpdated( s ); } SessionController* q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ qApp->applicationName() + "/sessions/"; } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } private slots: void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } }; SessionController::SessionController( QObject *parent ) : QObject( parent ), d(new SessionControllerPrivate(this)) { setObjectName(QStringLiteral("SessionController")); setComponentName(QStringLiteral("kdevsession"), QStringLiteral("KDevSession")); setXMLFile(QStringLiteral("kdevsessionui.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/SessionController"), this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction( QStringLiteral("new_session"), this, SLOT(newSession()) ); action->setText( i18nc("@action:inmenu", "Start New Session") ); action->setToolTip( i18nc("@info:tooltip", "Start a new KDevelop instance with an empty session") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); action = actionCollection()->addAction( QStringLiteral("rename_session"), this, SLOT(renameSession()) ); action->setText( i18n("Rename Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); action = actionCollection()->addAction( QStringLiteral("delete_session"), this, SLOT(deleteCurrentSession()) ); action->setText( i18n("Delete Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action = actionCollection()->addAction( QStringLiteral("quit"), this, SIGNAL(quitSession()) ); action->setText( i18n("Quit") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'Quit KDevelop...' actionCollection()->setDefaultShortcut( action, Qt::CTRL | Qt::Key_Q ); action->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); #if 0 action = actionCollection()->addAction( "configure_sessions", this, SLOT(configureSessions()) ); action->setText( i18n("Configure Sessions...") ); action->setToolTip( i18n("Create/Delete/Activate Sessions") ); action->setWhatsThis( i18n( "Shows a dialog to Create/Delete Sessions and set a new active session." ) ); #endif d->grp = new QActionGroup( this ); connect( d->grp, &QActionGroup::triggered, this, [&] (QAction* a) { d->loadSessionFromAction(a); } ); } SessionController::~SessionController() { delete d; } void SessionController::startNewSession() { d->newSession(); } void SessionController::cleanup() { if (d->activeSession) { Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); if (d->activeSession->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } d->activeSession = 0; } d->sessionLock.clear(); qDeleteAll(d->sessionActions); d->sessionActions.clear(); } void SessionController::initialize( const QString& session ) { QDir sessiondir( SessionControllerPrivate::sessionBaseDirectory() ); foreach( const QString& s, sessiondir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot ) ) { QUuid id( s ); if( id.isNull() ) continue; // Only create sessions for directories that represent proper uuid's Session* ses = new Session( id.toString(), this ); //Delete sessions that have no name and are empty if( ses->containedProjects().isEmpty() && ses->name().isEmpty() && (session.isEmpty() || (ses->id().toString() != session && ses->name() != session)) ) { TryLockSessionResult result = tryLockSession(s); if (result.lock) { deleteSessionFromDisk(result.lock); } delete ses; } else { d->addSession( ses ); } } loadDefaultSession( session ); updateXmlGuiActionList(); } ISession* SessionController::activeSession() const { return d->activeSession; } ISessionLock::Ptr SessionController::activeSessionLock() const { return d->sessionLock; } void SessionController::loadSession( const QString& nameOrId ) { d->loadSessionExternally( session( nameOrId ) ); } QList SessionController::sessionNames() const { QStringList l; foreach( const Session* s, d->sessionActions.keys() ) { l << s->name(); } return l; } QList< const KDevelop::Session* > SessionController::sessions() const { QList< const KDevelop::Session* > ret; foreach( const Session* s, d->sessionActions.keys() ) { ret << s; } return ret; } Session* SessionController::createSession( const QString& name ) { Session* s; if(name.startsWith('{')) { s = new Session( QUuid(name).toString() ); }else{ qsrand(QDateTime::currentDateTimeUtc().toTime_t()); s = new Session( QUuid::createUuid().toString() ); s->setName( name ); } d->addSession( s ); updateXmlGuiActionList(); return s; } void SessionController::deleteSession( const ISessionLock::Ptr& lock ) { Session* s = session(lock->id()); QHash::iterator it = d->sessionActions.find(s); Q_ASSERT( it != d->sessionActions.end() ); unplugActionList( QStringLiteral("available_sessions") ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( QStringLiteral("available_sessions"), d->grp->actions() ); } if (s == d->activeSession) { d->activeSession = nullptr; } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); delete s; } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { qCDebug(SHELL) << "Deleting session:" << lock->id(); static_cast(lock.data())->removeFromDisk(); ItemRepositoryRegistry::deleteRepositoryFromDisk( lock ); } void SessionController::loadDefaultSession( const QString& session ) { QString load = session; if (load.isEmpty()) { KConfigGroup grp = KSharedConfig::openConfig()->group( cfgSessionGroup() ); load = grp.readEntry( cfgActiveSessionEntry(), "default" ); } // Iteratively try to load the session, asking user what to do in case of failure // If showForceOpenDialog() returns empty string, stop trying Session* s = 0; do { s = this->session( load ); if( !s ) { s = createSession( load ); } TryLockSessionResult result = d->activateSession( s ); if( result.lock ) { Q_ASSERT(d->activeSession == s); Q_ASSERT(d->sessionLock = result.lock); break; } load = handleLockedSession( s->name(), s->id().toString(), result.runInfo ); } while( !load.isEmpty() ); } Session* SessionController::session( const QString& nameOrId ) const { Session* ret = d->findSessionForName( nameOrId ); if(ret) return ret; return d->findSessionForId( nameOrId ); } QString SessionController::cloneSession( const QString& nameOrid ) { Session* origSession = session( nameOrid ); qsrand(QDateTime::currentDateTimeUtc().toTime_t()); QUuid id = QUuid::createUuid(); auto copyJob = KIO::copy(QUrl::fromLocalFile(sessionDirectory(origSession->id().toString())), QUrl::fromLocalFile(sessionDirectory( id.toString()))); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); copyJob->exec(); Session* newSession = new Session( id.toString() ); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); updateXmlGuiActionList(); return newSession->name(); } void SessionController::updateXmlGuiActionList() { unplugActionList( QStringLiteral("available_sessions") ); - 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); + if (d->grp) { + auto actions = d->grp->actions(); + std::sort(actions.begin(), actions.end(), [](const QAction* lhs, const QAction* rhs) { + auto s1 = lhs->data().value(); + auto s2 = rhs->data().value(); + return QString::localeAwareCompare(s1->description(), s2->description()) < 0; + }); + plugActionList(QStringLiteral("available_sessions"), actions); + } } QString SessionController::cfgSessionGroup() { return QStringLiteral("Sessions"); } QString SessionController::cfgActiveSessionEntry() { return QStringLiteral("Active Session ID"); } QList SessionController::availableSessionInfo() { return availableSessionInfos().toList(); } SessionInfos SessionController::availableSessionInfos() { SessionInfos sessionInfos; foreach( const QString& sessionId, QDir( SessionControllerPrivate::sessionBaseDirectory() ).entryList( QDir::AllDirs ) ) { if( !QUuid( sessionId ).isNull() ) { sessionInfos << Session::parse( sessionId ); } } return sessionInfos; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id) { return SessionLock::tryLockSession(id, true); } bool SessionController::isSessionRunning(const QString& id) { return sessionRunInfo(id).isRunning; } SessionRunInfo SessionController::sessionRunInfo(const QString& id) { return SessionLock::tryLockSession(id, false).runInfo; } QString SessionController::showSessionChooserDialog(QString headerText, bool onlyRunning) { ///FIXME: move this code into sessiondialog.cpp QListView* view = new QListView; QLineEdit* filter = new QLineEdit; filter->setClearButtonEnabled( true ); filter->setPlaceholderText(i18n("Search")); QStandardItemModel* model = new QStandardItemModel(view); QSortFilterProxyModel *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, &QLineEdit::textChanged, proxy, &QSortFilterProxyModel::setFilterFixedString); SessionChooserDialog dialog(view, proxy, filter); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QVBoxLayout layout(dialog.mainWidget()); if(!headerText.isEmpty()) { QLabel* heading = new QLabel(headerText); QFont font = heading->font(); font.setBold(true); heading->setFont(font); layout.addWidget(heading); } model->setColumnCount(3); model->setHeaderData(0, Qt::Horizontal,i18n("Identity")); model->setHeaderData(1, Qt::Horizontal, i18n("Contents")); model->setHeaderData(2, Qt::Horizontal,i18n("State")); view->setModel(proxy); view->setModelColumn(1); QHBoxLayout* filterLayout = new QHBoxLayout(); filterLayout->addWidget(new QLabel(i18n("Filter:"))); filterLayout->addWidget(filter); layout.addLayout(filterLayout); layout.addWidget(view); filter->setFocus(); int row = 0; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); ++row; } model->sort(1); if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); const QModelIndex defaultSessionIndex = model->match(model->index(0, 0), Qt::DisplayRole, defaultSession, 1, Qt::MatchExactly).value(0); view->selectionModel()->setCurrentIndex(proxy->mapFromSource(defaultSessionIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ///@todo We need a way to get a proper size-hint from the view, but unfortunately, that only seems possible after the view was shown. dialog.resize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); if (!selected.isValid()) return QString(); const QString selectedSessionId = selected.sibling(selected.row(), 0).data().toString(); if (selectedSessionId.isEmpty()) { // "Create New Session" item selected, return a fresh UUID qsrand(QDateTime::currentDateTimeUtc().toTime_t()); return QUuid::createUuid().toString(); } return selectedSessionId; } QString SessionController::handleLockedSession( const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo ) { return SessionLock::handleLockedSession(sessionName, sessionId, runInfo); } QString SessionController::sessionDir() { if( !activeSession() ) return QString(); return d->ownSessionDirectory(); } QString SessionController::sessionName() { if(!activeSession()) return QString(); return activeSession()->description(); } } #include "sessioncontroller.moc" #include "moc_sessioncontroller.cpp" diff --git a/shell/watcheddocumentset.h b/shell/watcheddocumentset.h index 7a53703a91..9884d0bb69 100644 --- a/shell/watcheddocumentset.h +++ b/shell/watcheddocumentset.h @@ -1,133 +1,134 @@ /* * KDevelop Problem Reporter * * Copyright 2010 Dmitry Risenberg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_WATCHEDDOCUMENTSET_H #define KDEVPLATFORM_PLUGIN_WATCHEDDOCUMENTSET_H #include #include #include +#include #include "problemconstants.h" namespace KDevelop { class IDocument; class IProject; class ProjectFileItem; class Path; /** * Helper class that tracks set of documents and notifies its owner whenever this set changes. Derived classes implement different tracking strategies. */ -class WatchedDocumentSet : public QObject +class KDEVPLATFORMSHELL_EXPORT WatchedDocumentSet : public QObject { Q_OBJECT public: typedef QSet DocumentSet; explicit WatchedDocumentSet(QObject* parent); virtual DocumentSet get() const; virtual void setCurrentDocument(const IndexedString& url); virtual ProblemScope getScope() const = 0; ~WatchedDocumentSet() override {} signals: void changed(); protected: DocumentSet m_documents; }; /** * Tracks a document that is current at any given moment. * When a new file is activated, it becomes tracked instead of the old one. */ class CurrentDocumentSet : public WatchedDocumentSet { Q_OBJECT public: explicit CurrentDocumentSet(const IndexedString& document, QObject* parent); void setCurrentDocument(const IndexedString& url) override; ProblemScope getScope() const override; }; /** * Tracks all open documents. */ class OpenDocumentSet : public WatchedDocumentSet { Q_OBJECT public: explicit OpenDocumentSet(QObject* parent); ProblemScope getScope() const override; private slots: void documentClosed(IDocument* doc); void documentCreated(IDocument* doc); }; /** * Tracks documents that are in the same project as the current file. * If current file is not in any project, none are tracked. */ class ProjectSet : public WatchedDocumentSet { Q_OBJECT public: explicit ProjectSet(QObject* parent); protected: void trackProjectFiles(const IProject* project); protected slots: void fileAdded(ProjectFileItem*); void fileRemoved(ProjectFileItem* file); void fileRenamed(const Path& oldFile, ProjectFileItem* newFile); }; /** * Tracks files in all open projects. */ class CurrentProjectSet : public ProjectSet { Q_OBJECT public: explicit CurrentProjectSet(const IndexedString& document, QObject* parent); void setCurrentDocument(const IndexedString& url) override; ProblemScope getScope() const override; private: void setCurrentDocumentInternal(const IndexedString& url); // to avoid virtual in constructor IProject* m_currentProject; }; class AllProjectSet : public ProjectSet { Q_OBJECT public: explicit AllProjectSet(QObject* parent); ProblemScope getScope() const override; }; } #endif // KDEVPLATFORM_PLUGIN_WATCHEDDOCUMENTSET_H