/*************************************************************************** * 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 "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../../interfaces/iprojectcontroller.h" #include "../interfaces/isession.h" namespace KDevelop { FrameStackModel::FrameStackModel(IDebugSession *session) : IFrameStackModel(session) , m_currentThread(-1) , m_currentFrame(-1) , m_subsequentFrameFetchOperations(0) , m_updateCurrentFrameOnNextFetch(false) { connect(session, SIGNAL(stateChanged(KDevelop::IDebugSession::DebuggerState)), SLOT(stateChanged(KDevelop::IDebugSession::DebuggerState))); } FrameStackModel::~FrameStackModel() { } void FrameStackModel::setThreads(const QList &threads) { kDebug() << 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(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 { //frame if (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) { KMimeType::Ptr p = KMimeType::findByUrl(frame.file); return KIcon(p->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, 0); } } 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) { kDebug() << 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; kDebug() << "currentFrame" << m_currentFrame; m_currentThread = threadNumber; emit currentFrameChanged(m_currentFrame); } kDebug() << "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); } 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) { kDebug() << 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); } } } #include "moc_framestackmodel.cpp"