diff options
Diffstat (limited to 'propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java')
-rw-r--r-- | propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java new file mode 100644 index 0000000..5e05549 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java @@ -0,0 +1,332 @@ +/******************************************************************************* + * 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); + } +} |