View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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      //--- Constant(s) ---
71  
72  	/***
73  	 * Kicker offset.
74  	 */
75  	public static final int POPUP_MENU_HEIGHT_INSET = 50;
76  
77      //--- Data field(s) ---
78  
79      protected static Preferences prefs = Preferences.getInstance();
80  
81      //--- Constructor(s) ---
82  
83      //--- Method(s) ---
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 //      public JComponent createSeparator(String text, int alignment) 
202 //  	{
203 //          JPanel header = new JPanel(new GridBagLayout());
204 //          GridBagConstraints gbc = new GridBagConstraints();
205 //          gbc.weightx = 0.0;
206 //          gbc.weighty = 1.0;
207 //          gbc.anchor = GridBagConstraints.SOUTHWEST;
208 //          gbc.fill = GridBagConstraints.BOTH;
209 //          gbc.gridwidth = 1;
210 //          gbc.gridheight = 3;
211 //          if (text != null && text.length() > 0) {
212 //  			JLabel label = new TitleLabel();
213 //          setTextAndMnemonic(label, textWithMnemonic);
214 //          label.setVerticalAlignment(SwingConstants.CENTER);
215 //          label.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, gap));
216 //          return label;
217 
218 //              header.add(createTitle(text, 4), gbc);
219 //          }
220 
221 //          gbc.weightx = 1.0;
222 //          gbc.weighty = 1.0;
223 //          gbc.gridwidth = GridBagConstraints.REMAINDER;
224 //          gbc.gridheight = 1;
225 //          JSeparator separator = new JSeparator();
226 //          header.add(Box.createGlue(), gbc);
227 //          gbc.weighty = 0.0;
228 //          header.add(separator, gbc);
229 //          gbc.weighty = 1.0;
230 //          header.add(Box.createGlue(), gbc);
231 
232 //          return header;
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 //  		JPanel panel = new JPanel(new BorderLayout());
252 //  		panel.add(new JSeparator(), BorderLayout.CENTER);
253 //  		JLabel label = new JLabel(title);
254 //  		label.setFont(new Font("Dialog", Font.BOLD, 14));
255 //  		label.setForeground(Color.blue);
256 //  		panel.add(label, BorderLayout.EAST);
257 //  		return panel;
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 //          Element map = jt.getDocument().getDefaultRootElement();
294 //  		Element lastLine = map.getElement(map.getElementCount() - 1);
295 //  		jt.setCaretPosition(lastLine.getEndOffset() - 1);
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 			// recurse
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 		// try first letters of words first
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 		// pick any character, start with the second one
396 		// the first one has already been checked
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 		// try first letters of words first
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 		// pick any character, start with the second one
422 		// the first one has already been checked
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 		// try first letters of words first
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 		// pick any character, start with the second one
453 		// the first one has already been checked
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 		// file reading failed for some reason, fallback to altText
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 			//x -=  (origin.x + width) - screen.width;
514 			// we prefer kde behaviour
515 			x -= width;
516 		}
517 
518 		if (origin.y + height > screen.height) {
519 			// we prefer kde behaviour
520 			//y -= (origin.y + height) - screen.height;
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 }