Commit | Line | Data |
---|---|---|
86057589 | 1 | package be.nikiroo.utils.ui; |
8caeb8bd | 2 | |
ef13cd7f | 3 | import java.awt.Color; |
1fdc7577 NR |
4 | import java.awt.Component; |
5 | import java.awt.Desktop; | |
ef13cd7f NR |
6 | import java.awt.GradientPaint; |
7 | import java.awt.Graphics; | |
8 | import java.awt.Graphics2D; | |
66c20ee9 NR |
9 | import java.awt.GraphicsConfiguration; |
10 | import java.awt.GraphicsDevice; | |
11 | import java.awt.GraphicsEnvironment; | |
ef13cd7f NR |
12 | import java.awt.Paint; |
13 | import java.awt.RadialGradientPaint; | |
14 | import java.awt.RenderingHints; | |
66c20ee9 | 15 | import java.awt.Window; |
1fdc7577 NR |
16 | import java.io.IOException; |
17 | import java.net.URISyntaxException; | |
ef13cd7f | 18 | |
d831b327 | 19 | import javax.swing.JButton; |
a917f100 | 20 | import javax.swing.JComponent; |
1fdc7577 NR |
21 | import javax.swing.JEditorPane; |
22 | import javax.swing.JLabel; | |
23 | import javax.swing.JOptionPane; | |
a917f100 | 24 | import javax.swing.JScrollPane; |
8caeb8bd NR |
25 | import javax.swing.UIManager; |
26 | import javax.swing.UnsupportedLookAndFeelException; | |
1fdc7577 NR |
27 | import javax.swing.event.HyperlinkEvent; |
28 | import javax.swing.event.HyperlinkListener; | |
29 | ||
66c20ee9 NR |
30 | import com.sun.java.swing.plaf.windows.resources.windows; |
31 | ||
1fdc7577 NR |
32 | import be.nikiroo.utils.Version; |
33 | import be.nikiroo.utils.VersionCheck; | |
8caeb8bd NR |
34 | |
35 | /** | |
36 | * Some Java Swing utilities. | |
37 | * | |
38 | * @author niki | |
39 | */ | |
40 | public class UIUtils { | |
d831b327 NR |
41 | static private Color buttonNormal; |
42 | static private Color buttonPressed; | |
66c20ee9 | 43 | |
8caeb8bd | 44 | /** |
b9839223 | 45 | * Set a fake "native Look & Feel" for the application if possible |
8caeb8bd NR |
46 | * (check for the one currently in use, then try GTK). |
47 | * <p> | |
48 | * <b>Must</b> be called prior to any GUI work. | |
b9839223 NR |
49 | * |
50 | * @return TRUE if it succeeded | |
8caeb8bd | 51 | */ |
b9839223 | 52 | static public boolean setLookAndFeel() { |
8caeb8bd | 53 | // native look & feel |
b9839223 NR |
54 | String noLF = "javax.swing.plaf.metal.MetalLookAndFeel"; |
55 | String lf = UIManager.getSystemLookAndFeelClassName(); | |
56 | if (lf.equals(noLF)) | |
57 | lf = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; | |
66c20ee9 | 58 | |
b9839223 NR |
59 | return setLookAndFeel(lf); |
60 | } | |
61 | ||
62 | /** | |
63 | * Switch to the given Look & Feel for the application if possible | |
64 | * (check for the one currently in use, then try GTK). | |
65 | * <p> | |
66 | * <b>Must</b> be called prior to any GUI work. | |
67 | * | |
68 | * @param laf | |
69 | * the Look & Feel to use | |
70 | * | |
71 | * @return TRUE if it succeeded | |
72 | */ | |
73 | static public boolean setLookAndFeel(String laf) { | |
8caeb8bd | 74 | try { |
b9839223 NR |
75 | UIManager.setLookAndFeel(laf); |
76 | return true; | |
8caeb8bd NR |
77 | } catch (InstantiationException e) { |
78 | } catch (ClassNotFoundException e) { | |
79 | } catch (UnsupportedLookAndFeelException e) { | |
80 | } catch (IllegalAccessException e) { | |
81 | } | |
b9839223 NR |
82 | |
83 | return false; | |
8caeb8bd | 84 | } |
a917f100 | 85 | |
db0af0d9 NR |
86 | /** |
87 | * Draw a 3D-looking ellipse at the given location, if the given | |
88 | * {@link Graphics} object is compatible (with {@link Graphics2D}); draw a | |
89 | * simple ellipse if not. | |
90 | * | |
91 | * @param g | |
92 | * the {@link Graphics} to draw on | |
93 | * @param color | |
94 | * the base colour | |
95 | * @param x | |
96 | * the X coordinate | |
97 | * @param y | |
98 | * the Y coordinate | |
99 | * @param width | |
100 | * the width radius | |
101 | * @param height | |
102 | * the height radius | |
103 | */ | |
a917f100 NR |
104 | static public void drawEllipse3D(Graphics g, Color color, int x, int y, |
105 | int width, int height) { | |
db0af0d9 NR |
106 | drawEllipse3D(g, color, x, y, width, height, true); |
107 | } | |
ef13cd7f NR |
108 | |
109 | /** | |
110 | * Draw a 3D-looking ellipse at the given location, if the given | |
111 | * {@link Graphics} object is compatible (with {@link Graphics2D}); draw a | |
112 | * simple ellipse if not. | |
113 | * | |
114 | * @param g | |
115 | * the {@link Graphics} to draw on | |
116 | * @param color | |
117 | * the base colour | |
118 | * @param x | |
7ce18848 | 119 | * the X coordinate of the upper left corner |
ef13cd7f | 120 | * @param y |
7ce18848 | 121 | * the Y coordinate of the upper left corner |
ef13cd7f NR |
122 | * @param width |
123 | * the width radius | |
124 | * @param height | |
125 | * the height radius | |
a917f100 NR |
126 | * @param fill |
127 | * fill the content of the ellipse | |
ef13cd7f NR |
128 | */ |
129 | static public void drawEllipse3D(Graphics g, Color color, int x, int y, | |
db0af0d9 | 130 | int width, int height, boolean fill) { |
ef13cd7f NR |
131 | if (g instanceof Graphics2D) { |
132 | Graphics2D g2 = (Graphics2D) g; | |
133 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, | |
134 | RenderingHints.VALUE_ANTIALIAS_ON); | |
135 | ||
136 | // Retains the previous state | |
137 | Paint oldPaint = g2.getPaint(); | |
138 | ||
139 | // Base shape | |
140 | g2.setColor(color); | |
db0af0d9 NR |
141 | if (fill) { |
142 | g2.fillOval(x, y, width, height); | |
143 | } else { | |
144 | g2.drawOval(x, y, width, height); | |
145 | } | |
a917f100 | 146 | |
ef13cd7f NR |
147 | // Compute dark/bright colours |
148 | Paint p = null; | |
db0af0d9 NR |
149 | Color dark = color.darker().darker(); |
150 | Color bright = color.brighter().brighter(); | |
ef13cd7f NR |
151 | Color darkEnd = new Color(dark.getRed(), dark.getGreen(), |
152 | dark.getBlue(), 0); | |
153 | Color darkPartial = new Color(dark.getRed(), dark.getGreen(), | |
154 | dark.getBlue(), 64); | |
155 | Color brightEnd = new Color(bright.getRed(), bright.getGreen(), | |
156 | bright.getBlue(), 0); | |
157 | ||
158 | // Adds shadows at the bottom left | |
159 | p = new GradientPaint(0, height, dark, width, 0, darkEnd); | |
160 | g2.setPaint(p); | |
db0af0d9 NR |
161 | if (fill) { |
162 | g2.fillOval(x, y, width, height); | |
163 | } else { | |
164 | g2.drawOval(x, y, width, height); | |
165 | } | |
ef13cd7f NR |
166 | // Adds highlights at the top right |
167 | p = new GradientPaint(width, 0, bright, 0, height, brightEnd); | |
168 | g2.setPaint(p); | |
db0af0d9 NR |
169 | if (fill) { |
170 | g2.fillOval(x, y, width, height); | |
171 | } else { | |
172 | g2.drawOval(x, y, width, height); | |
173 | } | |
a917f100 | 174 | |
ef13cd7f NR |
175 | // Darken the edges |
176 | p = new RadialGradientPaint(x + width / 2f, y + height / 2f, | |
177 | Math.min(width / 2f, height / 2f), new float[] { 0f, 1f }, | |
178 | new Color[] { darkEnd, darkPartial }, | |
179 | RadialGradientPaint.CycleMethod.NO_CYCLE); | |
180 | g2.setPaint(p); | |
db0af0d9 NR |
181 | if (fill) { |
182 | g2.fillOval(x, y, width, height); | |
183 | } else { | |
184 | g2.drawOval(x, y, width, height); | |
185 | } | |
ef13cd7f NR |
186 | |
187 | // Adds inner highlight at the top right | |
188 | p = new RadialGradientPaint(x + 3f * width / 4f, y + height / 4f, | |
189 | Math.min(width / 4f, height / 4f), | |
190 | new float[] { 0.0f, 0.8f }, | |
191 | new Color[] { bright, brightEnd }, | |
192 | RadialGradientPaint.CycleMethod.NO_CYCLE); | |
193 | g2.setPaint(p); | |
db0af0d9 NR |
194 | if (fill) { |
195 | g2.fillOval(x * 2, y, width, height); | |
196 | } else { | |
197 | g2.drawOval(x * 2, y, width, height); | |
198 | } | |
ef13cd7f NR |
199 | |
200 | // Reset original paint | |
201 | g2.setPaint(oldPaint); | |
202 | } else { | |
203 | g.setColor(color); | |
db0af0d9 NR |
204 | if (fill) { |
205 | g.fillOval(x, y, width, height); | |
206 | } else { | |
207 | g.drawOval(x, y, width, height); | |
208 | } | |
ef13cd7f NR |
209 | } |
210 | } | |
a917f100 NR |
211 | |
212 | /** | |
213 | * Add a {@link JScrollPane} around the given panel and use a sensible (for | |
214 | * me) increment for the mouse wheel. | |
215 | * | |
216 | * @param pane | |
217 | * the panel to wrap in a {@link JScrollPane} | |
218 | * @param allowHorizontal | |
219 | * allow horizontal scrolling (not always desired) | |
220 | * | |
221 | * @return the {@link JScrollPane} | |
222 | */ | |
223 | static public JScrollPane scroll(JComponent pane, boolean allowHorizontal) { | |
3af909c1 NR |
224 | return scroll(pane, allowHorizontal, true); |
225 | } | |
226 | ||
227 | /** | |
228 | * Add a {@link JScrollPane} around the given panel and use a sensible (for | |
229 | * me) increment for the mouse wheel. | |
230 | * | |
231 | * @param pane | |
232 | * the panel to wrap in a {@link JScrollPane} | |
233 | * @param allowHorizontal | |
234 | * allow horizontal scrolling (not always desired) | |
235 | * @param allowVertical | |
236 | * allow vertical scrolling (usually yes, but sometimes you only | |
237 | * want horizontal) | |
238 | * | |
239 | * @return the {@link JScrollPane} | |
240 | */ | |
241 | static public JScrollPane scroll(JComponent pane, boolean allowHorizontal, | |
242 | boolean allowVertical) { | |
a917f100 | 243 | JScrollPane scroll = new JScrollPane(pane); |
3af909c1 | 244 | |
a917f100 | 245 | scroll.getVerticalScrollBar().setUnitIncrement(16); |
3af909c1 NR |
246 | scroll.getHorizontalScrollBar().setUnitIncrement(16); |
247 | ||
a917f100 | 248 | if (!allowHorizontal) { |
66c20ee9 | 249 | scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); |
a917f100 | 250 | } |
3af909c1 | 251 | if (!allowVertical) { |
66c20ee9 | 252 | scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); |
3af909c1 NR |
253 | } |
254 | ||
a917f100 NR |
255 | return scroll; |
256 | } | |
1fdc7577 NR |
257 | |
258 | /** | |
259 | * Show a confirmation message to the user to show him the changes since | |
260 | * last version. | |
261 | * <p> | |
262 | * HTML 3.2 supported, links included (the user browser will be launched if | |
263 | * possible). | |
264 | * <p> | |
265 | * If this is already the latest version, a message will still be displayed. | |
266 | * | |
267 | * @param parentComponent | |
268 | * determines the {@link java.awt.Frame} in which the dialog is | |
269 | * displayed; if <code>null</code>, or if the | |
270 | * <code>parentComponent</code> has no {@link java.awt.Frame}, a | |
271 | * default {@link java.awt.Frame} is used | |
272 | * @param updates | |
273 | * the new version | |
274 | * @param introText | |
275 | * an introduction text before the list of changes | |
276 | * @param title | |
277 | * the title of the dialog | |
278 | * | |
279 | * @return TRUE if the user clicked on OK, false if the dialog was dismissed | |
280 | */ | |
281 | static public boolean showUpdatedDialog(Component parentComponent, | |
282 | VersionCheck updates, String introText, String title) { | |
66c20ee9 | 283 | |
1fdc7577 NR |
284 | StringBuilder builder = new StringBuilder(); |
285 | final JEditorPane updateMessage = new JEditorPane("text/html", ""); | |
286 | if (introText != null && !introText.isEmpty()) { | |
287 | builder.append(introText); | |
288 | builder.append("<br>"); | |
289 | builder.append("<br>"); | |
290 | } | |
291 | for (Version v : updates.getNewer()) { | |
292 | builder.append("\t<b>" // | |
293 | + "Version " + v.toString() // | |
294 | + "</b>"); | |
295 | builder.append("<br>"); | |
296 | builder.append("<ul>"); | |
297 | for (String item : updates.getChanges().get(v)) { | |
298 | builder.append("<li>" + item + "</li>"); | |
299 | } | |
300 | builder.append("</ul>"); | |
301 | } | |
302 | ||
303 | // html content | |
304 | updateMessage.setText("<html><body>" // | |
305 | + builder// | |
306 | + "</body></html>"); | |
307 | ||
308 | // handle link events | |
309 | updateMessage.addHyperlinkListener(new HyperlinkListener() { | |
310 | @Override | |
311 | public void hyperlinkUpdate(HyperlinkEvent e) { | |
312 | if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) | |
313 | try { | |
314 | Desktop.getDesktop().browse(e.getURL().toURI()); | |
315 | } catch (IOException ee) { | |
59f93f81 | 316 | ee.printStackTrace(); |
1fdc7577 | 317 | } catch (URISyntaxException ee) { |
59f93f81 | 318 | ee.printStackTrace(); |
1fdc7577 NR |
319 | } |
320 | } | |
321 | }); | |
322 | updateMessage.setEditable(false); | |
323 | updateMessage.setBackground(new JLabel().getBackground()); | |
324 | updateMessage.addHyperlinkListener(new HyperlinkListener() { | |
325 | @Override | |
326 | public void hyperlinkUpdate(HyperlinkEvent evn) { | |
327 | if (evn.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { | |
328 | if (Desktop.isDesktopSupported()) { | |
329 | try { | |
330 | Desktop.getDesktop().browse(evn.getURL().toURI()); | |
331 | } catch (IOException e) { | |
332 | } catch (URISyntaxException e) { | |
333 | } | |
334 | } | |
335 | } | |
336 | } | |
337 | }); | |
338 | ||
339 | return JOptionPane.showConfirmDialog(parentComponent, updateMessage, | |
340 | title, JOptionPane.DEFAULT_OPTION) == JOptionPane.OK_OPTION; | |
341 | } | |
66c20ee9 | 342 | |
d831b327 NR |
343 | /** |
344 | * Set the given {@link JButton} as "pressed" (selected, but with more UI | |
345 | * visibility). | |
346 | * <p> | |
347 | * The {@link JButton} will answer {@link JButton#isSelected()} if it is | |
348 | * pressed. | |
349 | * | |
350 | * @param button | |
351 | * the button to select/press | |
352 | * @param pressed | |
353 | * the new "pressed" state | |
354 | */ | |
355 | static public void setButtonPressed(JButton button, boolean pressed) { | |
356 | if (buttonNormal == null) { | |
357 | JButton defButton = new JButton(" "); | |
358 | buttonNormal = defButton.getBackground(); | |
359 | if (buttonNormal.getBlue() >= 128) { | |
360 | buttonPressed = new Color( // | |
361 | Math.max(buttonNormal.getRed() - 100, 0), // | |
362 | Math.max(buttonNormal.getGreen() - 100, 0), // | |
363 | Math.max(buttonNormal.getBlue() - 100, 0)); | |
364 | } else { | |
365 | buttonPressed = new Color( // | |
366 | Math.min(buttonNormal.getRed() + 100, 255), // | |
367 | Math.min(buttonNormal.getGreen() + 100, 255), // | |
368 | Math.min(buttonNormal.getBlue() + 100, 255)); | |
369 | } | |
370 | } | |
371 | ||
372 | button.setSelected(pressed); | |
373 | button.setBackground(pressed ? buttonPressed : buttonNormal); | |
374 | } | |
66c20ee9 NR |
375 | |
376 | /** | |
377 | * Set the given {@link windows} to full screen mode, on the desktop it | |
378 | * currently resides on. | |
379 | * <p> | |
380 | * Can be cancelled by calling again with a NULL value. | |
381 | * | |
382 | * @param win | |
383 | * the window to set to full screen | |
384 | */ | |
385 | static public void setFullscreenWindow(Window win) { | |
386 | GraphicsEnvironment env = GraphicsEnvironment | |
387 | .getLocalGraphicsEnvironment(); | |
388 | GraphicsDevice[] screens = env.getScreenDevices(); | |
389 | ||
390 | if (win == null) { | |
391 | for (GraphicsDevice screen : screens) { | |
392 | if (win == null) { | |
393 | screen.setFullScreenWindow(null); | |
394 | } | |
395 | } | |
396 | ||
397 | return; | |
398 | } | |
399 | ||
400 | GraphicsDevice current = null; | |
401 | for (GraphicsDevice screen : screens) { | |
402 | GraphicsConfiguration[] confs = screen.getConfigurations(); | |
403 | for (GraphicsConfiguration conf : confs) { | |
404 | if (conf.getBounds().contains(win.getBounds())) { | |
405 | current = screen; | |
406 | break; | |
407 | } | |
408 | } | |
409 | ||
410 | if (current != null) | |
411 | break; | |
412 | } | |
413 | ||
414 | if (current == null) { | |
415 | current = GraphicsEnvironment.getLocalGraphicsEnvironment() | |
416 | .getDefaultScreenDevice(); | |
417 | } | |
418 | ||
419 | current.setFullScreenWindow(win); | |
420 | } | |
8caeb8bd | 421 | } |