Skip to content

Commit e98b0c0

Browse files
authored
Withdraw textPropertyForNode API, add removeKeyboardTypeForNode API (#442)
1 parent 277ccf6 commit e98b0c0

7 files changed

Lines changed: 115 additions & 132 deletions

File tree

gradle/include/android/grandroid_ext.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,9 @@ jobject substrateGetActivity();
5858
#ifdef SUBSTRATE
5959
void __attribute__((weak)) attach_setActivityResult(jint requestCode, jint resultCode, jobject intent) {}
6060
void __attribute__((weak)) attach_setLifecycleEvent(const char *event) {}
61-
void __attribute__((weak)) attach_setComposingText(const char *id, const char *text) {}
6261
#else
6362
void attach_setActivityResult(jint requestCode, jint resultCode, jobject intent);
6463
void attach_setLifecycleEvent(const char *event);
65-
void attach_setComposingText(const char *id, const char *text);
6664
#endif
6765

6866
#define ATTACH_GRAAL() \

modules/keyboard/src/main/java/com/gluonhq/attach/keyboard/KeyboardService.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929

3030
import com.gluonhq.attach.util.Services;
3131
import javafx.beans.property.ReadOnlyFloatProperty;
32-
import javafx.beans.property.ReadOnlyStringProperty;
3332
import javafx.scene.Node;
3433
import javafx.scene.Parent;
3534

@@ -79,6 +78,16 @@ static Optional<KeyboardService> create() {
7978
*/
8079
void keepVisibilityForNode(Node node, Parent parent);
8180

81+
/**
82+
* Stops adjusting the node when the software keyboard shows up,
83+
* removing the listener previously registered via
84+
* {@link #keepVisibilityForNode(Node)} or {@link #keepVisibilityForNode(Node, Parent)}.
85+
*
86+
* @param node the Node that was previously registered
87+
* @since 4.0.25
88+
*/
89+
void releaseVisibilityForNode(Node node);
90+
8291
/**
8392
* Gets the visible height of the Keyboard, so scene or views can adjusted
8493
* to prevent some of their content from being covered by the keyboard.
@@ -101,17 +110,13 @@ static Optional<KeyboardService> create() {
101110
void setKeyboardTypeForNode(Node node, KeyboardType type);
102111

103112
/**
104-
* Returns a read-only property that reflects the current composing text for the given node
105-
* (typically a {@link javafx.scene.control.TextInputControl}), as reported by the native IME.
106-
*
107-
* <p>Note that the JavaFX text input control default {@code textProperty()} will still
108-
* catch all the internals of the text composition when predictive text is enabled (that could show
109-
* partial text being removed and added back again while the user is typing)</p>
113+
* Removes the keyboard type assignment and event filter previously installed via
114+
* {@link #setKeyboardTypeForNode(Node, KeyboardType)}. After this call the node
115+
* will simply use the default keyboard type.
110116
*
111-
* @param node the node whose text to observe
112-
* @return a ReadOnlyStringProperty with the composed text for the given node
117+
* @param node the node to unregister
113118
* @since 4.0.25
114119
*/
115-
ReadOnlyStringProperty textPropertyForNode(Node node);
120+
void removeKeyboardTypeForNode(Node node);
116121

117122
}

modules/keyboard/src/main/java/com/gluonhq/attach/keyboard/impl/AndroidKeyboardService.java

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@
2828
package com.gluonhq.attach.keyboard.impl;
2929

3030
import javafx.application.Platform;
31-
import javafx.beans.property.ReadOnlyFloatProperty;
32-
import javafx.scene.Node;
33-
import javafx.scene.Parent;
3431

3532
public class AndroidKeyboardService extends BaseKeyboardService {
3633

@@ -41,20 +38,6 @@ public class AndroidKeyboardService extends BaseKeyboardService {
4138
public AndroidKeyboardService() {
4239
}
4340

44-
@Override
45-
public void keepVisibilityForNode(Node node) {
46-
keepVisibilityForNode(node, null);
47-
}
48-
49-
@Override
50-
public void keepVisibilityForNode(Node node, Parent parent) {
51-
VISIBLE_HEIGHT.addListener((obs, ov, nv) -> adjustPosition(node, parent, nv.doubleValue()));
52-
}
53-
54-
@Override
55-
public ReadOnlyFloatProperty visibleHeightProperty() {
56-
return VISIBLE_HEIGHT.getReadOnlyProperty();
57-
}
5841

5942
@Override
6043
protected void applyKeyboardType(int nativeValue) {
@@ -77,12 +60,4 @@ private static void notifyVisibleHeight(float height) {
7760
}
7861
}
7962

80-
/**
81-
* Called from keyboard.c when the native layer receives composing text
82-
* tagged with a node id.
83-
*/
84-
private static void notifyComposingText(String id, String text) {
85-
updateTextForId(id, text);
86-
}
87-
8863
}

modules/keyboard/src/main/java/com/gluonhq/attach/keyboard/impl/BaseKeyboardService.java

Lines changed: 99 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,19 @@
3232
import com.gluonhq.attach.util.Util;
3333
import javafx.animation.Interpolator;
3434
import javafx.animation.TranslateTransition;
35-
import javafx.application.Platform;
35+
import javafx.beans.property.ReadOnlyFloatProperty;
3636
import javafx.beans.property.ReadOnlyFloatWrapper;
37-
import javafx.beans.property.ReadOnlyStringProperty;
38-
import javafx.beans.property.ReadOnlyStringWrapper;
37+
import javafx.beans.value.ChangeListener;
38+
import javafx.beans.value.ObservableValue;
3939
import javafx.scene.Node;
4040
import javafx.scene.Parent;
41-
import javafx.scene.input.MouseEvent;
41+
import javafx.scene.Scene;
4242
import javafx.util.Duration;
4343

44-
import java.util.HashMap;
44+
import java.util.Collections;
4545
import java.util.Map;
4646
import java.util.Objects;
47+
import java.util.Set;
4748
import java.util.WeakHashMap;
4849
import java.util.logging.Level;
4950
import java.util.logging.Logger;
@@ -60,75 +61,126 @@ public abstract class BaseKeyboardService implements KeyboardService {
6061
/** Map of nodes and keyboard types. */
6162
private final Map<Node, KeyboardType> nodeKeyboardTypes = new WeakHashMap<>();
6263

63-
/** Map of nodes and text properties. */
64-
private static final Map<Node, ReadOnlyStringWrapper> nodeTextProperties = new WeakHashMap<>();
64+
/** Map of nodes to their visibility listeners. */
65+
private final Map<Node, ChangeListener<Number>> visibilityListeners = new WeakHashMap<>();
6566

66-
/** Map of ids and nodes. */
67-
private static final Map<String, Node> idToNode = new HashMap<>();
67+
/** Scenes for which a focusOwner listener has already been installed. */
68+
private final Set<Scene> trackedScenes = Collections.newSetFromMap(new WeakHashMap<>());
6869

6970
BaseKeyboardService() {
70-
VISIBLE_HEIGHT.addListener((obs, ov, nv) -> {
71-
if (nv != null && nv.doubleValue() <= 0) {
72-
if (debug) {
73-
LOG.info("Keyboard hidden, reset default type");
74-
}
75-
applyActiveNodeId(""); // reset active node
76-
applyKeyboardType(KeyboardType.ASCII.getValue());
77-
}
78-
});
71+
}
72+
73+
@Override
74+
public void keepVisibilityForNode(Node node) {
75+
keepVisibilityForNode(node, null);
76+
}
77+
78+
@Override
79+
public void keepVisibilityForNode(Node node, Parent parent) {
80+
Objects.requireNonNull(node, "node must not be null");
81+
releaseVisibilityForNode(node);
82+
ChangeListener<Number> listener = (obs, ov, nv) -> adjustPosition(node, parent, nv.doubleValue());
83+
visibilityListeners.put(node, listener);
84+
VISIBLE_HEIGHT.addListener(listener);
85+
}
86+
87+
@Override
88+
public void releaseVisibilityForNode(Node node) {
89+
Objects.requireNonNull(node, "node must not be null");
90+
ChangeListener<Number> listener = visibilityListeners.remove(node);
91+
if (listener != null) {
92+
VISIBLE_HEIGHT.removeListener(listener);
93+
}
94+
}
95+
96+
@Override
97+
public ReadOnlyFloatProperty visibleHeightProperty() {
98+
return VISIBLE_HEIGHT.getReadOnlyProperty();
7999
}
80100

81101
@Override
82102
public void setKeyboardTypeForNode(Node node, KeyboardType type) {
83103
Objects.requireNonNull(node, "node must not be null");
84104
Objects.requireNonNull(type, "type must not be null");
85105
nodeKeyboardTypes.put(node, type);
86-
installEventFilter(node);
106+
attachFocusTracker(node);
87107
}
88108

89109
@Override
90-
public ReadOnlyStringProperty textPropertyForNode(Node node) {
110+
public void removeKeyboardTypeForNode(Node node) {
91111
Objects.requireNonNull(node, "node must not be null");
92-
installEventFilter(node);
93-
return nodeTextProperties.computeIfAbsent(node, n -> {
94-
idToNode.put(syntheticId(n), n);
95-
return new ReadOnlyStringWrapper("");
96-
}).getReadOnlyProperty();
112+
nodeKeyboardTypes.remove(node);
97113
}
98114

99-
private void installEventFilter(Node node) {
100-
node.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> {
101-
KeyboardType type = nodeKeyboardTypes.getOrDefault(node, KeyboardType.ASCII);
102-
if (debug) {
103-
LOG.info(String.format("Active keyboard type: %s for id %s", type, syntheticId(node)));
115+
/**
116+
* Ensures a single focusOwner listener is installed on the scene that
117+
* contains {@code node}. The listener drives the native keyboard type for
118+
* every focus change in that scene, whether the newly focused node was
119+
* explicitly registered via {@link #setKeyboardTypeForNode} or not.
120+
* If {@code node} is not yet in a scene, the installation is deferred
121+
* until it is.
122+
*/
123+
private void attachFocusTracker(Node node) {
124+
Scene scene = node.getScene();
125+
if (scene != null) {
126+
trackScene(scene);
127+
// If this node is already the focus owner, apply its type now
128+
if (scene.getFocusOwner() == node) {
129+
applyTypeFor(node);
130+
}
131+
return;
132+
}
133+
node.sceneProperty().addListener(new ChangeListener<>() {
134+
@Override
135+
public void changed(ObservableValue<? extends Scene> obs, Scene ov, Scene nv) {
136+
if (nv != null) {
137+
trackScene(nv);
138+
if (nv.getFocusOwner() == node) {
139+
applyTypeFor(node);
140+
}
141+
obs.removeListener(this);
142+
}
104143
}
105-
applyActiveNodeId(syntheticId(node));
106-
applyKeyboardType(type.getValue());
107144
});
108145
}
109146

110-
/**
111-
* Uses the node's own {@link Node#getId() id} if set, otherwise falls back to an
112-
* id based on its identity hash code.
113-
*/
114-
protected static String syntheticId(Node node) {
115-
String id = node.getId();
116-
return id != null ? id : "attach-kb-" + System.identityHashCode(node);
147+
private void trackScene(Scene scene) {
148+
if (!trackedScenes.add(scene)) {
149+
return;
150+
}
151+
scene.focusOwnerProperty().addListener((obs, ov, newNode) -> applyTypeFor(newNode));
117152
}
118153

119154
/**
120-
* Called from the native callback to update the text property for the
121-
* node identified by {@code id}.
155+
* Pushes the id and keyboard type for {@code focused} down to the native
156+
* layer. Registered nodes use their stored {@link KeyboardType}; any other
157+
* focus owner (including {@code null}) falls back to {@link KeyboardType#ASCII}.
122158
*/
123-
protected static void updateTextForId(String id, String text) {
124-
Node node = idToNode.get(id);
125-
if (node == null) {
159+
private void applyTypeFor(Node focused) {
160+
if (focused == null) {
161+
if (debug) {
162+
LOG.info("Focus cleared, applying default ASCII keyboard");
163+
}
164+
applyActiveNodeId("");
165+
applyKeyboardType(KeyboardType.ASCII.getValue());
126166
return;
127167
}
128-
ReadOnlyStringWrapper wrapper = nodeTextProperties.get(node);
129-
if (wrapper != null && !Objects.equals(wrapper.get(), text)) {
130-
Platform.runLater(() -> wrapper.set(text));
168+
KeyboardType type = nodeKeyboardTypes.getOrDefault(focused, KeyboardType.ASCII);
169+
String id = syntheticId(focused);
170+
if (debug) {
171+
LOG.info(String.format("Active keyboard type: %s for id %s", type, id));
131172
}
173+
applyActiveNodeId(id);
174+
applyKeyboardType(type.getValue());
175+
}
176+
177+
/**
178+
* Uses the node's own {@link Node#getId() id} if set, otherwise falls back to an
179+
* id based on its identity hash code.
180+
*/
181+
protected static String syntheticId(Node node) {
182+
String id = node.getId();
183+
return id != null ? id : "attach-kb-" + System.identityHashCode(node);
132184
}
133185

134186
protected static void adjustPosition(Node node, Parent parent, double kh) {

modules/keyboard/src/main/java/com/gluonhq/attach/keyboard/impl/IOSKeyboardService.java

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@
3030
import com.gluonhq.attach.lifecycle.LifecycleEvent;
3131
import com.gluonhq.attach.lifecycle.LifecycleService;
3232
import javafx.application.Platform;
33-
import javafx.beans.property.ReadOnlyFloatProperty;
34-
import javafx.beans.property.ReadOnlyStringProperty;
35-
import javafx.scene.Node;
36-
import javafx.scene.Parent;
37-
import javafx.scene.control.TextInputControl;
3833

3934
public class IOSKeyboardService extends BaseKeyboardService {
4035

@@ -54,34 +49,12 @@ public IOSKeyboardService() {
5449
startObserver();
5550
}
5651

57-
@Override
58-
public void keepVisibilityForNode(Node node) {
59-
keepVisibilityForNode(node, null);
60-
}
61-
62-
@Override
63-
public void keepVisibilityForNode(Node node, Parent parent) {
64-
VISIBLE_HEIGHT.addListener((obs, ov, nv) -> adjustPosition(node, parent, nv.doubleValue()));
65-
}
66-
67-
@Override
68-
public ReadOnlyFloatProperty visibleHeightProperty() {
69-
return VISIBLE_HEIGHT.getReadOnlyProperty();
70-
}
7152

7253
@Override
7354
protected void applyKeyboardType(int nativeValue) {
7455
nativeSetKeyboardType(nativeValue);
7556
}
7657

77-
@Override
78-
public ReadOnlyStringProperty textPropertyForNode(Node node) {
79-
if (node instanceof TextInputControl) {
80-
return ((TextInputControl) node).textProperty();
81-
}
82-
return super.textPropertyForNode(node);
83-
}
84-
8558
@Override
8659
protected void applyActiveNodeId(String id) {
8760
// no-op: iOS does not track active node, so no need to inform native layer

modules/keyboard/src/main/native/android/c/keyboard.c

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ static jclass jKeyboardServiceClass;
3131
static jclass jAttachKeyboardClass;
3232
static jclass jActivityClass;
3333
static jmethodID jAttach_notifyHeightMethod;
34-
static jmethodID jAttach_notifyComposingTextMethod;
3534
static jmethodID jActivity_setKeyboardTypeMethod;
3635
static jmethodID jActivity_setActiveNodeIdMethod;
3736

@@ -53,7 +52,6 @@ JNI_OnLoad_keyboard(JavaVM *vm, void *reserved)
5352
ATTACH_LOG_FINE("Initializing native Keyboard from OnLoad");
5453
jAttachKeyboardClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/keyboard/impl/AndroidKeyboardService"));
5554
jAttach_notifyHeightMethod = (*env)->GetStaticMethodID(env, jAttachKeyboardClass, "notifyVisibleHeight", "(F)V");
56-
jAttach_notifyComposingTextMethod = (*env)->GetStaticMethodID(env, jAttachKeyboardClass, "notifyComposingText", "(Ljava/lang/String;Ljava/lang/String;)V");
5755
initKeyboard();
5856
ATTACH_LOG_FINE("Initializing native Keyboard done");
5957
return JNI_VERSION_1_8;
@@ -115,23 +113,6 @@ JNIEXPORT void JNICALL Java_com_gluonhq_attach_keyboard_impl_AndroidKeyboardServ
115113
ATTACH_LOG_FINE("nativeSetActiveNodeId done");
116114
}
117115

118-
//////////////////////////////////
119-
// native (Substrate) to Java //
120-
//////////////////////////////////
121-
122-
void attach_setComposingText(const char *id, const char *text)
123-
{
124-
ATTACH_LOG_FINE("attach_setComposingText: forwarding to Graal: id=%s, text=%s", id, text);
125-
ATTACH_GRAAL();
126-
jstring graalId = (*graalEnv)->NewStringUTF(graalEnv, id);
127-
jstring graalText = (*graalEnv)->NewStringUTF(graalEnv, text);
128-
(*graalEnv)->CallStaticVoidMethod(graalEnv, jAttachKeyboardClass, jAttach_notifyComposingTextMethod, graalId, graalText);
129-
(*graalEnv)->DeleteLocalRef(graalEnv, graalText);
130-
(*graalEnv)->DeleteLocalRef(graalEnv, graalId);
131-
DETACH_GRAAL();
132-
ATTACH_LOG_FINE("attach_setComposingText done");
133-
}
134-
135116
///////////////////////////
136117
// From Dalvik to native //
137118
///////////////////////////

modules/keyboard/src/main/resources/META-INF/substrate/config/jniconfig-aarch64-android.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
{
33
"name" : "com.gluonhq.attach.keyboard.impl.AndroidKeyboardService",
44
"methods":[
5-
{"name":"notifyVisibleHeight","parameterTypes":["float"]},
6-
{"name":"notifyComposingText","parameterTypes":["java.lang.String","java.lang.String"]}
5+
{"name":"notifyVisibleHeight","parameterTypes":["float"]}
76
]
87
}
98
]

0 commit comments

Comments
 (0)