1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Copyright 2011-2015, Rene Gollent, rene@gollent.com.
4 * Distributed under the terms of the MIT License.
5 */
6
7#include "ImageFunctionsView.h"
8
9#include <stdio.h>
10
11#include <new>
12#include <set>
13
14#include <ControlLook.h>
15#include <LayoutBuilder.h>
16#include <MessageRunner.h>
17#include <StringList.h>
18#include <TextControl.h>
19
20#include <AutoDeleter.h>
21#include <RegExp.h>
22
23#include "table/TableColumns.h"
24
25#include "FunctionInstance.h"
26#include "GuiSettingsUtils.h"
27#include "Image.h"
28#include "ImageDebugInfo.h"
29#include "LocatableFile.h"
30#include "TargetAddressTableColumn.h"
31#include "Tracing.h"
32
33
34static const uint32 MSG_FUNCTION_FILTER_CHANGED = 'mffc';
35static const uint32 MSG_FUNCTION_TYPING_TIMEOUT	= 'mftt';
36
37static const uint32 kKeypressTimeout = 250000;
38
39// from ColumnTypes.cpp
40static const float kTextMargin = 8.0;
41
42
43// #pragma mark - SourcePathComponentNode
44
45
46class ImageFunctionsView::SourcePathComponentNode : public BReferenceable {
47public:
48	SourcePathComponentNode(SourcePathComponentNode* parent,
49		const BString& componentName, LocatableFile* sourceFile,
50		FunctionInstance* function)
51		:
52		fParent(parent),
53		fComponentName(componentName),
54		fSourceFile(sourceFile),
55		fFunction(function),
56		fFilterMatch(),
57		fHasMatchingChild(false)
58	{
59		if (fSourceFile != NULL)
60			fSourceFile->AcquireReference();
61		if (fFunction != NULL)
62			fFunction->AcquireReference();
63	}
64
65	virtual ~SourcePathComponentNode()
66	{
67		for (int32 i = 0; i < fChildPathComponents.CountItems(); i++)
68			fChildPathComponents.ItemAt(i)->ReleaseReference();
69
70		if (fSourceFile != NULL)
71			fSourceFile->ReleaseReference();
72
73		if (fFunction != NULL)
74			fFunction->ReleaseReference();
75	}
76
77	const BString& ComponentName() const
78	{
79		return fComponentName;
80	}
81
82	LocatableFile* SourceFile() const
83	{
84		return fSourceFile;
85	}
86
87	FunctionInstance* Function() const
88	{
89		return fFunction;
90	}
91
92	int32 CountChildren() const
93	{
94		return fChildPathComponents.CountItems();
95	}
96
97	SourcePathComponentNode* ChildAt(int32 index)
98	{
99		return fChildPathComponents.ItemAt(index);
100	}
101
102	SourcePathComponentNode* FindChildByName(const BString& name) const
103	{
104		return fChildPathComponents.BinarySearchByKey(name,
105			&CompareByComponentName);
106	}
107
108	int32 FindChildIndexByName(const BString& name) const
109	{
110		return fChildPathComponents.BinarySearchIndexByKey(name,
111			&CompareByComponentName);
112	}
113
114	bool AddChild(SourcePathComponentNode* child)
115	{
116		if (!fChildPathComponents.BinaryInsert(child,
117				&CompareComponents)) {
118			return false;
119		}
120
121		child->AcquireReference();
122
123		return true;
124	}
125
126	bool RemoveChild(SourcePathComponentNode* child)
127	{
128		if (!fChildPathComponents.RemoveItem(child))
129			return false;
130
131		child->ReleaseReference();
132
133		return true;
134	}
135
136	bool RemoveAllChildren()
137	{
138		for (int32 i = 0; i < fChildPathComponents.CountItems(); i++)
139			RemoveChild(fChildPathComponents.ItemAt(i));
140
141		return true;
142	}
143
144	const RegExp::MatchResult& FilterMatch() const
145	{
146		return fFilterMatch;
147	}
148
149	void SetFilterMatch(const RegExp::MatchResult& match)
150	{
151		fFilterMatch = match;
152	}
153
154	bool HasMatchingChild() const
155	{
156		return fHasMatchingChild;
157	}
158
159	void SetHasMatchingChild()
160	{
161		fHasMatchingChild = true;
162	}
163
164private:
165	friend class ImageFunctionsView::FunctionsTableModel;
166
167	static int CompareByComponentName(const BString* name, const
168		SourcePathComponentNode* node)
169	{
170		return name->Compare(node->ComponentName());
171	}
172
173	static int CompareComponents(const SourcePathComponentNode* a,
174		const SourcePathComponentNode* b)
175	{
176		return a->ComponentName().Compare(b->ComponentName());
177	}
178
179private:
180	typedef BObjectList<SourcePathComponentNode> ChildPathComponentList;
181
182private:
183	SourcePathComponentNode* fParent;
184	BString					fComponentName;
185	LocatableFile*			fSourceFile;
186	FunctionInstance*		fFunction;
187	ChildPathComponentList	fChildPathComponents;
188	RegExp::MatchResult		fFilterMatch;
189	bool					fHasMatchingChild;
190};
191
192
193// #pragma mark - HighlightingTableColumn
194
195
196class ImageFunctionsView::HighlightingTableColumn : public StringTableColumn {
197public:
198	HighlightingTableColumn(int32 modelIndex, const char* title, float width,
199		float minWidth, float maxWidth, uint32 truncate,
200		alignment align = B_ALIGN_LEFT)
201		:
202		StringTableColumn(modelIndex, title, width, minWidth, maxWidth,
203			truncate, align),
204		fHasFilter(false)
205	{
206	}
207
208	void SetHasFilter(bool hasFilter)
209	{
210		fHasFilter = hasFilter;
211	}
212
213	virtual void DrawValue(const BVariant& value, BRect rect,
214		BView* targetView)
215	{
216		StringTableColumn::DrawValue(value, rect, targetView);
217
218		if (fHasFilter) {
219			// TODO: handle this case as well
220			if (fField.HasClippedString())
221				return;
222
223			const SourcePathComponentNode* node
224				= (const SourcePathComponentNode*)value.ToPointer();
225
226			const RegExp::MatchResult& match = node->FilterMatch();
227			if (!match.HasMatched())
228				return;
229
230			targetView->PushState();
231			BRect fillRect(rect);
232			fillRect.left += kTextMargin + targetView->StringWidth(
233				fField.String(), match.StartOffset());
234			float filterWidth = targetView->StringWidth(fField.String()
235					+ match.StartOffset(), match.EndOffset()
236					- match.StartOffset());
237			fillRect.right = fillRect.left + filterWidth;
238			targetView->SetLowColor(255, 255, 0, 255);
239			targetView->SetDrawingMode(B_OP_MIN);
240			targetView->FillRect(fillRect, B_SOLID_LOW);
241			targetView->PopState();
242		}
243	}
244
245	virtual	BField*	PrepareField(const BVariant& value) const
246	{
247		const SourcePathComponentNode* node
248			= (const SourcePathComponentNode*)value.ToPointer();
249
250		BVariant tempValue(node->ComponentName(), B_VARIANT_DONT_COPY_DATA);
251		return StringTableColumn::PrepareField(tempValue);
252	}
253
254
255private:
256	bool fHasFilter;
257};
258
259
260// #pragma mark - FunctionsTableModel
261
262
263class ImageFunctionsView::FunctionsTableModel : public TreeTableModel {
264public:
265	FunctionsTableModel()
266		:
267		fImageDebugInfo(NULL),
268		fSourcelessNode(NULL)
269	{
270	}
271
272	~FunctionsTableModel()
273	{
274		SetImageDebugInfo(NULL);
275	}
276
277	void SetImageDebugInfo(ImageDebugInfo* imageDebugInfo)
278	{
279		// unset old functions
280		int32 count = fChildPathComponents.CountItems();
281		if (fImageDebugInfo != NULL) {
282			for (int32 i = 0; i < count; i++)
283				fChildPathComponents.ItemAt(i)->ReleaseReference();
284
285			fChildPathComponents.MakeEmpty();
286			fSourcelessNode = NULL;
287		}
288
289		fImageDebugInfo = imageDebugInfo;
290
291		// set new functions
292		if (fImageDebugInfo == NULL || fImageDebugInfo->CountFunctions()
293				== 0) {
294			NotifyNodesRemoved(TreeTablePath(), 0, count);
295			return;
296		}
297
298		std::set<target_addr_t> functionAddresses;
299
300		SourcePathComponentNode* sourcelessNode = new(std::nothrow)
301			SourcePathComponentNode(NULL, "<no source file>", NULL, NULL);
302		BReference<SourcePathComponentNode> sourceNodeRef(
303			sourcelessNode, true);
304
305		LocatableFile* currentFile = NULL;
306		BStringList pathComponents;
307		bool applyFilter = !fFilterString.IsEmpty()
308			&& fCurrentFilter.IsValid();
309		int32 functionCount = fImageDebugInfo->CountFunctions();
310		for (int32 i = 0; i < functionCount; i++) {
311			FunctionInstance* instance = fImageDebugInfo->FunctionAt(i);
312			target_addr_t address = instance->Address();
313			if (functionAddresses.find(address) != functionAddresses.end())
314				continue;
315			else {
316				try {
317					functionAddresses.insert(address);
318				} catch (...) {
319					return;
320				}
321			}
322
323			LocatableFile* sourceFile = instance->SourceFile();
324			BString sourcePath;
325			if (sourceFile != NULL)
326				sourceFile->GetPath(sourcePath);
327
328			RegExp::MatchResult pathMatch;
329			RegExp::MatchResult functionMatch;
330			if (applyFilter && !_FilterFunction(instance, sourcePath,
331					pathMatch, functionMatch)) {
332				continue;
333			}
334
335			if (sourceFile == NULL) {
336				if (!_AddFunctionNode(sourcelessNode, instance, NULL,
337						functionMatch)) {
338					return;
339				}
340				continue;
341			}
342
343			if (sourceFile != currentFile) {
344				currentFile = sourceFile;
345				pathComponents.MakeEmpty();
346				if (applyFilter) {
347					pathComponents.Add(sourcePath);
348				} else {
349					if (!_GetSourcePathComponents(currentFile,
350						pathComponents)) {
351						return;
352					}
353				}
354			}
355
356			if (!_AddFunctionByPath(pathComponents, instance, currentFile,
357					pathMatch, functionMatch)) {
358				return;
359			}
360		}
361
362		if (sourcelessNode->CountChildren() != 0) {
363			if (fChildPathComponents.BinaryInsert(sourcelessNode,
364					&SourcePathComponentNode::CompareComponents)) {
365				fSourcelessNode = sourcelessNode;
366				sourceNodeRef.Detach();
367			}
368		}
369
370		NotifyTableModelReset();
371	}
372
373	virtual int32 CountColumns() const
374	{
375		return 2;
376	}
377
378	virtual void* Root() const
379	{
380		return (void*)this;
381	}
382
383	virtual int32 CountChildren(void* parent) const
384	{
385		if (parent == this)
386			return fChildPathComponents.CountItems();
387
388		return ((SourcePathComponentNode*)parent)->CountChildren();
389	}
390
391	virtual void* ChildAt(void* parent, int32 index) const
392	{
393		if (parent == this)
394			return fChildPathComponents.ItemAt(index);
395
396		return ((SourcePathComponentNode*)parent)->ChildAt(index);
397	}
398
399	virtual bool GetValueAt(void* object, int32 columnIndex, BVariant& value)
400	{
401		if (object == this)
402			return false;
403
404		SourcePathComponentNode* node = (SourcePathComponentNode*)object;
405		switch (columnIndex) {
406			case 0:
407			{
408				value.SetTo(node);
409				break;
410			}
411			case 1:
412			{
413				FunctionInstance* function = node->Function();
414				if (function == NULL)
415					return false;
416				value.SetTo(function->Address());
417				break;
418			}
419			default:
420				return false;
421		}
422
423		return true;
424	}
425
426	bool HasMatchingChildAt(void* parent, int32 index) const
427	{
428		SourcePathComponentNode* node
429			= (SourcePathComponentNode*)ChildAt(parent, index);
430		if (node != NULL)
431			return node->HasMatchingChild();
432
433		return false;
434	}
435
436	bool GetFunctionPath(FunctionInstance* function, TreeTablePath& _path)
437	{
438		if (function == NULL)
439			return false;
440
441		LocatableFile* sourceFile = function->SourceFile();
442		SourcePathComponentNode* node = NULL;
443		int32 childIndex = -1;
444		if (sourceFile == NULL) {
445			node = fSourcelessNode;
446			_path.AddComponent(fChildPathComponents.IndexOf(node));
447		} else {
448			BStringList pathComponents;
449			if (!_GetSourcePathComponents(sourceFile, pathComponents))
450				return false;
451
452			for (int32 i = 0; i < pathComponents.CountStrings(); i++) {
453				BString component = pathComponents.StringAt(i);
454
455				if (node == NULL) {
456					childIndex = fChildPathComponents.BinarySearchIndexByKey(
457						component,
458						&SourcePathComponentNode::CompareByComponentName);
459					node = fChildPathComponents.ItemAt(childIndex);
460				} else {
461					childIndex = node->FindChildIndexByName(component);
462					node = node->ChildAt(childIndex);
463				}
464
465				if (childIndex < 0)
466					return false;
467
468				_path.AddComponent(childIndex);
469			}
470		}
471
472		if (node == NULL)
473			return false;
474
475		childIndex = node->FindChildIndexByName(function->PrettyName());
476		if (childIndex < 0)
477			return false;
478
479		_path.AddComponent(childIndex);
480		return true;
481	}
482
483	bool GetObjectForPath(const TreeTablePath& path,
484		LocatableFile*& _sourceFile, FunctionInstance*& _function)
485	{
486		SourcePathComponentNode* node = fChildPathComponents.ItemAt(
487			path.ComponentAt(0));
488
489		if (node == NULL)
490			return false;
491
492		for (int32 i = 1; i < path.CountComponents(); i++)
493			node = node->ChildAt(path.ComponentAt(i));
494
495		if (node != NULL) {
496			_sourceFile = node->SourceFile();
497			_function = node->Function();
498			return true;
499		}
500
501		return false;
502	}
503
504	void SetFilter(const char* filter)
505	{
506		fFilterString = filter;
507		if (fFilterString.IsEmpty()
508			|| fCurrentFilter.SetPattern(filter, RegExp::PATTERN_TYPE_WILDCARD,
509				false)) {
510			SetImageDebugInfo(fImageDebugInfo);
511		}
512	}
513
514private:
515	bool _GetSourcePathComponents(LocatableFile* currentFile,
516		BStringList& pathComponents)
517	{
518		BString sourcePath;
519		currentFile->GetPath(sourcePath);
520		if (sourcePath.IsEmpty())
521			return false;
522
523		int32 startIndex = 0;
524		if (sourcePath[0] == '/')
525			startIndex = 1;
526
527		while (startIndex < sourcePath.Length()) {
528			int32 searchIndex = sourcePath.FindFirst('/', startIndex);
529			BString data;
530			if (searchIndex < 0)
531				searchIndex = sourcePath.Length();
532
533			sourcePath.CopyInto(data, startIndex, searchIndex - startIndex);
534			if (!pathComponents.Add(data))
535				return false;
536
537			startIndex = searchIndex + 1;
538		}
539
540		return true;
541	}
542
543	bool _AddFunctionByPath(const BStringList& pathComponents,
544		FunctionInstance* function, LocatableFile* file,
545		RegExp::MatchResult& pathMatch, RegExp::MatchResult& functionMatch)
546	{
547		SourcePathComponentNode* parentNode = NULL;
548		SourcePathComponentNode* currentNode = NULL;
549		for (int32 i = 0; i < pathComponents.CountStrings(); i++) {
550			const BString pathComponent = pathComponents.StringAt(i);
551			if (parentNode == NULL) {
552				currentNode = fChildPathComponents.BinarySearchByKey(
553					pathComponent,
554					SourcePathComponentNode::CompareByComponentName);
555			} else
556				currentNode = parentNode->FindChildByName(pathComponent);
557
558			if (currentNode == NULL) {
559				currentNode = new(std::nothrow) SourcePathComponentNode(
560					parentNode,	pathComponent, NULL, NULL);
561				if (currentNode == NULL)
562					return false;
563
564				if (pathComponents.CountStrings() == 1)
565					currentNode->SetFilterMatch(pathMatch);
566
567				BReference<SourcePathComponentNode> nodeReference(currentNode,
568					true);
569				if (parentNode != NULL) {
570					if (!parentNode->AddChild(currentNode))
571						return false;
572				} else {
573					if (!fChildPathComponents.BinaryInsert(currentNode,
574						&SourcePathComponentNode::CompareComponents)) {
575						return false;
576					}
577
578					nodeReference.Detach();
579				}
580			}
581
582			if (functionMatch.HasMatched())
583				currentNode->SetHasMatchingChild();
584
585			parentNode = currentNode;
586
587		}
588
589		return _AddFunctionNode(currentNode, function, file,
590			functionMatch);
591	}
592
593	bool _AddFunctionNode(SourcePathComponentNode* parent,
594		FunctionInstance* function, LocatableFile* file,
595		RegExp::MatchResult& match)
596	{
597		SourcePathComponentNode* functionNode = new(std::nothrow)
598			SourcePathComponentNode(parent, function->PrettyName(), file,
599				function);
600
601		if (functionNode == NULL)
602			return B_NO_MEMORY;
603
604		functionNode->SetFilterMatch(match);
605
606		BReference<SourcePathComponentNode> nodeReference(functionNode, true);
607		if (!parent->AddChild(functionNode))
608			return false;
609
610		return true;
611	}
612
613	bool _FilterFunction(FunctionInstance* instance, const BString& sourcePath,
614		RegExp::MatchResult& pathMatch, RegExp::MatchResult& functionMatch)
615	{
616		functionMatch = fCurrentFilter.Match(instance->PrettyName());
617		pathMatch = fCurrentFilter.Match(sourcePath.String());
618
619		return functionMatch.HasMatched() || pathMatch.HasMatched();
620	}
621
622
623private:
624	typedef BObjectList<SourcePathComponentNode> ChildPathComponentList;
625
626private:
627	ImageDebugInfo*			fImageDebugInfo;
628	ChildPathComponentList	fChildPathComponents;
629	SourcePathComponentNode* fSourcelessNode;
630	BString					fFilterString;
631	RegExp					fCurrentFilter;
632};
633
634
635// #pragma mark - ImageFunctionsView
636
637
638ImageFunctionsView::ImageFunctionsView(Listener* listener)
639	:
640	BGroupView(B_VERTICAL),
641	fImageDebugInfo(NULL),
642	fFilterField(NULL),
643	fFunctionsTable(NULL),
644	fFunctionsTableModel(NULL),
645	fListener(listener),
646	fHighlightingColumn(NULL),
647	fLastFilterKeypress(0)
648{
649	SetName("Functions");
650}
651
652
653ImageFunctionsView::~ImageFunctionsView()
654{
655	SetImageDebugInfo(NULL);
656	fFunctionsTable->SetTreeTableModel(NULL);
657	delete fFunctionsTableModel;
658}
659
660
661/*static*/ ImageFunctionsView*
662ImageFunctionsView::Create(Listener* listener)
663{
664	ImageFunctionsView* self = new ImageFunctionsView(listener);
665
666	try {
667		self->_Init();
668	} catch (...) {
669		delete self;
670		throw;
671	}
672
673	return self;
674}
675
676
677void
678ImageFunctionsView::UnsetListener()
679{
680	fListener = NULL;
681}
682
683
684void
685ImageFunctionsView::SetImageDebugInfo(ImageDebugInfo* imageDebugInfo)
686{
687	if (imageDebugInfo == fImageDebugInfo)
688		return;
689
690	TRACE_GUI("ImageFunctionsView::SetImageDebugInfo(%p)\n", imageDebugInfo);
691
692	if (fImageDebugInfo != NULL)
693		fImageDebugInfo->ReleaseReference();
694
695	fImageDebugInfo = imageDebugInfo;
696
697	if (fImageDebugInfo != NULL)
698		fImageDebugInfo->AcquireReference();
699
700	fFunctionsTableModel->SetImageDebugInfo(fImageDebugInfo);
701
702	// If there's only one source file (i.e. "no source file"), expand the item.
703	if (fImageDebugInfo != NULL
704		&& fFunctionsTableModel->CountChildren(fFunctionsTableModel) == 1) {
705		TreeTablePath path;
706		path.AddComponent(0);
707		fFunctionsTable->SetNodeExpanded(path, true, false);
708	}
709
710	TRACE_GUI("ImageFunctionsView::SetImageDebugInfo(%p) done\n",
711		imageDebugInfo);
712}
713
714
715void
716ImageFunctionsView::SetFunction(FunctionInstance* function)
717{
718	TRACE_GUI("ImageFunctionsView::SetFunction(%p)\n", function);
719
720	TreeTablePath path;
721	if (fFunctionsTableModel->GetFunctionPath(function, path)) {
722		fFunctionsTable->SetNodeExpanded(path, true, true);
723		fFunctionsTable->SelectNode(path, false);
724		fFunctionsTable->ScrollToNode(path);
725	} else
726		fFunctionsTable->DeselectAllNodes();
727}
728
729
730void
731ImageFunctionsView::AttachedToWindow()
732{
733	BView::AttachedToWindow();
734
735	fFilterField->SetTarget(this);
736}
737
738
739void
740ImageFunctionsView::MessageReceived(BMessage* message)
741{
742	switch (message->what) {
743		case MSG_FUNCTION_FILTER_CHANGED:
744		{
745			fLastFilterKeypress = system_time();
746			BMessage keypressMessage(MSG_FUNCTION_TYPING_TIMEOUT);
747			BMessageRunner::StartSending(BMessenger(this), &keypressMessage,
748				kKeypressTimeout, 1);
749			break;
750		}
751
752		case MSG_FUNCTION_TYPING_TIMEOUT:
753		{
754			if (system_time() - fLastFilterKeypress >= kKeypressTimeout) {
755				fFunctionsTableModel->SetFilter(fFilterField->Text());
756				fHighlightingColumn->SetHasFilter(
757					fFilterField->TextView()->TextLength() > 0);
758				_ExpandFilteredNodes();
759			}
760			break;
761		}
762
763		default:
764			BView::MessageReceived(message);
765			break;
766	}
767}
768
769
770void
771ImageFunctionsView::LoadSettings(const BMessage& settings)
772{
773	BMessage tableSettings;
774	if (settings.FindMessage("functionsTable", &tableSettings) == B_OK) {
775		GuiSettingsUtils::UnarchiveTableSettings(tableSettings,
776			fFunctionsTable);
777	}
778}
779
780
781status_t
782ImageFunctionsView::SaveSettings(BMessage& settings)
783{
784	settings.MakeEmpty();
785
786	BMessage tableSettings;
787	status_t result = GuiSettingsUtils::ArchiveTableSettings(tableSettings,
788		fFunctionsTable);
789	if (result == B_OK)
790		result = settings.AddMessage("functionsTable", &tableSettings);
791
792	return result;
793}
794
795
796void
797ImageFunctionsView::TreeTableSelectionChanged(TreeTable* table)
798{
799	if (fListener == NULL)
800		return;
801
802	LocatableFile* sourceFile = NULL;
803	FunctionInstance* function = NULL;
804	TreeTablePath path;
805	if (table->SelectionModel()->GetPathAt(0, path))
806		fFunctionsTableModel->GetObjectForPath(path, sourceFile, function);
807
808	fListener->FunctionSelectionChanged(function);
809}
810
811
812void
813ImageFunctionsView::_Init()
814{
815	fFunctionsTable = new TreeTable("functions", 0, B_FANCY_BORDER);
816	fFunctionsTable->SetFont(B_FONT_ROW, be_fixed_font);
817	AddChild(fFunctionsTable->ToView());
818	AddChild(fFilterField = new BTextControl("filtertext", "Filter:",
819			NULL, NULL));
820
821	fFilterField->SetModificationMessage(new BMessage(
822			MSG_FUNCTION_FILTER_CHANGED));
823	fFunctionsTable->SetSortingEnabled(false);
824
825	float addressWidth = be_plain_font->StringWidth("0x00000000")
826		+ be_control_look->DefaultLabelSpacing() * 3;
827
828	// columns
829	fFunctionsTable->AddColumn(fHighlightingColumn
830		= new HighlightingTableColumn(0, "File/Function", 300, 100, 1000,
831			B_TRUNCATE_BEGINNING, B_ALIGN_LEFT));
832	fFunctionsTable->AddColumn(new TargetAddressTableColumn(1, "Address",
833		addressWidth, 40, 1000, B_TRUNCATE_END, B_ALIGN_RIGHT));
834
835	fFunctionsTableModel = new FunctionsTableModel();
836	fFunctionsTable->SetTreeTableModel(fFunctionsTableModel);
837
838	fFunctionsTable->SetSelectionMode(B_SINGLE_SELECTION_LIST);
839	fFunctionsTable->AddTreeTableListener(this);
840}
841
842
843void
844ImageFunctionsView::_ExpandFilteredNodes()
845{
846	if (fFilterField->TextView()->TextLength() == 0)
847		return;
848
849	for (int32 i = 0; i < fFunctionsTableModel->CountChildren(
850		fFunctionsTableModel); i++) {
851		// only expand nodes if the match actually hit a function,
852		// and not just the containing path.
853		if (fFunctionsTableModel->CountChildren(fFunctionsTableModel) == 1
854			|| fFunctionsTableModel->HasMatchingChildAt(fFunctionsTableModel,
855				i)) {
856			TreeTablePath path;
857			path.AddComponent(i);
858			fFunctionsTable->SetNodeExpanded(path, true, true);
859		}
860	}
861}
862
863
864// #pragma mark - Listener
865
866
867ImageFunctionsView::Listener::~Listener()
868{
869}
870