File indexing completed on 2024-05-12 04:37:35
0001 /* 0002 SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "framestackmodel.h" 0008 0009 #include <QFileInfo> 0010 #include <QIcon> 0011 #include <QMimeType> 0012 #include <QMimeDatabase> 0013 0014 #include <KLocalizedString> 0015 #include <KColorScheme> 0016 0017 #include "../../interfaces/icore.h" 0018 #include "../../interfaces/iprojectcontroller.h" 0019 #include "../interfaces/isession.h" 0020 #include <debug.h> 0021 0022 namespace KDevelop { 0023 0024 class FrameStackModelPrivate 0025 { 0026 public: 0027 explicit FrameStackModelPrivate(FrameStackModel* q) : q(q) {} 0028 0029 void update(); 0030 0031 QModelIndex indexForThreadNumber(int threadNumber); 0032 0033 FrameStackModel* q; 0034 0035 int m_currentThread = -1; 0036 int m_currentFrame = -1; 0037 0038 int m_crashedThreadIndex = -1; 0039 0040 // used to count how often a user has scrolled down and more frames needed to be fetched; 0041 // this way, the number of frames fetched in each chunk can be increased if the user wants 0042 // to scroll far 0043 int m_subsequentFrameFetchOperations = 0; 0044 bool m_updateCurrentFrameOnNextFetch = false; 0045 0046 QVector<FrameStackModel::ThreadItem> m_threads; 0047 QHash<int, QVector<FrameStackModel::FrameItem>> m_frames; 0048 QHash<int, bool> m_hasMoreFrames; 0049 0050 // Caches 0051 mutable QHash<QString, bool> m_fileExistsCache; 0052 }; 0053 0054 FrameStackModel::FrameStackModel(IDebugSession *session) 0055 : IFrameStackModel(session) 0056 , d_ptr(new FrameStackModelPrivate(this)) 0057 { 0058 connect(session, &IDebugSession::stateChanged, this, &FrameStackModel::stateChanged); 0059 } 0060 0061 FrameStackModel::~FrameStackModel() 0062 { 0063 } 0064 0065 void FrameStackModel::setThreads(const QVector<ThreadItem>& threads) 0066 { 0067 Q_D(FrameStackModel); 0068 0069 qCDebug(DEBUGGER) << threads.count(); 0070 0071 if (!d->m_threads.isEmpty()) { 0072 beginRemoveRows(QModelIndex(), 0, d->m_threads.count()-1); 0073 d->m_threads.clear(); 0074 endRemoveRows(); 0075 } 0076 0077 if (!threads.isEmpty()) { 0078 beginInsertRows(QModelIndex(), 0, threads.count()-1); 0079 d->m_threads = threads; 0080 endInsertRows(); 0081 } 0082 } 0083 0084 QModelIndex FrameStackModelPrivate::indexForThreadNumber(int threadNumber) 0085 { 0086 int i=0; 0087 for (const auto& t : qAsConst(m_threads)) { 0088 if (t.nr == threadNumber) { 0089 return q->index(i, 0); 0090 } 0091 i++; 0092 } 0093 return QModelIndex(); 0094 } 0095 0096 void FrameStackModel::setFrames(int threadNumber, const QVector<FrameItem>& frames) 0097 { 0098 Q_D(FrameStackModel); 0099 0100 QModelIndex threadIndex = d->indexForThreadNumber(threadNumber); 0101 Q_ASSERT(threadIndex.isValid()); 0102 0103 auto& threadFrames = d->m_frames[threadNumber]; 0104 if (!threadFrames.empty()) { 0105 beginRemoveRows(threadIndex, 0, threadFrames.size() - 1); 0106 threadFrames.clear(); 0107 endRemoveRows(); 0108 } 0109 0110 if (!frames.isEmpty()) { 0111 beginInsertRows(threadIndex, 0, frames.count()-1); 0112 threadFrames = frames; 0113 endInsertRows(); 0114 } 0115 0116 if (d->m_currentThread == threadNumber && d->m_updateCurrentFrameOnNextFetch) { 0117 d->m_currentFrame = 0; 0118 d->m_updateCurrentFrameOnNextFetch = false; 0119 } 0120 0121 d->m_fileExistsCache.clear(); 0122 0123 session()->raiseEvent(IDebugSession::thread_or_frame_changed); 0124 0125 // FIXME: Ugly hack. Apparently, when rows are added, the selection 0126 // in the view is cleared. Emit this so that some frame is still 0127 // selected. 0128 emit currentFrameChanged(d->m_currentFrame); 0129 } 0130 0131 void FrameStackModel::insertFrames(int threadNumber, const QVector<FrameItem>& frames) 0132 { 0133 Q_D(FrameStackModel); 0134 0135 QModelIndex threadIndex = d->indexForThreadNumber(threadNumber); 0136 Q_ASSERT(threadIndex.isValid()); 0137 0138 auto& threadFrames = d->m_frames[threadNumber]; 0139 beginInsertRows(threadIndex, threadFrames.size() - 1, threadFrames.size() + frames.size() - 1); 0140 threadFrames << frames; 0141 endInsertRows(); 0142 } 0143 0144 void FrameStackModel::setHasMoreFrames(int threadNumber, bool hasMoreFrames) 0145 { 0146 Q_D(FrameStackModel); 0147 0148 d->m_hasMoreFrames[threadNumber] = hasMoreFrames; 0149 } 0150 0151 FrameStackModel::FrameItem FrameStackModel::frame(const QModelIndex& index) 0152 { 0153 Q_D(FrameStackModel); 0154 0155 Q_ASSERT(index.internalId()); 0156 Q_ASSERT(static_cast<quintptr>(d->m_threads.count()) >= index.internalId()); 0157 const ThreadItem &thread = d->m_threads.at(index.internalId()-1); 0158 return d->m_frames[thread.nr].at(index.row()); 0159 } 0160 0161 QVector<FrameStackModel::FrameItem> FrameStackModel::frames(int threadNumber) const 0162 { 0163 Q_D(const FrameStackModel); 0164 0165 return d->m_frames.value(threadNumber); 0166 } 0167 0168 QVariant FrameStackModel::data(const QModelIndex& index, int role) const 0169 { 0170 Q_D(const FrameStackModel); 0171 0172 if (!index.internalId()) { 0173 //thread 0174 if (d->m_threads.count() <= index.row()) return QVariant(); 0175 const ThreadItem &thread = d->m_threads.at(index.row()); 0176 if (index.column() == 0) { 0177 if (role == Qt::DisplayRole) { 0178 if (thread.nr == d->m_crashedThreadIndex) { 0179 return i18nc("#thread-id at function-name or address", "#%1 at %2 (crashed)", thread.nr, thread.name); 0180 } else { 0181 return i18nc("#thread-id at function-name or address", "#%1 at %2", thread.nr, thread.name); 0182 } 0183 } else if (role == Qt::ForegroundRole) { 0184 if (thread.nr == d->m_crashedThreadIndex) { 0185 KColorScheme scheme(QPalette::Active); 0186 return scheme.foreground(KColorScheme::NegativeText).color(); 0187 } 0188 } 0189 } 0190 } else { 0191 //frame 0192 if (static_cast<quintptr>(d->m_threads.count()) < index.internalId()) return QVariant(); 0193 const ThreadItem &thread = d->m_threads.at(index.internalId()-1); 0194 const auto& threadFrames = d->m_frames[thread.nr]; 0195 if (index.row() >= threadFrames.size()) 0196 return QVariant(); 0197 const FrameItem& frame = threadFrames.at(index.row()); 0198 if (index.column() == 0) { 0199 if (role == Qt::DisplayRole) { 0200 return QVariant(QString::number(frame.nr)); 0201 } 0202 } else if (index.column() == 1) { 0203 if (role == Qt::DisplayRole) { 0204 return QVariant(frame.name); 0205 } 0206 } else if (index.column() == 2) { 0207 if (role == Qt::DisplayRole) { 0208 QString ret = ICore::self()->projectController() 0209 ->prettyFileName(frame.file, IProjectController::FormatPlain); 0210 if (frame.line != -1) { 0211 ret += QLatin1Char(':') + QString::number(frame.line + 1); 0212 } 0213 return ret; 0214 } else if (role == Qt::DecorationRole) { 0215 QMimeType mime = QMimeDatabase().mimeTypeForUrl(frame.file); 0216 return QIcon::fromTheme(mime.iconName()); 0217 } else if (role == Qt::ForegroundRole) { 0218 const auto fileName = frame.file.toLocalFile(); 0219 auto cacheIt = d->m_fileExistsCache.find(fileName); 0220 if (cacheIt == d->m_fileExistsCache.end()) { 0221 cacheIt = d->m_fileExistsCache.insert(fileName, QFileInfo::exists(fileName)); 0222 } 0223 const bool fileExists = cacheIt.value(); 0224 if (!fileExists) { 0225 KColorScheme scheme(QPalette::Active); 0226 return scheme.foreground(KColorScheme::InactiveText).color(); 0227 } 0228 } 0229 } 0230 } 0231 return QVariant(); 0232 } 0233 0234 int FrameStackModel::columnCount(const QModelIndex& parent) const 0235 { 0236 Q_UNUSED(parent); 0237 return 3; 0238 } 0239 0240 int FrameStackModel::rowCount(const QModelIndex& parent) const 0241 { 0242 Q_D(const FrameStackModel); 0243 0244 if (!parent.isValid()) { 0245 return d->m_threads.count(); 0246 } else if (!parent.internalId() && parent.column() == 0) { 0247 if (parent.row() < d->m_threads.count()) { 0248 return d->m_frames[d->m_threads.at(parent.row()).nr].count(); 0249 } 0250 } 0251 return 0; 0252 } 0253 0254 QModelIndex FrameStackModel::parent(const QModelIndex& child) const 0255 { 0256 if (!child.internalId()) { 0257 return QModelIndex(); 0258 } else { 0259 return index(child.internalId()-1, 0); 0260 } 0261 } 0262 0263 QModelIndex FrameStackModel::index(int row, int column, const QModelIndex& parent) const 0264 { 0265 Q_D(const FrameStackModel); 0266 0267 if (parent.isValid()) { 0268 Q_ASSERT(!parent.internalId()); 0269 Q_ASSERT(parent.column() == 0); 0270 Q_ASSERT(parent.row() < d->m_threads.count()); 0271 return createIndex(row, column, parent.row()+1); 0272 } else { 0273 return createIndex(row, column); 0274 } 0275 } 0276 0277 0278 QVariant FrameStackModel::headerData(int section, Qt::Orientation orientation, int role) const 0279 { 0280 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { 0281 if (section == 0) { 0282 return i18n("Depth"); 0283 } else if (section == 1) { 0284 return i18n("Function"); 0285 } else if (section == 2) { 0286 return i18n("Source"); 0287 } 0288 } 0289 return QAbstractItemModel::headerData(section, orientation, role); 0290 } 0291 0292 void FrameStackModel::setCurrentThread(int threadNumber) 0293 { 0294 Q_D(FrameStackModel); 0295 0296 qCDebug(DEBUGGER) << threadNumber; 0297 if (d->m_currentThread != threadNumber && threadNumber != -1) { 0298 // FIXME: this logic means that if we switch to thread 3 and 0299 // then to thread 2 and then to thread 3, we'll request frames 0300 // for thread 3 again, even if the program was not run in between 0301 // and therefore frames could not have changed. 0302 d->m_currentFrame = 0; //set before fetchFrames else --frame argument would be wrong 0303 d->m_updateCurrentFrameOnNextFetch = true; 0304 fetchFrames(threadNumber, 0, 20); 0305 } 0306 if (threadNumber != d->m_currentThread) { 0307 d->m_currentFrame = 0; 0308 d->m_currentThread = threadNumber; 0309 emit currentFrameChanged(d->m_currentFrame); 0310 } 0311 qCDebug(DEBUGGER) << "currentThread: " << d->m_currentThread << "currentFrame: " << d->m_currentFrame; 0312 emit currentThreadChanged(threadNumber); 0313 session()->raiseEvent(IDebugSession::thread_or_frame_changed); 0314 } 0315 0316 void FrameStackModel::setCurrentThread(const QModelIndex& index) 0317 { 0318 Q_D(const FrameStackModel); 0319 0320 Q_ASSERT(index.isValid()); 0321 Q_ASSERT(!index.internalId()); 0322 Q_ASSERT(index.column() == 0); 0323 setCurrentThread(d->m_threads[index.row()].nr); 0324 } 0325 0326 void FrameStackModel::setCrashedThreadIndex(int index) 0327 { 0328 Q_D(FrameStackModel); 0329 0330 d->m_crashedThreadIndex = index; 0331 } 0332 0333 int FrameStackModel::crashedThreadIndex() const 0334 { 0335 Q_D(const FrameStackModel); 0336 0337 return d->m_crashedThreadIndex; 0338 } 0339 0340 int FrameStackModel::currentThread() const 0341 { 0342 Q_D(const FrameStackModel); 0343 0344 return d->m_currentThread; 0345 } 0346 0347 QModelIndex FrameStackModel::currentThreadIndex() const 0348 { 0349 Q_D(const FrameStackModel); 0350 0351 int i = 0; 0352 for (const ThreadItem& t : qAsConst(d->m_threads)) { 0353 if (t.nr == currentThread()) { 0354 return index(i, 0); 0355 } 0356 ++i; 0357 } 0358 return QModelIndex(); 0359 } 0360 0361 int FrameStackModel::currentFrame() const 0362 { 0363 Q_D(const FrameStackModel); 0364 0365 return d->m_currentFrame; 0366 } 0367 0368 QModelIndex FrameStackModel::currentFrameIndex() const 0369 { 0370 Q_D(const FrameStackModel); 0371 0372 return index(d->m_currentFrame, 0, currentThreadIndex()); 0373 } 0374 0375 void FrameStackModel::setCurrentFrame(int frame) 0376 { 0377 Q_D(FrameStackModel); 0378 0379 qCDebug(DEBUGGER) << frame; 0380 if (frame != d->m_currentFrame) 0381 { 0382 d->m_currentFrame = frame; 0383 session()->raiseEvent(IDebugSession::thread_or_frame_changed); 0384 emit currentFrameChanged(frame); 0385 } 0386 } 0387 0388 void FrameStackModelPrivate::update() 0389 { 0390 m_subsequentFrameFetchOperations = 0; 0391 q->fetchThreads(); 0392 if (m_currentThread != -1) { 0393 q->fetchFrames(m_currentThread, 0, 20); 0394 } 0395 } 0396 0397 void FrameStackModel::handleEvent(IDebugSession::event_t event) 0398 { 0399 Q_D(FrameStackModel); 0400 0401 switch (event) 0402 { 0403 case IDebugSession::program_state_changed: 0404 d->update(); 0405 break; 0406 0407 default: 0408 break; 0409 } 0410 } 0411 0412 void FrameStackModel::stateChanged(IDebugSession::DebuggerState state) 0413 { 0414 Q_D(FrameStackModel); 0415 0416 if (state == IDebugSession::PausedState) { 0417 setCurrentFrame(-1); 0418 d->m_updateCurrentFrameOnNextFetch = true; 0419 } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { 0420 setThreads(QVector<FrameStackModel::ThreadItem>()); 0421 } 0422 } 0423 0424 // FIXME: it should be possible to fetch more frames for 0425 // an arbitrary thread, without making it current. 0426 void FrameStackModel::fetchMoreFrames() 0427 { 0428 Q_D(FrameStackModel); 0429 0430 d->m_subsequentFrameFetchOperations += 1; 0431 const int fetch = 20 * d->m_subsequentFrameFetchOperations * d->m_subsequentFrameFetchOperations; 0432 if (d->m_currentThread != -1 && d->m_hasMoreFrames[d->m_currentThread]) { 0433 setHasMoreFrames(d->m_currentThread, false); 0434 const int frameCount = d->m_frames[d->m_currentThread].size(); 0435 fetchFrames(d->m_currentThread, frameCount, frameCount - 1 + fetch); 0436 } 0437 } 0438 0439 } 0440 0441 #include "moc_framestackmodel.cpp"