forked from dnSpy/ICSharpCode.TreeView
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathSharpTreeViewTextSearch.cs
145 lines (123 loc) · 4.16 KB
/
SharpTreeViewTextSearch.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Threading;
namespace ICSharpCode.TreeView {
/// <summary>
/// Custom TextSearch-implementation.
/// Fixes #67 - Moving to class member in tree view by typing in first character of member name selects parent assembly
/// </summary>
public class SharpTreeViewTextSearch : DependencyObject {
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
static extern int GetDoubleClickTime();
static readonly DependencyPropertyKey TextSearchInstancePropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"TextSearchInstance",
typeof(SharpTreeViewTextSearch), typeof(SharpTreeViewTextSearch), new FrameworkPropertyMetadata(null));
static readonly DependencyProperty TextSearchInstanceProperty = TextSearchInstancePropertyKey.DependencyProperty;
DispatcherTimer timer;
bool isActive;
int lastMatchIndex;
string matchPrefix;
readonly Stack<string> inputStack;
readonly SharpTreeView treeView;
SharpTreeViewTextSearch(SharpTreeView treeView) {
this.treeView = treeView ?? throw new ArgumentNullException(nameof(treeView));
inputStack = new Stack<string>(8);
ClearState();
}
public static SharpTreeViewTextSearch GetInstance(SharpTreeView sharpTreeView) {
var textSearch = (SharpTreeViewTextSearch)sharpTreeView.GetValue(TextSearchInstanceProperty);
if (textSearch == null) {
textSearch = new SharpTreeViewTextSearch(sharpTreeView);
sharpTreeView.SetValue(TextSearchInstancePropertyKey, textSearch);
}
return textSearch;
}
public bool RevertLastCharacter() {
if (!isActive || inputStack.Count == 0)
return false;
matchPrefix = matchPrefix.Substring(0, matchPrefix.Length - inputStack.Pop().Length);
ResetTimeout();
return true;
}
public bool Search(string nextChar) {
int startIndex = isActive ? lastMatchIndex : Math.Max(0, treeView.SelectedIndex);
bool lookBackwards = inputStack.Count > 0 &&
string.Compare(inputStack.Peek(), nextChar, StringComparison.OrdinalIgnoreCase) == 0;
int nextMatchIndex = IndexOfMatch(matchPrefix + nextChar, startIndex, lookBackwards, out bool wasNewCharUsed);
if (nextMatchIndex != -1) {
if (!isActive || nextMatchIndex != startIndex) {
treeView.SelectedItem = treeView.Items[nextMatchIndex];
treeView.FocusNode((SharpTreeNode)treeView.SelectedItem);
lastMatchIndex = nextMatchIndex;
}
if (wasNewCharUsed) {
matchPrefix += nextChar;
inputStack.Push(nextChar);
}
isActive = true;
}
if (isActive) {
ResetTimeout();
}
return nextMatchIndex != -1;
}
int IndexOfMatch(string needle, int startIndex, bool tryBackward, out bool charWasUsed) {
charWasUsed = false;
if (treeView.Items.Count == 0 || string.IsNullOrEmpty(needle))
return -1;
int index = -1;
int fallbackIndex = -1;
bool fallbackMatch = false;
int i = startIndex;
var comparisonType = treeView.IsTextSearchCaseSensitive
? StringComparison.Ordinal
: StringComparison.OrdinalIgnoreCase;
do {
var item = (SharpTreeNode)treeView.Items[i];
if (item != null && item.Text != null) {
string text = item.Text.ToString();
if (text.StartsWith(needle, comparisonType)) {
charWasUsed = true;
index = i;
break;
}
if (tryBackward) {
if (fallbackMatch && matchPrefix != string.Empty) {
if (fallbackIndex == -1 && text.StartsWith(matchPrefix, comparisonType)) {
fallbackIndex = i;
}
}
else {
fallbackMatch = true;
}
}
}
i++;
if (i >= treeView.Items.Count)
i = 0;
} while (i != startIndex);
return index == -1 ? fallbackIndex : index;
}
void ClearState() {
isActive = false;
matchPrefix = string.Empty;
lastMatchIndex = -1;
inputStack.Clear();
timer?.Stop();
timer = null;
}
void ResetTimeout() {
if (timer == null) {
timer = new DispatcherTimer(DispatcherPriority.Normal);
timer.Tick += (_, _) => ClearState();
}
else {
timer.Stop();
}
timer.Interval = TimeSpan.FromMilliseconds(GetDoubleClickTime() * 2);
timer.Start();
}
}
}