diff --git a/Xwt.XamMac/Xwt.Mac/ComboBoxEntryBackend.cs b/Xwt.XamMac/Xwt.Mac/ComboBoxEntryBackend.cs index 1c6cb0269..a037f2a45 100644 --- a/Xwt.XamMac/Xwt.Mac/ComboBoxEntryBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/ComboBoxEntryBackend.cs @@ -43,7 +43,7 @@ public ComboBoxEntryBackend () public override void Initialize () { base.Initialize (); - ViewObject = new MacComboBox (EventSink, ApplicationContext); + ViewObject = new MacComboBox (this); } protected override Size GetNaturalSize () @@ -56,7 +56,11 @@ protected override Size GetNaturalSize () public ITextEntryBackend TextEntryBackend { get { if (entryBackend == null) + { entryBackend = new Xwt.Mac.TextEntryBackend ((MacComboBox)ViewObject); + if (Widget.Delegate is TextFieldDelegate) + ((TextFieldDelegate)Widget.Delegate).Backend = entryBackend; + } return entryBackend; } } @@ -102,7 +106,31 @@ public void SetTextColumn (int column) #endregion } - class MacComboBox : NSComboBox, IViewObject, INSComboBoxDelegate + class MacComboBoxDelegate : TextFieldDelegate, INSComboBoxDelegate + { + WeakReference weakBackend; + + public ComboBoxEntryBackend ComboBoxEntryBackend + { + get { return weakBackend?.Target as ComboBoxEntryBackend; } + set { weakBackend = new WeakReference(value); } + } + + [Export("comboBoxSelectionDidChange:")] + public void SelectionChanged (NSNotification notification) + { + var combobackend = ComboBoxEntryBackend; + if (combobackend?.EventSink != null) + { + combobackend.ApplicationContext.InvokeUserCode(delegate { + Backend?.EventSink?.OnChanged(); + combobackend.EventSink.OnSelectionChanged(); + }); + } + } + } + + class MacComboBox : NSComboBox, IViewObject { IComboBoxEventSink eventSink; ITextEntryEventSink entryEventSink; @@ -111,11 +139,11 @@ class MacComboBox : NSComboBox, IViewObject, INSComboBoxDelegate int cacheSelectionStart, cacheSelectionLength; bool checkMouseMovement; - public MacComboBox (IComboBoxEventSink eventSink, ApplicationContext context) + public MacComboBox (ComboBoxEntryBackend backend) { - this.context = context; - this.eventSink = eventSink; - Delegate = this; + context = backend.ApplicationContext; + eventSink = backend.EventSink; + Delegate = new MacComboBoxDelegate { ComboBoxEntryBackend = backend }; } public void SetEntryEventSink (ITextEntryEventSink entryEventSink) @@ -131,34 +159,12 @@ public NSView View { public ViewBackend Backend { get; set; } - [Export ("comboBoxSelectionDidChange:")] - public new void SelectionChanged (NSNotification notification) - { - if (entryEventSink != null) { - context.InvokeUserCode (delegate { - entryEventSink.OnChanged (); - eventSink.OnSelectionChanged (); - }); - } - } - - public override void DidChange (NSNotification notification) - { - base.DidChange (notification); - if (entryEventSink != null) { - context.InvokeUserCode (delegate { - entryEventSink.OnChanged (); - entryEventSink.OnSelectionChanged (); - }); - } - } public override void KeyUp (NSEvent theEvent) { base.KeyUp (theEvent); HandleSelectionChanged (); } - NSTrackingArea trackingArea; public override void UpdateTrackingAreas () { diff --git a/Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs b/Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs index a466ba2c8..720fb88c4 100644 --- a/Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs @@ -25,6 +25,7 @@ // THE SOFTWARE. using System; +using System.Linq; using AppKit; using CoreGraphics; using Foundation; @@ -52,7 +53,7 @@ public override void Initialize () if (ViewObject is MacComboBox) { ((MacComboBox)ViewObject).SetEntryEventSink (EventSink); } else if (ViewObject == null) { - var view = new CustomTextField (EventSink, ApplicationContext); + var view = new CustomTextField (EventSink, ApplicationContext) { Backend = this }; ViewObject = new CustomAlignedContainer (EventSink, ApplicationContext, (NSView)view) { DrawsBackground = false }; Container.ExpandVertically = true; MultiLine = false; @@ -232,16 +233,51 @@ void HandleSelectionChanged () } } + string[] completions; + Func completionsMatchFunc; + public bool HasCompletions { - get { return false; } + get { + return completions?.Length > 0; + } } public void SetCompletions (string[] completions) { + this.completions = completions; + if (completions != null) { + var entryDelegate = Widget.Delegate; + if (entryDelegate == null) { + entryDelegate = Widget.Delegate = new TextFieldDelegate (); + } + if (entryDelegate is TextFieldDelegate) { + ((TextFieldDelegate)entryDelegate).Backend = this; + } + } + if (completionsMatchFunc == null) { + completionsMatchFunc = DefaultCompletionMatchFunc; + } } public void SetCompletionMatchFunc (Func matchFunc) { + completionsMatchFunc = matchFunc ?? DefaultCompletionMatchFunc; + } + + bool DefaultCompletionMatchFunc (string word, string completion) + { + if (word == null || completion == null) + return false; + return completion.StartsWith (word, StringComparison.CurrentCulture); + } + + internal string [] GetCompletions (string word) + { + if (completions?.Length > 0) + { + return completions.Where (c => completionsMatchFunc(word, c)).ToArray (); + } + return new string[0]; } #endregion @@ -283,6 +319,65 @@ public override Drawing.Color BackgroundColor { Widget.Cell.BackgroundColor = value.ToNSColor (); } } + + protected override void Dispose(bool disposing) + { + completions = null; + base.Dispose (disposing); + } + } + + class TextFieldDelegate : NSTextFieldDelegate + { + WeakReference weakBackend; + + public TextEntryBackend Backend + { + get { return weakBackend?.Target as TextEntryBackend; } + set { weakBackend = new WeakReference (value); } + } + + public override string[] GetCompletions (NSControl control, NSTextView textView, string[] words, NSRange charRange, ref nint index) + { + var backend = Backend; + if (backend != null) + { + string word; + try { + word = textView.String.Substring ((int)charRange.Location, (int)charRange.Length); + } catch (ArgumentOutOfRangeException) { + return new string[0]; + } + return backend.GetCompletions (word); + } + return new string[0]; + } + + bool isCompleting; + [Export("controlTextDidChange:")] + public void DidChange (NSNotification notification) + { + var editor = notification.Object as NSTextView ?? (notification.Object as NSTextField)?.CurrentEditor as NSTextView; + if (!isCompleting && editor != null && editor.String.Length > 0 && Backend?.HasCompletions == true) { + // Cocoa will call DidChange for each completion, even if the text didn't change + // avoid an infinite loop with an isCompleting check. + isCompleting = true; + editor.Complete (null); + isCompleting = false; + } + if (Backend.EventSink != null) { + Backend.ApplicationContext.InvokeUserCode (delegate { + Backend.EventSink.OnChanged (); + Backend.EventSink.OnSelectionChanged (); + }); + } + } + + protected override void Dispose(bool disposing) + { + weakBackend = null; + base.Dispose(disposing); + } } class CustomTextField: NSTextField, IViewObject @@ -313,15 +408,6 @@ public NSView View { } public ViewBackend Backend { get; set; } - - public override void DidChange (NSNotification notification) - { - base.DidChange (notification); - context.InvokeUserCode (delegate { - eventSink.OnChanged (); - eventSink.OnSelectionChanged (); - }); - } public override string StringValue {