CS 136 - Lecture 19

  1. More GUI Programming
    1. CardLayout
    2. Multiple Listeners with Shared Context
  2. Queues
    1. Interface
    2. Linked List implementation
    3. Vector implementation
    4. Clever array implementation

More GUI Programming

CardLayout

One very useful layout manager that we haven't yet discussed is CardLayout. It can be imagined as holding a list of panels, only one of which shows at a time. They are added with the command add(name, myPanel), where name is a string which can be used to bring up the panel. If the name of the layout manager is cardManager, then the panel with name myName can be brought to the top (and shown) with the command cardManager.show(this, myName). The manager remembers the order the panels were inserted, so commands like cardManager.first(this), cardManager.last(this), and cardManager.next(this) also can be used to select among the panels.

One minor complication is that you will likely be calling these methods from inside "listener" inner classes of the frame. If you write just cardManager.first(this) it will refer to the "this" of the inner class. To get it to call the correct "this", write cardManager.first(OuterClass.this), where OuterClass is the name of the outer class.

The following is a very simple example that illustrates the use of the CardLayout manager. This layout includes three panels, through which a user can cycle by clicking on buttons.

// (c) October 1999, Andrea Danyluk
// Card Layout Demo; listener as inner class

import java.awt.*;
import java.awt.event.*;

public class CardDemo extends Frame {

	protected CardLayout cardManager;
	protected Button button1, button2, button3;	// to switch from one card to
												// another
	
	public CardDemo()
	{
		super("Card Layout Demo");
		setSize(400, 200);
		
		// use CardLayout
		cardManager = new CardLayout(20, 10);
		setLayout(cardManager);
		
		// set up the ActionListener
		ButtonListener myListener = new ButtonListener();
		
		// the first card
		Panel card1 = new Panel();
		card1.setBackground(Color.green);
		button1 = new Button("Go to page 2");
		card1.add(button1);
		button1.addActionListener(myListener);
		add("Card1", card1);
		
		// card 2
		Panel card2 = new Panel();
		card2.setBackground(Color.yellow);
		button2 = new Button("Go to page 3");
		card2.add(button2);
		button2.addActionListener(myListener);
		add("Card2", card2);
		
		// card 3
		Panel card3 = new Panel();
		card3.setBackground(Color.red);
		button3 = new Button("Back to start");
		card3.add(button3);
		button3.addActionListener(myListener);
		add("Card3", card3);
		
		// tell the CardLayout to go to the first card of "this", i.e., the frame
		cardManager.first(this);
		
		setVisible(true);
	}

	public static void main(String args[]) {
		CardDemo app = new CardDemo();

	}
	
	protected class ButtonListener implements ActionListener
	{
		public void actionPerformed (ActionEvent evt)
		{
			Object src = evt.getSource();
			if (src == button1)
				cardManager.next(CardDemo.this);
				// cardManager.show(CardDemo.this, "Card2");
			else if (src == button2)
				cardManager.show(CardDemo.this, "Card3");
			else if (src == button3)
				cardManager.show(CardDemo.this, "Card1");		
		}
	}
}


Multiple listeners with shared context

The Lab 5 handout discussed the idea that listeners can be implemented using inner classes or using separate listener classes. In all of the examples we looked at, there were no explicit constructors given.

Furthermore, when using an inner class, we had listeners share information by allowing them access to the instance variables of the outer class.

Now let's consider implementing a constructor for a listener class. Furthermore, let's consider how we might use parameters to give objects of a listener class access to shared information.

// (c) October 1999, Andrea Danyluk
// Demonstration of multiple listeners with a shared context.

import java.awt.*;
import java.awt.event.*;

public class ColorText extends Frame
{
protected Button redButton, greenButton, blueButton, magentaButton, cyanButton, blackButton;
protected TextField display;

	public ColorText()
	{
		super("Shared state demo");
		
		display = new TextField("Watch me change");		// text field - color can be
									// changed by selecting color buttons
		
		// set up the color buttons
		redButton = new Button("red");						
		// add a listener specific to this button -- shares access to the TextField.
		redButton.addActionListener(new ButtonListener(display, Color.red));
		
		greenButton = new Button("green");
		greenButton.addActionListener(new ButtonListener(display, Color.green));
		
		blueButton = new Button("blue");
		blueButton.addActionListener(new ButtonListener(display, Color.blue));
		
		magentaButton = new Button("magenta");
		magentaButton.addActionListener(new ButtonListener(display, Color.magenta));
		
		cyanButton = new Button("cyan");
		cyanButton.addActionListener(new ButtonListener(display, Color.cyan));
		
		blackButton = new Button("black");
		blackButton.addActionListener(new ButtonListener(display, Color.black));
		
		// place all buttons in grid arrangement
		Panel pan = new Panel();
		pan.setLayout(new GridLayout(2,3,10,10));
		pan.add(redButton);
		pan.add(greenButton);
		pan.add(blueButton);
		pan.add(magentaButton);
		pan.add(cyanButton);
		pan.add(blackButton);
		
		add(display, "North");
		add(pan, "Center");
		
		// resize(200,100);
		pack();
		
		validate();
		setVisible(true);
		
	}
	
	public static void main(String args[])
	{
		ColorText c = new ColorText();
	}
}


// (c) October 1999, Andrea Danyluk

import java.awt.*;
import java.awt.event.*;

public class ButtonListener implements ActionListener
{
protected TextField theField;	// a text field
protected Color myColor;		// color associated with button

	public ButtonListener(TextField someField, Color colorName)
	{
		theField = someField;
		myColor = colorName;
	}

	public void actionPerformed(ActionEvent evt)
	{
		theField.setForeground(myColor);	
	}
	
	
	public static void main(String args[])
	{
		System.out.println( "Hello World!" );
	}
}


Queues

Queues are FIFO (first in-first out) structures.

Applications include:

Interface

public interface Queue extends Linear {
    public void enqueue(Object value);
    // post: the value is added to the tail of the structure

    public Object dequeue();
    // pre: the queue is not empty
    // post: the head of the queue is removed and returned

    public void add(Object value);
    // post: the value is added to the tail of the structure

    public Object remove();
    // pre: the queue is not empty
    // post: the head of the queue is removed and returned

    public Object peek();
    // pre: the queue is not empty
    // post: the element at the head of the queue is returned

    public int size();
    // post: returns the number of elements in the queue.

    public void clear();
    // post: removes all elements from the queue.
    
    public boolean isEmpty();
    // post: returns true iff the queue is empty
}

Linked List implementation

We can easily hold the queue as a linked list where we keep pointers to the front and rear of the queue.

Which way should pointers go? From front to rear or vice-versa?
If the pointers point from the rear to the front, it will be quite difficult to remove the front element. As a result, we orient them to point from the front to the rear.

All of the operations are O(1).


Vector implementation

Can put queue in a Vector with head at index 0 and tail to the right. Addition of new elements is done using the addElement method, while deletions set the head slot to null and move head one place to the right.

The main problem with this is that deletions and additions to the queue will result in elements removed from the left side and added to the right side. Thus the queue will "walk" off to the right over time, even if it remains the same size (e.g., imagine what happens after 100 adds and 100 removes). While the vector will keep growing to compensate, the Vector will keep growing and each time it grows, it will cost twice as much.

Alternatively we could change the deletions so that the element at the head (0 index) is actually removed. However this would make the remove method O(n). [Note: This is the actual implementation.]


Clever array implementation

Also have array implementation, but bit trickier!

Suppose we can set an upper bound on the maximum size of the queue.

How can we solve the problem of the queue "walking" off one end of the array?

We try a 'Circular' Array Implementation w/ "references" (subscripts) referring to the head and tail of the list.

We increase the subscripts by one when we add or remove an element from the queue. In particular, add 1 to front when you remove an element and add 1 to rear when you add an element. If nothing else is done, you soon bump up against the end of the array, even if there is lots of space at the beginning (which used to hold elements which have now been removed from the queue).

To avoid this, we become more clever. When you walk off one end of the array, we go back to beginning. Use

    index = (index + 1) mod MaxQueue
to walk forward. This avoids the problem with falling off of the end of the array.

Notice that the representation of a full queue and an empty queue can have identical values for front and rear.

The only way we can keep track of whether it is full or empty is to keep track of the number of elements in the queue. (But notice we now don't have to keep track of the rear of the queue, since it is count-1 after front.)

There is an alternative way of keeping track of the front and rear which allow you to determine whether it is full or empty without keeping track of the number of elements in the queue. Always keep the rear pointer pointing to the slot where the next element added will be put. Thus an empty queue will have front = rear. We will say that a queue is full if front = rear + 1 (mod queue_length). When this is true, there will still be one empty slot in the queue, but we will sacrifice this empty slot in order to be able to determine whether the queue is empty or full.

public class QueueArray implements Queue
{
    protected Object[] data;    // array of the data
    protected int head;         // next dequeue-able value
    protected int count;        // # elts in queue

    public QueueArray(int size)
    // post: create a queue capable of holding at most size 
    //          values.
    {
        data = new Object[size];
        head = 0;
        count = 0;
    }

    public void enqueue(Object value)
    // post: the value is added to the tail of the structure
    {
        Assert.pre(!isFull(),"Queue is not full.");
        int tail = (head + count) % data.length;
        data[tail] = value;
        count++;
    }

    public Object dequeue()
    // pre: the queue is not empty
    // post: the element at the head of the queue is removed 
    //          and returned
    {
        Assert.pre(!isEmpty(),"The queue is not empty.");
        Object value = data[head];
        head = (head + 1) % data.length;
        count--;
        return value;
    }

    public Object peek()
    // pre: the queue is not empty
    // post: the element at the head of the queue is returned
    {
        Assert.pre(!isEmpty(),"The queue is not empty.");
        return data[head];
    }

    ...

    public int size()
    // post: returns the number of elements in the queue.
    {
        return count;
    }

    public void clear()
    // post: removes all elements from the queue.
    {
        // we could remove all the elements from the queue.
        count = 0;
        head = 0;
    }
    
    public boolean isFull()
    // post: returns true if the queue is at the capacity.
    {
        return count == data.length;
    }

    public boolean isEmpty()
    // post: returns true iff the queue is empty
    {
        return count == 0;
    }
}

The complexity of operations for the array implementation of the queue is the same as for the linked list implementation.

There are the same trade-offs between the two implementations in terms of space and time as with stacks above. Notice that we do not bother to set array entry for dequeued element to null. Similarly with clear. Thus the garbage collector would not be able to sweep up removed elements even if not in use elsewhere. It would probably make sense to be more consistent with Vector and clean up behind ourselves.