* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2011-2015, Rene Gollent, rene@gollent.com.
* Distributed under the terms of the MIT License.
*/
#include "ImageFunctionsView.h"
#include <stdio.h>
#include <new>
#include <set>
#include <ControlLook.h>
#include <LayoutBuilder.h>
#include <MessageRunner.h>
#include <StringList.h>
#include <TextControl.h>
#include <AutoDeleter.h>
#include <RegExp.h>
#include "table/TableColumns.h"
#include "FunctionInstance.h"
#include "GuiSettingsUtils.h"
#include "Image.h"
#include "ImageDebugInfo.h"
#include "LocatableFile.h"
#include "TargetAddressTableColumn.h"
#include "Tracing.h"
static const uint32 MSG_FUNCTION_FILTER_CHANGED = 'mffc';
static const uint32 MSG_FUNCTION_TYPING_TIMEOUT = 'mftt';
static const uint32 kKeypressTimeout = 250000;
static const float kTextMargin = 8.0;
class ImageFunctionsView::SourcePathComponentNode : public BReferenceable {
public:
SourcePathComponentNode(SourcePathComponentNode* parent,
const BString& componentName, LocatableFile* sourceFile,
FunctionInstance* function)
:
fParent(parent),
fComponentName(componentName),
fSourceFile(sourceFile),
fFunction(function),
fFilterMatch(),
fHasMatchingChild(false)
{
if (fSourceFile != NULL)
fSourceFile->AcquireReference();
if (fFunction != NULL)
fFunction->AcquireReference();
}
virtual ~SourcePathComponentNode()
{
for (int32 i = 0; i < fChildPathComponents.CountItems(); i++)
fChildPathComponents.ItemAt(i)->ReleaseReference();
if (fSourceFile != NULL)
fSourceFile->ReleaseReference();
if (fFunction != NULL)
fFunction->ReleaseReference();
}
const BString& ComponentName() const
{
return fComponentName;
}
LocatableFile* SourceFile() const
{
return fSourceFile;
}
FunctionInstance* Function() const
{
return fFunction;
}
int32 CountChildren() const
{
return fChildPathComponents.CountItems();
}
SourcePathComponentNode* ChildAt(int32 index)
{
return fChildPathComponents.ItemAt(index);
}
SourcePathComponentNode* FindChildByName(const BString& name) const
{
return fChildPathComponents.BinarySearchByKey(name,
&CompareByComponentName);
}
int32 FindChildIndexByName(const BString& name) const
{
return fChildPathComponents.BinarySearchIndexByKey(name,
&CompareByComponentName);
}
bool AddChild(SourcePathComponentNode* child)
{
if (!fChildPathComponents.BinaryInsert(child,
&CompareComponents)) {
return false;
}
child->AcquireReference();
return true;
}
bool RemoveChild(SourcePathComponentNode* child)
{
if (!fChildPathComponents.RemoveItem(child))
return false;
child->ReleaseReference();
return true;
}
bool RemoveAllChildren()
{
for (int32 i = 0; i < fChildPathComponents.CountItems(); i++)
RemoveChild(fChildPathComponents.ItemAt(i));
return true;
}
const RegExp::MatchResult& FilterMatch() const
{
return fFilterMatch;
}
void SetFilterMatch(const RegExp::MatchResult& match)
{
fFilterMatch = match;
}
bool HasMatchingChild() const
{
return fHasMatchingChild;
}
void SetHasMatchingChild()
{
fHasMatchingChild = true;
}
private:
friend class ImageFunctionsView::FunctionsTableModel;
static int CompareByComponentName(const BString* name, const
SourcePathComponentNode* node)
{
return name->Compare(node->ComponentName());
}
static int CompareComponents(const SourcePathComponentNode* a,
const SourcePathComponentNode* b)
{
return a->ComponentName().Compare(b->ComponentName());
}
private:
typedef BObjectList<SourcePathComponentNode> ChildPathComponentList;
private:
SourcePathComponentNode* fParent;
BString fComponentName;
LocatableFile* fSourceFile;
FunctionInstance* fFunction;
ChildPathComponentList fChildPathComponents;
RegExp::MatchResult fFilterMatch;
bool fHasMatchingChild;
};
class ImageFunctionsView::HighlightingTableColumn : public StringTableColumn {
public:
HighlightingTableColumn(int32 modelIndex, const char* title, float width,
float minWidth, float maxWidth, uint32 truncate,
alignment align = B_ALIGN_LEFT)
:
StringTableColumn(modelIndex, title, width, minWidth, maxWidth,
truncate, align),
fHasFilter(false)
{
}
void SetHasFilter(bool hasFilter)
{
fHasFilter = hasFilter;
}
virtual void DrawValue(const BVariant& value, BRect rect,
BView* targetView)
{
StringTableColumn::DrawValue(value, rect, targetView);
if (fHasFilter) {
if (fField.HasClippedString())
return;
const SourcePathComponentNode* node
= (const SourcePathComponentNode*)value.ToPointer();
const RegExp::MatchResult& match = node->FilterMatch();
if (!match.HasMatched())
return;
targetView->PushState();
BRect fillRect(rect);
fillRect.left += kTextMargin + targetView->StringWidth(
fField.String(), match.StartOffset());
float filterWidth = targetView->StringWidth(fField.String()
+ match.StartOffset(), match.EndOffset()
- match.StartOffset());
fillRect.right = fillRect.left + filterWidth;
targetView->SetLowColor(255, 255, 0, 255);
targetView->SetDrawingMode(B_OP_MIN);
targetView->FillRect(fillRect, B_SOLID_LOW);
targetView->PopState();
}
}
virtual BField* PrepareField(const BVariant& value) const
{
const SourcePathComponentNode* node
= (const SourcePathComponentNode*)value.ToPointer();
BVariant tempValue(node->ComponentName(), B_VARIANT_DONT_COPY_DATA);
return StringTableColumn::PrepareField(tempValue);
}
private:
bool fHasFilter;
};
class ImageFunctionsView::FunctionsTableModel : public TreeTableModel {
public:
FunctionsTableModel()
:
fImageDebugInfo(NULL),
fSourcelessNode(NULL)
{
}
~FunctionsTableModel()
{
SetImageDebugInfo(NULL);
}
void SetImageDebugInfo(ImageDebugInfo* imageDebugInfo)
{
int32 count = fChildPathComponents.CountItems();
if (fImageDebugInfo != NULL) {
for (int32 i = 0; i < count; i++)
fChildPathComponents.ItemAt(i)->ReleaseReference();
fChildPathComponents.MakeEmpty();
fSourcelessNode = NULL;
}
fImageDebugInfo = imageDebugInfo;
if (fImageDebugInfo == NULL || fImageDebugInfo->CountFunctions()
== 0) {
NotifyNodesRemoved(TreeTablePath(), 0, count);
return;
}
std::set<target_addr_t> functionAddresses;
SourcePathComponentNode* sourcelessNode = new(std::nothrow)
SourcePathComponentNode(NULL, "<no source file>", NULL, NULL);
BReference<SourcePathComponentNode> sourceNodeRef(
sourcelessNode, true);
LocatableFile* currentFile = NULL;
BStringList pathComponents;
bool applyFilter = !fFilterString.IsEmpty()
&& fCurrentFilter.IsValid();
int32 functionCount = fImageDebugInfo->CountFunctions();
for (int32 i = 0; i < functionCount; i++) {
FunctionInstance* instance = fImageDebugInfo->FunctionAt(i);
target_addr_t address = instance->Address();
if (functionAddresses.find(address) != functionAddresses.end())
continue;
else {
try {
functionAddresses.insert(address);
} catch (...) {
return;
}
}
LocatableFile* sourceFile = instance->SourceFile();
BString sourcePath;
if (sourceFile != NULL)
sourceFile->GetPath(sourcePath);
RegExp::MatchResult pathMatch;
RegExp::MatchResult functionMatch;
if (applyFilter && !_FilterFunction(instance, sourcePath,
pathMatch, functionMatch)) {
continue;
}
if (sourceFile == NULL) {
if (!_AddFunctionNode(sourcelessNode, instance, NULL,
functionMatch)) {
return;
}
continue;
}
if (sourceFile != currentFile) {
currentFile = sourceFile;
pathComponents.MakeEmpty();
if (applyFilter) {
pathComponents.Add(sourcePath);
} else {
if (!_GetSourcePathComponents(currentFile,
pathComponents)) {
return;
}
}
}
if (!_AddFunctionByPath(pathComponents, instance, currentFile,
pathMatch, functionMatch)) {
return;
}
}
if (sourcelessNode->CountChildren() != 0) {
if (fChildPathComponents.BinaryInsert(sourcelessNode,
&SourcePathComponentNode::CompareComponents)) {
fSourcelessNode = sourcelessNode;
sourceNodeRef.Detach();
}
}
NotifyTableModelReset();
}
virtual int32 CountColumns() const
{
return 2;
}
virtual void* Root() const
{
return (void*)this;
}
virtual int32 CountChildren(void* parent) const
{
if (parent == this)
return fChildPathComponents.CountItems();
return ((SourcePathComponentNode*)parent)->CountChildren();
}
virtual void* ChildAt(void* parent, int32 index) const
{
if (parent == this)
return fChildPathComponents.ItemAt(index);
return ((SourcePathComponentNode*)parent)->ChildAt(index);
}
virtual bool GetValueAt(void* object, int32 columnIndex, BVariant& value)
{
if (object == this)
return false;
SourcePathComponentNode* node = (SourcePathComponentNode*)object;
switch (columnIndex) {
case 0:
{
value.SetTo(node);
break;
}
case 1:
{
FunctionInstance* function = node->Function();
if (function == NULL)
return false;
value.SetTo(function->Address());
break;
}
default:
return false;
}
return true;
}
bool HasMatchingChildAt(void* parent, int32 index) const
{
SourcePathComponentNode* node
= (SourcePathComponentNode*)ChildAt(parent, index);
if (node != NULL)
return node->HasMatchingChild();
return false;
}
bool GetFunctionPath(FunctionInstance* function, TreeTablePath& _path)
{
if (function == NULL)
return false;
LocatableFile* sourceFile = function->SourceFile();
SourcePathComponentNode* node = NULL;
int32 childIndex = -1;
if (sourceFile == NULL) {
node = fSourcelessNode;
_path.AddComponent(fChildPathComponents.IndexOf(node));
} else {
BStringList pathComponents;
if (!_GetSourcePathComponents(sourceFile, pathComponents))
return false;
for (int32 i = 0; i < pathComponents.CountStrings(); i++) {
BString component = pathComponents.StringAt(i);
if (node == NULL) {
childIndex = fChildPathComponents.BinarySearchIndexByKey(
component,
&SourcePathComponentNode::CompareByComponentName);
node = fChildPathComponents.ItemAt(childIndex);
} else {
childIndex = node->FindChildIndexByName(component);
node = node->ChildAt(childIndex);
}
if (childIndex < 0)
return false;
_path.AddComponent(childIndex);
}
}
if (node == NULL)
return false;
childIndex = node->FindChildIndexByName(function->PrettyName());
if (childIndex < 0)
return false;
_path.AddComponent(childIndex);
return true;
}
bool GetObjectForPath(const TreeTablePath& path,
LocatableFile*& _sourceFile, FunctionInstance*& _function)
{
SourcePathComponentNode* node = fChildPathComponents.ItemAt(
path.ComponentAt(0));
if (node == NULL)
return false;
for (int32 i = 1; i < path.CountComponents(); i++)
node = node->ChildAt(path.ComponentAt(i));
if (node != NULL) {
_sourceFile = node->SourceFile();
_function = node->Function();
return true;
}
return false;
}
void SetFilter(const char* filter)
{
fFilterString = filter;
if (fFilterString.IsEmpty()
|| fCurrentFilter.SetPattern(filter, RegExp::PATTERN_TYPE_WILDCARD,
false)) {
SetImageDebugInfo(fImageDebugInfo);
}
}
private:
bool _GetSourcePathComponents(LocatableFile* currentFile,
BStringList& pathComponents)
{
BString sourcePath;
currentFile->GetPath(sourcePath);
if (sourcePath.IsEmpty())
return false;
int32 startIndex = 0;
if (sourcePath[0] == '/')
startIndex = 1;
while (startIndex < sourcePath.Length()) {
int32 searchIndex = sourcePath.FindFirst('/', startIndex);
BString data;
if (searchIndex < 0)
searchIndex = sourcePath.Length();
sourcePath.CopyInto(data, startIndex, searchIndex - startIndex);
if (!pathComponents.Add(data))
return false;
startIndex = searchIndex + 1;
}
return true;
}
bool _AddFunctionByPath(const BStringList& pathComponents,
FunctionInstance* function, LocatableFile* file,
RegExp::MatchResult& pathMatch, RegExp::MatchResult& functionMatch)
{
SourcePathComponentNode* parentNode = NULL;
SourcePathComponentNode* currentNode = NULL;
for (int32 i = 0; i < pathComponents.CountStrings(); i++) {
const BString pathComponent = pathComponents.StringAt(i);
if (parentNode == NULL) {
currentNode = fChildPathComponents.BinarySearchByKey(
pathComponent,
SourcePathComponentNode::CompareByComponentName);
} else
currentNode = parentNode->FindChildByName(pathComponent);
if (currentNode == NULL) {
currentNode = new(std::nothrow) SourcePathComponentNode(
parentNode, pathComponent, NULL, NULL);
if (currentNode == NULL)
return false;
if (pathComponents.CountStrings() == 1)
currentNode->SetFilterMatch(pathMatch);
BReference<SourcePathComponentNode> nodeReference(currentNode,
true);
if (parentNode != NULL) {
if (!parentNode->AddChild(currentNode))
return false;
} else {
if (!fChildPathComponents.BinaryInsert(currentNode,
&SourcePathComponentNode::CompareComponents)) {
return false;
}
nodeReference.Detach();
}
}
if (functionMatch.HasMatched())
currentNode->SetHasMatchingChild();
parentNode = currentNode;
}
return _AddFunctionNode(currentNode, function, file,
functionMatch);
}
bool _AddFunctionNode(SourcePathComponentNode* parent,
FunctionInstance* function, LocatableFile* file,
RegExp::MatchResult& match)
{
SourcePathComponentNode* functionNode = new(std::nothrow)
SourcePathComponentNode(parent, function->PrettyName(), file,
function);
if (functionNode == NULL)
return B_NO_MEMORY;
functionNode->SetFilterMatch(match);
BReference<SourcePathComponentNode> nodeReference(functionNode, true);
if (!parent->AddChild(functionNode))
return false;
return true;
}
bool _FilterFunction(FunctionInstance* instance, const BString& sourcePath,
RegExp::MatchResult& pathMatch, RegExp::MatchResult& functionMatch)
{
functionMatch = fCurrentFilter.Match(instance->PrettyName());
pathMatch = fCurrentFilter.Match(sourcePath.String());
return functionMatch.HasMatched() || pathMatch.HasMatched();
}
private:
typedef BObjectList<SourcePathComponentNode> ChildPathComponentList;
private:
ImageDebugInfo* fImageDebugInfo;
ChildPathComponentList fChildPathComponents;
SourcePathComponentNode* fSourcelessNode;
BString fFilterString;
RegExp fCurrentFilter;
};
ImageFunctionsView::ImageFunctionsView(Listener* listener)
:
BGroupView(B_VERTICAL),
fImageDebugInfo(NULL),
fFilterField(NULL),
fFunctionsTable(NULL),
fFunctionsTableModel(NULL),
fListener(listener),
fHighlightingColumn(NULL),
fLastFilterKeypress(0)
{
SetName("Functions");
}
ImageFunctionsView::~ImageFunctionsView()
{
SetImageDebugInfo(NULL);
fFunctionsTable->SetTreeTableModel(NULL);
delete fFunctionsTableModel;
}
ImageFunctionsView*
ImageFunctionsView::Create(Listener* listener)
{
ImageFunctionsView* self = new ImageFunctionsView(listener);
try {
self->_Init();
} catch (...) {
delete self;
throw;
}
return self;
}
void
ImageFunctionsView::UnsetListener()
{
fListener = NULL;
}
void
ImageFunctionsView::SetImageDebugInfo(ImageDebugInfo* imageDebugInfo)
{
if (imageDebugInfo == fImageDebugInfo)
return;
TRACE_GUI("ImageFunctionsView::SetImageDebugInfo(%p)\n", imageDebugInfo);
if (fImageDebugInfo != NULL)
fImageDebugInfo->ReleaseReference();
fImageDebugInfo = imageDebugInfo;
if (fImageDebugInfo != NULL)
fImageDebugInfo->AcquireReference();
fFunctionsTableModel->SetImageDebugInfo(fImageDebugInfo);
if (fImageDebugInfo != NULL
&& fFunctionsTableModel->CountChildren(fFunctionsTableModel) == 1) {
TreeTablePath path;
path.AddComponent(0);
fFunctionsTable->SetNodeExpanded(path, true, false);
}
TRACE_GUI("ImageFunctionsView::SetImageDebugInfo(%p) done\n",
imageDebugInfo);
}
void
ImageFunctionsView::SetFunction(FunctionInstance* function)
{
TRACE_GUI("ImageFunctionsView::SetFunction(%p)\n", function);
TreeTablePath path;
if (fFunctionsTableModel->GetFunctionPath(function, path)) {
fFunctionsTable->SetNodeExpanded(path, true, true);
fFunctionsTable->SelectNode(path, false);
fFunctionsTable->ScrollToNode(path);
} else
fFunctionsTable->DeselectAllNodes();
}
void
ImageFunctionsView::AttachedToWindow()
{
BView::AttachedToWindow();
fFilterField->SetTarget(this);
}
void
ImageFunctionsView::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_FUNCTION_FILTER_CHANGED:
{
fLastFilterKeypress = system_time();
BMessage keypressMessage(MSG_FUNCTION_TYPING_TIMEOUT);
BMessageRunner::StartSending(BMessenger(this), &keypressMessage,
kKeypressTimeout, 1);
break;
}
case MSG_FUNCTION_TYPING_TIMEOUT:
{
if (system_time() - fLastFilterKeypress >= kKeypressTimeout) {
fFunctionsTableModel->SetFilter(fFilterField->Text());
fHighlightingColumn->SetHasFilter(
fFilterField->TextView()->TextLength() > 0);
_ExpandFilteredNodes();
}
break;
}
default:
BView::MessageReceived(message);
break;
}
}
void
ImageFunctionsView::LoadSettings(const BMessage& settings)
{
BMessage tableSettings;
if (settings.FindMessage("functionsTable", &tableSettings) == B_OK) {
GuiSettingsUtils::UnarchiveTableSettings(tableSettings,
fFunctionsTable);
}
}
status_t
ImageFunctionsView::SaveSettings(BMessage& settings)
{
settings.MakeEmpty();
BMessage tableSettings;
status_t result = GuiSettingsUtils::ArchiveTableSettings(tableSettings,
fFunctionsTable);
if (result == B_OK)
result = settings.AddMessage("functionsTable", &tableSettings);
return result;
}
void
ImageFunctionsView::TreeTableSelectionChanged(TreeTable* table)
{
if (fListener == NULL)
return;
LocatableFile* sourceFile = NULL;
FunctionInstance* function = NULL;
TreeTablePath path;
if (table->SelectionModel()->GetPathAt(0, path))
fFunctionsTableModel->GetObjectForPath(path, sourceFile, function);
fListener->FunctionSelectionChanged(function);
}
void
ImageFunctionsView::_Init()
{
fFunctionsTable = new TreeTable("functions", 0, B_FANCY_BORDER);
fFunctionsTable->SetFont(B_FONT_ROW, be_fixed_font);
AddChild(fFunctionsTable->ToView());
AddChild(fFilterField = new BTextControl("filtertext", "Filter:",
NULL, NULL));
fFilterField->SetModificationMessage(new BMessage(
MSG_FUNCTION_FILTER_CHANGED));
fFunctionsTable->SetSortingEnabled(false);
float addressWidth = be_plain_font->StringWidth("0x00000000")
+ be_control_look->DefaultLabelSpacing() * 3;
fFunctionsTable->AddColumn(fHighlightingColumn
= new HighlightingTableColumn(0, "File/Function", 300, 100, 1000,
B_TRUNCATE_BEGINNING, B_ALIGN_LEFT));
fFunctionsTable->AddColumn(new TargetAddressTableColumn(1, "Address",
addressWidth, 40, 1000, B_TRUNCATE_END, B_ALIGN_RIGHT));
fFunctionsTableModel = new FunctionsTableModel();
fFunctionsTable->SetTreeTableModel(fFunctionsTableModel);
fFunctionsTable->SetSelectionMode(B_SINGLE_SELECTION_LIST);
fFunctionsTable->AddTreeTableListener(this);
}
void
ImageFunctionsView::_ExpandFilteredNodes()
{
if (fFilterField->TextView()->TextLength() == 0)
return;
for (int32 i = 0; i < fFunctionsTableModel->CountChildren(
fFunctionsTableModel); i++) {
if (fFunctionsTableModel->CountChildren(fFunctionsTableModel) == 1
|| fFunctionsTableModel->HasMatchingChildAt(fFunctionsTableModel,
i)) {
TreeTablePath path;
path.AddComponent(i);
fFunctionsTable->SetNodeExpanded(path, true, true);
}
}
}
ImageFunctionsView::Listener::~Listener()
{
}