blob: 5e05549bd9242932725612e3ef528b16e70e5033 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 Google, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.wb.internal.core.model.property.table;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.LocationAdapter;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.ProgressAdapter;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.eclipse.wb.draw2d.IColorConstants;
import org.eclipse.wb.internal.core.DesignerPlugin;
import org.eclipse.wb.internal.core.EnvironmentUtils;
import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;
import org.eclipse.wb.internal.core.utils.ui.GridDataFactory;
import org.eclipse.wb.internal.core.utils.ui.PixelConverter;
import java.io.StringReader;
import java.net.URL;
import java.text.MessageFormat;
/**
* Helper for displaying HTML tooltips.
*
* @author scheglov_ke
* @coverage core.model.property.table
*/
public final class HtmlTooltipHelper {
public static Control createTooltipControl(Composite parent, String header, String details) {
return createTooltipControl(parent, header, details, 0);
}
public static Control createTooltipControl(Composite parent,
String header,
String details,
int heightLimit) {
// prepare Control
Control control;
try {
String html = "<table cellspacing=2 cellpadding=0 border=0 margins=0 id=_wbp_tooltip_body>";
if (header != null) {
html += "<tr align=center><td><b>" + header + "</b></td></tr>";
}
html += "<tr><td align=justify>" + details + "</td></tr>";
html += "</table>";
control = createTooltipControl_Browser(parent, html, heightLimit);
} catch (Throwable e) {
control = createTooltipControl_Label(parent, details);
}
// set listeners
{
Listener listener = new Listener() {
@Override
public void handleEvent(Event event) {
Control tooltipControl = (Control) event.widget;
hideTooltip(tooltipControl);
}
};
control.addListener(SWT.MouseExit, listener);
}
// done
return control;
}
/**
* Creates {@link Browser} for displaying tooltip.
*/
private static Control createTooltipControl_Browser(Composite parent,
String html,
final int heightLimitChars) {
// prepare styles
String styles;
try {
styles = DesignerPlugin.readFile(PropertyTable.class.getResourceAsStream("Tooltip.css"),
Charsets.US_ASCII);
if (styles == null) {
styles = "";
}
} catch (Throwable e) {
styles = "";
}
// prepare HTML with styles and tags
String wrappedHtml;
{
String bodyAttributes =
MessageFormat.format(
"text=''{0}'' bgcolor=''{1}''",
getColorWebString(IColorConstants.tooltipForeground),
getColorWebString(IColorConstants.tooltipBackground));
String closeElement =
EnvironmentUtils.IS_LINUX
? " <a href='' style='position:absolute;right:1em;' id=_wbp_close>Close</a>"
: "";
wrappedHtml =
/*CodeUtils.*/getSource(
"<html>",
" <style CHARSET='ISO-8859-1' TYPE='text/css'>",
styles,
" </style>",
" <body " + bodyAttributes + ">",
closeElement,
html,
" </body>",
"</html>");
}
// prepare Browser
final Browser browser = new Browser(parent, SWT.NONE);
browser.setText(wrappedHtml);
// open URLs in new window
browser.addLocationListener(new LocationAdapter() {
@Override
public void changing(LocationEvent event) {
event.doit = false;
hideTooltip((Browser) event.widget);
if (!"about:blank".equals(event.location)) {
try {
IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport();
IWebBrowser browserSupport = support.createBrowser("wbp.browser");
browserSupport.openURL(new URL(event.location));
} catch (Throwable e) {
DesignerPlugin.log(e);
}
}
}
});
// set size
{
int textLength = getTextLength(html);
// horizontal hint
int hintH = 50;
if (textLength < 100) {
hintH = 40;
}
// vertical hint
int hintV = textLength / hintH + 3;
hintV = Math.min(hintV, 8);
// do set
GridDataFactory.create(browser).hintC(hintH, hintV);
}
// tweak size after rendering HTML
browser.addProgressListener(new ProgressAdapter() {
@Override
public void completed(ProgressEvent event) {
browser.removeProgressListener(this);
tweakBrowserSize(browser, heightLimitChars);
browser.getShell().setVisible(true);
}
});
// done
return browser;
}
private static void tweakBrowserSize(Browser browser, int heightLimitChars) {
GridDataFactory.create(browser).grab().fill();
// limit height
if (heightLimitChars != 0) {
PixelConverter pixelConverter = new PixelConverter(browser);
int maxHeight = pixelConverter.convertHeightInCharsToPixels(heightLimitChars);
expandShellToShowFullPage_Height(browser, maxHeight);
}
// if no limit, then show all, so make as tall as required
if (heightLimitChars == 0) {
expandShellToShowFullPage_Height(browser, Integer.MAX_VALUE);
}
}
private static void expandShellToShowFullPage_Height(Browser browser, int maxHeight) {
try {
Shell shell = browser.getShell();
// calculate required
int contentHeight;
{
getContentOffsetHeight(browser);
contentHeight = getContentScrollHeight(browser);
}
// apply height
int useHeight = Math.min(contentHeight + ((EnvironmentUtils.IS_LINUX) ? 2 : 10), maxHeight);
shell.setSize(shell.getSize().x, useHeight);
// trim height to content
{
int offsetHeight = getBodyOffsetHeight(browser);
int scrollHeight = getBodyScrollHeight(browser);
int delta = scrollHeight - offsetHeight;
if (delta != 0 && delta < 10) {
Point size = shell.getSize();
shell.setSize(size.x, size.y + delta + 1);
}
}
// trim width to content
{
int offsetWidth = getContentOffsetWidth(browser);
{
Point size = shell.getSize();
shell.setSize(offsetWidth + ((EnvironmentUtils.IS_MAC) ? 6 : 10), size.y);
}
}
// hide 'Close' if too narrow
if (EnvironmentUtils.IS_LINUX) {
if (shell.getSize().y < 30) {
hideCloseElement(browser);
}
}
} catch (Throwable e) {
}
}
private static int getContentOffsetWidth(Browser browser) throws Exception {
return evaluateScriptInt(
browser,
"return document.getElementById('_wbp_tooltip_body').offsetWidth;");
}
private static int getContentOffsetHeight(Browser browser) throws Exception {
return evaluateScriptInt(
browser,
"return document.getElementById('_wbp_tooltip_body').offsetHeight;");
}
private static int getContentScrollHeight(Browser browser) throws Exception {
return evaluateScriptInt(
browser,
"return document.getElementById('_wbp_tooltip_body').scrollHeight;");
}
private static int getBodyOffsetHeight(Browser browser) throws Exception {
return evaluateScriptInt(browser, "return document.body.offsetHeight;");
}
private static int getBodyScrollHeight(Browser browser) throws Exception {
return evaluateScriptInt(browser, "return document.body.scrollHeight;");
}
private static int evaluateScriptInt(Browser browser, String script) throws Exception {
Object o = ReflectionUtils.invokeMethod(browser, "evaluate(java.lang.String)", script);
return ((Number) o).intValue();
}
private static void hideCloseElement(Browser browser) throws Exception {
String script = "document.getElementById('_wbp_close').style.display = 'none'";
ReflectionUtils.invokeMethod(browser, "evaluate(java.lang.String)", script);
}
/**
* @return the length of text in given HTML. Uses internal class, so may fail, in this case
* returns length on HTML.
*/
private static int getTextLength(String html) {
StringReader htmlStringReader = new StringReader(html);
try {
ClassLoader classLoader = PropertyTable.class.getClassLoader();
Class<?> readerClass =
classLoader.loadClass("org.eclipse.jface.internal.text.html.HTML2TextReader");
Object reader = readerClass.getConstructors()[0].newInstance(htmlStringReader, null);
String text = (String) ReflectionUtils.invokeMethod(reader, "getString()");
return text.length();
} catch (Throwable e) {
return html.length();
}
}
/**
* Returns a string representation of {@link Color} suitable for web pages.
*
* @param color
* the {@link Color} instance, not <code>null</code>.
* @return a string representation of {@link Color} suitable for web pages.
*/
private static String getColorWebString(final Color color) {
String colorString = "#" + Integer.toHexString(color.getRed());
colorString += Integer.toHexString(color.getGreen());
colorString += Integer.toHexString(color.getBlue());
return colorString;
}
/**
* Creates {@link Label} if {@link Browser} can not be used.
*/
private static Control createTooltipControl_Label(Composite parent, String html) {
// prepare Label
final Label label = new Label(parent, SWT.WRAP);
label.setText(html);
// set size
int requiredWidth = label.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
GridDataFactory.create(label).hintHC(50).hintHMin(requiredWidth);
// copy colors
label.setForeground(parent.getForeground());
label.setBackground(parent.getBackground());
// done
parent.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
Shell shell = label.getShell();
shell.setVisible(true);
}
});
return label;
}
private static void hideTooltip(Control tooltip) {
tooltip.getShell().dispose();
}
// Copied from CodeUtils.java: CodeUtils.getSource()
/**
* @return the source as single {@link String}, lines joined using "\n".
*/
public static String getSource(String... lines) {
return Joiner.on('\n').join(lines);
}
}