Commit | Line | Data |
---|---|---|
c879c4db AM |
1 | /* |
2 | * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com> | |
3 | * | |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | */ | |
9 | ||
10 | package org.lttng.scope.tmf2.views.ui.jfx; | |
11 | ||
12 | import static java.util.Objects.requireNonNull; | |
13 | ||
14 | import java.lang.invoke.MethodHandle; | |
15 | import java.lang.invoke.MethodHandles; | |
16 | import java.lang.reflect.Method; | |
17 | import java.util.List; | |
18 | ||
19 | import javafx.application.Platform; | |
20 | import javafx.beans.property.ReadOnlyDoubleProperty; | |
21 | import javafx.beans.property.SimpleDoubleProperty; | |
22 | import javafx.geometry.Rectangle2D; | |
23 | import javafx.scene.Node; | |
24 | import javafx.scene.control.Dialog; | |
25 | import javafx.scene.control.OverrunStyle; | |
26 | import javafx.scene.text.Font; | |
27 | import javafx.stage.Screen; | |
28 | import javafx.stage.Window; | |
29 | ||
30 | /** | |
31 | * JavaFX-related utilities | |
32 | * | |
33 | * @author Alexandre Montplaisir | |
34 | */ | |
35 | public final class JfxUtils { | |
36 | ||
37 | /** | |
38 | * Double property with a non-modifiable value of 0. For things that should | |
39 | * remain at 0. | |
40 | */ | |
41 | public static final ReadOnlyDoubleProperty ZERO_PROPERTY = new SimpleDoubleProperty(0); | |
42 | ||
43 | ||
44 | private static final MethodHandles.Lookup LOOKUP = requireNonNull(MethodHandles.lookup()); | |
45 | private static final MethodHandle COMPUTE_CLIPPED_TEXT_HANDLE; | |
46 | static { | |
47 | MethodHandle handle = null; | |
48 | try { | |
49 | Class<?> c = Class.forName("com.sun.javafx.scene.control.skin.Utils"); //$NON-NLS-1$ | |
50 | Method method = c.getDeclaredMethod("computeClippedText", //$NON-NLS-1$ | |
51 | Font.class, String.class, double.class, OverrunStyle.class, String.class); | |
52 | method.setAccessible(true); | |
53 | handle = LOOKUP.unreflect(method); | |
54 | } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException e) { | |
55 | /* Should not fail if we did everything correctly. */ | |
56 | } | |
57 | COMPUTE_CLIPPED_TEXT_HANDLE = requireNonNull(handle); | |
58 | } | |
59 | ||
60 | /** | |
61 | * Accessor for the | |
62 | * com.sun.javafx.scene.control.skin.Utils.computeClippedText() method. | |
63 | * | |
64 | * This method implements the logic to clip Label strings. It can be useful | |
65 | * for other types, like Text. Unfortunately it is not public, but this | |
66 | * accessor allows calling it through reflection. It makes use of | |
67 | * {@link MethodHandle#invokeExact}, which should be close to just as fast | |
68 | * as a standard compiled method call. | |
69 | * | |
70 | * @param font | |
71 | * The font of the text that will be used | |
72 | * @param text | |
73 | * The string to clip | |
74 | * @param width | |
75 | * The maximum width we want to limit the string to | |
76 | * @param type | |
77 | * The {@link OverrunStyle} | |
78 | * @param ellipsisString | |
79 | * The string to use as ellipsis | |
80 | * @return The clipped string, or "ERROR" if an error happened. | |
81 | * Unfortunately we lose the exception typing due to the reflection | |
82 | * call, so we do not want to throw "Throwable" here. | |
83 | */ | |
84 | public static String computeClippedText(Font font, String text, double width, | |
85 | OverrunStyle type, String ellipsisString) { | |
86 | try { | |
87 | String str = (String) COMPUTE_CLIPPED_TEXT_HANDLE.invokeExact(font, text, width, type, ellipsisString); | |
88 | return requireNonNull(str); | |
89 | } catch (Throwable e) { | |
90 | return "ERROR"; //$NON-NLS-1$ | |
91 | } | |
92 | } | |
93 | ||
94 | /** | |
95 | * Run the given {@link Runnable} on the UI/main/application thread. | |
96 | * | |
97 | * If you know for sure you are *not* on the main thread, you should use | |
98 | * {@link Platform#runLater} to queue the runnable for the main thread. | |
99 | * | |
100 | * If you are not sure, you can use this method. The difference with | |
101 | * {@link Platform#runLater} is that if you are actually already on the UI | |
102 | * thread, the runnable will be run immediately. Whereas calling runLater | |
103 | * from the UI will just queue the runnable at the end of the queue. | |
104 | * | |
105 | * @param r | |
106 | * The runnable to run on the main thread | |
107 | */ | |
108 | public static void runOnMainThread(Runnable r) { | |
109 | if (Platform.isFxApplicationThread()) { | |
110 | r.run(); | |
111 | } else { | |
112 | Platform.runLater(r); | |
113 | } | |
114 | } | |
115 | ||
116 | /** | |
117 | * Utility method to center a Dialog/Alert on the middle of the current | |
118 | * screen. Used to workaround a "bug" with the current version of JavaFX (or | |
119 | * the SWT/JavaFX embedding?) where alerts always show on the primary | |
120 | * screen, not necessarily the current one. | |
121 | * | |
122 | * @param dialog | |
123 | * The dialog to reposition. It must be already shown, or else | |
124 | * this will do nothing. | |
125 | * @param referenceNode | |
126 | * The dialog should be moved to the same screen as this node's | |
127 | * window. | |
128 | */ | |
129 | public static void centerDialogOnScreen(Dialog<?> dialog, Node referenceNode) { | |
130 | Window window = referenceNode.getScene().getWindow(); | |
131 | Rectangle2D windowRectangle = new Rectangle2D(window.getX(), window.getY(), window.getWidth(), window.getHeight()); | |
132 | ||
133 | List<Screen> screens = Screen.getScreensForRectangle(windowRectangle); | |
134 | Screen screen = screens.stream() | |
135 | .findFirst() | |
136 | .orElse(Screen.getPrimary()); | |
137 | ||
138 | Rectangle2D screenBounds = screen.getBounds(); | |
139 | dialog.setX((screenBounds.getWidth() - dialog.getWidth()) / 2 + screenBounds.getMinX()); | |
140 | // dialog.setY((screenBounds.getHeight() - dialog.getHeight()) / 2 + screenBounds.getMinY()); | |
141 | } | |
142 | } |