Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.openide.awt.SwingBrowserImpl Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.awt;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.EditorKit;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.CSS.Attribute;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Vector;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.JEditorPane;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.html.*;
/**
* Implementation of BrowserImpl in Swing.
*/
final class SwingBrowserImpl extends HtmlBrowser.Impl implements Runnable {
/** state of history management */
private static final int NO_NAVIGATION = 1;
private static final int NAVIGATION_BACK = 2;
private static final int NAVIGATION_FWD = 3;
private static final RequestProcessor rp = new RequestProcessor("Swing Browser"); //NOI18N
/** Current URL. */
private URL url;
/** URL loaded by JEditorPane */
private URL loadingURL;
private PropertyChangeSupport pcs;
private String statusMessage = ""; // NOI18N
private SwingBrowser swingBrowser;
private final JScrollPane scroll;
/** list of accessed URLs for back/fwd navigation */
private Vector historyList;
/** current position in history */
private int historyIndex;
/** navigation indication */
private int historyNavigating = NO_NAVIGATION;
private String title = null;
boolean fetchingTitle = false;
private static Logger LOG = Logger.getLogger(SwingBrowserImpl.class.getName());
SwingBrowserImpl() {
pcs = new PropertyChangeSupport(this);
swingBrowser = new SwingBrowser();
scroll = new JScrollPane(swingBrowser);
historyList = new Vector(5, 3);
historyIndex = -1;
swingBrowser.addPropertyChangeListener(
"page", // NOI18N
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() instanceof URL) {
URL old = SwingBrowserImpl.this.url;
SwingBrowserImpl.this.url = (URL) evt.getNewValue();
SwingBrowserImpl.this.pcs.firePropertyChange(PROP_URL, old, url);
if (((URL) evt.getNewValue()).equals(loadingURL)) {
loadingURL = null;
}
// update history
if (historyNavigating == NAVIGATION_BACK) {
int idx = historyList.lastIndexOf(evt.getNewValue(), historyIndex - 1);
if (idx != -1) {
historyIndex = idx;
}
} else if (historyNavigating == NAVIGATION_FWD) {
int idx = historyList.indexOf(evt.getNewValue(), historyIndex + 1);
if (idx != -1) {
historyIndex = idx;
}
} else {
while (historyList.size() > (historyIndex + 1))
historyList.remove(historyList.size() - 1);
historyList.add(evt.getNewValue());
historyIndex = historyList.size() - 1;
}
historyNavigating = NO_NAVIGATION;
pcs.firePropertyChange(PROP_BACKWARD, null, null);
pcs.firePropertyChange(PROP_FORWARD, null, null);
SwingUtilities.invokeLater(SwingBrowserImpl.this);
}
}
}
);
}
/**
* Returns visual component of html browser.
*
* @return visual component of html browser.
*/
public java.awt.Component getComponent() {
return scroll;
}
/**
* Reloads current html page.
*/
public void reloadDocument() {
synchronized (rp) {
try {
if ((url == null) || (loadingURL != null)) {
return;
}
Document doc = swingBrowser.getDocument();
loadingURL = url;
if (doc instanceof AbstractDocument) {
String protocol = url.getProtocol();
if ("ftp".equalsIgnoreCase(protocol) // NOI18N
||"http".equalsIgnoreCase(protocol) // NOI18N
) {
((AbstractDocument) doc).setAsynchronousLoadPriority(Thread.NORM_PRIORITY);
} else {
((AbstractDocument) doc).setAsynchronousLoadPriority(-1);
}
}
rp.post(this);
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
pcs.firePropertyChange(PROP_STATUS_MESSAGE, null, statusMessage = "" + e); // NOI18N
}
}
}
/**
* Stops loading of current html page.
*/
public void stopLoading() {
}
/**
* Sets current URL.
*
* @param url URL to show in the browser.
*/
public void setURL(URL url) {
synchronized (rp) {
try {
if (url == null) {
return;
}
loadingURL = url;
rp.post(this);
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
pcs.firePropertyChange(PROP_STATUS_MESSAGE, null, statusMessage = "" + e); // NOI18N
}
}
}
/**
* Returns current URL.
*
* @return current URL.
*/
public URL getURL() {
return url;
}
/**
* Returns status message representing status of html browser.
*
* @return status message.
*/
public String getStatusMessage() {
return statusMessage;
}
/** Returns title of the displayed page.
* @return title
*/
public String getTitle() {
if (title == null) {
Mutex.EVENT.readAccess(this);
}
return (title == null) ? NbBundle.getMessage(SwingBrowserImpl.class, "LBL_Loading") : title; //NOI18N
}
void updateTitle() {
assert SwingUtilities.isEventDispatchThread();
// System.err.println("Update title");
if (fetchingTitle) {
// System.err.println(" ...already updating");
return;
}
fetchingTitle = true;
String oldTitle = getTitle();
try {
Document d = swingBrowser.getDocument();
title = (String) d.getProperty(HTMLDocument.TitleProperty);
// System.err.println("Title from document is " + title);
if ((title == null) || (title.trim().length() == 0)) {
// System.err.println("No title from document, trying from url ");
URL u = getURL();
if (u != null) {
title = u.getFile();
if (title.length() == 0) {
title = NbBundle.getMessage(SwingBrowserImpl.class, "LBL_Untitled"); //NOI18N
} else {
//Trim any extraneous path info
int i = title.lastIndexOf("/"); //NOI18N
if ((i != -1) && (i != (title.length() - 1))) {
title = title.substring(i + 1);
}
}
// System.err.println("Using from url: " + title);
}
}
if (title != null) {
if (title.length() > 60) {
//Truncate to a reasonable tab length
title = NbBundle.getMessage(
SwingBrowserImpl.class, "LBL_Title", new Object[] { title.substring(0, 57) }
);
}
if (!oldTitle.equals(title)) {
// System.err.println("Firing prop change from " + oldTitle + " to " + title);
pcs.firePropertyChange(PROP_TITLE, oldTitle, title); //NOI18N
}
}
} finally {
fetchingTitle = false;
}
}
public void run() {
if (SwingUtilities.isEventDispatchThread()) {
title = null;
updateTitle();
} else {
URL requestedURL;
synchronized (rp) {
if ((this.url != null) && this.url.sameFile(url)) {
Document doc = swingBrowser.getDocument();
if (doc != null) {
//force reload
doc.putProperty(Document.StreamDescriptionProperty, null);
}
}
requestedURL = loadingURL;
loadingURL = null;
}
try {
swingBrowser.setPage(requestedURL);
setStatusText(null);
} catch (java.net.UnknownHostException uhe) {
setStatusText(
NbBundle.getMessage(SwingBrowserImpl.class, "FMT_UnknownHost", new Object[] { requestedURL })
); // NOI18N
} catch (java.net.NoRouteToHostException nrthe) {
setStatusText(
NbBundle.getMessage(SwingBrowserImpl.class, "FMT_NoRouteToHost", new Object[] { requestedURL })
); // NOI18N
} catch (IOException ioe) {
setStatusText(
NbBundle.getMessage(SwingBrowserImpl.class, "FMT_InvalidURL", new Object[] { requestedURL })
); // NOI18N
}
SwingUtilities.invokeLater(this);
}
}
/**
* Accessor to allow a message about bad urls to be displayed - see
* HtmlBrowser.setURL().
*/
void setStatusText(String s) {
pcs.firePropertyChange(PROP_STATUS_MESSAGE, null, statusMessage = s); // NOI18N
}
/** Is forward button enabled?
* @return true if it is
*/
public boolean isForward() {
return (historyIndex >= 0) && (historyIndex < (historyList.size() - 1)) &&
(historyNavigating == NO_NAVIGATION);
}
/** Moves the browser forward. Failure is ignored.
*/
public void forward() {
if (isForward()) {
historyNavigating = NAVIGATION_FWD;
setURL((URL) historyList.elementAt(historyIndex + 1));
}
}
/** Is backward button enabled?
* @return true if it is
*/
public boolean isBackward() {
return (historyIndex > 0) && (historyIndex < historyList.size()) && (historyNavigating == NO_NAVIGATION);
}
/** Moves the browser forward. Failure is ignored.
*/
public void backward() {
if (isBackward()) {
historyNavigating = NAVIGATION_BACK;
setURL((URL) historyList.elementAt(historyIndex - 1));
}
}
/** Is history button enabled?
* @return true if it is
*/
public boolean isHistory() {
return false;
}
/** Invoked when the history button is pressed.
*/
public void showHistory() {
}
/**
* Adds PropertyChangeListener to this browser.
*
* @param l Listener to add.
*/
public void addPropertyChangeListener(PropertyChangeListener l) {
pcs.addPropertyChangeListener(l);
}
/**
* Removes PropertyChangeListener from this browser.
*
* @param l Listener to remove.
*/
public void removePropertyChangeListener(PropertyChangeListener l) {
pcs.removePropertyChangeListener(l);
}
// encoding support; copied from html/HtmlEditorSupport
private static String findEncodingFromURL(InputStream stream) {
try {
byte[] arr = new byte[4096];
int len = stream.read(arr, 0, arr.length);
String txt = new String(arr, 0, (len >= 0) ? len : 0).toUpperCase();
// encoding
return findEncoding(txt);
} catch (Exception x) {
x.printStackTrace();
}
return null;
}
/** Tries to guess the mime type from given input stream. Tries to find
* <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
* @param txt the string to search in (should be in upper case)
* @return the encoding or null if no has been found
*/
private static String findEncoding(String txt) {
int headLen = txt.indexOf(""); // NOI18N
if (headLen == -1) {
headLen = txt.length();
}
int content = txt.indexOf("CONTENT-TYPE"); // NOI18N
if ((content == -1) || (content > headLen)) {
return null;
}
int charset = txt.indexOf("CHARSET=", content); // NOI18N
if (charset == -1) {
return null;
}
int charend = txt.indexOf('"', charset);
int charend2 = txt.indexOf('\'', charset);
if ((charend == -1) && (charend2 == -1)) {
return null;
}
if (charend2 != -1) {
if ((charend == -1) || (charend > charend2)) {
charend = charend2;
}
}
return txt.substring(charset + "CHARSET=".length(), charend); // NOI18N
}
// innerclasses ..............................................................
private class SwingBrowser extends JEditorPane {
private boolean lastPaintException = false;
private SwingBrowser() {
setEditable(false);
addHyperlinkListener(
new HyperlinkListener() {
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
if (e instanceof HTMLFrameHyperlinkEvent) {
HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) e;
HTMLDocument doc = (HTMLDocument) getDocument();
URL old = getURL();
doc.processHTMLFrameHyperlinkEvent(evt);
pcs.firePropertyChange(PROP_URL, old, e.getURL());
} else {
try {
SwingBrowserImpl.this.setURL(e.getURL());
} catch (Exception ex) {
LOG.log(Level.WARNING, null, ex);
}
}
}
}
}
);
//when up/down arrow keys are pressed, ensure the whole browser content
//scrolls up/down instead of moving the caret position only
ActionMap actionMap = getActionMap();
actionMap.put(DefaultEditorKit.upAction, new ScrollAction(-1));
actionMap.put(DefaultEditorKit.downAction, new ScrollAction(1));
}
@Override
public EditorKit getEditorKitForContentType( String type ) {
if( "text/html".equals( type ) ) {
return new HTMLEditorKit() {
@Override
public Document createDefaultDocument() {
StyleSheet styles = getStyleSheet();
//#200472 - hack to make JDK 1.7 javadoc readable
StyleSheet ss = new FilteredStyleSheet();
ss.addStyleSheet(styles);
HTMLDocument doc = new HTMLDocument(ss);
doc.setParser(getParser());
doc.setAsynchronousLoadPriority(4);
doc.setTokenThreshold(100);
return doc;
}
};
}
return super.getEditorKitForContentType( type );
}
/**
* Fetches a stream for the given URL, which is about to
* be loaded by the setPage
method.
* This method is expected to have the the side effect of
* establishing the content type, and therefore setting the
* appropriate EditorKit
to use for loading the stream.
*
* If debugger is not running returns super implementation.
*
* If debugger runs it will set content type to text/html.
* Forwarding is not supported is that case.
*
Control using sysprop org.openide.awt.SwingBrowserImpl.do-not-block-awt=true.
*
* @param page the URL of the page
*/
protected @Override InputStream getStream(URL page) throws IOException {
SwingUtilities.invokeLater(SwingBrowserImpl.this);
try {
// #53207: pre-read encoding from loaded URL
String charset = findEncodingFromURL(page.openStream());
LOG.log(Level.FINE, "Url " + page + " has charset " + charset); // NOI18N
if (charset != null) {
putClientProperty("charset", charset);
}
} catch( IllegalArgumentException iaE ) {
//#165266 - empty url
MalformedURLException e = new MalformedURLException();
e.initCause(iaE);
throw e;
}
// XXX debugger ought to set this temporarily
if (Boolean.getBoolean("org.openide.awt.SwingBrowserImpl.do-not-block-awt")) {
// try to set contentType quickly and return (don't block AWT Thread)
setContentType("text/html"); // NOI18N
return new FilteredInputStream(page.openConnection(), SwingBrowserImpl.this);
} else {
return super.getStream(page);
}
}
public @Override Dimension getPreferredSize() {
try {
return super.getPreferredSize();
} catch (RuntimeException e) {
//Bug in javax.swing.text.html.BlockView
return new Dimension(400, 600);
}
}
public @Override void paint(Graphics g) {
try {
super.paint(g);
lastPaintException = false;
} catch (RuntimeException e) {
//Bug in javax.swing.text.html.BlockView
//do nothing
if (!lastPaintException) {
repaint();
}
lastPaintException = true;
}
}
@Override
public void scrollToReference(String reference) {
if( !isShowing() || null == getParent() || getWidth() < 1 || getHeight() < 1 )
return;
super.scrollToReference(reference);
}
@Override
@Deprecated
public void layout() {
try {
super.layout();
} catch( ArrayIndexOutOfBoundsException aioobE ) {
//HACK - workaround for issue #168988
StackTraceElement[] stack = aioobE.getStackTrace();
if( stack.length > 0 && stack[0].getClassName().endsWith("BoxView") ) { //NOI18N
Logger.getLogger(SwingBrowser.class.getName()).log(Level.INFO, null, aioobE);
} else {
throw aioobE;
}
}
}
/**
* An action to scroll the browser content up or down.
*/
private class ScrollAction extends AbstractAction {
int direction;
public ScrollAction(int direction) {
this.direction = direction;
}
public void actionPerformed(java.awt.event.ActionEvent e) {
Rectangle r = getVisibleRect();
int increment = getScrollableUnitIncrement(r, SwingConstants.VERTICAL, direction);
r.y += (increment * direction);
scrollRectToVisible(r);
}
}
}
/**
* FilterInputStream that delays opening of stream.
* The purpose is not to initialize the stream when it is created in getStream()
* but to do it later when the content is asynchronously loaded in separate thread.
*/
private static class FilteredInputStream extends FilterInputStream {
private final URLConnection conn;
private final SwingBrowserImpl browser;
FilteredInputStream(URLConnection conn, SwingBrowserImpl browser) {
super((FilterInputStream) null);
this.conn = conn;
this.browser = browser;
}
private synchronized void openStream() throws IOException {
if (in == null) {
in = conn.getInputStream();
}
}
public @Override int available() throws IOException {
openStream();
return super.available();
}
public @Override long skip(long n) throws IOException {
openStream();
return super.skip(n);
}
public @Override void reset() throws IOException {
openStream();
super.reset();
}
public @Override void close() throws IOException {
openStream();
super.close();
Mutex.EVENT.readAccess(browser);
}
public @Override int read(byte[] b) throws IOException {
openStream();
return super.read(b);
}
public @Override int read(byte[] b, int off, int len) throws IOException {
openStream();
return super.read(b, off, len);
}
public @Override int read() throws IOException {
openStream();
return super.read();
}
}
private static class FilteredStyleSheet extends StyleSheet {
@Override
public void addCSSAttribute( MutableAttributeSet attr, Attribute key, String value ) {
value = fixFontSize( key, value );
super.addCSSAttribute( attr, key, value );
}
@Override
public boolean addCSSAttributeFromHTML( MutableAttributeSet attr, Attribute key, String value ) {
value = fixFontSize( key, value );
return super.addCSSAttributeFromHTML( attr, key, value );
}
/**
* CSS with e.g. 'font-size: 75%' makes the HTML unreadable in the default JEditorPane
* @param key
* @param value
* @return
*/
private static String fixFontSize( Attribute key, String value ) {
if( "font-size".equals( key.toString() ) && null != value && value.endsWith( "%" ) ) {
String strPercentage = value.replace( "%", "");
int percentage = Integer.parseInt( strPercentage );
if( percentage < 100 ) {
value = "100%";
}
}
return value;
}
}
}