Skip to content

Commit 3fb294e

Browse files
committed
Connect IntelliSenseDisplay to UIMonitor
1 parent ed03b9f commit 3fb294e

File tree

4 files changed

+251
-77
lines changed

4 files changed

+251
-77
lines changed

Source/ExcelDna.IntelliSense/FormattedText.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace ExcelDna.IntelliSense
66
{
7+
// TODO: Needs cleaning up for efficiency
8+
79
class FormattedText : IEnumerable<TextLine>
810
{
911
readonly List<TextLine> _lines;

Source/ExcelDna.IntelliSense/IntelliSenseDisplay.cs

Lines changed: 188 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Text.RegularExpressions;
66
using System.Threading;
7+
using System.Windows;
78
using System.Windows.Forms;
89

910
namespace ExcelDna.IntelliSense
@@ -29,7 +30,9 @@ class IntelliSenseDisplay : IDisposable
2930
{
3031

3132
// SyncContextMain is running on the main Excel thread (not a 'macro' context, though)
32-
// Not sure we need this here... (the UIMonitor internally uses it, and raises all events on the main thread).
33+
// Not sure we need this here... (the UIMonitor internally references it, and could raise the update events on the main thread...).
34+
// CONSIDER: We could send in the two filters for selecteditem and formula change, so that these checks don't run on the main thread...
35+
3336
SynchronizationContext _syncContextMain;
3437
readonly UIMonitor _uiMonitor;
3538

@@ -39,6 +42,7 @@ class IntelliSenseDisplay : IDisposable
3942
// Need to make these late ...?
4043
ToolTipForm _descriptionToolTip;
4144
ToolTipForm _argumentsToolTip;
45+
IntPtr _mainWindow;
4246

4347
public IntelliSenseDisplay(SynchronizationContext syncContextMain, UIMonitor uiMonitor)
4448
{
@@ -49,77 +53,196 @@ public IntelliSenseDisplay(SynchronizationContext syncContextMain, UIMonitor uiM
4953

5054
_syncContextMain = syncContextMain;
5155
_uiMonitor = uiMonitor;
52-
_uiMonitor.StateChanged += _uiMonitor_StateChanged;
56+
_uiMonitor.StateUpdateFilter = StateUpdateFilter;
57+
_uiMonitor.StateUpdate = StateUpdate;
5358
}
5459

55-
private void _uiMonitor_StateChanged(object sender, UIStateUpdate e)
60+
// This runs on the UIMonitor's automation thread
61+
bool StateUpdateFilter(UIStateUpdate update)
5662
{
57-
Debug.Print($"STATE UPDATE ({e.Update}): {e.OldState} => {e.NewState}");
63+
if (update.Update == UIStateUpdate.UpdateType.FormulaEditTextChange)
64+
{
65+
var fe = (UIState.FormulaEdit)update.NewState;
66+
return ShouldProcessFormulaEditTextChange(fe.FormulaPrefix);
67+
}
68+
else if (update.Update == UIStateUpdate.UpdateType.FunctionListSelectedItemChange)
69+
{
70+
var fl = (UIState.FunctionList)update.NewState;
71+
return ShouldProcessFunctionListSelectedItemChange(fl.SelectedItemText);
72+
}
73+
else
74+
{
75+
return true; // allow the update event to be raised
76+
}
5877
}
5978

79+
// This runs on the main thread
80+
void StateUpdate(UIStateUpdate update)
81+
{
82+
Debug.Print($"STATE UPDATE ({update.Update}): {update.OldState} => {update.NewState}");
6083

61-
//// Runs on the main thread
62-
//void MainWindowChanged(object _unused_)
63-
//{
64-
// // TODO: This is to guard against shutdown, but we should not have a race here
65-
// // - shutdown should be on the main thread, as is this event handler.
66-
// if (_windowWatcher == null)
67-
// return;
84+
switch (update.Update)
85+
{
86+
case UIStateUpdate.UpdateType.FormulaEditStart:
87+
UpdateMainWindow((update.NewState as UIState.FormulaEdit).MainWindow);
88+
FormulaEditStart();
89+
break;
90+
case UIStateUpdate.UpdateType.FormulaEditMove:
91+
break;
92+
case UIStateUpdate.UpdateType.FormulaEditMainWindowChange:
93+
UpdateMainWindow((update.NewState as UIState.FormulaEdit).MainWindow);
94+
break;
95+
case UIStateUpdate.UpdateType.FormulaEditTextChange:
96+
var fe = (UIState.FormulaEdit)update.NewState;
97+
FormulaEditTextChange(fe.FormulaPrefix, fe.EditWindowBounds, fe.ExcelTooltipBounds);
98+
break;
99+
case UIStateUpdate.UpdateType.FunctionListShow:
100+
FunctionListShow();
101+
var fls = (UIState.FunctionList)update.NewState;
102+
FunctionListSelectedItemChange(fls.SelectedItemText, fls.SelectedItemBounds);
103+
break;
104+
case UIStateUpdate.UpdateType.FunctionListMove:
105+
break;
106+
case UIStateUpdate.UpdateType.FunctionListSelectedItemChange:
107+
var fl = (UIState.FunctionList)update.NewState;
108+
FunctionListSelectedItemChange(fl.SelectedItemText, fl.SelectedItemBounds);
109+
break;
110+
case UIStateUpdate.UpdateType.FunctionListHide:
111+
FunctionListHide();
112+
break;
113+
case UIStateUpdate.UpdateType.FormulaEditEnd:
114+
FormulaEditEnd();
115+
break;
116+
case UIStateUpdate.UpdateType.SelectDataSourceShow:
117+
case UIStateUpdate.UpdateType.SelectDataSourceMainWindowChange:
118+
case UIStateUpdate.UpdateType.SelectDataSourceHide:
119+
// We ignore these
120+
break;
121+
default:
122+
throw new InvalidOperationException("Unexpected UIStateUpdate");
123+
}
124+
}
68125

69-
// // TODO: !!! Reset / re-parent ToolTipWindows
70-
// Debug.Print($"IntelliSenseDisplay - MainWindowChanged - New window - {_windowWatcher.MainWindow:X}, Thread {Thread.CurrentThread.ManagedThreadId}");
126+
// Runs on the main thread
127+
void UpdateMainWindow(IntPtr mainWindow)
128+
{
129+
if (_mainWindow != mainWindow &&
130+
(_descriptionToolTip != null ||
131+
_argumentsToolTip != null ))
132+
{
133+
if (_descriptionToolTip != null)
134+
{
135+
_descriptionToolTip.Dispose();
136+
_descriptionToolTip = new ToolTipForm(_mainWindow);
137+
}
138+
if (_argumentsToolTip != null)
139+
{
140+
_argumentsToolTip.Dispose();
141+
_argumentsToolTip = new ToolTipForm(_mainWindow);
142+
}
143+
_mainWindow = mainWindow;
144+
}
145+
}
71146

72-
// // _descriptionToolTip.SetOwner(e.Handle); // Not Parent, of course!
73-
// if (_descriptionToolTip != null)
74-
// {
75-
// if (_descriptionToolTip.OwnerHandle != _windowWatcher.MainWindow)
76-
// {
77-
// _descriptionToolTip.Dispose();
78-
// _descriptionToolTip = null;
79-
// }
80-
// }
81-
// if (_argumentsToolTip != null)
82-
// {
83-
// if (_argumentsToolTip.OwnerHandle != _windowWatcher.MainWindow)
84-
// {
85-
// _argumentsToolTip.Dispose();
86-
// _argumentsToolTip = null;
87-
// }
88-
// }
89-
// // _descriptionToolTip = new ToolTipWindow("", _windowWatcher.MainWindow);
90-
//}
147+
// Runs on the main thread
148+
void FunctionListShow()
149+
{
150+
if (_descriptionToolTip == null)
151+
_descriptionToolTip = new ToolTipForm(_mainWindow);
152+
}
91153

92-
//// Runs on the main thread
93-
//void PopupListSelectedItemChanged(object _unused_)
94-
//{
95-
// Debug.Print($"IntelliSenseDisplay - PopupListSelectedItemChanged - New text - {_popupListWatcher?.SelectedItemText}, Thread {Thread.CurrentThread.ManagedThreadId}");
154+
// Runs on the main thread
155+
void FunctionListHide()
156+
{
157+
_descriptionToolTip.Hide();
158+
}
96159

97-
// if (_popupListWatcher == null)
98-
// return;
99-
// string functionName = _popupListWatcher.SelectedItemText;
160+
// Runs on the main thread
161+
void FormulaEditStart()
162+
{
163+
if (_argumentsToolTip == null)
164+
_argumentsToolTip = new ToolTipForm(_mainWindow);
165+
}
100166

101-
// IntelliSenseFunctionInfo functionInfo;
102-
// if ( _functionInfoMap.TryGetValue(functionName, out functionInfo))
103-
// {
104-
// if (_descriptionToolTip == null)
105-
// {
106-
// _descriptionToolTip = new ToolTipForm(_windowWatcher.MainWindow);
107-
// _argumentsToolTip = new ToolTipForm(_windowWatcher.MainWindow);
108-
// }
109-
// // It's ours!
110-
// _descriptionToolTip.ShowToolTip(
111-
// text: new FormattedText { GetFunctionDescription(functionInfo) },
112-
// left: (int)_popupListWatcher.SelectedItemBounds.Right + 25,
113-
// top: (int)_popupListWatcher.SelectedItemBounds.Top);
114-
// }
115-
// else
116-
// {
117-
// if (_descriptionToolTip != null)
118-
// {
119-
// _descriptionToolTip.Hide();
120-
// }
121-
// }
122-
//}
167+
// Runs on the main thread
168+
void FormulaEditTextChange(string formulaPrefix, Rect editWindowBounds, Rect excelTooltipBounds)
169+
{
170+
Debug.Print($"^^^ FormulaEditStateChanged. CurrentPrefix: {formulaPrefix}, Thread {Thread.CurrentThread.ManagedThreadId}");
171+
var match = Regex.Match(formulaPrefix, @"^=(?<functionName>\w*)\(");
172+
if (match.Success)
173+
{
174+
string functionName = match.Groups["functionName"].Value;
175+
176+
IntelliSenseFunctionInfo functionInfo;
177+
if (_functionInfoMap.TryGetValue(functionName, out functionInfo))
178+
{
179+
// TODO: Fix this: Need to consider subformulae
180+
int currentArgIndex = formulaPrefix.Count(c => c == ',');
181+
_argumentsToolTip.ShowToolTip(
182+
GetFunctionIntelliSense(functionInfo, currentArgIndex),
183+
(int)editWindowBounds.Left, (int)editWindowBounds.Bottom + 5);
184+
return;
185+
}
186+
}
187+
188+
// All other paths, we just clear the box
189+
_argumentsToolTip.Hide();
190+
}
191+
192+
// Runs on the main thread
193+
void FormulaEditEnd()
194+
{
195+
_argumentsToolTip.Hide();
196+
}
197+
198+
// Runs on the UIMonitor's automation thread - return true if we might want to process
199+
bool ShouldProcessFunctionListSelectedItemChange(string selectedItemText)
200+
{
201+
if (_descriptionToolTip?.Visible == true)
202+
return true;
203+
204+
return _functionInfoMap.ContainsKey(selectedItemText);
205+
}
206+
207+
// Runs on the UIMonitor's automation thread - return true if we might want to process
208+
bool ShouldProcessFormulaEditTextChange(string formulaPrefix)
209+
{
210+
// CAREFUL: Because of threading, this might run before FormulaEditStart!
211+
212+
if (_argumentsToolTip?.Visible == true)
213+
return true;
214+
215+
// TODO: Consolidate the check here with the FormulaMonitor
216+
var match = Regex.Match(formulaPrefix, @"^=(?<functionName>\w*)\(");
217+
if (match.Success)
218+
{
219+
string functionName = match.Groups["functionName"].Value;
220+
return _functionInfoMap.ContainsKey(functionName);
221+
}
222+
// Not interested...
223+
Debug.Print($"Not processing formula {formulaPrefix}");
224+
return false;
225+
}
226+
227+
// Runs on the main thread
228+
void FunctionListSelectedItemChange(string selectedItemText, Rect selectedItemBounds)
229+
{
230+
Debug.Print($"IntelliSenseDisplay - PopupListSelectedItemChanged - New text - {selectedItemText}, Thread {Thread.CurrentThread.ManagedThreadId}");
231+
232+
IntelliSenseFunctionInfo functionInfo;
233+
if (_functionInfoMap.TryGetValue(selectedItemText, out functionInfo))
234+
{
235+
// It's ours!
236+
_descriptionToolTip.ShowToolTip(
237+
text: new FormattedText { GetFunctionDescription(functionInfo) },
238+
left: (int)selectedItemBounds.Right + 25,
239+
top: (int)selectedItemBounds.Top);
240+
}
241+
else
242+
{
243+
FunctionListHide();
244+
}
245+
}
123246

124247
//// Runs on the main thread
125248
//// TODO: Need better formula parsing story here
@@ -172,12 +295,15 @@ private void _uiMonitor_StateChanged(object sender, UIStateUpdate e)
172295
// _argumentsToolTip.Hide();
173296
//}
174297

298+
299+
// TODO: Performance / efficiency - cache these somehow
175300
// TODO: Probably not a good place for LINQ !?
301+
static readonly string[] s_newLineStringArray = new string[] { Environment.NewLine };
176302
IEnumerable<TextLine> GetFunctionDescription(IntelliSenseFunctionInfo functionInfo)
177303
{
178304
return
179305
functionInfo.Description
180-
.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)
306+
.Split(s_newLineStringArray, StringSplitOptions.None)
181307
.Select(line =>
182308
new TextLine {
183309
new TextRun

0 commit comments

Comments
 (0)