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