1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.xnap.commons.gui.util;
22
23 import java.awt.Color;
24 import java.awt.Component;
25 import java.awt.Container;
26 import java.awt.Dimension;
27 import java.awt.Font;
28 import java.awt.Point;
29 import java.awt.Toolkit;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.ActionListener;
32 import java.awt.event.ComponentAdapter;
33 import java.awt.event.ComponentEvent;
34 import java.awt.event.InputEvent;
35 import java.awt.event.KeyEvent;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.util.Enumeration;
39 import java.util.HashSet;
40 import java.util.StringTokenizer;
41 import javax.swing.AbstractAction;
42 import javax.swing.AbstractButton;
43 import javax.swing.Action;
44 import javax.swing.ActionMap;
45 import javax.swing.BorderFactory;
46 import javax.swing.InputMap;
47 import javax.swing.JComponent;
48 import javax.swing.JLabel;
49 import javax.swing.JMenu;
50 import javax.swing.JMenuItem;
51 import javax.swing.JPopupMenu;
52 import javax.swing.JScrollBar;
53 import javax.swing.JTabbedPane;
54 import javax.swing.JTable;
55 import javax.swing.JTree;
56 import javax.swing.KeyStroke;
57 import javax.swing.UIManager;
58 import javax.swing.border.Border;
59 import javax.swing.text.JTextComponent;
60 import javax.swing.tree.TreeNode;
61 import javax.swing.tree.TreePath;
62 import org.xnap.commons.gui.ThinBevelBorder;
63 import org.xnap.commons.i18n.I18n;
64 import org.xnap.commons.i18n.I18nFactory;
65 import org.xnap.commons.util.FileHelper;
66 import org.xnap.commons.util.SystemHelper;
67
68 /***
69 * Helps with gui related tasks.
70 */
71 public class GUIHelper
72 {
73 static final I18n I18N = I18nFactory.getI18n(GUIHelper.class);
74
75 /***
76 * Kicker offset.
77 */
78 public static final int POPUP_MENU_HEIGHT_INSET = 50;
79
80 /***
81 * Adds a mapping between the enter key and <code>action</code> to the
82 * input map of <code>c</code>.
83 *
84 * @return true, if successful; false, otherwise
85 */
86 public static boolean bindEnterKey(JComponent c, Action action)
87 {
88 KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
89 return bindKey(c, ks, action, true);
90 }
91
92 /***
93 * Does the same as {@link #bindEnterKey(JComponent, Action)}but uses the
94 * default input map and not the window input map.
95 *
96 * @return true, if successful; false, otherwise
97 */
98 public static boolean bindEnterKeyLocally(JComponent c, Action action)
99 {
100 KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
101 return bindKey(c, ks, action, false);
102 }
103
104 /***
105 * Adds a mapping between the escape key and <code>action</code> to the
106 * input map of <code>c</code>.
107 *
108 * @return true, if successful; false, otherwise
109 */
110 public static boolean bindEscapeKey(JComponent c, Action action)
111 {
112 KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
113 return bindKey(c, ks, action, true);
114 }
115
116 /***
117 * Adds a mapping between <code>ks</code> and <code>action</code> to the
118 * input map of <code>c</code>.
119 *
120 * @return true, if successful; false, otherwise
121 */
122 public static boolean bindKey(JComponent c, KeyStroke ks, Action action,
123 boolean whenInFocusedWindow)
124 {
125 InputMap inputMap = (whenInFocusedWindow) ? c
126 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) : c
127 .getInputMap();
128 ActionMap actionMap = c.getActionMap();
129 if (inputMap != null && actionMap != null) {
130 inputMap.put(ks, action);
131 actionMap.put(action, action);
132 return true;
133 }
134 return false;
135 }
136
137 /***
138 * Returns an etched default border.
139 * TODO move this to builder?
140 */
141 public static Border createDefaultBorder(String title)
142 {
143 return BorderFactory.createTitledBorder(BorderFactory
144 .createEtchedBorder(), " " + title + " ");
145 }
146
147 /***
148 * Returns an empty border.
149 */
150 public static Border createEmptyBorder(int inset)
151 {
152 return BorderFactory.createEmptyBorder(inset, inset, inset, inset);
153 }
154
155 /***
156 * Returns an empty border.
157 */
158 public static Border createEmptyBorder()
159 {
160 return createEmptyBorder(0);
161 }
162
163 /***
164 * Returns an empty border.
165 */
166 public static Border createEtchedBorder()
167 {
168 return BorderFactory.createEtchedBorder();
169 }
170
171 public static Border createLoweredBorder()
172 {
173 return new ThinBevelBorder(ThinBevelBorder.LOWERED);
174 }
175
176 public static Border createRaisedBorder()
177 {
178 return new ThinBevelBorder(ThinBevelBorder.RAISED);
179 }
180
181 /***
182 * Creates and answers a label with separator; useful to separate paragraphs
183 * in a panel. This is often a better choice than a
184 * <code>TitledBorder</code>.
185 * <p>
186 * The current implementation doesn't support component alignments.
187 *
188 * <p>
189 * Copyright (c) 2003 JGoodies Karsten Lentzsch. All Rights Reserved.
190 * Modified by Steffen Pingel for XNap.
191 *
192 * @param text
193 * the title's text
194 * @param alignment
195 * text alignment: left, center, right
196 * @return a separator with title label
197 */
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 /***
230 * Returns a component that displays <code>title</code> in a bold font.
231 *
232 */
233 public static JComponent createHeader(String title)
234 {
235 JLabel label = new JLabel(title);
236 Font font = UIManager.getFont("TitledBorder.font");
237 if (font != null) {
238 label.setFont(font.deriveFont(Font.BOLD));
239 }
240 Color foreground = UIManager.getColor("TitledBorder.titleColor");
241 if (foreground != null) {
242 label.setForeground(foreground);
243 }
244 return label;
245 }
246
247 /***
248 * Returns a titled border.
249 */
250 public static Border createTitledBorder(String title, int inset)
251 {
252 return BorderFactory.createCompoundBorder(createDefaultBorder(title),
253 createEmptyBorder(inset));
254 }
255
256 public static void expandAllNodes(JTree tree, boolean expand)
257 {
258 TreeNode root = (TreeNode)tree.getModel().getRoot();
259 expandAllNodes(tree, new TreePath(root), expand);
260 }
261
262 public static void expandAllNodes(JTree tree, TreePath path, boolean expand) {
263
264
265 TreeNode node = (TreeNode)path.getLastPathComponent();
266 if (node.getChildCount() > 0) {
267 for (Enumeration e = node.children(); e.hasMoreElements(); ) {
268 TreeNode child = (TreeNode)e.nextElement();
269 TreePath childPath = path.pathByAddingChild(child);
270 expandAllNodes(tree, childPath, expand);
271 }
272 }
273
274 if (expand) {
275 tree.expandPath(path);
276 } else {
277 tree.collapsePath(path);
278 }
279 }
280
281 public static void restrictWidth(JComponent jc)
282 {
283 jc.setMaximumSize(new Dimension(jc.getPreferredSize().width, jc
284 .getMaximumSize().height));
285 }
286
287 public static void scrollToEnd(JTextComponent jt)
288 {
289
290
291
292 jt.setCaretPosition(jt.getDocument().getEndPosition().getOffset() - 1);
293 }
294
295 /***
296 * Returns true, if <code>jsb</code> is at the maximum value.
297 */
298 public static boolean shouldScroll(JScrollBar jsb)
299 {
300 int pos = jsb.getValue() + jsb.getVisibleAmount();
301 return (pos == jsb.getMaximum());
302 }
303
304 public static KeyStroke getMenuKeyStroke(int keyCode)
305 {
306 int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
307 return KeyStroke.getKeyStroke(keyCode, mask);
308 }
309
310 public static void setAccelerator(JMenuItem jmi, int keyCode)
311 {
312 int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
313 jmi.setAccelerator(KeyStroke.getKeyStroke(keyCode, mask));
314 }
315
316 public static void setMnemonics(JTabbedPane pane)
317 {
318 HashSet<Integer> letters = new HashSet<Integer>();
319 for (int i = 0; i < pane.getTabCount(); i++) {
320 if (pane.getMnemonicAt(i) == 0) {
321 pane.setMnemonicAt(i, getMnemonicForText(pane.getTitleAt(i),
322 letters));
323 }
324 }
325 }
326
327
328 public static void setMnemonics(Container c)
329 {
330 setMnemonics(c, null);
331 }
332
333 public static void setMnemonics(Container c, HashSet<Integer> l)
334 {
335 HashSet<Integer> letters = (l != null) ? l : new HashSet<Integer>();
336 for (int i = 0; i < c.getComponentCount(); i++) {
337 Component component = c.getComponent(i);
338 if (component instanceof AbstractButton) {
339 AbstractButton ab = (AbstractButton)component;
340 if (ab.getMnemonic() == 0) {
341 setMnemonics(ab, letters);
342 }
343 else {
344 letters.add(new Integer(ab.getMnemonic()));
345 }
346 }
347 if (component instanceof JLabel) {
348 JLabel label = (JLabel)component;
349 if (label.getDisplayedMnemonic() != 0) {
350 letters.add(new Integer(label.getDisplayedMnemonic()));
351 }
352 if (label.getLabelFor() != null) {
353 setMnemonics(label, letters);
354 }
355 }
356 if (component instanceof JMenu) {
357 setMnemonics(((JMenu)component).getPopupMenu());
358 }
359
360 if (component instanceof Container) {
361 setMnemonics((Container)component, letters);
362 }
363 }
364 }
365
366 private static boolean setMnemonics(JLabel label, HashSet<Integer> letters)
367 {
368 if (label.getText() == null) {
369 return true;
370 }
371 String text = label.getText();
372
373 StringTokenizer t = new StringTokenizer(text);
374 while (t.hasMoreTokens()) {
375 int character = (int)t.nextToken().charAt(0);
376 if (!letters.contains(character)) {
377 letters.add(character);
378 label.setDisplayedMnemonic(character);
379 return true;
380 }
381 }
382
383
384 for (int i = 1; i < text.length(); i++) {
385 int character = (int)text.charAt(i);
386 if (text.charAt(i) != ' ' && !letters.contains(character)) {
387 letters.add(character);
388 label.setDisplayedMnemonic(character);
389 return true;
390 }
391 }
392 return false;
393 }
394
395 private static int getMnemonicForText(String text, HashSet<Integer> letters)
396 {
397
398 StringTokenizer t = new StringTokenizer(text);
399 while (t.hasMoreTokens()) {
400 int character = (int)t.nextToken().charAt(0);
401 if (!letters.contains(character)) {
402 letters.add(character);
403 return character;
404 }
405 }
406
407
408 for (int i = 1; i < text.length(); i++) {
409 int character = (int)text.charAt(i);
410 if (text.charAt(i) != ' ' && !letters.contains(character)) {
411 letters.add(character);
412 return character;
413 }
414 }
415 return 0;
416 }
417
418 private static boolean setMnemonics(AbstractButton ab, HashSet<Integer> letters)
419 {
420 if (ab.getText() == null) {
421 return true;
422 }
423 String text = ab.getText().toUpperCase();
424
425 StringTokenizer t = new StringTokenizer(text);
426 while (t.hasMoreTokens()) {
427 int character = (int)t.nextToken().charAt(0);
428 if (!letters.contains(character)) {
429 letters.add(character);
430 ab.setMnemonic(character);
431 return true;
432 }
433 }
434
435
436 for (int i = 1; i < text.length(); i++) {
437 int character = (int)text.charAt(i);
438 if (text.charAt(i) != ' ' && !letters.contains(character)) {
439 letters.add(character);
440 ab.setMnemonic(character);
441 return true;
442 }
443 }
444 return false;
445 }
446
447 /***
448 * Loads text from file and sets it to <code>jtc</code>.
449 *
450 * <p>
451 * If file is not found or could not be read, sets <code>altText</code>
452 * instead.
453 */
454 public static void showFile(JTextComponent jtc, String filename,
455 String altText)
456 {
457 InputStream s = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename);
458 if (s != null) {
459 try {
460 jtc.setText(FileHelper.readText(s));
461 jtc.setCaretPosition(0);
462 return;
463 }
464 catch (IOException e) {}
465 finally {
466 try {
467 s.close();
468 }
469 catch (IOException e) {}
470 }
471 }
472
473 jtc.setText(altText);
474 }
475
476 public static void showPopupMenu(JPopupMenu jpm, Component source, int x,
477 int y, int yOffset)
478 {
479 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
480 screen.height -= POPUP_MENU_HEIGHT_INSET;
481 Point origin = source.getLocationOnScreen();
482 origin.translate(x, y);
483 int height = jpm.getHeight();
484 if (height == 0) {
485 jpm.addComponentListener(new SizeListener(source, x, y));
486 }
487 int width = jpm.getWidth();
488 if (origin.x + width > screen.width) {
489
490
491 x -= width;
492 }
493 if (origin.y + height > screen.height) {
494
495
496 y -= height;
497 y += yOffset;
498 }
499 jpm.show(source, x, y);
500 }
501
502 public static void showPopupMenu(JPopupMenu jpm, Component source, int x,
503 int y)
504 {
505 showPopupMenu(jpm, source, x, y, 0);
506 }
507
508 /***
509 * Wraps HTML tags around <code>text</code> so the maximum width is
510 * limited to a senseful value.
511 *
512 * @return text, enclosed in table html tags
513 */
514 public static String label(String text)
515 {
516 return tt(text, 500);
517 }
518
519 /***
520 * Wraps HTML tags around <code>text</code> so the maximum width is
521 * limited to a senseful value.
522 *
523 * @return text, enclosed in table html tags
524 */
525 public static String tt(String text, int width)
526 {
527 StringBuilder sb = new StringBuilder(33 + text.length() + 25);
528 sb.append("<html>");
529 sb.append("<table><tr><td width=\"" + width + "\">");
530 sb.append(text);
531 sb.append("</td></tr></table>");
532 sb.append("</html>");
533 return sb.toString();
534 }
535
536 public static KeyStroke getMenuShortcut(int keyCode)
537 {
538 int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
539 return KeyStroke.getKeyStroke(keyCode, mask);
540 }
541
542 /***
543 * Wraps HTML tags around <code>text</code> so the maximum width is
544 * limited to a sensible value.
545 *
546 * @return text, enclosed in table html tags
547 */
548 public static String tt(String text)
549 {
550 return tt(text, 300);
551 }
552
553 /***
554 * Formats key, value as a HTML table row, the key is highlighted as bold.
555 */
556 public static String tableRow(String key, String value)
557 {
558 StringBuilder sb = new StringBuilder();
559 sb.append("<tr><td><b>");
560 sb.append(key);
561 sb.append("</b></td><td> ");
562 sb.append((value != null) ? value : I18N.tr("Unknown"));
563 sb.append("</td></tr>");
564 return sb.toString();
565 }
566
567 public static void limitSize(JComponent c)
568 {
569 c.setMaximumSize(new Dimension(c.getPreferredSize().width, c
570 .getMaximumSize().height));
571 }
572
573 public static void bindExpandCollapseKeysToTree(final JTree tree)
574 {
575 Action treeExpandAction = new AbstractAction() {
576 public void actionPerformed(ActionEvent event)
577 {
578 TreePath path = tree.getSelectionPath();
579 if (path != null) {
580 GUIHelper.expandAllNodes(tree, path, true);
581 }
582 }
583 };
584 tree.getActionMap().put(treeExpandAction, treeExpandAction);
585 tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MULTIPLY, 0), treeExpandAction);
586 tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ASTERISK, KeyEvent.SHIFT_MASK), treeExpandAction);
587
588 Action treeCollapseAction = new AbstractAction() {
589 public void actionPerformed(ActionEvent event)
590 {
591 TreePath path = tree.getSelectionPath();
592 if (path != null) {
593 GUIHelper.expandAllNodes(tree, path, false);
594 }
595 }
596 };
597 tree.getActionMap().put(treeCollapseAction, treeCollapseAction);
598 tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DIVIDE, 0), treeCollapseAction);
599 }
600
601 /***
602 * Adds some Emacs like keybindings to a table for moving between rows.
603 */
604 public static void bindEmacsKeysToTable(JTable jta)
605 {
606 ActionListener al = jta.getActionForKeyStroke(KeyStroke.getKeyStroke(
607 KeyEvent.VK_DOWN, 0));
608 jta.registerKeyboardAction(al, KeyStroke.getKeyStroke(KeyEvent.VK_N,
609 InputEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
610 al = jta.getActionForKeyStroke(KeyStroke
611 .getKeyStroke(KeyEvent.VK_UP, 0));
612 jta.registerKeyboardAction(al, KeyStroke.getKeyStroke(KeyEvent.VK_P,
613 InputEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
614 al = jta.getActionForKeyStroke(KeyStroke.getKeyStroke(
615 KeyEvent.VK_PAGE_DOWN, 0));
616 jta.registerKeyboardAction(al, KeyStroke.getKeyStroke(KeyEvent.VK_V,
617 InputEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
618 al = jta.getActionForKeyStroke(KeyStroke.getKeyStroke(
619 KeyEvent.VK_PAGE_UP, 0));
620 jta.registerKeyboardAction(al, KeyStroke.getKeyStroke(KeyEvent.VK_V,
621 InputEvent.ALT_MASK), JComponent.WHEN_FOCUSED);
622 al = jta.getActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_HOME,
623 InputEvent.CTRL_MASK));
624 jta.registerKeyboardAction(al, KeyStroke.getKeyStroke(KeyEvent.VK_LESS,
625 InputEvent.ALT_MASK), JComponent.WHEN_FOCUSED);
626 al = jta.getActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_END,
627 InputEvent.CTRL_MASK));
628 jta.registerKeyboardAction(al, KeyStroke.getKeyStroke(KeyEvent.VK_LESS,
629 InputEvent.ALT_MASK + InputEvent.SHIFT_MASK),
630 JComponent.WHEN_FOCUSED);
631 }
632
633 private static class SizeListener extends ComponentAdapter
634 {
635
636 private Component source;
637 private int x;
638 private int y;
639
640 public SizeListener(Component source, int x, int y)
641 {
642 this.source = source;
643 this.x = x;
644 this.y = y;
645 }
646
647 public void componentResized(ComponentEvent e)
648 {
649 e.getComponent().removeComponentListener(this);
650 GUIHelper.showPopupMenu((JPopupMenu)e.getComponent(), source, x, y);
651 }
652 }
653
654 }