Skip to content

Commit 05980c4

Browse files
authored
Fix ChartPlugin scene listener is not removed (#676)
Change ChartPlugin to remove all listeners once a plugin is removed from it's chart and add a unit-test to verify that this case is correctly handled.
1 parent 7908d4c commit 05980c4

File tree

2 files changed

+97
-9
lines changed

2 files changed

+97
-9
lines changed

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/ChartPlugin.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package io.fair_acc.chartfx.plugins;
22

3+
import java.util.HashMap;
34
import java.util.LinkedList;
45
import java.util.List;
6+
import java.util.Map;
57

68
import javafx.beans.property.BooleanProperty;
79
import javafx.beans.property.ObjectProperty;
810
import javafx.beans.property.SimpleBooleanProperty;
911
import javafx.beans.property.SimpleObjectProperty;
12+
import javafx.beans.value.ChangeListener;
1013
import javafx.collections.FXCollections;
1114
import javafx.collections.ObservableList;
1215
import javafx.event.EventHandler;
1316
import javafx.event.EventType;
1417
import javafx.geometry.Point2D;
1518
import javafx.scene.Node;
19+
import javafx.scene.Scene;
1620
import javafx.scene.canvas.Canvas;
1721
import javafx.scene.input.InputEvent;
1822
import javafx.scene.input.MouseEvent;
@@ -45,6 +49,7 @@ public abstract class ChartPlugin implements Measurable.EmptyDefault {
4549
private static final Logger LOGGER = LoggerFactory.getLogger(ChartPlugin.class);
4650
private final ObservableList<Node> chartChildren = FXCollections.observableArrayList();
4751
private final List<Pair<EventType<? extends InputEvent>, EventHandler<? extends InputEvent>>> mouseEventHandlers = new LinkedList<>();
52+
private final Map<Node, ChangeListener<? super Scene>> sceneChangeListeners = new HashMap<>();
4853

4954
/**
5055
* The associated {@link Chart}. Initialised when the plug-in is added to the Chart, set to {@code null} when
@@ -90,19 +95,29 @@ private <T extends InputEvent> void addEventHandlers(final Node node) {
9095
final EventType<T> type = (EventType<T>) pair.getKey();
9196
final EventHandler<T> handler = (EventHandler<T>) pair.getValue();
9297
node.addEventHandler(type, handler);
93-
node.sceneProperty().addListener((ch, o, n) -> {
94-
if (o == n) {
95-
return;
96-
}
97-
if (o != null) {
98+
}
99+
ChangeListener<? super Scene> sceneListener = (ch, o, n) -> {
100+
if (o == n) {
101+
return;
102+
}
103+
if (o != null) {
104+
for (final Pair<EventType<? extends InputEvent>, EventHandler<? extends InputEvent>> pair : mouseEventHandlers) {
105+
final EventType<T> type = (EventType<T>) pair.getKey();
106+
final EventHandler<T> handler = (EventHandler<T>) pair.getValue();
98107
o.removeEventHandler(type, handler);
99108
}
109+
}
100110

101-
if (n != null) {
111+
if (n != null) {
112+
for (final Pair<EventType<? extends InputEvent>, EventHandler<? extends InputEvent>> pair : mouseEventHandlers) {
113+
final EventType<T> type = (EventType<T>) pair.getKey();
114+
final EventHandler<T> handler = (EventHandler<T>) pair.getValue();
102115
n.addEventHandler(type, handler);
103116
}
104-
});
105-
}
117+
}
118+
};
119+
sceneChangeListeners.put(node, sceneListener);
120+
node.sceneProperty().addListener(sceneListener);
106121
}
107122

108123
/**
@@ -230,9 +245,11 @@ private <T extends InputEvent> void removeEventHandlers(final Node node) {
230245
final EventHandler<T> handler = (EventHandler<T>) pair.getValue();
231246
node.removeEventHandler(type, handler);
232247
if (node.getScene() != null) {
233-
node.getScene().removeEventFilter(type, handler);
248+
node.getScene().removeEventHandler(type, handler);
234249
}
235250
}
251+
node.sceneProperty().removeListener(sceneChangeListeners.get(node));
252+
sceneChangeListeners.put(node, null);
236253
}
237254

238255
/**
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package io.fair_acc.chartfx.plugins;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
6+
import javafx.scene.Scene;
7+
import javafx.scene.control.Label;
8+
import javafx.scene.input.MouseButton;
9+
import javafx.scene.input.MouseEvent;
10+
import javafx.scene.layout.BorderPane;
11+
import javafx.stage.Stage;
12+
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
import org.testfx.api.FxRobot;
16+
import org.testfx.framework.junit5.ApplicationExtension;
17+
import org.testfx.framework.junit5.Start;
18+
19+
import io.fair_acc.chartfx.XYChart;
20+
import io.fair_acc.chartfx.axes.spi.DefaultNumericAxis;
21+
import io.fair_acc.chartfx.utils.FXUtils;
22+
23+
@ExtendWith(ApplicationExtension.class)
24+
class ChartPluginTest {
25+
static class TestChartPlugin extends ChartPlugin {
26+
public TestChartPlugin() {
27+
registerInputEventHandler(MouseEvent.MOUSE_CLICKED, this::handle);
28+
}
29+
30+
boolean clicked = false;
31+
32+
private void handle(MouseEvent mouseEvent) {
33+
clicked = true;
34+
}
35+
}
36+
37+
private XYChart chart;
38+
private Label label;
39+
private BorderPane root;
40+
41+
@Start
42+
void start(Stage stage) {
43+
chart = new XYChart(new DefaultNumericAxis(), new DefaultNumericAxis());
44+
root = new BorderPane(chart);
45+
label = new Label("Click");
46+
root.setBottom(label);
47+
Scene scene = new Scene(root, 500, 400);
48+
stage.setScene(scene);
49+
stage.show();
50+
}
51+
52+
@Test
53+
void testSceneListenerIsRemoved(FxRobot robot) {
54+
assertNotNull(chart);
55+
assertEquals(0, chart.getPlugins().size());
56+
final TestChartPlugin testChartPlugin = new TestChartPlugin();
57+
assertDoesNotThrow(() -> FXUtils.runAndWait(() -> chart.getPlugins().add(testChartPlugin)));
58+
assertEquals(1, chart.getPlugins().size());
59+
robot.moveTo(chart).clickOn(MouseButton.PRIMARY).interrupt();
60+
assertTrue(testChartPlugin.clicked);
61+
assertDoesNotThrow(() -> FXUtils.runAndWait(() -> chart.getPlugins().remove(testChartPlugin)));
62+
assertEquals(0, chart.getPlugins().size());
63+
testChartPlugin.clicked = false;
64+
assertDoesNotThrow(() -> FXUtils.runAndWait(() -> {
65+
root.setCenter(null);
66+
root.setCenter(chart);
67+
}));
68+
robot.moveTo(label).clickOn(MouseButton.PRIMARY).interrupt();
69+
assertFalse(testChartPlugin.clicked);
70+
}
71+
}

0 commit comments

Comments
 (0)