components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/GUIUtil.java
changeset 3553 f1d133b09a8c
parent 3552 077ebe3d0d24
child 3554 ef58713bafc4
equal deleted inserted replaced
3552:077ebe3d0d24 3553:f1d133b09a8c
     1 /*
       
     2  * CDDL HEADER START
       
     3  *
       
     4  * The contents of this file are subject to the terms of the
       
     5  * Common Development and Distribution License (the "License").
       
     6  * You may not use this file except in compliance with the License.
       
     7  *
       
     8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
     9  * or http://www.opensolaris.org/os/licensing.
       
    10  * See the License for the specific language governing permissions
       
    11  * and limitations under the License.
       
    12  *
       
    13  * When distributing Covered Code, include this CDDL HEADER in each
       
    14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    15  * If applicable, add the following below this CDDL HEADER, with the
       
    16  * fields enclosed by brackets "[]" replaced with your own identifying
       
    17  * information: Portions Copyright [yyyy] [name of copyright owner]
       
    18  *
       
    19  * CDDL HEADER END
       
    20  */
       
    21 
       
    22 /*
       
    23  * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
       
    24  */
       
    25 
       
    26 package com.oracle.solaris.vp.util.swing;
       
    27 
       
    28 import java.awt.*;
       
    29 import java.awt.event.*;
       
    30 import java.io.IOException;
       
    31 import java.lang.reflect.InvocationTargetException;
       
    32 import java.util.*;
       
    33 import java.util.List;
       
    34 import javax.swing.*;
       
    35 import javax.swing.border.Border;
       
    36 import com.oracle.solaris.vp.util.misc.*;
       
    37 import com.oracle.solaris.vp.util.misc.finder.Finder;
       
    38 
       
    39 public class GUIUtil {
       
    40     //
       
    41     // Inner classes
       
    42     //
       
    43 
       
    44     public static interface HasMnemonic {
       
    45 	String getText();
       
    46 
       
    47 	void setMnemonic(int i);
       
    48 
       
    49 	void setMnemonicIndex(int i);
       
    50 
       
    51 	void setText(String text);
       
    52     }
       
    53 
       
    54     public static class MnemonicAction implements HasMnemonic {
       
    55 	//
       
    56 	// Instance data
       
    57 	//
       
    58 
       
    59 	private Action action;
       
    60 
       
    61 	//
       
    62 	// Constructors
       
    63 	//
       
    64 
       
    65 	public MnemonicAction(Action action) {
       
    66 	    this.action = action;
       
    67 	}
       
    68 
       
    69 	//
       
    70 	// HasMnemonic methods
       
    71 	//
       
    72 
       
    73 	@Override
       
    74 	public String getText() {
       
    75 	    Object text = action.getValue(Action.NAME);
       
    76 	    return text == null ? null : text.toString();
       
    77 	}
       
    78 
       
    79 	@Override
       
    80 	public void setMnemonic(int i) {
       
    81 	    action.putValue(Action.MNEMONIC_KEY, i);
       
    82 	}
       
    83 
       
    84 	@Override
       
    85 	public void setMnemonicIndex(int i) {
       
    86 	    action.putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, i);
       
    87 	}
       
    88 
       
    89 	@Override
       
    90 	public void setText(String text) {
       
    91 	    action.putValue(Action.NAME, text);
       
    92 	}
       
    93 
       
    94 	//
       
    95 	// MnemonicAction methods
       
    96 	//
       
    97 
       
    98 	public Action getAction() {
       
    99 	    return action;
       
   100 	}
       
   101     }
       
   102 
       
   103     public static class MnemonicButton implements HasMnemonic {
       
   104 	//
       
   105 	// Instance data
       
   106 	//
       
   107 
       
   108 	private AbstractButton button;
       
   109 
       
   110 	//
       
   111 	// Constructors
       
   112 	//
       
   113 
       
   114 	public MnemonicButton(AbstractButton button) {
       
   115 	    this.button = button;
       
   116 	}
       
   117 
       
   118 	//
       
   119 	// HasMnemonic methods
       
   120 	//
       
   121 
       
   122 	@Override
       
   123 	public String getText() {
       
   124 	    return button.getText();
       
   125 	}
       
   126 
       
   127 	@Override
       
   128 	public void setMnemonic(int i) {
       
   129 	    button.setMnemonic(i);
       
   130 	}
       
   131 
       
   132 	@Override
       
   133 	public void setMnemonicIndex(int i) {
       
   134 	    button.setDisplayedMnemonicIndex(i);
       
   135 	}
       
   136 
       
   137 	@Override
       
   138 	public void setText(String text) {
       
   139 	    button.setText(text);
       
   140 	}
       
   141 
       
   142 	//
       
   143 	// MnemonicButton methods
       
   144 	//
       
   145 
       
   146 	public AbstractButton getButton() {
       
   147 	    return button;
       
   148 	}
       
   149     }
       
   150 
       
   151     public static class MnemonicLabel implements HasMnemonic {
       
   152 	//
       
   153 	// Instance data
       
   154 	//
       
   155 
       
   156 	private JLabel label;
       
   157 
       
   158 	//
       
   159 	// Constructors
       
   160 	//
       
   161 
       
   162 	public MnemonicLabel(JLabel label) {
       
   163 	    this.label = label;
       
   164 	}
       
   165 
       
   166 	//
       
   167 	// HasMnemonic methods
       
   168 	//
       
   169 
       
   170 	@Override
       
   171 	public String getText() {
       
   172 	    return label.getText();
       
   173 	}
       
   174 
       
   175 	@Override
       
   176 	public void setMnemonic(int i) {
       
   177 	    label.setDisplayedMnemonic(i);
       
   178 	}
       
   179 
       
   180 	@Override
       
   181 	public void setMnemonicIndex(int i) {
       
   182 	    label.setDisplayedMnemonicIndex(i);
       
   183 	}
       
   184 
       
   185 	@Override
       
   186 	public void setText(String text) {
       
   187 	    label.setText(text);
       
   188 	}
       
   189 
       
   190 	//
       
   191 	// MnemonicLabel methods
       
   192 	//
       
   193 
       
   194 	public JLabel getLabel() {
       
   195 	    return label;
       
   196 	}
       
   197     }
       
   198 
       
   199     //
       
   200     // Static data
       
   201     //
       
   202 
       
   203     public static final Border BORDER_LINE =
       
   204 	BorderFactory.createLineBorder(Color.GRAY, 1);
       
   205 
       
   206     public static final String PROPERTY_COMPONENT_SPACING =
       
   207 	"toolkit.component.spacing";
       
   208 
       
   209     public static final String PROPERTY_BUTTON_SPACING =
       
   210 	"toolkit.button.spacing";
       
   211 
       
   212     public static final TypedPropertiesWrapper prefs =
       
   213 	new TypedPropertiesWrapper();
       
   214 
       
   215     static {
       
   216 	try {
       
   217 	    Properties defaultPrefs = new Properties();
       
   218 	    defaultPrefs.load(
       
   219 		Finder.getResource("runtime.properties").openStream());
       
   220 	    prefs.getPropertiesList().add(defaultPrefs);
       
   221 	} catch (IOException e) {
       
   222 	    // Unlikely -- the resource should always exist and be accessible
       
   223 	}
       
   224     }
       
   225 
       
   226     //
       
   227     // Static methods
       
   228     //
       
   229 
       
   230     /**
       
   231      * Adds the given {@code AWTKeyStroke}s to the given {@code Component}'s
       
   232      * list of focus traversal keys.
       
   233      *
       
   234      * @param	    comp
       
   235      *		    the {@code Component}
       
   236      *
       
   237      * @param	    forward
       
   238      *		    {@code true} to make {@code keyStrokes} traverse forward,
       
   239      *		    {@code false} to traverse backward
       
   240      *
       
   241      * @param	    keyStrokes
       
   242      *		    the {@code AWTKeyStroke}s
       
   243      */
       
   244     public static void addFocusTraversalKeys(Component comp, boolean forward,
       
   245 	AWTKeyStroke... keyStrokes) {
       
   246 	modifyFocusTraversalKeys(comp, forward, true, keyStrokes);
       
   247     }
       
   248 
       
   249     public static void adjustForMnemonic(HasMnemonic obj) {
       
   250 	String text = obj.getText();
       
   251 	String[] groups = TextUtil.match(text, "(.*?)_(([a-zA-Z0-9]).*)");
       
   252 	if (groups != null) {
       
   253 	    obj.setText(groups[1] + groups[2]);
       
   254 
       
   255 	    String mnemonic = groups[3].toUpperCase();
       
   256 	    KeyStroke mnemonicKey = KeyStroke.getKeyStroke(mnemonic);
       
   257 
       
   258 	    if (mnemonicKey != null) {
       
   259 		int mnemonicKeyCode = mnemonicKey.getKeyCode();
       
   260 		obj.setMnemonic(mnemonicKeyCode);
       
   261 		obj.setMnemonicIndex(groups[1].length());
       
   262 	    }
       
   263 	}
       
   264     }
       
   265 
       
   266     public static void adjustForMnemonic(Action action) {
       
   267 	adjustForMnemonic(new MnemonicAction(action));
       
   268     }
       
   269 
       
   270     public static void adjustForMnemonic(AbstractButton button) {
       
   271 	adjustForMnemonic(new MnemonicButton(button));
       
   272     }
       
   273 
       
   274     public static void adjustForMnemonic(JLabel label) {
       
   275 	adjustForMnemonic(new MnemonicLabel(label));
       
   276     }
       
   277 
       
   278     public static Border createCompoundBorder(Border... borders) {
       
   279 	Border border = null;
       
   280 	for (int i = 0; i < borders.length; i++) {
       
   281 	    border = i == 0 ? borders[i] :
       
   282 		BorderFactory.createCompoundBorder(border, borders[i]);
       
   283 	}
       
   284 	return border;
       
   285     }
       
   286 
       
   287     /**
       
   288      * Creates and returns a JTextField of the {@link #getTextFieldWidth default
       
   289      * width}.
       
   290      */
       
   291     public static JTextField createTextField() {
       
   292 	return new JTextField(getTextFieldWidth());
       
   293     }
       
   294 
       
   295     /**
       
   296      * Get the default spacing between buttons, as on a toolbar.
       
   297      */
       
   298     public static int getButtonGap() {
       
   299 	return prefs.getInt("toolkit.button.spacing");
       
   300     }
       
   301 
       
   302     /**
       
   303      * Get the default height for button icons.
       
   304      */
       
   305     public static int getButtonIconHeight() {
       
   306 	return prefs.getInt("toolkit.button.icon.height");
       
   307     }
       
   308 
       
   309     public static String getClippedString(
       
   310 	String text, FontMetrics metrics, int width) {
       
   311 
       
   312 	if (text == null) {
       
   313 	    return text;
       
   314 	}
       
   315 
       
   316 	int sWidth = metrics.stringWidth(text);
       
   317 	if (sWidth <= width) {
       
   318 	    return text;
       
   319 	}
       
   320 
       
   321 	String ellipsis = "...";
       
   322 	width -= metrics.stringWidth(ellipsis);
       
   323 	char[] chars = text.toCharArray();
       
   324 
       
   325 	int i;
       
   326 	for (i = chars.length - 1; i >= 0 && sWidth > width; i--) {
       
   327 	    sWidth -= metrics.charWidth(chars[i]);
       
   328 	}
       
   329 
       
   330 	return text.substring(0, i + 1) + ellipsis;
       
   331     }
       
   332 
       
   333     public static Border getEmptyBorder() {
       
   334 	int gap = getGap();
       
   335 	return BorderFactory.createEmptyBorder(gap, gap, gap, gap);
       
   336     }
       
   337 
       
   338     public static Border getEmptyHalfBorder() {
       
   339 	int gap = getHalfGap();
       
   340 	return BorderFactory.createEmptyBorder(gap, gap, gap, gap);
       
   341     }
       
   342 
       
   343     /**
       
   344      * Gets an icon for the given message type.  An attempt will first be made
       
   345      * to retrieve a look-and-feel-specific icon from the {@code JOptionPane}
       
   346      * class.  However, some looks and feels (like GTK) don't expose these icons
       
   347      * via the API; in this case local defaults will be returned.
       
   348      *
       
   349      * @param	    messageType
       
   350      *		    JOptionPane.ERROR_MESSAGE,
       
   351      *		    JOptionPane.INFORMATION_MESSAGE,
       
   352      *		    JOptionPane.WARNING_MESSAGE, or
       
   353      *		    JOptionPane.QUESTION_MESSAGE.
       
   354      *
       
   355      * @return	    an {@code Icon}, or {@code null} if no icon could be found
       
   356      *		    for the given message type
       
   357      */
       
   358     public static Icon getIcon(int messageType) {
       
   359 	Icon icon = null;
       
   360 
       
   361 	switch (messageType) {
       
   362 	    case JOptionPane.ERROR_MESSAGE:
       
   363 		icon = UIManager.getIcon("OptionPane.errorIcon");
       
   364 		if (icon == null) {
       
   365 		    icon = Finder.getIcon("images/dialog/dialog-error.png");
       
   366 		}
       
   367 		break;
       
   368 
       
   369 
       
   370 	    case JOptionPane.INFORMATION_MESSAGE:
       
   371 		icon = UIManager.getIcon("OptionPane.informationIcon");
       
   372 		if (icon == null) {
       
   373 		    icon = Finder.getIcon(
       
   374 			"images/dialog/dialog-information.png");
       
   375 		}
       
   376 		break;
       
   377 
       
   378 	    case JOptionPane.WARNING_MESSAGE:
       
   379 		icon = UIManager.getIcon("OptionPane.warningIcon");
       
   380 		if (icon == null) {
       
   381 		    icon = Finder.getIcon(
       
   382 			"images/dialog/dialog-warning.png");
       
   383 		}
       
   384 		break;
       
   385 
       
   386 	    case JOptionPane.QUESTION_MESSAGE:
       
   387 		icon = UIManager.getIcon("OptionPane.questionIcon");
       
   388 		if (icon == null) {
       
   389 		    icon = Finder.getIcon(
       
   390 			"images/dialog/dialog-question.png");
       
   391 		}
       
   392 		break;
       
   393 	}
       
   394 
       
   395 	return icon;
       
   396     }
       
   397 
       
   398     /**
       
   399      * Get the default spacing between components.
       
   400      */
       
   401     public static int getGap() {
       
   402 	return prefs.getInt("toolkit.component.spacing");
       
   403     }
       
   404 
       
   405     public static int getHalfGap() {
       
   406 	return getGap() / 2;
       
   407     }
       
   408 
       
   409     /**
       
   410      * Get the default height for menu icons.
       
   411      */
       
   412     public static int getMenuIconHeight() {
       
   413 	return prefs.getInt("toolkit.menu.icon.height");
       
   414     }
       
   415 
       
   416     /**
       
   417      * Gets the preferred size of the given {@code Window}.  If the given {@code
       
   418      * Window} is a {@code JFrame} or a {@code JDialog}, and its glass pane is
       
   419      * visible, the glass pane's preferred size will be accounted for as well
       
   420      * ({@code Window.getPreferredSize} does not take the glass pane into
       
   421      * consideration).
       
   422      */
       
   423     public static Dimension getPreferredSize(Window window) {
       
   424 	Dimension pSize = window.getPreferredSize();
       
   425 
       
   426 	Component glass = null;
       
   427 	Component content = null;
       
   428 
       
   429 	if (window instanceof JFrame) {
       
   430 	    JFrame frame = (JFrame)window;
       
   431 	    glass = frame.getGlassPane();
       
   432 	    content = frame.getContentPane();
       
   433 	} else if (window instanceof JDialog) {
       
   434 	    JDialog dialog = (JDialog)window;
       
   435 	    glass = dialog.getGlassPane();
       
   436 	    content = dialog.getContentPane();
       
   437 	}
       
   438 
       
   439 	if (glass != null && glass.isVisible()) {
       
   440 	    Dimension cpSize = content.getPreferredSize();
       
   441 	    content.setPreferredSize(glass.getPreferredSize());
       
   442 
       
   443 	    Dimension newPSize = window.getPreferredSize();
       
   444 
       
   445 	    pSize.width = Math.max(pSize.width, newPSize.width);
       
   446 	    pSize.height = Math.max(pSize.height, newPSize.height);
       
   447 
       
   448 	    // Restore previous preferred size, if any
       
   449 	    content.setPreferredSize(cpSize);
       
   450 	}
       
   451 
       
   452 	return pSize;
       
   453     }
       
   454 
       
   455     /**
       
   456      * Get the default scroll unit increment.
       
   457      */
       
   458     public static int getScrollableUnitIncrement() {
       
   459 	return prefs.getInt("toolkit.scroll.increment.unit");
       
   460     }
       
   461 
       
   462     /**
       
   463      * Get the default width for a textfield.
       
   464      */
       
   465     public static int getTextFieldWidth() {
       
   466 	return prefs.getInt("toolkit.textfield.width");
       
   467     }
       
   468 
       
   469     public static int getTextXOffset(AbstractButton c) {
       
   470 	int xoffset = c.getPreferredSize().width;
       
   471 
       
   472 	Border b = c.getBorder();
       
   473 	if (b != null) {
       
   474 	    xoffset -= b.getBorderInsets(c).right;
       
   475 	}
       
   476 
       
   477 //	Insets m = c.getMargin();
       
   478 //	if (m != null) {
       
   479 //	    xoffset -= m.right;
       
   480 //	}
       
   481 
       
   482 	String text = c.getText();
       
   483 	if (text != null) {
       
   484 	    xoffset -= c.getFontMetrics(c.getFont()).stringWidth(text);
       
   485 	}
       
   486 
       
   487 	return xoffset;
       
   488     }
       
   489 
       
   490     public static Border getTitledBorder(String title) {
       
   491 	return BorderFactory.createCompoundBorder(
       
   492 	    BorderFactory.createTitledBorder(
       
   493 	    BorderFactory.createEtchedBorder(), title), getEmptyBorder());
       
   494     }
       
   495 
       
   496     /**
       
   497      * Get the default margin for toolbar buttons.
       
   498      */
       
   499     public static int getToolBarButtonMargin() {
       
   500 	return prefs.getInt("toolkit.toolbar.button.margin");
       
   501     }
       
   502 
       
   503     public static TypedPropertiesWrapper getUIPreferences() {
       
   504 	return prefs;
       
   505     }
       
   506 
       
   507     /**
       
   508      * Get the default vertical spacing between a label and its {@code
       
   509      * Component} below it.
       
   510      */
       
   511     public static int getVerticalLabelGap() {
       
   512 	return prefs.getInt("toolkit.vertical.label.spacing");
       
   513     }
       
   514 
       
   515     /**
       
   516      * Grows the given {@code Window} if its preferred size exceeds its actual
       
   517      * size.  Will not shrink the {@code Window}.
       
   518      */
       
   519     public static void growIfNecessary(Window window) {
       
   520 	Dimension size = window.getSize();
       
   521 	Dimension pSize = getPreferredSize(window);
       
   522 
       
   523 	window.setSize(Math.max(size.width, pSize.width),
       
   524 	    Math.max(size.height, pSize.height));
       
   525     }
       
   526 
       
   527     public static void installKeyBinding(JComponent c, int condition,
       
   528 	String actionName, Action action, KeyStroke... keyStrokes) {
       
   529 
       
   530 	InputMap iMap = c.getInputMap(condition);
       
   531 	ActionMap aMap = c.getActionMap();
       
   532 
       
   533 	for (KeyStroke keyStroke : keyStrokes) {
       
   534 	    iMap.put(keyStroke, actionName);
       
   535 	    aMap.put(actionName, action);
       
   536 	}
       
   537     }
       
   538 
       
   539     public static void installKeyBinding(JComponent c, int condition,
       
   540 	String actionName, Action action, int... keyStrokes) {
       
   541 
       
   542 	KeyStroke[] array = new KeyStroke[keyStrokes.length];
       
   543 	for (int i = 0; i < array.length; i++) {
       
   544 	    array[i] = KeyStroke.getKeyStroke(keyStrokes[i], 0);
       
   545 	}
       
   546 
       
   547 	installKeyBinding(c, condition, actionName, action, array);
       
   548     }
       
   549 
       
   550     /**
       
   551      * Invokes the given {@code Runnable} on the AWT event dispatching thread
       
   552      * (which could be the current thread).  This addresses two
       
   553      * disadvantages/annoyances to {@code EventQueue.invokeAndWait}:
       
   554      * <ol>
       
   555      *	 <li>
       
   556      *	   Any {@code RuntimeException}s or {@code Error}s thrown by the given
       
   557      *	   {@code Runnable} are thrown in the current thread, rather than as an
       
   558      *	   {@code InvocationTargetException}.
       
   559      *	 </li>
       
   560      *	 <li>
       
   561      *	   If the AWT event dispatching thread is interrupted while the given
       
   562      *	   {@code Runnable} is running, it will be re-run until it completes.
       
   563      *	   If this behavior is not desired, the given {@code Runnable} should
       
   564      *	   account for this possibility.
       
   565      *	 </li>
       
   566      * <ol>
       
   567      *
       
   568      * @param	    runnable
       
   569      *		    a {@code Runnable} to run
       
   570      */
       
   571     public static void invokeAndWait(final Runnable runnable) {
       
   572 	if (EventQueue.isDispatchThread()) {
       
   573 	    runnable.run();
       
   574 	} else {
       
   575 	    while (true) {
       
   576 		try {
       
   577 		    EventQueue.invokeAndWait(runnable);
       
   578 		    break;
       
   579 		} catch (InvocationTargetException e) {
       
   580 		    Throwable cause = e.getCause();
       
   581 		    ThrowableUtil.appendStackTrace(cause);
       
   582 
       
   583 		    if (cause instanceof RuntimeException) {
       
   584 			throw (RuntimeException)cause;
       
   585 		    }
       
   586 
       
   587 		    if (cause instanceof Error) {
       
   588 			throw (Error)cause;
       
   589 		    }
       
   590 
       
   591 		    // It should not be possible to be here -- any Throwable
       
   592 		    // thrown by run() should have been a RunTimeException or an
       
   593 		    // Error, since run throws no checked Exceptions.
       
   594 		    assert (false);
       
   595 		} catch (InterruptedException e) {
       
   596 		    // Not bloody likely
       
   597 		}
       
   598 	    }
       
   599 	}
       
   600     }
       
   601 
       
   602     /**
       
   603      * See {@link #invokeAndWait(java.lang.Runnable)}.
       
   604      *
       
   605      * @return	    the returned object of the given {@code TypedRunnable}
       
   606      */
       
   607     public static <T> T invokeAndWait(final TypedRunnable<T> runnable) {
       
   608 	final List<T> t = new ArrayList<T>(1);
       
   609 
       
   610 	invokeAndWait(
       
   611 	    new Runnable() {
       
   612 		@Override
       
   613 		public void run() {
       
   614 		    t.add(runnable.run());
       
   615 		}
       
   616 	    });
       
   617 
       
   618 	return t.get(0);
       
   619     }
       
   620 
       
   621     public static boolean isModifierKeyPressed(InputEvent e) {
       
   622 	return e.isShiftDown() || e.isControlDown() || e.isAltDown() ||
       
   623 	    e.isMetaDown() || e.isAltGraphDown();
       
   624     }
       
   625 
       
   626     /**
       
   627      * Returns {@code true} if the given {@code MouseEvent} represents a mouse
       
   628      * click with the given number of clicks, button 1, with no modifier keys
       
   629      * pressed, or {@code false} otherwise.
       
   630      */
       
   631     public static boolean isUnmodifiedClick(MouseEvent e, int clickCount) {
       
   632 	return e.getID() == MouseEvent.MOUSE_CLICKED && e.getButton() == 1 &&
       
   633 	    e.getClickCount() == clickCount && !isModifierKeyPressed(e);
       
   634     }
       
   635 
       
   636     public static void propagate(MouseEvent e, Component c) {
       
   637 	Point p = SwingUtilities.convertPoint(e.getComponent(),
       
   638 	    e.getX(), e.getY(), c);
       
   639 
       
   640 	MouseEvent e2;
       
   641 	if (e instanceof MouseWheelEvent) {
       
   642             e2 = new MouseWheelEvent(c, e.getID(), e.getWhen(),
       
   643                 e.getModifiers(), p.x, p.y, e.getClickCount(),
       
   644                 e.isPopupTrigger(), ((MouseWheelEvent)e).getScrollType(),
       
   645                 ((MouseWheelEvent)e).getScrollAmount(),
       
   646                 ((MouseWheelEvent)e).getWheelRotation());
       
   647 	} else {
       
   648             e2 = new MouseEvent(c, e.getID(), e.getWhen(), e.getModifiers(),
       
   649 		p.x, p.y, e.getClickCount(), e.isPopupTrigger());
       
   650 	}
       
   651 
       
   652 	c.dispatchEvent(e2);
       
   653     }
       
   654 
       
   655     /**
       
   656      * Removes the given {@code AWTKeyStroke}s from the given {@code
       
   657      * Component}'s list of focus traversal keys.
       
   658      *
       
   659      * @param	    comp
       
   660      *		    the {@code Component}
       
   661      *
       
   662      * @param	    forward
       
   663      *		    {@code true} if {@code keyStrokes} traverse forward, {@code
       
   664      *		    false} if backward
       
   665      *
       
   666      * @param	    keyStrokes
       
   667      *		    the {@code AWTKeyStroke}s
       
   668      */
       
   669     public static void removeFocusTraversalKeys(Component comp, boolean forward,
       
   670 	AWTKeyStroke... keyStrokes) {
       
   671 	modifyFocusTraversalKeys(comp, forward, false, keyStrokes);
       
   672     }
       
   673 
       
   674     /**
       
   675      * Turn on text antialiasing if appropriate for this platform.  Technically
       
   676      * this should follow the conventions that Swing uses (matching this setting
       
   677      * to the user's desktop settings), but since Swing doesn't expose this
       
   678      * functionality, turn it on here unconditionally.
       
   679      */
       
   680     public static void setAARendering(Graphics2D g) {
       
   681 	g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
       
   682 	    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
       
   683     }
       
   684 
       
   685     public static void setBorderFromFont(JComponent c) {
       
   686 	FontMetrics metrics = c.getFontMetrics(c.getFont());
       
   687 	int top = metrics.getDescent();
       
   688 	int left = metrics.charWidth(' ');
       
   689 	c.setBorder(BorderFactory.createEmptyBorder(top, left, top, left));
       
   690     }
       
   691 
       
   692     public static void setEnableStateOnSelect(final AbstractButton button,
       
   693 	final boolean recursive, final boolean enabled,
       
   694 	final Component... toEnable) {
       
   695 
       
   696 	ItemListener l = new ItemListener() {
       
   697 	    @Override
       
   698 	    public void itemStateChanged(ItemEvent e) {
       
   699 		boolean state = !(button.isSelected() ^ enabled);
       
   700 		for (Component c : toEnable) {
       
   701 		    if (recursive) {
       
   702 			setEnabledRecursive(c, state);
       
   703 		    } else {
       
   704 			c.setEnabled(state);
       
   705 		    }
       
   706 		}
       
   707 	    }
       
   708 	};
       
   709 
       
   710 	button.addItemListener(l);
       
   711 
       
   712 	// Set initial state
       
   713 	l.itemStateChanged(null);
       
   714     }
       
   715 
       
   716     /**
       
   717      * Sets the enabled status of the given {@code Component}'s descendants, if
       
   718      * it is a {@code Container}, then of the given {@code Component} itself.
       
   719      * <p>
       
   720      * If a {@code Container} implementing {@code DescendantEnabler} is
       
   721      * encountered along the way, recursion into the {@code Component}
       
   722      * hierarchy is aborted.
       
   723      */
       
   724     public static void setEnabledRecursive(Component parent, boolean enabled) {
       
   725 	if (parent instanceof Container &&
       
   726 	    !(parent instanceof DescendantEnabler)) {
       
   727 
       
   728 	    for (Component c : ((Container)parent).getComponents()) {
       
   729 		setEnabledRecursive((Container)c, enabled);
       
   730 	    }
       
   731 	}
       
   732 
       
   733 	// A disabled JPanel is ugly
       
   734 	if (enabled || parent instanceof DescendantEnabler ||
       
   735 	    !(parent instanceof JPanel)) {
       
   736 
       
   737 	    parent.setEnabled(enabled);
       
   738 	}
       
   739     }
       
   740 
       
   741     /**
       
   742      * Sets the left and right margin of an {@code AbstractButton} in a
       
   743      * L&amp;F-independent manner (some L&amp;Fs set this value in the margin,
       
   744      * while others ignore this property and use the border instead).
       
   745      */
       
   746     public static void setHorizontalMargin(AbstractButton button, int hMargin) {
       
   747 	Insets margin = button.getMargin();
       
   748 	margin.left = margin.right = hMargin;
       
   749 
       
   750 	Border empty = BorderFactory.createEmptyBorder(
       
   751 	    margin.top, margin.left, margin.bottom, margin.right);
       
   752 
       
   753 	Border outer = button.getBorder();
       
   754 	Border compound = BorderFactory.createCompoundBorder(outer, empty);
       
   755 
       
   756 	button.setMargin(new Insets(0, 0, 0, 0));
       
   757 	button.setBorder(compound);
       
   758     }
       
   759 
       
   760     public static void setMinorFont(Component comp) {
       
   761 	Font font = comp.getFont();
       
   762 	font = font.deriveFont(font.getSize() * .85f);
       
   763 	comp.setFont(font);
       
   764     }
       
   765 
       
   766     public static void setUIPreferences(Properties p) {
       
   767 	List<Properties> pList = prefs.getPropertiesList();
       
   768 	if (pList.size() > 1) {
       
   769 	    pList.remove(1);
       
   770 	}
       
   771 	pList.add(p);
       
   772     }
       
   773 
       
   774     /**
       
   775      * Sets whether the given {@code Component} uses tab and shift-tab as
       
   776      * forward/backward focus traversal keys.
       
   777      */
       
   778     public static void setUseStandardFocusTraversalKeys(Component comp,
       
   779 	boolean use) {
       
   780 
       
   781 	modifyFocusTraversalKeys(comp, true, use, KeyStroke.getKeyStroke(
       
   782 	    KeyEvent.VK_TAB, 0));
       
   783 
       
   784 	modifyFocusTraversalKeys(comp, false, use, KeyStroke.getKeyStroke(
       
   785 	    KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK));
       
   786     }
       
   787 
       
   788     /**
       
   789      * Shows a confirmation message dialog.
       
   790      *
       
   791      * @param	    component
       
   792      *		    a {@code Component} contained within a {@code Frame} for
       
   793      *		    which the dialog will be modal, or {@code null} to display a
       
   794      *		    non-modal dialog
       
   795      *
       
   796      * @param	    title
       
   797      *		    the title to display in the dialog, or {@code null} to use a
       
   798      *		    default
       
   799      *
       
   800      * @param	    optionType
       
   801      *		    a static constant from the {@code JOptionPane} class: {@code
       
   802      *		    YES_NO_OPTION}, {@code YES_NO_CANCEL_OPTION}, or {@code
       
   803      *		    OK_CANCEL_OPTION}, or {@code null} to use the default
       
   804      *		    ({@code OK_CANCEL_OPTION})
       
   805      *
       
   806      * @param	    message
       
   807      *		    the message to display in the dialog
       
   808      *
       
   809      * @return	    an integer indicating the option selected by the user
       
   810      */
       
   811     public static int showConfirmation(Component component, String title,
       
   812 	Object message, Integer optionType) {
       
   813 
       
   814 	if (title == null) {
       
   815 	    title = Finder.getString("dialog.title.confirm");
       
   816 	}
       
   817 
       
   818 	JOptionPane pane = new ExtOptionPane(
       
   819 	    message, JOptionPane.QUESTION_MESSAGE);
       
   820 	pane.setOptionType(JOptionPane.OK_CANCEL_OPTION);
       
   821 
       
   822 	JDialog dialog = pane.createDialog(component, title);
       
   823 	dialog.setVisible(true);
       
   824 
       
   825 	Object selection = pane.getValue();
       
   826 	if (selection == null) {
       
   827 	    return JOptionPane.CLOSED_OPTION;
       
   828 	}
       
   829 
       
   830 	return (Integer)selection;
       
   831     }
       
   832 
       
   833     /**
       
   834      * Shows an error message dialog.
       
   835      *
       
   836      * @param	    component
       
   837      *		    a {@code Component} contained within a {@code Frame} for
       
   838      *		    which the dialog will be modal, or {@code null} to display a
       
   839      *		    non-modal dialog
       
   840      *
       
   841      * @param	    title
       
   842      *		    the title to display in the dialog, or {@code null} to use a
       
   843      *		    default
       
   844      *
       
   845      * @param	    message
       
   846      *		    the message to display in the dialog
       
   847      */
       
   848     public static void showError(Component component, String title,
       
   849 	Object message) {
       
   850 
       
   851 	if (message != null) {
       
   852 	    if (title == null) {
       
   853 		title = Finder.getString("dialog.title.error");
       
   854 	    }
       
   855 
       
   856 	    showOptionPane(JOptionPane.ERROR_MESSAGE, component, title,
       
   857 		message);
       
   858 	}
       
   859     }
       
   860 
       
   861     /**
       
   862      * Shows an info message dialog.
       
   863      *
       
   864      * @param	    component
       
   865      *		    a {@code Component} contained within a {@code Frame} for
       
   866      *		    which the dialog will be modal, or {@code null} to display a
       
   867      *		    non-modal dialog
       
   868      *
       
   869      * @param	    title
       
   870      *		    the title to display in the dialog, or {@code null} to use a
       
   871      *		    default
       
   872      *
       
   873      * @param	    message
       
   874      *		    the message to display in the dialog
       
   875      */
       
   876     public static void showInfo(Component component, String title,
       
   877 	String message) {
       
   878 
       
   879 	if (title == null) {
       
   880 	    title = Finder.getString("dialog.title.info");
       
   881 	}
       
   882 
       
   883 	showOptionPane(JOptionPane.INFORMATION_MESSAGE, component, title,
       
   884 	    message);
       
   885     }
       
   886 
       
   887     /**
       
   888      * Shows a width-constrained {@code JOptionPane} with the specified
       
   889      * parameters.
       
   890      *
       
   891      * @see	    ExtOptionPane
       
   892      */
       
   893     public static void showOptionPane(int messageType, Component component,
       
   894 	String title, Object message) {
       
   895 
       
   896 	JOptionPane pane = new ExtOptionPane(message, messageType);
       
   897 	JDialog dialog = pane.createDialog(component, title);
       
   898 	dialog.setVisible(true);
       
   899     }
       
   900 
       
   901     /**
       
   902      * Shows an warning message dialog.
       
   903      *
       
   904      * @param	    component
       
   905      *		    a {@code Component} contained within a {@code Frame} for
       
   906      *		    which the dialog will be modal, or {@code null} to display a
       
   907      *		    non-modal dialog
       
   908      *
       
   909      * @param	    title
       
   910      *		    the title to display in the dialog, or {@code null} to use a
       
   911      *		    default
       
   912      *
       
   913      * @param	    message
       
   914      *		    the message to display in the dialog
       
   915      */
       
   916     public static void showWarning(Component component, String title,
       
   917 	String message) {
       
   918 
       
   919 	if (title == null) {
       
   920 	    title = Finder.getString("dialog.title.warning");
       
   921 	}
       
   922 
       
   923 	showOptionPane(JOptionPane.WARNING_MESSAGE, component, title, message);
       
   924     }
       
   925 
       
   926     //
       
   927     // Private static methods
       
   928     //
       
   929 
       
   930     private static void modifyFocusTraversalKeys(Component comp,
       
   931 	boolean forward, boolean add, AWTKeyStroke... keyStrokes) {
       
   932 
       
   933 	int dir = forward ? KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS :
       
   934 	    KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS;
       
   935 
       
   936 	Set<AWTKeyStroke> keys = new HashSet<AWTKeyStroke>(
       
   937 	    comp.getFocusTraversalKeys(dir));
       
   938 
       
   939 	for (AWTKeyStroke key : keyStrokes) {
       
   940 	    if (add) {
       
   941 		keys.add(key);
       
   942 	    } else {
       
   943 		keys.remove(key);
       
   944 	    }
       
   945 	}
       
   946 
       
   947 	comp.setFocusTraversalKeys(dir, keys);
       
   948     }
       
   949 }