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