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