diff --git a/debugger/framestack/framestackmodel.cpp b/debugger/framestack/framestackmodel.cpp index 960a93b80a..c9b1be52af 100644 --- a/debugger/framestack/framestackmodel.cpp +++ b/debugger/framestack/framestackmodel.cpp @@ -1,353 +1,369 @@ /*************************************************************************** * 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 "framestackmodel.h" #include #include #include #include +#include #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../../interfaces/iprojectcontroller.h" #include "../interfaces/isession.h" #include "util/debug.h" namespace KDevelop { FrameStackModel::FrameStackModel(IDebugSession *session) : IFrameStackModel(session) , m_currentThread(-1) , m_currentFrame(-1) + , m_crashedThreadIndex(-1) , m_subsequentFrameFetchOperations(0) , m_updateCurrentFrameOnNextFetch(false) { connect(session, &IDebugSession::stateChanged, this, &FrameStackModel::stateChanged); } FrameStackModel::~FrameStackModel() { } void FrameStackModel::setThreads(const QList &threads) { qCDebug(DEBUGGER) << threads.count(); if (!m_threads.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_threads.count()-1); m_threads.clear(); endRemoveRows(); } if (!threads.isEmpty()) { beginInsertRows(QModelIndex(), 0, threads.count()-1); m_threads = threads; endInsertRows(); } } QModelIndex FrameStackModel::indexForThreadNumber(int threadNumber) { int i=0; foreach (const ThreadItem &t, m_threads) { if (t.nr == threadNumber) { return index(i, 0); } i++; } return QModelIndex(); } void FrameStackModel::setFrames(int threadNumber, QList frames) { QModelIndex threadIndex = indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); if (!m_frames[threadNumber].isEmpty()) { beginRemoveRows(threadIndex, 0, m_frames[threadNumber].count()-1); m_frames[threadNumber].clear(); endRemoveRows(); } if (!frames.isEmpty()) { beginInsertRows(threadIndex, 0, frames.count()-1); m_frames[threadNumber] = frames; endInsertRows(); } if (m_currentThread == threadNumber && m_updateCurrentFrameOnNextFetch) { m_currentFrame = 0; m_updateCurrentFrameOnNextFetch = false; } session()->raiseEvent(IDebugSession::thread_or_frame_changed); // FIXME: Ugly hack. Apparently, when rows are added, the selection // in the view is cleared. Emit this so that some frame is still // selected. emit currentFrameChanged(m_currentFrame); } void FrameStackModel::insertFrames(int threadNumber, const QList &frames) { QModelIndex threadIndex = indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); beginInsertRows(threadIndex, m_frames[threadNumber].count()-1, m_frames[threadNumber].count()+frames.count()-1); m_frames[threadNumber] << frames; endInsertRows(); } void FrameStackModel::setHasMoreFrames(int threadNumber, bool hasMoreFrames) { m_hasMoreFrames[threadNumber] = hasMoreFrames; } FrameStackModel::FrameItem FrameStackModel::frame(const QModelIndex& index) { Q_ASSERT(index.internalId()); Q_ASSERT(static_cast(m_threads.count()) >= index.internalId()); const ThreadItem &thread = m_threads.at(index.internalId()-1); return m_frames[thread.nr].at(index.row()); } QList FrameStackModel::frames(int threadNumber) const { return m_frames.value(threadNumber); } QVariant FrameStackModel::data(const QModelIndex& index, int role) const { if (!index.internalId()) { //thread if (m_threads.count() <= index.row()) return QVariant(); const ThreadItem &thread = m_threads.at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { return i18nc("#thread-id at function-name or address", "#%1 at %2", thread.nr, thread.name); + } else if (role == Qt::TextColorRole) { + if (thread.nr == m_crashedThreadIndex) { + KColorScheme scheme(QPalette::Active); + return scheme.foreground(KColorScheme::NegativeText).color(); + } } } } else { //frame if (static_cast(m_threads.count()) < index.internalId()) return QVariant(); const ThreadItem &thread = m_threads.at(index.internalId()-1); if (m_frames[thread.nr].count() <= index.row()) return QVariant(); const FrameItem &frame = m_frames[thread.nr].at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { return QVariant(QString::number(frame.nr)); } } else if (index.column() == 1) { if (role == Qt::DisplayRole) { return QVariant(frame.name); } } else if (index.column() == 2) { if (role == Qt::DisplayRole) { QString ret = ICore::self()->projectController() ->prettyFileName(frame.file, IProjectController::FormatPlain); if (frame.line != -1) { ret += ':' + QString::number(frame.line + 1); } return ret; } else if (role == Qt::DecorationRole) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(frame.file); return QIcon::fromTheme(mime.iconName()); } } } return QVariant(); } int FrameStackModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 3; } int FrameStackModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return m_threads.count(); } else if (!parent.internalId() && parent.column() == 0) { if (parent.row() < m_threads.count()) { return m_frames[m_threads.at(parent.row()).nr].count(); } } return 0; } QModelIndex FrameStackModel::parent(const QModelIndex& child) const { if (!child.internalId()) { return QModelIndex(); } else { return index(child.internalId()-1, 0); } } QModelIndex FrameStackModel::index(int row, int column, const QModelIndex& parent) const { if (parent.isValid()) { Q_ASSERT(!parent.internalId()); Q_ASSERT(parent.column() == 0); Q_ASSERT(parent.row() < m_threads.count()); return createIndex(row, column, parent.row()+1); } else { return createIndex(row, column); } } QVariant FrameStackModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) { return i18n("Depth"); } else if (section == 1) { return i18n("Function"); } else if (section == 2) { return i18n("Source"); } } return QAbstractItemModel::headerData(section, orientation, role); } void FrameStackModel::setCurrentThread(int threadNumber) { qCDebug(DEBUGGER) << threadNumber; if (m_currentThread != threadNumber && threadNumber != -1) { // FIXME: this logic means that if we switch to thread 3 and // then to thread 2 and then to thread 3, we'll request frames // for thread 3 again, even if the program was not run in between // and therefore frames could not have changed. m_currentFrame = 0; //set before fetchFrames else --frame argument would be wrong m_updateCurrentFrameOnNextFetch = true; fetchFrames(threadNumber, 0, 20); } if (threadNumber != m_currentThread) { m_currentFrame = 0; qCDebug(DEBUGGER) << "currentFrame" << m_currentFrame; m_currentThread = threadNumber; emit currentFrameChanged(m_currentFrame); } qCDebug(DEBUGGER) << "currentThread: " << m_currentThread << "currentFrame: " << m_currentFrame; emit currentThreadChanged(threadNumber); session()->raiseEvent(IDebugSession::thread_or_frame_changed); } void FrameStackModel::setCurrentThread(const QModelIndex& index) { Q_ASSERT(index.isValid()); Q_ASSERT(!index.internalId()); Q_ASSERT(index.column() == 0); setCurrentThread(m_threads[index.row()].nr); } +void FrameStackModel::setCrashedThreadIndex(int index) +{ + m_crashedThreadIndex = index; +} + +int FrameStackModel::crashedThreadIndex() const +{ + return m_crashedThreadIndex; +} int FrameStackModel::currentThread() const { return m_currentThread; } QModelIndex FrameStackModel::currentThreadIndex() const { int i = 0; foreach (const ThreadItem &t, m_threads) { if (t.nr == currentThread()) { return index(i, 0); } ++i; } return QModelIndex(); } int FrameStackModel::currentFrame() const { return m_currentFrame; } QModelIndex FrameStackModel::currentFrameIndex() const { QModelIndex idx = currentThreadIndex(); return idx.child(m_currentFrame, 0); } void FrameStackModel::setCurrentFrame(int frame) { qCDebug(DEBUGGER) << frame; if (frame != m_currentFrame) { m_currentFrame = frame; session()->raiseEvent(IDebugSession::thread_or_frame_changed); emit currentFrameChanged(frame); } } void FrameStackModel::update() { m_subsequentFrameFetchOperations = 0; fetchThreads(); if (m_currentThread != -1) { fetchFrames(m_currentThread, 0, 20); } } void FrameStackModel::handleEvent(IDebugSession::event_t event) { switch (event) { case IDebugSession::program_state_changed: update(); break; default: break; } } void FrameStackModel::stateChanged(IDebugSession::DebuggerState state) { if (state == IDebugSession::PausedState) { setCurrentFrame(-1); m_updateCurrentFrameOnNextFetch = true; } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { setThreads(QList()); } } // FIXME: it should be possible to fetch more frames for // an arbitrary thread, without making it current. void FrameStackModel::fetchMoreFrames() { m_subsequentFrameFetchOperations += 1; const int fetch = 20 * m_subsequentFrameFetchOperations * m_subsequentFrameFetchOperations; if (m_currentThread != -1 && m_hasMoreFrames[m_currentThread]) { setHasMoreFrames(m_currentThread, false); fetchFrames(m_currentThread, m_frames[m_currentThread].count(), m_frames[m_currentThread].count()-1+fetch); } } } diff --git a/debugger/framestack/framestackmodel.h b/debugger/framestack/framestackmodel.h index 256215616f..b50c7bf9e5 100644 --- a/debugger/framestack/framestackmodel.h +++ b/debugger/framestack/framestackmodel.h @@ -1,112 +1,118 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_FRAMESTACKMODEL_H #define KDEVPLATFORM_FRAMESTACKMODEL_H #include #include #include #include #include namespace KDevelop { class DebugController; /** FIXME: This class needs rework, since at present it is not true model. Client cannot just obtain frames by grabbing a thread and listing children. It should first setCurrentThread beforehand, and it is the method that will actually fetch threads. Therefore, if this model is submitted to plain QTreeView, it won't work at all. Ideally, this should hold current thread and current frame numbers, and only fetch the list of threads, and list of frames inside thread when asked for by the view. */ class KDEVPLATFORMDEBUGGER_EXPORT FrameStackModel : public IFrameStackModel { Q_OBJECT public: explicit FrameStackModel(IDebugSession* session); ~FrameStackModel() override; struct ThreadItem { int nr; QString name; }; void setThreads(const QList &threads); /** * Update frames for thread @p threadNumber * * @note The currentFrame property will be set to the first frame * containing debug information */ void setFrames(int threadNumber, QList frames); void insertFrames(int threadNumber, const QList &frames); void setHasMoreFrames(int threadNumber, bool hasMoreFrames); FrameItem frame(const QModelIndex &index) override; QList frames(int threadNumber) const; //ItemModel implementation QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& child) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void setCurrentThread(int threadNumber) override; void setCurrentThread(const QModelIndex &index) override; int currentThread() const override; QModelIndex currentThreadIndex() const override; int currentFrame() const override; QModelIndex currentFrameIndex() const override; void setCurrentFrame(int frame) override; void fetchMoreFrames() override; + void setCrashedThreadIndex(int index); + int crashedThreadIndex() const; + private Q_SLOTS: void stateChanged(KDevelop::IDebugSession::DebuggerState state); private: void handleEvent(IDebugSession::event_t event) override; void update(); QModelIndex indexForThreadNumber(int threadNumber); int m_currentThread; int m_currentFrame; + + int m_crashedThreadIndex; + // used to count how often a user has scrolled down and more frames needed to be fetched; // this way, the number of frames fetched in each chunk can be increased if the user wants // to scroll far int m_subsequentFrameFetchOperations; bool m_updateCurrentFrameOnNextFetch; QList m_threads; QHash > m_frames; QHash m_hasMoreFrames; }; } #endif