import java.awt.*; import java.awt.event.*; /** A graphical (AWT-based) front-end for KnightBoard. Provides completely automatic and adjusting display as well as intuitive drag functionality for resizing the board.
Dragging is implemented completely within the class. The class provides event notification in a fashion similar to other AWT classes. To use these events, the caller must specify an object implementing the BoardListener interface using setBoardListener(); however, this is optional.
By default, both mouse clicks on squares and resizing by dragging borders are disabled. After specifying a listener, they may be enabled with setClickable() and setResizable(), respectively.
Note: GraphicalBoard subclasses java.awt.Canvas via KnightBoard. @see KnightBoard @see java.awt.Canvas @see BoardListener */ public class GraphicalBoard extends KnightBoard implements MouseListener, MouseMotionListener { /** Maximum width and height of the board in squares. */ public static final int MAX_DIMENSION = 10; private static final int GAP = 5, BORDER = 12; private static final int NO_DRAG = 0, E_DRAG = 1, S_DRAG = 2, SE_DRAG = E_DRAG+S_DRAG; private boolean resizable = false, clickable = false; private BoardListener listener = null; private Image image; private int dragging = NO_DRAG; private boolean pressed = false; private int squaresize, boardwidth, boardheight; private int new_width = width, new_height = height; private int squarex, squarey; private int[][] oldcontent; private boolean drawall = true; /** Constructs a new GraphicalBoard of the specified dimensions. The knight image is drawn on the current location of the knight, being scaled to fit within the square. @param width width of the board in squares. @param height height of the board in squares. @param knightimage image of a knight; if null, a filled red circle is used instead. @see #update @see java.awt.Image */ public GraphicalBoard(int width, int height, Image knightimage) { super(width, height); this.image = knightimage; addMouseMotionListener(this); addMouseListener(this); setFont(new Font("SansSerif", Font.PLAIN, 12)); oldcontent = new int[width][height]; } /** Sets the listener that will receive boardResized() and boardClicked() events (if enabled). Only one listener can be used at a time. @param l a class implementing the BoardListener interface. @see #removeBoardListener @see BoardListener */ public void setBoardListener(BoardListener l) { listener = l; } /** Removes the current listener. BoardListener events will be lost after this. @see #setBoardListener */ public void removeBoardListener() { removeMouseMotionListener(this); removeMouseListener(this); listener = null; } /** Enables or disables resizing by mouse-dragging the borders. @param b true if the board can be resized. */ public void setResizable(boolean b) { resizable = b; } /** Enables or disables choosing a square by pointing and clicking. @param b true if a square can be selected with the mouse. */ public void setClickable(boolean b) { clickable = b; } /** Updates the board on screen. Automatically adjusts to the available space. Only squares whose content has changed are re-drawn, unlike in paint(). Normally, the Java AWT takes care of calling these methods, and the programmer just uses repaint().
In unvisited squares, the value of the square is displayed. Visited squares are marked with an orange circle and the number of the visiting move, except for the square the knight is currently in, in which a knight image is drawn. A filled blue circle is drawn in the start square. @param g the Graphics object of this canvas. @see #paint @see java.awt.Component#repaint */ public void update(Graphics g) { int nmoves = getNumMoves(); int size = (getSize().width < getSize().height) ? getSize().width : getSize().height; // Enforce minimum size to avoid division by zero if (size - 2 * BORDER < MAX_DIMENSION) size = 2 * BORDER + 2 * MAX_DIMENSION; squaresize = (size - 2 * BORDER) / MAX_DIMENSION; for (int y = 0; y < height; y++) { int sy = BORDER + y * squaresize; Color bg = (y % 2 == 0) ? Color.black : Color.white; for (int x = 0; x < width; x++) { int sx = BORDER + x * squaresize; int v = getValue(x, y), m = getMove(x, y); bg = (bg == Color.white) ? Color.black : Color.white; // Create a sum representing the content of the square. // If it is not changed, no need to redraw. int content = m * 4096 + v; if (!drawall && content == oldcontent[x][y]) continue; oldcontent[x][y] = content; g.setColor(bg); g.fillRect(sx, sy, squaresize, squaresize); if (nmoves > 0 && m == nmoves) { // Current position if (image != null) g.drawImage(image, sx, sy, squaresize, squaresize, this); else { g.setColor(Color.red); g.fillOval(sx, sy, squaresize, squaresize); } } else if (v == STARTSQUARE) { g.setColor(Color.blue); g.fillOval(sx, sy, squaresize, squaresize); } else { if (m > 0) { g.setColor(Color.orange); g.fillOval(sx, sy, squaresize, squaresize); g.setColor(Color.black); g.drawString(String.valueOf(m), sx + squaresize/2 - 5, sy + squaresize/2 + 5); } else { g.setColor(Color.red); g.drawString(String.valueOf(v), sx + squaresize/2 - 5, sy + squaresize/2 + 5); } } } } // Only draw borders on complete re-draw if (drawall) { boardwidth = width * squaresize; boardheight = height * squaresize; g.setColor(Color.red); g.draw3DRect(GAP, GAP, boardwidth + 14, boardheight + 14, true); g.draw3DRect(GAP + 2, GAP + 2, boardwidth + 10, boardheight + 10, true); g.draw3DRect(GAP + 4, GAP + 4, boardwidth + 6, boardheight + 6, true); } drawall = false; } /** Re-draws the board completely. Normally there is no reason to call this method directly. @param g the Graphics object of this canvas. @see #update @see java.awt.Component#repaint */ public void paint(Graphics g) { drawall = true; update(g); } /** Catches mouseMoved events and changes the mouse cursor to an appropriate shape when moved over the right or bottom borders of the board. Also notifies the BoardListener if a drag was ended. @param e mouse move event received from AWT. @see BoardListener @see #mouseDragged */ public void mouseMoved(MouseEvent e) { if (!resizable || listener == null) return; int in = inBorder(e.getX(), e.getY()); if (in == S_DRAG) setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); else if (in == E_DRAG) setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); else if (in == SE_DRAG) setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)); else if (in == NO_DRAG && dragging != NO_DRAG) { // We just ended dragging setCursor(Cursor.getDefaultCursor()); dragging = NO_DRAG; if (new_width != width || new_height != height) listener.boardResized(new_width, new_height); else repaint(); } dragging = in; } /** Checks if the specified coordinates are in the borders of the board. @param x x-coordinate. @param y y-coordinate @return NO_DRAG, S_DRAG, E_DRAG or SE_DRAG. */ private int inBorder(int x, int y) { int in = NO_DRAG; // In bottom border? if (x >= GAP && x <= GAP + boardwidth + 14 && y >= GAP + boardheight + 6 && y <= GAP + boardheight + 20) in += S_DRAG; // In right border? if (y >= GAP && y <= GAP + boardheight + 14 && x >= GAP + boardwidth + 6 && x <= GAP + boardwidth + 20) in += E_DRAG; return in; } /** If a drag has started, draws a resizing grid as the user moves the mouse. Ending the drag with the same board dimensions that it was started with will cancel the drag. @param e mouse drag event received from AWT. */ public void mouseDragged(MouseEvent e) { if (!resizable || listener == null) return; Graphics g = getGraphics(); if (dragging != NO_DRAG) { if (dragging == E_DRAG || dragging == SE_DRAG) { new_width = (e.getX() + squaresize/2 - BORDER) / squaresize; if (new_width < MIN_DIMENSION) new_width = MIN_DIMENSION; if (new_width > MAX_DIMENSION) new_width = MAX_DIMENSION; } if (dragging == S_DRAG || dragging == SE_DRAG) { new_height = (e.getY() + squaresize/2 - BORDER) / squaresize; if (new_height < MIN_DIMENSION) new_height = MIN_DIMENSION; if (new_height > MAX_DIMENSION) new_height = MAX_DIMENSION; } g.setColor(getBackground()); g.fillRect(GAP, GAP, getSize().width - GAP, getSize().height - GAP); // Print the number of squares, in red if odd if ((new_width*new_height) % 2 == 1) g.setColor(Color.red); else g.setColor(Color.black); g.drawString(String.valueOf(new_width * new_height), BORDER + 5, BORDER + 15); // Draw grid g.setColor(Color.black); for (int y = 0; y < new_height; y++) { for (int x = 0; x < new_width; x++) { g.drawRect(BORDER + x*squaresize, BORDER + y*squaresize, squaresize, squaresize); } } // Draw embossed border g.setColor(Color.red); g.draw3DRect(GAP, GAP, new_width*squaresize + 14, new_height*squaresize + 14, false); g.draw3DRect(GAP + 2, GAP + 2, new_width*squaresize + 10, new_height*squaresize + 10, false); g.draw3DRect(GAP + 4, GAP + 4, new_width*squaresize + 6, new_height*squaresize + 6, false); drawall = true; } } /** Notices when a user presses the mouse button on a square inside the board, assuming that click events are enabled. The click can be canceled by moving the mouse out of the current square before releasing. @param e mouse press event received from AWT. */ public void mousePressed(MouseEvent e) { if (!clickable || listener == null) return; int x = e.getX(); int y = e.getY(); Graphics g = getGraphics(); if (x > BORDER && x < BORDER + boardwidth && y > BORDER && y < BORDER + boardheight) { squarex = (x - BORDER) / squaresize; squarey = (y - BORDER) / squaresize; g.setXORMode(Color.red); g.fillRect(BORDER + squarex*squaresize, BORDER + squarey*squaresize, squaresize, squaresize); g.setPaintMode(); pressed = true; dragging = NO_DRAG; } } /* Notifies the listener if the user clicked on a square or ended a drag. @param e mouse button release event received from AWT. @see BoardListener @see #mouseDragged @see #mousePressed */ public void mouseReleased(MouseEvent e) { if (listener == null) return; if (pressed) { Graphics g = getGraphics(); g.setXORMode(Color.red); g.fillRect(BORDER + squarex*squaresize, BORDER + squarey*squaresize, squaresize, squaresize); g.setPaintMode(); pressed = false; if ((e.getX() - BORDER) / squaresize == squarex && (e.getY() - BORDER) / squaresize == squarey) listener.boardClicked(squarex, squarey); } else if (dragging != NO_DRAG) { // Ended dragging setCursor(Cursor.getDefaultCursor()); dragging = NO_DRAG; if (new_width != width || new_height != height) listener.boardResized(new_width, new_height); else repaint(); } } public Dimension getMinimumSize() { return new Dimension(MAX_DIMENSION*20 + 2*BORDER, MAX_DIMENSION*20 + 2*BORDER); } public Dimension getPreferredSize() { return new Dimension(MAX_DIMENSION*40 + 2*BORDER, MAX_DIMENSION*40 + 2*BORDER); } // Dummy methods public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } }