|
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