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 }