/*
* 12/21/2008
*
* Util.java - Utility methods for the autocompletion package.
*
* This library is distributed under a modified BSD license. See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.autocomplete;
import java.awt.Color;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.AccessControlException;
import java.util.regex.Pattern;
import javax.swing.JLabel;
import javax.swing.UIManager;
import org.fife.ui.rsyntaxtextarea.PopupWindowDecorator;
/**
* Utility methods for the auto-complete framework.
*
* @author Robert Futrell
* @version 1.0
*/
public class Util {
/**
* If a system property is defined with this name and set, ignoring case,
* to <code>true</code>, this library will not attempt to use Substance
* renderers. Otherwise, if a Substance Look and Feel is installed, we
* will attempt to use Substance cell renderers in all of our dropdowns.<p>
*
* Note that we do not have a build dependency on Substance, so all access
* to Substance stuff is done via reflection. We will fall back onto
* default renderers if something goes horribly wrong.
*/
public static final String PROPERTY_DONT_USE_SUBSTANCE_RENDERERS =
"org.fife.ui.autocomplete.DontUseSubstanceRenderers";
/**
* If this system property is <code>true</code>, then even the "main" two
* auto-complete windows will allow window decorations via
* {@link PopupWindowDecorator}. If this property is undefined or
* <code>false</code>, they won't honor such decorations. This is due to
* certain performance issues with translucent windows (used for drop
* shadows), even as of Java 7u2.
*/
public static final String PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS =
"org.fife.ui.autocomplete.allowDecoratedAutoCompleteWindows";
/**
* Used for the color of hyperlinks when a LookAndFeel uses light text
* against a dark background.
*/
private static final Color LIGHT_HYPERLINK_FG = new Color(0xd8ffff);
private static final Pattern TAG_PATTERN = Pattern.compile("<[^>]*>");
private static final boolean useSubstanceRenderers;
private static boolean desktopCreationAttempted;
private static Object desktop;
private static final Object LOCK_DESKTOP_CREATION = new Object();
/**
* Attempts to open a web browser to the specified URI.
*
* @param uri The URI to open. If this is <code>null</code>, nothing
happens and this method returns <code>false</code>.
* @return Whether the operation was successful. This will be
* <code>false</code> on JRE's older than 1.6.
*/
public static boolean browse(URI uri) {
boolean success = false;
if (uri!=null) {
Object desktop = getDesktop();
if (desktop!=null) {
try {
Method m = desktop.getClass().getDeclaredMethod(
"browse", new Class[] { URI.class });
m.invoke(desktop, new Object[] { uri });
success = true;
} catch (RuntimeException re) {
throw re; // Keep FindBugs happy
} catch (Exception e) {
// Ignore, just return "false" below.
}
}
}
return success;
}
/**
* Returns the singleton <code>java.awt.Desktop</code> instance, or
* <code>null</code> if it is unsupported on this platform (or the JRE
* is older than 1.6).
*
* @return The desktop, as an {@link Object}.
*/
private static Object getDesktop() {
synchronized (LOCK_DESKTOP_CREATION) {
if (!desktopCreationAttempted) {
desktopCreationAttempted = true;
try {
Class<?> desktopClazz = Class.forName("java.awt.Desktop");
Method m = desktopClazz.
getDeclaredMethod("isDesktopSupported");
boolean supported= ((Boolean)m.invoke(null)).booleanValue();
if (supported) {
m = desktopClazz.getDeclaredMethod("getDesktop");
desktop = m.invoke(null);
}
} catch (RuntimeException re) {
throw re; // Keep FindBugs happy
} catch (Exception e) {
// Ignore; keeps desktop as null.
}
}
}
return desktop;
}
/**
* Returns a hex string for the specified color, suitable for HTML.
*
* @param c The color.
* @return The string representation, in the form "<code>#rrggbb</code>",
* or <code>null</code> if <code>c</code> is <code>null</code>.
*/
public static String getHexString(Color c) {
if (c==null) {
return null;
}
// Don't assume 0xff alpha
//return "#" + Integer.toHexString(c.getRGB()&0xffffff).substring(2);
StringBuilder sb = new StringBuilder("#");
int r = c.getRed();
if (r<16) {
sb.append('0');
}
sb.append(Integer.toHexString(r));
int g = c.getGreen();
if (g<16) {
sb.append('0');
}
sb.append(Integer.toHexString(g));
int b = c.getBlue();
if (b<16) {
sb.append('0');
}
sb.append(Integer.toHexString(b));
return sb.toString();
}
/**
* Returns the color to use for hyperlink-style components. This method
* will return <code>Color.blue</code> unless it appears that the current
* LookAndFeel uses light text on a dark background, in which case a
* brighter alternative is returned.
*
* @return The color to use for hyperlinks.
*/
static final Color getHyperlinkForeground() {
// This property is defined by all standard LaFs, even Nimbus (!),
// but you never know what crazy LaFs there are...
Color fg = UIManager.getColor("Label.foreground");
if (fg==null) {
fg = new JLabel().getForeground();
}
return isLightForeground(fg) ? LIGHT_HYPERLINK_FG : Color.blue;
}
/**
* Returns the screen coordinates for the monitor that contains the
* specified point. This is useful for setups with multiple monitors,
* to ensure that popup windows are positioned properly.
*
* @param x The x-coordinate, in screen coordinates.
* @param y The y-coordinate, in screen coordinates.
* @return The bounds of the monitor that contains the specified point.
*/
public static Rectangle getScreenBoundsForPoint(int x, int y) {
GraphicsEnvironment env = GraphicsEnvironment.
getLocalGraphicsEnvironment();
GraphicsDevice[] devices = env.getScreenDevices();
for (int i=0; i<devices.length; i++) {
GraphicsConfiguration config = devices[i].getDefaultConfiguration();
Rectangle gcBounds = config.getBounds();
if (gcBounds.contains(x, y)) {
return gcBounds;
}
}
// If point is outside all monitors, default to default monitor (?)
return env.getMaximumWindowBounds();
}
/**
* Give apps a chance to decorate us with drop shadows, etc. Since very
* scrolly things such as lists (of e.g. completions) are *very* slow when
* in per-pixel translucent windows, even as of Java 7u2, we force the user
* to specify an extra option for the two "main" auto-complete windows.
*
* @return Whether to allow decorating the main auto-complete windows.
* @see #PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS
*/
public static boolean getShouldAllowDecoratingMainAutoCompleteWindows() {
try {
return Boolean.getBoolean(
PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS);
} catch (AccessControlException ace) { // We're in an applet.
return false;
}
}
/**
* Returns whether we should attempt to use Substance cell renderers and
* styles for things such as completion choices, if a Substance Look and
* Feel is installed. If this is <code>false</code>, we'll use our
* standard rendering for completions, even when Substance is being used.
*
* @return Whether to use Substance renderers if Substance is installed.
*/
public static boolean getUseSubstanceRenderers() {
return useSubstanceRenderers;
}
/**
* Returns whether the specified color is "light" to use as a foreground.
* Colors that return <code>true</code> indicate that the current Look and
* Feel probably uses light text colors on a dark background.
*
* @param fg The foreground color.
* @return Whether it is a "light" foreground color.
*/
public static final boolean isLightForeground(Color fg) {
return fg.getRed()>0xa0 && fg.getGreen()>0xa0 && fg.getBlue()>0xa0;
}
/**
* Returns whether <code>str</code> starts with <code>start</code>,
* ignoring case.
*
* @param str The string to check.
* @param start The prefix to check for.
* @return Whether <code>str</code> starts with <code>start</code>,
* ignoring case.
*/
public static boolean startsWithIgnoreCase(String str, String start) {
int startLen = start.length();
if (str.length()>=startLen) {
for (int i=0; i<startLen; i++) {
char c1 = str.charAt(i);
char c2 = start.charAt(i);
if (Character.toLowerCase(c1)!=Character.toLowerCase(c2)) {
return false;
}
}
return true;
}
return false;
}
/**
* Strips any HTML from a string. The string must start with
* "<code><html></code>" for markup tags to be stripped.
*
* @param text The string.
* @return The string, with any HTML stripped.
*/
public static String stripHtml(String text) {
if (text==null || !text.startsWith("<html>")) {
return text;
}
// TODO: Micro-optimize me, might be called in renderers and loops
return TAG_PATTERN.matcher(text).replaceAll("");
}
static {
boolean use = true;
try {
use = !Boolean.getBoolean(PROPERTY_DONT_USE_SUBSTANCE_RENDERERS);
} catch (AccessControlException ace) { // We're in an applet.
use = true;
}
useSubstanceRenderers = use;
}
}