|
25 | 25 | // THE SOFTWARE. |
26 | 26 |
|
27 | 27 | using System; |
| 28 | +using System.Linq; |
28 | 29 | using AppKit; |
29 | 30 | using CoreGraphics; |
30 | 31 | using Foundation; |
@@ -52,7 +53,7 @@ public override void Initialize () |
52 | 53 | if (ViewObject is MacComboBox) { |
53 | 54 | ((MacComboBox)ViewObject).SetEntryEventSink (EventSink); |
54 | 55 | } else if (ViewObject == null) { |
55 | | - var view = new CustomTextField (EventSink, ApplicationContext); |
| 56 | + var view = new CustomTextField (EventSink, ApplicationContext) { Backend = this }; |
56 | 57 | ViewObject = new CustomAlignedContainer (EventSink, ApplicationContext, (NSView)view) { DrawsBackground = false }; |
57 | 58 | Container.ExpandVertically = true; |
58 | 59 | MultiLine = false; |
@@ -232,16 +233,51 @@ void HandleSelectionChanged () |
232 | 233 | } |
233 | 234 | } |
234 | 235 |
|
| 236 | + string[] completions; |
| 237 | + Func<string, string, bool> completionsMatchFunc; |
| 238 | + |
235 | 239 | public bool HasCompletions { |
236 | | - get { return false; } |
| 240 | + get { |
| 241 | + return completions?.Length > 0; |
| 242 | + } |
237 | 243 | } |
238 | 244 |
|
239 | 245 | public void SetCompletions (string[] completions) |
240 | 246 | { |
| 247 | + this.completions = completions; |
| 248 | + if (completions != null) { |
| 249 | + var entryDelegate = Widget.Delegate; |
| 250 | + if (entryDelegate == null) { |
| 251 | + entryDelegate = Widget.Delegate = new TextFieldDelegate (); |
| 252 | + } |
| 253 | + if (entryDelegate is TextFieldDelegate) { |
| 254 | + ((TextFieldDelegate)entryDelegate).Backend = this; |
| 255 | + } |
| 256 | + } |
| 257 | + if (completionsMatchFunc == null) { |
| 258 | + completionsMatchFunc = DefaultCompletionMatchFunc; |
| 259 | + } |
241 | 260 | } |
242 | 261 |
|
243 | 262 | public void SetCompletionMatchFunc (Func<string, string, bool> matchFunc) |
244 | 263 | { |
| 264 | + completionsMatchFunc = matchFunc ?? DefaultCompletionMatchFunc; |
| 265 | + } |
| 266 | + |
| 267 | + bool DefaultCompletionMatchFunc (string word, string completion) |
| 268 | + { |
| 269 | + if (word == null || completion == null) |
| 270 | + return false; |
| 271 | + return completion.StartsWith (word, StringComparison.CurrentCulture); |
| 272 | + } |
| 273 | + |
| 274 | + internal string [] GetCompletions (string word) |
| 275 | + { |
| 276 | + if (completions?.Length > 0) |
| 277 | + { |
| 278 | + return completions.Where (c => completionsMatchFunc(word, c)).ToArray (); |
| 279 | + } |
| 280 | + return new string[0]; |
245 | 281 | } |
246 | 282 |
|
247 | 283 | #endregion |
@@ -283,6 +319,65 @@ public override Drawing.Color BackgroundColor { |
283 | 319 | Widget.Cell.BackgroundColor = value.ToNSColor (); |
284 | 320 | } |
285 | 321 | } |
| 322 | + |
| 323 | + protected override void Dispose(bool disposing) |
| 324 | + { |
| 325 | + completions = null; |
| 326 | + base.Dispose (disposing); |
| 327 | + } |
| 328 | + } |
| 329 | + |
| 330 | + class TextFieldDelegate : NSTextFieldDelegate |
| 331 | + { |
| 332 | + WeakReference weakBackend; |
| 333 | + |
| 334 | + public TextEntryBackend Backend |
| 335 | + { |
| 336 | + get { return weakBackend?.Target as TextEntryBackend; } |
| 337 | + set { weakBackend = new WeakReference (value); } |
| 338 | + } |
| 339 | + |
| 340 | + public override string[] GetCompletions (NSControl control, NSTextView textView, string[] words, NSRange charRange, ref nint index) |
| 341 | + { |
| 342 | + var backend = Backend; |
| 343 | + if (backend != null) |
| 344 | + { |
| 345 | + string word; |
| 346 | + try { |
| 347 | + word = textView.String.Substring ((int)charRange.Location, (int)charRange.Length); |
| 348 | + } catch (ArgumentOutOfRangeException) { |
| 349 | + return new string[0]; |
| 350 | + } |
| 351 | + return backend.GetCompletions (word); |
| 352 | + } |
| 353 | + return new string[0]; |
| 354 | + } |
| 355 | + |
| 356 | + bool isCompleting; |
| 357 | + [Export("controlTextDidChange:")] |
| 358 | + public void DidChange (NSNotification notification) |
| 359 | + { |
| 360 | + var editor = notification.Object as NSTextView ?? (notification.Object as NSTextField)?.CurrentEditor as NSTextView; |
| 361 | + if (!isCompleting && editor != null && editor.String.Length > 0 && Backend?.HasCompletions == true) { |
| 362 | + // Cocoa will call DidChange for each completion, even if the text didn't change |
| 363 | + // avoid an infinite loop with an isCompleting check. |
| 364 | + isCompleting = true; |
| 365 | + editor.Complete (null); |
| 366 | + isCompleting = false; |
| 367 | + } |
| 368 | + if (Backend.EventSink != null) { |
| 369 | + Backend.ApplicationContext.InvokeUserCode (delegate { |
| 370 | + Backend.EventSink.OnChanged (); |
| 371 | + Backend.EventSink.OnSelectionChanged (); |
| 372 | + }); |
| 373 | + } |
| 374 | + } |
| 375 | + |
| 376 | + protected override void Dispose(bool disposing) |
| 377 | + { |
| 378 | + weakBackend = null; |
| 379 | + base.Dispose(disposing); |
| 380 | + } |
286 | 381 | } |
287 | 382 |
|
288 | 383 | class CustomTextField: NSTextField, IViewObject |
@@ -313,15 +408,6 @@ public NSView View { |
313 | 408 | } |
314 | 409 |
|
315 | 410 | public ViewBackend Backend { get; set; } |
316 | | - |
317 | | - public override void DidChange (NSNotification notification) |
318 | | - { |
319 | | - base.DidChange (notification); |
320 | | - context.InvokeUserCode (delegate { |
321 | | - eventSink.OnChanged (); |
322 | | - eventSink.OnSelectionChanged (); |
323 | | - }); |
324 | | - } |
325 | 411 |
|
326 | 412 | public override string StringValue |
327 | 413 | { |
|
0 commit comments