package jadex.commons.gui; import jadex.commons.BrowserLauncher2; import jadex.commons.SUtil; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.JPopupMenu; import javax.swing.JTextPane; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.Element; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import javax.swing.text.View; import javax.swing.text.ViewFactory; import javax.swing.text.html.HTML; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.ImageView; /** * A text pane that is able to follow hyperlinks when clicked. * Also supports an "open in external Browser" option. */ public class BrowserPane extends JTextPane { //-------- attributes -------- /** The current URL under the mouse cursor. */ protected URL url; /** The current URL under the mouse cursor when local reference (i.e. #ref). */ protected String reference; // /** The current URL (as frame event, if available). */ // protected HTMLFrameHyperlinkEvent frameevent; /** Indicates that the last mouse click included a popup trigger. */ protected boolean popup; /** The url history. */ // protected List history; /** The external references. */ protected Map externals; /** Open links per click in external browser. */ protected boolean inbrowser; //-------- constructor -------- /** * Create a new BrowserPane. */ public BrowserPane() { // this.history = new ArrayList(); setContentType("text/html"); setEditable(false); setEditorKit(new ClasspathHTMLEditorKit()); // Listen for hyperlink events, to remember current url. // todo: Problem: does only work when setEditable(false) was called!!! this.addHyperlinkListener(new HyperlinkListener() { public void hyperlinkUpdate(HyperlinkEvent e) { if(e.getEventType()==HyperlinkEvent.EventType.ENTERED) { BrowserPane.this.url = e.getURL(); if(url==null && e.getDescription()!=null) { if(e.getDescription().startsWith("#")) { reference = e.getDescription().substring(1); } else { // Try to create relative file url. File dir = new File("."); try { url = new URL("file:///"+dir.getAbsolutePath()+"/"+e.getDescription()); } catch(MalformedURLException ex) { System.out.println("url: "+"file:///"+dir.getAbsolutePath()+"/"+e.getDescription()+", "+ex); } } } // else // { // System.out.println(url); // System.out.println("source="+e.getSourceElement()); // for(Enumeration enum=e.getSourceElement().getAttributes().getAttributeNames(); enum.hasMoreElements(); ) // { // Object attr = enum.nextElement(); // System.out.println(attr+" = "+e.getSourceElement().getAttributes().getAttribute(attr)); // } // } // if(e instanceof HTMLFrameHyperlinkEvent) // { // BrowserPane.this.frameevent = (HTMLFrameHyperlinkEvent)e; // HTMLDocument doc = (HTMLDocument)BrowserPane.this.getDocument(); // doc.processHTMLFrameHyperlinkEvent(frameevent); // } } else if(e.getEventType()==HyperlinkEvent.EventType.EXITED) { BrowserPane.this.url = null; BrowserPane.this.reference = null; // BrowserPane.this.frameevent = null; } } }); // Listen for mouseclicks and open last url. this.addMouseListener(new MouseAdapter() { // When popup trigger, open url in external browser. public void mousePressed(MouseEvent e) { if(popup=e.isPopupTrigger()) { doPopup(e.getPoint()); } } // When popup trigger, open url in external browser. public void mouseReleased(MouseEvent e) { if(popup=e.isPopupTrigger()) { doPopup(e.getPoint()); } } // When not popup trigger, open url in same window. public void mouseClicked(MouseEvent e) { //System.out.println("ha"); if(!popup && url!=null) { //System.out.println("opening: "+url); if(inbrowser) openExternal(url); else setPage(url); url = null; } if(!popup && reference!=null) { //System.out.println("referencing: "+reference); if(externals!=null && externals.containsKey(reference)) { setText((String)externals.get(reference)); } else { scrollToReference(reference); } reference = null; } // else if(!popup && frameevent!=null) // { // HTMLDocument doc = (HTMLDocument)BrowserPane.this.getDocument(); // doc.processHTMLFrameHyperlinkEvent(frameevent); // } } }); } //-------- methods -------- /* * * Show a document in this pane. * / // Hack!!! Do not override, internally used to often public void setDocument(Document doc) { System.out.println("setDocument"); super.setDocument(doc); if(history!=null) // Hack!!! setDocument called from JTextPane constructor. history.add(doc); }*/ /** * Show a document in this pane. */ public void setStyledDocument(StyledDocument doc) { super.setStyledDocument(doc); // history.add(doc); // System.out.println("SD: "+doc); if(doc instanceof HTMLDocument) { ((HTMLDocument)doc).setBase(null); } } /** * Open an url in this pane. */ public void setPage(URL url) { try { // Hack??? Allow relative file urls to be loaded setDocument(getEditorKit().createDefaultDocument()); super.setPage(url); // history.add(url); } catch(IOException ex) { BrowserPane.this.setText("Could not open page: "+ex); } } /** * Show some text in this pane. */ public void setText(String text) { setDocument(getEditorKit().createDefaultDocument()); super.setText(text); // history.add(text); if(getDocument() instanceof HTMLDocument) { ((HTMLDocument)getDocument()).setBase(null); //System.out.println("Base is now: "+((HTMLDocument)getDocument()).getBase()); } } /** * Set the externals of this BrowserPane. * @param externals The externals to set. */ public void setExternals(Map externals) { this.externals = externals; } /** * Set the default open mode for a link click. * @param inbrowser True, for opening clicked links in external browser. */ public void setDefaultOpenMode(boolean inbrowser) { this.inbrowser = inbrowser; } //-------- helper methods -------- /** * React on popup trigger. */ protected void doPopup(Point p) { JPopupMenu menu = new JPopupMenu(); // // Back action // if(history.size()>1) // { // menu.add(new AbstractAction("Back") // { // public void actionPerformed(ActionEvent e) // { // // Remove current page. // history.remove(history.size()-1); // // // Remove previous page (will be re-added to history). // Object prev = history.remove(history.size()-1); // if(prev instanceof String) // setText((String)prev); // else if(prev instanceof StyledDocument) // setStyledDocument((StyledDocument)prev); // else if(prev instanceof Document) // setDocument((Document)prev); // else if(prev instanceof URL) // setPage((URL)prev); // } // }); // } // Open... actions. if(url!=null) { final URL url = this.url; menu.add(new AbstractAction("Open") { public void actionPerformed(ActionEvent e) { setPage(url); } }); menu.add(new AbstractAction("Open link in external browser") { public void actionPerformed(ActionEvent e) { openExternal(url); } }); } // Open menu, when some actions are available. if(menu.getComponentCount()>0) menu.show(this, p.x, p.y); } /** * Open an url in external browser. */ protected void openExternal(URL url) { try { BrowserLauncher2.openURL(url.toString()); } catch(IOException e) { BrowserPane.this.setText("Could not start browser: "+e); } } //-------- helper classes -------- /** * An HTML editor kit for supporting images loaded from classpath. */ public static class ClasspathHTMLEditorKit extends HTMLEditorKit { /** The view factory. */ protected ViewFactory thefactory = new HTMLFactory() { public View create(Element elem) { View ret; Object attr = elem.getAttributes().getAttribute(StyleConstants.NameAttribute); if(attr==HTML.Tag.IMG) { ret = new ImageView(elem) { public URL getImageURL() { URL ret = super.getImageURL(); if(ret==null) { String src = (String)getElement().getAttributes().getAttribute(HTML.Attribute.SRC); if(src!=null) { ret = SUtil.class.getClassLoader().getResource(src.startsWith("/") ? src.substring(1) : src); } } return ret; } }; } else { ret = super.create(elem); } return ret; } }; /** * Return the vioew factory. */ public ViewFactory getViewFactory() { return thefactory; } } }