package org.limewire.ui.swing.util;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.lang.reflect.Field;
import java.net.URL;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.Locale;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.plaf.ComponentUI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.application.Application;
import org.jdesktop.application.SingleFrameApplication;
import org.limewire.ui.swing.mainframe.AppFrame;
import org.limewire.util.OSUtils;
public class GuiUtils {
private static final Log LOG = LogFactory.getLog(GuiUtils.class);
/**
* 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;
/**
* Localizable constants.
*/
public static String GENERAL_UNIT_BYTES;
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;
static {
setLocale(Locale.getDefault());
}
static void setLocale(Locale locale) {
NUMBER_FORMAT0 = NumberFormat.getNumberInstance(locale);
NUMBER_FORMAT0.setMaximumFractionDigits(0);
NUMBER_FORMAT0.setMinimumFractionDigits(0);
NUMBER_FORMAT0.setGroupingUsed(true);
NUMBER_FORMAT1 = NumberFormat.getNumberInstance(locale);
NUMBER_FORMAT1.setMaximumFractionDigits(2);
NUMBER_FORMAT1.setMinimumFractionDigits(2);
NUMBER_FORMAT1.setGroupingUsed(true);
DATETIME_FORMAT = DateFormat.getDateTimeInstance(DateFormat.DEFAULT,
DateFormat.DEFAULT, locale);
GENERAL_UNIT_BYTES =
I18n.tr("bytes");
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 "B", or "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
* "B"/"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 < 1024) {
unitName = GENERAL_UNIT_BYTES;
unitValue = 1;
} else if (bytes < 0x100000) { // below 1MB, use KB
unitValue = 0x400;
unitName = GENERAL_UNIT_KILOBYTES;
} else if (bytes < 0x40000000L) { // below 1GB, use MB
unitValue = 0x100000;
unitName = GENERAL_UNIT_MEGABYTES;
} else if (bytes < 0x10000000000L) { // below 1TB, use GB
unitValue = 0x40000000;
unitName = GENERAL_UNIT_GIGABYTES;
} else { // at least 1TB, use TB
unitValue = 0x10000000000L;
unitName = GENERAL_UNIT_TERABYTES;
}
NumberFormat numberFormat; // one of localizable formats
if(bytes < 0x100000) {
numberFormat = NUMBER_FORMAT0;
} else {
// return a minimum "100.0xB", and maximum "999.9xB"
numberFormat = NUMBER_FORMAT1; // localized "#,##0.00"
}
try {
return numberFormat.format((double)bytes / unitValue) + " " + unitName;
} catch(ArithmeticException ae) {
return "0 " + unitName;
// internal java error, just return 0.
}
}
/**
* Converts the passed in number of bytes into a byte-size string.
* The returned String is always a locale-dependant thousand separated
* String of bytes.
*/
public static String toBytes(long bytes) {
if (bytes < 0) {
return "? " + GENERAL_UNIT_KILOBYTES;
}
NumberFormat numberFormat = NUMBER_FORMAT0;
try {
return numberFormat.format(bytes) + " " + GENERAL_UNIT_BYTES;
} catch(ArithmeticException ae) {
return "0 " + GENERAL_UNIT_BYTES;
// internal java error, just return 0.
}
}
/**
* 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);
}
/**
* Returns the application's default frame.
*/
public static JFrame getMainFrame() {
if(AppFrame.isStarted()) {
Application app = Application.getInstance();
if(app instanceof SingleFrameApplication) {
return ((SingleFrameApplication)app).getMainFrame();
} else {
return null;
}
} else {
return null;
}
}
/**
* Returns the window frame containing the specified component. If the
* component has not been added to a window frame, then the main GUI
* frame is returned.
* @param component the UI component being examined
* @return window frame containing the component
*/
public static Frame getParentFrame(Component component) {
// Access top level ancestor for Swing component.
if (component instanceof JComponent) {
Container ancestor = ((JComponent) component).getTopLevelAncestor();
if (ancestor instanceof Frame) {
return (Frame) ancestor;
}
}
// Return main frame if parent frame cannot be determined.
return GuiUtils.getMainFrame();
}
/**
* Inject fields from AppFrame.properties into object. Fields to be injected
* should be annotated <code>@Resource</code> and defined in AppFrame.properties as
* <code>ClassNameWithoutPackage.variableName=resource</code>
*
* @param object the object whose fields will be injected
*/
public static void assignResources(Object object) {
Application.getInstance().getContext().getResourceMap(AppFrame.class)
.injectFields(object);
}
/**
* 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. */
public static String toHex(int i) {
String hex = Integer.toHexString(i).toUpperCase(Locale.US);
if (hex.length() == 1)
return "0" + hex;
else
return hex;
}
/**
* Updates the component to use the native UI resource.
*/
public static ComponentUI getNativeUI(JComponent c) {
ComponentUI ret = null;
String name = UIManager.getSystemLookAndFeelClassName();
if (name != null) {
try {
Class clazz = Class.forName(name);
LookAndFeel lf = (LookAndFeel) clazz.newInstance();
lf.initialize();
UIDefaults def = lf.getDefaults();
ret = def.getUI(c);
} catch (ExceptionInInitializerError e) {
} catch (ClassNotFoundException e) {
} catch (LinkageError e) {
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (SecurityException e) {
} catch (ClassCastException e) {
}
}
// if any of those failed, default to the current UI.
if (ret == null)
ret = UIManager.getUI(c);
return ret;
}
/**
* 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>";
}
/**
* 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;
}
/**
* Strips the first ampersand '&' in <code>text</code> that appears
* before a letter or digit.
*
* @return the original text if there is no such ampersand
*/
public static String stripAmpersand(String text) {
int index = getAmpersandPosition(text);
if (index >= 0) {
return text.substring(0, index) + text.substring(index + 1);
}
return text;
}
/**
* Finds the first ampersand '&' in <code>text</code> that appears
* before a letter or a digit and returns the key code for the letter
* or digit after it.
*/
public static int getMnemonicKeyCode(String text) {
// parse out mnemonic key
int index = getAmpersandPosition(text);
if (index >= 0) {
return getCodeForCharKey(text.substring(index + 1, index + 2));
}
return -1;
}
/**
* Determines if the Start On Startup option is available.
*/
public static boolean shouldShowStartOnStartupWindow() {
return OSUtils.isMacOSX() ||
WindowsUtils.isLoginStatusAvailable();
}
/**
* Returns a string representing the quality score just like
* toQualityString not including the trailing word 'Quality'.
*/
public static String toQualityStringShort(long qualityScore) {
if (qualityScore <= 1) {
return I18n.trc("Poor (media quality)", "Poor");
} else if (qualityScore == 2) {
return I18n.trc("Good (media quality)", "Good");
} else {
return I18n.trc("Excellent (media quality)", "Excellent");
}
}
/**
* Returns a quality string for the given quality score.
* <pre>
* <= 1 Poor Quality
* == 2 Good Quality
* > 2 Excellent Quality
* </pre>
*/
public static String toQualityString(long qualityScore) {
if (qualityScore <= 1) {
return I18n.tr("Poor Quality");
} else if (qualityScore == 2) {
return I18n.tr("Good Quality");
} else {
return I18n.tr("Excellent Quality");
}
}
/** Returns a listener that opens hyperlinks in a browser. */
public static HyperlinkListener getHyperlinkListener() {
return new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if(e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
URL url = e.getURL();
if(url != null) {
NativeLaunchUtils.openURL(url.toExternalForm());
}
}
}
};
}
/** Returns the ESC action. */
public static Action getDisposeAction() {
return 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));
}
};
}
/**
* Adds a hide action to a JDialog.
*/
public static void addHideAction(JDialog jd) {
addHideAction(jd.getRootPane());
}
/**
* Adds an action to hide a window / dialog.
* <p>
* 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);
}
}
/**
* Returns the point for the placing the specified component on the
* center of the screen.
*
* @param comp the <tt>Component</tt> to use for getting the relative
* center point
* @return the <tt>Point</tt> for centering the specified
* <tt>Component</tt> on the screen
*/
public static Point getScreenCenterPoint(Component comp) {
Dimension dimension = comp.getSize();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int appWidth = Math.min(screenSize.width, dimension.width);
// compare against a little bit less than the screen size,
// as the screen size includes the taskbar
int appHeight = Math.min(screenSize.height - 40, dimension.height);
return new Point((screenSize.width - appWidth) / 2, (screenSize.height - appHeight) / 2);
}
/**
* Determines if a font can display up to a point in the string.
* <p>
* 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;
}
/**
* Iterates over all the windows owned by this process then hides and
* disposes them.
*/
public static void hideAndDisposeAllWindows() {
for(Window window : Window.getWindows()) {
try {
window.setVisible(false);
window.dispose();
} catch(Throwable ignored) {}
}
}
}