FileDocCategorySizeDatePackage
WebBrowser.javaAPI DocExample15037Sat Jan 24 10:44:34 GMT 2004je3.gui

WebBrowser.java

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.com/javaexamples3.
 */
package je3.gui;
import java.awt.*;                 // LayoutManager stuff
import javax.swing.*;              // Swing components
import java.awt.event.*;           // AWT event handlers
import javax.swing.event.*;        // Swing event handlers
import java.beans.*;               // JavaBeans event handlers
import java.io.*;                  // Input/output
import java.net.*;                 // Networking with URLs
import java.util.*;                // Hashtables and other utilities
// Import this class by name.  JFileChooser uses it, and its name conflicts
// with java.io.FileFilter
import javax.swing.filechooser.FileFilter;  

/**
 * This class implements a simple web browser using the HTML
 * display capabilities of the JEditorPane component.
 **/
public class WebBrowser extends JFrame
    implements HyperlinkListener, PropertyChangeListener
{
    /**
     * A simple main() method that allows the WebBrowser class to be used
     * as a stand-alone application.
     **/
    public static void main(String[] args) throws IOException {
	// End the program when there are no more open browser windows
	WebBrowser.setExitWhenLastWindowClosed(true);
	WebBrowser browser = new WebBrowser();  // Create a browser window
	browser.setSize(800, 600);              // Set its size
	browser.setVisible(true);               // Make it visible.

	// Tell the browser what to display.  This method is defined below.
	browser.displayPage((args.length > 0) ? args[0] : browser.getHome());
    }
    
    // This class uses GUIResourceBundle to create its menubar and toolbar
    // This static initializer performs one-time registration of the
    // required ResourceParser classes.
    static {
	GUIResourceBundle.registerResourceParser(new MenuBarParser());
	GUIResourceBundle.registerResourceParser(new MenuParser());
	GUIResourceBundle.registerResourceParser(new ActionParser());
	GUIResourceBundle.registerResourceParser(new CommandParser());
	GUIResourceBundle.registerResourceParser(new ToolBarParser());
    }

    // These are the Swing components that the browser uses
    JEditorPane textPane;      // Where the HTML is displayed
    JLabel messageLine;        // Displays one-line messages
    JTextField urlField;       // Displays and edits the current URL
    JFileChooser fileChooser;  // Allows the user to select a local file

    // These are Actions that are used in the menubar and toolbar.
    // We obtain explicit references to them from the GUIResourceBundle
    // so we can enable and disable them.
    Action backAction, forwardAction;

    // These fields are used to maintain the browsing history of the window
    java.util.List history = new ArrayList();  // The history list
    int currentHistoryPage = -1;               // Current location in it
    public static final int MAX_HISTORY = 50;  // Trim list when over this size

    // These static fields control the behavior of the close() action
    static int numBrowserWindows = 0;
    static boolean exitWhenLastWindowClosed = false;

    // This is where the "home()" method takes us.  See also setHome()
    String home = "http://www.davidflanagan.com";  // A default value

    /** Create and initialize a new WebBrowser window */
    public WebBrowser() {
	super("WebBrowser");              // Chain to JFrame constructor

	textPane = new JEditorPane();     // Create HTML window
	textPane.setEditable(false);      // Don't allow the user to edit it

	// Register action listeners.  The first is to handle hyperlinks.
	// The second is to receive property change notifications, which tell
	// us when a document is done loading.  This class implements these
	// EventListener interfaces, and the methods are defined below
	textPane.addHyperlinkListener(this); 
	textPane.addPropertyChangeListener(this);

	// Put the text pane in a JScrollPane in the center of the window
	this.getContentPane().add(new JScrollPane(textPane),
				  BorderLayout.CENTER);

	// Now create a message line and place it at the bottom of the window
	messageLine = new JLabel(" ");
	this.getContentPane().add(messageLine, BorderLayout.SOUTH);

	// Read the file WebBrowserResources.properties (and any localized
	// variants appropriate for the current Locale) to create a
	// GUIResourceBundle from which we'll get our menubar and toolbar.
	GUIResourceBundle resources =
	    new GUIResourceBundle(this,"je3.gui." +
				  "WebBrowserResources");

	// Read a menubar from the resource bundle and display it
	JMenuBar menubar = (JMenuBar) resources.getResource("menubar",
							    JMenuBar.class);
	this.setJMenuBar(menubar);

	// Read a toolbar from the resource bundle.  Don't display it yet.
	JToolBar toolbar = 
	    (JToolBar) resources.getResource("toolbar", JToolBar.class);

	// Create a text field that the user can enter a URL in.
	// Set up an action listener to respond to the ENTER key in that field
	urlField = new JTextField();
	urlField.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    displayPage(urlField.getText());
		}
	    });

	// Add the URL field and a label for it to the end of the toolbar
	toolbar.add(new JLabel("         URL:"));
	toolbar.add(urlField);

	// And add the toolbar to the top of the window
	this.getContentPane().add(toolbar, BorderLayout.NORTH);

	// Read cached copies of two Action objects from the resource bundle
	// These actions are used by the menubar and toolbar, and enabling and
	// disabling them enables and disables the menu and toolbar items.
	backAction = (Action)resources.getResource("action.back",Action.class);
	forwardAction =
	    (Action)resources.getResource("action.forward", Action.class);

	// Start off with both actions disabled
	backAction.setEnabled(false);
	forwardAction.setEnabled(false);
	
	// Create a ThemeManager for this frame, 
	// and add a Theme menu to the menubar
	ThemeManager themes = new ThemeManager(this, resources);
	menubar.add(themes.getThemeMenu());

	// Keep track of how many web browser windows are open
	WebBrowser.numBrowserWindows++;
    }

    /** Set the static property that controls the behavior of close() */
    public static void setExitWhenLastWindowClosed(boolean b) {
	exitWhenLastWindowClosed = b;
    }

    /** These are accessor methods for the home property. */
    public void setHome(String home) { this.home = home; }
    public String getHome() { return home; }

    /**
     * This internal method attempts to load and display the specified URL.
     * It is called from various places throughout the class.
     **/
    boolean visit(URL url) {
	try {
	    String href = url.toString();
	    // Start animating.  Animation is stopped in propertyChanged()
	    startAnimation("Loading " + href + "...");  
	    textPane.setPage(url);   // Load and display the URL 
	    this.setTitle(href);     // Display URL in window titlebar
	    urlField.setText(href);  // Display URL in text input field
	    return true;             // Return success
	}
	catch (IOException ex) {     // If page loading fails
	    stopAnimation();
	    messageLine.setText("Can't load page: " + ex.getMessage());
	    return false;            // Return failure
	}
    }

    /**
     * Ask the browser to display the specified URL, and put it in the
     * history list.
     **/
    public void displayPage(URL url) {
	if (visit(url)) {    // go to the specified url, and if we succeed:
	    history.add(url);       // Add the url to the history list
	    int numentries = history.size();
	    if (numentries > MAX_HISTORY+10) {  // Trim history when too large
		history = history.subList(numentries-MAX_HISTORY, numentries);
		numentries = MAX_HISTORY;
	    }
	    currentHistoryPage = numentries-1;  // Set current history page
	    // If we can go back, then enable the Back action
	    if (currentHistoryPage > 0) backAction.setEnabled(true);
	}
    }

    /** Like displayPage(URL), but takes a string instead */
    public void displayPage(String href) {
	try {
	    displayPage(new URL(href));
	}
	catch (MalformedURLException ex) {
	    messageLine.setText("Bad URL: " + href);
	}
    }

    /** Allow the user to choose a local file, and display it */
    public void openPage() {
	// Lazy creation: don't create the JFileChooser until it is needed
	if (fileChooser == null) {
	    fileChooser = new JFileChooser();
	    // This javax.swing.filechooser.FileFilter displays only HTML files
	    FileFilter filter = new FileFilter() {
		    public boolean accept(File f) {
			String fn = f.getName();
			if (fn.endsWith(".html") || fn.endsWith(".htm"))
			    return true;
			else return false;
		    }
		    public String getDescription() { return "HTML Files"; }
		};
	    fileChooser.setFileFilter(filter);
	    fileChooser.addChoosableFileFilter(filter);
	}

	// Ask the user to choose a file.
	int result = fileChooser.showOpenDialog(this);
	if (result == JFileChooser.APPROVE_OPTION) {
	    // If they didn't click "Cancel", then try to display the file.
	    File selectedFile = fileChooser.getSelectedFile();
	    String url = "file://" + selectedFile.getAbsolutePath();
	    displayPage(url);
	}
    }

    /** Go back to the previously displayed page. */
    public void back() {
	if (currentHistoryPage > 0)  // go back, if we can
	    visit((URL)history.get(--currentHistoryPage));
	// Enable or disable actions as appropriate
	backAction.setEnabled((currentHistoryPage > 0));
	forwardAction.setEnabled((currentHistoryPage < history.size()-1));
    }

    /** Go forward to the next page in the history list */
    public void forward() {
	if (currentHistoryPage < history.size()-1)  // go forward, if we can
	    visit((URL)history.get(++currentHistoryPage));
	// Enable or disable actions as appropriate
	backAction.setEnabled((currentHistoryPage > 0));
	forwardAction.setEnabled((currentHistoryPage < history.size()-1));
    }

    /** Reload the current page in the history list */
    public void reload() {
	if (currentHistoryPage != -1) {
	    // We can't reload the current document, so display a blank page
	    textPane.setDocument(new javax.swing.text.html.HTMLDocument());
	    // Now re-visit the current URL
	    visit((URL)history.get(currentHistoryPage));
	}
    }

    /** Display the page specified by the "home" property */
    public void home() { displayPage(getHome()); }

    /** Open a new browser window */
    public void newBrowser() {
	WebBrowser b = new WebBrowser();
	b.setSize(this.getWidth(), this.getHeight());
	b.setVisible(true);
    }

    /**
     * Close this browser window.  If this was the only open window,
     * and exitWhenLastBrowserClosed is true, then exit the VM
     **/
    public void close() {
	this.setVisible(false);             // Hide the window
	this.dispose();                     // Destroy the window
	synchronized(WebBrowser.class) {    // Synchronize for thread-safety
	    WebBrowser.numBrowserWindows--; // There is one window fewer now
	    if ((numBrowserWindows==0) && exitWhenLastWindowClosed)
		System.exit(0);             // Exit if it was the last one
	}
    }
       
    /**
     * Exit the VM.  If confirm is true, ask the user if they are sure.
     * Note that showConfirmDialog() displays a dialog, waits for the user,
     * and returns the user's response (i.e. the button the user selected).
     **/
    public void exit(boolean confirm) {
	if (!confirm ||
	    (JOptionPane.showConfirmDialog(this,  // dialog parent
	         /* message to display */  "Are you sure you want to quit?",
		 /* dialog title */	   "Really Quit?",
		 /* dialog buttons */	   JOptionPane.YES_NO_OPTION) == 
	     JOptionPane.YES_OPTION))  // If Yes button was clicked
	    System.exit(0);
    }

    /** 
     * This method implements HyperlinkListener.  It is invoked when the user
     * clicks on a hyperlink, or move the mouse onto or off of a link
     **/
    public void hyperlinkUpdate(HyperlinkEvent e) {
	HyperlinkEvent.EventType type = e.getEventType();  // what happened?
	if (type == HyperlinkEvent.EventType.ACTIVATED) {     // Click!
	    displayPage(e.getURL());   // Follow the link; display new page
	}
	else if (type == HyperlinkEvent.EventType.ENTERED) {  // Mouse over!
	    // When mouse goes over a link, display it in the message line
	    messageLine.setText(e.getURL().toString());  
	}
	else if (type == HyperlinkEvent.EventType.EXITED) {   // Mouse out!
	    messageLine.setText(" ");  // Clear the message line
	}
    }

    /**
     * This method implements java.beans.PropertyChangeListener.  It is 
     * invoked whenever a bound property changes in the JEditorPane object.
     * The property we are interested in is the "page" property, because it
     * tells us when a page has finished loading.
     **/
    public void propertyChange(PropertyChangeEvent e) {
	if (e.getPropertyName().equals("page")) // If the page property changed
	    stopAnimation();              // Then stop the loading... animation
    }

    /**
     * The fields and methods below implement a simple animation in the
     * web browser message line; they are used to provide user feedback
     * while web pages are loading.
     **/
    String animationMessage;  // The "loading..." message to display
    int animationFrame = 0;   // What "frame" of the animation are we on
    String[] animationFrames = new String[] {  // The content of each "frame"
	"-", "\\", "|", "/", "-", "\\", "|", "/", 
	",", ".", "o", "0", "O", "#", "*", "+"
    };

    /** This object calls the animate() method 8 times a second */
    javax.swing.Timer animator =
	new javax.swing.Timer(125, new ActionListener() {
		public void actionPerformed(ActionEvent e) { animate(); }
	    });

    /** Display the next frame. Called by the animator timer */
    void animate() {
	String frame = animationFrames[animationFrame++];    // Get next frame
	messageLine.setText(animationMessage + " " + frame); // Update msgline
	animationFrame = animationFrame % animationFrames.length;
    }

    /** Start the animation.  Called by the visit() method. */
    void startAnimation(String msg) {
	animationMessage = msg;     // Save the message to display
	animationFrame = 0;         // Start with frame 0 of the animation
	animator.start();           // Tell the timer to start firing.
    }

    /** Stop the animation.  Called by propertyChanged() method. */
    void stopAnimation() {       
	animator.stop();            // Tell the timer to stop firing events
	messageLine.setText(" ");   // Clear the message line
    }
}