1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package cz.zcu.mre.sparkle.gui.query.helpers;
21
22 import cz.zcu.mre.sparkle.gui.tools.ReferenceKeeper;
23 import javafx.application.Platform;
24 import javafx.beans.InvalidationListener;
25 import javafx.beans.value.ChangeListener;
26 import javafx.geometry.Bounds;
27 import javafx.geometry.Dimension2D;
28 import javafx.geometry.Point2D;
29 import javafx.scene.Node;
30 import javafx.scene.layout.Region;
31 import javafx.stage.Popup;
32 import javafx.stage.WindowEvent;
33 import java.util.Collection;
34
35
36
37
38
39
40
41
42
43 public final class RegionPopup
44 extends Popup {
45
46
47
48
49
50
51 public enum PopupPlacement {
52 LEFT, RIGHT, TOP, BOTTOM
53 }
54
55
56
57
58
59
60 public enum PopupAlignment {
61 LEFT_OR_TOP, CENTER, RIGHT_OR_BOTTOM
62 }
63
64 private Region owner;
65 private final PopupPlacement placement;
66 private final PopupAlignment alignment;
67 private double offsetX, offsetY;
68 private final ChangeListener<Number> windowXMoveListener, windowYMoveListener;
69 private boolean visible = false;
70 private final ReferenceKeeper keeper = new ReferenceKeeper();
71
72 public RegionPopup(final Region owner, final PopupPlacement placement) {
73 this(owner, placement, PopupAlignment.LEFT_OR_TOP);
74 }
75
76 public RegionPopup(final Region owner, final PopupPlacement placement, final PopupAlignment alignment) {
77 this.owner = owner;
78 this.placement = placement;
79 this.alignment = alignment;
80 this.setOffsetX(0.0);
81 this.setOffsetY(0.0);
82
83 final InvalidationListener sizeChangeListener = observable
84 -> {
85 if (isVisible()) {
86 updatePosition();
87 }
88 };
89
90 widthProperty().addListener(keeper.toWeak(sizeChangeListener));
91 heightProperty().addListener(keeper.toWeak(sizeChangeListener));
92 owner.widthProperty().addListener(keeper.toWeak(sizeChangeListener));
93 owner.heightProperty().addListener(keeper.toWeak(sizeChangeListener));
94
95 windowXMoveListener = (observable, oldValue, newValue) -> setAnchorX(
96 getAnchorX() + (newValue.doubleValue() - oldValue.doubleValue()));
97
98 windowYMoveListener = (observable, oldValue, newValue) -> setAnchorY(
99 getAnchorY() + (newValue.doubleValue() - oldValue.doubleValue()));
100
101 setOnShowing(keeper.toWeak((final WindowEvent event)
102 -> {
103 getOwnerWindow().widthProperty().addListener(sizeChangeListener);
104 getOwnerWindow().heightProperty().addListener(sizeChangeListener);
105 getOwnerWindow().xProperty().addListener(windowXMoveListener);
106 owner.layoutXProperty().addListener(windowXMoveListener);
107 getOwnerWindow().yProperty().addListener(windowYMoveListener);
108 owner.layoutYProperty().addListener(windowYMoveListener);
109 }));
110
111 setOnHiding(keeper.toWeak((final WindowEvent event)
112 -> {
113 getOwnerWindow().widthProperty().removeListener(sizeChangeListener);
114 getOwnerWindow().heightProperty().removeListener(sizeChangeListener);
115 getOwnerWindow().xProperty().removeListener(windowXMoveListener);
116 owner.layoutXProperty().removeListener(windowXMoveListener);
117 getOwnerWindow().yProperty().removeListener(windowYMoveListener);
118 owner.layoutYProperty().removeListener(windowYMoveListener);
119 }));
120
121 setOnShown(keeper.toWeak((final WindowEvent event) -> visible = true));
122 setOnHidden(keeper.toWeak((final WindowEvent event) -> visible = false));
123 }
124
125 public final Node getOwner() {
126 return owner;
127 }
128
129 public final void setOwner(final Region owner) {
130 this.owner = owner;
131 }
132
133 public final PopupPlacement getPlacement() {
134 return placement;
135 }
136
137 public final PopupAlignment getAlignment() {
138 return alignment;
139 }
140
141 private Point2D computePosition() {
142 double xOffset = 0.0;
143 double yOffset = 0.0;
144
145 switch (placement) {
146 case LEFT:
147 case RIGHT:
148 switch (alignment) {
149 case CENTER:
150 yOffset = (owner.getHeight() - getContentSize().getHeight()) / 2.0;
151 break;
152 case RIGHT_OR_BOTTOM:
153 yOffset = owner.getHeight() - getContentSize().getHeight();
154 break;
155 case LEFT_OR_TOP:
156 default:
157 break;
158 }
159 break;
160 case BOTTOM:
161 case TOP:
162 switch (alignment) {
163 case CENTER:
164 xOffset = (owner.getWidth() - getContentSize().getWidth()) / 2.0;
165 break;
166 case RIGHT_OR_BOTTOM:
167 xOffset = owner.getWidth() - getContentSize().getWidth();
168 break;
169 case LEFT_OR_TOP:
170 default:
171 break;
172 }
173 default:
174 break;
175 }
176
177 switch (placement) {
178 case BOTTOM:
179 xOffset += offsetX;
180 yOffset += owner.getHeight() + offsetY;
181 break;
182 case LEFT:
183 xOffset += -getWidth() + offsetX;
184 yOffset += offsetY;
185 break;
186 case RIGHT:
187 xOffset += owner.getWidth() + offsetX;
188 yOffset += offsetY;
189 break;
190 case TOP:
191 xOffset += offsetX;
192 yOffset += -getHeight() + offsetY;
193 break;
194 }
195
196 final Point2D p = owner.localToScreen(0.0, 0.0);
197
198 return p.add(xOffset, yOffset);
199 }
200
201 private void updatePosition() {
202 updatePosition(computePosition());
203 }
204
205 private void updatePosition(final Point2D p) {
206 setAnchorX(p.getX());
207 setAnchorY(p.getY());
208 }
209
210 public final void showPositioned() {
211 final Point2D p = computePosition();
212 show(owner, p.getX(), p.getY());
213
214
215 Platform.runLater(new Runnable() {
216 private int repeatsLeft = 20;
217
218 @Override
219 public final void run() {
220 Point2D p;
221
222 p = computePosition();
223 updatePosition(p);
224
225 if ((p.getX() < 0.0) && (p.getY() < 0.0) && isVisible() && (repeatsLeft > 0)) {
226 repeatsLeft--;
227 Platform.runLater(this);
228 }
229 }
230 });
231
232
233 }
234
235 public final boolean isVisible() {
236 return visible;
237 }
238
239 public final double getOffsetX() {
240 return offsetX;
241 }
242
243 public final void setOffsetX(final double offsetX) {
244 this.offsetX = offsetX;
245 if (isVisible()) {
246 updatePosition();
247 }
248 }
249
250 public final double getOffsetY() {
251 return offsetY;
252 }
253
254 private void setOffsetY(final double offsetY) {
255 this.offsetY = offsetY;
256 if (isVisible()) {
257 updatePosition();
258 }
259 }
260
261
262
263
264
265
266 private Dimension2D getContentSize() {
267 final Collection<Node> content = getContent();
268 if (content.isEmpty()) {
269 return new Dimension2D(0.0, 0.0);
270 }
271
272 double minX = Double.MAX_VALUE;
273 double minY = Double.MAX_VALUE;
274 double maxX = Double.MIN_VALUE;
275 double maxY = Double.MIN_VALUE;
276
277 for (final Node node : getContent()) {
278 final Bounds b = node.getBoundsInLocal();
279 minX = Math.min(minX, b.getMinX());
280 minY = Math.min(minY, b.getMinY());
281 maxX = Math.max(maxX, b.getMaxX());
282 maxY = Math.max(maxY, b.getMaxY());
283 }
284
285 return new Dimension2D(maxX - minX, maxY - minY);
286 }
287 }