|
Advanced Uses of the Observer Pattern in Java
Multicasting, Property Changes, and Other Issues |
|
Prof. David Bernstein |
| Computer Science Department |
| bernstdh@jmu.edu |
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* A Component that can be used to enter secret combinations
* using the keyboard. A combination can consist of any number
* of characters. The combination is never displayed.
* Users indicates that they are done by pressing [Enter].
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class CombinationField extends JPanel implements KeyListener
{
private static final long serialVersionUID = 1L;
private ActionListener actionListener = null;
Color background;
private int id = ActionEvent.ACTION_FIRST;
private JLabel label;
private String combination;
/**
* Default Constructor.
*/
public CombinationField()
{
super();
setLayout(new BorderLayout());
label = new JLabel("", SwingConstants.CENTER);
add(label, BorderLayout.CENTER);
background = getBackground();
reset();
setFocusable(true);
requestFocusInWindow();
}
/**
* Add an ActionListener to this Component. The ActionListener
* will be informed when the user presses [Enter].
*
* @param l The ActionListener to add
*/
public synchronized void addActionListener(ActionListener l)
{
actionListener = AWTEventMulticaster.add(actionListener, l);
}
/**
* Fire an ActionEvent to all listeners.
*/
private void fireEvent()
{
ActionEvent ae = new ActionEvent(this, id, "CombinationEntered");
id++;
actionListener.actionPerformed(ae);
}
/**
* Get the most recent combination that was entered
* since the last call to reset().
*
* @return The combination
*/
public String getCombination()
{
String result = combination;
combination = "";
return result;
}
/**
* Handle keyTyped messages.
*
* @param e The event that generated the message
*/
public void keyTyped(KeyEvent e)
{
}
/**
* Handle keyPressed messages.
*
* @param e The event that generated the message
*/
public void keyPressed(KeyEvent e)
{
setBackground(Color.YELLOW);
repaint();
Toolkit.getDefaultToolkit().beep();
}
/**
* Handle keyReleased messages.
*
* @param e The event that generated the message
*/
public void keyReleased(KeyEvent e)
{
setBackground(background);
int code = e.getKeyCode();
if (code == KeyEvent.VK_ENTER)
{
removeKeyListener(this);
setText("Disabled");
if (actionListener != null) fireEvent();
}
else
{
combination += e.getKeyChar();
}
}
/**
* Remove an ActionListener from this Component.
*
* @param l The ActionListener to remove
*/
public synchronized void removeActionListener(ActionListener l)
{
actionListener = AWTEventMulticaster.remove(actionListener, l);
}
/**
* Reset the state of this Component.
*/
public void reset()
{
combination = "";
addKeyListener(this);
label.setText("Enter A Combination");
}
/**
* Set the text on this Component.
*
* @param text The text
*/
public void setText(String text)
{
label.setText(text);
}
}
import java.awt.*;
import java.beans.*;
/*
* An encapsulation of a rectangle.
*
* This implementation differs in significant ways from both the Rectangle class
* and the Rectangle2D inner classes. Perhaps most importantly, it's
* attributes are private so that it can serve as a property change subject.
*
* @author Prof. David Bernstein, James Madison Univerity
* @version 1.0
*/
public class Rectangle
{
private static final int SQUARE_WIDTH = 3;
private static final int SQUARE_GAP = 1;
private boolean selected;
private int height, width, x, y;
private PropertyChangeSupport support;
/**
* Explicit value constructor.
*
* @param x The x-coordinate of the upper-left corner
* @param y The y-coordinate of the upper-left corner
* @param width The width
* @param height The height
*/
public Rectangle(int x, int y, int width, int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
selected = false;
support = new PropertyChangeSupport(this);
}
/**
* Add a PropertyChangeListener to this Rectangle.
* The given object will actually listen to four different properties:
* x, y, width, and height.
*
* @param listener The listener to add
*/
public void addPropertyChangeListener(PropertyChangeListener listener)
{
support.addPropertyChangeListener("x", listener);
support.addPropertyChangeListener("y", listener);
support.addPropertyChangeListener("width", listener);
support.addPropertyChangeListener("height", listener);
}
/**
* Create a square visual grabber (for dragging a corner of this Rectangle).
*
* @param x The x-coordinate of the point of interest
* @param y The y-coordinate of the point of interest
* @param deltaX The horizontal offset (-1 for West, 1 for East)
* @param deltaY The vertical offset (-1 for North, 1 for South)
* @return
*/
private int[] createGrabber(int x, int y, int deltaX, int deltaY)
{
int x1 = x + deltaX * SQUARE_GAP;
int y1 = y + deltaY * SQUARE_GAP;
int x2 = x1 + deltaX * SQUARE_WIDTH;
int y2 = y1 + deltaY * SQUARE_WIDTH;
int sx = Math.min(x1, x2);
int sy = Math.min(y1, y2);
int[] result = {sx, sy, SQUARE_WIDTH, SQUARE_WIDTH};
return result;
}
/**
* Return true if this Rectangle contains the given Point.
*
* @param p The Point of interest
* @return true if p is contained in this Rectangle; false otherwise
*/
public boolean contains(Point p)
{
return (p.x >= x) && (p.x <= x + width) && (p.y >= y) && (p.y <= y + height);
}
/**
* Get the four corners for this Rectangle.
*
* @return The four corners;
*/
public Point[] corners()
{
Point[] result = new Point[4];
result[0] = new Point(x, y); // Upper-left
result[1] = new Point(x + width, y); // Upper-right
result[2] = new Point(x + width, y + height); // Lower-left
result[3] = new Point(x, y + height); // Lower-left
return result;
}
/**
* Draw a rectangular area.
*
* @param g The rendering engine to use
* @param r The x, y, width, and height of the rectangular area
*/
private void drawRect(Graphics g, int...r )
{
g.drawRect(r[0], r[1], r[2], r[3]);
}
/**
* Fill a rectangular area.
*
* @param g The rendering engine to use
* @param r The x, y, width, and height of the rectangular area
*/
private void fillRect(Graphics g, int...r )
{
g.fillRect(r[0], r[1], r[2], r[3]);
}
/**
* Get the height.
*
* @return The height
*/
public int getHeight()
{
return height;
}
/**
* Get the width.
*
* @return The width
*/
public int getWidth()
{
return width;
}
/**
* Get the x-coordinate of the upper-left corner.
*
* @return The x-coordinate
*/
public int getX()
{
return x;
}
/**
* Get the y-coordinate of the upper-left corner.
*
* @return The x-coordinate
*/
public int getY()
{
return y;
}
/**
* Return true if this Rectangle is currently selected.
*
* @return true if selected; false otherwise
*/
public boolean isSelected()
{
return selected;
}
/**
* Render this Rectangle.
*
* @param g The rendering engine to use
*/
public void paint(Graphics g)
{
drawRect(g, x, y, width, height);
if (selected)
{
Color old = g.getColor();
g.setColor(Color.BLUE);
paintGrabbers(g, x, y);
paintGrabbers(g, x+width, y);
paintGrabbers(g, x+width, y+height);
paintGrabbers(g, x, y+height);
g.setColor(old);
}
}
/**
* Render the visual grabbers for a particular point/corner.
*
* @param g The rendering engine to use
* @param x The x-coordinate of the point/corner
* @param y The y-coordinate of the point/corner
*/
private void paintGrabbers(Graphics g, int x, int y)
{
fillRect(g, createGrabber(x, y, -1, -1)); // NORTH_WEST grabber
fillRect(g, createGrabber(x, y, -1, 1)); // NORTH_EAST grabber
fillRect(g, createGrabber(x, y, 1, 1)); // SOUTH_EAST grabber
fillRect(g, createGrabber(x, y, 1, -1)); // SOUTH_WEST grabber
}
/**
* Remove a PropertyChangeListener (for all properties).
*
* @param listener The listener to remove.
*/
public void removePropertyChangeListener(PropertyChangeListener listener)
{
support.removePropertyChangeListener(listener);
}
/**
* Select this Rectangle or not.
*
* @param selected true to select; false to unselect
*/
public void setSelected(boolean selected)
{
this.selected = selected;
}
/**
* Set the height.
*
* @param height The height
*/
public void setHeight(int height)
{
support.firePropertyChange("height", this.height, height);
this.height = height;
}
/**
* Set the width.
*
* @param width The width
*/
public void setWidth(int width)
{
support.firePropertyChange("width", this.width, width);
this.width = width;
}
/**
* Set the x-coordinate of the upper-left corner.
*
* @param x The x-coordinate
*/
public void setX(int x)
{
support.firePropertyChange("x", this.x, x);
this.x = x;
}
/**
* Set the y-coordinate of the upper-left corner.
*
* @param y The y-coordinate
*/
public void setY(int y)
{
support.firePropertyChange("y", this.y, y);
this.y = y;
}
}
import java.beans.*;
import javax.swing.*;
/**
* A PropertyLabel is a JLabel that responds to appropriate
* propertyChange messages by changing its text.
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class PropertyLabel extends JLabel implements PropertyChangeListener
{
private static final long serialVersionUID = 1L;
private String property;
/**
* Explicit value constructor.
*
* @param property The property being displayed
* @param value The initial value to display
*/
public PropertyLabel(String property, Object value)
{
super(value.toString());
this.property = property;
setBorder(BorderFactory.createTitledBorder(property));
}
/**
* Handle propertyChange messages.
*
* @param evt The event that generated the message
*/
public void propertyChange(PropertyChangeEvent evt)
{
if (property.equals(evt.getPropertyName()))
{
String text = evt.getNewValue().toString();
setText(text);
}
}
}
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* A RectanglePanel can be used to display and edit a Rectangle.
*
* @author Prof. David Bernstein, James Madison University
*
*/
public class RectanglePanel extends JPanel
implements MouseListener, MouseMotionListener
{
private static final long serialVersionUID = 1L;
private int draggedCorner;
private Rectangle rect;
/**
* Explicit value constructor.
*
* @param rect The Rectangle to display/edit
*/
public RectanglePanel(Rectangle rect)
{
this.rect = rect;
addMouseListener(this);
addMouseMotionListener(this);
}
/**
* Handle mouseClicked messages.
*
* @param e The event that generated the message
*/
public void mouseClicked(MouseEvent e)
{
Point p = e.getPoint();
if (!rect.isSelected())
{
if (rect.contains(p)) rect.setSelected(true);
else rect.setSelected(false);
repaint();
}
}
/**
* Handle mouseDragged messages.
*
* @param e The event that generated the message
*/
public void mouseDragged(MouseEvent e)
{
if (rect.isSelected())
{
Point finish = e.getPoint();
int xMin = rect.getX();
int yMin = rect.getY();
int xMax = xMin + rect.getWidth();
int yMax = yMin + rect.getHeight();
if (draggedCorner == 0)
{
xMin = finish.x;
yMin = finish.y;
}
else if (draggedCorner == 1)
{
xMax = finish.x;
yMin = finish.y;
}
else if (draggedCorner == 2)
{
xMax = finish.x;
yMax = finish.y;
}
else if (draggedCorner == 3)
{
xMin = finish.x;
yMax = finish.y;
}
rect.setX(Math.min(xMin, xMax));
rect.setY(Math.min(yMin, yMax));
rect.setWidth(Math.abs(xMax - xMin));
rect.setHeight(Math.abs(yMax - yMin));
repaint();
}
}
/**
* Handle mouseEntered messages.
*
* @param e The event that generated the message
*/
public void mouseEntered(MouseEvent e)
{
}
/**
* Handle mouseExited messages.
*
* @param e The event that generated the message
*/
public void mouseExited(MouseEvent e)
{
}
/**
* Handle mouseMoved messages.
*
* @param e The event that generated the message
*/
public void mouseMoved(MouseEvent e)
{
}
/**
* Handle mousePressed messages.
*
* @param e The event that generated the message
*/
public void mousePressed(MouseEvent e)
{
if (rect.isSelected())
{
Point p = e.getPoint();
Point[] corners = rect.corners();
double min = Double.POSITIVE_INFINITY;
for (int i=0; i<corners.length; i++)
{
double d = p.distance(corners[i]);
if (d < min)
{
min = d;
draggedCorner = i;
}
}
}
}
/**
* Handle mouseReleased messages.
*
* @param e The event that generated the message
*/
public void mouseReleased(MouseEvent e)
{
if (rect.isSelected())
{
rect.setSelected(false);
repaint();
}
}
/**
* Render this component.
*
* @param g The rendering engine to use
*/
public void paint(Graphics g)
{
super.paint(g);
rect.paint(g);
}
}
Observer and Observable?