/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004
ISBN: 0-596-00782-5
*/
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingTypeTester10 extends JFrame implements CharacterSource {
protected RandomCharacterGenerator producer;
private AnimatedCharacterDisplayCanvas displayCanvas;
private CharacterDisplayCanvas feedbackCanvas;
private JButton quitButton;
private JButton startButton;
private JButton stopButton;
private CharacterEventHandler handler;
private ScoreLabel score;
public SwingTypeTester10() {
initComponents();
}
private void initComponents() {
handler = new CharacterEventHandler();
producer = new RandomCharacterGenerator();
producer.setDone(true);
producer.start();
displayCanvas = new AnimatedCharacterDisplayCanvas(producer);
feedbackCanvas = new CharacterDisplayCanvas(this);
quitButton = new JButton();
startButton = new JButton();
stopButton = new JButton();
score = new ScoreLabel(producer, this);
Container pane = getContentPane();
JPanel p1 = new JPanel();
p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
p1.add(displayCanvas);
p1.add(feedbackCanvas);
JPanel p2 = new JPanel();
score.setText(" ");
score.setFont(new Font("MONOSPACED", Font.BOLD, 30));
p2.add(score);
startButton.setText("Start");
p2.add(startButton);
stopButton.setText("Stop");
stopButton.setEnabled(false);
p2.add(stopButton);
quitButton.setText("Quit");
p2.add(quitButton);
p1.add(p2);
pane.add(p1, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
quit();
}
});
feedbackCanvas.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent ke) {
char c = ke.getKeyChar();
if (c != KeyEvent.CHAR_UNDEFINED)
newCharacter((int) c);
}
});
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
displayCanvas.setDone(false);
producer.setDone(false);
score.resetScore();
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus();
}
});
stopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
startButton.setEnabled(true);
stopButton.setEnabled(false);
producer.setDone(true);
displayCanvas.setDone(true);
feedbackCanvas.setEnabled(false);
}
});
quitButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
quit();
}
});
}
private void quit() {
System.exit(0);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void newCharacter(int c) {
handler.fireNewCharacter(this, c);
}
public void nextCharacter() {
throw new IllegalStateException("We don't produce on demand");
}
public static void main(String args[]) {
new SwingTypeTester10().show();
}
}
class ScoreLabel extends JLabel implements CharacterListener {
private volatile int score = 0;
private int char2type = -1;
private CharacterSource generator = null, typist = null;
private Lock scoreLock = new ReentrantLock();
public ScoreLabel(CharacterSource generator, CharacterSource typist) {
this.generator = generator;
this.typist = typist;
if (generator != null)
generator.addCharacterListener(this);
if (typist != null)
typist.addCharacterListener(this);
}
public ScoreLabel() {
this(null, null);
}
public void resetGenerator(CharacterSource newGenerator) {
try {
scoreLock.lock();
if (generator != null)
generator.removeCharacterListener(this);
generator = newGenerator;
if (generator != null)
generator.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetTypist(CharacterSource newTypist) {
try {
scoreLock.lock();
if (typist != null)
typist.removeCharacterListener(this);
typist = newTypist;
if (typist != null)
typist.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetScore() {
try {
scoreLock.lock();
score = 0;
char2type = -1;
setScore();
} finally {
scoreLock.unlock();
}
}
private void setScore() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setText(Integer.toString(score));
}
});
}
public void newCharacter(CharacterEvent ce) {
scoreLock.lock();
try {
if (ce.source == generator) {
if (char2type != -1) {
score--;
setScore();
}
char2type = ce.character;
} else {
if (char2type != ce.character) {
score--;
} else {
score++;
char2type = -1;
}
}
setScore();
} finally {
scoreLock.unlock();
}
}
}
class RandomCharacterGenerator extends Thread implements CharacterSource {
private static char[] chars;
private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
static {
chars = charArray.toCharArray();
}
private Random random;
private CharacterEventHandler handler;
private boolean done = true;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
public RandomCharacterGenerator() {
random = new Random();
handler = new CharacterEventHandler();
}
public int getPauseTime(int minTime, int maxTime) {
return (int) (minTime + ((maxTime - minTime) * random.nextDouble()));
}
public int getPauseTime() {
return getPauseTime(2000, 5500);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void nextCharacter() {
handler.fireNewCharacter(this,
(int) chars[random.nextInt(chars.length)]);
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
nextCharacter();
cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (!done)
cv.signal();
} finally {
lock.unlock();
}
}
}
interface CharacterListener {
public void newCharacter(CharacterEvent ce);
}
interface CharacterSource {
public void addCharacterListener(CharacterListener cl);
public void removeCharacterListener(CharacterListener cl);
public void nextCharacter();
}
class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas implements
CharacterListener, Runnable {
private volatile boolean done = false;
private int curX;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
private Thread timer = null;
public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
super(cs);
}
public synchronized void newCharacter(CharacterEvent ce) {
curX = 0;
tmpChar[0] = (char) ce.character;
repaint();
}
public synchronized void paintComponent(Graphics gc) {
if (tmpChar[0] == 0)
return;
Dimension d = getSize();
int charWidth = fm.charWidth(tmpChar[0]);
gc.clearRect(0, 0, d.width, d.height);
gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
if (curX > d.width - charWidth)
curX = 0;
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
repaint();
cv.await(100, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (timer == null) {
timer = new Thread(this);
timer.start();
}
if (!done)
cv.signal();
} finally {
lock.unlock();
}
}
}
class CharacterDisplayCanvas extends JComponent implements CharacterListener {
protected FontMetrics fm;
protected char[] tmpChar = new char[1];
protected int fontHeight;
public CharacterDisplayCanvas(CharacterSource cs) {
setFont(new Font("Monospaced", Font.BOLD, 18));
fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
fontHeight = fm.getHeight();
cs.addCharacterListener(this);
}
public void setCharacterListener(CharacterSource cs) {
cs.addCharacterListener(this);
}
public Dimension preferredSize() {
return new Dimension(fm.getMaxAscent() + 10, fm.getMaxAdvance() + 10);
}
public synchronized void newCharacter(CharacterEvent ce) {
tmpChar[0] = (char) ce.character;
repaint();
}
protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize();
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0)
return;
int charWidth = fm.charWidth((int) tmpChar[0]);
gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight);
}
}
class CharacterEvent {
public CharacterSource source;
public int character;
public CharacterEvent(CharacterSource cs, int c) {
source = cs;
character = c;
}
}
class CharacterEventHandler {
private Vector listeners = new Vector();
public void addCharacterListener(CharacterListener cl) {
listeners.add(cl);
}
public void removeCharacterListener(CharacterListener cl) {
listeners.remove(cl);
}
public void fireNewCharacter(CharacterSource source, int c) {
CharacterEvent ce = new CharacterEvent(source, c);
CharacterListener[] cl = (CharacterListener[]) listeners
.toArray(new CharacterListener[0]);
for (int i = 0; i < cl.length; i++)
cl[i].newCharacter(ce);
}
}