• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright (c) 2011 Google, Inc.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *    Google, Inc. - initial API and implementation
10  *******************************************************************************/
11 package org.eclipse.wb.internal.core.model.property.table;
12 
13 import com.google.common.base.Charsets;
14 import com.google.common.base.Joiner;
15 
16 import org.eclipse.swt.SWT;
17 import org.eclipse.swt.browser.Browser;
18 import org.eclipse.swt.browser.LocationAdapter;
19 import org.eclipse.swt.browser.LocationEvent;
20 import org.eclipse.swt.browser.ProgressAdapter;
21 import org.eclipse.swt.browser.ProgressEvent;
22 import org.eclipse.swt.graphics.Color;
23 import org.eclipse.swt.graphics.Point;
24 import org.eclipse.swt.widgets.Composite;
25 import org.eclipse.swt.widgets.Control;
26 import org.eclipse.swt.widgets.Event;
27 import org.eclipse.swt.widgets.Label;
28 import org.eclipse.swt.widgets.Listener;
29 import org.eclipse.swt.widgets.Shell;
30 import org.eclipse.ui.PlatformUI;
31 import org.eclipse.ui.browser.IWebBrowser;
32 import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
33 import org.eclipse.wb.draw2d.IColorConstants;
34 import org.eclipse.wb.internal.core.DesignerPlugin;
35 import org.eclipse.wb.internal.core.EnvironmentUtils;
36 import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;
37 import org.eclipse.wb.internal.core.utils.ui.GridDataFactory;
38 import org.eclipse.wb.internal.core.utils.ui.PixelConverter;
39 
40 import java.io.StringReader;
41 import java.net.URL;
42 import java.text.MessageFormat;
43 
44 /**
45  * Helper for displaying HTML tooltips.
46  *
47  * @author scheglov_ke
48  * @coverage core.model.property.table
49  */
50 public final class HtmlTooltipHelper {
createTooltipControl(Composite parent, String header, String details)51   public static Control createTooltipControl(Composite parent, String header, String details) {
52     return createTooltipControl(parent, header, details, 0);
53   }
54 
createTooltipControl(Composite parent, String header, String details, int heightLimit)55   public static Control createTooltipControl(Composite parent,
56       String header,
57       String details,
58       int heightLimit) {
59     // prepare Control
60     Control control;
61     try {
62       String html = "<table cellspacing=2 cellpadding=0 border=0 margins=0 id=_wbp_tooltip_body>";
63       if (header != null) {
64         html += "<tr align=center><td><b>" + header + "</b></td></tr>";
65       }
66       html += "<tr><td align=justify>" + details + "</td></tr>";
67       html += "</table>";
68       control = createTooltipControl_Browser(parent, html, heightLimit);
69     } catch (Throwable e) {
70       control = createTooltipControl_Label(parent, details);
71     }
72     // set listeners
73     {
74       Listener listener = new Listener() {
75         @Override
76         public void handleEvent(Event event) {
77           Control tooltipControl = (Control) event.widget;
78           hideTooltip(tooltipControl);
79         }
80       };
81       control.addListener(SWT.MouseExit, listener);
82     }
83     // done
84     return control;
85   }
86 
87   /**
88    * Creates {@link Browser} for displaying tooltip.
89    */
createTooltipControl_Browser(Composite parent, String html, final int heightLimitChars)90   private static Control createTooltipControl_Browser(Composite parent,
91       String html,
92       final int heightLimitChars) {
93     // prepare styles
94     String styles;
95     try {
96         styles = DesignerPlugin.readFile(PropertyTable.class.getResourceAsStream("Tooltip.css"),
97                 Charsets.US_ASCII);
98         if (styles == null) {
99             styles = "";
100         }
101     } catch (Throwable e) {
102       styles = "";
103     }
104     // prepare HTML with styles and tags
105     String wrappedHtml;
106     {
107       String bodyAttributes =
108           MessageFormat.format(
109               "text=''{0}'' bgcolor=''{1}''",
110               getColorWebString(IColorConstants.tooltipForeground),
111               getColorWebString(IColorConstants.tooltipBackground));
112       String closeElement =
113           EnvironmentUtils.IS_LINUX
114               ? "    <a href='' style='position:absolute;right:1em;' id=_wbp_close>Close</a>"
115               : "";
116       wrappedHtml =
117           /*CodeUtils.*/getSource(
118               "<html>",
119               "  <style CHARSET='ISO-8859-1' TYPE='text/css'>",
120               styles,
121               "  </style>",
122               "  <body " + bodyAttributes + ">",
123               closeElement,
124               html,
125               "  </body>",
126               "</html>");
127     }
128     // prepare Browser
129     final Browser browser = new Browser(parent, SWT.NONE);
130     browser.setText(wrappedHtml);
131     // open URLs in new window
132     browser.addLocationListener(new LocationAdapter() {
133       @Override
134       public void changing(LocationEvent event) {
135         event.doit = false;
136         hideTooltip((Browser) event.widget);
137         if (!"about:blank".equals(event.location)) {
138           try {
139             IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport();
140             IWebBrowser browserSupport = support.createBrowser("wbp.browser");
141             browserSupport.openURL(new URL(event.location));
142           } catch (Throwable e) {
143             DesignerPlugin.log(e);
144           }
145         }
146       }
147     });
148     // set size
149     {
150       int textLength = getTextLength(html);
151       // horizontal hint
152       int hintH = 50;
153       if (textLength < 100) {
154         hintH = 40;
155       }
156       // vertical hint
157       int hintV = textLength / hintH + 3;
158       hintV = Math.min(hintV, 8);
159       // do set
160       GridDataFactory.create(browser).hintC(hintH, hintV);
161     }
162     // tweak size after rendering HTML
163     browser.addProgressListener(new ProgressAdapter() {
164       @Override
165       public void completed(ProgressEvent event) {
166         browser.removeProgressListener(this);
167         tweakBrowserSize(browser, heightLimitChars);
168         browser.getShell().setVisible(true);
169       }
170     });
171     // done
172     return browser;
173   }
174 
tweakBrowserSize(Browser browser, int heightLimitChars)175   private static void tweakBrowserSize(Browser browser, int heightLimitChars) {
176     GridDataFactory.create(browser).grab().fill();
177     // limit height
178     if (heightLimitChars != 0) {
179       PixelConverter pixelConverter = new PixelConverter(browser);
180       int maxHeight = pixelConverter.convertHeightInCharsToPixels(heightLimitChars);
181       expandShellToShowFullPage_Height(browser, maxHeight);
182     }
183     // if no limit, then show all, so make as tall as required
184     if (heightLimitChars == 0) {
185       expandShellToShowFullPage_Height(browser, Integer.MAX_VALUE);
186     }
187   }
188 
expandShellToShowFullPage_Height(Browser browser, int maxHeight)189   private static void expandShellToShowFullPage_Height(Browser browser, int maxHeight) {
190     try {
191       Shell shell = browser.getShell();
192       // calculate required
193       int contentHeight;
194       {
195         getContentOffsetHeight(browser);
196         contentHeight = getContentScrollHeight(browser);
197       }
198       // apply height
199       int useHeight = Math.min(contentHeight + ((EnvironmentUtils.IS_LINUX) ? 2 : 10), maxHeight);
200       shell.setSize(shell.getSize().x, useHeight);
201       // trim height to content
202       {
203         int offsetHeight = getBodyOffsetHeight(browser);
204         int scrollHeight = getBodyScrollHeight(browser);
205         int delta = scrollHeight - offsetHeight;
206         if (delta != 0 && delta < 10) {
207           Point size = shell.getSize();
208           shell.setSize(size.x, size.y + delta + 1);
209         }
210       }
211       // trim width to content
212       {
213         int offsetWidth = getContentOffsetWidth(browser);
214         {
215           Point size = shell.getSize();
216           shell.setSize(offsetWidth + ((EnvironmentUtils.IS_MAC) ? 6 : 10), size.y);
217         }
218       }
219       // hide 'Close' if too narrow
220       if (EnvironmentUtils.IS_LINUX) {
221         if (shell.getSize().y < 30) {
222           hideCloseElement(browser);
223         }
224       }
225     } catch (Throwable e) {
226     }
227   }
228 
getContentOffsetWidth(Browser browser)229   private static int getContentOffsetWidth(Browser browser) throws Exception {
230     return evaluateScriptInt(
231         browser,
232         "return document.getElementById('_wbp_tooltip_body').offsetWidth;");
233   }
234 
getContentOffsetHeight(Browser browser)235   private static int getContentOffsetHeight(Browser browser) throws Exception {
236     return evaluateScriptInt(
237         browser,
238         "return document.getElementById('_wbp_tooltip_body').offsetHeight;");
239   }
240 
getContentScrollHeight(Browser browser)241   private static int getContentScrollHeight(Browser browser) throws Exception {
242     return evaluateScriptInt(
243         browser,
244         "return document.getElementById('_wbp_tooltip_body').scrollHeight;");
245   }
246 
getBodyOffsetHeight(Browser browser)247   private static int getBodyOffsetHeight(Browser browser) throws Exception {
248     return evaluateScriptInt(browser, "return document.body.offsetHeight;");
249   }
250 
getBodyScrollHeight(Browser browser)251   private static int getBodyScrollHeight(Browser browser) throws Exception {
252     return evaluateScriptInt(browser, "return document.body.scrollHeight;");
253   }
254 
evaluateScriptInt(Browser browser, String script)255   private static int evaluateScriptInt(Browser browser, String script) throws Exception {
256     Object o = ReflectionUtils.invokeMethod(browser, "evaluate(java.lang.String)", script);
257     return ((Number) o).intValue();
258   }
259 
hideCloseElement(Browser browser)260   private static void hideCloseElement(Browser browser) throws Exception {
261     String script = "document.getElementById('_wbp_close').style.display = 'none'";
262     ReflectionUtils.invokeMethod(browser, "evaluate(java.lang.String)", script);
263   }
264 
265   /**
266    * @return the length of text in given HTML. Uses internal class, so may fail, in this case
267    *         returns length on HTML.
268    */
getTextLength(String html)269   private static int getTextLength(String html) {
270     StringReader htmlStringReader = new StringReader(html);
271     try {
272       ClassLoader classLoader = PropertyTable.class.getClassLoader();
273       Class<?> readerClass =
274           classLoader.loadClass("org.eclipse.jface.internal.text.html.HTML2TextReader");
275       Object reader = readerClass.getConstructors()[0].newInstance(htmlStringReader, null);
276       String text = (String) ReflectionUtils.invokeMethod(reader, "getString()");
277       return text.length();
278     } catch (Throwable e) {
279       return html.length();
280     }
281   }
282 
283   /**
284    * Returns a string representation of {@link Color} suitable for web pages.
285    *
286    * @param color
287    *          the {@link Color} instance, not <code>null</code>.
288    * @return a string representation of {@link Color} suitable for web pages.
289    */
getColorWebString(final Color color)290   private static String getColorWebString(final Color color) {
291     String colorString = "#" + Integer.toHexString(color.getRed());
292     colorString += Integer.toHexString(color.getGreen());
293     colorString += Integer.toHexString(color.getBlue());
294     return colorString;
295   }
296 
297   /**
298    * Creates {@link Label} if {@link Browser} can not be used.
299    */
createTooltipControl_Label(Composite parent, String html)300   private static Control createTooltipControl_Label(Composite parent, String html) {
301     // prepare Label
302     final Label label = new Label(parent, SWT.WRAP);
303     label.setText(html);
304     // set size
305     int requiredWidth = label.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
306     GridDataFactory.create(label).hintHC(50).hintHMin(requiredWidth);
307     // copy colors
308     label.setForeground(parent.getForeground());
309     label.setBackground(parent.getBackground());
310     // done
311     parent.getDisplay().asyncExec(new Runnable() {
312       @Override
313     public void run() {
314         Shell shell = label.getShell();
315         shell.setVisible(true);
316       }
317     });
318     return label;
319   }
320 
hideTooltip(Control tooltip)321   private static void hideTooltip(Control tooltip) {
322     tooltip.getShell().dispose();
323   }
324 
325   // Copied from CodeUtils.java: CodeUtils.getSource()
326   /**
327    * @return the source as single {@link String}, lines joined using "\n".
328    */
getSource(String... lines)329   public static String getSource(String... lines) {
330       return Joiner.on('\n').join(lines);
331   }
332 }
333