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