/**
*
*/
package cz.cuni.mff.peckam.java.origamist.services;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Hashtable;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.swing.BorderFactory;
import javax.swing.KeyStroke;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import cz.cuni.mff.peckam.java.origamist.services.interfaces.ConfigurationManager;
/**
* A factory class for creating visually interesting HTML tooltips.
*
* <b>Service dependencies</b>:
* ConfigurationManager
*
* @author Martin Pecka
*/
public class TooltipFactory extends Service
{
/** The application resource bundle. */
protected ResourceBundle messages = null;
/** The names for the key modifiers. */
protected Hashtable<String, String> modifierTranslations = new Hashtable<String, String>(7);
/** The names for the custom keys. */
protected Hashtable<String, String> keyTranslations = new Hashtable<String, String>();
/** The first word contained in the <code>os.name</code> system property. */
protected final String osName;
public TooltipFactory()
{
String os = System.getProperty("os.name", "Windows").trim();
int index = os.indexOf(' ');
if (index > 0)
os = os.substring(0, index);
this.osName = os;
PropertyChangeListener l = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt)
{
messages = ResourceBundle.getBundle("application", (Locale) evt.getNewValue());
keyTranslations.clear();
modifierTranslations.clear();
for (String mod : new String[] { "Ctrl", "Meta", "Alt", "AltGr", "Shift" }) {
modifierTranslations.put(mod, getKeyTranslation(mod));
}
}
};
ServiceLocator.get(ConfigurationManager.class).get().addPropertyChangeListener("locale", l);
l.propertyChange(new PropertyChangeEvent(this, "locale", null, ServiceLocator.get(ConfigurationManager.class)
.get().getLocale()));
UIManager.put("ToolTip.border", BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
UIManager.put("ToolTip.background", new Color(231, 231, 184, 230));
ToolTipManager.sharedInstance().setDismissDelay(30000);
}
/**
* Return a default looking tooltip.
*
* @param text The text to be displayed.
* @return A default looking tooltip.
*/
public String getPlain(String text)
{
return text;
}
/**
* Return a default looking tooltip with the accelerator text added.
*
* @param text The text to be displayed.
* @param accelerator The accelerator to be appended to the given text.
* @return A default looking tooltip.
*/
public String getPlain(String text, KeyStroke accelerator)
{
return text
+ (accelerator != null ? " " + messages.getString("accelerator") + ": "
+ getPlainKeyStrokeText(accelerator) : "");
}
/**
* Return a custom (and good-looking) tooltip.
*
* @param text The text to be displayed. Shouldn't be <code>null</code>.
* @return A custom (and good-looking) tooltip.
*/
public String getDecorated(String text)
{
return getDecorated(text, null, null, null);
}
/**
* Return a custom (and good-looking) tooltip.
*
* @param text The text to be displayed. Shouldn't be <code>null</code>.
* @param title The short title describing the tooltip. Can be <code>null</code>.
* @param iconName The name of the icon file in <code>/resources/images/</code> to display with this tooltip. Can be
* <code>null</code>.
* @param accelerator The accelerator to be displayed. Can be <code>null</code>.
*
* @return A custom (and good-looking) tooltip.
*/
public String getDecorated(String text, String title, String iconName, KeyStroke accelerator)
{
StringBuilder result = new StringBuilder();
String innerText = text.trim();
// encode HTML entities if the given text isn't HTML yet
if (!text.substring(0, Math.min(text.length(), 6)).toLowerCase().startsWith("<html>")) {
innerText = innerText.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
} else {
innerText = innerText.substring(6).replaceAll("</html>$", "");
}
result.append("<html>");
result.append("<head>");
result.append("<style type=\"text/css\">");
result.append("html {margin: 0px; padding: 0px;} ");
result.append("body {background: transparent url('")
.append(getClass().getResource("/resources/images/tooltip-bg.png")).append("') repeat-x top left;} ");
result.append("#content {background: transparent url('")
.append(getClass().getResource("/resources/images/lightbulb-24.png"))
.append("') no-repeat top right; margin: 5px 0px 10px 10px; padding: 5px 25px 0px 0px;} ");
result.append("#icon {width: 24px; height: 24px; border: 0px none; margin: 3px;} ");
result.append("#title {font-weight: bold; font-size: 120%; margin: 0px 4px 0px 4px;}");
result.append(".accelerator {margin-left: 10px; padding: 2px; background-color: #eaeaca;}");
result.append(".key {margin: 0px; padding: 1px; background-color: #888888;}");
result.append(".key div {margin: 0px; padding: 1px; background-color: #aaaaaa;}");
result.append(".key div div {padding: 1px 3px; background-color: #eeeeee;}");
try {
result.append(messages.getString("tooltip.additionalCSS"));
} catch (MissingResourceException e) {}
result.append("</style>");
result.append("</head>");
result.append("<body><div id=\"content\">");
if (title != null || iconName != null || accelerator != null) {
result.append("<table id=\"heading\"><tr>");
if (iconName != null) {
result.append("<td><img src=\"").append(getClass().getResource("/resources/images/" + iconName))
.append("\" alt=\"").append((title != null ? title : "icon"))
.append("\" id=\"icon\" width=\"24\" height=\"24\"/></td>");
}
if (title != null) {
result.append("<td><span id=\"title\">").append(title).append("</span></td>");
}
if (accelerator != null) {
result.append("<td>").append(getDecoratedKeyStrokeText(accelerator)).append("</td>");
}
result.append("</tr></table>");
}
result.append("<div id=\"main\">").append(innerText).append("</div>");
result.append("</div></body>");
result.append("</html>");
return result.toString();
}
/**
* Return a string corresponding to <code>key</code> in the current locale and the given OS. If no translation is
* found, the <code>key</code> is returned.
*
* @param key The key to be translated, can be either <code>(Ctrl|Meta|Alt|AltGr|Shift)</code> or a
* <code>part after "VK_" in a KeyEvent constant</code>.
* @return A string corresponding to <code>key</code> in the current locale and the given OS.
*/
protected String getKeyTranslation(String key)
{
try {
return messages.getString("key." + key + "." + osName);
} catch (MissingResourceException e) {
try {
return messages.getString("key." + key);
} catch (MissingResourceException e2) {
return key;
}
}
}
/**
* Return a string array of textual description of the keystroke.
*
* If the length of the returned array is <code>n</code>, then items <code>0</code> to <code>n-2</code> contain the
* modifier key descriptions, and item <code>n-1</code> contains the key description.
*
* Modifier can be on of:
* <ul>
* <li>Ctrl</li>
* <li>Meta</li>
* <li>Alt</li>
* <li>AltGr</li>
* <li>Shift</li>
* </ul>
* The modifiers will always be returned in this order.
*
* The key can be any part following <code>VK_</code> in {@link KeyEvent} constants.
*
* @param stroke The {@link KeyStroke} to parse.
* @return A string array of textual description of the keystroke.
*/
protected String[] parseKeyStroke(KeyStroke stroke)
{
if (stroke == null)
return new String[0];
int mod = stroke.getModifiers();
boolean isCtrl = (mod & KeyEvent.CTRL_DOWN_MASK) > 0;
boolean isShift = (mod & KeyEvent.SHIFT_DOWN_MASK) > 0;
boolean isMeta = (mod & KeyEvent.META_DOWN_MASK) > 0;
boolean isAlt = (mod & KeyEvent.ALT_DOWN_MASK) > 0;
boolean isAltGr = (mod & KeyEvent.ALT_GRAPH_DOWN_MASK) > 0;
String[] result = new String[(isCtrl ? 1 : 0) + (isShift ? 1 : 0) + (isMeta ? 1 : 0) + (isAlt ? 1 : 0)
+ (isAltGr ? 1 : 0) + 1];
int i = 0;
if (isCtrl)
result[i++] = "Ctrl";
if (isMeta)
result[i++] = "Meta";
if (isAlt)
result[i++] = "Alt";
if (isAltGr)
result[i++] = "AltGr";
if (isShift)
result[i++] = "Shift";
result[i] = KeyStroke.getKeyStroke(stroke.getKeyCode(), 0).toString().replaceAll("^[a-z]* ", "");
return result;
}
/**
* Localize the strings returned by {@link parseKeyStroke(KeyStroke)} according to the current locale and OS.
*
* Eg. localizes <code>Meta</code> modifier on Windows in English to <code>Win</code>.
*
* @param stroke The array of strings returned by {@link parseKeyStroke(KeyStroke)}.
* @return The array of localized strings.
*/
protected String[] localizeParsedKeyStroke(String[] stroke)
{
if (stroke == null || stroke.length == 0)
return stroke;
if (stroke.length > 1) {
for (int i = 0; i < stroke.length - 1; i++) {
stroke[i] = modifierTranslations.get(stroke[i]);
}
}
String key = stroke[stroke.length - 1];
if (keyTranslations.get(key) == null) {
String trans = getKeyTranslation(key);
// we really want to compare the String addresses! If equal, it may mean the
// getKeyTranslation() didn't find anything usable and we've still got a suffix from the
// VK_ constant name.
if (trans == key) {
trans = trans.replaceAll("_", " ").toLowerCase();
StringBuilder sb = new StringBuilder(trans.length());
for (String s : trans.split(" ")) {
sb.append(Character.toUpperCase(s.charAt(0)));
if (s.length() > 1)
sb.append(s.substring(1)).append(" ");
}
trans = sb.toString().trim();
}
keyTranslations.put(key, trans);
}
stroke[stroke.length - 1] = keyTranslations.get(key);
return stroke;
}
/**
* Return a plain-text representation of the given {@link KeyStroke}.
*
* @param stroke The {@link KeyStroke} we want text representation of.
* @return A plain-text representation of the given {@link KeyStroke}.
*/
protected String getPlainKeyStrokeText(KeyStroke stroke)
{
StringBuffer sb = new StringBuffer();
String[] parsed = localizeParsedKeyStroke(parseKeyStroke(stroke));
for (int i = 0; i < parsed.length; i++) {
sb.append(parsed[i]);
if (i < parsed.length - 1)
sb.append("+");
}
return sb.toString();
}
/**
* Return a HTML-decorated representation of the given {@link KeyStroke}.
*
* @param stroke The {@link KeyStroke} we want text representation of.
* @return A HTML-decorated representation of the given {@link KeyStroke}.
*/
protected String getDecoratedKeyStrokeText(KeyStroke stroke)
{
StringBuffer sb = new StringBuffer("<table class=\"accelerator\"><tr>");
String[] parsed = localizeParsedKeyStroke(parseKeyStroke(stroke));
for (int i = 0; i < parsed.length; i++) {
sb.append("<td class=\"key\"><div><div>").append(parsed[i]).append("</div></div></td>");
if (i < parsed.length - 1)
sb.append("<td>+</td>");
}
return sb.append("</tr></table>").toString();
}
@Override
protected Class<?>[] getDependecies()
{
return new Class<?>[] { ConfigurationManager.class };
}
}