FileDocCategorySizeDatePackage
Chapter2.javaAPI DocExample7788Thu Oct 04 11:59:12 BST 2001None

Chapter2.java

/*
 * File: Chap2Solutions.java
 * Tim Balls : Feb 2001
 *
 * Solutions to questions 1-3 of Chapter 2 (more or less!)
 *
 * The JComboBox generates ActionEvents when return is pressed and the
 * Component has focus.  It is necessary to add the item to the list that
 * forms the "model" of the JComboBox - checking to see that it not already
 * in the list.
 * The display part can be updated (without affecting the storage list) by
 * making use of another internal "model" - the JComboBoxEditor that is
 * responsible for the entry line.  Its item can be set and retrieved.
 *
 * The JComboBox also sends ItemEvents (to an ItemListener) whenever items
 * are selected or deselected.  Trapping an appropriate event allows list
 * selection to change the displayed page.
 */

import javax.swing.*;
import javax.swing.event.*;
import java.net.*;
import java.io.IOException;
import java.awt.event.*;
import java.awt.*;
import java.util.*;

class Ch2Sol123 extends JFrame
{	public static void main( String[] args )
	{	if( args.length == 0 )
		{	new Ch2Sol123( "NoURL" );
		}
		else
		{	new Ch2Sol123( args[0] );
		}
	}

	private JEditorPane html = new JEditorPane();
/**/	private JComboBox location;
	private JLabel statusLine = new JLabel( "", JLabel.LEFT );
   private JButton back = new JButton( "<<" );
  	private JButton forward = new JButton( ">>" );
	private HistoryList historyList = new HistoryList();

	public Ch2Sol123( String site )
	{	setSize( 550, 600 );
		setTitle( "Chapter Two Solutions" );
		addWindowListener( new WindowAdapter() {
			public void windowClosing( WindowEvent e )
			{	System.exit( 0 );
			}
		});

		// Build more interesting interface
/**/	location = new JComboBox();
/**/  location.setEditable( true );
/**/	location.getEditor().setItem( site );
		
		JPanel topLine = new JPanel();
		topLine.setLayout( new FlowLayout( FlowLayout.LEFT ) );
      topLine.add( back );
      topLine.add( forward );
		topLine.add( new JLabel( "URL:" ) );
		topLine.add( location );		
		
		// for the JFrame:
		this.getContentPane().setLayout( new BorderLayout() );
		this.getContentPane().add( topLine, BorderLayout.NORTH );
		this.getContentPane().add( statusLine, BorderLayout.SOUTH );

		if( !site.equals( "NoURL" ) )
		{  if( loadURL( site ) )
		   {  try
		      {  historyList.add( new URL( site ) );
		      }
		      catch( Exception ex ) {} // it must work as loadURL returned true.
		      location.addItem( site );
		   }
		}
		
		JScrollPane scroller = new JScrollPane();
		JViewport viewport = scroller.getViewport();
		viewport.add( html );
		
		this.getContentPane().add( scroller, BorderLayout.CENTER );
		html.setEditable( false ); // to allow links to be activated

		setVisible( true );		
		
/**/  // Event handlers - new code but much has simply been moved here

      // First pick up <return> pressed in editable section of the
      // JComboBox.  Need to add the new item to the JComboBox list.		
		location.addActionListener( new ActionListener() {
		   public void actionPerformed( ActionEvent e )
		   {  String newSite = location.getEditor().getItem().toString();
		      if( loadURL( newSite ) )
		      {  try
		         {  historyList.add( new URL( newSite ) );
		         }
		         catch( Exception ex ) {} // it must work as loadURL returned true.
		         int index = -1;
		         for( int item = 0; item < location.getItemCount(); item++ )
		         {  if( location.getItemAt( item ).equals( newSite ) )
		            {  index = item;
		            }
		         }
		         if( index == -1 ) // new item, not in list, so put at front
		         {  location.insertItemAt( newSite, 0 );
		         }
		      }
         }
      });

      // This one is needed to capture an item picked from the list
      location.addItemListener( new ItemListener() {
         public void itemStateChanged( ItemEvent e )
         {  if( e.getStateChange() == ItemEvent.SELECTED )
            {  boolean b = loadURL( e.getItem().toString() ); // must work!
            }
         }
      });
		
      // history list buttons:
      back.addActionListener( new ActionListener() {
      	public void actionPerformed( ActionEvent e )
         {	historyList.back();
            loadURL( historyList.getCurrent().toString() );    	
     	   }
     	});

      forward.addActionListener( new ActionListener() {
 	      public void actionPerformed( ActionEvent e )
	      {	historyList.forward();
	         loadURL( historyList.getCurrent().toString() );
         }
	   });

/**/  // hyperlinkListener moved to here		
		html.addHyperlinkListener( new HyperlinkListener() {
			public void hyperlinkUpdate( HyperlinkEvent e )
	      {	if( e.getEventType() == HyperlinkEvent.EventType.ACTIVATED )
		      {	if( loadURL( e.getURL().toString() ) )
		         {   historyList.add( e.getURL() );
		         }
		      }
		      if( e.getEventType() == HyperlinkEvent.EventType.ENTERED )
		      {	statusLine.setText( "" + e.getURL() );
		      }
		      if( e.getEventType() == HyperlinkEvent.EventType.EXITED )
		      {	statusLine.setText( "" );
		      }
	      }
      });		

/**/  // end of event handlers
		
		
	}  // end of Constructor (easily missed!)


	// Attempt to load the html JEditorPane from the URL at "site"
	// modified to return an indication of success
	// but NOT to update the HistoryList
/**/	private boolean loadURL( String site )
	{	URL url = null;
	   try
		{	url = new URL( site );
		}
		catch( MalformedURLException e )
		{	error( "Invalid URL specified" );
			return false;
		}
		try
		{	html.setPage( url ); // load page
		}
		catch( IOException e )
		{	error( "Can't load url: " + url );
			return false;
		}
/**/  // set it as the currently displayed item in the JComboBox
/**/  // but DO NOT add it to the list at this point
/**/  location.getEditor().setItem( site );
      return true;		
	}

	
/**/ // Another way to handle errors - create a special method
   // It could write to the status line - in this case it uses
   // the predfined dialogs in the JOptionPane class 	
	private void error( String message )
	{  JOptionPane.showMessageDialog( this,
	                                  message,
	                                  "Error",
	                                  JOptionPane.ERROR_MESSAGE);
	}
/**/	
	
/**/ // No changes to the HistoryList class
	/* Encapsulate the behaviour of a history list for URLs
	   Enable/Disable the arrow controls so that
	      illegal operations are impossible
	   This class is (too) strongly coupled to its outer class
	
	   Maintain position in list, ensure that adding a new item
	      deletes any that would have followed (becomes new tail)
	*/
	private class HistoryList
	{	private ArrayList history = new ArrayList();
		private int current = 0;

		public HistoryList()
		{	back.setEnabled( false );
			forward.setEnabled( false );
		}

		public void add( URL url )
		{	if( history.size() > (current+1) )
			{	history.subList( current+1, history.size() ).clear();
			}
			history.add( url );
			current = history.size()-1;
			if( current != 0 )
			{	back.setEnabled( true );
			}
			forward.setEnabled( false );
		}

		/* Decrement the current pointer
		   PreCondition: must not be at start of list
		*/
		public void back()
		{	current--;
			if( current == 0 )
			{	back.setEnabled( false );
			}
			if( current < (history.size()-1) )
			{	forward.setEnabled( true );
			}
		}

		/* Increment the current pointer
		   PreCondition: must not be at end of list
		*/
		public void forward()
		{	current++;
			if( current > 0 )
			{	back.setEnabled( true );
			}
			if( current == (history.size()-1) )
			{	forward.setEnabled( false );
			}
		}

		/* Return the URL access by the current pointer
		*/
		public URL getCurrent()
		{	return (URL)(history.get( current ));
		}
	}

}