/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2011-2018, Rene Gollent, rene@gollent.com.
* Distributed under the terms of the MIT License.
*/
#include "VariablesView.h"
#include <new>
#include <debugger.h>
#include <Alert.h>
#include <Clipboard.h>
#include <Looper.h>
#include <PopUpMenu.h>
#include <ToolTip.h>
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <PromptWindow.h>
#include "table/TableColumns.h"
#include "ActionMenuItem.h"
#include "AppMessageCodes.h"
#include "Architecture.h"
#include "ExpressionInfo.h"
#include "ExpressionValues.h"
#include "FileSourceCode.h"
#include "Function.h"
#include "FunctionID.h"
#include "FunctionInstance.h"
#include "GuiSettingsUtils.h"
#include "MessageCodes.h"
#include "RangeList.h"
#include "Register.h"
#include "SettingsMenu.h"
#include "SourceLanguage.h"
#include "StackTrace.h"
#include "StackFrame.h"
#include "StackFrameValues.h"
#include "StringUtils.h"
#include "StringValue.h"
#include "SyntheticPrimitiveType.h"
#include "TableCellValueEditor.h"
#include "TableCellValueRenderer.h"
#include "Team.h"
#include "TeamDebugInfo.h"
#include "Thread.h"
#include "Tracing.h"
#include "TypeComponentPath.h"
#include "TypeHandler.h"
#include "TypeHandlerMenuItem.h"
#include "TypeHandlerRoster.h"
#include "TypeLookupConstraints.h"
#include "UiUtils.h"
#include "Value.h"
#include "ValueHandler.h"
#include "ValueHandlerRoster.h"
#include "ValueLocation.h"
#include "ValueNode.h"
#include "ValueNodeManager.h"
#include "Variable.h"
#include "VariableEditWindow.h"
#include "VariableValueNodeChild.h"
#include "VariablesViewState.h"
#include "VariablesViewStateHistory.h"
enum {
VALUE_NODE_TYPE = 'valn'
};
enum {
MSG_MODEL_NODE_HIDDEN = 'monh',
MSG_VALUE_NODE_NEEDS_VALUE = 'mvnv',
MSG_RESTORE_PARTIAL_VIEW_STATE = 'mpvs',
MSG_ADD_WATCH_EXPRESSION = 'awex',
MSG_REMOVE_WATCH_EXPRESSION = 'rwex',
MSG_USE_AUTOMATIC_HANDLER = 'uaha',
MSG_USE_EXPLICIT_HANDLER = 'ueha'
};
// maximum number of array elements to show by default
static const uint64 kMaxArrayElementCount = 10;
// #pragma mark - FunctionKey
struct VariablesView::FunctionKey {
FunctionID* function;
FunctionKey(FunctionID* function)
:
function(function)
{
}
uint32 HashValue() const
{
return function->HashValue();
}
bool operator==(const FunctionKey& other) const
{
return *function == *other.function;
}
};
// #pragma mark - ExpressionInfoEntry
struct VariablesView::ExpressionInfoEntry : FunctionKey, ExpressionInfoList {
ExpressionInfoEntry* next;
ExpressionInfoEntry(FunctionID* function)
:
FunctionKey(function),
ExpressionInfoList(10, false)
{
function->AcquireReference();
}
~ExpressionInfoEntry()
{
_Cleanup();
}
void SetInfo(const ExpressionInfoList& infoList)
{
_Cleanup();
for (int32 i = 0; i < infoList.CountItems(); i++) {
ExpressionInfo* info = infoList.ItemAt(i);
if (!AddItem(info))
break;
info->AcquireReference();
}
}
private:
void _Cleanup()
{
for (int32 i = 0; i < CountItems(); i++)
ItemAt(i)->ReleaseReference();
MakeEmpty();
}
};
// #pragma mark - ExpressionInfoEntryHashDefinition
struct VariablesView::ExpressionInfoEntryHashDefinition {
typedef FunctionKey KeyType;
typedef ExpressionInfoEntry ValueType;
size_t HashKey(const FunctionKey& key) const
{
return key.HashValue();
}
size_t Hash(const ExpressionInfoEntry* value) const
{
return value->HashValue();
}
bool Compare(const FunctionKey& key,
const ExpressionInfoEntry* value) const
{
return key == *value;
}
ExpressionInfoEntry*& GetLink(ExpressionInfoEntry* value) const
{
return value->next;
}
};
// #pragma mark - ContainerListener
class VariablesView::ContainerListener : public ValueNodeContainer::Listener {
public:
ContainerListener(BHandler* indirectTarget);
void SetModel(VariableTableModel* model);
virtual void ValueNodeChanged(ValueNodeChild* nodeChild,
ValueNode* oldNode, ValueNode* newNode);
virtual void ValueNodeChildrenCreated(ValueNode* node);
virtual void ValueNodeChildrenDeleted(ValueNode* node);
virtual void ValueNodeValueChanged(ValueNode* node);
virtual void ModelNodeHidden(ModelNode* node);
virtual void ModelNodeValueRequested(ModelNode* node);
virtual void ModelNodeRestoreViewStateRequested(ModelNode* node);
private:
BHandler* fIndirectTarget;
VariableTableModel* fModel;
};
// #pragma mark - ExpressionVariableID
class VariablesView::ExpressionVariableID : public ObjectID {
public:
ExpressionVariableID(ExpressionInfo* info)
:
fInfo(info)
{
fInfo->AcquireReference();
}
virtual ~ExpressionVariableID()
{
fInfo->ReleaseReference();
}
virtual bool operator==(const ObjectID& other) const
{
const ExpressionVariableID* otherID
= dynamic_cast<const ExpressionVariableID*>(&other);
if (otherID == NULL)
return false;
return fInfo == otherID->fInfo;
}
protected:
virtual uint32 ComputeHashValue() const
{
uint32 hash = reinterpret_cast<addr_t>(fInfo);
hash = hash * 19 + StringUtils::HashValue(fInfo->Expression());
return hash;
}
private:
ExpressionInfo* fInfo;
};
// #pragma mark - ModelNode
class VariablesView::ModelNode : public BReferenceable {
public:
ModelNode(ModelNode* parent, Variable* variable, ValueNodeChild* nodeChild,
bool isPresentationNode)
:
fParent(parent),
fNodeChild(nodeChild),
fVariable(variable),
fValue(NULL),
fPreviousValue(),
fValueHandler(NULL),
fTableCellRenderer(NULL),
fLastRendererSettings(),
fCastedType(NULL),
fTypeHandler(NULL),
fComponentPath(NULL),
fIsPresentationNode(isPresentationNode),
fHidden(false),
fValueChanged(false),
fPresentationName()
{
fVariable->AcquireReference();
fNodeChild->AcquireReference();
}
~ModelNode()
{
SetTableCellRenderer(NULL);
SetValueHandler(NULL);
SetValue(NULL);
for (int32 i = 0; ModelNode* child = fChildren.ItemAt(i); i++)
child->ReleaseReference();
fNodeChild->ReleaseReference();
fVariable->ReleaseReference();
if (fComponentPath != NULL)
fComponentPath->ReleaseReference();
if (fCastedType != NULL)
fCastedType->ReleaseReference();
if (fTypeHandler != NULL)
fTypeHandler->ReleaseReference();
}
status_t Init()
{
fComponentPath = new(std::nothrow) TypeComponentPath();
if (fComponentPath == NULL)
return B_NO_MEMORY;
if (fParent != NULL)
*fComponentPath = *fParent->GetPath();
TypeComponent component;
// TODO: this should actually discriminate between different
// classes of type component kinds
component.SetToBaseType(fNodeChild->GetType()->Kind(),
0, fNodeChild->Name());
fComponentPath->AddComponent(component);
return B_OK;
}
ModelNode* Parent() const
{
return fParent;
}
ValueNodeChild* NodeChild() const
{
return fNodeChild;
}
const BString& Name() const
{
return fPresentationName.IsEmpty()
? fNodeChild->Name() : fPresentationName;
}
void SetPresentationName(const BString& name)
{
fPresentationName = name;
}
Type* GetType() const
{
if (fCastedType != NULL)
return fCastedType;
return fNodeChild->GetType();
}
Variable* GetVariable() const
{
return fVariable;
}
Value* GetValue() const
{
return fValue;
}
void SetValue(Value* value)
{
if (value == fValue)
return;
if (fValue != NULL)
fValue->ReleaseReference();
fValue = value;
if (fValue != NULL)
fValue->AcquireReference();
_CompareValues();
}
const BVariant& PreviousValue() const
{
return fPreviousValue;
}
void SetPreviousValue(const BVariant& value)
{
fPreviousValue = value;
}
Type* GetCastedType() const
{
return fCastedType;
}
void SetCastedType(Type* type)
{
if (fCastedType != NULL)
fCastedType->ReleaseReference();
fCastedType = type;
if (type != NULL)
fCastedType->AcquireReference();
}
TypeHandler* GetTypeHandler() const
{
return fTypeHandler;
}
void SetTypeHandler(TypeHandler* handler)
{
if (fTypeHandler != NULL)
fTypeHandler->ReleaseReference();
fTypeHandler = handler;
if (fTypeHandler != NULL)
fTypeHandler->AcquireReference();
}
const BMessage& GetLastRendererSettings() const
{
return fLastRendererSettings;
}
void SetLastRendererSettings(const BMessage& settings)
{
fLastRendererSettings = settings;
}
TypeComponentPath* GetPath() const
{
return fComponentPath;
}
ValueHandler* GetValueHandler() const
{
return fValueHandler;
}
void SetValueHandler(ValueHandler* handler)
{
if (handler == fValueHandler)
return;
if (fValueHandler != NULL)
fValueHandler->ReleaseReference();
fValueHandler = handler;
if (fValueHandler != NULL)
fValueHandler->AcquireReference();
}
TableCellValueRenderer* TableCellRenderer() const
{
return fTableCellRenderer;
}
void SetTableCellRenderer(TableCellValueRenderer* renderer)
{
if (renderer == fTableCellRenderer)
return;
if (fTableCellRenderer != NULL)
fTableCellRenderer->ReleaseReference();
fTableCellRenderer = renderer;
if (fTableCellRenderer != NULL)
fTableCellRenderer->AcquireReference();
}
bool IsPresentationNode() const
{
return fIsPresentationNode;
}
bool IsHidden() const
{
return fHidden;
}
void SetHidden(bool hidden)
{
fHidden = hidden;
}
bool ValueChanged() const
{
return fValueChanged;
}
int32 CountChildren() const
{
return fChildren.CountItems();
}
ModelNode* ChildAt(int32 index) const
{
return fChildren.ItemAt(index);
}
int32 IndexOf(ModelNode* child) const
{
return fChildren.IndexOf(child);
}
bool AddChild(ModelNode* child)
{
if (!fChildren.AddItem(child))
return false;
child->AcquireReference();
return true;
}
bool RemoveChild(ModelNode* child)
{
if (!fChildren.RemoveItem(child))
return false;
child->ReleaseReference();
return true;
}
bool RemoveAllChildren()
{
for (int32 i = 0; i < fChildren.CountItems(); i++)
RemoveChild(fChildren.ItemAt(i));
return true;
}
private:
typedef BObjectList<ModelNode> ChildList;
private:
void _CompareValues()
{
fValueChanged = false;
if (fValue != NULL) {
if (fPreviousValue.Type() != 0) {
BVariant newValue;
fValue->ToVariant(newValue);
fValueChanged = (fPreviousValue != newValue);
} else {
// for expression variables, always consider the initial
// value as changed, since their evaluation has just been
// requested, and thus their initial value is by definition
// new/of interest
fValueChanged = dynamic_cast<ExpressionVariableID*>(
fVariable->ID()) != NULL;
}
}
}
private:
ModelNode* fParent;
ValueNodeChild* fNodeChild;
Variable* fVariable;
Value* fValue;
BVariant fPreviousValue;
ValueHandler* fValueHandler;
TableCellValueRenderer* fTableCellRenderer;
BMessage fLastRendererSettings;
Type* fCastedType;
TypeHandler* fTypeHandler;
ChildList fChildren;
TypeComponentPath* fComponentPath;
bool fIsPresentationNode;
bool fHidden;
bool fValueChanged;
BString fPresentationName;
public:
ModelNode* fNext;
};
// #pragma mark - VariablesExpressionInfo
class VariablesView::VariablesExpressionInfo : public ExpressionInfo {
public:
VariablesExpressionInfo(const BString& expression, ModelNode* node)
:
ExpressionInfo(expression),
fTargetNode(node)
{
fTargetNode->AcquireReference();
}
virtual ~VariablesExpressionInfo()
{
fTargetNode->ReleaseReference();
}
inline ModelNode* TargetNode() const
{
return fTargetNode;
}
private:
ModelNode* fTargetNode;
};
// #pragma mark - VariableValueColumn
class VariablesView::VariableValueColumn : public StringTableColumn {
public:
VariableValueColumn(int32 modelIndex, const char* title, float width,
float minWidth, float maxWidth, uint32 truncate = B_TRUNCATE_MIDDLE,
alignment align = B_ALIGN_RIGHT)
:
StringTableColumn(modelIndex, title, width, minWidth, maxWidth,
truncate, align)
{
}
protected:
void DrawValue(const BVariant& value, BRect rect, BView* targetView)
{
// draw the node's value with the designated renderer
if (value.Type() == VALUE_NODE_TYPE) {
ModelNode* node = dynamic_cast<ModelNode*>(value.ToReferenceable());
if (node != NULL && node->GetValue() != NULL
&& node->TableCellRenderer() != NULL) {
node->TableCellRenderer()->RenderValue(node->GetValue(),
node->ValueChanged(), rect, targetView);
return;
}
} else if (value.Type() == B_STRING_TYPE) {
fField.SetString(value.ToString());
} else {
// fall back to drawing an empty string
fField.SetString("");
}
fField.SetWidth(Width());
fColumn.DrawField(&fField, rect, targetView);
}
float GetPreferredWidth(const BVariant& value, BView* targetView) const
{
// get the preferred width from the node's designated renderer
if (value.Type() == VALUE_NODE_TYPE) {
ModelNode* node = dynamic_cast<ModelNode*>(value.ToReferenceable());
if (node != NULL && node->GetValue() != NULL
&& node->TableCellRenderer() != NULL) {
return node->TableCellRenderer()->PreferredValueWidth(
node->GetValue(), targetView);
}
}
return fColumn.BTitledColumn::GetPreferredWidth(NULL, targetView);
}
virtual BField* PrepareField(const BVariant& _value) const
{
return NULL;
}
};
// #pragma mark - VariableTableModel
class VariablesView::VariableTableModel : public TreeTableModel,
public TreeTableToolTipProvider {
public:
VariableTableModel(ValueNodeManager* manager);
~VariableTableModel();
status_t Init();
void SetContainerListener(
ContainerListener* listener);
void SetStackFrame(::Thread* thread,
StackFrame* stackFrame);
void ValueNodeChanged(ValueNodeChild* nodeChild,
ValueNode* oldNode, ValueNode* newNode);
void ValueNodeChildrenCreated(ValueNode* node);
void ValueNodeChildrenDeleted(ValueNode* node);
void ValueNodeValueChanged(ValueNode* node);
virtual int32 CountColumns() const;
virtual void* Root() const;
virtual int32 CountChildren(void* parent) const;
virtual void* ChildAt(void* parent, int32 index) const;
virtual bool GetValueAt(void* object, int32 columnIndex,
BVariant& _value);
bool GetTreePath(ModelNode* node,
TreeTablePath& _path) const;
void NodeExpanded(ModelNode* node);
void NotifyNodeChanged(ModelNode* node);
void NotifyNodeHidden(ModelNode* node);
virtual bool GetToolTipForTablePath(
const TreeTablePath& path,
int32 columnIndex, BToolTip** _tip);
status_t AddSyntheticNode(Variable* variable,
ValueNodeChild*& _child,
const char* presentationName = NULL);
void RemoveSyntheticNode(ModelNode* node);
private:
struct NodeHashDefinition {
typedef ValueNodeChild* KeyType;
typedef ModelNode ValueType;
size_t HashKey(const ValueNodeChild* key) const
{
return (size_t)key;
}
size_t Hash(const ModelNode* value) const
{
return HashKey(value->NodeChild());
}
bool Compare(const ValueNodeChild* key,
const ModelNode* value) const
{
return value->NodeChild() == key;
}
ModelNode*& GetLink(ModelNode* value) const
{
return value->fNext;
}
};
typedef BObjectList<ModelNode> NodeList;
typedef BOpenHashTable<NodeHashDefinition> NodeTable;
private:
// container must be locked
status_t _AddNode(Variable* variable, ModelNode* parent,
ValueNodeChild* nodeChild,
bool isPresentationNode = false,
bool isOnlyChild = false);
private:
::Thread* fThread;
ValueNodeManager* fNodeManager;
ContainerListener* fContainerListener;
NodeList fNodes;
NodeTable fNodeTable;
};
class VariablesView::ContextMenu : public BPopUpMenu {
public:
ContextMenu(const BMessenger& parent, const char* name)
: BPopUpMenu(name, false, false),
fParent(parent)
{
}
virtual void Hide()
{
BPopUpMenu::Hide();
BMessage message(MSG_VARIABLES_VIEW_CONTEXT_MENU_DONE);
message.AddPointer("menu", this);
fParent.SendMessage(&message);
}
private:
BMessenger fParent;
};
// #pragma mark - TableCellContextMenuTracker
class VariablesView::TableCellContextMenuTracker : public BReferenceable,
Settings::Listener {
public:
TableCellContextMenuTracker(ModelNode* node, BLooper* parentLooper,
const BMessenger& parent)
:
fNode(node),
fParentLooper(parentLooper),
fParent(parent),
fRendererSettings(NULL),
fRendererSettingsMenu(NULL),
fRendererMenuAdded(false),
fMenuPreparedToShow(false)
{
fNode->AcquireReference();
}
~TableCellContextMenuTracker()
{
FinishMenu(true);
if (fRendererSettingsMenu != NULL)
fRendererSettingsMenu->ReleaseReference();
if (fRendererSettings != NULL)
fRendererSettings->ReleaseReference();
fNode->ReleaseReference();
}
status_t Init(Settings* rendererSettings,
SettingsMenu* rendererSettingsMenu,
ContextActionList* preSettingsActions = NULL,
ContextActionList* postSettingsActions = NULL)
{
if (rendererSettings == NULL && preSettingsActions == NULL
&& postSettingsActions == NULL) {
return B_BAD_VALUE;
}
if (rendererSettings != NULL) {
fRendererSettings = rendererSettings;
fRendererSettings->AcquireReference();
fRendererSettingsMenu = rendererSettingsMenu;
fRendererSettingsMenu->AcquireReference();
}
fContextMenu = new(std::nothrow) ContextMenu(fParent,
"table cell settings popup");
if (fContextMenu == NULL)
return B_NO_MEMORY;
status_t error = B_OK;
if (preSettingsActions != NULL
&& preSettingsActions->CountItems() > 0) {
error = _AddActionItems(preSettingsActions);
if (error != B_OK)
return error;
if (fRendererSettingsMenu != NULL || postSettingsActions != NULL)
fContextMenu->AddSeparatorItem();
}
if (fRendererSettingsMenu != NULL) {
error = fRendererSettingsMenu->AddToMenu(fContextMenu,
fContextMenu->CountItems());
if (error != B_OK)
return error;
if (postSettingsActions != NULL)
fContextMenu->AddSeparatorItem();
}
if (postSettingsActions != NULL) {
error = _AddActionItems(postSettingsActions);
if (error != B_OK)
return error;
}
if (fRendererSettings != NULL) {
AutoLocker<Settings> settingsLocker(fRendererSettings);
fRendererSettings->AddListener(this);
fRendererMenuAdded = true;
}
return B_OK;
}
void ShowMenu(BPoint screenWhere)
{
if (fRendererMenuAdded)
fRendererSettingsMenu->PrepareToShow(fParentLooper);
for (int32 i = 0; i < fContextMenu->CountItems(); i++) {
ActionMenuItem* item = dynamic_cast<ActionMenuItem*>(
fContextMenu->ItemAt(i));
if (item != NULL)
item->PrepareToShow(fParentLooper, fParent.Target(NULL));
}
fMenuPreparedToShow = true;
BRect mouseRect(screenWhere, screenWhere);
mouseRect.InsetBy(-4.0, -4.0);
fContextMenu->Go(screenWhere, true, false, mouseRect, true);
}
bool FinishMenu(bool force)
{
bool stillActive = false;
if (fMenuPreparedToShow) {
if (fRendererMenuAdded)
stillActive = fRendererSettingsMenu->Finish(fParentLooper,
force);
for (int32 i = 0; i < fContextMenu->CountItems(); i++) {
ActionMenuItem* item = dynamic_cast<ActionMenuItem*>(
fContextMenu->ItemAt(i));
if (item != NULL) {
stillActive |= item->Finish(fParentLooper,
fParent.Target(NULL), force);
}
}
fMenuPreparedToShow = stillActive;
}
if (fRendererMenuAdded) {
fRendererSettingsMenu->RemoveFromMenu();
fRendererSettings->RemoveListener(this);
fRendererMenuAdded = false;
}
if (fContextMenu != NULL) {
delete fContextMenu;
fContextMenu = NULL;
}
return stillActive;
}
private:
// Settings::Listener
virtual void SettingValueChanged(Setting* setting)
{
BMessage message(MSG_VARIABLES_VIEW_NODE_SETTINGS_CHANGED);
fNode->AcquireReference();
if (message.AddPointer("node", fNode) != B_OK
|| fParent.SendMessage(&message) != B_OK) {
fNode->ReleaseReference();
}
}
status_t _AddActionItems(ContextActionList* actions)
{
if (fContextMenu == NULL)
return B_BAD_VALUE;
int32 index = fContextMenu->CountItems();
for (int32 i = 0; ActionMenuItem* item = actions->ItemAt(i); i++) {
if (!fContextMenu->AddItem(item, index + i)) {
for (i--; i >= 0; i--)
fContextMenu->RemoveItem(fContextMenu->ItemAt(index + i));
return B_NO_MEMORY;
}
}
return B_OK;
}
private:
ModelNode* fNode;
BLooper* fParentLooper;
BMessenger fParent;
ContextMenu* fContextMenu;
Settings* fRendererSettings;
SettingsMenu* fRendererSettingsMenu;
bool fRendererMenuAdded;
bool fMenuPreparedToShow;
};
// #pragma mark - ContainerListener
VariablesView::ContainerListener::ContainerListener(BHandler* indirectTarget)
:
fIndirectTarget(indirectTarget),
fModel(NULL)
{
}
void
VariablesView::ContainerListener::SetModel(VariableTableModel* model)
{
fModel = model;
}
void
VariablesView::ContainerListener::ValueNodeChanged(ValueNodeChild* nodeChild,
ValueNode* oldNode, ValueNode* newNode)
{
// If the looper is already locked, invoke the model's hook synchronously.
if (fIndirectTarget->Looper()->IsLocked()) {
fModel->ValueNodeChanged(nodeChild, oldNode, newNode);
return;
}
// looper not locked yet -- call asynchronously to avoid reverse locking
// order
BReference<ValueNodeChild> nodeChildReference(nodeChild);
BReference<ValueNode> oldNodeReference(oldNode);
BReference<ValueNode> newNodeReference(newNode);
BMessage message(MSG_VALUE_NODE_CHANGED);
if (message.AddPointer("nodeChild", nodeChild) == B_OK
&& message.AddPointer("oldNode", oldNode) == B_OK
&& message.AddPointer("newNode", newNode) == B_OK
&& fIndirectTarget->Looper()->PostMessage(&message, fIndirectTarget)
== B_OK) {
nodeChildReference.Detach();
oldNodeReference.Detach();
newNodeReference.Detach();
}
}
void
VariablesView::ContainerListener::ValueNodeChildrenCreated(ValueNode* node)
{
// If the looper is already locked, invoke the model's hook synchronously.
if (fIndirectTarget->Looper()->IsLocked()) {
fModel->ValueNodeChildrenCreated(node);
return;
}
// looper not locked yet -- call asynchronously to avoid reverse locking
// order
BReference<ValueNode> nodeReference(node);
BMessage message(MSG_VALUE_NODE_CHILDREN_CREATED);
if (message.AddPointer("node", node) == B_OK
&& fIndirectTarget->Looper()->PostMessage(&message, fIndirectTarget)
== B_OK) {
nodeReference.Detach();
}
}
void
VariablesView::ContainerListener::ValueNodeChildrenDeleted(ValueNode* node)
{
// If the looper is already locked, invoke the model's hook synchronously.
if (fIndirectTarget->Looper()->IsLocked()) {
fModel->ValueNodeChildrenDeleted(node);
return;
}
// looper not locked yet -- call asynchronously to avoid reverse locking
// order
BReference<ValueNode> nodeReference(node);
BMessage message(MSG_VALUE_NODE_CHILDREN_DELETED);
if (message.AddPointer("node", node) == B_OK
&& fIndirectTarget->Looper()->PostMessage(&message, fIndirectTarget)
== B_OK) {
nodeReference.Detach();
}
}
void
VariablesView::ContainerListener::ValueNodeValueChanged(ValueNode* node)
{
// If the looper is already locked, invoke the model's hook synchronously.
if (fIndirectTarget->Looper()->IsLocked()) {
fModel->ValueNodeValueChanged(node);
return;
}
// looper not locked yet -- call asynchronously to avoid reverse locking
// order
BReference<ValueNode> nodeReference(node);
BMessage message(MSG_VALUE_NODE_VALUE_CHANGED);
if (message.AddPointer("node", node) == B_OK
&& fIndirectTarget->Looper()->PostMessage(&message, fIndirectTarget)
== B_OK) {
nodeReference.Detach();
}
}
void
VariablesView::ContainerListener::ModelNodeHidden(ModelNode* node)
{
BReference<ModelNode> nodeReference(node);
BMessage message(MSG_MODEL_NODE_HIDDEN);
if (message.AddPointer("node", node) == B_OK
&& fIndirectTarget->Looper()->PostMessage(&message, fIndirectTarget)
== B_OK) {
nodeReference.Detach();
}
}
void
VariablesView::ContainerListener::ModelNodeValueRequested(ModelNode* node)
{
BReference<ModelNode> nodeReference(node);
BMessage message(MSG_VALUE_NODE_NEEDS_VALUE);
if (message.AddPointer("node", node) == B_OK
&& fIndirectTarget->Looper()->PostMessage(&message, fIndirectTarget)
== B_OK) {
nodeReference.Detach();
}
}
void
VariablesView::ContainerListener::ModelNodeRestoreViewStateRequested(
ModelNode* node)
{
BReference<ModelNode> nodeReference(node);
BMessage message(MSG_RESTORE_PARTIAL_VIEW_STATE);
if (message.AddPointer("node", node) == B_OK
&& fIndirectTarget->Looper()->PostMessage(&message, fIndirectTarget)
== B_OK) {
nodeReference.Detach();
}
}
// #pragma mark - VariableTableModel
VariablesView::VariableTableModel::VariableTableModel(
ValueNodeManager* manager)
:
fThread(NULL),
fNodeManager(manager),
fContainerListener(NULL),
fNodeTable()
{
fNodeManager->AcquireReference();
}
VariablesView::VariableTableModel::~VariableTableModel()
{
fNodeManager->ReleaseReference();
}
status_t
VariablesView::VariableTableModel::Init()
{
return fNodeTable.Init();
}
void
VariablesView::VariableTableModel::SetContainerListener(
ContainerListener* listener)
{
if (listener == fContainerListener)
return;
if (fContainerListener != NULL) {
if (fNodeManager != NULL)
fNodeManager->RemoveListener(fContainerListener);
fContainerListener->SetModel(NULL);
}
fContainerListener = listener;
if (fContainerListener != NULL) {
fContainerListener->SetModel(this);
if (fNodeManager != NULL)
fNodeManager->AddListener(fContainerListener);
}
}
void
VariablesView::VariableTableModel::SetStackFrame(::Thread* thread,
StackFrame* stackFrame)
{
fThread = thread;
fNodeManager->SetStackFrame(thread, stackFrame);
int32 count = fNodes.CountItems();
fNodeTable.Clear(true);
if (!fNodes.IsEmpty()) {
for (int32 i = 0; i < count; i++)
fNodes.ItemAt(i)->ReleaseReference();
fNodes.MakeEmpty();
}
NotifyNodesRemoved(TreeTablePath(), 0, count);
if (stackFrame == NULL)
return;
ValueNodeContainer* container = fNodeManager->GetContainer();
AutoLocker<ValueNodeContainer> containerLocker(container);
for (int32 i = 0; i < container->CountChildren(); i++) {
VariableValueNodeChild* child = dynamic_cast<VariableValueNodeChild *>(
container->ChildAt(i));
_AddNode(child->GetVariable(), NULL, child);
// top level nodes get their children added immediately
// so those won't invoke our callback hook. Add them directly here.
ValueNodeChildrenCreated(child->Node());
}
}
void
VariablesView::VariableTableModel::ValueNodeChanged(ValueNodeChild* nodeChild,
ValueNode* oldNode, ValueNode* newNode)
{
AutoLocker<ValueNodeContainer> containerLocker(
fNodeManager->GetContainer());
ModelNode* modelNode = fNodeTable.Lookup(nodeChild);
if (modelNode == NULL)
return;
if (oldNode != NULL) {
ValueNodeChildrenDeleted(oldNode);
NotifyNodeChanged(modelNode);
}
}
void
VariablesView::VariableTableModel::ValueNodeChildrenCreated(
ValueNode* valueNode)
{
AutoLocker<ValueNodeContainer> containerLocker(
fNodeManager->GetContainer());
// check whether we know the node
ValueNodeChild* nodeChild = valueNode->NodeChild();
if (nodeChild == NULL)
return;
ModelNode* modelNode = fNodeTable.Lookup(nodeChild);
if (modelNode == NULL)
return;
// Iterate through the children and create model nodes for the ones we
// don't know yet.
int32 childCount = valueNode->CountChildren();
for (int32 i = 0; i < childCount; i++) {
ValueNodeChild* child = valueNode->ChildAt(i);
if (fNodeTable.Lookup(child) == NULL) {
_AddNode(modelNode->GetVariable(), modelNode, child,
child->IsInternal(), childCount == 1);
}
ModelNode* childNode = fNodeTable.Lookup(child);
if (childNode != NULL)
fContainerListener->ModelNodeValueRequested(childNode);
}
if (valueNode->ChildCreationNeedsValue())
fContainerListener->ModelNodeRestoreViewStateRequested(modelNode);
}
void
VariablesView::VariableTableModel::ValueNodeChildrenDeleted(ValueNode* node)
{
AutoLocker<ValueNodeContainer> containerLocker(
fNodeManager->GetContainer());
// check whether we know the node
ValueNodeChild* nodeChild = node->NodeChild();
if (nodeChild == NULL)
return;
ModelNode* modelNode = fNodeTable.Lookup(nodeChild);
if (modelNode == NULL)
return;
// in the case of an address node with a hidden child,
// we want to send removal notifications for the children
// instead.
BReference<ModelNode> hiddenChild;
if (modelNode->CountChildren() == 1
&& modelNode->ChildAt(0)->IsHidden()) {
hiddenChild.SetTo(modelNode->ChildAt(0));
modelNode->RemoveChild(hiddenChild);
modelNode = hiddenChild;
fNodeTable.Remove(hiddenChild);
}
for (int32 i = modelNode->CountChildren() - 1; i >= 0 ; i--) {
BReference<ModelNode> childNode = modelNode->ChildAt(i);
// recursively remove the current node's child hierarchy.
if (childNode->CountChildren() != 0)
ValueNodeChildrenDeleted(childNode->NodeChild()->Node());
TreeTablePath treePath;
if (GetTreePath(childNode, treePath)) {
int32 index = treePath.RemoveLastComponent();
NotifyNodesRemoved(treePath, index, 1);
}
modelNode->RemoveChild(childNode);
fNodeTable.Remove(childNode);
}
}
void
VariablesView::VariableTableModel::ValueNodeValueChanged(ValueNode* valueNode)
{
AutoLocker<ValueNodeContainer> containerLocker(
fNodeManager->GetContainer());
// check whether we know the node
ValueNodeChild* nodeChild = valueNode->NodeChild();
if (nodeChild == NULL)
return;
ModelNode* modelNode = fNodeTable.Lookup(nodeChild);
if (modelNode == NULL)
return;
// check whether the value actually changed
Value* value = valueNode->GetValue();
if (value == modelNode->GetValue())
return;
// get a value handler
ValueHandler* valueHandler;
status_t error = ValueHandlerRoster::Default()->FindValueHandler(value,
valueHandler);
if (error != B_OK)
return;
BReference<ValueHandler> handlerReference(valueHandler, true);
// create a table cell renderer for the value
TableCellValueRenderer* renderer = NULL;
error = valueHandler->GetTableCellValueRenderer(value, renderer);
if (error != B_OK)
return;
BReference<TableCellValueRenderer> rendererReference(renderer, true);
// set value/handler/renderer
modelNode->SetValue(value);
modelNode->SetValueHandler(valueHandler);
modelNode->SetTableCellRenderer(renderer);
// we have to restore renderer settings here since until this point
// we don't yet know what renderer is in use.
if (renderer != NULL) {
Settings* settings = renderer->GetSettings();
if (settings != NULL)
settings->RestoreValues(modelNode->GetLastRendererSettings());
}
// notify table model listeners
NotifyNodeChanged(modelNode);
}
int32
VariablesView::VariableTableModel::CountColumns() const
{
return 3;
}
void*
VariablesView::VariableTableModel::Root() const
{
return (void*)this;
}
int32
VariablesView::VariableTableModel::CountChildren(void* parent) const
{
if (parent == this)
return fNodes.CountItems();
// If the node only has a hidden child, pretend the node directly has the
// child's children.
ModelNode* modelNode = (ModelNode*)parent;
int32 childCount = modelNode->CountChildren();
if (childCount == 1) {
ModelNode* child = modelNode->ChildAt(0);
if (child->IsHidden())
return child->CountChildren();
}
return childCount;
}
void*
VariablesView::VariableTableModel::ChildAt(void* parent, int32 index) const
{
if (parent == this)
return fNodes.ItemAt(index);
// If the node only has a hidden child, pretend the node directly has the
// child's children.
ModelNode* modelNode = (ModelNode*)parent;
int32 childCount = modelNode->CountChildren();
if (childCount == 1) {
ModelNode* child = modelNode->ChildAt(0);
if (child->IsHidden())
return child->ChildAt(index);
}
return modelNode->ChildAt(index);
}
bool
VariablesView::VariableTableModel::GetValueAt(void* object, int32 columnIndex,
BVariant& _value)
{
ModelNode* node = (ModelNode*)object;
switch (columnIndex) {
case 0:
_value.SetTo(node->Name(), B_VARIANT_DONT_COPY_DATA);
return true;
case 1:
if (node->GetValue() == NULL) {
ValueLocation* location = node->NodeChild()->Location();
if (location == NULL)
return false;
ValueNode* childNode = node->NodeChild()->Node();
if (childNode == NULL)
return false;
Type* nodeChildRawType = childNode->GetType()->ResolveRawType(
false);
if (nodeChildRawType->Kind() == TYPE_COMPOUND)
{
if (location->CountPieces() > 1)
return false;
BString data;
ValuePieceLocation piece = location->PieceAt(0);
if (piece.type != VALUE_PIECE_LOCATION_MEMORY)
return false;
data.SetToFormat("[@ %#" B_PRIx64 "]", piece.address);
_value.SetTo(data);
return true;
}
return false;
}
_value.SetTo(node, VALUE_NODE_TYPE);
return true;
case 2:
{
// use the type of the underlying value node, as it may
// be different from the initially assigned top level type
// due to casting
ValueNode* childNode = node->NodeChild()->Node();
if (childNode == NULL)
return false;
Type* type = childNode->GetType();
if (type == NULL)
return false;
_value.SetTo(type->Name(), B_VARIANT_DONT_COPY_DATA);
return true;
}
default:
return false;
}
}
void
VariablesView::VariableTableModel::NodeExpanded(ModelNode* node)
{
AutoLocker<ValueNodeContainer> containerLocker(
fNodeManager->GetContainer());
// add children of all children
// If the node only has a hidden child, add the child's children instead.
if (node->CountChildren() == 1) {
ModelNode* child = node->ChildAt(0);
if (child->IsHidden())
node = child;
}
// add the children
for (int32 i = 0; ModelNode* child = node->ChildAt(i); i++)
fNodeManager->AddChildNodes(child->NodeChild());
}
void
VariablesView::VariableTableModel::NotifyNodeChanged(ModelNode* node)
{
if (!node->IsHidden()) {
TreeTablePath treePath;
if (GetTreePath(node, treePath)) {
int32 index = treePath.RemoveLastComponent();
NotifyNodesChanged(treePath, index, 1);
}
}
}
void
VariablesView::VariableTableModel::NotifyNodeHidden(ModelNode* node)
{
fContainerListener->ModelNodeHidden(node);
}
bool
VariablesView::VariableTableModel::GetToolTipForTablePath(
const TreeTablePath& path, int32 columnIndex, BToolTip** _tip)
{
ModelNode* node = (ModelNode*)NodeForPath(path);
if (node == NULL)
return false;
BString tipData;
ValueNodeChild* child = node->NodeChild();
status_t error = child->LocationResolutionState();
if (error != B_OK)
tipData.SetToFormat("Unable to resolve location: %s", strerror(error));
else {
ValueNode* valueNode = child->Node();
if (valueNode == NULL)
return false;
error = valueNode->LocationAndValueResolutionState();
if (error != B_OK) {
tipData.SetToFormat("Unable to resolve value: %s\n\n",
strerror(error));
}
switch (columnIndex) {
case 0:
{
ValueLocation* location = child->Location();
for (int32 i = 0; i < location->CountPieces(); i++) {
ValuePieceLocation piece = location->PieceAt(i);
BString pieceData;
switch (piece.type) {
case VALUE_PIECE_LOCATION_MEMORY:
pieceData.SetToFormat("(%" B_PRId32 "): Address: "
"%#" B_PRIx64 ", Size: %" B_PRId64 " bytes\n",
i, piece.address, piece.size);
break;
case VALUE_PIECE_LOCATION_REGISTER:
{
Architecture* architecture = fThread->GetTeam()
->GetArchitecture();
pieceData.SetToFormat("(%" B_PRId32 "): Register "
"(%s)\n", i,
architecture->Registers()[piece.reg].Name());
break;
}
default:
break;
}
tipData += pieceData;
}
tipData += "Editable: ";
tipData += error == B_OK && location->IsWritable()
? "Yes" : "No";
break;
}
case 1:
{
Value* value = node->GetValue();
if (value != NULL)
value->ToString(tipData);
break;
}
default:
break;
}
}
if (tipData.IsEmpty())
return false;
*_tip = new(std::nothrow) BTextToolTip(tipData);
if (*_tip == NULL)
return false;
return true;
}
status_t
VariablesView::VariableTableModel::AddSyntheticNode(Variable* variable,
ValueNodeChild*& _child, const char* presentationName)
{
ValueNodeContainer* container = fNodeManager->GetContainer();
AutoLocker<ValueNodeContainer> containerLocker(container);
status_t error;
if (_child == NULL) {
_child = new(std::nothrow) VariableValueNodeChild(variable);
if (_child == NULL)
return B_NO_MEMORY;
BReference<ValueNodeChild> childReference(_child, true);
ValueNode* valueNode;
if (_child->IsInternal())
error = _child->CreateInternalNode(valueNode);
else {
error = TypeHandlerRoster::Default()->CreateValueNode(_child,
_child->GetType(), NULL, valueNode);
}
if (error != B_OK)
return error;
_child->SetNode(valueNode);
valueNode->ReleaseReference();
}
container->AddChild(_child);
error = _AddNode(variable, NULL, _child);
if (error != B_OK) {
container->RemoveChild(_child);
return error;
}
// since we're injecting these nodes synthetically,
// we have to manually ask the node manager to create any
// applicable children; this would normally be done implicitly
// for top level nodes, as they're added from the parameters/locals,
// but not here.
fNodeManager->AddChildNodes(_child);
ModelNode* childNode = fNodeTable.Lookup(_child);
if (childNode != NULL) {
if (presentationName != NULL)
childNode->SetPresentationName(presentationName);
ValueNode* valueNode = _child->Node();
if (valueNode->LocationAndValueResolutionState()
== VALUE_NODE_UNRESOLVED) {
fContainerListener->ModelNodeValueRequested(childNode);
} else
ValueNodeValueChanged(valueNode);
}
ValueNodeChildrenCreated(_child->Node());
return B_OK;
}
void
VariablesView::VariableTableModel::RemoveSyntheticNode(ModelNode* node)
{
int32 index = fNodes.IndexOf(node);
if (index < 0)
return;
fNodeTable.Remove(node);
fNodes.RemoveItemAt(index);
NotifyNodesRemoved(TreeTablePath(), index, 1);
node->ReleaseReference();
}
status_t
VariablesView::VariableTableModel::_AddNode(Variable* variable,
ModelNode* parent, ValueNodeChild* nodeChild, bool isPresentationNode,
bool isOnlyChild)
{
// Don't create nodes for unspecified types -- we can't get/show their
// value anyway.
Type* nodeChildRawType = nodeChild->GetType()->ResolveRawType(false);
if (nodeChildRawType->Kind() == TYPE_UNSPECIFIED)
return B_OK;
ModelNode* node = new(std::nothrow) ModelNode(parent, variable, nodeChild,
isPresentationNode);
BReference<ModelNode> nodeReference(node, true);
if (node == NULL || node->Init() != B_OK)
return B_NO_MEMORY;
int32 childIndex;
if (parent != NULL) {
childIndex = parent->CountChildren();
if (!parent->AddChild(node))
return B_NO_MEMORY;
// the parent has a reference, now
} else {
childIndex = fNodes.CountItems();
if (!fNodes.AddItem(node))
return B_NO_MEMORY;
nodeReference.Detach();
// the fNodes list has a reference, now
}
fNodeTable.Insert(node);
// if an address type node has only a single child, and that child
// is a compound type, mark it hidden
if (isOnlyChild && parent != NULL) {
ValueNode* parentValueNode = parent->NodeChild()->Node();
if (parentValueNode != NULL) {
if (parentValueNode->GetType()->ResolveRawType(false)->Kind()
== TYPE_ADDRESS) {
type_kind childKind = nodeChildRawType->Kind();
if (childKind == TYPE_COMPOUND || childKind == TYPE_ARRAY) {
node->SetHidden(true);
// we need to tell the listener about nodes like this so
// any necessary actions can be taken for them (i.e. value
// resolution), since they're otherwise invisible to
// outsiders.
NotifyNodeHidden(node);
}
}
}
}
// notify table model listeners
if (!node->IsHidden()) {
TreeTablePath path;
if (parent == NULL || GetTreePath(parent, path))
NotifyNodesAdded(path, childIndex, 1);
}
// if the node is hidden, add its children
if (node->IsHidden())
fNodeManager->AddChildNodes(nodeChild);
return B_OK;
}
bool
VariablesView::VariableTableModel::GetTreePath(ModelNode* node,
TreeTablePath& _path) const
{
// recurse, if the node has a parent
if (ModelNode* parent = node->Parent()) {
if (!GetTreePath(parent, _path))
return false;
if (node->IsHidden())
return true;
return _path.AddComponent(parent->IndexOf(node));
}
// no parent -- get the index and start the path
int32 index = fNodes.IndexOf(node);
_path.Clear();
return index >= 0 && _path.AddComponent(index);
}
// #pragma mark - VariablesView
VariablesView::VariablesView(Listener* listener)
:
BGroupView(B_VERTICAL),
fThread(NULL),
fStackFrame(NULL),
fVariableTable(NULL),
fVariableTableModel(NULL),
fContainerListener(NULL),
fPreviousViewState(NULL),
fViewStateHistory(NULL),
fExpressions(NULL),
fExpressionChildren(10, false),
fTableCellContextMenuTracker(NULL),
fPendingTypecastInfo(NULL),
fTemporaryExpression(NULL),
fFrameClearPending(false),
fEditWindow(NULL),
fListener(listener)
{
SetName("Variables");
}
VariablesView::~VariablesView()
{
if (fEditWindow != NULL)
BMessenger(fEditWindow).SendMessage(B_QUIT_REQUESTED);
SetStackFrame(NULL, NULL);
fVariableTable->SetTreeTableModel(NULL);
if (fPreviousViewState != NULL)
fPreviousViewState->ReleaseReference();
delete fViewStateHistory;
if (fVariableTableModel != NULL) {
fVariableTableModel->SetContainerListener(NULL);
delete fVariableTableModel;
}
delete fContainerListener;
if (fPendingTypecastInfo != NULL)
fPendingTypecastInfo->ReleaseReference();
if (fTemporaryExpression != NULL)
fTemporaryExpression->ReleaseReference();
if (fExpressions != NULL) {
ExpressionInfoEntry* entry = fExpressions->Clear();
while (entry != NULL) {
ExpressionInfoEntry* next = entry->next;
delete entry;
entry = next;
}
}
delete fExpressions;
}
/*static*/ VariablesView*
VariablesView::Create(Listener* listener, ValueNodeManager* manager)
{
VariablesView* self = new VariablesView(listener);
try {
self->_Init(manager);
} catch (...) {
delete self;
throw;
}
return self;
}
void
VariablesView::SetStackFrame(::Thread* thread, StackFrame* stackFrame)
{
bool updateValues = fFrameClearPending;
// We only want to save previous values if we've continued
// execution (i.e. thread/frame are being cleared).
// Otherwise, we'll overwrite our previous values simply
// by switching frames within the same stack trace, which isn't
// desired behavior.
fFrameClearPending = false;
if (thread == fThread && stackFrame == fStackFrame)
return;
_SaveViewState(updateValues);
_FinishContextMenu(true);
for (int32 i = 0; i < fExpressionChildren.CountItems(); i++)
fExpressionChildren.ItemAt(i)->ReleaseReference();
fExpressionChildren.MakeEmpty();
if (fThread != NULL)
fThread->ReleaseReference();
if (fStackFrame != NULL)
fStackFrame->ReleaseReference();
fThread = thread;
fStackFrame = stackFrame;
if (fThread != NULL)
fThread->AcquireReference();
if (fStackFrame != NULL)
fStackFrame->AcquireReference();
fVariableTableModel->SetStackFrame(fThread, fStackFrame);
// request loading the parameter and variable values
if (fThread != NULL && fStackFrame != NULL) {
AutoLocker<Team> locker(fThread->GetTeam());
void* root = fVariableTableModel->Root();
int32 count = fVariableTableModel->CountChildren(root);
for (int32 i = 0; i < count; i++) {
ModelNode* node = (ModelNode*)fVariableTableModel->ChildAt(root, i);
_RequestNodeValue(node);
}
_RestoreExpressionNodes();
}
_RestoreViewState();
}
void
VariablesView::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_SHOW_INSPECTOR_WINDOW:
{
// TODO: it'd probably be more ideal to extend the context
// action mechanism to allow one to specify an explicit
// target for each action rather than them all defaulting
// to targetting here.
Looper()->PostMessage(message);
break;
}
case MSG_SHOW_VARIABLE_EDIT_WINDOW:
{
if (fEditWindow != NULL)
fEditWindow->Activate();
else {
ModelNode* node = NULL;
if (message->FindPointer("node", reinterpret_cast<void**>(
&node)) != B_OK) {
break;
}
Value* value = NULL;
if (message->FindPointer("value", reinterpret_cast<void**>(
&value)) != B_OK) {
break;
}
_HandleEditVariableRequest(node, value);
}
break;
}
case MSG_VARIABLE_EDIT_WINDOW_CLOSED:
{
fEditWindow = NULL;
break;
}
case MSG_WRITE_VARIABLE_VALUE:
{
Value* value = NULL;
if (message->FindPointer("value", reinterpret_cast<void**>(
&value)) != B_OK) {
break;
}
BReference<Value> valueReference(value, true);
ValueNode* node = NULL;
if (message->FindPointer("node", reinterpret_cast<void**>(
&node)) != B_OK) {
break;
}
fListener->ValueNodeWriteRequested(node,
fStackFrame->GetCpuState(), value);
break;
}
case MSG_SHOW_TYPECAST_NODE_PROMPT:
{
BMessage* promptMessage = new(std::nothrow) BMessage(
MSG_TYPECAST_NODE);
if (promptMessage == NULL)
return;
ObjectDeleter<BMessage> messageDeleter(promptMessage);
promptMessage->AddPointer("node", fVariableTable
->SelectionModel()->NodeAt(0));
PromptWindow* promptWindow = new(std::nothrow) PromptWindow(
"Specify Type", "Type: ", NULL, BMessenger(this),
promptMessage);
if (promptWindow == NULL)
return;
messageDeleter.Detach();
promptWindow->CenterOnScreen();
promptWindow->Show();
break;
}
case MSG_TYPECAST_NODE:
{
ModelNode* node = NULL;
if (message->FindPointer("node", reinterpret_cast<void **>(&node))
!= B_OK) {
break;
}
BString typeExpression;
if (message->FindString("text", &typeExpression) == B_OK) {
if (typeExpression.IsEmpty())
break;
if (fPendingTypecastInfo != NULL)
fPendingTypecastInfo->ReleaseReference();
fPendingTypecastInfo = new(std::nothrow)
VariablesExpressionInfo(typeExpression, node);
if (fPendingTypecastInfo == NULL) {
// TODO: notify user
break;
}
fPendingTypecastInfo->AddListener(this);
fListener->ExpressionEvaluationRequested(fPendingTypecastInfo,
fStackFrame, fThread);
}
break;
}
case MSG_TYPECAST_TO_ARRAY:
{
ModelNode* node = NULL;
if (message->FindPointer("node", reinterpret_cast<void **>(&node))
!= B_OK) {
break;
}
Type* baseType = dynamic_cast<AddressType*>(node->NodeChild()
->Node()->GetType())->BaseType();
ArrayType* arrayType = NULL;
if (baseType->CreateDerivedArrayType(0, kMaxArrayElementCount,
false, arrayType) != B_OK) {
break;
}
AddressType* addressType = NULL;
BReference<Type> typeRef(arrayType, true);
if (arrayType->CreateDerivedAddressType(DERIVED_TYPE_POINTER,
addressType) != B_OK) {
break;
}
typeRef.Detach();
typeRef.SetTo(addressType, true);
ValueNode* valueNode = NULL;
if (TypeHandlerRoster::Default()->CreateValueNode(
node->NodeChild(), addressType, NULL, valueNode) != B_OK) {
break;
}
typeRef.Detach();
node->NodeChild()->SetNode(valueNode);
node->SetCastedType(addressType);
fVariableTableModel->NotifyNodeChanged(node);
break;
}
case MSG_SHOW_CONTAINER_RANGE_PROMPT:
{
ModelNode* node = (ModelNode*)fVariableTable
->SelectionModel()->NodeAt(0);
int32 lowerBound, upperBound;
ValueNode* valueNode = node->NodeChild()->Node();
if (!valueNode->IsRangedContainer()) {
valueNode = node->ChildAt(0)->NodeChild()->Node();
if (!valueNode->IsRangedContainer())
break;
}
bool fixedRange = valueNode->IsContainerRangeFixed();
if (valueNode->SupportedChildRange(lowerBound, upperBound)
!= B_OK) {
break;
}
BMessage* promptMessage = new(std::nothrow) BMessage(
MSG_SET_CONTAINER_RANGE);
if (promptMessage == NULL)
break;
ObjectDeleter<BMessage> messageDeleter(promptMessage);
promptMessage->AddPointer("node", node);
promptMessage->AddBool("fixedRange", fixedRange);
BString infoText;
if (fixedRange) {
infoText.SetToFormat("Allowed range: %" B_PRId32
"-%" B_PRId32 ".", lowerBound, upperBound);
} else {
infoText.SetToFormat("Current range: %" B_PRId32
"-%" B_PRId32 ".", lowerBound, upperBound);
}
PromptWindow* promptWindow = new(std::nothrow) PromptWindow(
"Set Range", "Range: ", infoText.String(), BMessenger(this),
promptMessage);
if (promptWindow == NULL)
return;
messageDeleter.Detach();
promptWindow->CenterOnScreen();
promptWindow->Show();
break;
}
case MSG_SET_CONTAINER_RANGE:
{
ModelNode* node = (ModelNode*)fVariableTable
->SelectionModel()->NodeAt(0);
int32 lowerBound, upperBound;
ValueNode* valueNode = node->NodeChild()->Node();
if (!valueNode->IsRangedContainer())
valueNode = node->ChildAt(0)->NodeChild()->Node();
if (valueNode->SupportedChildRange(lowerBound, upperBound) != B_OK)
break;
bool fixedRange = message->FindBool("fixedRange");
BString rangeExpression = message->FindString("text");
if (rangeExpression.Length() == 0)
break;
RangeList ranges;
status_t result = UiUtils::ParseRangeExpression(
rangeExpression, lowerBound, upperBound, fixedRange, ranges);
if (result != B_OK)
break;
valueNode->ClearChildren();
for (int32 i = 0; i < ranges.CountRanges(); i++) {
const Range* range = ranges.RangeAt(i);
result = valueNode->CreateChildrenInRange(
fThread->GetTeam()->GetTeamTypeInformation(),
range->lowerBound, range->upperBound);
if (result != B_OK)
break;
}
break;
}
case MSG_SHOW_WATCH_VARIABLE_PROMPT:
{
ModelNode* node = reinterpret_cast<ModelNode*>(
fVariableTable->SelectionModel()->NodeAt(0));
ValueLocation* location = node->NodeChild()->Location();
ValuePieceLocation piece = location->PieceAt(0);
if (piece.type != VALUE_PIECE_LOCATION_MEMORY)
break;
BMessage looperMessage(*message);
looperMessage.AddUInt64("address", piece.address);
looperMessage.AddInt32("length", piece.size);
looperMessage.AddUInt32("type", B_DATA_READ_WRITE_WATCHPOINT);
Looper()->PostMessage(&looperMessage);
break;
}
case MSG_ADD_WATCH_EXPRESSION:
{
BMessage looperMessage(MSG_SHOW_EXPRESSION_PROMPT_WINDOW);
looperMessage.AddPointer("target", this);
Looper()->PostMessage(&looperMessage);
break;
}
case MSG_REMOVE_WATCH_EXPRESSION:
{
ModelNode* node;
if (message->FindPointer("node", reinterpret_cast<void**>(&node))
!= B_OK) {
break;
}
_RemoveExpression(node);
break;
}
case MSG_ADD_NEW_EXPRESSION:
{
const char* expression;
if (message->FindString("expression", &expression) != B_OK)
break;
bool persistentExpression = message->FindBool("persistent");
ExpressionInfo* info;
status_t error = _AddExpression(expression, persistentExpression,
info);
if (error != B_OK) {
// TODO: notify user of failure
break;
}
fListener->ExpressionEvaluationRequested(info, fStackFrame,
fThread);
break;
}
case MSG_EXPRESSION_EVALUATED:
{
ExpressionInfo* info;
status_t result;
ExpressionResult* value = NULL;
if (message->FindPointer("info",
reinterpret_cast<void**>(&info)) != B_OK
|| message->FindInt32("result", &result) != B_OK) {
break;
}
BReference<ExpressionResult> valueReference;
if (message->FindPointer("value", reinterpret_cast<void**>(&value))
== B_OK) {
valueReference.SetTo(value, true);
}
VariablesExpressionInfo* variableInfo
= dynamic_cast<VariablesExpressionInfo*>(info);
if (variableInfo != NULL) {
if (fPendingTypecastInfo == variableInfo) {
_HandleTypecastResult(result, value);
fPendingTypecastInfo->ReleaseReference();
fPendingTypecastInfo = NULL;
}
} else {
_AddExpressionNode(info, result, value);
if (info == fTemporaryExpression) {
info->ReleaseReference();
fTemporaryExpression = NULL;
}
}
break;
}
case MSG_USE_AUTOMATIC_HANDLER:
case MSG_USE_EXPLICIT_HANDLER:
{
TypeHandler* handler = NULL;
ModelNode* node = NULL;
if (message->FindPointer("node", reinterpret_cast<void **>(&node))
!= B_OK) {
break;
}
if (message->what == MSG_USE_EXPLICIT_HANDLER
&& message->FindPointer("handler", reinterpret_cast<void**>(
&handler)) != B_OK) {
break;
}
ValueNode* newNode;
ValueNodeChild* child = node->NodeChild();
if (TypeHandlerRoster::Default()->CreateValueNode(child,
child->GetType(), handler, newNode) != B_OK) {
return;
}
node->SetTypeHandler(handler);
child->SetNode(newNode);
_RequestNodeValue(node);
break;
}
case MSG_VALUE_NODE_CHANGED:
{
ValueNodeChild* nodeChild;
ValueNode* oldNode;
ValueNode* newNode;
if (message->FindPointer("nodeChild", (void**)&nodeChild) == B_OK
&& message->FindPointer("oldNode", (void**)&oldNode) == B_OK
&& message->FindPointer("newNode", (void**)&newNode) == B_OK) {
BReference<ValueNodeChild> nodeChildReference(nodeChild, true);
BReference<ValueNode> oldNodeReference(oldNode, true);
BReference<ValueNode> newNodeReference(newNode, true);
fVariableTableModel->ValueNodeChanged(nodeChild, oldNode,
newNode);
}
break;
}
case MSG_VALUE_NODE_CHILDREN_CREATED:
{
ValueNode* node;
if (message->FindPointer("node", (void**)&node) == B_OK) {
BReference<ValueNode> newNodeReference(node, true);
fVariableTableModel->ValueNodeChildrenCreated(node);
}
break;
}
case MSG_VALUE_NODE_CHILDREN_DELETED:
{
ValueNode* node;
if (message->FindPointer("node", (void**)&node) == B_OK) {
BReference<ValueNode> newNodeReference(node, true);
fVariableTableModel->ValueNodeChildrenDeleted(node);
}
break;
}
case MSG_VALUE_NODE_VALUE_CHANGED:
{
ValueNode* node;
if (message->FindPointer("node", (void**)&node) == B_OK) {
BReference<ValueNode> newNodeReference(node, true);
fVariableTableModel->ValueNodeValueChanged(node);
}
break;
}
case MSG_RESTORE_PARTIAL_VIEW_STATE:
{
ModelNode* node;
if (message->FindPointer("node", (void**)&node) == B_OK) {
BReference<ModelNode> nodeReference(node, true);
TreeTablePath path;
if (fVariableTableModel->GetTreePath(node, path)) {
FunctionID* functionID = fStackFrame->Function()
->GetFunctionID();
if (functionID == NULL)
return;
BReference<FunctionID> functionIDReference(functionID,
true);
VariablesViewState* viewState = fViewStateHistory
->GetState(fThread->ID(), functionID);
if (viewState != NULL) {
_ApplyViewStateDescendentNodeInfos(viewState, node,
path);
}
}
}
break;
}
case MSG_VALUE_NODE_NEEDS_VALUE:
case MSG_MODEL_NODE_HIDDEN:
{
ModelNode* node;
if (message->FindPointer("node", (void**)&node) == B_OK) {
BReference<ModelNode> modelNodeReference(node, true);
_RequestNodeValue(node);
}
break;
}
case MSG_VARIABLES_VIEW_CONTEXT_MENU_DONE:
{
_FinishContextMenu(false);
break;
}
case MSG_VARIABLES_VIEW_NODE_SETTINGS_CHANGED:
{
ModelNode* node;
if (message->FindPointer("node", (void**)&node) != B_OK)
break;
BReference<ModelNode> nodeReference(node, true);
fVariableTableModel->NotifyNodeChanged(node);
break;
}
case B_COPY:
{
_CopyVariableValueToClipboard();
break;
}
default:
BGroupView::MessageReceived(message);
break;
}
}
void
VariablesView::DetachedFromWindow()
{
_FinishContextMenu(true);
}
void
VariablesView::LoadSettings(const BMessage& settings)
{
BMessage tableSettings;
if (settings.FindMessage("variableTable", &tableSettings) == B_OK) {
GuiSettingsUtils::UnarchiveTableSettings(tableSettings,
fVariableTable);
}
}
status_t
VariablesView::SaveSettings(BMessage& settings)
{
settings.MakeEmpty();
BMessage tableSettings;
status_t result = GuiSettingsUtils::ArchiveTableSettings(tableSettings,
fVariableTable);
if (result == B_OK)
result = settings.AddMessage("variableTable", &tableSettings);
return result;
}
void
VariablesView::SetStackFrameClearPending()
{
fFrameClearPending = true;
}
void
VariablesView::TreeTableNodeExpandedChanged(TreeTable* table,
const TreeTablePath& path, bool expanded)
{
if (fFrameClearPending)
return;
if (expanded) {
ModelNode* node = (ModelNode*)fVariableTableModel->NodeForPath(path);
if (node == NULL)
return;
fVariableTableModel->NodeExpanded(node);
// request the values of all children that don't have any yet
// If the node only has a hidden child, directly load the child's
// children's values.
if (node->CountChildren() == 1) {
ModelNode* child = node->ChildAt(0);
if (child->IsHidden())
node = child;
}
// request the values
for (int32 i = 0; ModelNode* child = node->ChildAt(i); i++) {
if (child->IsPresentationNode())
continue;
_RequestNodeValue(child);
}
}
}
void
VariablesView::TreeTableNodeInvoked(TreeTable* table,
const TreeTablePath& path)
{
ModelNode* node = (ModelNode*)fVariableTableModel->NodeForPath(path);
if (node == NULL)
return;
ValueNodeChild* child = node->NodeChild();
if (child->LocationResolutionState() != B_OK)
return;
ValueLocation* location = child->Location();
if (!location->IsWritable())
return;
Value* value = node->GetValue();
if (value == NULL)
return;
BMessage message(MSG_SHOW_VARIABLE_EDIT_WINDOW);
message.AddPointer("node", node);
message.AddPointer("value", value);
BMessenger(this).SendMessage(&message);
}
void
VariablesView::TreeTableCellMouseDown(TreeTable* table,
const TreeTablePath& path, int32 columnIndex, BPoint screenWhere,
uint32 buttons)
{
if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0)
return;
if (fFrameClearPending)
return;
_FinishContextMenu(true);
ModelNode* node = (ModelNode*)fVariableTableModel->NodeForPath(path);
if (node == NULL)
return;
Settings* settings = NULL;
SettingsMenu* settingsMenu = NULL;
BReference<SettingsMenu> settingsMenuReference;
status_t error = B_OK;
TableCellValueRenderer* cellRenderer = node->TableCellRenderer();
if (cellRenderer != NULL) {
settings = cellRenderer->GetSettings();
if (settings != NULL) {
error = node->GetValueHandler()
->CreateTableCellValueSettingsMenu(node->GetValue(), settings,
settingsMenu);
settingsMenuReference.SetTo(settingsMenu, true);
if (error != B_OK)
return;
}
}
TableCellContextMenuTracker* tracker = new(std::nothrow)
TableCellContextMenuTracker(node, Looper(), this);
BReference<TableCellContextMenuTracker> trackerReference(tracker);
ContextActionList* preActionList;
ContextActionList* postActionList;
error = _GetContextActionsForNode(node, preActionList, postActionList);
if (error != B_OK)
return;
BPrivate::ObjectDeleter<ContextActionList> preActionListDeleter(
preActionList);
BPrivate::ObjectDeleter<ContextActionList> postActionListDeleter(
postActionList);
if (tracker == NULL || tracker->Init(settings, settingsMenu, preActionList,
postActionList) != B_OK) {
return;
}
fTableCellContextMenuTracker = trackerReference.Detach();
fTableCellContextMenuTracker->ShowMenu(screenWhere);
}
void
VariablesView::ExpressionEvaluated(ExpressionInfo* info, status_t result,
ExpressionResult* value)
{
BMessage message(MSG_EXPRESSION_EVALUATED);
message.AddPointer("info", info);
message.AddInt32("result", result);
BReference<ExpressionResult> valueReference;
if (value != NULL) {
valueReference.SetTo(value);
message.AddPointer("value", value);
}
if (BMessenger(this).SendMessage(&message) == B_OK)
valueReference.Detach();
}
void
VariablesView::_Init(ValueNodeManager* manager)
{
fVariableTable = new TreeTable("variable list", 0, B_FANCY_BORDER);
AddChild(fVariableTable->ToView());
fVariableTable->SetSortingEnabled(false);
// columns
fVariableTable->AddColumn(new StringTableColumn(0, "Variable", 80, 40, 1000,
B_TRUNCATE_END, B_ALIGN_LEFT));
fVariableTable->AddColumn(new VariableValueColumn(1, "Value", 80, 40, 1000,
B_TRUNCATE_END, B_ALIGN_RIGHT));
fVariableTable->AddColumn(new StringTableColumn(2, "Type", 80, 40, 1000,
B_TRUNCATE_END, B_ALIGN_LEFT));
fVariableTableModel = new VariableTableModel(manager);
if (fVariableTableModel->Init() != B_OK)
throw std::bad_alloc();
fVariableTable->SetTreeTableModel(fVariableTableModel);
fVariableTable->SetToolTipProvider(fVariableTableModel);
fContainerListener = new ContainerListener(this);
fVariableTableModel->SetContainerListener(fContainerListener);
fVariableTable->AddTreeTableListener(this);
fViewStateHistory = new VariablesViewStateHistory;
if (fViewStateHistory->Init() != B_OK)
throw std::bad_alloc();
fExpressions = new ExpressionInfoTable();
if (fExpressions->Init() != B_OK)
throw std::bad_alloc();
}
void
VariablesView::_RequestNodeValue(ModelNode* node)
{
// get the node child and its container
ValueNodeChild* nodeChild = node->NodeChild();
ValueNodeContainer* container = nodeChild->Container();
BReference<ValueNodeContainer> containerReference(container);
AutoLocker<ValueNodeContainer> containerLocker(container);
if (container == NULL || nodeChild->Container() != container)
return;
// get the value node and check whether its value has not yet been resolved
ValueNode* valueNode = nodeChild->Node();
if (valueNode == NULL) {
ModelNode* parent = node->Parent();
if (parent != NULL) {
TreeTablePath path;
if (!fVariableTableModel->GetTreePath(parent, path))
return;
// if the parent node was already expanded when the child was
// added, we may not yet have added a value node.
// Notify the table model that this needs to be done.
if (fVariableTable->IsNodeExpanded(path))
fVariableTableModel->NodeExpanded(parent);
}
}
if (valueNode == NULL || valueNode->LocationAndValueResolutionState()
!= VALUE_NODE_UNRESOLVED) {
return;
}
BReference<ValueNode> valueNodeReference(valueNode);
containerLocker.Unlock();
// request resolution of the value
fListener->ValueNodeValueRequested(fStackFrame != NULL
? fStackFrame->GetCpuState() : NULL, container, valueNode);
}
status_t
VariablesView::_GetContextActionsForNode(ModelNode* node,
ContextActionList*& _preActions, ContextActionList*& _postActions)
{
_preActions = NULL;
_postActions = NULL;
ValueLocation* location = node->NodeChild()->Location();
_preActions = new(std::nothrow) ContextActionList;
if (_preActions == NULL)
return B_NO_MEMORY;
BPrivate::ObjectDeleter<ContextActionList> preActionListDeleter(
_preActions);
status_t result = B_OK;
BMessage* message = NULL;
// only show the Inspect option if the value is in fact located
// in memory.
if (location != NULL) {
if (location->PieceAt(0).type == VALUE_PIECE_LOCATION_MEMORY) {
result = _AddContextAction("Inspect", MSG_SHOW_INSPECTOR_WINDOW,
_preActions, message);
if (result != B_OK)
return result;
message->AddUInt64("address", location->PieceAt(0).address);
}
ValueNodeChild* child = node->NodeChild();
ValueNode* valueNode = child->Node();
result = _AddTypeHandlerMenuIfNeeded(node, _preActions);
if (result != B_OK)
return result;
if (valueNode != NULL) {
Value* value = valueNode->GetValue();
if (location->IsWritable() && value != NULL) {
result = _AddContextAction("Edit" B_UTF8_ELLIPSIS,
MSG_SHOW_VARIABLE_EDIT_WINDOW, _preActions, message);
if (result != B_OK)
return result;
message->AddPointer("node", node);
message->AddPointer("value", value);
}
AddressType* type = dynamic_cast<AddressType*>(valueNode->GetType());
if (type != NULL && type->BaseType() != NULL) {
result = _AddContextAction("Cast to array", MSG_TYPECAST_TO_ARRAY,
_preActions, message);
if (result != B_OK)
return result;
message->AddPointer("node", node);
}
}
result = _AddContextAction("Cast as" B_UTF8_ELLIPSIS,
MSG_SHOW_TYPECAST_NODE_PROMPT, _preActions, message);
if (result != B_OK)
return result;
result = _AddContextAction("Watch" B_UTF8_ELLIPSIS,
MSG_SHOW_WATCH_VARIABLE_PROMPT, _preActions, message);
if (result != B_OK)
return result;
if (valueNode == NULL)
return B_OK;
if (valueNode->LocationAndValueResolutionState() == B_OK) {
result = _AddContextAction("Copy Value", B_COPY, _preActions, message);
if (result != B_OK)
return result;
}
bool addRangedContainerItem = false;
// if the current node isn't itself a ranged container, check if it
// contains a hidden node which is, since in the latter case we
// want to present the range selection as well.
if (valueNode->IsRangedContainer())
addRangedContainerItem = true;
else if (node->CountChildren() == 1 && node->ChildAt(0)->IsHidden()) {
valueNode = node->ChildAt(0)->NodeChild()->Node();
if (valueNode != NULL && valueNode->IsRangedContainer())
addRangedContainerItem = true;
}
if (addRangedContainerItem) {
result = _AddContextAction("Set visible range" B_UTF8_ELLIPSIS,
MSG_SHOW_CONTAINER_RANGE_PROMPT, _preActions, message);
if (result != B_OK)
return result;
}
}
_postActions = new(std::nothrow) ContextActionList;
if (_postActions == NULL)
return B_NO_MEMORY;
BPrivate::ObjectDeleter<ContextActionList> postActionListDeleter(
_postActions);
result = _AddContextAction("Add watch expression" B_UTF8_ELLIPSIS,
MSG_ADD_WATCH_EXPRESSION, _postActions, message);
if (result != B_OK)
return result;
if (fExpressionChildren.HasItem(node->NodeChild())) {
result = _AddContextAction("Remove watch expression",
MSG_REMOVE_WATCH_EXPRESSION, _postActions, message);
if (result != B_OK)
return result;
message->AddPointer("node", node);
}
preActionListDeleter.Detach();
postActionListDeleter.Detach();
return B_OK;
}
status_t
VariablesView::_AddContextAction(const char* action, uint32 what,
ContextActionList* actions, BMessage*& _message)
{
ActionMenuItem* item = NULL;
status_t result = _CreateContextAction(action, what, item);
if (result != B_OK)
return result;
ObjectDeleter<ActionMenuItem> actionDeleter(item);
if (!actions->AddItem(item))
return B_NO_MEMORY;
actionDeleter.Detach();
_message = item->Message();
return B_OK;
}
status_t
VariablesView::_CreateContextAction(const char* action, uint32 what,
ActionMenuItem*& _item)
{
BMessage* message = new(std::nothrow) BMessage(what);
if (message == NULL)
return B_NO_MEMORY;
ObjectDeleter<BMessage> messageDeleter(message);
_item = new(std::nothrow) ActionMenuItem(action,
message);
if (_item == NULL)
return B_NO_MEMORY;
messageDeleter.Detach();
return B_OK;
}
status_t
VariablesView::_AddTypeHandlerMenuIfNeeded(ModelNode* node,
ContextActionList* actions)
{
ValueNodeChild* child = node->NodeChild();
if (node->CountChildren() == 1 && node->ChildAt(0)->IsHidden()) {
node = node->ChildAt(0);
child = node->NodeChild();
}
int32 handlerCount = TypeHandlerRoster::Default()->CountTypeHandlers(
child->GetType());
if (handlerCount > 1) {
TypeHandler* lastHandler = node->GetTypeHandler();
BMenu* handlerMenu = new(std::nothrow) BMenu("Show as");
if (handlerMenu == NULL)
return B_NO_MEMORY;
ObjectDeleter<BMenu> menuDeleter(handlerMenu);
ActionMenuItem* menuItem = new(std::nothrow) ActionMenuItem(
handlerMenu);
if (menuItem == NULL)
return B_NO_MEMORY;
ObjectDeleter<ActionMenuItem> menuItemDeleter(menuItem);
menuDeleter.Detach();
ActionMenuItem* item = NULL;
status_t result = _CreateContextAction("Automatic",
MSG_USE_AUTOMATIC_HANDLER, item);
if (item == NULL)
return B_NO_MEMORY;
item->Message()->AddPointer("node", node);
ObjectDeleter<ActionMenuItem> itemDeleter(item);
if (!handlerMenu->AddItem(item) || !handlerMenu->AddSeparatorItem())
return B_NO_MEMORY;
itemDeleter.Detach();
if (lastHandler == NULL)
item->SetMarked(true);
TypeHandlerList* handlers = NULL;
result = TypeHandlerRoster::Default()->FindTypeHandlers(child,
child->GetType(), handlers);
if (result != B_OK)
return result;
ObjectDeleter<TypeHandlerList> listDeleter(handlers);
while (handlers->CountItems() > 0) {
TypeHandler* handler = handlers->ItemAt(0);
BMessage* message = new(std::nothrow) BMessage(
MSG_USE_EXPLICIT_HANDLER);
if (message == NULL) {
result = B_NO_MEMORY;
break;
}
message->AddPointer("node", node);
TypeHandlerMenuItem* typeItem
= new(std::nothrow) TypeHandlerMenuItem(handler->Name(),
message);
if (typeItem == NULL) {
result = B_NO_MEMORY;
break;
}
ObjectDeleter<TypeHandlerMenuItem> typeItemDeleter(typeItem);
result = typeItem->SetTypeHandler(handler);
if (result != B_OK)
break;
handlers->RemoveItemAt(0);
if (!handlerMenu->AddItem(typeItem)) {
result = B_NO_MEMORY;
break;
}
typeItemDeleter.Detach();
if (handler == lastHandler)
typeItem->SetMarked(true);
}
if (result != B_OK) {
for (int32 i = 0; TypeHandler* handler = handlers->ItemAt(i);
i++) {
handler->ReleaseReference();
}
return result;
}
if (!actions->AddItem(menuItem))
return B_NO_MEMORY;
handlerMenu->SetTargetForItems(this);
menuItemDeleter.Detach();
}
return B_OK;
}
void
VariablesView::_FinishContextMenu(bool force)
{
if (fTableCellContextMenuTracker != NULL) {
if (!fTableCellContextMenuTracker->FinishMenu(force) || force) {
fTableCellContextMenuTracker->ReleaseReference();
fTableCellContextMenuTracker = NULL;
}
}
}
void
VariablesView::_SaveViewState(bool updateValues) const
{
if (fThread == NULL || fStackFrame == NULL
|| fStackFrame->Function() == NULL) {
return;
}
// get the function ID
FunctionID* functionID = fStackFrame->Function()->GetFunctionID();
if (functionID == NULL)
return;
BReference<FunctionID> functionIDReference(functionID, true);
StackFrameValues* values = NULL;
ExpressionValues* expressionValues = NULL;
BReference<StackFrameValues> valuesReference;
BReference<ExpressionValues> expressionsReference;
if (!updateValues) {
VariablesViewState* viewState = fViewStateHistory->GetState(fThread->ID(),
functionID);
if (viewState != NULL) {
values = viewState->Values();
valuesReference.SetTo(values);
expressionValues = viewState->GetExpressionValues();
expressionsReference.SetTo(expressionValues);
}
}
if (values == NULL) {
values = new(std::nothrow) StackFrameValues;
if (values == NULL)
return;
valuesReference.SetTo(values, true);
if (values->Init() != B_OK)
return;
expressionValues = new(std::nothrow) ExpressionValues;
if (expressionValues == NULL)
return;
expressionsReference.SetTo(expressionValues, true);
if (expressionValues->Init() != B_OK)
return;
}
// create an empty view state
VariablesViewState* viewState = new(std::nothrow) VariablesViewState;
if (viewState == NULL)
return;
BReference<VariablesViewState> viewStateReference(viewState, true);
if (viewState->Init() != B_OK)
return;
viewState->SetValues(values);
viewState->SetExpressionValues(expressionValues);
// populate it
TreeTablePath path;
if (_AddViewStateDescendentNodeInfos(viewState,
fVariableTableModel->Root(), path, updateValues) != B_OK) {
return;
}
// add the view state to the history
fViewStateHistory->SetState(fThread->ID(), functionID, viewState);
}
void
VariablesView::_RestoreViewState()
{
if (fPreviousViewState != NULL) {
fPreviousViewState->ReleaseReference();
fPreviousViewState = NULL;
}
if (fThread == NULL || fStackFrame == NULL
|| fStackFrame->Function() == NULL) {
return;
}
// get the function ID
FunctionID* functionID = fStackFrame->Function()->GetFunctionID();
if (functionID == NULL)
return;
BReference<FunctionID> functionIDReference(functionID, true);
// get the previous view state
VariablesViewState* viewState = fViewStateHistory->GetState(fThread->ID(),
functionID);
if (viewState == NULL)
return;
// apply the view state
TreeTablePath path;
_ApplyViewStateDescendentNodeInfos(viewState, fVariableTableModel->Root(),
path);
}
status_t
VariablesView::_AddViewStateDescendentNodeInfos(VariablesViewState* viewState,
void* parent, TreeTablePath& path, bool updateValues) const
{
bool isRoot = parent == fVariableTableModel->Root();
int32 childCount = isRoot ? fVariableTableModel->CountChildren(parent)
: ((ModelNode*)parent)->CountChildren();
for (int32 i = 0; i < childCount; i++) {
ModelNode* node = (ModelNode*)(isRoot ? fVariableTableModel->ChildAt(
parent, i)
: ((ModelNode*)parent)->ChildAt(i));
if (!path.AddComponent(i))
return B_NO_MEMORY;
// add the node's info
VariablesViewNodeInfo nodeInfo;
nodeInfo.SetNodeExpanded(fVariableTable->IsNodeExpanded(path));
nodeInfo.SetCastedType(node->GetCastedType());
nodeInfo.SetTypeHandler(node->GetTypeHandler());
TableCellValueRenderer* renderer = node->TableCellRenderer();
if (renderer != NULL) {
Settings* settings = renderer->GetSettings();
if (settings != NULL)
nodeInfo.SetRendererSettings(settings->Message());
}
Value* value = node->GetValue();
Variable* variable = node->GetVariable();
TypeComponentPath* componentPath = node->GetPath();
ObjectID* id = variable->ID();
status_t error = viewState->SetNodeInfo(id, componentPath, nodeInfo);
if (error != B_OK)
return error;
if (value != NULL && updateValues) {
BVariant variableValueData;
if (value->ToVariant(variableValueData))
error = viewState->Values()->SetValue(id, componentPath,
variableValueData);
if (error != B_OK)
return error;
}
// recurse
error = _AddViewStateDescendentNodeInfos(viewState, node, path,
updateValues);
if (error != B_OK)
return error;
path.RemoveLastComponent();
}
return B_OK;
}
status_t
VariablesView::_ApplyViewStateDescendentNodeInfos(VariablesViewState* viewState,
void* parent, TreeTablePath& path)
{
bool isRoot = parent == fVariableTableModel->Root();
int32 childCount = isRoot ? fVariableTableModel->CountChildren(parent)
: ((ModelNode*)parent)->CountChildren();
for (int32 i = 0; i < childCount; i++) {
ModelNode* node = (ModelNode*)(isRoot ? fVariableTableModel->ChildAt(
parent, i)
: ((ModelNode*)parent)->ChildAt(i));
if (!path.AddComponent(i))
return B_NO_MEMORY;
// apply the node's info, if any
ObjectID* objectID = node->GetVariable()->ID();
TypeComponentPath* componentPath = node->GetPath();
const VariablesViewNodeInfo* nodeInfo = viewState->GetNodeInfo(
objectID, componentPath);
if (nodeInfo != NULL) {
// NB: if the node info indicates that the node in question
// was being cast to a different type, this *must* be applied
// before any other view state restoration, since it
// potentially changes the child hierarchy under that node.
Type* type = nodeInfo->GetCastedType();
TypeHandler* handler = nodeInfo->GetTypeHandler();
node->SetCastedType(type);
node->SetTypeHandler(handler);
if (type != NULL || handler != NULL) {
if (type == NULL)
type = node->GetType();
ValueNode* valueNode = NULL;
if (TypeHandlerRoster::Default()->CreateValueNode(
node->NodeChild(), type, handler, valueNode) == B_OK) {
node->NodeChild()->SetNode(valueNode);
}
}
// we don't have a renderer yet so we can't apply the settings
// at this stage. Store them on the model node so we can lazily
// apply them once the value is retrieved.
node->SetLastRendererSettings(nodeInfo->GetRendererSettings());
fVariableTable->SetNodeExpanded(path,
nodeInfo->IsNodeExpanded());
BVariant previousValue;
if (viewState->Values()->GetValue(objectID, componentPath,
previousValue)) {
node->SetPreviousValue(previousValue);
}
}
// recurse
status_t error = _ApplyViewStateDescendentNodeInfos(viewState, node,
path);
if (error != B_OK)
return error;
path.RemoveLastComponent();
}
return B_OK;
}
void
VariablesView::_CopyVariableValueToClipboard()
{
ModelNode* node = reinterpret_cast<ModelNode*>(
fVariableTable->SelectionModel()->NodeAt(0));
Value* value = node->GetValue();
BString valueData;
if (value != NULL && value->ToString(valueData)) {
be_clipboard->Lock();
be_clipboard->Data()->RemoveData("text/plain");
be_clipboard->Data()->AddData ("text/plain",
B_MIME_TYPE, valueData.String(),
valueData.Length());
be_clipboard->Commit();
be_clipboard->Unlock();
}
}
status_t
VariablesView::_AddExpression(const char* expression,
bool persistentExpression, ExpressionInfo*& _info)
{
ExpressionInfoEntry* entry = NULL;
if (persistentExpression) {
// if our stack frame doesn't have an associated function,
// we can't add an expression
FunctionInstance* function = fStackFrame->Function();
if (function == NULL)
return B_NOT_ALLOWED;
FunctionID* id = function->GetFunctionID();
if (id == NULL)
return B_NO_MEMORY;
BReference<FunctionID> idReference(id, true);
entry = fExpressions->Lookup(FunctionKey(id));
if (entry == NULL) {
entry = new(std::nothrow) ExpressionInfoEntry(id);
if (entry == NULL)
return B_NO_MEMORY;
status_t error = fExpressions->Insert(entry);
if (error != B_OK) {
delete entry;
return error;
}
}
}
ExpressionInfo* info = new(std::nothrow) ExpressionInfo(expression);
if (info == NULL)
return B_NO_MEMORY;
BReference<ExpressionInfo> infoReference(info, true);
if (persistentExpression) {
if (!entry->AddItem(info))
return B_NO_MEMORY;
} else
fTemporaryExpression = info;
info->AddListener(this);
infoReference.Detach();
_info = info;
return B_OK;
}
void
VariablesView::_RemoveExpression(ModelNode* node)
{
if (!fExpressionChildren.HasItem(node->NodeChild()))
return;
FunctionID* id = fStackFrame->Function()->GetFunctionID();
BReference<FunctionID> idReference(id, true);
ExpressionInfoEntry* entry = fExpressions->Lookup(FunctionKey(id));
if (entry == NULL)
return;
for (int32 i = 0; i < entry->CountItems(); i++) {
ExpressionInfo* info = entry->ItemAt(i);
if (info->Expression() == node->Name()) {
entry->RemoveItemAt(i);
info->RemoveListener(this);
info->ReleaseReference();
break;
}
}
fVariableTableModel->RemoveSyntheticNode(node);
}
void
VariablesView::_RestoreExpressionNodes()
{
FunctionInstance* instance = fStackFrame->Function();
if (instance == NULL)
return;
FunctionID* id = instance->GetFunctionID();
if (id == NULL)
return;
BReference<FunctionID> idReference(id, true);
ExpressionInfoEntry* entry = fExpressions->Lookup(FunctionKey(id));
if (entry == NULL)
return;
for (int32 i = 0; i < entry->CountItems(); i++) {
ExpressionInfo* info = entry->ItemAt(i);
fListener->ExpressionEvaluationRequested(info, fStackFrame, fThread);
}
}
void
VariablesView::_AddExpressionNode(ExpressionInfo* info, status_t result,
ExpressionResult* value)
{
bool temporaryExpression = (info == fTemporaryExpression);
Variable* variable = NULL;
BReference<Variable> variableReference;
BVariant valueData;
ExpressionVariableID* id
= new(std::nothrow) ExpressionVariableID(info);
if (id == NULL)
return;
BReference<ObjectID> idReference(id, true);
Type* type = NULL;
ValueLocation* location = NULL;
ValueNodeChild* child = NULL;
BReference<Type> typeReference;
BReference<ValueLocation> locationReference;
if (value->Kind() == EXPRESSION_RESULT_KIND_PRIMITIVE) {
value->PrimitiveValue()->ToVariant(valueData);
if (_GetTypeForTypeCode(valueData.Type(), type) != B_OK)
return;
typeReference.SetTo(type, true);
location = new(std::nothrow) ValueLocation();
if (location == NULL)
return;
locationReference.SetTo(location, true);
if (valueData.IsNumber()) {
ValuePieceLocation piece;
if (!piece.SetToValue(valueData.Bytes(), valueData.Size())
|| !location->AddPiece(piece)) {
return;
}
}
} else if (value->Kind() == EXPRESSION_RESULT_KIND_VALUE_NODE) {
child = value->ValueNodeValue();
type = child->GetType();
typeReference.SetTo(type);
location = child->Location();
locationReference.SetTo(location);
}
variable = new(std::nothrow) Variable(id,
info->Expression(), type, location);
if (variable == NULL)
return;
variableReference.SetTo(variable, true);
status_t error = fVariableTableModel->AddSyntheticNode(variable, child,
info->Expression());
if (error != B_OK)
return;
// In the case of either an evaluation error, or an unsupported result
// type, set an explanatory string for the result directly.
if (result != B_OK || valueData.Type() == B_STRING_TYPE) {
StringValue* explicitValue = new(std::nothrow) StringValue(
valueData.ToString());
if (explicitValue == NULL)
return;
child->Node()->SetLocationAndValue(NULL, explicitValue, B_OK);
}
if (temporaryExpression || fExpressionChildren.AddItem(child)) {
child->AcquireReference();
if (temporaryExpression)
return;
// attempt to restore our newly added node's view state,
// if applicable.
FunctionID* functionID = fStackFrame->Function()
->GetFunctionID();
if (functionID == NULL)
return;
BReference<FunctionID> functionIDReference(functionID,
true);
VariablesViewState* viewState = fViewStateHistory
->GetState(fThread->ID(), functionID);
if (viewState != NULL) {
TreeTablePath path;
_ApplyViewStateDescendentNodeInfos(viewState,
fVariableTableModel->Root(), path);
}
}
}
void
VariablesView::_HandleTypecastResult(status_t result, ExpressionResult* value)
{
BString errorMessage;
if (value == NULL) {
errorMessage.SetToFormat("Failed to evaluate expression \"%s\": %s (%"
B_PRId32 ")", fPendingTypecastInfo->Expression().String(),
strerror(result), result);
} else if (result != B_OK) {
BVariant valueData;
value->PrimitiveValue()->ToVariant(valueData);
// usually, the evaluation can give us back an error message to
// specifically indicate why it failed. If it did, simply use
// the message directly, otherwise fall back to generating an error
// message based on the error code
if (valueData.Type() == B_STRING_TYPE)
errorMessage = valueData.ToString();
else {
errorMessage.SetToFormat("Failed to evaluate expression \"%s\":"
" %s (%" B_PRId32 ")",
fPendingTypecastInfo->Expression().String(), strerror(result),
result);
}
} else if (value->Kind() != EXPRESSION_RESULT_KIND_TYPE) {
errorMessage.SetToFormat("Expression \"%s\" does not evaluate to a"
" type.", fPendingTypecastInfo->Expression().String());
}
if (!errorMessage.IsEmpty()) {
BAlert* alert = new(std::nothrow) BAlert("Typecast error",
errorMessage, "Close");
if (alert != NULL)
alert->Go();
return;
}
Type* type = value->GetType();
BReference<Type> typeRef(type);
ValueNode* valueNode = NULL;
ModelNode* node = fPendingTypecastInfo->TargetNode();
if (TypeHandlerRoster::Default()->CreateValueNode(node->NodeChild(), type,
NULL, valueNode) != B_OK) {
return;
}
node->NodeChild()->SetNode(valueNode);
node->SetCastedType(type);
fVariableTableModel->NotifyNodeChanged(node);
}
void
VariablesView::_HandleEditVariableRequest(ModelNode* node, Value* value)
{
// get a value handler
ValueHandler* valueHandler;
status_t error = ValueHandlerRoster::Default()->FindValueHandler(value,
valueHandler);
if (error != B_OK)
return;
ValueNode* valueNode = node->NodeChild()->Node();
BReference<ValueHandler> handlerReference(valueHandler, true);
TableCellValueRenderer* renderer = node->TableCellRenderer();
TableCellValueEditor* editor = NULL;
error = valueHandler->GetTableCellValueEditor(value,
renderer != NULL ? renderer->GetSettings() : NULL, editor);
if (error != B_OK || editor == NULL)
return;
BReference<TableCellValueEditor> editorReference(editor, true);
try {
fEditWindow = VariableEditWindow::Create(value, valueNode, editor,
this);
} catch (...) {
fEditWindow = NULL;
return;
}
fEditWindow->Show();
}
status_t
VariablesView::_GetTypeForTypeCode(int32 type, Type*& _resultType) const
{
if (BVariant::TypeIsNumber(type) || type == B_STRING_TYPE) {
_resultType = new(std::nothrow) SyntheticPrimitiveType(type);
if (_resultType == NULL)
return B_NO_MEMORY;
return B_OK;
}
return B_NOT_SUPPORTED;
}
// #pragma mark - Listener
VariablesView::Listener::~Listener()
{
}
↑ V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fNext.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fContextMenu.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: next.
↑ V595 The 'fContextMenu' pointer was utilized before it was verified against nullptr. Check lines: 909, 925.