001: /* ====================================================================
002: * The QueryForm License, Version 1.1
003: *
004: * Copyright (c) 1998 - 2003 David F. Glasser. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution,
020: * if any, must include the following acknowledgment:
021: * "This product includes software developed by
022: * David F. Glasser."
023: * Alternately, this acknowledgment may appear in the software itself,
024: * if and wherever such third-party acknowledgments normally appear.
025: *
026: * 4. The names "QueryForm" and "David F. Glasser" must
027: * not be used to endorse or promote products derived from this
028: * software without prior written permission. For written
029: * permission, please contact dglasser@pobox.com.
030: *
031: * 5. Products derived from this software may not be called "QueryForm",
032: * nor may "QueryForm" appear in their name, without prior written
033: * permission of David F. Glasser.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL DAVID F. GLASSER, THE APACHE SOFTWARE
039: * FOUNDATION OR ITS CONTRIBUTORS, OR ANY AUTHORS OR DISTRIBUTORS
040: * OF THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This product includes software developed by the
051: * Apache Software Foundation (http://www.apache.org/).
052: *
053: * ====================================================================
054: *
055: * $Source: /cvsroot/qform/qform/src/org/glasser/swing/GUIHelper.java,v $
056: * $Revision: 1.9 $
057: * $Author: dglasser $
058: * $Date: 2005/01/26 02:55:43 $
059: *
060: * --------------------------------------------------------------------
061: */
062: package org.glasser.swing;
063:
064: import javax.swing.*;
065: import java.awt.*;
066: import java.awt.event.*;
067: import javax.swing.event.*;
068: import java.net.URL;
069: import java.util.*;
070: import java.lang.reflect.*;
071: import org.glasser.util.*;
072:
073: import java.util.ResourceBundle;
074:
075: public class GUIHelper {
076:
077: private GUIHelper() {
078: }
079:
080: private static boolean debug = System
081: .getProperty("GUIHelper.debug") != null;
082:
083: public static String defaultMessageTitle = null;
084:
085: /**
086: * After a JButton has been passed to this method, it will be invoked by the ENTER
087: * key whenever it has the focus. This is not the default behavior for the Metal
088: * look-and-feel.
089: */
090: public static void enterPressesWhenFocused(JButton button) {
091:
092: button.registerKeyboardAction(button
093: .getActionForKeyStroke(KeyStroke.getKeyStroke(
094: KeyEvent.VK_SPACE, 0, false)), KeyStroke
095: .getKeyStroke(KeyEvent.VK_ENTER, 0, false),
096: JComponent.WHEN_FOCUSED);
097:
098: button.registerKeyboardAction(button
099: .getActionForKeyStroke(KeyStroke.getKeyStroke(
100: KeyEvent.VK_SPACE, 0, true)), KeyStroke
101: .getKeyStroke(KeyEvent.VK_ENTER, 0, true),
102: JComponent.WHEN_FOCUSED);
103:
104: }
105:
106: /**
107: * Centers a window on the screen. Should be used for top-level windows only. Does nothing
108: * if any needed information is missing, or if window is larger than the screen in
109: * either direction. */
110: public static void centerWindowOnScreen(java.awt.Window w) {
111: Dimension wsize = w.getSize();
112: Dimension screenSize = Toolkit.getDefaultToolkit()
113: .getScreenSize();
114: if (wsize == null || screenSize == null)
115: return;
116: int hdiff = screenSize.width - wsize.width;
117: int vdiff = screenSize.height - wsize.height;
118: if (hdiff < 0 || vdiff < 0)
119: return;
120: w.setLocation(hdiff / 2, vdiff / 2);
121: }
122:
123: /**
124: * Beeps and displays message box with icon of given type. messageType parameter
125: * may be JOptionPane.INFORMATION_MESSAGE, JOptionPane.WARNING_MESSAGE
126: * or JOptionPane.ERROR_MESSAGE.
127: */
128: public static void showMessageDialog(Component parent, String msg,
129: String title, int messageType) {
130:
131: msg = Util.wrapLines(msg, 55);
132:
133: Toolkit.getDefaultToolkit().beep();
134:
135: JOptionPane.showMessageDialog(parent, msg, title, messageType);
136:
137: }
138:
139: public static int showConfirmDialog(Component parent, Object msg,
140: String title, int optionType) {
141:
142: return JOptionPane.showConfirmDialog(parent, msg, title,
143: optionType);
144: }
145:
146: public static int showConfirmDialog(Component parent, Object msg,
147: String title, int optionType, int messageType) {
148:
149: return JOptionPane.showConfirmDialog(parent, msg, title,
150: optionType, messageType);
151: }
152:
153: /**
154: * Beeps and displays message box with info icon.
155: */
156: public static void infoMsg(Component parent, String msg,
157: String title) {
158: if (title == null)
159: title = defaultMessageTitle;
160: showMessageDialog(parent, msg, title,
161: JOptionPane.INFORMATION_MESSAGE);
162: }
163:
164: /**
165: * Beeps and displays message box with error icon.
166: */
167: public static void errMsg(Component parent, String msg, String title) {
168: if (title == null)
169: title = defaultMessageTitle;
170: showMessageDialog(parent, msg, title, JOptionPane.ERROR_MESSAGE);
171:
172: }
173:
174: /**
175: * Beeps and displays message box with a warning icon.
176: */
177: public static void warningMsg(Component parent, String msg,
178: String title) {
179: if (title == null)
180: title = defaultMessageTitle;
181: showMessageDialog(parent, msg, title,
182: JOptionPane.WARNING_MESSAGE);
183: }
184:
185: /**
186: * Displays info about an exception in a message box.
187: */
188: public static void exceptionMsg(Component parent, Exception ex) {
189: if (ex == null)
190: return;
191: String msg = ex.getMessage();
192: if (msg == null)
193: msg = "A "
194: + ex.getClass().getName()
195: + " has occurred. Please see the error logs for more information.";
196: errMsg(parent, msg, ex.getClass().getName());
197: }
198:
199: /**
200: * Displays info about an exception in a message box.
201: */
202: public static void exceptionMsg(Exception ex) {
203: exceptionMsg(null, ex);
204: }
205:
206: /**
207: * This method peforms the tedious chore of constructing a form
208: * panel consisting of JTextField objects, each with a corresponding
209: * JLabel to its left. It uses a GridBagLayout to layout the components.
210: *
211: * @param panel The JPanel to be laid out.
212: * @param fields Each member array in this NxN array is of the form:
213: * <pre>
214: * {<JTextField object>, <Label Text String>, [<Tooltip Text String>]}
215: * </pre>
216: * The String containing the label text is used to construct a JLabel
217: * for the JTextField. If the Tooltip Text String is present (it need not be)
218: * then it will be used as the tooltip text for both the JLabel and
219: * the JTextField.<p>
220: *
221: * @param fieldWidth if negative, this parameter is ignored, otherwise it
222: * is used as the ipadx value in the GridBagConstraints object for each
223: * JTextField object. If the panel's width will not be determined by a layout
224: * manager or some other means, this parameter should be used.
225: *
226: * @param vgap the vertical gap between label/textfield rows.
227: *
228: * @param labelFont the Font that will be used for the JLabels. If null,
229: * the default font is used.
230: *
231: * @param labelForeground the color that will be used for the foreground of
232: * the JLabels. If null, the default color is used.
233: *
234: * @param stretchLastRow if true, the component in the last row will have it's
235: * fill attribute set to "BOTH" so that it will stretch vertically as well as
236: * horizontally.
237: *
238: * @param lastRowHeight this is the amount used for ipady on the last row's component.
239: * if less than 1, then the component's preferred height will be used.
240: */
241: public static void buildFormPanel(JPanel panel, Object[][] fields,
242: int fieldWidth, int vgap, Font labelFont,
243: Color labelForeground, boolean stretchLastRow,
244: int lastRowHeight) {
245: GridBagLayout gb = new GridBagLayout();
246: panel.setLayout(gb);
247: GridBagConstraints gc = new GridBagConstraints();
248: Insets labelInsets = new Insets(0, 0, 0, 0);
249: Insets fieldInsets = new Insets(0, 5, vgap, 0);
250: gc.anchor = gc.NORTH;
251: gc.fill = gc.HORIZONTAL;
252:
253: // configure one row at a time...
254: for (int row = 0; row < fields.length; row++) {
255:
256: // if we're on the last row...
257: if (row == fields.length - 1) {
258: // make this row "stretchable"
259: gc.weighty = 1;
260:
261: // and don't add any padding to the bottom
262: // fieldInsets.bottom = 0;
263: } else { // if we're not on the last row ...
264: // constrain the height of this row
265: gc.weighty = 0;
266:
267: // and pad the bottom to acheive a gap between
268: // the rows.
269: fieldInsets.bottom = vgap;
270: }
271:
272: // add the label
273: gc.gridx = 0;
274: gc.gridy = row;
275: gc.weightx = 0;
276: gc.ipadx = 0;
277: gc.insets = labelInsets;
278:
279: // adding a tooltip to a label seems to add it to the focus
280: // cycle, which we don't want, so we'll make sure the labels
281: // we instantiate can't get the focus by overriding their
282: // isFocusTraversable() method.
283: JLabel label = new JLabel((String) fields[row][1]) {
284: public boolean isFocusTraversable() {
285: return false;
286: }
287: };
288: if (labelFont != null)
289: label.setFont(labelFont);
290: if (labelForeground != null)
291: label.setForeground(labelForeground);
292: panel.add(label, gc);
293: // if a tooltip was supplied, set it for the label
294: if (fields[row].length > 2)
295: label.setToolTipText((String) fields[row][2]);
296:
297: // now add the textfield
298: gc.gridx = 1;
299: gc.weightx = 1;
300: if (fieldWidth > -1)
301: gc.ipadx = fieldWidth;
302: gc.insets = fieldInsets;
303: JComponent field = (JComponent) fields[row][0];
304:
305: // if thie is the last row
306: if (row == fields.length - 1) {
307: if (stretchLastRow)
308: gc.fill = gc.BOTH;
309: if (lastRowHeight > 0)
310: gc.ipady = lastRowHeight;
311: }
312:
313: // enableInputMethods(false) is called so pressing Alt-<key>
314: // combinations (for button mnemonics) will not cause the
315: // <key> character to be inserted into the text field. This
316: // works around a bug in JDK 1.2 which is fixed in JDK 1.3.
317: // field.enableInputMethods(false);
318: panel.add(field, gc);
319: // if a tooltip was supplied, set it for the label
320: if (fields[row].length > 2)
321: field.setToolTipText((String) fields[row][2]);
322:
323: }
324: }
325:
326: /**
327: * Adds JMenuItems, separators and submenus to a JMenu or JPopup menu.
328: *
329: * @param menu the menu to be configured. If null, a JPopupMenu is intantiated and returned.
330: *
331: * @param menuConfig a array of arrays containing the information needed to configure
332: * the menu. Each element array is used to configure a JMenuItem that is added to the
333: * menu, or a submenu. An element array must have at least one element, and may have
334: * up to four. These elements represent:
335: *
336: * <ul>
337: * <li> element 0:</li> (REQUIRED) The String used as the text for a menu item or a submenu. If it is
338: * is for a submenu, it should begin with an underscore (_), which will be stripped off. Optionally,
339: * this element may be the String "SEPARATOR", in which case a menu separator will be inserted.
340: * <li> element 1:</li> (REQUIRED, unless element 0 is "SEPARATOR") If this row is for a JMenuItem,
341: * then this element is a string that will be set as the "actionCommand" for that JMenuItem, so
342: * ActionListeners can identify which menu item was selected. If this row is for a submenu, then
343: * this element is another Object[][] menuConfig array that will be used to configure the
344: * submenu.
345: * <li> element 2:</li> (OPTIONAL) a String, the first character of which will be set as the
346: * mnemonic for the menu item of submenu.
347: * <li> element 3:</li> (OPTIONAL) a String which contains a message that is sent to the StatusMessageDisplay object
348: * (which typically would be a statusbar) if one was provided, any time the menu item is
349: * passed over by the mouse.
350: * </ul>
351: *
352: * @param listener an ActionListener that will be added to each JMenuItem.
353: *
354: * @param statusDisplay a StatusMessageDisplay object that will receive status messages from JMenuItems
355: * they're passed over in an open menu. This would typically be a status bar. If status message
356: * elements are provided in any of the config array elements, this argument should be provided also.
357: *
358: */
359: public static JComponent buildMenu(JComponent menu,
360: Object[][] menuConfig, ActionListener listener,
361: ChangeListener changeListener, ItemListener itemListener) {
362: return buildMenu(menu, menuConfig, listener, changeListener,
363: itemListener, null);
364: }
365:
366: /**
367: * Adds JMenuItems, separators and submenus to a JMenu or JPopup menu.
368: *
369: * @param menu the menu to be configured. If null, a JPopupMenu is intantiated and returned.
370: *
371: * @param menuConfig a array of arrays containing the information needed to configure
372: * the menu. Each element array is used to configure a JMenuItem that is added to the
373: * menu, or a submenu. An element array must have at least one element, and may have
374: * up to four. These elements represent:
375: *
376: * <ul>
377: * <li> element 0:</li> (REQUIRED) The String used as the text for a menu item or a submenu. If it is
378: * is for a submenu, it should begin with an underscore (_), which will be stripped off. Optionally,
379: * this element may be the String "SEPARATOR", in which case a menu separator will be inserted.
380: * <li> element 1:</li> (REQUIRED, unless element 0 is "SEPARATOR") If this row is for a JMenuItem,
381: * then this element is a string that will be set as the "actionCommand" for that JMenuItem, so
382: * ActionListeners can identify which menu item was selected. If this row is for a submenu, then
383: * this element is another Object[][] menuConfig array that will be used to configure the
384: * submenu.
385: * <li> element 2:</li> (OPTIONAL) a String, the first character of which will be set as the
386: * mnemonic for the menu item of submenu.
387: * <li> element 3:</li> (OPTIONAL) a String which contains a message that is sent to the StatusMessageDisplay object
388: * (which typically would be a statusbar) if one was provided, any time the menu item is
389: * passed over by the mouse.
390: * </ul>
391: *
392: * @param listener an ActionListener that will be added to each JMenuItem.
393: *
394: * @param statusDisplay a StatusMessageDisplay object that will receive status messages from JMenuItems
395: * they're passed over in an open menu. This would typically be a status bar. If status message
396: * elements are provided in any of the config array elements, this argument should be provided also.
397: *
398: */
399: public static JComponent buildMenu(JComponent menu,
400: Object[][] menuConfig, ActionListener listener,
401: ChangeListener changeListener, ItemListener itemListener,
402: ResourceBundle bundle) {
403:
404: if (menu == null)
405: menu = new JPopupMenu();
406:
407: for (int j = 0; j < menuConfig.length; j++) {
408: Object[] row = menuConfig[j];
409:
410: String label = (String) row[0];
411: if (debug)
412: System.out.println("label " + label);
413:
414: // if the label text is "SEPARATOR", then this is a menu separator. That means
415: // that you can't have an actual menu item called "SEPARATOR unless you're using
416: // a resource bundle.
417: boolean isSeparator = "SEPARATOR".equals(label);
418:
419: // if the label text begins with "CHECKBOX_, it indicates that this menuitem is supposed
420: // to be a checkbox. The "CHECKBOX_" prefix is then removed.
421: boolean isCheckbox = label.indexOf("CHECKBOX_") == 0;
422: if (isCheckbox) {
423: label = label.substring(9);
424: }
425:
426: boolean isSubMenu = (!isSeparator && (row[1] instanceof Object[][]));
427:
428: String actionCommand = null;
429: Object[][] subMenuConfig = null;
430:
431: if (isSubMenu) {
432: subMenuConfig = (Object[][]) row[1];
433: } else if (!isSeparator) {
434: actionCommand = (String) row[1];
435: }
436:
437: // to be consistent with previous versions of this code, we'll remove the leading
438: // underscore off of the menu item. If you want a leading underscore on your menu
439: // item, you have to use a double-leading underscore, and only the first one will be
440: // removed.
441: if (label.indexOf("_") == 0) {
442: // remove the leading underscore from the label text.
443: label = label.substring(1);
444: }
445:
446: String mnemonicCharacter = null;
447: String tooltipText = null;
448: if (row.length > 2) {
449: mnemonicCharacter = (String) row[2];
450: }
451: if (row.length > 3) {
452: tooltipText = (String) row[3];
453: }
454:
455: // now that we've gotten all of the elments out of the array, we'll use them as strings
456: // to fetch localized strings from a ResourceBundle, if we have one. Otherwise, we'll use
457: // them as the actual displayed text.
458: if (!isSeparator && bundle != null) {
459: label = bundle.getString(label);
460: if (mnemonicCharacter != null) {
461: mnemonicCharacter = bundle
462: .getString(mnemonicCharacter);
463: }
464: if (tooltipText != null) {
465: tooltipText = bundle.getString(tooltipText);
466: }
467: }
468:
469: if (isSubMenu) {
470: JMenu subMenu = new JMenu(label);
471: if (mnemonicCharacter != null
472: && mnemonicCharacter.length() > 0) {
473: subMenu.setMnemonic(mnemonicCharacter.charAt(0));
474: }
475: if (tooltipText != null) {
476: subMenu.setToolTipText(tooltipText);
477: }
478: if (changeListener != null)
479: subMenu.addChangeListener(changeListener);
480: if (itemListener != null)
481: subMenu.addItemListener(itemListener);
482: menu.add(buildMenu(subMenu, subMenuConfig, listener,
483: changeListener, itemListener, bundle));
484: } else if (isSeparator) {
485: if (menu instanceof JPopupMenu) {
486: ((JPopupMenu) menu).addSeparator();
487: } else if (menu instanceof JMenu) {
488: ((JMenu) menu).addSeparator();
489: }
490: } else {
491: JMenuItem item = null;
492: if (isCheckbox) {
493: item = new JCheckBoxMenuItem(label);
494: } else {
495: item = new JMenuItem(label);
496: }
497: item.setActionCommand(actionCommand);
498: menu.add(item);
499:
500: if (mnemonicCharacter != null
501: && mnemonicCharacter.length() > 0) {
502: item.setMnemonic(mnemonicCharacter.charAt(0));
503: }
504: if (tooltipText != null) {
505: item.setToolTipText(tooltipText);
506: }
507:
508: if (listener != null)
509: item.addActionListener(listener);
510: if (changeListener != null)
511: item.addChangeListener(changeListener);
512: if (itemListener != null)
513: item.addItemListener(itemListener);
514:
515: }
516: }
517:
518: return menu;
519: }
520:
521: /**
522: * This returns an array containing all of the JMenuItems from
523: * the given JMenu. It does not recurse through submenus.
524: */
525: public static JMenuItem[] getMenuItems(JMenu menu) {
526: ArrayList list = new ArrayList();
527: for (int j = 0; j < menu.getItemCount(); j++) {
528: JMenuItem mi = menu.getItem(j);
529: if (mi != null)
530: list.add(mi);
531: }
532: return (JMenuItem[]) list.toArray(new JMenuItem[list.size()]);
533: }
534:
535: /**
536: * This returns an array containing all of the JMenuItems from
537: * the given JPopupMenu. It does not recurse through submenus.
538: */
539: public static JMenuItem[] getMenuItems(JPopupMenu menu) {
540: ArrayList list = new ArrayList();
541: MenuElement[] elements = menu.getSubElements();
542: for (int j = 0; j < elements.length; j++) {
543: if (elements[j] instanceof JMenuItem) {
544: list.add(elements[j]);
545: }
546: }
547: return (JMenuItem[]) list.toArray(new JMenuItem[list.size()]);
548: }
549:
550: /**
551: * Searches through a tree of menus and returns the JMenuItem that has the string
552: * "command" set as its ActionCommand.
553: */
554: public static JMenuItem findMenuItemByActionCommand(
555: MenuElement menu, String command) {
556: MenuElement[] subElements = menu.getSubElements();
557: for (int j = 0; j < subElements.length; j++) {
558: if (subElements[j] instanceof JMenuItem) {
559: JMenuItem item = (JMenuItem) subElements[j];
560: if (command.equals(item.getActionCommand()))
561: return item;
562: }
563: JMenuItem descendantItem = findMenuItemByActionCommand(
564: subElements[j], command);
565: if (descendantItem != null)
566: return descendantItem;
567: }
568: return null;
569: }
570:
571: /**
572: * Searches through a tree of menus and returns the JMenuItem that has the string
573: * "labelText" set as its label text.
574: */
575: public static JMenuItem findMenuItemByLabelText(MenuElement menu,
576: String labelText) {
577: MenuElement[] subElements = menu.getSubElements();
578: for (int j = 0; j < subElements.length; j++) {
579: if (subElements[j] instanceof JMenuItem) {
580: JMenuItem item = (JMenuItem) subElements[j];
581: if (labelText.equals(item.getText()))
582: return item;
583: }
584: JMenuItem descendantItem = findMenuItemByLabelText(
585: subElements[j], labelText);
586: if (descendantItem != null)
587: return descendantItem;
588: }
589: return null;
590: }
591:
592: /**
593: * Configures and adds JButtons to a JPanel, which is assumed to have a FlowLayout
594: * (although other layout managers may work.)
595: *
596: * @param buttonPanel the JPanel to which the JButtons will be added.
597: * @param buttonConfig a two-dimensional Object array, where each outer element
598: * is an array with the following elements:
599: * <ul>
600: * <li>A JButton to be added</li>
601: * <li>A single-character String, which will be used to set the mnemonic for the button.</li>
602: * <li>A String representing the ActionCommand that will be set for the button.</li>
603: * <li>A String that will be set as the ToolTipText for the button
604: * </ul>
605: * @param An ActionListener that will be passed to each JButton's addActionListener() method.
606: *
607: */
608: public static void buildButtonPanel(JPanel buttonPanel,
609: Object[][] buttonConfig, ActionListener listener) {
610: for (int j = 0; j < buttonConfig.length; j++) {
611: JButton button = (JButton) buttonConfig[j][0];
612: if (buttonConfig[j][1] != null) {
613: button.setMnemonic(((String) buttonConfig[j][1])
614: .charAt(0));
615: }
616: button.setActionCommand((String) buttonConfig[j][2]);
617: button.setToolTipText((String) buttonConfig[j][3]);
618: if (listener != null)
619: button.addActionListener(listener);
620: buttonPanel.add(button);
621: enterPressesWhenFocused(button);
622: }
623: }
624:
625: public static void setAllSizes(JComponent c, Dimension d) {
626: c.setPreferredSize(d == null ? d : (Dimension) d.clone());
627: c.setMaximumSize(d == null ? d : (Dimension) d.clone());
628: c.setMinimumSize(d == null ? d : (Dimension) d.clone());
629: }
630:
631: public static void configureToolbar(JToolBar toolBar,
632: Object[][] toolBarConfig, String imageDir,
633: Dimension buttonSize, ActionListener actionListener) {
634:
635: if (imageDir != null) {
636: imageDir = imageDir.replace('\\', '/');
637: if ((imageDir = imageDir.trim()).length() > 0
638: && imageDir.endsWith("/") == false) {
639: imageDir += "/";
640: }
641: }
642:
643: for (int j = 0; j < toolBarConfig.length; j++) {
644:
645: JButton button = (JButton) toolBarConfig[j][0];
646: String cmd = (String) toolBarConfig[j][1];
647: String image = (String) toolBarConfig[j][2];
648: String tip = (String) toolBarConfig[j][3];
649: int numSeps = 0;
650: if (toolBarConfig[j].length > 4) {
651: try {
652: numSeps = Integer
653: .parseInt((String) toolBarConfig[j][4]);
654: } catch (Exception ex) {
655: ex.printStackTrace();
656: }
657: }
658:
659: if (image != null) {
660: if (imageDir != null)
661: image = imageDir + image;
662: ImageIcon icon = getImageIconFromClasspath(image);
663: if (icon != null)
664: button.setIcon(icon);
665: }
666: button.setToolTipText(tip);
667: button.setActionCommand(cmd);
668: button.addActionListener(actionListener);
669: if (buttonSize != null) {
670: button.setMinimumSize(buttonSize);
671: button.setPreferredSize(buttonSize);
672: button.setMaximumSize(buttonSize);
673: }
674:
675: toolBar.add(button);
676:
677: for (int n = 0; n < numSeps; n++) {
678: toolBar.addSeparator();
679: }
680: }
681: }
682:
683: /**
684: * Given the path, relative to the classpath, of an image file which is IN the classpath,
685: * this will return an ImageIcon constructed from the given image.
686: */
687: public static ImageIcon getImageIconFromClasspath(
688: String imageFilePath) {
689:
690: ClassLoader cl = GUIHelper.class.getClassLoader();
691: URL url = cl.getResource(imageFilePath);
692:
693: if (url != null) {
694: if (debug)
695: System.out.println("GOT URL: " + url);
696: return new ImageIcon(url);
697: } else {
698: System.err
699: .println("Icon image not found: " + imageFilePath);
700: return null;
701: }
702: }
703:
704: public static JViewport getViewport(Component c) {
705: Container parent = c.getParent();
706: while (parent != null) {
707: if (parent instanceof JViewport)
708: return (JViewport) parent;
709: parent = parent.getParent();
710: }
711: return null;
712: }
713:
714: static boolean isJavaVersion_1_4_OrGreater = false;
715:
716: static int FORWARD_TRAVERSAL_KEYS = 0, BACKWARD_TRAVERSAL_KEYS = 0;
717:
718: static Object[] tabArgs = null;
719:
720: static Object[] shiftTabArgs = null;
721:
722: static Method setFocusEnableMethod = null;
723: static Method setFocusTraversalMethod = null;
724:
725: static HashSet forwardTraversalKeys = null;
726: static HashSet backwardTraversalKeys = null;
727:
728: static {
729:
730: try {
731: Class c = Class.forName("java.awt.KeyboardFocusManager");
732: Field f1 = c.getField("FORWARD_TRAVERSAL_KEYS");
733: Field f2 = c.getField("BACKWARD_TRAVERSAL_KEYS");
734:
735: FORWARD_TRAVERSAL_KEYS = f1.getInt(null);
736: BACKWARD_TRAVERSAL_KEYS = f2.getInt(null);
737:
738: if (debug) {
739: System.out.println("FORWARD_TRAVERSAL_KEYS = "
740: + FORWARD_TRAVERSAL_KEYS);
741: System.out.println("BACKWARD_TRAVERSAL_KEYS = "
742: + BACKWARD_TRAVERSAL_KEYS);
743: }
744:
745: forwardTraversalKeys = new HashSet();
746: forwardTraversalKeys.add(KeyStroke.getKeyStroke(
747: java.awt.event.KeyEvent.VK_TAB, 0));
748:
749: backwardTraversalKeys = new HashSet();
750: backwardTraversalKeys.add(KeyStroke.getKeyStroke(
751: KeyEvent.VK_TAB,
752: java.awt.event.InputEvent.SHIFT_MASK));
753:
754: tabArgs = new Object[] {
755: new Integer(FORWARD_TRAVERSAL_KEYS),
756: forwardTraversalKeys };
757:
758: shiftTabArgs = new Object[] {
759: new Integer(BACKWARD_TRAVERSAL_KEYS),
760: backwardTraversalKeys };
761:
762: c = javax.swing.JTextArea.class;
763:
764: setFocusTraversalMethod = c.getMethod(
765: "setFocusTraversalKeys", new Class[] { int.class,
766: java.util.Set.class });
767:
768: setFocusEnableMethod = c.getMethod(
769: "setFocusTraversalKeysEnabled",
770: new Class[] { boolean.class });
771:
772: isJavaVersion_1_4_OrGreater = true;
773:
774: System.out
775: .println("Using Java 1.4 functionality for JTextArea focus traversal.");
776: } catch (Throwable t) {
777: if (debug) {
778: t.printStackTrace();
779: }
780: }
781: }
782:
783: /**
784: * This method will modify the given JTextArea for Java versions 1.4.0 and above so that
785: * pressing the tab key will shift focus away from the JTextArea, rather than insert
786: * a tab character. It uses reflection so that the code can be compiled and run with
787: * versions prior to 1.4.0. Before version 1.4.0, this affect could be achieved
788: * by overriding isManagingFocus() on the JTextArea to return false, however that no
789: * longer works.
790: */
791: public static void setTabTraversal(JTextArea textArea) {
792:
793: if (isJavaVersion_1_4_OrGreater == false)
794: return;
795: try {
796:
797: setFocusTraversalMethod.invoke(textArea, tabArgs);
798: setFocusTraversalMethod.invoke(textArea, shiftTabArgs);
799:
800: } catch (Exception ex) {
801: ex.printStackTrace();
802: throw new RuntimeException(ex.toString());
803: }
804: }
805:
806: }
|