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

Commit 0c47f53

Browse files
committed
[Mac] Add TextEntry completion support
1 parent ffbc049 commit 0c47f53

File tree

2 files changed

+131
-39
lines changed

2 files changed

+131
-39
lines changed

Xwt.XamMac/Xwt.Mac/ComboBoxEntryBackend.cs

+34-28
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public ComboBoxEntryBackend ()
4343
public override void Initialize ()
4444
{
4545
base.Initialize ();
46-
ViewObject = new MacComboBox (EventSink, ApplicationContext);
46+
ViewObject = new MacComboBox (this);
4747
}
4848

4949
protected override Size GetNaturalSize ()
@@ -56,7 +56,11 @@ protected override Size GetNaturalSize ()
5656
public ITextEntryBackend TextEntryBackend {
5757
get {
5858
if (entryBackend == null)
59+
{
5960
entryBackend = new Xwt.Mac.TextEntryBackend ((MacComboBox)ViewObject);
61+
if (Widget.Delegate is TextFieldDelegate)
62+
((TextFieldDelegate)Widget.Delegate).Backend = entryBackend;
63+
}
6064
return entryBackend;
6165
}
6266
}
@@ -102,7 +106,31 @@ public void SetTextColumn (int column)
102106
#endregion
103107
}
104108

105-
class MacComboBox : NSComboBox, IViewObject, INSComboBoxDelegate
109+
class MacComboBoxDelegate : TextFieldDelegate, INSComboBoxDelegate
110+
{
111+
WeakReference weakBackend;
112+
113+
public ComboBoxEntryBackend ComboBoxEntryBackend
114+
{
115+
get { return weakBackend?.Target as ComboBoxEntryBackend; }
116+
set { weakBackend = new WeakReference(value); }
117+
}
118+
119+
[Export("comboBoxSelectionDidChange:")]
120+
public void SelectionChanged (NSNotification notification)
121+
{
122+
var combobackend = ComboBoxEntryBackend;
123+
if (combobackend?.EventSink != null)
124+
{
125+
combobackend.ApplicationContext.InvokeUserCode(delegate {
126+
Backend?.EventSink?.OnChanged();
127+
combobackend.EventSink.OnSelectionChanged();
128+
});
129+
}
130+
}
131+
}
132+
133+
class MacComboBox : NSComboBox, IViewObject
106134
{
107135
IComboBoxEventSink eventSink;
108136
ITextEntryEventSink entryEventSink;
@@ -111,11 +139,11 @@ class MacComboBox : NSComboBox, IViewObject, INSComboBoxDelegate
111139
int cacheSelectionStart, cacheSelectionLength;
112140
bool checkMouseMovement;
113141

114-
public MacComboBox (IComboBoxEventSink eventSink, ApplicationContext context)
142+
public MacComboBox (ComboBoxEntryBackend backend)
115143
{
116-
this.context = context;
117-
this.eventSink = eventSink;
118-
Delegate = this;
144+
context = backend.ApplicationContext;
145+
eventSink = backend.EventSink;
146+
Delegate = new MacComboBoxDelegate { ComboBoxEntryBackend = backend };
119147
}
120148

121149
public void SetEntryEventSink (ITextEntryEventSink entryEventSink)
@@ -131,34 +159,12 @@ public NSView View {
131159

132160
public ViewBackend Backend { get; set; }
133161

134-
[Export ("comboBoxSelectionDidChange:")]
135-
public new void SelectionChanged (NSNotification notification)
136-
{
137-
if (entryEventSink != null) {
138-
context.InvokeUserCode (delegate {
139-
entryEventSink.OnChanged ();
140-
eventSink.OnSelectionChanged ();
141-
});
142-
}
143-
}
144-
145-
public override void DidChange (NSNotification notification)
146-
{
147-
base.DidChange (notification);
148-
if (entryEventSink != null) {
149-
context.InvokeUserCode (delegate {
150-
entryEventSink.OnChanged ();
151-
entryEventSink.OnSelectionChanged ();
152-
});
153-
}
154-
}
155162
public override void KeyUp (NSEvent theEvent)
156163
{
157164
base.KeyUp (theEvent);
158165
HandleSelectionChanged ();
159166
}
160167

161-
162168
NSTrackingArea trackingArea;
163169
public override void UpdateTrackingAreas ()
164170
{

Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs

+97-11
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
// THE SOFTWARE.
2626

2727
using System;
28+
using System.Linq;
2829
using AppKit;
2930
using CoreGraphics;
3031
using Foundation;
@@ -52,7 +53,7 @@ public override void Initialize ()
5253
if (ViewObject is MacComboBox) {
5354
((MacComboBox)ViewObject).SetEntryEventSink (EventSink);
5455
} else if (ViewObject == null) {
55-
var view = new CustomTextField (EventSink, ApplicationContext);
56+
var view = new CustomTextField (EventSink, ApplicationContext) { Backend = this };
5657
ViewObject = new CustomAlignedContainer (EventSink, ApplicationContext, (NSView)view) { DrawsBackground = false };
5758
Container.ExpandVertically = true;
5859
MultiLine = false;
@@ -232,16 +233,51 @@ void HandleSelectionChanged ()
232233
}
233234
}
234235

236+
string[] completions;
237+
Func<string, string, bool> completionsMatchFunc;
238+
235239
public bool HasCompletions {
236-
get { return false; }
240+
get {
241+
return completions?.Length > 0;
242+
}
237243
}
238244

239245
public void SetCompletions (string[] completions)
240246
{
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+
}
241260
}
242261

243262
public void SetCompletionMatchFunc (Func<string, string, bool> matchFunc)
244263
{
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];
245281
}
246282

247283
#endregion
@@ -283,6 +319,65 @@ public override Drawing.Color BackgroundColor {
283319
Widget.Cell.BackgroundColor = value.ToNSColor ();
284320
}
285321
}
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+
}
286381
}
287382

288383
class CustomTextField: NSTextField, IViewObject
@@ -313,15 +408,6 @@ public NSView View {
313408
}
314409

315410
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-
}
325411

326412
public override string StringValue
327413
{

0 commit comments

Comments
 (0)