package org.erikaredmark.monkeyshines.menu;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import org.erikaredmark.monkeyshines.ImmutablePoint2D;
import org.erikaredmark.monkeyshines.ImmutableRectangle;
/**
*
* A component that allows a user to click in a text field and type a Single key (not a key combination
* like with using Shift, Control, or other modifiers). Said key will then be displayed and the underlying
* model will be updated.
* <p/>
* Whilst this is a bit of a generic concept, this specific class works in the context of Monkey Shines,
* with support for an image on the right side of a specific size so that it blends in with the original
* look of the game. because of this, this component has a fixed, set size and is only used in the absence
* of layout managers.
*
* @author Erika Redmark
*
*/
public class KeyBinder extends JComponent {
private static final long serialVersionUID = 1L;
// Constant size
private static final Dimension SIZE = new Dimension(191, 28);
// Determines state of control. Either it has not been clicked (not awaiting events, just showing
// data) or it has been clicked and is waiting for a keyboard event to change the key.
private boolean awaitingKeyInput;
// This single character code is the entire model of this control.
private int value;
// The image displayed that indicates what action this key binding will be for.
private final BufferedImage contextImage;
private static final int CONTEXT_IMAGE_DRAW_X = 104;
private static final int CONTEXT_IMAGE_DRAW_Y = 2;
private static final Color BACKGROUND_BLUE = Color.decode("0x94adff");
private static final int CLICKABLE_REGION_X = 7;
private static final int CLICKABLE_REGION_Y = 6;
private static final int CLICKABLE_REGION_X2 = 106;
private static final int CLICKABLE_REGION_Y2 = 21;
private static final ImmutableRectangle CLICKABLE_REGION =
ImmutableRectangle.of(CLICKABLE_REGION_X,
CLICKABLE_REGION_Y,
CLICKABLE_REGION_X2 - CLICKABLE_REGION_X,
CLICKABLE_REGION_Y2 - CLICKABLE_REGION_Y);
/**
*
* Initialises the keybinder with the given image and will send all key-change selections to the passed listener. Uses
* the passed model for the initial value.
*
* @param contextImage
* the image that should be displayed to the right of the key selector, to tell the user what
* they are determining a binding for
*
* @param listener
* the listener that will be fired when the selected key is changed. The change event fired will contain
* the old and new keycode values (as integers)
*
* @param initialValue
* the initial character used for this key binder. This is a character code from {@code KeyEvent}
*
*/
public KeyBinder(BufferedImage contextImage, final PropertyChangeListener listener, int initialValue) {
this.value = initialValue;
this.contextImage = contextImage;
addMouseListener(new MouseAdapter() {
@Override public void mouseClicked(MouseEvent e) {
if (CLICKABLE_REGION.inBounds(ImmutablePoint2D.of(e.getX(), e.getY() ) ) ) {
setAwaitingKeyboardInput(true);
} else {
setAwaitingKeyboardInput(false);
}
repaint();
}
});
addKeyListener(new KeyAdapter() {
@Override public void keyPressed(KeyEvent e) {
if (awaitingKeyInput) {
if (value != e.getKeyCode() ) {
int oldValue = value;
value = e.getKeyCode();
listener.propertyChange(new PropertyChangeEvent(KeyBinder.this, "", oldValue, value) );
}
setAwaitingKeyboardInput(false);
repaint();
}
}
});
setSize(SIZE);
setPreferredSize(SIZE);
setMinimumSize(SIZE);
}
private void setAwaitingKeyboardInput(boolean awaiting) {
this.awaitingKeyInput = awaiting;
if (awaiting) {
requestFocus();
}
}
/**
*
* Returns the currently selected keycode for this binding.
*
* @return
*/
public int getSelectedKeyCode() {
return value;
}
/**
*
* Paints the background, the image label to the right indicating the type of keybinding, and then the current
* character selected, whatever it may be.
*
*/
@Override public void paintComponent(Graphics g) {
// Background borders
g.setColor(Color.BLACK);
g.drawLine(0, 0, SIZE.width, 0);
g.drawLine(0, 1, SIZE.width, 1);
g.drawLine(0, 0, 0, SIZE.height);
g.drawLine(1, 0, 1, SIZE.height);
g.setColor(BACKGROUND_BLUE);
g.fillRect(2, 2, SIZE.width - 2, SIZE.height - 2);
g.setColor(Color.WHITE);
g.drawLine(2, SIZE.height - 1, SIZE.width, SIZE.height - 1);
g.drawLine(2, SIZE.height, SIZE.width, SIZE.height);
g.drawLine(SIZE.width - 1, 0, SIZE.width - 1, SIZE.height);
g.drawLine(SIZE.width, 0, SIZE.width, SIZE.height);
// Context image
g.drawImage(contextImage,
CONTEXT_IMAGE_DRAW_X, CONTEXT_IMAGE_DRAW_Y,
contextImage.getWidth() + CONTEXT_IMAGE_DRAW_X, contextImage.getHeight() + CONTEXT_IMAGE_DRAW_Y,
0, 0,
contextImage.getWidth(), contextImage.getHeight(),
null);
// Similiar black/white box for the inner clickable region. We draw lines bordering but not
// inside the clickable region
g.setColor(Color.BLACK);
g.drawLine(CLICKABLE_REGION_X - 2, CLICKABLE_REGION_Y - 2, CLICKABLE_REGION_X2 + 2, CLICKABLE_REGION_Y - 2);
g.drawLine(CLICKABLE_REGION_X - 2, CLICKABLE_REGION_Y - 1, CLICKABLE_REGION_X2 + 2, CLICKABLE_REGION_Y - 1);
g.drawLine(CLICKABLE_REGION_X - 2, CLICKABLE_REGION_Y - 2, CLICKABLE_REGION_X - 2, CLICKABLE_REGION_Y2 + 2);
g.drawLine(CLICKABLE_REGION_X - 1, CLICKABLE_REGION_Y - 2, CLICKABLE_REGION_X - 1, CLICKABLE_REGION_Y2 + 2);
g.setColor(Color.WHITE);
g.drawLine(CLICKABLE_REGION_X, CLICKABLE_REGION_Y2 + 2, CLICKABLE_REGION_X2 + 2, CLICKABLE_REGION_Y2 + 2);
g.drawLine(CLICKABLE_REGION_X, CLICKABLE_REGION_Y2 + 1, CLICKABLE_REGION_X2 + 2, CLICKABLE_REGION_Y2 + 1);
g.drawLine(CLICKABLE_REGION_X2 + 1, CLICKABLE_REGION_Y, CLICKABLE_REGION_X2 + 1, CLICKABLE_REGION_Y2 + 2);
g.drawLine(CLICKABLE_REGION_X2 + 2, CLICKABLE_REGION_Y, CLICKABLE_REGION_X2 + 2, CLICKABLE_REGION_Y2 + 2);
// Stroke for the selected key
// If we have the focus AND awaiting events, colour red.
g.setColor( isFocusOwner() && awaitingKeyInput
? Color.RED
: Color.BLUE);
g.setFont(new Font("serif", Font.BOLD, 16) );
g.drawString(KeyEvent.getKeyText(this.value),
41, 20);
// done
}
}