com.limegroup.gnutella.gui.GUIUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.limegroup.gnutella.gui.GUIUtils.java

Source

/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.limegroup.gnutella.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.table.TableModel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.util.FileUtils;
import org.limewire.util.OSUtils;

import com.frostwire.gui.player.MediaPlayer;
import com.frostwire.gui.player.AudioSource;
import com.limegroup.gnutella.SpeedConstants;
import com.limegroup.gnutella.gui.themes.ThemeSettings;
import com.limegroup.gnutella.gui.util.BackgroundExecutorService;

/**
 *  This class serves as a holder for any static gui convenience
 *  methods.
 */
public final class GUIUtils {

    private static final Log LOG = LogFactory.getLog(GUIUtils.class);

    /**
      * Make sure the constructor is never called.
      */
    private GUIUtils() {
    }

    /**
     * Localizable Number Format constant for the current default locale
     * set at init time.
     */
    private static NumberFormat NUMBER_FORMAT0; // localized "#,##0"
    private static NumberFormat NUMBER_FORMAT1; // localized "#,##0.0"

    /**
     * Localizable Date Format constant for the current default locale
     * set at init time.
     */
    private static DateFormat DATETIME_FORMAT;

    /** A full datetime format. */
    private static DateFormat FULL_DATETIME_FORMAT;

    /**
     * Localizable constants
     */
    public static String GENERAL_UNIT_KILOBYTES;
    public static String GENERAL_UNIT_MEGABYTES;
    public static String GENERAL_UNIT_GIGABYTES;
    public static String GENERAL_UNIT_TERABYTES;
    /* ambiguous name: means kilobytes/second, not kilobits/second! */
    public static String GENERAL_UNIT_KBPSEC;

    public static final HyperlinkListener HYPER_LISTENER;

    /**
     * An action that disposes the parent window.
     * Constructed lazily.
     */
    public static Action ACTION_DISPOSE;

    static {
        HYPER_LISTENER = new HyperlinkListener() {
            public void hyperlinkUpdate(HyperlinkEvent he) {
                if (he.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                    URL url = he.getURL();
                    if (url != null)
                        GUIMediator.openURL(url.toExternalForm());
                }
            }
        };
        resetLocale();
    }

    static void resetLocale() {
        NUMBER_FORMAT0 = NumberFormat.getNumberInstance(GUIMediator.getLocale());
        NUMBER_FORMAT0.setMaximumFractionDigits(0);
        NUMBER_FORMAT0.setMinimumFractionDigits(0);
        NUMBER_FORMAT0.setGroupingUsed(true);

        NUMBER_FORMAT1 = NumberFormat.getNumberInstance(GUIMediator.getLocale());
        NUMBER_FORMAT1.setMaximumFractionDigits(1);
        NUMBER_FORMAT1.setMinimumFractionDigits(1);
        NUMBER_FORMAT1.setGroupingUsed(true);

        DATETIME_FORMAT = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT,
                GUIMediator.getLocale());

        FULL_DATETIME_FORMAT = new SimpleDateFormat("EEE, MMM. d, yyyy h:mm a", GUIMediator.getLocale());

        GENERAL_UNIT_KILOBYTES = I18n.tr("KB");
        GENERAL_UNIT_MEGABYTES = I18n.tr("MB");
        GENERAL_UNIT_GIGABYTES = I18n.tr("GB");
        GENERAL_UNIT_TERABYTES = I18n.tr("TB");
        GENERAL_UNIT_KBPSEC = I18n.tr("KB/s");
    }

    /**
     * This static method converts the passed in number
     * into a localizable representation of an integer, with
     * digit grouping using locale dependant separators.
     *
     * @param value the number to convert to a numeric String.
     *
     * @return a localized String representing the integer value
     */
    public static String toLocalizedInteger(long value) {
        return NUMBER_FORMAT0.format(value);
    }

    /**
     * This static method converts the passed in number of bytes into a
     * kilobyte string grouping digits with locale-dependant thousand separator
     * and with "KB" locale-dependant unit at the end.
     *
     * @param bytes the number of bytes to convert to a kilobyte String.
     *
     * @return a String representing the number of kilobytes that the
     *         <code>bytes</code> argument evaluates to, with "KB" appended
     *         at the end.  If the input value is negative, the string
     *         returned will be "? KB".
     */
    public static String toKilobytes(long bytes) {
        if (bytes < 0)
            return "? " + GENERAL_UNIT_KILOBYTES;
        long kbytes = bytes / 1024;
        // round to nearest multiple, or round up if size below 1024
        if ((bytes & 512) != 0 || (bytes > 0 && bytes < 1024))
            kbytes++;
        // result formating, according to the current locale
        return NUMBER_FORMAT0.format(kbytes) + GENERAL_UNIT_KILOBYTES;
    }

    /**
     * Converts the passed in number of bytes into a byte-size string.
     * Group digits with locale-dependant thousand separator if needed, but
     * with "KB", or "MB" or "GB" or "TB" locale-dependant unit at the end,
     * and a limited precision of 4 significant digits.
     *
     * @param bytes the number of bytes to convert to a size String.
     * @return a String representing the number of kilobytes that the
     *         <code>bytes</code> argument evaluates to, with
     *         "KB"/"MB"/"GB"/TB" appended at the end. If the input value is
     *         negative, the string returned will be "? KB".
     */
    public static String toUnitbytes(long bytes) {
        if (bytes < 0) {
            return "? " + GENERAL_UNIT_KILOBYTES;
        }
        long unitValue; // the multiple associated with the unit
        String unitName; // one of localizable units
        if (bytes < 0xA00000) { // below 10MB, use KB
            unitValue = 0x400;
            unitName = GENERAL_UNIT_KILOBYTES;
        } else if (bytes < 0x280000000L) { // below 10GB, use MB
            unitValue = 0x100000;
            unitName = GENERAL_UNIT_MEGABYTES;
        } else if (bytes < 0xA0000000000L) { // below 10TB, use GB
            unitValue = 0x40000000;
            unitName = GENERAL_UNIT_GIGABYTES;
        } else { // at least 10TB, use TB
            unitValue = 0x10000000000L;
            unitName = GENERAL_UNIT_TERABYTES;
        }
        NumberFormat numberFormat; // one of localizable formats
        if ((double) bytes * 100 / unitValue < 99995)
            // return a minimum "100.0xB", and maximum "999.9xB"
            numberFormat = NUMBER_FORMAT1; // localized "#,##0.0"
        else
            // return a minimum "1,000xB"
            numberFormat = NUMBER_FORMAT0; // localized "#,##0"
        try {
            return numberFormat.format((double) bytes / unitValue) + " " + unitName;
        } catch (ArithmeticException ae) {
            return "0 " + unitName;
            // internal java error, just return 0.
        }
    }

    /**
     * Returns a label with multiple lines that is sized according to
     * the string parameter.
     *
     * @param msg the string that will be contained in the label.
     *
     * @return a MultiLineLabel sized according to the passed
     *  in string.
     */
    public static MultiLineLabel getSizedLabel(String msg) {
        Dimension dim = new Dimension();
        MultiLineLabel label = new MultiLineLabel(msg);
        FontMetrics fm = label.getFontMetrics(label.getFont());
        int width = fm.stringWidth(msg);
        dim.setSize(Integer.MAX_VALUE, width / 9); //what's this magic?
        label.setPreferredSize(dim);
        return label;
    }

    /**
     * Converts the following bandwidth value, in kbytes/second, to
     * a human readable.
     */
    public static String speed2name(int rate) {
        if (rate <= SpeedConstants.MODEM_SPEED_INT)
            return GUIConstants.MODEM_SPEED;
        else if (rate <= SpeedConstants.CABLE_SPEED_INT)
            return GUIConstants.CABLE_SPEED;
        else if (rate <= SpeedConstants.T1_SPEED_INT)
            return GUIConstants.T1_SPEED;
        else if (rate == SpeedConstants.THIRD_PARTY_SPEED_INT)
            return GUIConstants.THIRD_PARTY_RESULTS_SPEED;
        else if (rate < Integer.MAX_VALUE - 1)
            return GUIConstants.T3_SPEED;
        else
            return GUIConstants.MULTICAST_SPEED;
    }

    /**
     * Converts an rate into a human readable and localized KB/s speed.
     */
    public static String rate2speed(double rate) {
        return NUMBER_FORMAT0.format(rate) + " " + GENERAL_UNIT_KBPSEC;
    }

    /**
     * Converts number of milliseconds since way back when to
     * a local-formatted date String
     */
    public static String msec2DateTime(long milliseconds) {
        Date d = new Date(milliseconds);
        return DATETIME_FORMAT.format(d);
    }

    /** Gets the full datetime formatter. */
    public static DateFormat getFullDateTimeFormat() {
        return FULL_DATETIME_FORMAT;
    }

    /**
     * Sets the child components of a component to all be either
     * opaque or not opaque.
     */
    public static void setOpaque(boolean op, JComponent c) {
        c.setOpaque(op);
        Component[] cs = c.getComponents();
        for (int i = 0; i < cs.length; i++) {
            if (cs[i] instanceof JComponent && !(cs[i] instanceof JTextField)
                    && (ThemeSettings.isNativeOSXTheme() || !(cs[i] instanceof JButton))) {
                ((JComponent) cs[i]).setOpaque(op);
                setOpaque(op, (JComponent) cs[i]);
            }
        }
    }

    /**
     * Centers the given component.
     */
    public static JPanel center(JComponent c) {
        JPanel p = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
        p.add(c);
        return p;
    }

    /**
     * Left flushes the given component.
     */
    public static JPanel left(JComponent c) {
        JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
        p.add(c);
        return p;
    }

    /**
     * Gets the width of a given label.
     */
    public static int width(JLabel c) {
        FontMetrics fm = c.getFontMetrics(c.getFont());
        return fm.stringWidth(c.getText()) + 3;
    }

    /**
     * Determines if a font can display up to a point in the string.
     *
     * Returns -1 if it can display the whole string.
     */
    public static boolean canDisplay(Font f, String s) {
        int upTo = f.canDisplayUpTo(s);
        if (upTo >= s.length() || upTo == -1)
            return true;
        else
            return false;
    }

    /**
     * Adds a hide action to a JDialog.
     */
    public static void addHideAction(JDialog jd) {
        addHideAction((JComponent) jd.getContentPane());
    }

    /**
     * Adds an action to hide a window / dialog.
     *
     * On OSX, this is done by typing 'Command-W'.
     * On all other platforms, this is done by hitting 'ESC'.
     */
    public static void addHideAction(JComponent jc) {
        InputMap map = jc.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        map.put(getHideKeystroke(), "limewire.hideWindow");
        jc.getActionMap().put("limewire.hideWindow", getDisposeAction());
    }

    /**
     * Gets the keystroke for hiding a window according to the platform.
     */
    public static KeyStroke getHideKeystroke() {
        if (OSUtils.isMacOSX())
            return KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
        else
            return KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
    }

    /**
     * Binds a key stroke to the given action for the component. The action is 
     * triggered when the key is pressed and the keyboard focus is withing the
     * specifiedd scope.
     *  
     * @param c component for which the keybinding is installed
     * @param key the key that triggers the action
     * @param a the action
     * @param focusScope one of {@link JComponent.WHEN_FOCUSED},
     * {@link JComponent.WHEN_IN_FOCUSED_WINDOW},
     * {@link JComponent.WHEN_ANCESTOR_OF_FOCUSED_WINDOW}
     */
    public static void bindKeyToAction(JComponent c, KeyStroke key, Action a, int focusScope) {
        InputMap inputMap = c.getInputMap(focusScope);
        ActionMap actionMap = c.getActionMap();
        if (inputMap != null && actionMap != null) {
            inputMap.put(key, a);
            actionMap.put(a, a);
        }
    }

    /**
     * Convenience wrapper for {@link #bindKeyToAction(JComponent, KeyStroke,
     * Action, int) bindKeyToAction(c, key, a, JComponentn.WHEN_FOCUSED)}.
     */
    public static void bindKeyToAction(JComponent c, KeyStroke key, Action a) {
        bindKeyToAction(c, key, a, JComponent.WHEN_FOCUSED);
    }

    /**
     * Returns (possibly constructing) the ESC action.
     */
    public static Action getDisposeAction() {
        if (ACTION_DISPOSE == null) {
            ACTION_DISPOSE = new AbstractAction() {

                /**
                 * 
                 */
                private static final long serialVersionUID = 3219036624812939826L;

                public void actionPerformed(ActionEvent ae) {
                    Window parent;
                    if (ae.getSource() instanceof Window)
                        parent = (Window) ae.getSource();
                    else
                        parent = SwingUtilities.getWindowAncestor((Component) ae.getSource());

                    if (parent != null)
                        parent.dispatchEvent(new WindowEvent(parent, WindowEvent.WINDOW_CLOSING));
                }
            };
        }
        return ACTION_DISPOSE;
    }

    /**
     * Fixes the InputMap to have the correct KeyStrokes registered for
     * actions on various OS's.
     *
     * Currently, this fixes OSX to use the 'meta' key instead of hard-coding
     * it to use the 'control' key for actions such as 'select all', etc..
     */
    public static void fixInputMap(JComponent jc) {
        InputMap map = jc.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

        if (OSUtils.isMacOSX()) {
            replaceAction(map, 'A'); // select all
            replaceAction(map, 'C'); // copy
            replaceAction(map, 'V'); // paste
            replaceAction(map, 'X'); // cut
        }
    }

    /**
     * Moves the action for the specified character from the 'ctrl' mask
     * to the 'meta' mask.
     */
    private static void replaceAction(InputMap map, char c) {
        KeyStroke ctrl = KeyStroke.getKeyStroke("control pressed " + c);
        KeyStroke meta = KeyStroke.getKeyStroke("meta pressed " + c);
        if (ctrl == null || meta == null)
            return;
        Object action = map.get(ctrl);
        if (action != null) {
            map.remove(ctrl);
            map.put(meta, action);
        }
    }

    /**
     * Returns the sole hyperlink listener.
     */
    public static HyperlinkListener getHyperlinkListener() {
        return HYPER_LISTENER;
    }

    /**
     * Returns a <code>MouseListener</code> that changes the cursor and
     * notifies <code>actionListener</code> on click.
     */
    public static MouseListener getURLInputListener(final ActionListener actionListener) {
        return new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                JComponent comp = (JComponent) e.getComponent();
                comp.getTopLevelAncestor().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
            }

            public void mouseExited(MouseEvent e) {
                JComponent comp = (JComponent) e.getComponent();
                comp.getTopLevelAncestor().setCursor(Cursor.getDefaultCursor());
            }

            public void mouseClicked(MouseEvent e) {
                actionListener.actionPerformed(new ActionEvent(e.getComponent(), 0, null));
            }
        };
    }

    /**
     * Returns a <code>MouseListener</code> that changes the cursor and opens
     * <code>url</code> on click.
     */
    public static MouseListener getURLInputListener(final String url) {
        return getURLInputListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                GUIMediator.openURL(url);
            }
        });
    }

    /**
     * Determines if the Start On Startup option is availble.
     */
    public static boolean shouldShowStartOnStartupWindow() {
        //System.out.println("********START UP DEBUG: GUIUtils is going to verify mac or windows");
        return OSUtils.isMacOSX() || WindowsUtils.isLoginStatusAvailable();
    }

    /**
     * Converts all spaces in the string to non-breaking spaces.
     *
     * Adds 'preSpaces' number of non-breaking spaces prior to the string.
     */
    public static String convertToNonBreakingSpaces(int preSpaces, String s) {
        StringBuilder b = new StringBuilder(preSpaces + s.length());
        for (int i = 0; i < preSpaces; i++)
            b.append('\u00a0');
        b.append(s.replace(' ', '\u00a0'));
        return b.toString();
    }

    /**
     * Convert a color object to a hex string
     **/
    public static String colorToHex(Color colorCode) {
        int r = colorCode.getRed();
        int g = colorCode.getGreen();
        int b = colorCode.getBlue();

        return toHex(r) + toHex(g) + toHex(b);
    }

    /**
     * Returns the int as a hex string.
     **/
    private static String toHex(int i) {
        String hex = Integer.toHexString(i).toUpperCase();
        if (hex.length() == 1)
            return "0" + hex;
        else
            return hex;
    }

    /**
     * Convert a hex string to a color object
     **/
    public static Color hexToColor(String hexString) {
        int decimalColor;
        decimalColor = Integer.parseInt(hexString, 16);
        return new Color(decimalColor);
    }

    /**
     * Launches file or enqueues it.
     * If <code>audioLaunched</code> is true and the playlist is visible the 
     * file is enqueued instead of played.
     * @param file   
     * @param audioLaunched
     * @return if audio has been launched in limewire's player
     */
    public static boolean launchOrEnqueueFile(final File file, boolean audioLaunched) {
        return launchFile(file, false, audioLaunched);
    }

    /**
     * Launches a file to be played once and only once. If the playlist is currently playing and/or
     * set to continous this will preemept the current song and stop the player after completion
     */
    public static boolean launchOneTimeFile(final File file) {
        return launchFile(file, true, false);
    }

    /**
     * Internal launch of a song to play. 
     * @param file - song to play
     * @param playOneTime - if true, begin playing song immediately even if other songs are currently playing
     *          after completing the song stops the player regardless of the continous setting
     * @param isPlaying - if true and playOneTime != true, will enqueue the song to the playlist rather
     *          than immediately playing it, otherwise will play immediately
     */
    private static boolean launchFile(final File file, boolean playOneTime, boolean isPlaying) {
        String extension = FileUtils.getFileExtension(file);
        if (extension != null && extension.equals("torrent")) {
            GUIMediator.instance().openTorrentFile(file, true);
            return false;
        }

        if (GUIMediator.isPlaylistVisible()) {
            //            if (PlaylistMediator.getInstance().openIfPlaylist(file))
            //                return false;

            if (MediaPlayer.isPlayableFile(file)) {
                if (playOneTime) {
                    BackgroundExecutorService.schedule(new Runnable() {
                        public void run() {
                            GUIMediator.safeInvokeAndWait(new Runnable() {
                                public void run() {
                                    GUIMediator.instance().launchAudio(new AudioSource(file));
                                }
                            });
                        }
                    });
                } else if (!isPlaying) {
                    BackgroundExecutorService.schedule(new Runnable() {
                        public void run() {
                            GUIMediator.safeInvokeAndWait(new Runnable() {
                                public void run() {
                                    GUIMediator.instance().launchAudio(new AudioSource(file));
                                }
                            });
                        }
                    });
                } else {
                    //PlaylistMediator.getInstance().addFileToPlaylist(file);
                }
                return true;
            }
        }

        GUIMediator.launchFile(file);
        return false;
    }

    /**
        * Launches an audio file.
        * If the FrostWire media player is enabled it will enqueue the song on the
        * playlist. It won't take into consideration if the song is complete or not.
        * If the frostwire player isn't enabled it will just use whatever is configured
        * as an external player to launch the file.
        * 
        * Note, this wont take in consideration if the song is being played or not.
        * It will launch it no matter what (maybe this causes problems)
        * 
        * @param file
        * @param audioLaunched
        * @return True if the song was launched with frostplayer
        */
    public static boolean launchAndEnqueueFile(File file, boolean audioLaunched) {
        if (MediaPlayer.isPlayableFile(file) && GUIMediator.isPlaylistVisible()) {
            GUIMediator.instance().attemptStopAudio();
            GUIMediator.instance().launchAudio(new AudioSource(file));
            return true;
        } else {
            //use external player to launch file
            return launchFile(file, false, audioLaunched);
        }
        //return false;
    }

    /**
     * Sets the location of <tt>dialog</tt> so it appears centered regarding
     * the main application or centered on the screen if the main application is
     * not visible.
     */
    public static void centerOnScreen(JDialog dialog) {
        if (GUIMediator.isAppVisible()) {
            dialog.setLocationRelativeTo(GUIMediator.getAppFrame());
        } else {
            dialog.setLocation(GUIMediator.getScreenCenterPoint(dialog));
        }
    }

    /**
     * Returns <code>text</code> wrapped by an HTML table tag that is set to a
     * fixed width.
     * <p>
     * Note: It seems to be a possible to trigger a NullPointerException in
     * Swing when this is used in a JLabel: GUI-239.
     */
    public static String restrictWidth(String text, int width) {
        return "<html><table width=\"" + width + "\"><tr><td>" + text + "</td></tr></table></html>";
    }

    /**
     * Restricts the size of a component by setting its minimum size and 
     * maximum size to the value of its preferred size.
     *
     */
    public static void restrictSize(JComponent component, SizePolicy sizePolicy) {
        restrictSize(component, sizePolicy, false);
    }

    public static void restrictSize(JComponent component, SizePolicy sizePolicy, boolean addClientProperty) {
        switch (sizePolicy) {
        case RESTRICT_HEIGHT:
            int height = component.getPreferredSize().height;
            int width = component.getPreferredSize().width;
            component.setMinimumSize(new Dimension(width, height));
            //component.setPreferredSize(new Dimension(width, height));
            component.setMaximumSize(new Dimension(Integer.MAX_VALUE, height));
            break;
        case RESTRICT_BOTH:
            height = component.getPreferredSize().height;
            width = component.getPreferredSize().width;
            component.setMinimumSize(new Dimension(width, height));
            //component.setPreferredSize(STANDARD_DIMENSION);
            component.setMaximumSize(new Dimension(width, height));
            break;
        case RESTRICT_NONE:
            component.setMinimumSize(null);
            component.setMaximumSize(null);
        }
        if (addClientProperty) {
            component.putClientProperty(SizePolicy.class, sizePolicy);
        }
    }

    public static class EmptyIcon implements Icon {
        private final String name;
        private final int width;
        private final int height;

        public EmptyIcon(String name, int width, int height) {
            this.name = name;
            this.width = width;
            this.height = height;
        }

        public void paintIcon(Component c, Graphics g, int x, int y) {
        }

        public int getIconWidth() {
            return width;
        }

        public int getIconHeight() {
            return height;
        }

        public String toString() {
            return name;
        }
    }

    public enum SizePolicy {
        RESTRICT_NONE, RESTRICT_HEIGHT, RESTRICT_BOTH
    }

    /**
      * Using a little reflection here for a lack of any better way 
      * to access locale-specific char codes for menu mnemonics.
      * We could at least defer this in the future.
      *
      * @param str the key for the locale-specific char resource to
      *  look up -- the key as it appears in the locale-specific
      *  properties file
      * @return the code for the passed-in key as defined in 
      *  <tt>java.awt.event.KeyEvent</tt>, or -1 if no key code
      *  could be found
      */
    public static int getCodeForCharKey(String str) {
        int charCode = -1;
        String charStr = str.toUpperCase(Locale.US);
        if (charStr.length() > 1)
            return -1;
        try {
            Field charField = KeyEvent.class.getField("VK_" + charStr);
            charCode = charField.getInt(KeyEvent.class);
        } catch (NoSuchFieldException e) {
            LOG.error("can't get key for: " + charStr, e);
        } catch (SecurityException e) {
            LOG.error("can't get key for: " + charStr, e);
        } catch (IllegalAccessException e) {
            LOG.error("can't get key for: " + charStr, e);
        }
        return charCode;
    }

    private static int getAmpersandPosition(String text) {
        int index = -1;
        while ((index = text.indexOf('&', index + 1)) != -1) {
            if (index < text.length() - 1 && Character.isLetterOrDigit(text.charAt(index + 1))) {
                break;
            }
        }
        return index;
    }

    public static String stripAmpersand(String text) {
        int index = getAmpersandPosition(text);
        if (index >= 0) {
            return text.substring(0, index) + text.substring(index + 1);
        }
        return text;
    }

    public static int getMnemonicKeyCode(String text) {
        // parse out mnemonic key
        int index = getAmpersandPosition(text);
        if (index >= 0) {
            return GUIUtils.getCodeForCharKey(text.substring(index + 1, index + 2));
        }
        return -1;
    }

    /**
     * It will adjust the column width to match the widest element.
     * (You might not want to use this for every column, consider some columns might be really long)
     * @param model
     * @param columnIndex
     * @param table
     * @return
     */
    public static void adjustColumnWidth(TableModel model, int columnIndex, int maxWidth, int rightPadding,
            JTable table) {

        if (columnIndex > model.getColumnCount() - 1) {
            //invalid column index
            return;
        }

        if (!model.getColumnClass(columnIndex).equals(String.class)) {
            return;
        }

        String longestValue = "";
        for (int row = 0; row < model.getRowCount(); row++) {
            String strValue = (String) model.getValueAt(row, columnIndex);
            if (strValue != null && strValue.length() > longestValue.length()) {
                longestValue = strValue;
            }
        }

        Graphics g = table.getGraphics();

        try {
            int suggestedWidth = (int) g.getFontMetrics(table.getFont()).getStringBounds(longestValue, g)
                    .getWidth();
            table.getColumnModel().getColumn(columnIndex)
                    .setPreferredWidth(((suggestedWidth > maxWidth) ? maxWidth : suggestedWidth) + rightPadding);
        } catch (Exception e) {
            table.getColumnModel().getColumn(columnIndex).setPreferredWidth(maxWidth);
            e.printStackTrace();
        }

    }
}