1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Copyright 2011-2017, Rene Gollent, rene@gollent.com.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include "ThreadListView.h"
9
10#include <new>
11
12#include <Looper.h>
13#include <Message.h>
14
15#include <AutoLocker.h>
16#include <ControlLook.h>
17#include <ObjectList.h>
18#include <ToolTip.h>
19
20#include "GuiSettingsUtils.h"
21#include "table/TableColumns.h"
22#include "UiUtils.h"
23
24
25enum {
26	MSG_SYNC_THREAD_LIST	= 'sytl'
27};
28
29
30// #pragma mark - ThreadsTableModel
31
32
33class ThreadListView::ThreadsTableModel : public TableModel,
34	public TableToolTipProvider {
35public:
36	ThreadsTableModel(Team* team)
37		:
38		fTeam(team)
39	{
40		Update(-1);
41	}
42
43	~ThreadsTableModel()
44	{
45		fTeam = NULL;
46		Update(-1);
47	}
48
49	bool Update(thread_id threadID)
50	{
51		if (fTeam == NULL) {
52			for (int32 i = 0; Thread* thread = fThreads.ItemAt(i); i++)
53				thread->ReleaseReference();
54			fThreads.MakeEmpty();
55
56			return true;
57		}
58
59		AutoLocker<Team> locker(fTeam);
60
61		ThreadList::ConstIterator it = fTeam->Threads().GetIterator();
62		Thread* newThread = it.Next();
63		int32 index = 0;
64
65		// remove no longer existing threads
66		while (Thread* oldThread = fThreads.ItemAt(index)) {
67			if (oldThread == newThread) {
68				if (threadID >= 0 && oldThread->ID() == threadID)
69					NotifyRowsChanged(index, 1);
70				index++;
71				newThread = it.Next();
72			} else {
73				// TODO: Not particularly efficient!
74				fThreads.RemoveItemAt(index);
75				oldThread->ReleaseReference();
76				NotifyRowsRemoved(index, 1);
77			}
78		}
79
80		// add new threads
81		int32 countBefore = fThreads.CountItems();
82		while (newThread != NULL) {
83			if (!fThreads.AddItem(newThread))
84				return false;
85
86			newThread->AcquireReference();
87			newThread = it.Next();
88		}
89
90		int32 count = fThreads.CountItems();
91		if (count > countBefore)
92			NotifyRowsAdded(countBefore, count - countBefore);
93
94		return true;
95	}
96
97	virtual int32 CountColumns() const
98	{
99		return 4;
100	}
101
102	virtual int32 CountRows() const
103	{
104		return fThreads.CountItems();
105	}
106
107	virtual bool GetValueAt(int32 rowIndex, int32 columnIndex, BVariant& value)
108	{
109		Thread* thread = fThreads.ItemAt(rowIndex);
110		if (thread == NULL)
111			return false;
112
113		switch (columnIndex) {
114			case 0:
115				value.SetTo(thread->ID());
116				return true;
117			case 1:
118			{
119				const char* string = UiUtils::ThreadStateToString(
120					thread->State(), thread->StoppedReason());
121				value.SetTo(string, B_VARIANT_DONT_COPY_DATA);
122				return true;
123			}
124			case 2:
125				value.SetTo(thread->Name(), B_VARIANT_DONT_COPY_DATA);
126				return true;
127			case 3:
128			{
129				if (thread->State() != THREAD_STATE_RUNNING) {
130					value.SetTo(thread->StoppedReasonInfo(),
131						B_VARIANT_DONT_COPY_DATA);
132					return true;
133				}
134				return false;
135			}
136			default:
137				return false;
138		}
139	}
140
141	virtual bool GetToolTipForTableCell(int32 rowIndex, int32 columnIndex,
142		BToolTip** _tip)
143	{
144		Thread* thread = fThreads.ItemAt(rowIndex);
145		if (thread == NULL)
146			return false;
147
148		BString text;
149		text << "Thread: \"" << thread->Name() << "\" (" << thread->ID()
150			<< ")\n";
151
152		switch (thread->State()) {
153			case THREAD_STATE_RUNNING:
154				text << "Running";
155				break;
156			case THREAD_STATE_STOPPED:
157			{
158				switch (thread->StoppedReason()) {
159					case THREAD_STOPPED_DEBUGGER_CALL:
160						text << "Called debugger(): "
161							<< thread->StoppedReasonInfo();
162						break;
163					case THREAD_STOPPED_EXCEPTION:
164						text << "Caused exception: "
165							<< thread->StoppedReasonInfo();
166						break;
167					case THREAD_STOPPED_BREAKPOINT:
168					case THREAD_STOPPED_WATCHPOINT:
169					case THREAD_STOPPED_SINGLE_STEP:
170					case THREAD_STOPPED_DEBUGGED:
171					case THREAD_STOPPED_UNKNOWN:
172					default:
173						text << "Stopped for debugging";
174						break;
175				}
176				break;
177			}
178			case THREAD_STATE_UNKNOWN:
179			default:
180				text << "Current State Unknown";
181				break;
182		}
183
184		BTextToolTip* tip = new(std::nothrow) BTextToolTip(text);
185		if (tip == NULL)
186			return false;
187
188		*_tip = tip;
189		return true;
190	}
191
192	Thread* ThreadAt(int32 index) const
193	{
194		return fThreads.ItemAt(index);
195	}
196
197private:
198	Team*				fTeam;
199	BObjectList<Thread>	fThreads;
200};
201
202
203// #pragma mark - ThreadListView
204
205
206ThreadListView::ThreadListView(Team* team, Listener* listener)
207	:
208	BGroupView(B_VERTICAL),
209	fTeam(team),
210	fThread(NULL),
211	fThreadsTable(NULL),
212	fThreadsTableModel(NULL),
213	fListener(listener)
214{
215	SetName("Threads");
216}
217
218
219ThreadListView::~ThreadListView()
220{
221	fTeam->RemoveListener(this);
222	fThreadsTable->SetTableModel(NULL);
223	delete fThreadsTableModel;
224}
225
226
227/*static*/ ThreadListView*
228ThreadListView::Create(Team* team, Listener* listener)
229{
230	ThreadListView* self = new ThreadListView(team, listener);
231
232	try {
233		self->_Init();
234	} catch (...) {
235		delete self;
236		throw;
237	}
238
239	return self;
240}
241
242
243void
244ThreadListView::UnsetListener()
245{
246	fListener = NULL;
247}
248
249
250void
251ThreadListView::SetThread(Thread* thread)
252{
253	if (thread == fThread)
254		return;
255
256	if (fThread != NULL)
257		fThread->ReleaseReference();
258
259	fThread = thread;
260
261	if (fThread != NULL) {
262		fThread->AcquireReference();
263
264		for (int32 i = 0; Thread* other = fThreadsTableModel->ThreadAt(i);
265				i++) {
266			if (fThread == other) {
267				fThreadsTable->SelectRow(i, false);
268				return;
269			}
270		}
271	}
272
273	fThreadsTable->DeselectAllRows();
274}
275
276
277void
278ThreadListView::MessageReceived(BMessage* message)
279{
280	switch (message->what) {
281		case MSG_SYNC_THREAD_LIST:
282		{
283			thread_id threadID;
284			if (message->FindInt32("thread", &threadID) != B_OK)
285				threadID = -1;
286
287			fThreadsTableModel->Update(threadID);
288			break;
289		}
290		default:
291			BGroupView::MessageReceived(message);
292			break;
293	}
294}
295
296
297void
298ThreadListView::LoadSettings(const BMessage& settings)
299{
300	BMessage tableSettings;
301	if (settings.FindMessage("threadsTable", &tableSettings) == B_OK) {
302		GuiSettingsUtils::UnarchiveTableSettings(tableSettings,
303			fThreadsTable);
304	}
305}
306
307
308status_t
309ThreadListView::SaveSettings(BMessage& settings)
310{
311	settings.MakeEmpty();
312
313	BMessage tableSettings;
314	status_t result = GuiSettingsUtils::ArchiveTableSettings(tableSettings,
315		fThreadsTable);
316	if (result == B_OK)
317		result = settings.AddMessage("threadsTable", &tableSettings);
318
319	return result;
320}
321
322
323
324
325void
326ThreadListView::ThreadAdded(const Team::ThreadEvent& event)
327{
328	Looper()->PostMessage(MSG_SYNC_THREAD_LIST, this);
329}
330
331
332void
333ThreadListView::ThreadRemoved(const Team::ThreadEvent& event)
334{
335	Looper()->PostMessage(MSG_SYNC_THREAD_LIST, this);
336}
337
338
339void
340ThreadListView::ThreadStateChanged(const Team::ThreadEvent& event)
341{
342	BMessage message(MSG_SYNC_THREAD_LIST);
343	message.AddInt32("thread", event.GetThread()->ID());
344
345	Looper()->PostMessage(&message, this);
346}
347
348
349void
350ThreadListView::TableSelectionChanged(Table* table)
351{
352	if (fListener == NULL)
353		return;
354
355	Thread* thread = NULL;
356	TableSelectionModel* selectionModel = table->SelectionModel();
357	thread = fThreadsTableModel->ThreadAt(selectionModel->RowAt(0));
358
359	fListener->ThreadSelectionChanged(thread);
360}
361
362
363void
364ThreadListView::_Init()
365{
366	fThreadsTable = new Table("threads list", 0, B_FANCY_BORDER);
367	AddChild(fThreadsTable->ToView());
368
369	// columns
370	const float padding = be_control_look->DefaultLabelSpacing() * 2;
371	fThreadsTable->AddColumn(new Int32TableColumn(0, "ID",
372		be_plain_font->StringWidth("12345") + padding, 20, 1000,
373		B_TRUNCATE_MIDDLE, B_ALIGN_RIGHT));
374	fThreadsTable->AddColumn(new StringTableColumn(1, "State",
375		be_plain_font->StringWidth("Debugged") + padding, 40, 1000,
376		B_TRUNCATE_END, B_ALIGN_LEFT));
377	fThreadsTable->AddColumn(new StringTableColumn(2, "Name", 200, 40, 1000,
378		B_TRUNCATE_END, B_ALIGN_LEFT));
379	fThreadsTable->AddColumn(new StringTableColumn(3, "Stop reason",
380		200, 40, 1000, B_TRUNCATE_END, B_ALIGN_LEFT));
381
382	fThreadsTable->SetSelectionMode(B_SINGLE_SELECTION_LIST);
383	fThreadsTable->AddTableListener(this);
384
385	fThreadsTableModel = new ThreadsTableModel(fTeam);
386	fThreadsTable->SetTableModel(fThreadsTableModel);
387	fThreadsTable->SetToolTipProvider(fThreadsTableModel);
388	fTeam->AddListener(this);
389}
390
391
392// #pragma mark - Listener
393
394
395ThreadListView::Listener::~Listener()
396{
397}
398