package com.limegroup.gnutella.gui; 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.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; 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.net.URL; import java.text.DateFormat; import java.text.NumberFormat; import java.util.Date; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import com.limegroup.gnutella.SpeedConstants; import com.limegroup.gnutella.gui.themes.ThemeSettings; import com.limegroup.gnutella.util.CommonUtils; //2345678|012345678|012345678|012345678|012345678|012345678|012345678|012345678| /** * This class serves as a holder for any static gui convenience * methods. */ public final class GUIUtils { /** * Make sure the constructor is never called. */ private GUIUtils() {} /** * Localizable Number Format constant for the current default locale * set at init time. */ private static final NumberFormat NUMBER_FORMAT0; // localized "#,##0" private static final NumberFormat NUMBER_FORMAT1; // localized "#,##0.0" /** * Localizable Date Format constant for the current default locale * set at init time. */ private static final DateFormat DATETIME_FORMAT; /** * Localizable constants */ public static final String GENERAL_UNIT_KILOBYTES; public static final String GENERAL_UNIT_MEGABYTES; public static final String GENERAL_UNIT_GIGABYTES; public static final String GENERAL_UNIT_TERABYTES; /* ambiguous name: means kilobytes/second, not kilobits/second! */ public static final 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 { NUMBER_FORMAT0 = NumberFormat.getNumberInstance(); NUMBER_FORMAT0.setMaximumFractionDigits(0); NUMBER_FORMAT0.setMinimumFractionDigits(0); NUMBER_FORMAT0.setGroupingUsed(true); NUMBER_FORMAT1 = NumberFormat.getNumberInstance(); NUMBER_FORMAT1.setMaximumFractionDigits(1); NUMBER_FORMAT1.setMinimumFractionDigits(1); NUMBER_FORMAT1.setGroupingUsed(true); DATETIME_FORMAT = DateFormat.getDateTimeInstance(); GENERAL_UNIT_KILOBYTES = GUIMediator.getStringResource("GENERAL_UNIT_KILOBYTES"); GENERAL_UNIT_MEGABYTES = GUIMediator.getStringResource("GENERAL_UNIT_MEGABYTES"); GENERAL_UNIT_GIGABYTES = GUIMediator.getStringResource("GENERAL_UNIT_GIGABYTES"); GENERAL_UNIT_TERABYTES = GUIMediator.getStringResource("GENERAL_UNIT_TERABYTES"); GENERAL_UNIT_KBPSEC = GUIMediator.getStringResource("GENERAL_UNIT_KBPSEC"); 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()); } } }; } /** * 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 < 0x6400000) { // below 100MB, use KB unitValue = 0x400; unitName = GENERAL_UNIT_KILOBYTES; } else if (bytes < 0x1900000000L) { // below 100GB, use MB unitValue = 0x100000; unitName = GENERAL_UNIT_MEGABYTES; } else if (bytes < 0x640000000000L) { // below 100TB, 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; // see: http://bugs.limewire.com:8080/bugs/servlet/Search?l=152&c=537&o=3&j=43&m=470_319 // internal java error, just return 0. } } /** * Converts the passed in number into a short localized string. * Group digits with locale-dependant thousand separator if needed, but * first try to append a "k", "M", "G", or "T" locale-independant unit. * Allows negative numbers in a locale-dependant representation. * * @param value the number of bytes to convert to a size String. * @param allowFractional enables 1 fractional digit for values lower than * 999.95; 1 fractional digit is always enabled * for values greater than or equal to 99999.5, * which have a unit appended. * @return a <code>String</code> representing the number that the * <code>value</code> argument evaluates to, with a * possible "k"/"M"/"G"/T" unit appended at the end. */ public static String toUnitnumber(double value, boolean allowFractional) { double abs = (value < 0) ? -value : value; long unitValue; // the multiple associated with the unit String unitName; // one of localizable units if (abs < 100000) { // below 100k, don't use unit unitValue = 1; unitName = ""; // no unit } else if (abs < 100000000) { // below 100M, use k unitValue = 1000; unitName = "k"; // international unit for thousands (kilo) allowFractional = true; } else if (abs < 100000000000L) { // below 100G, use M unitValue = 1000000; unitName = "M"; // international unit for millions (mega) allowFractional = true; } else if (abs < 100000000000000L) { // below 100T, use G unitValue = 1000000000; unitName = "G"; // international unit for billions (giga) allowFractional = true; } else { // at least 10TB, use T unitValue = 1000000000000L; unitName = "T"; // international unit for trillions (tera) allowFractional = true; } NumberFormat numberFormat; // one of localizable formats if (allowFractional && abs * 100 / unitValue < 99995) { // return a minimum "100.0x", and maximum "999.9x" numberFormat = NUMBER_FORMAT1; // localized "#,##0.0" } else { // return a minimum "1,000x" numberFormat = NUMBER_FORMAT0; // localized "#,##0" } return numberFormat.format(value / unitValue) + unitName; } /** * 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 < Integer.MAX_VALUE) 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); } /** * 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 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(CommonUtils.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() { 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(CommonUtils.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; } /** * A MouseListener that changes the cursor & * goes to a URL on a click. */ public static MouseListener getURLInputListener(final String url) { 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) { GUIMediator.openURL(url); } }; } /** * Determines if the Start On Startup option is availble. */ public static boolean shouldShowStartOnStartupWindow() { return (CommonUtils.isMacOSX() && CommonUtils.isCocoaFoundationAvailable()) || (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) { StringBuffer b = new StringBuffer(preSpaces + s.length()); for(int i = 0; i < preSpaces; i++) b.append('\u00a0'); b.append(s.replace(' ', '\u00a0')); return b.toString(); } }