/* * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2011-2014, Rene Gollent, rene@gollent.com. * Distributed under the terms of the MIT License. */ #include "VariablesView.h" #include #include #include #include #include #include #include #include #include #include #include "table/TableColumns.h" #include "ActionMenuItem.h" #include "Architecture.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 "TableCellValueRenderer.h" #include "Team.h" #include "TeamDebugInfo.h" #include "Thread.h" #include "Tracing.h" #include "TypeComponentPath.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 "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' }; // maximum number of array elements to show by default static const uint64 kMaxArrayElementCount = 10; 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; }; class VariablesView::ModelNode : public BReferenceable { public: ModelNode(ModelNode* parent, Variable* variable, ValueNodeChild* nodeChild, bool isPresentationNode) : fParent(parent), fNodeChild(nodeChild), fVariable(variable), fValue(NULL), fValueHandler(NULL), fTableCellRenderer(NULL), fLastRendererSettings(), fCastedType(NULL), fComponentPath(NULL), fIsPresentationNode(isPresentationNode), fHidden(false) { fNodeChild->AcquireReference(); } ~ModelNode() { SetTableCellRenderer(NULL); SetValueHandler(NULL); SetValue(NULL); for (int32 i = 0; ModelNode* child = fChildren.ItemAt(i); i++) child->ReleaseReference(); fNodeChild->ReleaseReference(); if (fComponentPath != NULL) fComponentPath->ReleaseReference(); if (fCastedType != NULL) fCastedType->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 fNodeChild->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(); } Type* GetCastedType() const { return fCastedType; } void SetCastedType(Type* type) { if (fCastedType != NULL) fCastedType->ReleaseReference(); fCastedType = type; if (type != NULL) fCastedType->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; } 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 ChildList; private: ModelNode* fParent; ValueNodeChild* fNodeChild; Variable* fVariable; Value* fValue; ValueHandler* fValueHandler; TableCellValueRenderer* fTableCellRenderer; BMessage fLastRendererSettings; Type* fCastedType; ChildList fChildren; TypeComponentPath* fComponentPath; bool fIsPresentationNode; bool fHidden; public: ModelNode* fNext; }; // #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(value.ToReferenceable()); if (node != NULL && node->GetValue() != NULL && node->TableCellRenderer() != NULL) { node->TableCellRenderer()->RenderValue(node->GetValue(), 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(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(); ~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); 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 NodeList; typedef BOpenHashTable 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 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( 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( 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 nodeChildReference(nodeChild); BReference oldNodeReference(oldNode); BReference 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 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 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 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 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 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 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() : fThread(NULL), fNodeManager(NULL), fContainerListener(NULL), fNodeTable() { } VariablesView::VariableTableModel::~VariableTableModel() { if (fNodeManager != NULL) fNodeManager->ReleaseReference(); } status_t VariablesView::VariableTableModel::Init() { fNodeManager = new(std::nothrow) ValueNodeManager(); if (fNodeManager == NULL) return B_NO_MEMORY; 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 containerLocker(container); for (int32 i = 0; i < container->CountChildren(); i++) { VariableValueNodeChild* child = dynamic_cast( 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 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 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 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 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 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 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 handlerReference(valueHandler, true); // create a table cell renderer for the value TableCellValueRenderer* renderer = NULL; error = valueHandler->GetTableCellValueRenderer(value, renderer); if (error != B_OK) return; // 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; Type* nodeChildRawType = node->NodeChild()->Node()->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: { Type* type = node->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 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; if (node->NodeChild()->LocationResolutionState() != B_OK) return false; BString tipData; switch (columnIndex) { case 0: { ValueLocation* location = node->NodeChild()->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", i, piece.address, piece.size); break; case VALUE_PIECE_LOCATION_REGISTER: { Architecture* architecture = fThread->GetTeam() ->GetArchitecture(); pieceData.SetToFormat("(%" B_PRId32 "): Register (%s)", i, architecture->Registers()[piece.reg].Name()); break; } default: break; } tipData += pieceData; if (i < location->CountPieces() - 1) tipData += "\n"; } 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::_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 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), fTableCellContextMenuTracker(NULL), fFrameClearPending(false), fListener(listener) { SetName("Variables"); } VariablesView::~VariablesView() { SetStackFrame(NULL, NULL); fVariableTable->SetTreeTableModel(NULL); if (fPreviousViewState != NULL) fPreviousViewState->ReleaseReference(); delete fViewStateHistory; if (fVariableTableModel != NULL) { fVariableTableModel->SetContainerListener(NULL); delete fVariableTableModel; } delete fContainerListener; } /*static*/ VariablesView* VariablesView::Create(Listener* listener) { VariablesView* self = new VariablesView(listener); try { self->_Init(); } catch (...) { delete self; throw; } return self; } void VariablesView::SetStackFrame(Thread* thread, StackFrame* stackFrame) { fFrameClearPending = false; if (thread == fThread && stackFrame == fStackFrame) return; _SaveViewState(); _FinishContextMenu(true); 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 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); } } _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_TYPECAST_NODE_PROMPT: { BMessage* promptMessage = new(std::nothrow) BMessage( MSG_TYPECAST_NODE); if (promptMessage == NULL) return; ObjectDeleter 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(&node)) != B_OK) { break; } Type* type = NULL; BString typeExpression = message->FindString("text"); if (typeExpression.Length() == 0) break; FileSourceCode* code = fStackFrame->Function()->GetFunction() ->GetSourceCode(); if (code == NULL) break; SourceLanguage* language = code->GetSourceLanguage(); if (language == NULL) break; if (language->ParseTypeExpression(typeExpression, fThread->GetTeam()->DebugInfo(), type) != B_OK) { BString errorMessage; errorMessage.SetToFormat("Failed to resolve type %s", typeExpression.String()); BAlert* alert = new(std::nothrow) BAlert("Error", errorMessage.String(), "Close"); if (alert != NULL) alert->Go(); break; } BReference typeRef(type, true); ValueNode* valueNode = NULL; if (TypeHandlerRoster::Default()->CreateValueNode( node->NodeChild(), type, valueNode) != B_OK) { break; } typeRef.Detach(); node->NodeChild()->SetNode(valueNode); node->SetCastedType(type); fVariableTableModel->NotifyNodeChanged(node); break; } case MSG_TYPECAST_TO_ARRAY: { ModelNode* node = NULL; if (message->FindPointer("node", reinterpret_cast(&node)) != B_OK) { break; } Type* baseType = dynamic_cast(node->NodeChild() ->Node()->GetType())->BaseType(); ArrayType* arrayType = NULL; if (baseType->CreateDerivedArrayType(0, kMaxArrayElementCount, false, arrayType) != B_OK) { break; } AddressType* addressType = NULL; BReference 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, 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 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( range->lowerBound, range->upperBound); if (result != B_OK) break; } break; } case MSG_SHOW_WATCH_VARIABLE_PROMPT: { ModelNode* node = reinterpret_cast( 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_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 nodeChildReference(nodeChild, true); BReference oldNodeReference(oldNode, true); BReference 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 newNodeReference(node, true); fVariableTableModel->ValueNodeChildrenCreated(node); } break; } case MSG_VALUE_NODE_CHILDREN_DELETED: { ValueNode* node; if (message->FindPointer("node", (void**)&node) == B_OK) { BReference newNodeReference(node, true); fVariableTableModel->ValueNodeChildrenDeleted(node); } break; } case MSG_VALUE_NODE_VALUE_CHANGED: { ValueNode* node; if (message->FindPointer("node", (void**)&node) == B_OK) { BReference newNodeReference(node, true); fVariableTableModel->ValueNodeValueChanged(node); } break; } case MSG_RESTORE_PARTIAL_VIEW_STATE: { ModelNode* node; if (message->FindPointer("node", (void**)&node) == B_OK) { TreeTablePath path; if (fVariableTableModel->GetTreePath(node, path)) { FunctionID* functionID = fStackFrame->Function() ->GetFunctionID(); if (functionID == NULL) return; BReference 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 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 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::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 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 trackerReference(tracker); ContextActionList* preActionList = new(std::nothrow) ContextActionList; if (preActionList == NULL) return; BPrivate::ObjectDeleter preActionListDeleter( preActionList); error = _GetContextActionsForNode(node, preActionList); if (error != B_OK) return; if (tracker == NULL || tracker->Init(settings, settingsMenu, preActionList) != B_OK) return; fTableCellContextMenuTracker = trackerReference.Detach(); fTableCellContextMenuTracker->ShowMenu(screenWhere); } void VariablesView::_Init() { 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; 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(); } void VariablesView::_RequestNodeValue(ModelNode* node) { // get the node child and its container ValueNodeChild* nodeChild = node->NodeChild(); ValueNodeContainer* container = nodeChild->Container(); BReference containerReference(container); AutoLocker 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 valueNodeReference(valueNode); containerLocker.Unlock(); // request resolution of the value fListener->ValueNodeValueRequested(fStackFrame->GetCpuState(), container, valueNode); } status_t VariablesView::_GetContextActionsForNode(ModelNode* node, ContextActionList* actions) { ValueLocation* location = node->NodeChild()->Location(); if (location == NULL) return B_OK; status_t result = B_OK; BMessage* message = NULL; // only show the Inspect option if the value is in fact located // in memory. if (location->PieceAt(0).type == VALUE_PIECE_LOCATION_MEMORY) { result = _AddContextAction("Inspect", MSG_SHOW_INSPECTOR_WINDOW, actions, message); if (result != B_OK) return result; message->AddUInt64("address", location->PieceAt(0).address); } ValueNode* valueNode = node->NodeChild()->Node(); if (valueNode != NULL) { AddressType* type = dynamic_cast(valueNode->GetType()); if (type != NULL && type->BaseType() != NULL) { result = _AddContextAction("Cast to array", MSG_TYPECAST_TO_ARRAY, actions, message); if (result != B_OK) return result; message->AddPointer("node", node); } } result = _AddContextAction("Cast as" B_UTF8_ELLIPSIS, MSG_SHOW_TYPECAST_NODE_PROMPT, actions, message); if (result != B_OK) return result; result = _AddContextAction("Watch" B_UTF8_ELLIPSIS, MSG_SHOW_WATCH_VARIABLE_PROMPT, actions, message); if (result != B_OK) return result; if (valueNode == NULL) return B_OK; if (valueNode->LocationAndValueResolutionState() == B_OK) { result = _AddContextAction("Copy Value", B_COPY, actions, message); if (result != B_OK) return result; } // 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()) { if (node->CountChildren() == 1 && node->ChildAt(0)->IsHidden()) { valueNode = node->ChildAt(0)->NodeChild()->Node(); if (valueNode == NULL || !valueNode->IsRangedContainer()) return B_OK; } else return B_OK; } result = _AddContextAction("Set visible range" B_UTF8_ELLIPSIS, MSG_SHOW_CONTAINER_RANGE_PROMPT, actions, message); if (result != B_OK) return result; return B_OK; } status_t VariablesView::_AddContextAction(const char* action, uint32 what, ContextActionList* actions, BMessage*& _message) { _message = new(std::nothrow) BMessage(what); if (_message == NULL) return B_NO_MEMORY; ObjectDeleter messageDeleter(_message); ActionMenuItem* item = new(std::nothrow) ActionMenuItem(action, _message); if (item == NULL) return B_NO_MEMORY; messageDeleter.Detach(); ObjectDeleter actionDeleter(item); if (!actions->AddItem(item)) return B_NO_MEMORY; actionDeleter.Detach(); return B_OK; } void VariablesView::_FinishContextMenu(bool force) { if (fTableCellContextMenuTracker != NULL) { if (!fTableCellContextMenuTracker->FinishMenu(force) || force) { fTableCellContextMenuTracker->ReleaseReference(); fTableCellContextMenuTracker = NULL; } } } void VariablesView::_SaveViewState() const { if (fThread == NULL || fStackFrame == NULL || fStackFrame->Function() == NULL) { return; } // get the function ID FunctionID* functionID = fStackFrame->Function()->GetFunctionID(); if (functionID == NULL) return; BReference functionIDReference(functionID, true); // create an empty view state VariablesViewState* viewState = new(std::nothrow) VariablesViewState; if (viewState == NULL) return; BReference viewStateReference(viewState, true); if (viewState->Init() != B_OK) return; // populate it TreeTablePath path; if (_AddViewStateDescendentNodeInfos(viewState, fVariableTableModel->Root(), path) != B_OK) { return; } // TODO: Add values! // 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 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) const { int32 childCount = fVariableTableModel->CountChildren(parent); for (int32 i = 0; i < childCount; i++) { ModelNode* node = (ModelNode*)fVariableTableModel->ChildAt(parent, 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()); TableCellValueRenderer* renderer = node->TableCellRenderer(); if (renderer != NULL) { Settings* settings = renderer->GetSettings(); if (settings != NULL) nodeInfo.SetRendererSettings(settings->Message()); } status_t error = viewState->SetNodeInfo(node->GetVariable()->ID(), node->GetPath(), nodeInfo); if (error != B_OK) return error; // recurse error = _AddViewStateDescendentNodeInfos(viewState, node, path); if (error != B_OK) return error; path.RemoveLastComponent(); } return B_OK; } status_t VariablesView::_ApplyViewStateDescendentNodeInfos(VariablesViewState* viewState, void* parent, TreeTablePath& path) { int32 childCount = fVariableTableModel->CountChildren(parent); for (int32 i = 0; i < childCount; i++) { ModelNode* node = (ModelNode*)fVariableTableModel->ChildAt(parent, i); if (!path.AddComponent(i)) return B_NO_MEMORY; // apply the node's info, if any const VariablesViewNodeInfo* nodeInfo = viewState->GetNodeInfo( node->GetVariable()->ID(), node->GetPath()); 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(); if (type != NULL) { ValueNode* valueNode = NULL; if (TypeHandlerRoster::Default()->CreateValueNode( node->NodeChild(), type, valueNode) == B_OK) { node->NodeChild()->SetNode(valueNode); node->SetCastedType(type); } } // 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()); // 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( 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(); } } // #pragma mark - Listener VariablesView::Listener::~Listener() { }