/******************************************************************************* * Copyright (c) 2009, 2010 Tasktop Technologies and others. * 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: * Tasktop Technologies - initial API and implementation * Yatta Solutions - bug 413871: performance *******************************************************************************/ package org.eclipse.epp.internal.mpc.ui.wizards; import java.net.URL; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.Platform; import org.eclipse.epp.internal.mpc.core.util.TextUtil; import org.eclipse.epp.internal.mpc.ui.MarketplaceClientUi; import org.eclipse.epp.internal.mpc.ui.catalog.MarketplaceCatalogSource; import org.eclipse.epp.internal.mpc.ui.catalog.MarketplaceDiscoveryStrategy; import org.eclipse.epp.internal.mpc.ui.catalog.ResourceProvider; import org.eclipse.epp.internal.mpc.ui.catalog.ResourceProvider.ResourceReceiver; import org.eclipse.epp.internal.mpc.ui.util.Util; import org.eclipse.equinox.internal.p2.discovery.model.Overview; import org.eclipse.equinox.internal.p2.ui.discovery.util.WorkbenchUtil; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.window.ToolTip; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.LocationEvent; import org.eclipse.swt.browser.LocationListener; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.browser.IWorkbenchBrowserSupport; /** * @author David Green * @author Carsten Reckord */ class OverviewToolTip extends ToolTip { final int SCREENSHOT_HEIGHT = 240; final int SCREENSHOT_WIDTH = 320; private final Overview overview; private final MarketplaceCatalogSource source; private final Control parent; private final Image leftImage; private final IMarketplaceWebBrowser browser; public OverviewToolTip(Control control, IMarketplaceWebBrowser browser, MarketplaceCatalogSource source, Overview overview, Image leftImage) { super(control, ToolTip.RECREATE, true); Assert.isNotNull(source); Assert.isNotNull(overview); this.parent = control; this.source = source; this.overview = overview; this.leftImage = leftImage; this.browser = browser; setHideOnMouseDown(false); // required for links to work } @Override protected Composite createToolTipContentArea(Event event, final Composite parent) { Shell shell = parent.getShell(); setData(Shell.class.getName(), shell); DiscoveryItem.setWidgetId(shell, DiscoveryItem.WIDGET_ID_OVERVIEW); GridLayoutFactory.fillDefaults().applyTo(parent); Color backgroundColor = parent.getDisplay().getSystemColor(SWT.COLOR_WHITE); final Composite container = new Composite(parent, SWT.NULL); container.setBackground(backgroundColor); boolean hasImage = false; if (overview.getScreenshot() != null) { hasImage = true; } final boolean hasLearnMoreLink = overview.getUrl() != null && overview.getUrl().length() > 0; final int borderWidth = 1; final int heightHint = SCREENSHOT_HEIGHT + (borderWidth * 2); final int widthHint = SCREENSHOT_WIDTH; final int containerWidthHintWithImage = 650; final int containerWidthHintWithoutImage = 500; GridDataFactory.fillDefaults().grab(true, true).hint( hasImage ? containerWidthHintWithImage : containerWidthHintWithoutImage, SWT.DEFAULT) .applyTo( container); GridLayoutFactory.fillDefaults().numColumns((leftImage != null) ? 3 : 2).margins(5, 5).spacing(3, 0).applyTo( container); if (leftImage != null) { Label imageLabel = new Label(container, SWT.NONE); imageLabel.setImage(leftImage); imageLabel.setBackground(backgroundColor); int imageWidthHint = leftImage.getBounds().width + 5; GridDataFactory.fillDefaults() .align(SWT.BEGINNING, SWT.BEGINNING) .hint(imageWidthHint, SWT.DEFAULT) .applyTo(imageLabel); } String summary = overview.getSummary(); Composite summaryContainer = new Composite(container, SWT.NULL); summaryContainer.setBackground(backgroundColor); GridLayoutFactory.fillDefaults().applyTo(summaryContainer); GridDataFactory gridDataFactory = GridDataFactory.fillDefaults() .grab(true, true) .span(hasImage ? 1 : 2, 1); if (hasImage) { gridDataFactory.hint(widthHint, heightHint); } gridDataFactory.applyTo(summaryContainer); Browser summaryLabel = new Browser(summaryContainer, SWT.NULL); Font dialogFont = JFaceResources.getDialogFont(); FontData[] fontData = dialogFont.getFontData(); String attr = ""; //$NON-NLS-1$ String fontSizeUnitOfMeasure = "pt"; //$NON-NLS-1$ if (Platform.OS_MACOSX.equals(Platform.getOS())) { fontSizeUnitOfMeasure = "px"; //$NON-NLS-1$ } else if (Platform.OS_WIN32.equals(Platform.getOS())) { attr = "overflow: auto; "; //$NON-NLS-1$ } String cssStyle = "body, p, div, * {" + attr + "font-family:\"" + fontData[0].getName() //$NON-NLS-1$ //$NON-NLS-2$ + "\",Arial,sans-serif !important;font-size:" + fontData[0].getHeight() + fontSizeUnitOfMeasure + " !important;" //$NON-NLS-1$ //$NON-NLS-2$ + "} body { margin: 0px; background-color: white;}"; //$NON-NLS-1$ summaryLabel.setFont(dialogFont); String html = "<html><style>" + cssStyle + "</style><body>" + TextUtil.cleanInformalHtmlMarkup(summary) //$NON-NLS-1$//$NON-NLS-2$ + "</body></html>"; //$NON-NLS-1$ summaryLabel.setText(html); summaryLabel.setBackground(backgroundColor); // instead of opening links in the tooltip, open a new browser window summaryLabel.addLocationListener(new LocationListener() { public void changing(LocationEvent event) { if (event.location.equals("about:blank")) { //$NON-NLS-1$ return; } event.doit = false; OverviewToolTip.this.hide(); WorkbenchUtil.openUrl(event.location, IWorkbenchBrowserSupport.AS_EXTERNAL); } public void changed(LocationEvent event) { } }); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).hint(SWT.DEFAULT, hasImage ? SWT.DEFAULT : SCREENSHOT_HEIGHT) .applyTo(summaryLabel); if (hasImage) { final Composite imageContainer = new Composite(container, SWT.BORDER); GridLayoutFactory.fillDefaults().applyTo(imageContainer); GridDataFactory.fillDefaults().grab(false, false).align(SWT.CENTER, SWT.BEGINNING).hint( widthHint + (borderWidth * 2), heightHint).applyTo(imageContainer); Label imageLabel = new Label(imageContainer, SWT.NULL); GridDataFactory.fillDefaults().hint(widthHint, SCREENSHOT_HEIGHT).indent(borderWidth, borderWidth).applyTo( imageLabel); imageLabel.setBackground(backgroundColor); imageLabel.setSize(widthHint, SCREENSHOT_HEIGHT); provideImage(imageLabel, source, overview.getScreenshot()); final Cursor handCursor = new Cursor(imageLabel.getDisplay(), SWT.CURSOR_HAND); imageLabel.setCursor(handCursor); imageLabel.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { handCursor.dispose(); } }); imageLabel.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { OverviewToolTip.this.hide(); WorkbenchUtil.openUrl(overview.getScreenshot(), IWorkbenchBrowserSupport.AS_EXTERNAL); } }); // creates a border imageContainer.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); } if (hasLearnMoreLink) { Link link = new Link(summaryContainer, SWT.NULL); DiscoveryItem.setWidgetId(link, DiscoveryItem.WIDGET_ID_LEARNMORE); GridDataFactory.fillDefaults().grab(false, false).align(SWT.BEGINNING, SWT.CENTER).applyTo(link); link.setText(Messages.OverviewToolTip_learnMoreLink); link.setBackground(backgroundColor); link.setToolTipText(NLS.bind(Messages.OverviewToolTip_openUrlInBrowser, overview.getUrl())); link.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { OverviewToolTip.this.hide(); browser.openUrl(overview.getUrl()); } public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } }); } if (!hasImage) { // prevent overviews with no image from providing unlimited text. Point optimalSize = summaryContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); if (optimalSize.y > (heightHint + 10)) { ((GridData) summaryContainer.getLayoutData()).heightHint = heightHint; container.layout(true); } } // hack: cause the tooltip to gain focus so that we can capture the escape key // this must be done async since the tooltip is not yet visible. Display.getCurrent().asyncExec(new Runnable() { public void run() { if (!parent.isDisposed()) { parent.setFocus(); } } }); return container; } @Override protected void afterHideToolTip(Event event) { setData(Shell.class.getName(), null); } private void provideImage(final Label imageLabel, MarketplaceCatalogSource discoverySource, final String imagePath) { ResourceProvider resourceProvider = discoverySource.getResourceProvider(); MarketplaceDiscoveryStrategy.cacheResource(resourceProvider, overview.getItem(), imagePath); resourceProvider.provideResource(new ResourceReceiver<ImageDescriptor>() { public ImageDescriptor processResource(URL resource) { return ImageDescriptor.createFromURL(resource); } public void setResource(final ImageDescriptor resource) { if (resource != null && imageLabel != null && !imageLabel.isDisposed()) { imageLabel.getDisplay().asyncExec(new Runnable() { public void run() { if (!imageLabel.isDisposed()) { try { Image image = resource.createImage(); if (image != null) { Rectangle imageBounds = image.getBounds(); if (imageBounds.width > SCREENSHOT_WIDTH || imageBounds.height > SCREENSHOT_HEIGHT) { final Image scaledImage = Util.scaleImage(image, SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT); Image originalImage = image; image = scaledImage; originalImage.dispose(); } final Image fimage = image; imageLabel.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { fimage.dispose(); } }); imageLabel.setImage(image); } } catch (SWTException e) { // ignore, probably a bad image format MarketplaceClientUi .error(NLS.bind(Messages.OverviewToolTip_cannotRenderImage_reason, imagePath, e.getMessage()), e); } } } }); } } }, imagePath, null); } public void show(Control titleControl) { Point titleAbsLocation = titleControl.getParent().toDisplay(titleControl.getLocation()); Point containerAbsLocation = parent.getParent().toDisplay(parent.getLocation()); Rectangle bounds = titleControl.getBounds(); int relativeX = titleAbsLocation.x - containerAbsLocation.x; int relativeY = titleAbsLocation.y - containerAbsLocation.y; if (org.eclipse.jface.util.Util.isGtk()) { //GTK sends MOUSE_EXIT on entering the tooltip shell, closing it (bug 423908) //Workaround: open tooltip under cursor GC gc = new GC(titleControl); try { gc.setFont(titleControl.getFont()); int height = gc.getFontMetrics().getHeight(); relativeY += bounds.height - height; } finally { gc.dispose(); } } else { relativeY += bounds.height + 3; } show(new Point(relativeX, relativeY)); } }