1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.util.swing; |
|
27 |
|
28 import java.awt.*; |
|
29 import java.awt.event.*; |
|
30 import java.io.IOException; |
|
31 import java.lang.reflect.InvocationTargetException; |
|
32 import java.util.*; |
|
33 import java.util.List; |
|
34 import javax.swing.*; |
|
35 import javax.swing.border.Border; |
|
36 import com.oracle.solaris.vp.util.misc.*; |
|
37 import com.oracle.solaris.vp.util.misc.finder.Finder; |
|
38 |
|
39 public class GUIUtil { |
|
40 // |
|
41 // Inner classes |
|
42 // |
|
43 |
|
44 public static interface HasMnemonic { |
|
45 String getText(); |
|
46 |
|
47 void setMnemonic(int i); |
|
48 |
|
49 void setMnemonicIndex(int i); |
|
50 |
|
51 void setText(String text); |
|
52 } |
|
53 |
|
54 public static class MnemonicAction implements HasMnemonic { |
|
55 // |
|
56 // Instance data |
|
57 // |
|
58 |
|
59 private Action action; |
|
60 |
|
61 // |
|
62 // Constructors |
|
63 // |
|
64 |
|
65 public MnemonicAction(Action action) { |
|
66 this.action = action; |
|
67 } |
|
68 |
|
69 // |
|
70 // HasMnemonic methods |
|
71 // |
|
72 |
|
73 @Override |
|
74 public String getText() { |
|
75 Object text = action.getValue(Action.NAME); |
|
76 return text == null ? null : text.toString(); |
|
77 } |
|
78 |
|
79 @Override |
|
80 public void setMnemonic(int i) { |
|
81 action.putValue(Action.MNEMONIC_KEY, i); |
|
82 } |
|
83 |
|
84 @Override |
|
85 public void setMnemonicIndex(int i) { |
|
86 action.putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, i); |
|
87 } |
|
88 |
|
89 @Override |
|
90 public void setText(String text) { |
|
91 action.putValue(Action.NAME, text); |
|
92 } |
|
93 |
|
94 // |
|
95 // MnemonicAction methods |
|
96 // |
|
97 |
|
98 public Action getAction() { |
|
99 return action; |
|
100 } |
|
101 } |
|
102 |
|
103 public static class MnemonicButton implements HasMnemonic { |
|
104 // |
|
105 // Instance data |
|
106 // |
|
107 |
|
108 private AbstractButton button; |
|
109 |
|
110 // |
|
111 // Constructors |
|
112 // |
|
113 |
|
114 public MnemonicButton(AbstractButton button) { |
|
115 this.button = button; |
|
116 } |
|
117 |
|
118 // |
|
119 // HasMnemonic methods |
|
120 // |
|
121 |
|
122 @Override |
|
123 public String getText() { |
|
124 return button.getText(); |
|
125 } |
|
126 |
|
127 @Override |
|
128 public void setMnemonic(int i) { |
|
129 button.setMnemonic(i); |
|
130 } |
|
131 |
|
132 @Override |
|
133 public void setMnemonicIndex(int i) { |
|
134 button.setDisplayedMnemonicIndex(i); |
|
135 } |
|
136 |
|
137 @Override |
|
138 public void setText(String text) { |
|
139 button.setText(text); |
|
140 } |
|
141 |
|
142 // |
|
143 // MnemonicButton methods |
|
144 // |
|
145 |
|
146 public AbstractButton getButton() { |
|
147 return button; |
|
148 } |
|
149 } |
|
150 |
|
151 public static class MnemonicLabel implements HasMnemonic { |
|
152 // |
|
153 // Instance data |
|
154 // |
|
155 |
|
156 private JLabel label; |
|
157 |
|
158 // |
|
159 // Constructors |
|
160 // |
|
161 |
|
162 public MnemonicLabel(JLabel label) { |
|
163 this.label = label; |
|
164 } |
|
165 |
|
166 // |
|
167 // HasMnemonic methods |
|
168 // |
|
169 |
|
170 @Override |
|
171 public String getText() { |
|
172 return label.getText(); |
|
173 } |
|
174 |
|
175 @Override |
|
176 public void setMnemonic(int i) { |
|
177 label.setDisplayedMnemonic(i); |
|
178 } |
|
179 |
|
180 @Override |
|
181 public void setMnemonicIndex(int i) { |
|
182 label.setDisplayedMnemonicIndex(i); |
|
183 } |
|
184 |
|
185 @Override |
|
186 public void setText(String text) { |
|
187 label.setText(text); |
|
188 } |
|
189 |
|
190 // |
|
191 // MnemonicLabel methods |
|
192 // |
|
193 |
|
194 public JLabel getLabel() { |
|
195 return label; |
|
196 } |
|
197 } |
|
198 |
|
199 // |
|
200 // Static data |
|
201 // |
|
202 |
|
203 public static final Border BORDER_LINE = |
|
204 BorderFactory.createLineBorder(Color.GRAY, 1); |
|
205 |
|
206 public static final String PROPERTY_COMPONENT_SPACING = |
|
207 "toolkit.component.spacing"; |
|
208 |
|
209 public static final String PROPERTY_BUTTON_SPACING = |
|
210 "toolkit.button.spacing"; |
|
211 |
|
212 public static final TypedPropertiesWrapper prefs = |
|
213 new TypedPropertiesWrapper(); |
|
214 |
|
215 static { |
|
216 try { |
|
217 Properties defaultPrefs = new Properties(); |
|
218 defaultPrefs.load( |
|
219 Finder.getResource("runtime.properties").openStream()); |
|
220 prefs.getPropertiesList().add(defaultPrefs); |
|
221 } catch (IOException e) { |
|
222 // Unlikely -- the resource should always exist and be accessible |
|
223 } |
|
224 } |
|
225 |
|
226 // |
|
227 // Static methods |
|
228 // |
|
229 |
|
230 /** |
|
231 * Adds the given {@code AWTKeyStroke}s to the given {@code Component}'s |
|
232 * list of focus traversal keys. |
|
233 * |
|
234 * @param comp |
|
235 * the {@code Component} |
|
236 * |
|
237 * @param forward |
|
238 * {@code true} to make {@code keyStrokes} traverse forward, |
|
239 * {@code false} to traverse backward |
|
240 * |
|
241 * @param keyStrokes |
|
242 * the {@code AWTKeyStroke}s |
|
243 */ |
|
244 public static void addFocusTraversalKeys(Component comp, boolean forward, |
|
245 AWTKeyStroke... keyStrokes) { |
|
246 modifyFocusTraversalKeys(comp, forward, true, keyStrokes); |
|
247 } |
|
248 |
|
249 public static void adjustForMnemonic(HasMnemonic obj) { |
|
250 String text = obj.getText(); |
|
251 String[] groups = TextUtil.match(text, "(.*?)_(([a-zA-Z0-9]).*)"); |
|
252 if (groups != null) { |
|
253 obj.setText(groups[1] + groups[2]); |
|
254 |
|
255 String mnemonic = groups[3].toUpperCase(); |
|
256 KeyStroke mnemonicKey = KeyStroke.getKeyStroke(mnemonic); |
|
257 |
|
258 if (mnemonicKey != null) { |
|
259 int mnemonicKeyCode = mnemonicKey.getKeyCode(); |
|
260 obj.setMnemonic(mnemonicKeyCode); |
|
261 obj.setMnemonicIndex(groups[1].length()); |
|
262 } |
|
263 } |
|
264 } |
|
265 |
|
266 public static void adjustForMnemonic(Action action) { |
|
267 adjustForMnemonic(new MnemonicAction(action)); |
|
268 } |
|
269 |
|
270 public static void adjustForMnemonic(AbstractButton button) { |
|
271 adjustForMnemonic(new MnemonicButton(button)); |
|
272 } |
|
273 |
|
274 public static void adjustForMnemonic(JLabel label) { |
|
275 adjustForMnemonic(new MnemonicLabel(label)); |
|
276 } |
|
277 |
|
278 public static Border createCompoundBorder(Border... borders) { |
|
279 Border border = null; |
|
280 for (int i = 0; i < borders.length; i++) { |
|
281 border = i == 0 ? borders[i] : |
|
282 BorderFactory.createCompoundBorder(border, borders[i]); |
|
283 } |
|
284 return border; |
|
285 } |
|
286 |
|
287 /** |
|
288 * Creates and returns a JTextField of the {@link #getTextFieldWidth default |
|
289 * width}. |
|
290 */ |
|
291 public static JTextField createTextField() { |
|
292 return new JTextField(getTextFieldWidth()); |
|
293 } |
|
294 |
|
295 /** |
|
296 * Get the default spacing between buttons, as on a toolbar. |
|
297 */ |
|
298 public static int getButtonGap() { |
|
299 return prefs.getInt("toolkit.button.spacing"); |
|
300 } |
|
301 |
|
302 /** |
|
303 * Get the default height for button icons. |
|
304 */ |
|
305 public static int getButtonIconHeight() { |
|
306 return prefs.getInt("toolkit.button.icon.height"); |
|
307 } |
|
308 |
|
309 public static String getClippedString( |
|
310 String text, FontMetrics metrics, int width) { |
|
311 |
|
312 if (text == null) { |
|
313 return text; |
|
314 } |
|
315 |
|
316 int sWidth = metrics.stringWidth(text); |
|
317 if (sWidth <= width) { |
|
318 return text; |
|
319 } |
|
320 |
|
321 String ellipsis = "..."; |
|
322 width -= metrics.stringWidth(ellipsis); |
|
323 char[] chars = text.toCharArray(); |
|
324 |
|
325 int i; |
|
326 for (i = chars.length - 1; i >= 0 && sWidth > width; i--) { |
|
327 sWidth -= metrics.charWidth(chars[i]); |
|
328 } |
|
329 |
|
330 return text.substring(0, i + 1) + ellipsis; |
|
331 } |
|
332 |
|
333 public static Border getEmptyBorder() { |
|
334 int gap = getGap(); |
|
335 return BorderFactory.createEmptyBorder(gap, gap, gap, gap); |
|
336 } |
|
337 |
|
338 public static Border getEmptyHalfBorder() { |
|
339 int gap = getHalfGap(); |
|
340 return BorderFactory.createEmptyBorder(gap, gap, gap, gap); |
|
341 } |
|
342 |
|
343 /** |
|
344 * Gets an icon for the given message type. An attempt will first be made |
|
345 * to retrieve a look-and-feel-specific icon from the {@code JOptionPane} |
|
346 * class. However, some looks and feels (like GTK) don't expose these icons |
|
347 * via the API; in this case local defaults will be returned. |
|
348 * |
|
349 * @param messageType |
|
350 * JOptionPane.ERROR_MESSAGE, |
|
351 * JOptionPane.INFORMATION_MESSAGE, |
|
352 * JOptionPane.WARNING_MESSAGE, or |
|
353 * JOptionPane.QUESTION_MESSAGE. |
|
354 * |
|
355 * @return an {@code Icon}, or {@code null} if no icon could be found |
|
356 * for the given message type |
|
357 */ |
|
358 public static Icon getIcon(int messageType) { |
|
359 Icon icon = null; |
|
360 |
|
361 switch (messageType) { |
|
362 case JOptionPane.ERROR_MESSAGE: |
|
363 icon = UIManager.getIcon("OptionPane.errorIcon"); |
|
364 if (icon == null) { |
|
365 icon = Finder.getIcon("images/dialog/dialog-error.png"); |
|
366 } |
|
367 break; |
|
368 |
|
369 |
|
370 case JOptionPane.INFORMATION_MESSAGE: |
|
371 icon = UIManager.getIcon("OptionPane.informationIcon"); |
|
372 if (icon == null) { |
|
373 icon = Finder.getIcon( |
|
374 "images/dialog/dialog-information.png"); |
|
375 } |
|
376 break; |
|
377 |
|
378 case JOptionPane.WARNING_MESSAGE: |
|
379 icon = UIManager.getIcon("OptionPane.warningIcon"); |
|
380 if (icon == null) { |
|
381 icon = Finder.getIcon( |
|
382 "images/dialog/dialog-warning.png"); |
|
383 } |
|
384 break; |
|
385 |
|
386 case JOptionPane.QUESTION_MESSAGE: |
|
387 icon = UIManager.getIcon("OptionPane.questionIcon"); |
|
388 if (icon == null) { |
|
389 icon = Finder.getIcon( |
|
390 "images/dialog/dialog-question.png"); |
|
391 } |
|
392 break; |
|
393 } |
|
394 |
|
395 return icon; |
|
396 } |
|
397 |
|
398 /** |
|
399 * Get the default spacing between components. |
|
400 */ |
|
401 public static int getGap() { |
|
402 return prefs.getInt("toolkit.component.spacing"); |
|
403 } |
|
404 |
|
405 public static int getHalfGap() { |
|
406 return getGap() / 2; |
|
407 } |
|
408 |
|
409 /** |
|
410 * Get the default height for menu icons. |
|
411 */ |
|
412 public static int getMenuIconHeight() { |
|
413 return prefs.getInt("toolkit.menu.icon.height"); |
|
414 } |
|
415 |
|
416 /** |
|
417 * Gets the preferred size of the given {@code Window}. If the given {@code |
|
418 * Window} is a {@code JFrame} or a {@code JDialog}, and its glass pane is |
|
419 * visible, the glass pane's preferred size will be accounted for as well |
|
420 * ({@code Window.getPreferredSize} does not take the glass pane into |
|
421 * consideration). |
|
422 */ |
|
423 public static Dimension getPreferredSize(Window window) { |
|
424 Dimension pSize = window.getPreferredSize(); |
|
425 |
|
426 Component glass = null; |
|
427 Component content = null; |
|
428 |
|
429 if (window instanceof JFrame) { |
|
430 JFrame frame = (JFrame)window; |
|
431 glass = frame.getGlassPane(); |
|
432 content = frame.getContentPane(); |
|
433 } else if (window instanceof JDialog) { |
|
434 JDialog dialog = (JDialog)window; |
|
435 glass = dialog.getGlassPane(); |
|
436 content = dialog.getContentPane(); |
|
437 } |
|
438 |
|
439 if (glass != null && glass.isVisible()) { |
|
440 Dimension cpSize = content.getPreferredSize(); |
|
441 content.setPreferredSize(glass.getPreferredSize()); |
|
442 |
|
443 Dimension newPSize = window.getPreferredSize(); |
|
444 |
|
445 pSize.width = Math.max(pSize.width, newPSize.width); |
|
446 pSize.height = Math.max(pSize.height, newPSize.height); |
|
447 |
|
448 // Restore previous preferred size, if any |
|
449 content.setPreferredSize(cpSize); |
|
450 } |
|
451 |
|
452 return pSize; |
|
453 } |
|
454 |
|
455 /** |
|
456 * Get the default scroll unit increment. |
|
457 */ |
|
458 public static int getScrollableUnitIncrement() { |
|
459 return prefs.getInt("toolkit.scroll.increment.unit"); |
|
460 } |
|
461 |
|
462 /** |
|
463 * Get the default width for a textfield. |
|
464 */ |
|
465 public static int getTextFieldWidth() { |
|
466 return prefs.getInt("toolkit.textfield.width"); |
|
467 } |
|
468 |
|
469 public static int getTextXOffset(AbstractButton c) { |
|
470 int xoffset = c.getPreferredSize().width; |
|
471 |
|
472 Border b = c.getBorder(); |
|
473 if (b != null) { |
|
474 xoffset -= b.getBorderInsets(c).right; |
|
475 } |
|
476 |
|
477 // Insets m = c.getMargin(); |
|
478 // if (m != null) { |
|
479 // xoffset -= m.right; |
|
480 // } |
|
481 |
|
482 String text = c.getText(); |
|
483 if (text != null) { |
|
484 xoffset -= c.getFontMetrics(c.getFont()).stringWidth(text); |
|
485 } |
|
486 |
|
487 return xoffset; |
|
488 } |
|
489 |
|
490 public static Border getTitledBorder(String title) { |
|
491 return BorderFactory.createCompoundBorder( |
|
492 BorderFactory.createTitledBorder( |
|
493 BorderFactory.createEtchedBorder(), title), getEmptyBorder()); |
|
494 } |
|
495 |
|
496 /** |
|
497 * Get the default margin for toolbar buttons. |
|
498 */ |
|
499 public static int getToolBarButtonMargin() { |
|
500 return prefs.getInt("toolkit.toolbar.button.margin"); |
|
501 } |
|
502 |
|
503 public static TypedPropertiesWrapper getUIPreferences() { |
|
504 return prefs; |
|
505 } |
|
506 |
|
507 /** |
|
508 * Get the default vertical spacing between a label and its {@code |
|
509 * Component} below it. |
|
510 */ |
|
511 public static int getVerticalLabelGap() { |
|
512 return prefs.getInt("toolkit.vertical.label.spacing"); |
|
513 } |
|
514 |
|
515 /** |
|
516 * Grows the given {@code Window} if its preferred size exceeds its actual |
|
517 * size. Will not shrink the {@code Window}. |
|
518 */ |
|
519 public static void growIfNecessary(Window window) { |
|
520 Dimension size = window.getSize(); |
|
521 Dimension pSize = getPreferredSize(window); |
|
522 |
|
523 window.setSize(Math.max(size.width, pSize.width), |
|
524 Math.max(size.height, pSize.height)); |
|
525 } |
|
526 |
|
527 public static void installKeyBinding(JComponent c, int condition, |
|
528 String actionName, Action action, KeyStroke... keyStrokes) { |
|
529 |
|
530 InputMap iMap = c.getInputMap(condition); |
|
531 ActionMap aMap = c.getActionMap(); |
|
532 |
|
533 for (KeyStroke keyStroke : keyStrokes) { |
|
534 iMap.put(keyStroke, actionName); |
|
535 aMap.put(actionName, action); |
|
536 } |
|
537 } |
|
538 |
|
539 public static void installKeyBinding(JComponent c, int condition, |
|
540 String actionName, Action action, int... keyStrokes) { |
|
541 |
|
542 KeyStroke[] array = new KeyStroke[keyStrokes.length]; |
|
543 for (int i = 0; i < array.length; i++) { |
|
544 array[i] = KeyStroke.getKeyStroke(keyStrokes[i], 0); |
|
545 } |
|
546 |
|
547 installKeyBinding(c, condition, actionName, action, array); |
|
548 } |
|
549 |
|
550 /** |
|
551 * Invokes the given {@code Runnable} on the AWT event dispatching thread |
|
552 * (which could be the current thread). This addresses two |
|
553 * disadvantages/annoyances to {@code EventQueue.invokeAndWait}: |
|
554 * <ol> |
|
555 * <li> |
|
556 * Any {@code RuntimeException}s or {@code Error}s thrown by the given |
|
557 * {@code Runnable} are thrown in the current thread, rather than as an |
|
558 * {@code InvocationTargetException}. |
|
559 * </li> |
|
560 * <li> |
|
561 * If the AWT event dispatching thread is interrupted while the given |
|
562 * {@code Runnable} is running, it will be re-run until it completes. |
|
563 * If this behavior is not desired, the given {@code Runnable} should |
|
564 * account for this possibility. |
|
565 * </li> |
|
566 * <ol> |
|
567 * |
|
568 * @param runnable |
|
569 * a {@code Runnable} to run |
|
570 */ |
|
571 public static void invokeAndWait(final Runnable runnable) { |
|
572 if (EventQueue.isDispatchThread()) { |
|
573 runnable.run(); |
|
574 } else { |
|
575 while (true) { |
|
576 try { |
|
577 EventQueue.invokeAndWait(runnable); |
|
578 break; |
|
579 } catch (InvocationTargetException e) { |
|
580 Throwable cause = e.getCause(); |
|
581 ThrowableUtil.appendStackTrace(cause); |
|
582 |
|
583 if (cause instanceof RuntimeException) { |
|
584 throw (RuntimeException)cause; |
|
585 } |
|
586 |
|
587 if (cause instanceof Error) { |
|
588 throw (Error)cause; |
|
589 } |
|
590 |
|
591 // It should not be possible to be here -- any Throwable |
|
592 // thrown by run() should have been a RunTimeException or an |
|
593 // Error, since run throws no checked Exceptions. |
|
594 assert (false); |
|
595 } catch (InterruptedException e) { |
|
596 // Not bloody likely |
|
597 } |
|
598 } |
|
599 } |
|
600 } |
|
601 |
|
602 /** |
|
603 * See {@link #invokeAndWait(java.lang.Runnable)}. |
|
604 * |
|
605 * @return the returned object of the given {@code TypedRunnable} |
|
606 */ |
|
607 public static <T> T invokeAndWait(final TypedRunnable<T> runnable) { |
|
608 final List<T> t = new ArrayList<T>(1); |
|
609 |
|
610 invokeAndWait( |
|
611 new Runnable() { |
|
612 @Override |
|
613 public void run() { |
|
614 t.add(runnable.run()); |
|
615 } |
|
616 }); |
|
617 |
|
618 return t.get(0); |
|
619 } |
|
620 |
|
621 public static boolean isModifierKeyPressed(InputEvent e) { |
|
622 return e.isShiftDown() || e.isControlDown() || e.isAltDown() || |
|
623 e.isMetaDown() || e.isAltGraphDown(); |
|
624 } |
|
625 |
|
626 /** |
|
627 * Returns {@code true} if the given {@code MouseEvent} represents a mouse |
|
628 * click with the given number of clicks, button 1, with no modifier keys |
|
629 * pressed, or {@code false} otherwise. |
|
630 */ |
|
631 public static boolean isUnmodifiedClick(MouseEvent e, int clickCount) { |
|
632 return e.getID() == MouseEvent.MOUSE_CLICKED && e.getButton() == 1 && |
|
633 e.getClickCount() == clickCount && !isModifierKeyPressed(e); |
|
634 } |
|
635 |
|
636 public static void propagate(MouseEvent e, Component c) { |
|
637 Point p = SwingUtilities.convertPoint(e.getComponent(), |
|
638 e.getX(), e.getY(), c); |
|
639 |
|
640 MouseEvent e2; |
|
641 if (e instanceof MouseWheelEvent) { |
|
642 e2 = new MouseWheelEvent(c, e.getID(), e.getWhen(), |
|
643 e.getModifiers(), p.x, p.y, e.getClickCount(), |
|
644 e.isPopupTrigger(), ((MouseWheelEvent)e).getScrollType(), |
|
645 ((MouseWheelEvent)e).getScrollAmount(), |
|
646 ((MouseWheelEvent)e).getWheelRotation()); |
|
647 } else { |
|
648 e2 = new MouseEvent(c, e.getID(), e.getWhen(), e.getModifiers(), |
|
649 p.x, p.y, e.getClickCount(), e.isPopupTrigger()); |
|
650 } |
|
651 |
|
652 c.dispatchEvent(e2); |
|
653 } |
|
654 |
|
655 /** |
|
656 * Removes the given {@code AWTKeyStroke}s from the given {@code |
|
657 * Component}'s list of focus traversal keys. |
|
658 * |
|
659 * @param comp |
|
660 * the {@code Component} |
|
661 * |
|
662 * @param forward |
|
663 * {@code true} if {@code keyStrokes} traverse forward, {@code |
|
664 * false} if backward |
|
665 * |
|
666 * @param keyStrokes |
|
667 * the {@code AWTKeyStroke}s |
|
668 */ |
|
669 public static void removeFocusTraversalKeys(Component comp, boolean forward, |
|
670 AWTKeyStroke... keyStrokes) { |
|
671 modifyFocusTraversalKeys(comp, forward, false, keyStrokes); |
|
672 } |
|
673 |
|
674 /** |
|
675 * Turn on text antialiasing if appropriate for this platform. Technically |
|
676 * this should follow the conventions that Swing uses (matching this setting |
|
677 * to the user's desktop settings), but since Swing doesn't expose this |
|
678 * functionality, turn it on here unconditionally. |
|
679 */ |
|
680 public static void setAARendering(Graphics2D g) { |
|
681 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, |
|
682 RenderingHints.VALUE_TEXT_ANTIALIAS_ON); |
|
683 } |
|
684 |
|
685 public static void setBorderFromFont(JComponent c) { |
|
686 FontMetrics metrics = c.getFontMetrics(c.getFont()); |
|
687 int top = metrics.getDescent(); |
|
688 int left = metrics.charWidth(' '); |
|
689 c.setBorder(BorderFactory.createEmptyBorder(top, left, top, left)); |
|
690 } |
|
691 |
|
692 public static void setEnableStateOnSelect(final AbstractButton button, |
|
693 final boolean recursive, final boolean enabled, |
|
694 final Component... toEnable) { |
|
695 |
|
696 ItemListener l = new ItemListener() { |
|
697 @Override |
|
698 public void itemStateChanged(ItemEvent e) { |
|
699 boolean state = !(button.isSelected() ^ enabled); |
|
700 for (Component c : toEnable) { |
|
701 if (recursive) { |
|
702 setEnabledRecursive(c, state); |
|
703 } else { |
|
704 c.setEnabled(state); |
|
705 } |
|
706 } |
|
707 } |
|
708 }; |
|
709 |
|
710 button.addItemListener(l); |
|
711 |
|
712 // Set initial state |
|
713 l.itemStateChanged(null); |
|
714 } |
|
715 |
|
716 /** |
|
717 * Sets the enabled status of the given {@code Component}'s descendants, if |
|
718 * it is a {@code Container}, then of the given {@code Component} itself. |
|
719 * <p> |
|
720 * If a {@code Container} implementing {@code DescendantEnabler} is |
|
721 * encountered along the way, recursion into the {@code Component} |
|
722 * hierarchy is aborted. |
|
723 */ |
|
724 public static void setEnabledRecursive(Component parent, boolean enabled) { |
|
725 if (parent instanceof Container && |
|
726 !(parent instanceof DescendantEnabler)) { |
|
727 |
|
728 for (Component c : ((Container)parent).getComponents()) { |
|
729 setEnabledRecursive((Container)c, enabled); |
|
730 } |
|
731 } |
|
732 |
|
733 // A disabled JPanel is ugly |
|
734 if (enabled || parent instanceof DescendantEnabler || |
|
735 !(parent instanceof JPanel)) { |
|
736 |
|
737 parent.setEnabled(enabled); |
|
738 } |
|
739 } |
|
740 |
|
741 /** |
|
742 * Sets the left and right margin of an {@code AbstractButton} in a |
|
743 * L&F-independent manner (some L&Fs set this value in the margin, |
|
744 * while others ignore this property and use the border instead). |
|
745 */ |
|
746 public static void setHorizontalMargin(AbstractButton button, int hMargin) { |
|
747 Insets margin = button.getMargin(); |
|
748 margin.left = margin.right = hMargin; |
|
749 |
|
750 Border empty = BorderFactory.createEmptyBorder( |
|
751 margin.top, margin.left, margin.bottom, margin.right); |
|
752 |
|
753 Border outer = button.getBorder(); |
|
754 Border compound = BorderFactory.createCompoundBorder(outer, empty); |
|
755 |
|
756 button.setMargin(new Insets(0, 0, 0, 0)); |
|
757 button.setBorder(compound); |
|
758 } |
|
759 |
|
760 public static void setMinorFont(Component comp) { |
|
761 Font font = comp.getFont(); |
|
762 font = font.deriveFont(font.getSize() * .85f); |
|
763 comp.setFont(font); |
|
764 } |
|
765 |
|
766 public static void setUIPreferences(Properties p) { |
|
767 List<Properties> pList = prefs.getPropertiesList(); |
|
768 if (pList.size() > 1) { |
|
769 pList.remove(1); |
|
770 } |
|
771 pList.add(p); |
|
772 } |
|
773 |
|
774 /** |
|
775 * Sets whether the given {@code Component} uses tab and shift-tab as |
|
776 * forward/backward focus traversal keys. |
|
777 */ |
|
778 public static void setUseStandardFocusTraversalKeys(Component comp, |
|
779 boolean use) { |
|
780 |
|
781 modifyFocusTraversalKeys(comp, true, use, KeyStroke.getKeyStroke( |
|
782 KeyEvent.VK_TAB, 0)); |
|
783 |
|
784 modifyFocusTraversalKeys(comp, false, use, KeyStroke.getKeyStroke( |
|
785 KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK)); |
|
786 } |
|
787 |
|
788 /** |
|
789 * Shows a confirmation message dialog. |
|
790 * |
|
791 * @param component |
|
792 * a {@code Component} contained within a {@code Frame} for |
|
793 * which the dialog will be modal, or {@code null} to display a |
|
794 * non-modal dialog |
|
795 * |
|
796 * @param title |
|
797 * the title to display in the dialog, or {@code null} to use a |
|
798 * default |
|
799 * |
|
800 * @param optionType |
|
801 * a static constant from the {@code JOptionPane} class: {@code |
|
802 * YES_NO_OPTION}, {@code YES_NO_CANCEL_OPTION}, or {@code |
|
803 * OK_CANCEL_OPTION}, or {@code null} to use the default |
|
804 * ({@code OK_CANCEL_OPTION}) |
|
805 * |
|
806 * @param message |
|
807 * the message to display in the dialog |
|
808 * |
|
809 * @return an integer indicating the option selected by the user |
|
810 */ |
|
811 public static int showConfirmation(Component component, String title, |
|
812 Object message, Integer optionType) { |
|
813 |
|
814 if (title == null) { |
|
815 title = Finder.getString("dialog.title.confirm"); |
|
816 } |
|
817 |
|
818 JOptionPane pane = new ExtOptionPane( |
|
819 message, JOptionPane.QUESTION_MESSAGE); |
|
820 pane.setOptionType(JOptionPane.OK_CANCEL_OPTION); |
|
821 |
|
822 JDialog dialog = pane.createDialog(component, title); |
|
823 dialog.setVisible(true); |
|
824 |
|
825 Object selection = pane.getValue(); |
|
826 if (selection == null) { |
|
827 return JOptionPane.CLOSED_OPTION; |
|
828 } |
|
829 |
|
830 return (Integer)selection; |
|
831 } |
|
832 |
|
833 /** |
|
834 * Shows an error message dialog. |
|
835 * |
|
836 * @param component |
|
837 * a {@code Component} contained within a {@code Frame} for |
|
838 * which the dialog will be modal, or {@code null} to display a |
|
839 * non-modal dialog |
|
840 * |
|
841 * @param title |
|
842 * the title to display in the dialog, or {@code null} to use a |
|
843 * default |
|
844 * |
|
845 * @param message |
|
846 * the message to display in the dialog |
|
847 */ |
|
848 public static void showError(Component component, String title, |
|
849 Object message) { |
|
850 |
|
851 if (message != null) { |
|
852 if (title == null) { |
|
853 title = Finder.getString("dialog.title.error"); |
|
854 } |
|
855 |
|
856 showOptionPane(JOptionPane.ERROR_MESSAGE, component, title, |
|
857 message); |
|
858 } |
|
859 } |
|
860 |
|
861 /** |
|
862 * Shows an info message dialog. |
|
863 * |
|
864 * @param component |
|
865 * a {@code Component} contained within a {@code Frame} for |
|
866 * which the dialog will be modal, or {@code null} to display a |
|
867 * non-modal dialog |
|
868 * |
|
869 * @param title |
|
870 * the title to display in the dialog, or {@code null} to use a |
|
871 * default |
|
872 * |
|
873 * @param message |
|
874 * the message to display in the dialog |
|
875 */ |
|
876 public static void showInfo(Component component, String title, |
|
877 String message) { |
|
878 |
|
879 if (title == null) { |
|
880 title = Finder.getString("dialog.title.info"); |
|
881 } |
|
882 |
|
883 showOptionPane(JOptionPane.INFORMATION_MESSAGE, component, title, |
|
884 message); |
|
885 } |
|
886 |
|
887 /** |
|
888 * Shows a width-constrained {@code JOptionPane} with the specified |
|
889 * parameters. |
|
890 * |
|
891 * @see ExtOptionPane |
|
892 */ |
|
893 public static void showOptionPane(int messageType, Component component, |
|
894 String title, Object message) { |
|
895 |
|
896 JOptionPane pane = new ExtOptionPane(message, messageType); |
|
897 JDialog dialog = pane.createDialog(component, title); |
|
898 dialog.setVisible(true); |
|
899 } |
|
900 |
|
901 /** |
|
902 * Shows an warning message dialog. |
|
903 * |
|
904 * @param component |
|
905 * a {@code Component} contained within a {@code Frame} for |
|
906 * which the dialog will be modal, or {@code null} to display a |
|
907 * non-modal dialog |
|
908 * |
|
909 * @param title |
|
910 * the title to display in the dialog, or {@code null} to use a |
|
911 * default |
|
912 * |
|
913 * @param message |
|
914 * the message to display in the dialog |
|
915 */ |
|
916 public static void showWarning(Component component, String title, |
|
917 String message) { |
|
918 |
|
919 if (title == null) { |
|
920 title = Finder.getString("dialog.title.warning"); |
|
921 } |
|
922 |
|
923 showOptionPane(JOptionPane.WARNING_MESSAGE, component, title, message); |
|
924 } |
|
925 |
|
926 // |
|
927 // Private static methods |
|
928 // |
|
929 |
|
930 private static void modifyFocusTraversalKeys(Component comp, |
|
931 boolean forward, boolean add, AWTKeyStroke... keyStrokes) { |
|
932 |
|
933 int dir = forward ? KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS : |
|
934 KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS; |
|
935 |
|
936 Set<AWTKeyStroke> keys = new HashSet<AWTKeyStroke>( |
|
937 comp.getFocusTraversalKeys(dir)); |
|
938 |
|
939 for (AWTKeyStroke key : keyStrokes) { |
|
940 if (add) { |
|
941 keys.add(key); |
|
942 } else { |
|
943 keys.remove(key); |
|
944 } |
|
945 } |
|
946 |
|
947 comp.setFocusTraversalKeys(dir, keys); |
|
948 } |
|
949 } |
|