44using System . Linq ;
55using System . Text . RegularExpressions ;
66using System . Threading ;
7+ using System . Windows ;
78using System . Windows . Forms ;
89
910namespace 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