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