Skip to content

Commit 0ffa8e2

Browse files
committed
8244075: Accelerator of ContextMenu's MenuItem is not removed when ContextMenu is removed from Scene
Reviewed-by: kcr, aghaisas
1 parent e6cf1df commit 0ffa8e2

File tree

2 files changed

+113
-13
lines changed

2 files changed

+113
-13
lines changed

modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ControlAcceleratorSupport.java

+37-13
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,19 @@ public static void addAcceleratorsIntoScene(ObservableList<MenuItem> items, Node
7575
}
7676

7777
final Scene scene = anchor.getScene();
78-
if (scene == null) {
79-
// listen to the scene property on the anchor until it is set, and
80-
// then install the accelerators
81-
anchor.sceneProperty().addListener(new InvalidationListener() {
82-
@Override public void invalidated(Observable observable) {
83-
Scene scene = anchor.getScene();
84-
if (scene != null) {
85-
anchor.sceneProperty().removeListener(this);
86-
doAcceleratorInstall(items, scene);
87-
}
88-
}
89-
});
90-
} else {
78+
if (scene != null) {
9179
doAcceleratorInstall(items, scene);
9280
}
81+
// Scene change listener is added to the anchor for scenarios like,
82+
// 1. Installing accelerators when Control is added to Scene
83+
// 2. Removing accelerators when Control is removed from Scene
84+
// Remove previously added listener if any
85+
if (sceneChangeListenerMap.containsKey(anchor)) {
86+
anchor.sceneProperty().removeListener(sceneChangeListenerMap.get(anchor));
87+
sceneChangeListenerMap.remove(anchor);
88+
}
89+
// Add a new listener
90+
anchor.sceneProperty().addListener(getSceneChangeListener(anchor, items));
9391
}
9492

9593
private static void addAcceleratorsIntoScene(ObservableList<MenuItem> items, Object anchor) {
@@ -115,6 +113,24 @@ private static void addAcceleratorsIntoScene(ObservableList<MenuItem> items, Obj
115113
}
116114
}
117115

116+
private static Map<Object, ChangeListener<Scene>> sceneChangeListenerMap = new WeakHashMap<>();
117+
118+
private static ChangeListener<Scene> getSceneChangeListener(Object anchor, ObservableList<MenuItem> items) {
119+
ChangeListener<Scene> sceneChangeListener = sceneChangeListenerMap.get(anchor);
120+
if (sceneChangeListener == null) {
121+
sceneChangeListener = (ov, oldScene, newScene) -> {
122+
if (oldScene != null) {
123+
removeAcceleratorsFromScene(items, oldScene);
124+
}
125+
if (newScene != null) {
126+
doAcceleratorInstall(items, newScene);
127+
}
128+
};
129+
sceneChangeListenerMap.put(anchor, sceneChangeListener);
130+
}
131+
return sceneChangeListener;
132+
}
133+
118134
private static void doAcceleratorInstall(final ObservableList<MenuItem> items, final Scene scene) {
119135
// we're given an observable list of menu items, which we will add an observer to
120136
// so that when menu items are added or removed we can properly handle
@@ -222,6 +238,14 @@ public static void removeAcceleratorsFromScene(List<? extends MenuItem> items, T
222238

223239
public static void removeAcceleratorsFromScene(List<? extends MenuItem> items, Node anchor) {
224240
Scene scene = anchor.getScene();
241+
if (scene == null) {
242+
// The Node is not part of a Scene: Remove the Scene listener that was added
243+
// at the time of installing the accelerators.
244+
if (sceneChangeListenerMap.containsKey(anchor)) {
245+
anchor.sceneProperty().removeListener(sceneChangeListenerMap.get(anchor));
246+
sceneChangeListenerMap.remove(anchor);
247+
}
248+
}
225249
removeAcceleratorsFromScene(items, scene);
226250
}
227251

modules/javafx.controls/src/test/java/test/javafx/scene/control/AcceleratorParameterizedTest.java

+76
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
package test.javafx.scene.control;
2727

28+
import static test.com.sun.javafx.scene.control.infrastructure.ControlTestUtils.*;
2829
import test.com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
2930
import test.com.sun.javafx.scene.control.infrastructure.KeyModifier;
3031
import test.com.sun.javafx.scene.control.infrastructure.StageLoader;
@@ -34,12 +35,14 @@
3435
import javafx.scene.input.KeyCombination;
3536
import org.junit.After;
3637
import org.junit.Before;
38+
import org.junit.Ignore;
3739
import org.junit.Test;
3840
import org.junit.runner.RunWith;
3941
import org.junit.runners.Parameterized;
4042

4143
import java.util.Arrays;
4244
import java.util.Collection;
45+
import javafx.scene.Group;
4346
import javafx.scene.control.Button;
4447
import javafx.scene.control.ContextMenu;
4548
import javafx.scene.control.Menu;
@@ -297,4 +300,77 @@ private void assertSceneDoesNotContainKeyCombination(KeyCombination keyCombinati
297300
keyboard.doKeyPress(KeyCode.DIGIT1, KeyModifier.ALT);
298301
assertEquals(0, eventCounter);
299302
}
303+
304+
@Test public void testAcceleratorShouldNotGetFiredWhenMenuItemRemovedFromScene() {
305+
KeyEventFirer kb = new KeyEventFirer(item1, scene);
306+
307+
kb.doKeyPress(KeyCode.DIGIT1, KeyModifier.ALT);
308+
assertEquals(1, eventCounter);
309+
assertEquals(1, getListenerCount(item1.acceleratorProperty()));
310+
311+
// Remove all children from the scene
312+
Group root = (Group)scene.getRoot();
313+
root.getChildren().clear();
314+
315+
assertEquals(0, getListenerCount(item1.acceleratorProperty()));
316+
kb.doKeyPress(KeyCode.DIGIT1, KeyModifier.ALT);
317+
assertEquals(1, eventCounter);
318+
}
319+
320+
@Test public void testAcceleratorShouldGetFiredWhenMenuItemRemovedAndAddedBackToScene() {
321+
KeyEventFirer kb = new KeyEventFirer(item1, scene);
322+
323+
kb.doKeyPress(KeyCode.DIGIT1, KeyModifier.ALT);
324+
assertEquals(1, eventCounter);
325+
326+
Group root = (Group)scene.getRoot();
327+
scene.setRoot(new Group()); // Remove all children from the scene
328+
scene.setRoot(root); // Add the children to the same scene
329+
330+
assertEquals(1, getListenerCount(item1.acceleratorProperty()));
331+
kb.doKeyPress(KeyCode.DIGIT1, KeyModifier.ALT);
332+
assertEquals(2, eventCounter);
333+
}
334+
335+
@Test public void testAcceleratorShouldGetFiredWhenMenuItemRemovedAndAddedToDifferentScene() {
336+
KeyEventFirer kb = new KeyEventFirer(item1, scene);
337+
338+
kb.doKeyPress(KeyCode.DIGIT1, KeyModifier.ALT);
339+
assertEquals(1, eventCounter);
340+
341+
Group root = (Group)scene.getRoot();
342+
scene.setRoot(new Group()); // Remove all children from the scene
343+
Scene diffScene = new Scene(root); // Add the children to a different scene
344+
sl.getStage().setScene(diffScene);
345+
kb = new KeyEventFirer(item1, diffScene);
346+
347+
assertEquals(1, getListenerCount(item1.acceleratorProperty()));
348+
kb.doKeyPress(KeyCode.DIGIT1, KeyModifier.ALT);
349+
assertEquals(2, eventCounter);
350+
}
351+
352+
@Ignore("JDK-8268374")
353+
@Test public void testAcceleratorShouldNotGetFiredWhenControlsIsRemovedFromSceneThenContextMenuIsSetToNullAndControlIsAddedBackToScene() {
354+
KeyEventFirer kb = new KeyEventFirer(item1, scene);
355+
kb.doKeyPress(KeyCode.DIGIT1, KeyModifier.ALT);
356+
assertEquals(1, eventCounter);
357+
358+
Group root = (Group)scene.getRoot();
359+
scene.setRoot(new Group()); // Remove all children from the scene
360+
361+
if (testClass == Button.class) {
362+
btn.setContextMenu(null);
363+
} else if (testClass == Tab.class) {
364+
tab.setContextMenu(null);
365+
} else if (testClass == TableColumn.class) {
366+
tableColumn.setContextMenu(null);
367+
} else if (testClass == TreeTableColumn.class) {
368+
treeTableColumn.setContextMenu(null);
369+
}
370+
scene.setRoot(root); // Add the children to a different scene
371+
372+
assertEquals(0, getListenerCount(item1.acceleratorProperty()));
373+
kb.doKeyPress(KeyCode.DIGIT1, KeyModifier.ALT);
374+
assertEquals(1, eventCounter);
375+
}
300376
}

0 commit comments

Comments
 (0)