9dedbb2d18136a8315a5993e996d415f5c62280f
[nikiroo-utils.git] / ui / UIUtils.java
1 package be.nikiroo.utils.ui;
2
3 import java.awt.Color;
4 import java.awt.Component;
5 import java.awt.Desktop;
6 import java.awt.GradientPaint;
7 import java.awt.Graphics;
8 import java.awt.Graphics2D;
9 import java.awt.GraphicsConfiguration;
10 import java.awt.GraphicsDevice;
11 import java.awt.GraphicsEnvironment;
12 import java.awt.Paint;
13 import java.awt.Point;
14 import java.awt.RadialGradientPaint;
15 import java.awt.Rectangle;
16 import java.awt.RenderingHints;
17 import java.awt.Window;
18 import java.io.IOException;
19 import java.net.URISyntaxException;
20
21 import javax.swing.JButton;
22 import javax.swing.JComponent;
23 import javax.swing.JEditorPane;
24 import javax.swing.JLabel;
25 import javax.swing.JOptionPane;
26 import javax.swing.JScrollPane;
27 import javax.swing.UIManager;
28 import javax.swing.UnsupportedLookAndFeelException;
29 import javax.swing.event.HyperlinkEvent;
30 import javax.swing.event.HyperlinkListener;
31
32 import be.nikiroo.utils.Version;
33 import be.nikiroo.utils.VersionCheck;
34
35 import com.sun.java.swing.plaf.windows.resources.windows;
36
37 /**
38 * Some Java Swing utilities.
39 *
40 * @author niki
41 */
42 public class UIUtils {
43 static private Color buttonNormal;
44 static private Color buttonPressed;
45
46 /**
47 * Set a fake "native Look & Feel" for the application if possible
48 * (check for the one currently in use, then try GTK).
49 * <p>
50 * <b>Must</b> be called prior to any GUI work.
51 *
52 * @return TRUE if it succeeded
53 */
54 static public boolean setLookAndFeel() {
55 // native look & feel
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";
60
61 return setLookAndFeel(lf);
62 }
63
64 /**
65 * Switch to the given Look &amp; 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 &amp; Feel to use
72 *
73 * @return TRUE if it succeeded
74 */
75 static public boolean setLookAndFeel(String laf) {
76 try {
77 UIManager.setLookAndFeel(laf);
78 return true;
79 } catch (InstantiationException e) {
80 } catch (ClassNotFoundException e) {
81 } catch (UnsupportedLookAndFeelException e) {
82 } catch (IllegalAccessException e) {
83 }
84
85 return false;
86 }
87
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 */
106 static public void drawEllipse3D(Graphics g, Color color, int x, int y,
107 int width, int height) {
108 drawEllipse3D(g, color, x, y, width, height, true);
109 }
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
121 * the X coordinate of the upper left corner
122 * @param y
123 * the Y coordinate of the upper left corner
124 * @param width
125 * the width radius
126 * @param height
127 * the height radius
128 * @param fill
129 * fill the content of the ellipse
130 */
131 static public void drawEllipse3D(Graphics g, Color color, int x, int y,
132 int width, int height, boolean fill) {
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);
143 if (fill) {
144 g2.fillOval(x, y, width, height);
145 } else {
146 g2.drawOval(x, y, width, height);
147 }
148
149 // Compute dark/bright colours
150 Paint p = null;
151 Color dark = color.darker().darker();
152 Color bright = color.brighter().brighter();
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);
163 if (fill) {
164 g2.fillOval(x, y, width, height);
165 } else {
166 g2.drawOval(x, y, width, height);
167 }
168 // Adds highlights at the top right
169 p = new GradientPaint(width, 0, bright, 0, height, brightEnd);
170 g2.setPaint(p);
171 if (fill) {
172 g2.fillOval(x, y, width, height);
173 } else {
174 g2.drawOval(x, y, width, height);
175 }
176
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);
183 if (fill) {
184 g2.fillOval(x, y, width, height);
185 } else {
186 g2.drawOval(x, y, width, height);
187 }
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);
196 if (fill) {
197 g2.fillOval(x * 2, y, width, height);
198 } else {
199 g2.drawOval(x * 2, y, width, height);
200 }
201
202 // Reset original paint
203 g2.setPaint(oldPaint);
204 } else {
205 g.setColor(color);
206 if (fill) {
207 g.fillOval(x, y, width, height);
208 } else {
209 g.drawOval(x, y, width, height);
210 }
211 }
212 }
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) {
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) {
245 JScrollPane scroll = new JScrollPane(pane);
246
247 scroll.getVerticalScrollBar().setUnitIncrement(16);
248 scroll.getHorizontalScrollBar().setUnitIncrement(16);
249
250 if (!allowHorizontal) {
251 scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
252 }
253 if (!allowVertical) {
254 scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
255 }
256
257 return scroll;
258 }
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) {
285
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) {
318 ee.printStackTrace();
319 } catch (URISyntaxException ee) {
320 ee.printStackTrace();
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 }
344
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 }
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 }
401
402 Rectangle r = win.getBounds();
403 Point center = new Point(r.x + r.width / 2, r.y + r.height / 2);
404
405 GraphicsDevice current = null;
406 for (GraphicsDevice screen : screens) {
407 GraphicsConfiguration[] confs = screen.getConfigurations();
408 for (GraphicsConfiguration conf : confs) {
409 if (conf.getBounds().contains(center)) {
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 }
426 }