blob: 32939cb781055072406db4601da746f83b3ddfa8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.help.ui.internal.browser.embedded;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IProduct;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.help.internal.base.BaseHelpSystem;
import org.eclipse.help.internal.base.HelpApplication;
import org.eclipse.help.internal.util.ProductPreferences;
import org.eclipse.help.ui.internal.HelpUIPlugin;
import org.eclipse.help.ui.internal.Messages;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.osgi.util.NLS;
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.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.browser.VisibilityWindowListener;
import org.eclipse.swt.browser.WindowEvent;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
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.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.osgi.framework.Bundle;
/**
* Help browser employing SWT Browser widget
*/
public class EmbeddedBrowser {
private static final String BROWSER_X = "browser.x"; //$NON-NLS-1$
private static final String BROWSER_Y = "browser.y"; //$NON-NLS-1$
private static final String BROWSER_WIDTH = "browser.w"; //$NON-NLS-1$
private static final String BROWSER_HEIGTH = "browser.h"; //$NON-NLS-1$
private static final String BROWSER_MAXIMIZED = "browser.maximized"; //$NON-NLS-1$
private static String initialTitle = getWindowTitle();
private Shell shell;
private Browser browser;
private Composite statusBar;
private Label statusBarText;
private Label statusBarSeparator;
private ProgressBar statusBarProgress;
private String statusText;
private int x, y, w, h;
private long modalRequestTime = 0;
private Vector<IBrowserCloseListener> closeListeners = new Vector<>(1);
/**
* Constructor for main help window instance
*/
public EmbeddedBrowser() {
int style = SWT.SHELL_TRIM;
if (ProductPreferences.isRTL())
style |= SWT.RIGHT_TO_LEFT;
else
style |= SWT.LEFT_TO_RIGHT;
shell = new Shell(style);
initializeShell(shell);
shell.addControlListener(new ControlListener() {
@Override
public void controlMoved(ControlEvent e) {
if (!shell.getMaximized()) {
Point location = shell.getLocation();
x = location.x;
y = location.y;
}
}
@Override
public void controlResized(ControlEvent e) {
if (!shell.getMaximized()) {
Point size = shell.getSize();
w = size.x;
h = size.y;
}
}
});
shell.addDisposeListener(e -> {
// save position
IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(HelpUIPlugin.PLUGIN_ID);
prefs.putInt(BROWSER_X, x);
prefs.putInt(BROWSER_Y, y);
prefs.putInt(BROWSER_WIDTH, w);
prefs.putInt(BROWSER_HEIGTH, h);
prefs.putBoolean(BROWSER_MAXIMIZED, (shell.getMaximized()));
notifyCloseListners();
if (HelpApplication.isShutdownOnClose()) {
HelpApplication.stopHelp();
}
});
browser = new Browser(shell, SWT.NONE);
browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
initialize(browser);
createStatusBar(shell);
initializeStatusBar(browser);
// use saved location and size
x = Platform.getPreferencesService().getInt(HelpUIPlugin.PLUGIN_ID, BROWSER_X, 0, null);
y = Platform.getPreferencesService().getInt(HelpUIPlugin.PLUGIN_ID, BROWSER_Y, 0, null);
w = Platform.getPreferencesService().getInt(HelpUIPlugin.PLUGIN_ID, BROWSER_WIDTH, 0, null);
h = Platform.getPreferencesService().getInt(HelpUIPlugin.PLUGIN_ID, BROWSER_HEIGTH, 0, null);
if (w == 0 || h == 0) {
// first launch, use default size
w = 1024;
h = 768;
x = shell.getLocation().x;
y = shell.getLocation().y;
}
setSafeBounds(shell, x, y, w, h);
if (Platform.getPreferencesService().getBoolean(HelpUIPlugin.PLUGIN_ID, BROWSER_MAXIMIZED, false, null))
shell.setMaximized(true);
shell.addControlListener(new ControlListener() {
@Override
public void controlMoved(ControlEvent e) {
if (!shell.getMaximized()) {
Point location = shell.getLocation();
x = location.x;
y = location.y;
}
}
@Override
public void controlResized(ControlEvent e) {
if (!shell.getMaximized()) {
Point size = shell.getSize();
w = size.x;
h = size.y;
}
}
});
//
shell.open();
//browser.setUrl("about:blank");
browser.addLocationListener(new LocationAdapter() {
@Override
public void changing(LocationEvent e) {
// hack to know when help webapp needs modal window
modalRequestTime = 0;
if (e.location != null
&& e.location.startsWith("javascript://needModal")) { //$NON-NLS-1$
modalRequestTime = System.currentTimeMillis();
}
if (!e.doit && e.location != null
&& e.location.startsWith("https://")) { //$NON-NLS-1$
try {
BaseHelpSystem.getHelpBrowser(true).displayURL(
e.location);
} catch (Exception exc) {
}
}
}
});
}
/**
* Constructor for derived help window It is either secondary browser or a
* help dialog
*
* @param event
* @param parent
* Shell or null
*/
public EmbeddedBrowser(WindowEvent event, Shell parent) {
if (parent == null){
int style = SWT.SHELL_TRIM;
if (ProductPreferences.isRTL())
style |= SWT.RIGHT_TO_LEFT;
else
style |= SWT.LEFT_TO_RIGHT;
shell = new Shell(style);
} else
shell = new Shell(parent, SWT.PRIMARY_MODAL | SWT.DIALOG_TRIM | SWT.RESIZE);
initializeShell(shell);
Browser browser = new Browser(shell, SWT.NONE);
browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
initialize(browser);
event.browser = browser;
browser.addLocationListener(new LocationAdapter() {
@Override
public void changing(LocationEvent e) {
// hack to know when help webapp needs modal window
modalRequestTime = 0;
if (e.location != null
&& e.location.startsWith("javascript://needModal")) { //$NON-NLS-1$
modalRequestTime = System.currentTimeMillis();
}
}
});
}
private static void initializeShell(Shell s) {
s.setText(initialTitle);
final Image[] shellImages = createImages();
if (shellImages != null)
s.setImages(shellImages);
GridLayout layout = new GridLayout();
layout.marginWidth = 0;
layout.marginHeight = 0;
layout.verticalSpacing = 0;
layout.horizontalSpacing = 0;
s.setLayout(layout);
s.addDisposeListener(e -> {
if (shellImages != null) {
for (int i = 0; i < shellImages.length; i++) {
shellImages[i].dispose();
}
}
});
}
private void initialize(Browser browser) {
browser.addOpenWindowListener(event -> {
if (System.currentTimeMillis() - modalRequestTime <= 1000) {
new EmbeddedBrowser(event, shell);
} else if (event.required) {
new EmbeddedBrowser(event, null);
} else {
displayURLExternal(event);
}
});
browser.addVisibilityWindowListener(new VisibilityWindowListener() {
@Override
public void hide(WindowEvent event) {
Browser browser = (Browser) event.widget;
Shell shell = browser.getShell();
shell.setVisible(false);
}
@Override
public void show(WindowEvent event) {
Browser browser = (Browser) event.widget;
Shell shell = browser.getShell();
if (event.location != null)
shell.setLocation(event.location);
if (event.size != null) {
Point size = event.size;
shell.setSize(shell.computeSize(size.x, size.y));
}
shell.open();
}
});
browser.addCloseWindowListener(event -> {
Browser browser1 = (Browser) event.widget;
Shell shell = browser1.getShell();
shell.close();
});
browser.addTitleListener(event -> {
if (event.title != null && event.title.length() > 0) {
Browser browser1 = (Browser) event.widget;
Shell shell = browser1.getShell();
shell.setText(event.title);
}
});
browser.addLocationListener(new LocationAdapter() {
@Override
public void changing(LocationEvent e) {
if (!e.doit && e.location != null
&& e.location.startsWith("https://")) { //$NON-NLS-1$
try {
BaseHelpSystem.getHelpBrowser(true).displayURL(
e.location);
} catch (Exception exc) {
}
}
}
});
}
private void initializeStatusBar(Browser browser) {
browser.addStatusTextListener(event -> {
event.text = event.text.replaceAll("&", "&&"); //$NON-NLS-1$ //$NON-NLS-2$
if (!event.text.equals(statusText)) {
statusText = event.text;
statusBarText.setText(statusText);
}
});
browser.addProgressListener(new ProgressListener() {
@Override
public void changed(ProgressEvent event) {
if (event.total > 0) {
statusBarProgress.setMaximum(event.total);
statusBarProgress.setSelection(Math.min(event.current, event.total));
statusBarSeparator.setVisible(true);
statusBarProgress.setVisible(true);
}
}
@Override
public void completed(ProgressEvent event) {
statusBarSeparator.setVisible(false);
statusBarProgress.setVisible(false);
}
});
}
private void createStatusBar(Composite parent) {
statusBar = new Composite(parent, SWT.NONE);
statusBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
GridLayout layout = new GridLayout(3, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
layout.marginTop = 0;
layout.marginLeft = 5;
layout.marginRight = 5;
layout.marginBottom = 5;
statusBar.setLayout(layout);
statusBarText = new Label(statusBar, SWT.NONE);
statusBarText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
statusBarSeparator = new Label(statusBar, SWT.SEPARATOR | SWT.VERTICAL);
statusBarSeparator.setVisible(false);
statusBarProgress = new ProgressBar(statusBar, SWT.HORIZONTAL);
GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
data.widthHint = 100;
statusBarProgress.setLayoutData(data);
statusBarProgress.setVisible(false);
statusBarProgress.setMinimum(0);
/*
* Vertical separator labels are naturally too tall for the status bar.
* Size it to match the tallest of the text and progress bar.
*/
data = new GridData(SWT.FILL, SWT.CENTER, false, false);
data.heightHint = Math.max(statusBarText.computeSize(SWT.DEFAULT, SWT.DEFAULT).y, statusBarProgress.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
statusBarSeparator.setLayoutData(data);
}
public void displayUrl(String url) {
browser.setUrl(url);
shell.setMinimized(false);
shell.forceActive();
}
private void displayURLExternal(WindowEvent e) {
final Shell externalShell = new Shell(shell, SWT.NONE);
Browser externalBrowser = new Browser(externalShell, SWT.NONE);
externalBrowser.addLocationListener(new LocationAdapter() {
@Override
public void changing(final LocationEvent e) {
e.doit = false;
try {
BaseHelpSystem.getHelpBrowser(true).displayURL(e.location);
}
catch (Throwable t) {
String msg = "Error opening external Web browser"; //$NON-NLS-1$
HelpUIPlugin.logError(msg, t);
}
externalShell.getDisplay().asyncExec(() -> externalShell.dispose());
}
});
e.browser = externalBrowser;
}
public boolean isDisposed() {
return shell.isDisposed();
}
private static String getWindowTitle() {
if (Platform.getPreferencesService().getBoolean(HelpUIPlugin.PLUGIN_ID, "windowTitlePrefix", false, null)) { //$NON-NLS-1$
return NLS.bind(Messages.browserTitle, BaseHelpSystem
.getProductName());
}
return BaseHelpSystem.getProductName();
}
/**
* Create shell images
*/
private static Image[] createImages() {
String[] productImageURLs = getProductImageURLs();
if (productImageURLs != null) {
ArrayList<Image> shellImgs = new ArrayList<>();
for (int i = 0; i < productImageURLs.length; i++) {
if ("".equals(productImageURLs[i])) { //$NON-NLS-1$
continue;
}
URL imageURL = null;
try {
imageURL = new URL(productImageURLs[i]);
} catch (MalformedURLException mue) {
// must be a path relative to the product bundle
IProduct product = Platform.getProduct();
if (product != null) {
Bundle productBundle = product.getDefiningBundle();
if (productBundle != null) {
imageURL = FileLocator.find(productBundle, new Path(
productImageURLs[i]), null);
}
}
}
Image image = null;
if (imageURL != null) {
image = ImageDescriptor.createFromURL(imageURL)
.createImage();
}
if (image != null) {
shellImgs.add(image);
}
}
return shellImgs.toArray(new Image[shellImgs.size()]);
}
return new Image[0];
}
/**
* Obtains URLs to product image
*
* @return String[] with URLs as Strings or null
*/
private static String[] getProductImageURLs() {
IProduct product = Platform.getProduct();
if (product != null) {
String url = product.getProperty("windowImages"); //$NON-NLS-1$
if (url != null && url.length() > 0) {
return url.split(",\\s*"); //$NON-NLS-1$
}
url = product.getProperty("windowImage"); //$NON-NLS-1$
if (url != null && url.length() > 0) {
return new String[]{url};
}
}
return null;
}
/**
* Closes the browser.
*/
public void close() {
if (!shell.isDisposed())
shell.dispose();
}
private static void setSafeBounds(Shell s, int x, int y, int width,
int height) {
Rectangle clientArea = s.getDisplay().getClientArea();
width = Math.min(clientArea.width, width);
height = Math.min(clientArea.height, height);
x = Math.min(x + width, clientArea.x + clientArea.width) - width;
y = Math.min(y + height, clientArea.y + clientArea.height) - height;
x = Math.max(x, clientArea.x);
y = Math.max(y, clientArea.y);
s.setBounds(x, y, width, height);
}
public void setLocation(int x, int y) {
shell.setLocation(x, y);
}
public void setSize(int width, int height) {
shell.setSize(w, h);
}
private void notifyCloseListners() {
for (Iterator<IBrowserCloseListener> it = closeListeners.iterator(); it.hasNext();) {
IBrowserCloseListener listener = it.next();
listener.browserClosed();
}
}
public void addCloseListener(IBrowserCloseListener listener) {
if (!closeListeners.contains(listener)) {
closeListeners.add(listener);
}
}
public void removeCloseListener(IBrowserCloseListener listener) {
closeListeners.remove(listener);
}
}