/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "threadshandler.h" #include "debuggeractions.h" #include "debuggercore.h" #include "debuggerengine.h" #include "debuggericons.h" #include "debuggerprotocol.h" #include "watchutils.h" #include #include #include #include #include #include #include #include using namespace Utils; namespace Debugger { namespace Internal { //////////////////////////////////////////////////////////////////////// // // ThreadItem // /////////////////////////////////////////////////////////////////////// // ThreadItem ThreadItem::ThreadItem(const ThreadsHandler *handler, const ThreadData &data) : threadData(data), handler(handler) {} QVariant ThreadItem::data(int column, int role) const { switch (role) { case Qt::DisplayRole: if (column == 0) return QString("#%1 %2").arg(threadData.id).arg(threadData.name); return threadPart(column); case Qt::ToolTipRole: return threadToolTip(); case Qt::DecorationRole: // Return icon that indicates whether this is the active stack frame. if (column == 0) return this == handler->currentThread() ? Icons::LOCATION.icon() : Icons::EMPTY.icon(); break; default: break; } return QVariant(); } Qt::ItemFlags ThreadItem::flags(int column) const { return threadData.stopped ? TreeItem::flags(column) : Qt::ItemFlags({}); } QString ThreadItem::threadToolTip() const { const char start[] = ""; const char sep[] = ""; const char end[] = ""; QString rc; QTextStream str(&rc); str << "" << start << ThreadsHandler::tr("Thread id:") << sep << threadData.id << end; if (!threadData.targetId.isEmpty()) str << start << ThreadsHandler::tr("Target id:") << sep << threadData.targetId << end; if (!threadData.groupId.isEmpty()) str << start << ThreadsHandler::tr("Group id:") << sep << threadData.groupId << end; if (!threadData.name.isEmpty()) str << start << ThreadsHandler::tr("Name:") << sep << threadData.name << end; if (!threadData.state.isEmpty()) str << start << ThreadsHandler::tr("State:") << sep << threadData.state << end; if (!threadData.core.isEmpty()) str << start << ThreadsHandler::tr("Core:") << sep << threadData.core << end; if (threadData.address) { str << start << ThreadsHandler::tr("Stopped at:") << sep; if (!threadData.function.isEmpty()) str << threadData.function << "
"; if (!threadData.fileName.isEmpty()) str << threadData.fileName << ':' << threadData.lineNumber << "
"; str << formatToolTipAddress(threadData.address); } str << "
"; return rc; } QVariant ThreadItem::threadPart(int column) const { switch (column) { case ThreadData::IdColumn: return threadData.id; case ThreadData::FunctionColumn: return threadData.function; case ThreadData::FileColumn: return threadData.fileName.isEmpty() ? threadData.module : threadData.fileName; case ThreadData::LineColumn: return threadData.lineNumber >= 0 ? QString::number(threadData.lineNumber) : QString(); case ThreadData::AddressColumn: return threadData.address > 0 ? QLatin1String("0x") + QString::number(threadData.address, 16) : QString(); case ThreadData::CoreColumn: return threadData.core; case ThreadData::StateColumn: return threadData.state; case ThreadData::TargetIdColumn: if (threadData.targetId.startsWith(QLatin1String("Thread "))) return threadData.targetId.mid(7); return threadData.targetId; case ThreadData::NameColumn: return threadData.name; case ThreadData::DetailsColumn: return threadData.details; case ThreadData::ComboNameColumn: return QString::fromLatin1("#%1 %2").arg(threadData.id).arg(threadData.name); } return QVariant(); } void ThreadItem::notifyRunning() // Clear state information. { threadData.address = 0; threadData.function.clear(); threadData.fileName.clear(); threadData.frameLevel = -1; threadData.state.clear(); threadData.lineNumber = -1; threadData.stopped = false; update(); } void ThreadItem::notifyStopped() { threadData.stopped = true; update(); } void ThreadItem::mergeThreadData(const ThreadData &other) { if (!other.core.isEmpty()) threadData.core = other.core; if (!other.fileName.isEmpty()) threadData.fileName = other.fileName; if (!other.targetId.isEmpty()) threadData.targetId = other.targetId; if (!other.name.isEmpty()) threadData.name = other.name; if (other.frameLevel != -1) threadData.frameLevel = other.frameLevel; if (!other.function.isEmpty()) threadData.function = other.function; if (other.address) threadData.address = other.address; if (!other.module.isEmpty()) threadData.module = other.module; if (!other.details.isEmpty()) threadData.details = other.details; if (!other.state.isEmpty()) threadData.state = other.state; if (other.lineNumber != -1) threadData.lineNumber = other.lineNumber; update(); } // ThreadsHandler /*! \class Debugger::Internal::ThreadData \internal \brief The ThreadData class contains information about a single thread. */ /*! \class Debugger::Internal::ThreadsHandler \internal \brief The ThreadsHandler class provides a model to represent the running threads in a QTreeView or ComboBox. */ ThreadsHandler::ThreadsHandler(DebuggerEngine *engine) : m_engine(engine) { setObjectName(QLatin1String("ThreadsModel")); setHeader({ QLatin1String(" ") + tr("ID") + QLatin1String(" "), tr("Address"), tr("Function"), tr("File"), tr("Line"), tr("State"), tr("Name"), tr("Target ID"), tr("Details"), tr("Core"), }); } bool ThreadsHandler::setData(const QModelIndex &idx, const QVariant &data, int role) { if (role == BaseTreeView::ItemActivatedRole) { const Thread thread = itemForIndexAtLevel<1>(idx); m_engine->selectThread(thread); return true; } if (role == BaseTreeView::ItemViewEventRole) { ItemViewEvent ev = data.value(); if (ev.as()) { auto menu = new QMenu; menu->addAction(action(SettingsDialog)); menu->popup(ev.globalPos()); return true; } } return false; } int ThreadsHandler::currentThreadIndex() const { return rootItem()->indexOf(m_currentThread); } void ThreadsHandler::sort(int column, Qt::SortOrder order) { rootItem()->sortChildren([order, column](const ThreadItem *item1, const ThreadItem *item2) -> bool { const QVariant v1 = item1->threadPart(column); const QVariant v2 = item2->threadPart(column); if (v1 == v2) return false; if (column == 0) return (v1.toInt() < v2.toInt()) ^ (order == Qt::DescendingOrder); // FIXME: Use correct toXXX(); return (v1.toString() < v2.toString()) ^ (order == Qt::DescendingOrder); }); } Thread ThreadsHandler::currentThread() const { return m_currentThread; } Thread ThreadsHandler::threadAt(int index) const { QTC_ASSERT(index >= 0 && index < rootItem()->childCount(), return Thread()); return rootItem()->childAt(index); } void ThreadsHandler::setCurrentThread(const Thread &thread) { if (thread == m_currentThread) return; if (!threadForId(thread->id())) { qWarning("ThreadsHandler::setCurrentThreadId: No such thread %s.", qPrintable(thread->id())); return; } m_currentThread = thread; thread->update(); } QString ThreadsHandler::pidForGroupId(const QString &groupId) const { return m_pidForGroupId[groupId]; } void ThreadsHandler::notifyGroupCreated(const QString &groupId, const QString &pid) { m_pidForGroupId[groupId] = pid; } void ThreadsHandler::updateThread(const ThreadData &threadData) { if (Thread thread = threadForId(threadData.id)) thread->mergeThreadData(threadData); else rootItem()->appendChild(new ThreadItem(this, threadData)); } void ThreadsHandler::removeThread(const QString &id) { if (Thread thread = threadForId(id)) destroyItem(thread); } void ThreadsHandler::removeAll() { rootItem()->removeChildren(); } bool ThreadsHandler::notifyGroupExited(const QString &groupId) { QList list; forItemsAtLevel<1>([&list, groupId](ThreadItem *item) { if (item->threadData.groupId == groupId) list.append(item); }); foreach (ThreadItem *item, list) destroyItem(item); m_pidForGroupId.remove(groupId); return m_pidForGroupId.isEmpty(); } Thread ThreadsHandler::threadForId(const QString &id) const { return findItemAtLevel<1>([id](const Thread &item) { return item->threadData.id == id; }); } void ThreadsHandler::notifyRunning(const QString &id) { if (id.isEmpty() || id == "all") forItemsAtLevel<1>([](const Thread &thread) { thread->notifyRunning(); }); else if (Thread thread = threadForId(id)) thread->notifyRunning(); } void ThreadsHandler::notifyStopped(const QString &id) { if (id.isEmpty() || id == "all") forItemsAtLevel<1>([](const Thread &thread) { thread->notifyStopped(); }); else if (Thread thread = threadForId(id)) thread->notifyStopped(); } void ThreadsHandler::updateThreads(const GdbMi &data) { // ^done,threads=[{id="1",target-id="Thread 0xb7fdc710 (LWP 4264)", // frame={level="0",addr="0x080530bf",func="testQString",args=[], // file="/.../app.cpp",fullname="/../app.cpp",line="1175"}, // state="stopped",core="0"}],current-thread-id="1" const QVector &items = data["threads"].children(); for (const GdbMi &item : items) { const GdbMi &frame = item["frame"]; ThreadData thread; thread.id = item["id"].data(); thread.targetId = item["target-id"].data(); thread.details = item["details"].data(); thread.core = item["core"].data(); thread.state = item["state"].data(); thread.address = frame["addr"].toAddress(); thread.function = frame["func"].data(); thread.fileName = frame["fullname"].data(); thread.lineNumber = frame["line"].toInt(); thread.module = frame["from"].data(); thread.name = item["name"].data(); thread.stopped = thread.state != "running"; updateThread(thread); } const QString ¤tId = data["current-thread-id"].data(); m_currentThread = threadForId(currentId); } void ThreadsHandler::scheduleResetLocation() { m_resetLocationScheduled = true; } void ThreadsHandler::resetLocation() { if (m_resetLocationScheduled) { m_resetLocationScheduled = false; layoutChanged(); } } QAbstractItemModel *ThreadsHandler::model() { return this; } } // namespace Internal } // namespace Debugger