1/*
2 * Copyright 2014-2016, Rene Gollent, rene@gollent.com.
3 * Distributed under the terms of the MIT License.
4 */
5#include "ExpressionEvaluationWindow.h"
6
7#include <Button.h>
8#include <LayoutBuilder.h>
9#include <MenuField.h>
10#include <String.h>
11#include <TextControl.h>
12
13#include <AutoDeleter.h>
14#include <AutoLocker.h>
15
16#include "AppMessageCodes.h"
17#include "CppLanguage.h"
18#include "FunctionDebugInfo.h"
19#include "FunctionInstance.h"
20#include "MessageCodes.h"
21#include "SourceLanguage.h"
22#include "SpecificImageDebugInfo.h"
23#include "StackFrame.h"
24#include "StackTrace.h"
25#include "Thread.h"
26#include "UiUtils.h"
27#include "UserInterface.h"
28#include "ValueNodeManager.h"
29
30
31enum {
32	MSG_THREAD_ADDED				= 'thad',
33	MSG_THREAD_REMOVED				= 'thar',
34
35	MSG_THREAD_SELECTION_CHANGED	= 'thsc',
36	MSG_FRAME_SELECTION_CHANGED		= 'frsc'
37};
38
39
40ExpressionEvaluationWindow::ExpressionEvaluationWindow(BHandler* closeTarget,
41		::Team* team, UserInterfaceListener* listener)
42	:
43	BWindow(BRect(), "Evaluate Expression", B_TITLED_WINDOW,
44		B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE),
45	fExpressionInput(NULL),
46	fThreadList(NULL),
47	fFrameList(NULL),
48	fVariablesView(NULL),
49	fCloseButton(NULL),
50	fEvaluateButton(NULL),
51	fCloseTarget(closeTarget),
52	fCurrentLanguage(NULL),
53	fFallbackLanguage(NULL),
54	fTeam(team),
55	fSelectedThread(NULL),
56	fSelectedFrame(NULL),
57	fListener(listener)
58{
59	team->AddListener(this);
60}
61
62
63ExpressionEvaluationWindow::~ExpressionEvaluationWindow()
64{
65	fTeam->RemoveListener(this);
66
67	if (fCurrentLanguage != NULL)
68		fCurrentLanguage->ReleaseReference();
69
70	if (fFallbackLanguage != NULL)
71		fFallbackLanguage->ReleaseReference();
72
73	if (fSelectedThread != NULL)
74		fSelectedThread->ReleaseReference();
75
76	if (fSelectedFrame != NULL)
77		fSelectedFrame->ReleaseReference();
78}
79
80
81ExpressionEvaluationWindow*
82ExpressionEvaluationWindow::Create(BHandler* closeTarget, ::Team* team,
83	UserInterfaceListener* listener)
84{
85	ExpressionEvaluationWindow* self = new ExpressionEvaluationWindow(
86		closeTarget, team, listener);
87
88	try {
89		self->_Init();
90	} catch (...) {
91		delete self;
92		throw;
93	}
94
95	return self;
96
97}
98
99
100void
101ExpressionEvaluationWindow::Show()
102{
103	CenterOnScreen();
104	BWindow::Show();
105}
106
107
108bool
109ExpressionEvaluationWindow::QuitRequested()
110{
111	BMessenger messenger(fCloseTarget);
112	messenger.SendMessage(MSG_EXPRESSION_WINDOW_CLOSED);
113
114	return BWindow::QuitRequested();
115}
116
117
118void
119ExpressionEvaluationWindow::MessageReceived(BMessage* message)
120{
121	switch (message->what) {
122		case MSG_THREAD_SELECTION_CHANGED:
123		{
124			int32 threadID;
125			if (message->FindInt32("thread", &threadID) != B_OK)
126				threadID = -1;
127
128			_HandleThreadSelectionChanged(threadID);
129			break;
130		}
131
132		case MSG_FRAME_SELECTION_CHANGED:
133		{
134			if (fSelectedThread == NULL)
135				break;
136
137			int32 frameIndex;
138			if (message->FindInt32("frame", &frameIndex) != B_OK)
139				frameIndex = -1;
140
141			_HandleFrameSelectionChanged(frameIndex);
142			break;
143		}
144
145		case MSG_EVALUATE_EXPRESSION:
146		{
147			BMessage message(MSG_ADD_NEW_EXPRESSION);
148			message.AddString("expression", fExpressionInput->Text());
149			BMessenger(fVariablesView).SendMessage(&message);
150			break;
151		}
152
153		case MSG_THREAD_ADDED:
154		{
155			int32 threadID;
156			if (message->FindInt32("thread", &threadID) == B_OK)
157				_HandleThreadAdded(threadID);
158			break;
159		}
160
161		case MSG_THREAD_REMOVED:
162		{
163			int32 threadID;
164			if (message->FindInt32("thread", &threadID) == B_OK)
165				_HandleThreadRemoved(threadID);
166			break;
167		}
168
169		case MSG_THREAD_STATE_CHANGED:
170		{
171			int32 threadID;
172			if (message->FindInt32("thread", &threadID) == B_OK)
173				_HandleThreadStateChanged(threadID);
174			break;
175		}
176
177		case MSG_THREAD_STACK_TRACE_CHANGED:
178		{
179			int32 threadID;
180			if (message->FindInt32("thread", &threadID) == B_OK)
181				_HandleThreadStackTraceChanged(threadID);
182			break;
183		}
184
185		default:
186			BWindow::MessageReceived(message);
187			break;
188	}
189
190}
191
192
193void
194ExpressionEvaluationWindow::ThreadAdded(const Team::ThreadEvent& event)
195{
196	BMessage message(MSG_THREAD_ADDED);
197	message.AddInt32("thread", event.GetThread()->ID());
198	PostMessage(&message);
199}
200
201
202void
203ExpressionEvaluationWindow::ThreadRemoved(const Team::ThreadEvent& event)
204{
205	BMessage message(MSG_THREAD_REMOVED);
206	message.AddInt32("thread", event.GetThread()->ID());
207	PostMessage(&message);
208}
209
210
211void
212ExpressionEvaluationWindow::ThreadStateChanged(const Team::ThreadEvent& event)
213{
214	BMessage message(MSG_THREAD_STATE_CHANGED);
215	message.AddInt32("thread", event.GetThread()->ID());
216	PostMessage(&message);
217}
218
219
220void
221ExpressionEvaluationWindow::ThreadStackTraceChanged(
222	const Team::ThreadEvent& event)
223{
224	BMessage message(MSG_THREAD_STACK_TRACE_CHANGED);
225	message.AddInt32("thread", event.GetThread()->ID());
226	PostMessage(&message);
227}
228
229
230void
231ExpressionEvaluationWindow::ValueNodeValueRequested(CpuState* cpuState,
232	ValueNodeContainer* container, ValueNode* valueNode)
233{
234	fListener->ValueNodeValueRequested(cpuState, container, valueNode);
235}
236
237
238void
239ExpressionEvaluationWindow::ExpressionEvaluationRequested(ExpressionInfo* info,
240	StackFrame* frame, ::Thread* thread)
241{
242	SourceLanguage* language = fCurrentLanguage;
243	if (fCurrentLanguage == NULL)
244		language = fFallbackLanguage;
245	fListener->ExpressionEvaluationRequested(language, info, frame, thread);
246}
247
248
249void
250ExpressionEvaluationWindow::ValueNodeWriteRequested(ValueNode* node,
251	CpuState* state, Value* newValue)
252{
253}
254
255
256void
257ExpressionEvaluationWindow::_Init()
258{
259	ValueNodeManager* nodeManager = new ValueNodeManager(false);
260	fExpressionInput = new BTextControl("Expression:", NULL, NULL);
261	BLayoutItem* labelItem = fExpressionInput->CreateLabelLayoutItem();
262	BLayoutItem* inputItem = fExpressionInput->CreateTextViewLayoutItem();
263	inputItem->SetExplicitMinSize(BSize(200.0, B_SIZE_UNSET));
264	inputItem->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
265	labelItem->View()->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
266
267	BLayoutBuilder::Group<>(this, B_VERTICAL)
268		.SetInsets(B_USE_DEFAULT_SPACING)
269		.AddGroup(B_HORIZONTAL, 4.0f)
270			.Add((fThreadList = new BMenuField("threadList", "Thread:",
271					new BMenu("Thread"))))
272			.Add((fFrameList = new BMenuField("frameList", "Frame:",
273					new BMenu("Frame"))))
274		.End()
275		.AddGroup(B_HORIZONTAL, 4.0f)
276			.Add(labelItem)
277			.Add(inputItem)
278		.End()
279		.Add(fVariablesView = VariablesView::Create(this, nodeManager))
280		.AddGroup(B_HORIZONTAL, 4.0f)
281			.AddGlue()
282			.Add((fCloseButton = new BButton("Close",
283					new BMessage(B_QUIT_REQUESTED))))
284			.Add((fEvaluateButton = new BButton("Evaluate",
285					new BMessage(MSG_EVALUATE_EXPRESSION))))
286		.End();
287
288	fCloseButton->SetTarget(this);
289	fEvaluateButton->SetTarget(this);
290	fExpressionInput->TextView()->MakeFocus(true);
291
292	fThreadList->Menu()->SetLabelFromMarked(true);
293	fFrameList->Menu()->SetLabelFromMarked(true);
294
295	fThreadList->Menu()->AddItem(new BMenuItem("<None>",
296			new BMessage(MSG_THREAD_SELECTION_CHANGED)));
297	fFrameList->Menu()->AddItem(new BMenuItem("<None>",
298			new BMessage(MSG_FRAME_SELECTION_CHANGED)));
299
300	_UpdateThreadList();
301
302	fFallbackLanguage = new CppLanguage();
303}
304
305
306void
307ExpressionEvaluationWindow::_HandleThreadSelectionChanged(int32 threadID)
308{
309	if (fSelectedThread != NULL) {
310		fSelectedThread->ReleaseReference();
311		fSelectedThread = NULL;
312	}
313
314	AutoLocker< ::Team> teamLocker(fTeam);
315	fSelectedThread = fTeam->ThreadByID(threadID);
316	if (fSelectedThread != NULL)
317		fSelectedThread->AcquireReference();
318	else if (fThreadList->Menu()->FindMarked() == NULL) {
319		// if the selected thread was cleared due to a thread event
320		// rather than user selection, we need to reset the marked item
321		// to reflect the new state.
322		fThreadList->Menu()->ItemAt(0)->SetMarked(true);
323	}
324
325	_UpdateFrameList();
326
327	fVariablesView->SetStackFrame(fSelectedThread, fSelectedFrame);
328}
329
330
331void
332ExpressionEvaluationWindow::_HandleFrameSelectionChanged(int32 index)
333{
334	if (fSelectedFrame != NULL) {
335		fSelectedFrame->ReleaseReference();
336		fSelectedFrame = NULL;
337	}
338
339	if (fCurrentLanguage != NULL) {
340		fCurrentLanguage->ReleaseReference();
341		fCurrentLanguage = NULL;
342	}
343
344	AutoLocker< ::Team> teamLocker(fTeam);
345	StackTrace* stackTrace = fSelectedThread->GetStackTrace();
346	if (stackTrace != NULL) {
347		fSelectedFrame = stackTrace->FrameAt(index);
348		if (fSelectedFrame != NULL) {
349			fSelectedFrame->AcquireReference();
350
351			FunctionInstance* instance = fSelectedFrame->Function();
352			if (instance != NULL) {
353				FunctionDebugInfo* functionInfo
354					= instance->GetFunctionDebugInfo();
355				SpecificImageDebugInfo* imageInfo =
356					functionInfo->GetSpecificImageDebugInfo();
357
358				if (imageInfo->GetSourceLanguage(functionInfo,
359						fCurrentLanguage) == B_OK) {
360					fCurrentLanguage->AcquireReference();
361				}
362			}
363		}
364	}
365
366	fVariablesView->SetStackFrame(fSelectedThread, fSelectedFrame);
367}
368
369
370void
371ExpressionEvaluationWindow::_HandleThreadAdded(int32 threadID)
372{
373	AutoLocker< ::Team> teamLocker(fTeam);
374	::Thread* thread = fTeam->ThreadByID(threadID);
375	if (thread == NULL)
376		return;
377
378	if (thread->State() != THREAD_STATE_STOPPED)
379		return;
380
381	BMenuItem* item = NULL;
382	if (_CreateThreadMenuItem(thread, item) != B_OK)
383		return;
384
385	BMenu* threadMenu = fThreadList->Menu();
386	int32 index = 1;
387	// find appropriate insertion index to keep menu sorted in thread order.
388	for (; index < threadMenu->CountItems(); index++) {
389		BMenuItem* threadItem = threadMenu->ItemAt(index);
390		BMessage* message = threadItem->Message();
391		if (message->FindInt32("thread") > threadID)
392			break;
393	}
394
395	bool added = false;
396	if (index == threadMenu->CountItems())
397		added = threadMenu->AddItem(item);
398	else
399		added = threadMenu->AddItem(item, index);
400
401	if (!added)
402		delete item;
403}
404
405
406void
407ExpressionEvaluationWindow::_HandleThreadRemoved(int32 threadID)
408{
409	BMenu* threadMenu = fThreadList->Menu();
410	for (int32 i = 0; i < threadMenu->CountItems(); i++) {
411		BMenuItem* item = threadMenu->ItemAt(i);
412		BMessage* message = item->Message();
413		if (message->FindInt32("thread") == threadID) {
414			threadMenu->RemoveItem(i);
415			delete item;
416			break;
417		}
418	}
419
420	if (fSelectedThread != NULL && threadID == fSelectedThread->ID())
421		_HandleThreadSelectionChanged(-1);
422}
423
424
425void
426ExpressionEvaluationWindow::_HandleThreadStateChanged(int32 threadID)
427{
428	AutoLocker< ::Team> teamLocker(fTeam);
429
430	::Thread* thread = fTeam->ThreadByID(threadID);
431	if (thread == NULL)
432		return;
433
434	if (thread->State() == THREAD_STATE_STOPPED)
435		_HandleThreadAdded(threadID);
436	else
437		_HandleThreadRemoved(threadID);
438}
439
440
441void
442ExpressionEvaluationWindow::_HandleThreadStackTraceChanged(int32 threadID)
443{
444	AutoLocker< ::Team> teamLocker(fTeam);
445
446	::Thread* thread = fTeam->ThreadByID(threadID);
447	if (thread == NULL)
448		return;
449
450	if (thread != fSelectedThread)
451		return;
452
453	_UpdateFrameList();
454}
455
456
457void
458ExpressionEvaluationWindow::_UpdateThreadList()
459{
460	AutoLocker< ::Team> teamLocker(fTeam);
461
462	BMenu* frameMenu = fFrameList->Menu();
463	while (frameMenu->CountItems() > 1)
464		delete frameMenu->RemoveItem(1);
465
466	BMenu* threadMenu = fThreadList->Menu();
467	while (threadMenu->CountItems() > 1)
468		delete threadMenu->RemoveItem(1);
469
470	const ThreadList& threads = fTeam->Threads();
471	for (ThreadList::ConstIterator it = threads.GetIterator();
472		::Thread* thread = it.Next();) {
473		if (thread->State() != THREAD_STATE_STOPPED)
474			continue;
475
476		BMenuItem* item = NULL;
477		if (_CreateThreadMenuItem(thread, item) != B_OK)
478			return;
479
480		ObjectDeleter<BMenuItem> itemDeleter(item);
481		if (!threadMenu->AddItem(item))
482			return;
483
484		itemDeleter.Detach();
485		if (fSelectedThread == NULL) {
486			item->SetMarked(true);
487			_HandleThreadSelectionChanged(thread->ID());
488		}
489	}
490
491	if (fSelectedThread == NULL)
492		frameMenu->ItemAt(0L)->SetMarked(true);
493
494}
495
496
497void
498ExpressionEvaluationWindow::_UpdateFrameList()
499{
500	AutoLocker< ::Team> teamLocker(fTeam);
501
502	BMenu* frameMenu = fFrameList->Menu();
503	while (frameMenu->CountItems() > 1)
504		delete frameMenu->RemoveItem(1);
505
506	frameMenu->ItemAt(0L)->SetMarked(true);
507
508	if (fSelectedThread == NULL)
509		return;
510
511	StackTrace* stackTrace = fSelectedThread->GetStackTrace();
512	if (stackTrace == NULL)
513		return;
514
515 	char buffer[128];
516	for (int32 i = 0; i < stackTrace->CountFrames(); i++) {
517		StackFrame* frame = stackTrace->FrameAt(i);
518		UiUtils::FunctionNameForFrame(frame, buffer, sizeof(buffer));
519
520		BMessage* message = new(std::nothrow) BMessage(
521			MSG_FRAME_SELECTION_CHANGED);
522		if (message == NULL)
523			return;
524
525		message->AddInt32("frame", i);
526
527		BMenuItem* item = new(std::nothrow) BMenuItem(buffer,
528			message);
529		if (item == NULL)
530			return;
531
532		if (!frameMenu->AddItem(item))
533			return;
534
535		if (fSelectedFrame == NULL) {
536			item->SetMarked(true);
537			_HandleFrameSelectionChanged(i);
538		}
539	}
540}
541
542
543status_t
544ExpressionEvaluationWindow::_CreateThreadMenuItem(::Thread* thread,
545	BMenuItem*& _item) const
546{
547	BString nameString;
548	nameString.SetToFormat("%" B_PRId32 ": %s", thread->ID(),
549		thread->Name());
550
551	BMessage* message = new(std::nothrow) BMessage(
552		MSG_THREAD_SELECTION_CHANGED);
553	if (message == NULL)
554		return B_NO_MEMORY;
555
556	ObjectDeleter<BMessage> messageDeleter(message);
557	message->AddInt32("thread", thread->ID());
558	_item = new(std::nothrow) BMenuItem(nameString,
559		message);
560	if (_item == NULL)
561		return B_NO_MEMORY;
562
563	messageDeleter.Detach();
564	return B_OK;
565}
566