Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

[Mac] TextEntry completion support #1014

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions TestApps/Samples/Samples/TextEntries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ public TextEntries ()
la.Text = "Text: " + te1.Text;
};

te1.SetCompletions(new string [] {
"Lorem",
"Ipsum",
"Word1",
"Word2",
"Lorem Ipsum",
"Sample",
"Text"
});

HBox selBox = new HBox ();

Label las = new Label ("Selection:");
Expand Down
63 changes: 34 additions & 29 deletions Xwt.XamMac/Xwt.Mac/ComboBoxEntryBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ()
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -102,7 +106,31 @@ public void SetTextColumn (int column)
#endregion
}

class MacComboBox : NSComboBox, IViewObject, INSComboBoxDelegate
sealed 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();
});
}
}
}

sealed class MacComboBox : NSComboBox, IViewObject
{
IComboBoxEventSink eventSink;
ITextEntryEventSink entryEventSink;
Expand All @@ -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)
Expand All @@ -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 ()
{
Expand Down Expand Up @@ -329,4 +335,3 @@ protected override void Dispose (bool disposing)
}
}
}

108 changes: 97 additions & 11 deletions Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
// THE SOFTWARE.

using System;
using System.Linq;
using AppKit;
using CoreGraphics;
using Foundation;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -232,16 +233,51 @@ void HandleSelectionChanged ()
}
}

string[] completions;
Func<string, string, bool> 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<string, string, bool> 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand Down