1 #include <QShortcut>
2 #include "ThreadsWidget.h"
3 #include "ui_ThreadsWidget.h"
4 #include "common/JsonModel.h"
5 #include "QuickFilterView.h"
6 #include <r_debug.h>
7
8 #include "core/MainWindow.h"
9
10 #define DEBUGGED_PID (-1)
11
12 enum ColumnIndex {
13 COLUMN_PID = 0,
14 COLUMN_STATUS,
15 COLUMN_PATH,
16 };
17
ThreadsWidget(MainWindow * main)18 ThreadsWidget::ThreadsWidget(MainWindow *main) :
19 CutterDockWidget(main),
20 ui(new Ui::ThreadsWidget)
21 {
22 ui->setupUi(this);
23
24 // Setup threads model
25 modelThreads = new QStandardItemModel(1, 3, this);
26 modelThreads->setHorizontalHeaderItem(COLUMN_PID, new QStandardItem(tr("PID")));
27 modelThreads->setHorizontalHeaderItem(COLUMN_STATUS, new QStandardItem(tr("Status")));
28 modelThreads->setHorizontalHeaderItem(COLUMN_PATH, new QStandardItem(tr("Path")));
29 ui->viewThreads->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
30 ui->viewThreads->verticalHeader()->setVisible(false);
31 ui->viewThreads->setFont(Config()->getFont());
32
33 modelFilter = new ThreadsFilterModel(this);
34 modelFilter->setSourceModel(modelThreads);
35 ui->viewThreads->setModel(modelFilter);
36
37 // CTRL+F switches to the filter view and opens it in case it's hidden
38 QShortcut *searchShortcut = new QShortcut(QKeySequence::Find, this);
39 connect(searchShortcut, &QShortcut::activated, ui->quickFilterView, &QuickFilterView::showFilter);
40 searchShortcut->setContext(Qt::WidgetWithChildrenShortcut);
41
42 // ESC switches back to the thread table and clears the buffer
43 QShortcut *clearShortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this);
44 connect(clearShortcut, &QShortcut::activated, this, [this]() {
45 ui->quickFilterView->clearFilter();
46 ui->viewThreads->setFocus();
47 });
48 clearShortcut->setContext(Qt::WidgetWithChildrenShortcut);
49
50 refreshDeferrer = createRefreshDeferrer([this]() {
51 updateContents();
52 });
53
54 connect(ui->quickFilterView, &QuickFilterView::filterTextChanged, modelFilter,
55 &ThreadsFilterModel::setFilterWildcard);
56 connect(Core(), &CutterCore::refreshAll, this, &ThreadsWidget::updateContents);
57 connect(Core(), &CutterCore::registersChanged, this, &ThreadsWidget::updateContents);
58 connect(Core(), &CutterCore::debugTaskStateChanged, this, &ThreadsWidget::updateContents);
59 // Seek doesn't necessarily change when switching threads/processes
60 connect(Core(), &CutterCore::switchedThread, this, &ThreadsWidget::updateContents);
61 connect(Core(), &CutterCore::switchedProcess, this, &ThreadsWidget::updateContents);
62 connect(Config(), &Configuration::fontsUpdated, this, &ThreadsWidget::fontsUpdatedSlot);
63 connect(ui->viewThreads, &QTableView::activated, this, &ThreadsWidget::onActivated);
64 }
65
~ThreadsWidget()66 ThreadsWidget::~ThreadsWidget() {}
67
updateContents()68 void ThreadsWidget::updateContents()
69 {
70 if (!refreshDeferrer->attemptRefresh(nullptr)) {
71 return;
72 }
73
74 if (!Core()->currentlyDebugging) {
75 // Remove rows from the previous debugging session
76 modelThreads->removeRows(0, modelThreads->rowCount());
77 return;
78 }
79
80 if (Core()->isDebugTaskInProgress()) {
81 ui->viewThreads->setDisabled(true);
82 } else {
83 setThreadsGrid();
84 ui->viewThreads->setDisabled(false);
85 }
86 }
87
translateStatus(QString status)88 QString ThreadsWidget::translateStatus(QString status)
89 {
90 switch (status.toStdString().c_str()[0]) {
91 case R_DBG_PROC_STOP:
92 return "Stopped";
93 case R_DBG_PROC_RUN:
94 return "Running";
95 case R_DBG_PROC_SLEEP:
96 return "Sleeping";
97 case R_DBG_PROC_ZOMBIE:
98 return "Zombie";
99 case R_DBG_PROC_DEAD:
100 return "Dead";
101 case R_DBG_PROC_RAISED:
102 return "Raised event";
103 default:
104 return "Unknown status";
105 }
106 }
107
setThreadsGrid()108 void ThreadsWidget::setThreadsGrid()
109 {
110 QJsonArray threadsValues = Core()->getProcessThreads(DEBUGGED_PID).array();
111 int i = 0;
112 QFont font;
113
114 for (const QJsonValue &value : threadsValues) {
115 QJsonObject threadsItem = value.toObject();
116 int pid = threadsItem["pid"].toVariant().toInt();
117 QString status = translateStatus(threadsItem["status"].toString());
118 QString path = threadsItem["path"].toString();
119 bool current = threadsItem["current"].toBool();
120 // Use bold font to highlight active thread
121 font.setBold(current);
122 QStandardItem *rowPid = new QStandardItem(QString::number(pid));
123 rowPid->setFont(font);
124 QStandardItem *rowStatus = new QStandardItem(status);
125 rowStatus->setFont(font);
126 QStandardItem *rowPath = new QStandardItem(path);
127 rowPath->setFont(font);
128 modelThreads->setItem(i, COLUMN_PID, rowPid);
129 modelThreads->setItem(i, COLUMN_STATUS, rowStatus);
130 modelThreads->setItem(i, COLUMN_PATH, rowPath);
131 i++;
132 }
133
134 // Remove irrelevant old rows
135 if (modelThreads->rowCount() > i) {
136 modelThreads->removeRows(i, modelThreads->rowCount() - i);
137 }
138
139 modelFilter->setSourceModel(modelThreads);
140 ui->viewThreads->resizeColumnsToContents();;
141 }
142
fontsUpdatedSlot()143 void ThreadsWidget::fontsUpdatedSlot()
144 {
145 ui->viewThreads->setFont(Config()->getFont());
146 }
147
onActivated(const QModelIndex & index)148 void ThreadsWidget::onActivated(const QModelIndex &index)
149 {
150 if (!index.isValid())
151 return;
152
153 int tid = modelFilter->data(index.sibling(index.row(), COLUMN_PID)).toInt();
154
155 // Verify that the selected tid is still in the threads list since dpt= will
156 // attach to any given id. If it isn't found simply update the UI.
157 QJsonArray threadsValues = Core()->getProcessThreads(DEBUGGED_PID).array();
158 for (QJsonValue value : threadsValues) {
159 if (tid == value.toObject()["pid"].toInt()) {
160 Core()->setCurrentDebugThread(tid);
161 break;
162 }
163 }
164
165 updateContents();
166 }
167
ThreadsFilterModel(QObject * parent)168 ThreadsFilterModel::ThreadsFilterModel(QObject *parent)
169 : QSortFilterProxyModel(parent)
170 {
171 setFilterCaseSensitivity(Qt::CaseInsensitive);
172 setSortCaseSensitivity(Qt::CaseInsensitive);
173 }
174
filterAcceptsRow(int row,const QModelIndex & parent) const175 bool ThreadsFilterModel::filterAcceptsRow(int row, const QModelIndex &parent) const
176 {
177 // All columns are checked for a match
178 for (int i = COLUMN_PID; i <= COLUMN_PATH; ++i) {
179 QModelIndex index = sourceModel()->index(row, i, parent);
180 if (sourceModel()->data(index).toString().contains(filterRegExp())) {
181 return true;
182 }
183 }
184
185 return false;
186 }
187