// CardLayoutDemo.java
 
import java.awt.*; 
import java.applet.Applet; 
import java.util.Enumeration;

interface CardConstants {

	// The maximum number of cards is set here for
	// all classes which implement this interface for
	// a particular project. In a real world setting, this
	// interface would be public and separately compiled
	// for each particular need:
	final static byte NUMBER_OF_CARDS = 4;

	// The font to be applied to the card text:
	final static String CARD_TEXT_FONT = "Helvetica";
	
	// Card identifiers:
	final static String CARD_1 = "1";
	final static String CARD_2 = "2";
	final static String CARD_3 = "3";
	final static String CARD_4 = "4";
	final static String CARD_5 = "5";
	final static String CARD_6 = "6";
	final static String CARD_7 = "7";
	final static String CARD_8 = "8";
	final static String CARD_9 = "9";
	final static String CARD_10 = "10";

	// Border layout:
	final static String NORTH = "North";
	final static String CENTER = "Center";
	final static String SOUTH = "South";

	// Button identifiers:
	final static String FIRST = "FIRST";
	final static String PREV = "PREVIOUS";
	final static String NEXT = "NEXT";
	final static String LAST = "LAST";

	// The size of this array must at least equal NUMBER_OF_CARDS
	final static String[] crdName = {
		CARD_1,
		CARD_2,
		CARD_3,
		CARD_4,
		CARD_5,
		CARD_6,
		CARD_7,
		CARD_8,
		CARD_9,
		CARD_10
	};
}

class CardLogicError extends Exception {
	
	CardLogicError(String msg) {
	// Let class Exception carry the freight:
		super(msg);
	}
}

class CardContents implements CardConstants {
	
	private String crdText1 = 
		"The CardLayout class permits the Java programmer\n" +
		"to create any number of distinct cards which appear\n" +
		"one at a time.\n" +
		" \n" +
		"Moreover, each card has its own (and therefore\n" +
		"possibly unique) Layout.";

	private String crdText2 =
		"This implementation subclasses Panel as the Container\n" +
		"and as the basis for the CardLayout.\n" +
		" \n" +
		"The add method of class Container takes an object\n" +
		"of class Component as one of its arguments (the other\n" +
		"being the Component object's name), which means that\n" +
		"anything from a Button to a Label can be \"on a card\" (the\n" +
		"add method also adds the Component to the layout manager).";

	private String crdText3 =
		"Showing or \"flipping to\" to a card requires the show method\n" +
		"of class CardLayout--not the show method of class Component.\n" +
		" \n" +
		"Consequently, something like:\n" +
		" \n" +
		"((CardLayout)<object>.getLayout()).show(<object>,<name>)\n" +
		" \n" +
		"where <object> is the Container object and <name> is the String\n" +
		"Component name, is the key to getting things going. (There are also\n" +
		"methods for showing the first, next, previous, or last card, which do\n" +
		"not require the Component name.)";

	private String crdText4 =
		"The text you are now reading is in an object of class MultiLineLabel,\n" +
		"which was written by David Flanagan and is described in his book\n" +
		"\"Java in a Nutshell\" (O'Reilly & Associates, Inc.).\n" + 
		" \n" +
		"Class CardTest by Arthur van Hoff provided the insights into\n" +
		"just how CardLayout works and can be applied.\n" +
		" \n" +
		"                                                    *****";

	protected int cardCount = NUMBER_OF_CARDS;
 
	protected Object cardText[] = new Object[cardCount];

	public CardContents() {
		// Clearly, be sure that the number of assigned
		// elements is at least equal to cardCount:
		cardText[0] = crdText1;
		cardText[1] = crdText2;
		cardText[2] = crdText3;
		cardText[3] = crdText4;
	}
	
	public final synchronized Enumeration elements() {
		return new CardContentsEnumerator(this);
    }
}

final class CardContentsEnumerator implements Enumeration {
   
	CardContents	crdContents;
	int count = 0;

    	CardContentsEnumerator(CardContents crdContents) {
		this.crdContents = crdContents;
	 }

	public boolean hasMoreElements() { 
		return count < crdContents.cardCount;
	}

	public Object nextElement() { 
		synchronized (crdContents) {
	    	if (count < crdContents.cardCount)
			return crdContents.cardText[count++];
		}
		// The programmer will not catch this one (there should
		// be an Exception, but the compiler insists that the
		// Exception be of a particular category--home grown doesn't fly):
		throw new RuntimeException("CardContentsEnumerator");
	}
}

class MultiLineLabelX extends MultiLineLabel {

	// Class "MultiLineLabel" is by David Flanagan and is presented in his
	// book, "Java in a Nutshell," page 120. 

	// Class MultiLineLabel has a setForeground() method but not a
	// setBackground() method. This subclass provides a setBackground()
	// method.

	public MultiLineLabelX(String label, int margin_width, int margin_height,
                  			int alignment) {
		super(label, margin_width, margin_height, alignment);
	}

	public MultiLineLabelX(String label, int margin_width, int margin_height) {
        		super(label, margin_width, margin_height, LEFT);
    	}
    	
	public MultiLineLabelX(String label, int alignment) {
        		super(label, 10, 10, alignment);
    	}
    	
	public MultiLineLabelX(String label) {
        		super(label, 10, 10, LEFT);
    	}

	// Added method:
	public void setBackground(Color c) {
		super.setBackground(c);
		super.repaint();
	}
}

class CardPanel extends Panel implements CardConstants {

	private MultiLineLabelX	mlLabel;
	private Font	textFont = new Font(CARD_TEXT_FONT, Font.PLAIN, 12);
	private Color	textBackgroundColor = new Color(255, 255, 100);

	public CardPanel(String crdText) {
		setLayout(new FlowLayout(FlowLayout.CENTER, 15 ,15));
		mlLabel = new MultiLineLabelX(crdText);
		mlLabel.setBackground(textBackgroundColor);
		mlLabel.setFont(textFont);
		add(mlLabel);
	}
}

class Cards extends Panel implements CardConstants {

	private	CardLayout	crdLayout;

	/*
	As long as this and the other classes pay heed to NUMBER_OF_CARDS
	in interface CardConstants, things should go swimmingly. The design
	of this class is otherwise conceptually independent of the other classes.
	We only need to know the names of the players--not how they play the game.
	*/

	private final static byte NUMBER_OF_CARDS_INDEX = 
					(byte)(NUMBER_OF_CARDS - 1);

	private	CardPanel[] crdPanel = new CardPanel[NUMBER_OF_CARDS];

	protected byte currCardIndex = 0;
	
	public Cards() {

		CardContents	crdContents = new CardContents();
		Enumeration	e = crdContents.elements();
		int		i = 0;

		// The crux of the issue is to set this object's Layout to
		// CardLayout:
		setLayout(new CardLayout());

		// Create an object of class CardLayout in order to access the
		// proper "show" method:
		crdLayout = (CardLayout)this.getLayout();

		// Create each component (Panel) to be added to this object,
		// and then add the component:
		while (e.hasMoreElements()) {
			crdPanel[i] = new CardPanel((String)e.nextElement());
			add(crdName[i], crdPanel[i]);
			i++;
		}
	}

	/*
	This class does not "know" how it's being used. However, it is responsible
	for avoiding a kind of "out of bounds" condition, i.e., you can't show the
	previous card if you're already at the first card, and the same for the
	next card. (Obviously, it could be revised to have wraparound, if that's
	preferable.)
	*/

	public void showFirstCard()  {
		currCardIndex = 0;
		crdLayout.show(this, crdName[currCardIndex]);
	}

	public void showPreviousCard() throws CardLogicError {
		if (currCardIndex == 0)
			throw new CardLogicError("PreviousCard: now at first card.");
		currCardIndex -= 1;
		crdLayout.show(this, crdName[currCardIndex]);
	}

	public void showNextCard() throws CardLogicError {
		if (currCardIndex == NUMBER_OF_CARDS_INDEX)
			throw new CardLogicError("NextCard: now at last card.");
		currCardIndex += 1;
		crdLayout.show(this, crdName[currCardIndex]);
	}

	public void showLastCard() {
		currCardIndex = NUMBER_OF_CARDS_INDEX;
		crdLayout.show(this, crdName[currCardIndex]);
	}

	public boolean atFirstCard() {
		return ((currCardIndex == 0) ? true : false);
	}

	public boolean atLastCard() {
		return ((currCardIndex == NUMBER_OF_CARDS_INDEX) ? true : false);
	}
}

public class CardLayoutDemo extends Applet implements CardConstants {

	final static String LOGIC_ERROR = "Logic error - ";
	final static String LABEL_CAPTION = "CardLayout Demo";
	final static String LABEL_FONT = CARD_TEXT_FONT;
	final static int LABEL_FONT_STYLE = Font.BOLD;
	final static int LABEL_FONT_SIZE = 14;
	
	Font lblFont = new Font(LABEL_FONT, LABEL_FONT_STYLE, LABEL_FONT_SIZE);

	final static String BUTTON_FONT = CARD_TEXT_FONT;
	final static int BUTTON_FONT_STYLE = Font.BOLD;
	final static int BUTTON_FONT_SIZE = 12;

	Font btnFont = new Font(BUTTON_FONT, BUTTON_FONT_STYLE, BUTTON_FONT_SIZE);

	Label	lblDemo = new Label(LABEL_CAPTION, Label.CENTER);
		
	Cards	crdSet = new Cards();

	Panel	btnPanel = new Panel();
	Button  btnFirst = new Button(FIRST);
	Button	btnPrev = new Button(PREV);
	Button	btnNext = new Button(NEXT);
	Button	btnLast = new Button(LAST);
	
	public void init() {

		setLayout(new BorderLayout());

		lblDemo.setFont(lblFont);
	
		add(NORTH, lblDemo);

		add(CENTER, crdSet);

		btnPanel.setLayout(new FlowLayout());

		btnFirst.setFont(btnFont);
		btnPrev.setFont(btnFont);
		btnNext.setFont(btnFont);
		btnLast.setFont(btnFont);

		btnPanel.add(btnFirst);
		btnPanel.add(btnPrev);
		btnPanel.add(btnNext);
		btnPanel.add(btnLast);

		add(SOUTH, btnPanel);

		crdSet.showFirstCard();
		disableFirst();

	}

 	public boolean action(Event e, Object a) {  
		if (e.target instanceof Button) {

			if (a.equals(FIRST)) {
				crdSet.showFirstCard();
				disableFirst();
				enableLast();

			} else if (a.equals(PREV)) {
				try {
					crdSet.showPreviousCard();
				} catch (CardLogicError cle) {
					System.out.println(LOGIC_ERROR + cle.getMessage());
					System.exit(0);
				}
				if (crdSet.atFirstCard())
					disableFirst();
				enableLast();

			} else if (a.equals(NEXT)) {
				try {
					crdSet.showNextCard();
				} catch (CardLogicError cle) {
					System.out.println(LOGIC_ERROR + cle.getMessage());
					System.exit(0);
				}
				if (crdSet.atLastCard())
					disableLast();
				enableFirst();

			} else if (a.equals(LAST)) {
				crdSet.showLastCard();
				disableLast();
				enableFirst();
			}

			return true;
		}
		// From somewhere else...
		return false;
	}

	private void enableFirst() {
		btnFirst.enable();
		btnPrev.enable();
	}

	private void enableLast() {
		btnNext.enable();
		btnLast.enable();
	}

	private void disableFirst() {
		btnFirst.disable();
		btnPrev.disable();
	}

	private void disableLast() {
		btnNext.disable();
		btnLast.disable();
	}
}
