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