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; | |
9 | import java.awt.Paint; | |
10 | import java.awt.RadialGradientPaint; | |
11 | import java.awt.RenderingHints; | |
1fdc7577 NR |
12 | import java.io.IOException; |
13 | import java.net.URISyntaxException; | |
ef13cd7f | 14 | |
d831b327 | 15 | import javax.swing.JButton; |
a917f100 | 16 | import javax.swing.JComponent; |
1fdc7577 NR |
17 | import javax.swing.JEditorPane; |
18 | import javax.swing.JLabel; | |
19 | import javax.swing.JOptionPane; | |
a917f100 | 20 | import javax.swing.JScrollPane; |
8caeb8bd NR |
21 | import javax.swing.UIManager; |
22 | import javax.swing.UnsupportedLookAndFeelException; | |
1fdc7577 NR |
23 | import javax.swing.event.HyperlinkEvent; |
24 | import javax.swing.event.HyperlinkListener; | |
25 | ||
1fdc7577 NR |
26 | import be.nikiroo.utils.Version; |
27 | import be.nikiroo.utils.VersionCheck; | |
8caeb8bd NR |
28 | |
29 | /** | |
30 | * Some Java Swing utilities. | |
31 | * | |
32 | * @author niki | |
33 | */ | |
34 | public class UIUtils { | |
d831b327 NR |
35 | static private Color buttonNormal; |
36 | static private Color buttonPressed; | |
37 | ||
8caeb8bd | 38 | /** |
b9839223 | 39 | * Set a fake "native Look & Feel" for the application if possible |
8caeb8bd NR |
40 | * (check for the one currently in use, then try GTK). |
41 | * <p> | |
42 | * <b>Must</b> be called prior to any GUI work. | |
b9839223 NR |
43 | * |
44 | * @return TRUE if it succeeded | |
8caeb8bd | 45 | */ |
b9839223 | 46 | static public boolean setLookAndFeel() { |
8caeb8bd | 47 | // native look & feel |
b9839223 NR |
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 & 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 & Feel to use | |
64 | * | |
65 | * @return TRUE if it succeeded | |
66 | */ | |
67 | static public boolean setLookAndFeel(String laf) { | |
8caeb8bd | 68 | try { |
b9839223 NR |
69 | UIManager.setLookAndFeel(laf); |
70 | return true; | |
8caeb8bd NR |
71 | } catch (InstantiationException e) { |
72 | } catch (ClassNotFoundException e) { | |
73 | } catch (UnsupportedLookAndFeelException e) { | |
74 | } catch (IllegalAccessException e) { | |
75 | } | |
b9839223 NR |
76 | |
77 | return false; | |
8caeb8bd | 78 | } |
a917f100 | 79 | |
db0af0d9 NR |
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 | */ | |
a917f100 NR |
98 | static public void drawEllipse3D(Graphics g, Color color, int x, int y, |
99 | int width, int height) { | |
db0af0d9 NR |
100 | drawEllipse3D(g, color, x, y, width, height, true); |
101 | } | |
ef13cd7f NR |
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 | |
7ce18848 | 113 | * the X coordinate of the upper left corner |
ef13cd7f | 114 | * @param y |
7ce18848 | 115 | * the Y coordinate of the upper left corner |
ef13cd7f NR |
116 | * @param width |
117 | * the width radius | |
118 | * @param height | |
119 | * the height radius | |
a917f100 NR |
120 | * @param fill |
121 | * fill the content of the ellipse | |
ef13cd7f NR |
122 | */ |
123 | static public void drawEllipse3D(Graphics g, Color color, int x, int y, | |
db0af0d9 | 124 | int width, int height, boolean fill) { |
ef13cd7f NR |
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); | |
db0af0d9 NR |
135 | if (fill) { |
136 | g2.fillOval(x, y, width, height); | |
137 | } else { | |
138 | g2.drawOval(x, y, width, height); | |
139 | } | |
a917f100 | 140 | |
ef13cd7f NR |
141 | // Compute dark/bright colours |
142 | Paint p = null; | |
db0af0d9 NR |
143 | Color dark = color.darker().darker(); |
144 | Color bright = color.brighter().brighter(); | |
ef13cd7f NR |
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); | |
db0af0d9 NR |
155 | if (fill) { |
156 | g2.fillOval(x, y, width, height); | |
157 | } else { | |
158 | g2.drawOval(x, y, width, height); | |
159 | } | |
ef13cd7f NR |
160 | // Adds highlights at the top right |
161 | p = new GradientPaint(width, 0, bright, 0, height, brightEnd); | |
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 | } | |
a917f100 | 168 | |
ef13cd7f NR |
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); | |
db0af0d9 NR |
175 | if (fill) { |
176 | g2.fillOval(x, y, width, height); | |
177 | } else { | |
178 | g2.drawOval(x, y, width, height); | |
179 | } | |
ef13cd7f NR |
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); | |
db0af0d9 NR |
188 | if (fill) { |
189 | g2.fillOval(x * 2, y, width, height); | |
190 | } else { | |
191 | g2.drawOval(x * 2, y, width, height); | |
192 | } | |
ef13cd7f NR |
193 | |
194 | // Reset original paint | |
195 | g2.setPaint(oldPaint); | |
196 | } else { | |
197 | g.setColor(color); | |
db0af0d9 NR |
198 | if (fill) { |
199 | g.fillOval(x, y, width, height); | |
200 | } else { | |
201 | g.drawOval(x, y, width, height); | |
202 | } | |
ef13cd7f NR |
203 | } |
204 | } | |
a917f100 NR |
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) { | |
3af909c1 NR |
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) { | |
a917f100 | 237 | JScrollPane scroll = new JScrollPane(pane); |
3af909c1 | 238 | |
a917f100 | 239 | scroll.getVerticalScrollBar().setUnitIncrement(16); |
3af909c1 NR |
240 | scroll.getHorizontalScrollBar().setUnitIncrement(16); |
241 | ||
a917f100 NR |
242 | if (!allowHorizontal) { |
243 | scroll.setHorizontalScrollBarPolicy( | |
244 | JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); | |
245 | } | |
3af909c1 NR |
246 | if (!allowVertical) { |
247 | scroll.setVerticalScrollBarPolicy( | |
248 | JScrollPane.VERTICAL_SCROLLBAR_NEVER); | |
249 | } | |
250 | ||
a917f100 NR |
251 | return scroll; |
252 | } | |
1fdc7577 NR |
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) { | |
59f93f81 | 312 | ee.printStackTrace(); |
1fdc7577 | 313 | } catch (URISyntaxException ee) { |
59f93f81 | 314 | ee.printStackTrace(); |
1fdc7577 NR |
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 | } | |
d831b327 NR |
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 | } | |
8caeb8bd | 371 | } |