/* * Copyright 2014-2016, Rene Gollent, rene@gollent.com. * Distributed under the terms of the MIT License. */ #include "ExpressionEvaluationWindow.h" #include #include #include #include #include #include #include #include "AppMessageCodes.h" #include "CppLanguage.h" #include "FunctionDebugInfo.h" #include "FunctionInstance.h" #include "MessageCodes.h" #include "SourceLanguage.h" #include "SpecificImageDebugInfo.h" #include "StackFrame.h" #include "StackTrace.h" #include "Thread.h" #include "UiUtils.h" #include "UserInterface.h" #include "ValueNodeManager.h" enum { MSG_THREAD_ADDED = 'thad', MSG_THREAD_REMOVED = 'thar', MSG_THREAD_SELECTION_CHANGED = 'thsc', MSG_FRAME_SELECTION_CHANGED = 'frsc' }; ExpressionEvaluationWindow::ExpressionEvaluationWindow(BHandler* closeTarget, ::Team* team, UserInterfaceListener* listener) : BWindow(BRect(), "Evaluate Expression", B_TITLED_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE), fExpressionInput(NULL), fThreadList(NULL), fFrameList(NULL), fVariablesView(NULL), fCloseButton(NULL), fEvaluateButton(NULL), fCloseTarget(closeTarget), fCurrentLanguage(NULL), fFallbackLanguage(NULL), fTeam(team), fSelectedThread(NULL), fSelectedFrame(NULL), fListener(listener) { team->AddListener(this); } ExpressionEvaluationWindow::~ExpressionEvaluationWindow() { fTeam->RemoveListener(this); if (fCurrentLanguage != NULL) fCurrentLanguage->ReleaseReference(); if (fFallbackLanguage != NULL) fFallbackLanguage->ReleaseReference(); if (fSelectedThread != NULL) fSelectedThread->ReleaseReference(); if (fSelectedFrame != NULL) fSelectedFrame->ReleaseReference(); } ExpressionEvaluationWindow* ExpressionEvaluationWindow::Create(BHandler* closeTarget, ::Team* team, UserInterfaceListener* listener) { ExpressionEvaluationWindow* self = new ExpressionEvaluationWindow( closeTarget, team, listener); try { self->_Init(); } catch (...) { delete self; throw; } return self; } void ExpressionEvaluationWindow::Show() { CenterOnScreen(); BWindow::Show(); } bool ExpressionEvaluationWindow::QuitRequested() { BMessenger messenger(fCloseTarget); messenger.SendMessage(MSG_EXPRESSION_WINDOW_CLOSED); return BWindow::QuitRequested(); } void ExpressionEvaluationWindow::MessageReceived(BMessage* message) { switch (message->what) { case MSG_THREAD_SELECTION_CHANGED: { int32 threadID; if (message->FindInt32("thread", &threadID) != B_OK) threadID = -1; _HandleThreadSelectionChanged(threadID); break; } case MSG_FRAME_SELECTION_CHANGED: { if (fSelectedThread == NULL) break; int32 frameIndex; if (message->FindInt32("frame", &frameIndex) != B_OK) frameIndex = -1; _HandleFrameSelectionChanged(frameIndex); break; } case MSG_EVALUATE_EXPRESSION: { BMessage message(MSG_ADD_NEW_EXPRESSION); message.AddString("expression", fExpressionInput->Text()); BMessenger(fVariablesView).SendMessage(&message); break; } case MSG_THREAD_ADDED: { int32 threadID; if (message->FindInt32("thread", &threadID) == B_OK) _HandleThreadAdded(threadID); break; } case MSG_THREAD_REMOVED: { int32 threadID; if (message->FindInt32("thread", &threadID) == B_OK) _HandleThreadRemoved(threadID); break; } case MSG_THREAD_STATE_CHANGED: { int32 threadID; if (message->FindInt32("thread", &threadID) == B_OK) _HandleThreadStateChanged(threadID); break; } case MSG_THREAD_STACK_TRACE_CHANGED: { int32 threadID; if (message->FindInt32("thread", &threadID) == B_OK) _HandleThreadStackTraceChanged(threadID); break; } default: BWindow::MessageReceived(message); break; } } void ExpressionEvaluationWindow::ThreadAdded(const Team::ThreadEvent& event) { BMessage message(MSG_THREAD_ADDED); message.AddInt32("thread", event.GetThread()->ID()); PostMessage(&message); } void ExpressionEvaluationWindow::ThreadRemoved(const Team::ThreadEvent& event) { BMessage message(MSG_THREAD_REMOVED); message.AddInt32("thread", event.GetThread()->ID()); PostMessage(&message); } void ExpressionEvaluationWindow::ThreadStateChanged(const Team::ThreadEvent& event) { BMessage message(MSG_THREAD_STATE_CHANGED); message.AddInt32("thread", event.GetThread()->ID()); PostMessage(&message); } void ExpressionEvaluationWindow::ThreadStackTraceChanged( const Team::ThreadEvent& event) { BMessage message(MSG_THREAD_STACK_TRACE_CHANGED); message.AddInt32("thread", event.GetThread()->ID()); PostMessage(&message); } void ExpressionEvaluationWindow::ValueNodeValueRequested(CpuState* cpuState, ValueNodeContainer* container, ValueNode* valueNode) { fListener->ValueNodeValueRequested(cpuState, container, valueNode); } void ExpressionEvaluationWindow::ExpressionEvaluationRequested(ExpressionInfo* info, StackFrame* frame, ::Thread* thread) { SourceLanguage* language = fCurrentLanguage; if (fCurrentLanguage == NULL) language = fFallbackLanguage; fListener->ExpressionEvaluationRequested(language, info, frame, thread); } void ExpressionEvaluationWindow::ValueNodeWriteRequested(ValueNode* node, CpuState* state, Value* newValue) { } void ExpressionEvaluationWindow::_Init() { ValueNodeManager* nodeManager = new ValueNodeManager(false); fExpressionInput = new BTextControl("Expression:", NULL, NULL); BLayoutItem* labelItem = fExpressionInput->CreateLabelLayoutItem(); BLayoutItem* inputItem = fExpressionInput->CreateTextViewLayoutItem(); inputItem->SetExplicitMinSize(BSize(200.0, B_SIZE_UNSET)); inputItem->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); labelItem->View()->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); BLayoutBuilder::Group<>(this, B_VERTICAL) .SetInsets(B_USE_DEFAULT_SPACING) .AddGroup(B_HORIZONTAL, 4.0f) .Add((fThreadList = new BMenuField("threadList", "Thread:", new BMenu("Thread")))) .Add((fFrameList = new BMenuField("frameList", "Frame:", new BMenu("Frame")))) .End() .AddGroup(B_HORIZONTAL, 4.0f) .Add(labelItem) .Add(inputItem) .End() .Add(fVariablesView = VariablesView::Create(this, nodeManager)) .AddGroup(B_HORIZONTAL, 4.0f) .AddGlue() .Add((fCloseButton = new BButton("Close", new BMessage(B_QUIT_REQUESTED)))) .Add((fEvaluateButton = new BButton("Evaluate", new BMessage(MSG_EVALUATE_EXPRESSION)))) .End(); fCloseButton->SetTarget(this); fEvaluateButton->SetTarget(this); fExpressionInput->TextView()->MakeFocus(true); fThreadList->Menu()->SetLabelFromMarked(true); fFrameList->Menu()->SetLabelFromMarked(true); fThreadList->Menu()->AddItem(new BMenuItem("", new BMessage(MSG_THREAD_SELECTION_CHANGED))); fFrameList->Menu()->AddItem(new BMenuItem("", new BMessage(MSG_FRAME_SELECTION_CHANGED))); _UpdateThreadList(); fFallbackLanguage = new CppLanguage(); } void ExpressionEvaluationWindow::_HandleThreadSelectionChanged(int32 threadID) { if (fSelectedThread != NULL) { fSelectedThread->ReleaseReference(); fSelectedThread = NULL; } AutoLocker< ::Team> teamLocker(fTeam); fSelectedThread = fTeam->ThreadByID(threadID); if (fSelectedThread != NULL) fSelectedThread->AcquireReference(); else if (fThreadList->Menu()->FindMarked() == NULL) { // if the selected thread was cleared due to a thread event // rather than user selection, we need to reset the marked item // to reflect the new state. fThreadList->Menu()->ItemAt(0)->SetMarked(true); } _UpdateFrameList(); fVariablesView->SetStackFrame(fSelectedThread, fSelectedFrame); } void ExpressionEvaluationWindow::_HandleFrameSelectionChanged(int32 index) { if (fSelectedFrame != NULL) { fSelectedFrame->ReleaseReference(); fSelectedFrame = NULL; } if (fCurrentLanguage != NULL) { fCurrentLanguage->ReleaseReference(); fCurrentLanguage = NULL; } AutoLocker< ::Team> teamLocker(fTeam); StackTrace* stackTrace = fSelectedThread->GetStackTrace(); if (stackTrace != NULL) { fSelectedFrame = stackTrace->FrameAt(index); if (fSelectedFrame != NULL) { fSelectedFrame->AcquireReference(); FunctionInstance* instance = fSelectedFrame->Function(); if (instance != NULL) { FunctionDebugInfo* functionInfo = instance->GetFunctionDebugInfo(); SpecificImageDebugInfo* imageInfo = functionInfo->GetSpecificImageDebugInfo(); if (imageInfo->GetSourceLanguage(functionInfo, fCurrentLanguage) == B_OK) { fCurrentLanguage->AcquireReference(); } } } } fVariablesView->SetStackFrame(fSelectedThread, fSelectedFrame); } void ExpressionEvaluationWindow::_HandleThreadAdded(int32 threadID) { AutoLocker< ::Team> teamLocker(fTeam); ::Thread* thread = fTeam->ThreadByID(threadID); if (thread == NULL) return; if (thread->State() != THREAD_STATE_STOPPED) return; BMenuItem* item = NULL; if (_CreateThreadMenuItem(thread, item) != B_OK) return; BMenu* threadMenu = fThreadList->Menu(); int32 index = 1; // find appropriate insertion index to keep menu sorted in thread order. for (; index < threadMenu->CountItems(); index++) { BMenuItem* threadItem = threadMenu->ItemAt(index); BMessage* message = threadItem->Message(); if (message->FindInt32("thread") > threadID) break; } bool added = false; if (index == threadMenu->CountItems()) added = threadMenu->AddItem(item); else added = threadMenu->AddItem(item, index); if (!added) delete item; } void ExpressionEvaluationWindow::_HandleThreadRemoved(int32 threadID) { BMenu* threadMenu = fThreadList->Menu(); for (int32 i = 0; i < threadMenu->CountItems(); i++) { BMenuItem* item = threadMenu->ItemAt(i); BMessage* message = item->Message(); if (message->FindInt32("thread") == threadID) { threadMenu->RemoveItem(i); delete item; break; } } if (fSelectedThread != NULL && threadID == fSelectedThread->ID()) _HandleThreadSelectionChanged(-1); } void ExpressionEvaluationWindow::_HandleThreadStateChanged(int32 threadID) { AutoLocker< ::Team> teamLocker(fTeam); ::Thread* thread = fTeam->ThreadByID(threadID); if (thread == NULL) return; if (thread->State() == THREAD_STATE_STOPPED) _HandleThreadAdded(threadID); else _HandleThreadRemoved(threadID); } void ExpressionEvaluationWindow::_HandleThreadStackTraceChanged(int32 threadID) { AutoLocker< ::Team> teamLocker(fTeam); ::Thread* thread = fTeam->ThreadByID(threadID); if (thread == NULL) return; if (thread != fSelectedThread) return; _UpdateFrameList(); } void ExpressionEvaluationWindow::_UpdateThreadList() { AutoLocker< ::Team> teamLocker(fTeam); BMenu* frameMenu = fFrameList->Menu(); while (frameMenu->CountItems() > 1) delete frameMenu->RemoveItem(1); BMenu* threadMenu = fThreadList->Menu(); while (threadMenu->CountItems() > 1) delete threadMenu->RemoveItem(1); const ThreadList& threads = fTeam->Threads(); for (ThreadList::ConstIterator it = threads.GetIterator(); ::Thread* thread = it.Next();) { if (thread->State() != THREAD_STATE_STOPPED) continue; BMenuItem* item = NULL; if (_CreateThreadMenuItem(thread, item) != B_OK) return; ObjectDeleter itemDeleter(item); if (!threadMenu->AddItem(item)) return; itemDeleter.Detach(); if (fSelectedThread == NULL) { item->SetMarked(true); _HandleThreadSelectionChanged(thread->ID()); } } if (fSelectedThread == NULL) frameMenu->ItemAt(0L)->SetMarked(true); } void ExpressionEvaluationWindow::_UpdateFrameList() { AutoLocker< ::Team> teamLocker(fTeam); BMenu* frameMenu = fFrameList->Menu(); while (frameMenu->CountItems() > 1) delete frameMenu->RemoveItem(1); frameMenu->ItemAt(0L)->SetMarked(true); if (fSelectedThread == NULL) return; StackTrace* stackTrace = fSelectedThread->GetStackTrace(); if (stackTrace == NULL) return; char buffer[128]; for (int32 i = 0; i < stackTrace->CountFrames(); i++) { StackFrame* frame = stackTrace->FrameAt(i); UiUtils::FunctionNameForFrame(frame, buffer, sizeof(buffer)); BMessage* message = new(std::nothrow) BMessage( MSG_FRAME_SELECTION_CHANGED); if (message == NULL) return; message->AddInt32("frame", i); BMenuItem* item = new(std::nothrow) BMenuItem(buffer, message); if (item == NULL) return; if (!frameMenu->AddItem(item)) return; if (fSelectedFrame == NULL) { item->SetMarked(true); _HandleFrameSelectionChanged(i); } } } status_t ExpressionEvaluationWindow::_CreateThreadMenuItem(::Thread* thread, BMenuItem*& _item) const { BString nameString; nameString.SetToFormat("%" B_PRId32 ": %s", thread->ID(), thread->Name()); BMessage* message = new(std::nothrow) BMessage( MSG_THREAD_SELECTION_CHANGED); if (message == NULL) return B_NO_MEMORY; ObjectDeleter messageDeleter(message); message->AddInt32("thread", thread->ID()); _item = new(std::nothrow) BMenuItem(nameString, message); if (_item == NULL) return B_NO_MEMORY; messageDeleter.Detach(); return B_OK; }