1 /**
2  * The chess framework library.
3  * More information is available at http://www.jinchess.com/.
4  * Copyright (C) 2002 Alexander Maryanovsky.
5  * All rights reserved.
6  *
7  * The chess framework library is free software; you can redistribute
8  * it and/or modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * The chess framework library is distributed in the hope that it will
13  * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with the chess framework library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 
22 package free.chess;
23 
24 import java.awt.AWTEvent;
25 import java.awt.BasicStroke;
26 import java.awt.Color;
27 import java.awt.Font;
28 import java.awt.FontMetrics;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.Insets;
32 import java.awt.Point;
33 import java.awt.Rectangle;
34 import java.awt.event.MouseEvent;
35 import java.awt.geom.AffineTransform;
36 import java.awt.geom.GeneralPath;
37 import java.util.Vector;
38 
39 import javax.swing.JComponent;
40 import javax.swing.SwingUtilities;
41 import javax.swing.event.ChangeEvent;
42 import javax.swing.event.ChangeListener;
43 
44 import free.chess.event.MoveProgressEvent;
45 import free.chess.event.MoveProgressListener;
46 import free.util.PaintHook;
47 import free.util.PlatformUtils;
48 import free.util.Utilities;
49 
50 
51 /**
52  * An implementation of a chess board component.
53  * <B>IMPORTANT:</B> This class is not thread safe - all modifications should be
54  * done in the AWT event dispatching thread.
55  */
56 
57 public class JBoard extends JComponent{
58 
59 
60 
61   /**
62    * The default move highlighting color.
63    */
64 
65   private static final Color DEFAULT_MOVE_HIGHLIGHT_COLOR = Color.cyan.darker();
66 
67 
68 
69   /**
70    * The default coordinate display color.
71    */
72 
73   private static final Color DEFAULT_COORDS_DISPLAY_COLOR = Color.blue.darker();
74 
75 
76 
77   /**
78    * The default highlighting color for the squares of the move currently being
79    * made.
80    */
81 
82   private static final Color DEFAULT_MADE_MOVE_SQUARES_HIGHLIGHT_COLOR = Color.blue;
83 
84 
85 
86   /**
87    * The constant for drag'n'drop move input style.
88    */
89 
90   public static final int DRAG_N_DROP = 1;
91 
92 
93 
94   /**
95    * The constant for click'n'click move input style.
96    */
97 
98   public static final int CLICK_N_CLICK = 2;
99 
100 
101 
102   /**
103    * The constant for move input mode which doesn't let the user move any of the
104    * pieces.
105    */
106 
107   public static final int NO_PIECES_MOVE = 0;
108 
109 
110 
111   /**
112    * The constant for move input mode which only lets the user move white pieces.
113    */
114 
115   public static final int WHITE_PIECES_MOVE = 1;
116 
117 
118 
119   /**
120    * The constant for move input mode which only lets the user move black pieces.
121    */
122 
123   public static final int BLACK_PIECES_MOVE = 2;
124 
125 
126 
127   /**
128    * The constant for move input mode which lets the user move both white and
129    * black pieces.
130    */
131 
132   public static final int ALL_PIECES_MOVE = 3;
133 
134 
135 
136   /**
137    * The constant for move input mode which only lets the user move the pieces
138    * of the player to move.
139    */
140 
141   public static final int CURRENT_PLAYER_MOVES = 4;
142 
143 
144 
145   /**
146    * The constant for no move highlighting.
147    */
148 
149   public static final int NO_MOVE_HIGHLIGHTING = 0;
150 
151 
152 
153   /**
154    * The constant for move highlighting done by highlighting the target square
155    * of the move.
156    */
157 
158   public static final int TARGET_SQUARE_MOVE_HIGHLIGHTING = 1;
159 
160 
161 
162   /**
163    * The constant for move highlighting done by highlighting the source and
164    * target squares of the move.
165    */
166 
167   public static final int BOTH_SQUARES_MOVE_HIGHLIGHTING = 2;
168 
169 
170 
171   /**
172    * The constant for move highlighting done by drawing an arrow from the source
173    * square to the target square.
174    */
175 
176   public static final int ARROW_MOVE_HIGHLIGHTING = 3;
177 
178 
179 
180   /**
181    * The constant for not displaying coordinates at all.
182    */
183 
184   public static final int NO_COORDS = 0;
185 
186 
187 
188   /**
189    * The constant for displaying row and column coordinates on the outer rim
190    * of the board, xboard style.
191    */
192 
193   public static final int RIM_COORDS = 1;
194 
195 
196 
197   /**
198    * The constant for displaying row and column coordinates outside of the
199    * actual board.
200    */
201 
202   public static final int OUTSIDE_COORDS = 2;
203 
204 
205 
206   /**
207    * The constant for displaying square coordinates in each square.
208    */
209 
210   public static final int EVERY_SQUARE_COORDS = 3;
211 
212 
213 
214   /**
215    * The Position on the board.
216    */
217 
218   private Position position;
219 
220 
221 
222   /**
223    * A copy of the current position on the board. We keep this so that when the
224    * real position changes, we know which squares need repainting.
225    */
226 
227   private Position positionCopy;
228 
229 
230 
231 
232   /**
233    * The ChangeListener to the Position.
234    */
235 
236   private ChangeListener positionChangeListener = new ChangeListener(){
237 
238     public void stateChanged(ChangeEvent evt){
239       // Repaint only the parts that really need to be repainted by checking
240       // which squares changed.
241       boolean checkMovingPieceSquare = (movedPieceSquare != null);
242       Rectangle tmpRect = new Rectangle();
243 
244 
245       // Repaint the dragged piece position.
246       if (checkMovingPieceSquare)
247         repaint(tmpRect = getMoveAreaRect(tmpRect));
248 
249       for (int file = 0; file < 8; file++){
250         for (int rank = 0; rank < 8; rank++){
251           Piece oldPiece = positionCopy.getPieceAt(file, rank);
252           Piece newPiece = position.getPieceAt(file, rank);
253 
254           // We don't need to repaint the origin square of the moving piece.
255           if (checkMovingPieceSquare && (file == movedPieceSquare.getFile()) && (rank == movedPieceSquare.getRank())){
256             checkMovingPieceSquare = false;
257             continue;
258           }
259 
260           if (!Utilities.areEqual(oldPiece, newPiece))
261             repaint(tmpRect = squareToRect(file, rank, tmpRect));
262         }
263       }
264 
265       if (isHighlightMadeMoveSquares() && (movedPieceSquare != null))
266         repaint(tmpRect = squareToRect(movedPieceSquare, tmpRect));
267 
268       if (movedPieceSquare != null){                        // We were dragging a piece
269         if (position.getPieceAt(movedPieceSquare) == null){ // But the piece we were dragging
270           cancelMovingPiece();                              // is no longer there
271         }
272       }
273 
274       positionCopy.copyFrom(position);
275     }
276 
277   };
278 
279 
280 
281   /**
282    * The BoardPainter painting the board.
283    */
284 
285   private BoardPainter boardPainter;
286 
287 
288 
289   /**
290    * The PiecePainter painting the pieces.
291    */
292 
293   private PiecePainter piecePainter;
294 
295 
296 
297   /**
298    * PaintHooks.
299    */
300 
301   private Vector paintHooks = null;
302 
303 
304 
305   /**
306    * The current move input style.
307    */
308 
309   private int moveInputStyle = DRAG_N_DROP;
310 
311 
312 
313   /**
314    * The current move input mode.
315    */
316 
317   private int moveInputMode = ALL_PIECES_MOVE;
318 
319 
320 
321   /**
322    * Whether the piece follows the cursor as a move is being made.
323    */
324 
325   private boolean isPieceFollowsCursor = true;
326 
327 
328 
329   /**
330    * Whether the squares of the made move are highlighted.
331    */
332 
333   private boolean isHighlightMadeMoveSquares = false;
334 
335 
336 
337   /**
338    * The current move highlighting style.
339    */
340 
341   private int moveHighlightingStyle = NO_MOVE_HIGHLIGHTING;
342 
343 
344 
345   /**
346    * The current coordinates display style.
347    */
348 
349   private int coordsDisplayStyle = NO_COORDS;
350 
351 
352 
353   /**
354    * Is the board editable?
355    */
356 
357   private boolean isEditable = true;
358 
359 
360 
361   /**
362    * Is the board flipped?
363    */
364 
365   private boolean isFlipped = false;
366 
367 
368 
369   /**
370    * Are we currently manual promotion mode?
371    */
372 
373   private boolean isManualPromote = true;
374 
375 
376 
377   /**
378    * The color used for move highlighting.
379    */
380 
381   private Color moveHighlightingColor = DEFAULT_MOVE_HIGHLIGHT_COLOR;
382 
383 
384 
385   /**
386    * The color used for coordinate display. <code>null</code> means the default
387    * label color is used.
388    */
389 
390   private Color coordsDisplayColor = DEFAULT_COORDS_DISPLAY_COLOR;
391 
392 
393 
394   /**
395    * The color used for highlighting the squares of a move as it's being made.
396    */
397 
398   private Color madeMoveSquaresHighlightColor = DEFAULT_MADE_MOVE_SQUARES_HIGHLIGHT_COLOR;
399 
400 
401 
402   /**
403    * The current highlighted move.
404    */
405 
406   private Move highlightedMove = null;
407 
408 
409 
410   /**
411    * An array specifying which squares are shaded.
412    */
413 
414   private boolean [][] isShaded = new boolean[8][8];
415 
416 
417 
418   /**
419    * The square of the currently moved/dragged piece, or null if none.
420    */
421 
422   private Square movedPieceSquare = null;
423 
424 
425 
426   /**
427    * The location of the cursor when a piece is moved/dragged, null if no piece
428    * is currently moved/dragged.
429    */
430 
431   private Point movedPieceLoc = null;
432 
433 
434 
435   /**
436    * A boolean telling us whether we're currently showing the promotion target
437    * selection dialog. This is needed to workaround the bug which keeps sending
438    * events after show() has been called on a modal dialog but before it's
439    * actually displayed.
440    */
441 
442   private boolean isShowingModalDialog = false;
443 
444 
445 
446   /**
447    * Creates a new JBoard with the specified position and BoardPainter and
448    * PiecePainter.
449    */
450 
JBoard(Position position, BoardPainter boardPainter, PiecePainter piecePainter)451   public JBoard(Position position, BoardPainter boardPainter, PiecePainter piecePainter){
452     if (position == null)
453       throw new IllegalArgumentException("The Position may not be null");
454     if (boardPainter == null)
455       throw new IllegalArgumentException("The BoardPainter may not be null");
456     if (piecePainter == null)
457       throw new IllegalArgumentException("The PiecePainter may not be null");
458 
459     setPosition(position);
460     this.boardPainter = boardPainter;
461     this.piecePainter = piecePainter;
462 
463     setOpaque(true);
464     enableEvents(MouseEvent.MOUSE_EVENT_MASK |
465                  MouseEvent.MOUSE_MOTION_EVENT_MASK);
466   }
467 
468 
469 
470   /**
471    * Creates a new JBoard with the given position set on it.
472    */
473 
JBoard(Position position)474   public JBoard(Position position){
475     this(position, position.getVariant().createDefaultBoardPainter(), position.getVariant().createDefaultPiecePainter());
476   }
477 
478 
479 
480   /**
481    * Creates a new JBoard with the initial position set on it.
482    */
483 
JBoard()484   public JBoard(){
485     this(new Position());
486   }
487 
488 
489 
490   /**
491    * Adds a <code>MoveProgressListener</code>.
492    */
493 
addMoveProgressListener(MoveProgressListener listener)494   public void addMoveProgressListener(MoveProgressListener listener){
495     listenerList.add(MoveProgressListener.class, listener);
496   }
497 
498 
499 
500   /**
501    * Remove a <code>MoveProgressListener</code>.
502    */
503 
removeMoveProgressListener(MoveProgressListener listener)504   public void removeMoveProgressListener(MoveProgressListener listener){
505     listenerList.remove(MoveProgressListener.class, listener);
506   }
507 
508 
509 
510   /**
511    * Fires the specified <code>MoveProfressEvent</code> to interested listeners.
512    */
513 
fireMoveProgressEvent(MoveProgressEvent evt)514   protected void fireMoveProgressEvent(MoveProgressEvent evt){
515     int id = evt.getId();
516     Object [] listeners = listenerList.getListenerList();
517     for (int i = listeners.length-2; i>=0; i-=2 ){
518       if (listeners[i] == MoveProgressListener.class){
519         MoveProgressListener listener = (MoveProgressListener)listeners[i+1];
520         switch (id){
521           case MoveProgressEvent.MOVE_MAKING_STARTED:
522             listener.moveMakingStarted(evt);
523             break;
524           case MoveProgressEvent.MOVE_MAKING_ENDED:
525             listener.moveMakingEnded(evt);
526             break;
527         }
528       }
529     }
530   }
531 
532 
533 
534   /**
535    * Adds the given PaintHook to the list of PaintHooks which are called
536    * during the painting of this JBoard.
537    */
538 
addPaintHook(PaintHook hook)539   public void addPaintHook(PaintHook hook){
540      if (paintHooks == null)
541        paintHooks = new Vector(2);
542 
543      paintHooks.addElement(hook);
544   }
545 
546 
547 
548   /**
549    * Removes the given PaintHook from the list of PaintHooks which are called
550    * during the painting of this JBoard.
551    */
552 
removePaintHook(PaintHook hook)553   public void removePaintHook(PaintHook hook){
554     paintHooks.removeElement(hook);
555 
556     if (paintHooks.size() == 0)
557       paintHooks = null;
558   }
559 
560 
561 
562   /**
563    * Returns the Position on this JBoard.
564    */
565 
getPosition()566   public Position getPosition(){
567     return position;
568   }
569 
570 
571 
572   /**
573    * Sets this JBoard to display the given Position.
574    */
575 
setPosition(Position newPosition)576   public void setPosition(Position newPosition){
577     if (newPosition == null)
578       throw new IllegalArgumentException("Null position");
579 
580     Position oldPosition = position;
581     if (position != null)
582       position.removeChangeListener(positionChangeListener);
583     position = newPosition;
584     position.addChangeListener(positionChangeListener);
585 
586     if (positionCopy == null)
587       positionCopy = new Position(position);
588     else
589       positionCopy.copyFrom(position);
590 
591     repaint();
592 
593     firePropertyChange("position", oldPosition, newPosition);
594   }
595 
596 
597 
598   /**
599    * Sets the move input style of this JBoard to the given style. Possible values
600    * are {@link #DRAG_N_DROP} and {@link #CLICK_N_CLICK}.
601    */
602 
setMoveInputStyle(int newStyle)603   public void setMoveInputStyle(int newStyle){
604     switch(newStyle){
605       case DRAG_N_DROP:
606       case CLICK_N_CLICK:
607         break;
608       default:
609         throw new IllegalArgumentException("Illegal move input style value: "+newStyle);
610     }
611     int oldStyle = moveInputStyle;
612     moveInputStyle = newStyle;
613     firePropertyChange("moveInputStyle", oldStyle, newStyle);
614   }
615 
616 
617 
618   /**
619    * Returns the current move input style for this JBoard.
620    */
621 
getMoveInputStyle()622   public int getMoveInputStyle(){
623     return moveInputStyle;
624   }
625 
626 
627 
628   /**
629    * Sets the move input mode of this JBoard to the given mode. Possible values
630    * are {@link #NO_PIECES_MOVE}, {@link #WHITE_PIECES_MOVE}, {@link #BLACK_PIECES_MOVE},
631    * {@link #ALL_PIECES_MOVE} and {@link #CURRENT_PLAYER_MOVES}.
632    */
633 
setMoveInputMode(int newMode)634   public void setMoveInputMode(int newMode){
635     switch(newMode){
636       case NO_PIECES_MOVE:
637       case WHITE_PIECES_MOVE:
638       case BLACK_PIECES_MOVE:
639       case ALL_PIECES_MOVE:
640       case CURRENT_PLAYER_MOVES:
641         break;
642       default:
643         throw new IllegalArgumentException("Illegal move input mode value: "+newMode);
644     }
645     int oldMode = moveInputMode;
646     moveInputMode = newMode;
647     firePropertyChange("moveInputMode", oldMode, newMode);
648   }
649 
650 
651 
652   /**
653    * Returns the current move input mode for this JBoard.
654    */
655 
getMoveInputMode()656   public int getMoveInputMode(){
657     return moveInputMode;
658   }
659 
660 
661 
662   /**
663    * Sets whether pieces can at all be moved.
664    */
665 
setEditable(boolean isEditable)666   public void setEditable(boolean isEditable){
667     boolean oldEditable = this.isEditable;
668     this.isEditable = isEditable;
669     firePropertyChange("editable", oldEditable, isEditable);
670   }
671 
672 
673 
674   /**
675    * Returns <code>true</code> if the board is editable, i.e. the pieces can at
676    * all be moved.
677    */
678 
isEditable()679   public boolean isEditable(){
680     return isEditable;
681   }
682 
683 
684 
685   /**
686    * Sets whether the moved piece follows the mouse cursor while a move is being
687    * made.
688    */
689 
setPieceFollowsCursor(boolean isPieceFollowsCursor)690   public void setPieceFollowsCursor(boolean isPieceFollowsCursor){
691     boolean oldVal = this.isPieceFollowsCursor;
692     this.isPieceFollowsCursor = isPieceFollowsCursor;
693     firePropertyChange("pieceFollowsCursor", oldVal, isPieceFollowsCursor);
694   }
695 
696 
697 
698   /**
699    * Returns whether the moved piece follows the mouse cursor while a move is
700    * being made.
701    */
702 
isPieceFollowsCursor()703   public boolean isPieceFollowsCursor(){
704     return isPieceFollowsCursor;
705   }
706 
707 
708 
709   /**
710    * Sets whether the squares of a move being made are highlighted.
711    */
712 
setHighlightMadeMoveSquares(boolean highlight)713   public void setHighlightMadeMoveSquares(boolean highlight){
714     boolean oldVal = this.isHighlightMadeMoveSquares;
715     this.isHighlightMadeMoveSquares = highlight;
716     firePropertyChange("highlightMadeMoveSquares", oldVal, highlight);
717   }
718 
719 
720 
721   /**
722    * Returns whether the move being made is highlighted.
723    */
724 
isHighlightMadeMoveSquares()725   public boolean isHighlightMadeMoveSquares(){
726     return isHighlightMadeMoveSquares;
727   }
728 
729 
730 
731   /**
732    * Sets the move highlighting style.
733    */
734 
setMoveHighlightingStyle(int newStyle)735   public void setMoveHighlightingStyle(int newStyle){
736     switch(newStyle){
737       case NO_MOVE_HIGHLIGHTING:
738       case TARGET_SQUARE_MOVE_HIGHLIGHTING:
739       case BOTH_SQUARES_MOVE_HIGHLIGHTING:
740       case ARROW_MOVE_HIGHLIGHTING:
741         break;
742       default:
743         throw new IllegalArgumentException("Illegal move highlighting style value: " + newStyle);
744     }
745 
746     int oldStyle = moveHighlightingStyle;
747     this.moveHighlightingStyle = newStyle;
748     if (highlightedMove != null)
749       repaint();
750     firePropertyChange("moveHighlightingStyle", oldStyle, newStyle);
751   }
752 
753 
754 
755   /**
756    * Returns the move highlighting style.
757    */
758 
getMoveHighlightingStyle()759   public int getMoveHighlightingStyle(){
760     return moveHighlightingStyle;
761   }
762 
763 
764 
765   /**
766    * Sets the currently highlighted move, or <code>null</code> if no move
767    * should be highlighted.
768    */
769 
setHighlightedMove(Move move)770   public void setHighlightedMove(Move move){
771     repaintHighlighting();
772 
773     highlightedMove = move;
774 
775     repaintHighlighting();
776   }
777 
778 
779 
780   /**
781    * Calculates the area that needs to be repainted for the current
782    * highlighting and repaints it.
783    */
784 
repaintHighlighting()785   private void repaintHighlighting(){
786     int moveHighlightingStyle = getMoveHighlightingStyle();
787     if ((moveHighlightingStyle == NO_MOVE_HIGHLIGHTING) || (highlightedMove == null))
788       return;
789 
790     Square from = highlightedMove.getStartingSquare();
791     Square to = highlightedMove.getEndingSquare();
792 
793     if ((from == null) || (to == null))
794       return;
795 
796     if (moveHighlightingStyle == TARGET_SQUARE_MOVE_HIGHLIGHTING)
797       repaint(squareToRect(to, null));
798     if (moveHighlightingStyle == BOTH_SQUARES_MOVE_HIGHLIGHTING){
799       repaint(squareToRect(from, null));
800       repaint(squareToRect(to, null));
801     }
802     else if (moveHighlightingStyle == ARROW_MOVE_HIGHLIGHTING)
803       repaint(squareToRect(from, null).union(squareToRect(to, null)));
804   }
805 
806 
807 
808   /**
809    * Sets the coordinate display style. Possible values are {@link #NO_COORDS},
810    * {@link #RIM_COORDS}, {@link #OUTSIDE_COORDS} and
811    * {@link #EVERY_SQUARE_COORDS}.
812    */
813 
setCoordsDisplayStyle(int newStyle)814   public void setCoordsDisplayStyle(int newStyle){
815     switch (newStyle){
816       case NO_COORDS:
817       case RIM_COORDS:
818       case OUTSIDE_COORDS:
819       case EVERY_SQUARE_COORDS:
820         break;
821       default:
822         throw new IllegalArgumentException("Illegal coordinates display style value: " + newStyle);
823     }
824 
825     int oldStyle = coordsDisplayStyle;
826     this.coordsDisplayStyle = newStyle;
827     repaint();
828     firePropertyChange("coordsDisplayStyle", oldStyle, newStyle);
829   }
830 
831 
832 
833   /**
834    * Returns the current coordinates display style.
835    */
836 
getCoordsDisplayStyle()837   public int getCoordsDisplayStyle(){
838     return coordsDisplayStyle;
839   }
840 
841 
842 
843   /**
844    * Sets the board's flipped state. When the board is flipped, it displays the
845    * black side at the bottom.
846    */
847 
setFlipped(boolean isFlipped)848   public void setFlipped(boolean isFlipped){
849     boolean oldFlipped = this.isFlipped;
850     this.isFlipped = isFlipped;
851     repaint();
852     firePropertyChange("flipped", oldFlipped, isFlipped);
853   }
854 
855 
856 
857   /**
858    * Returns true if the JBoard is flipped, false otherwise.
859    */
860 
isFlipped()861   public boolean isFlipped(){
862     return isFlipped;
863   }
864 
865 
866 
867   /**
868    * Sets whether the user will be prompted which piece to promote to when a
869    * promotion occurs or will the default promotion piece be used.
870    */
871 
setManualPromote(boolean isManualPromote)872   public void setManualPromote(boolean isManualPromote){
873     boolean oldManualPromote = this.isManualPromote;
874     this.isManualPromote = isManualPromote;
875     firePropertyChange("manualPromote", oldManualPromote, isManualPromote);
876   }
877 
878 
879 
880   /**
881    * Returns true if on a promotion move, the user will be prompted which piece
882    * he wants to promote to, returns false if the default promotion piece
883    * will be used without prompting the user.
884    */
885 
isManualPromote()886   public boolean isManualPromote(){
887     return isManualPromote;
888   }
889 
890 
891 
892   /**
893    * Returns the BoardPainter of this JBoard.
894    */
895 
getBoardPainter()896   public BoardPainter getBoardPainter(){
897     return boardPainter;
898   }
899 
900 
901 
902   /**
903    * Returns the PiecePainter of this JBoard.
904    */
905 
getPiecePainter()906   public PiecePainter getPiecePainter(){
907     return piecePainter;
908   }
909 
910 
911 
912   /**
913    * Sets the BoardPainter for this JBoard. Passing <code>null</code> is
914    * equivalent to setting the board painter to the default one specified by the
915    * current position's variant.
916    */
917 
setBoardPainter(BoardPainter boardPainter)918   public void setBoardPainter(BoardPainter boardPainter){
919     if (boardPainter == null)
920       boardPainter = position.getVariant().createDefaultBoardPainter();
921 
922     Object oldBoardPainter = this.boardPainter;
923     this.boardPainter = boardPainter;
924     repaint();
925     firePropertyChange("boardPainter", oldBoardPainter, boardPainter);
926   }
927 
928 
929 
930   /**
931    * Sets the PiecePainter for this JBoard. Passing <code>null</code> is
932    * equivalent to setting the piece painter to the default one specified by the
933    * current position's variant.
934    */
935 
setPiecePainter(PiecePainter piecePainter)936   public void setPiecePainter(PiecePainter piecePainter){
937     if (piecePainter == null)
938       piecePainter = position.getVariant().createDefaultPiecePainter();
939 
940     Object oldPiecePainter = this.piecePainter;
941     this.piecePainter = piecePainter;
942     repaint();
943     firePropertyChange("piecePainter", oldPiecePainter, piecePainter);
944   }
945 
946 
947 
948   /**
949    * Sets the color used for move highlighting to the specified color. Passing
950    * <code>null</code> is equivalent to setting the color to the default one.
951    * Note: this refers to highlighting the move specified by setHighlightedMove,
952    * not the move being made by the user.
953    */
954 
setMoveHighlightingColor(Color moveHighlightingColor)955   public void setMoveHighlightingColor(Color moveHighlightingColor){
956     if (moveHighlightingColor == null)
957       moveHighlightingColor = DEFAULT_MOVE_HIGHLIGHT_COLOR;
958 
959     Object oldColor = this.moveHighlightingColor;
960     this.moveHighlightingColor = moveHighlightingColor;
961     repaint();
962     firePropertyChange("moveHighlightingColor", oldColor, moveHighlightingColor);
963   }
964 
965 
966 
967   /**
968    * Returns the color used for move highlighting.
969    */
970 
getMoveHighlightingColor()971   public Color getMoveHighlightingColor(){
972     return moveHighlightingColor;
973   }
974 
975 
976 
977   /**
978    * Sets the color used for coordinate display. Passing <code>null</code> is
979    * equivalent to setting the color to the default one.
980    */
981 
setCoordsDisplayColor(Color coordsDisplayColor)982   public void setCoordsDisplayColor(Color coordsDisplayColor){
983     if (coordsDisplayColor == null)
984       coordsDisplayColor = DEFAULT_COORDS_DISPLAY_COLOR;
985 
986     Object oldColor = this.coordsDisplayColor;
987     this.coordsDisplayColor = coordsDisplayColor;
988     repaint();
989     firePropertyChange("coordsDisplayColor", oldColor, coordsDisplayColor);
990   }
991 
992 
993 
994   /**
995    * Returns the color used for coordinate display.
996    */
997 
getCoordsDisplayColor()998   public Color getCoordsDisplayColor(){
999     return coordsDisplayColor;
1000   }
1001 
1002 
1003 
1004   /**
1005    * Sets the color used for highlighting the squares of the move being made.
1006    * This refers to the highlighting specified by the
1007    * <code>highlightMadeMoveSquares</code> property. Passing <code>null</code>
1008    * is equivalent to setting it to the default color.
1009    */
1010 
setMadeMoveSquaresHighlightColor(Color newColor)1011   public void setMadeMoveSquaresHighlightColor(Color newColor){
1012     if (newColor == null)
1013       newColor = DEFAULT_MADE_MOVE_SQUARES_HIGHLIGHT_COLOR;
1014 
1015     Object oldColor = this.madeMoveSquaresHighlightColor;
1016     this.madeMoveSquaresHighlightColor = newColor;
1017     repaint();
1018     firePropertyChange("madeMoveSquaresHighlightColor", oldColor, newColor);
1019   }
1020 
1021 
1022 
1023   /**
1024    * Returns the color used for highlighting the squares of the move being made.
1025    */
1026 
getMadeMoveSquaresHighlightColor()1027   public Color getMadeMoveSquaresHighlightColor(){
1028     return madeMoveSquaresHighlightColor;
1029   }
1030 
1031 
1032 
1033   /**
1034    * Sets the shaded state of the specified square.
1035    */
1036 
setShaded(Square square, boolean isShaded)1037   public void setShaded(Square square, boolean isShaded){
1038     boolean oldState = this.isShaded[square.getFile()][square.getRank()];
1039     this.isShaded[square.getFile()][square.getRank()] = isShaded;
1040     if (oldState != isShaded)
1041       repaint(squareToRect(square, null));
1042   }
1043 
1044 
1045 
1046   /**
1047    * Sets all the squares to the unshaded state.
1048    */
1049 
clearShaded()1050   public void clearShaded(){
1051     Rectangle helpRect = new Rectangle();
1052     for (int file = 0; file < 8; file++)
1053       for (int rank = 0; rank < 8; rank++){
1054         boolean oldState = isShaded[file][rank];
1055         isShaded[file][rank] = false;
1056         if (oldState)
1057           repaint(squareToRect(file, rank, helpRect));
1058       }
1059   }
1060 
1061 
1062 
1063   /**
1064    * Returns <code>true</code> iff the specified square is shaded.
1065    */
1066 
isShaded(Square square)1067   public boolean isShaded(Square square){
1068     return isShaded[square.getFile()][square.getRank()];
1069   }
1070 
1071 
1072 
1073   /**
1074    * Returns <code>true</code> iff a piece is currently being moved/dragged.
1075    */
1076 
isMovingPiece()1077   public boolean isMovingPiece(){
1078     return movedPieceSquare != null;
1079   }
1080 
1081 
1082 
1083   /**
1084    * If a piece is currently being moved/dragged, the moving/dragging is
1085    * canceled and the moved/dragged piece is returned to its original location.
1086    * If no piece is currently being moved/dragged, an
1087    * <code>IllegalStateException</code> is thrown.
1088    */
1089 
cancelMovingPiece()1090   public void cancelMovingPiece(){
1091     if (!isMovingPiece())
1092       throw new IllegalStateException();
1093 
1094     repaint(getMoveAreaRect(null));
1095     repaint(squareToRect(movedPieceSquare, null));
1096     movedPieceSquare = null;
1097     movedPieceLoc = null;
1098 
1099     fireMoveProgressEvent(new MoveProgressEvent(this, MoveProgressEvent.MOVE_MAKING_ENDED));
1100   }
1101 
1102 
1103 
1104   /**
1105    * Paints this JBoard on the given Graphics object.
1106    */
1107 
paintComponent(Graphics graphics)1108   public void paintComponent(Graphics graphics){
1109     super.paintComponent(graphics);
1110 
1111     Rectangle originalClip = graphics.getClipBounds();
1112 
1113     // The documentation of JComponent#paintComponent(Graphics) says we
1114     // shouldn't make permanent changes to the Graphics object, but we want to
1115     // clip it.
1116     Graphics2D g = (Graphics2D)graphics.create();
1117 
1118     g.setColor(getBackground());
1119     g.fillRect(0, 0, getWidth(), getHeight());
1120 
1121     Rectangle rect = getBoardRect(null);
1122     g.clipRect(rect.x, rect.y, rect.width, rect.height);
1123     Rectangle clipRect = g.getClipBounds();
1124 
1125     Position position = getPosition();
1126     BoardPainter boardPainter = getBoardPainter();
1127     PiecePainter piecePainter = getPiecePainter();
1128 
1129     boolean isPieceFollowsCursor = isPieceFollowsCursor();
1130     boolean isHighlightMadeMoveSquares = isHighlightMadeMoveSquares();
1131     int moveHighlightingStyle = getMoveHighlightingStyle();
1132 
1133     // Paint the board
1134     boardPainter.paintBoard(g, this, rect.x, rect.y, rect.width, rect.height);
1135 
1136     // Paint the stationary pieces
1137     for (int file = 0; file < 8; file++)
1138       for (int rank = 0; rank < 8; rank++){
1139         Square curSquare = Square.getInstance(file, rank);
1140 
1141         if (isPieceFollowsCursor && curSquare.equals(movedPieceSquare))
1142           continue;
1143 
1144         Piece piece = position.getPieceAt(curSquare);
1145         if (piece == null)
1146           continue;
1147 
1148         squareToRect(curSquare, rect);
1149         if (!rect.intersects(clipRect))
1150           continue;
1151 
1152         piecePainter.paintPiece(piece, g, this, rect, isShaded(curSquare));
1153       }
1154 
1155     // Paint move highlighting
1156     if ((moveHighlightingStyle != NO_MOVE_HIGHLIGHTING) && (highlightedMove != null)){
1157       Square from = highlightedMove.getStartingSquare();
1158       Square to = highlightedMove.getEndingSquare();
1159 
1160       squareToRect(Square.getInstance(0, 0), rect); // Just a sample square
1161       int highlightSize = Math.max(2, Math.min(rect.width, rect.height)/12);
1162       if ((from != null) && (to != null)){
1163         if (moveHighlightingStyle == BOTH_SQUARES_MOVE_HIGHLIGHTING){
1164           drawSquare(g, from, highlightSize - Math.max(1, highlightSize/3), getMoveHighlightingColor());
1165           drawSquare(g, to, highlightSize, getMoveHighlightingColor());
1166         }
1167         else if (moveHighlightingStyle == ARROW_MOVE_HIGHLIGHTING){
1168           drawArrow(g, from, to, highlightSize+1, getMoveHighlightingColor());
1169         }
1170       }
1171       if ((to != null) && (moveHighlightingStyle == TARGET_SQUARE_MOVE_HIGHLIGHTING)){
1172         drawSquare(g, to, highlightSize, getMoveHighlightingColor());
1173       }
1174     }
1175 
1176     // Paint the coordinates. Reset the original clip because of
1177     // OUTSIDE_COORDS mode, where the coordinates are drawn outside of our
1178     // usual clip rectangle.
1179     rect = g.getClipBounds();
1180     g.setClip(originalClip);
1181     drawCoords(graphics);
1182     g.setClip(rect);
1183 
1184     // Allow PaintHooks to paint
1185     callPaintHooks(g);
1186 
1187     // Paint the currently moved piece, or highlighted square
1188     if (movedPieceSquare != null){
1189       if (isHighlightMadeMoveSquares){
1190         getTargetSquareRect(rect);
1191         int targetHighlightSize = Math.max(2, Math.min(rect.width, rect.height)/15);
1192         int originHighlightSize = Math.min(2*targetHighlightSize/3, targetHighlightSize - 1);
1193 
1194         Square square = locationToSquare(rect.x, rect.y);
1195         if (square != null) // May be null if mouse is dragged out of the board
1196           drawSquare(g, square, targetHighlightSize, getMadeMoveSquaresHighlightColor());
1197 
1198         drawSquare(g, movedPieceSquare, originHighlightSize, getMadeMoveSquaresHighlightColor());
1199       }
1200       if (isPieceFollowsCursor){
1201         getMovedPieceGraphicRect(rect);
1202         Piece piece = position.getPieceAt(movedPieceSquare);
1203         piecePainter.paintPiece(piece, g, this, rect, false);
1204       }
1205     }
1206   }
1207 
1208 
1209 
1210   /**
1211    * The font we use for drawing coordinates. The size might be different in the
1212    * actual drawing though.
1213    */
1214 
1215   private static final Font COORDS_FONT = new Font("Monospaced", Font.BOLD, 10);
1216 
1217 
1218 
1219   /**
1220    * Draws the coordinates.
1221    */
1222 
drawCoords(Graphics g)1223   private void drawCoords(Graphics g){
1224     g.setColor(getCoordsDisplayColor());
1225 
1226     switch (getCoordsDisplayStyle()){
1227       case NO_COORDS: break;
1228       case RIM_COORDS: drawRimCoords(g); break;
1229       case OUTSIDE_COORDS: drawOutsideCoords(g); break;
1230       case EVERY_SQUARE_COORDS: drawEverySquareCoords(g); break;
1231       default:
1232         throw new IllegalStateException("Unknown coordinates display style value: " + getCoordsDisplayStyle());
1233     }
1234   }
1235 
1236 
1237 
1238   /**
1239    * Draws the coordinates for <code>RIM_COORDS</code> style.
1240    */
1241 
drawRimCoords(Graphics g)1242   private void drawRimCoords(Graphics g){
1243     Rectangle boardRect = getBoardRect(null);
1244     int squareWidth = boardRect.width/8;
1245     int squareHeight = boardRect.height/8;
1246 
1247     Rectangle clipRect = g.getClipBounds();
1248 
1249     int fontSize = Math.max(Math.min(squareWidth, squareHeight)/4, 8);
1250     Font font = new Font(COORDS_FONT.getName(), Font.BOLD, fontSize);
1251     g.setFont(font);
1252 
1253     FontMetrics fm = g.getFontMetrics(font);
1254     int fontWidth = fm.stringWidth("a");
1255     int fontHeight = fm.getMaxAscent() + fm.getMaxDescent();
1256 
1257     int dir = isFlipped() ? 1 : -1;
1258 
1259     // Row coordinates
1260     if (clipRect.intersects(new Rectangle(boardRect.x, boardRect.y, squareWidth/2, boardRect.height))){
1261       char row = isFlipped() ? '1' : '8';
1262       for (int i = 0; i < 8; i++){
1263         g.drawString(String.valueOf(row),
1264           boardRect.x + 3, boardRect.y + i*squareHeight + fontHeight);
1265         row += dir;
1266       }
1267     }
1268 
1269     // Column coordinates
1270     if (clipRect.intersects(new Rectangle(boardRect.x, boardRect.y + boardRect.height - squareHeight/2,
1271                                           boardRect.width, squareHeight/2))){
1272       char col = isFlipped() ? 'h' : 'a';
1273       for (int i = 0; i < 8; i++){
1274         g.drawString(String.valueOf(col),
1275           boardRect.x + (i+1)*squareWidth - fontWidth - 3, boardRect.y + boardRect.height - 3);
1276         col -= dir;
1277       }
1278     }
1279   }
1280 
1281 
1282 
1283   /**
1284    * Draws the coordinates for <code>OUTSIDE_COORDS</code> style.
1285    */
1286 
drawOutsideCoords(Graphics g)1287   private void drawOutsideCoords(Graphics g){
1288     Insets insets = getInsets();
1289     Rectangle boardRect = getBoardRect(null);
1290     int squareWidth = boardRect.width/8;
1291     int squareHeight = boardRect.height/8;
1292 
1293     Rectangle clipRect = g.getClipBounds();
1294 
1295     // IMPORTANT: If you modify this, you need to modify getBoardRect too
1296     int textWidth = squareWidth/3;
1297     int textHeight = squareHeight/3;
1298 
1299     int fontSize = Math.max(Math.min(textWidth, textHeight), 8);
1300     Font font = new Font(COORDS_FONT.getName(), COORDS_FONT.getStyle(), fontSize);
1301     g.setFont(font);
1302 
1303     FontMetrics fm = g.getFontMetrics(font);
1304     int fontWidth = fm.stringWidth("a");
1305 
1306     int dir = isFlipped() ? 1 : -1;
1307 
1308     // Row coordinates
1309     if (clipRect.intersects(new Rectangle(insets.left, insets.top, boardRect.x - insets.left, boardRect.height))){
1310       char row = isFlipped() ? '1' : '8';
1311       for (int i = 0; i < 8; i++){
1312         g.drawString(String.valueOf(row),
1313           insets.left + (boardRect.x - insets.left - fontWidth + 1)/2,
1314           boardRect.y + i*squareHeight + (squareHeight + fm.getAscent())/2 - 1);
1315         row += dir;
1316       }
1317     }
1318 
1319     // Column coordinates
1320     int bottomBorderHeight = getHeight() - boardRect.y - boardRect.height - insets.bottom;
1321     if (clipRect.intersects(new Rectangle(boardRect.x, boardRect.y + boardRect.height, boardRect.width, bottomBorderHeight))){
1322       char col = isFlipped() ? 'h' : 'a';
1323       for (int i = 0; i < 8; i++){
1324         g.drawString(String.valueOf(col),
1325           boardRect.x + i*squareWidth + (squareWidth - fontWidth)/2,
1326           boardRect.y + boardRect.height + (bottomBorderHeight + fm.getAscent())/2 - 1);
1327         col -= dir;
1328       }
1329     }
1330   }
1331 
1332 
1333 
1334   /**
1335    * Draws the coordinates for <code>EVERY_SQUARE_COORDS</code> style.
1336    */
1337 
drawEverySquareCoords(Graphics g)1338   private void drawEverySquareCoords(Graphics g){
1339     Rectangle boardRect = getBoardRect(null);
1340     int squareWidth = boardRect.width/8;
1341     int squareHeight = boardRect.height/8;
1342 
1343     Rectangle clipRect = g.getClipBounds();
1344 
1345     int fontSize = Math.max(Math.min(squareWidth, squareHeight)/5, 8);
1346 
1347     Font font = new Font(COORDS_FONT.getName(), COORDS_FONT.getStyle(), fontSize);
1348     g.setFont(font);
1349 
1350     FontMetrics fm = g.getFontMetrics(font);
1351     int fontHeight = fm.getMaxAscent() + fm.getMaxDescent();
1352 
1353     Rectangle rect = new Rectangle(boardRect.x, boardRect.y, squareWidth, squareHeight);
1354 
1355     int dir = isFlipped() ? 1 : -1;
1356     char row = isFlipped() ? '1' : '8';
1357     for (int i = 0; i < 8; i++){
1358       char col = isFlipped() ? 'h' : 'a';
1359       for (int j = 0; j < 8; j++){
1360         rect.x = boardRect.x + j*squareWidth;
1361         rect.y = boardRect.y + i*squareHeight;
1362 
1363         if (clipRect.intersects(rect))
1364           g.drawString(String.valueOf(col) + String.valueOf(row), rect.x + 3, rect.y + fontHeight);
1365 
1366         col -= dir;
1367       }
1368       row += dir;
1369     }
1370   }
1371 
1372 
1373 
1374   /**
1375    * Draws an arrow of the given size between the two specified squares on the
1376    * given <code>Graphics</code> object using the specified color.
1377    */
1378 
drawArrow(Graphics graphics, Square from, Square to, float arrowSize, Color color)1379   protected void drawArrow(Graphics graphics, Square from, Square to,
1380       float arrowSize, Color color){
1381 
1382     Graphics2D g = (Graphics2D)graphics;
1383 
1384     Rectangle fromRect = squareToRect(from, null);
1385     Rectangle toRect = squareToRect(to, null);
1386 
1387     float fromX = fromRect.x + fromRect.width/2;
1388     float fromY = fromRect.y + fromRect.height/2;
1389     float toX = toRect.x + toRect.width/2;
1390     float toY = toRect.y + toRect.height/2;
1391 
1392     double angle = Math.atan2(toY - fromY, toX - fromX);
1393     double sin = Math.sin(angle);
1394     double cos = Math.cos(angle);
1395 
1396     float cosXarrowSize = (float)(cos*arrowSize);
1397     float sinXarrowSize = (float)(sin*arrowSize);
1398     float cosXhalfArrowSize = cosXarrowSize/2.0f;
1399     float sinXhalfArrowSize = sinXarrowSize/2.0f;
1400 
1401     toX -= (int)(cos*toRect.width/2);
1402     toY -= (int)(sin*toRect.height/2);
1403 
1404     GeneralPath path = new GeneralPath();
1405 
1406     path.moveTo(fromX + sinXhalfArrowSize, fromY - cosXhalfArrowSize);
1407     path.lineTo(fromX + cosXhalfArrowSize*0.6f, fromY + sinXhalfArrowSize*0.6f);
1408     path.lineTo(fromX - sinXhalfArrowSize, fromY + cosXhalfArrowSize);
1409     path.lineTo(
1410         toX - cosXarrowSize*1.2f - sinXhalfArrowSize,
1411         toY - sinXarrowSize*1.2f + cosXhalfArrowSize);
1412     path.lineTo(
1413         (float)(toX + Math.cos(angle + Math.PI*3/4) * arrowSize*3),
1414         (float)(toY + Math.sin(angle + Math.PI*3/4) * arrowSize*3));
1415     path.lineTo(toX + cosXarrowSize, toY + sinXarrowSize);
1416     path.lineTo(
1417         (float)(toX + Math.cos(angle - Math.PI*3/4) * arrowSize*3),
1418         (float)(toY + Math.sin(angle - Math.PI*3/4) * arrowSize*3));
1419     path.lineTo(
1420         toX - cosXarrowSize*1.2f + sinXhalfArrowSize,
1421         toY - sinXarrowSize*1.2f - cosXhalfArrowSize);
1422 
1423     path.closePath();
1424 
1425     g.setColor(color);
1426     g.fill(path);
1427   }
1428 
1429 
1430 
1431   /**
1432    * Draws an outline of a square of the given size at the specified square on
1433    * the given <code>Graphics</code> object with the specific color. The size
1434    * specifies the width of the outline.
1435    */
1436 
drawSquare(Graphics graphics, Square circleSquare, int size, Color color)1437   protected void drawSquare(Graphics graphics, Square circleSquare, int size, Color color){
1438     Graphics2D g = (Graphics2D)graphics;
1439 
1440     Rectangle rect = squareToRect(circleSquare, null);
1441 
1442     g.translate(rect.x, rect.y);
1443 
1444     float halfSize = size/2.0f;
1445 
1446     GeneralPath path = new GeneralPath();
1447     path.moveTo(halfSize, halfSize);
1448     path.lineTo(rect.width - halfSize, halfSize);
1449     path.lineTo(rect.width - halfSize, rect.height - halfSize);
1450     path.lineTo(halfSize, rect.height - halfSize);
1451     path.closePath();
1452 
1453     // Mac OS X draws it differently from other platforms for some reason
1454     if (PlatformUtils.isMacOSX())
1455       path.transform(AffineTransform.getTranslateInstance(-0.5, -0.5));
1456 
1457     g.setColor(color);
1458     g.setStroke(new BasicStroke(size, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
1459 
1460     g.draw(path);
1461 
1462     g.translate(-rect.x, -rect.y);
1463   }
1464 
1465 
1466 
1467   /**
1468    * Calls all the registered PaintHooks.
1469    */
1470 
callPaintHooks(Graphics g)1471   private void callPaintHooks(Graphics g){
1472     int size = paintHooks == null ? 0 : paintHooks.size();
1473     for (int i = 0; i < size; i++){
1474       PaintHook hook = (PaintHook)paintHooks.elementAt(i);
1475       hook.paint(this, g);
1476     }
1477   }
1478 
1479 
1480 
1481   /**
1482    * Returns the rectangle actually occupied by the board, without the borders
1483    * or anything else that surrounds the board.
1484    */
1485 
getBoardRect(Rectangle rect)1486   public Rectangle getBoardRect(Rectangle rect){
1487     if (rect == null)
1488       rect = new Rectangle();
1489 
1490     Insets insets = getInsets();
1491 
1492     rect.x = insets.left;
1493     rect.y = insets.top;
1494     rect.width = getWidth() - insets.left - insets.right;
1495     rect.height = getHeight() - insets.top - insets.bottom;
1496 
1497     if (getCoordsDisplayStyle() == OUTSIDE_COORDS){
1498       int w = rect.width/30;
1499       int h = rect.height/30;
1500 
1501       rect.x += w;
1502       rect.width -= w;
1503       rect.height -= h;
1504     }
1505 
1506     rect.width -= rect.width%8;
1507     rect.height -= rect.height%8;
1508 
1509     return rect;
1510   }
1511 
1512 
1513 
1514   /**
1515    * Returns the rectangle (in pixels) of the given square.
1516    */
1517 
squareToRect(Square square, Rectangle squareRect)1518   public Rectangle squareToRect(Square square, Rectangle squareRect){
1519     return squareToRect(square.getFile(), square.getRank(), squareRect);
1520   }
1521 
1522 
1523 
1524   /**
1525    * Returns the rectangle (in pixels) of the given square.
1526    */
1527 
squareToRect(int file, int rank, Rectangle squareRect)1528   public Rectangle squareToRect(int file, int rank, Rectangle squareRect){
1529     squareRect = getBoardRect(squareRect);
1530 
1531     squareRect.width /= 8;
1532     squareRect.height /= 8;
1533 
1534     if (isFlipped()){
1535       squareRect.x += (7-file)*squareRect.width;
1536       squareRect.y += rank*squareRect.height;
1537     }
1538     else{
1539       squareRect.x += file*squareRect.width;
1540       squareRect.y += (7-rank)*squareRect.height;
1541     }
1542 
1543     return squareRect;
1544   }
1545 
1546 
1547 
1548   /**
1549    * Calculates a rectangle (in pixels) which completely contains the graphic of
1550    * the piece currently being moved, if we are in
1551    * <code>pieceFollowsCursor</code> mode.
1552    * Throws an <code>IllegalStateException</code> if we are not in in
1553    * <code>pieceFollowsCursor</code> mode or if a piece is not currently being
1554    * moved.
1555    */
1556 
getMovedPieceGraphicRect(Rectangle rect)1557   private Rectangle getMovedPieceGraphicRect(Rectangle rect){
1558     if (!isPieceFollowsCursor())
1559       throw new IllegalStateException("Not in pieceFollowsCursor mode");
1560 
1561     if (movedPieceLoc == null)
1562       throw new IllegalStateException("No piece is being moved");
1563 
1564     rect = getBoardRect(rect);
1565     int squareWidth = rect.width/8;
1566     int squareHeight = rect.height/8;
1567     rect.x = movedPieceLoc.x - squareWidth/2;
1568     rect.y = movedPieceLoc.y - squareHeight/2;
1569     rect.width = squareWidth;
1570     rect.height = squareHeight;
1571 
1572     return rect;
1573   }
1574 
1575 
1576 
1577   /**
1578    * Calculates a rectangle (in pixels) of the square under the mouse, which
1579    * needs to be highlighted when in <code>highlightMadeMoveSquares</code> mode.
1580    * Throws an <code>IllegalStateException</code> if we are not in
1581    * <code>highlightMadeMove</code> or if a piece is not currently being
1582    * moved.
1583    */
1584 
getTargetSquareRect(Rectangle rect)1585   private Rectangle getTargetSquareRect(Rectangle rect){
1586     if (!isHighlightMadeMoveSquares())
1587       throw new IllegalStateException("Not in highlightMadeMoveSquares mode");
1588 
1589     if (movedPieceLoc == null)
1590       throw new IllegalStateException("No piece is being moved");
1591 
1592     rect = getBoardRect(rect);
1593     int squareWidth = rect.width/8;
1594     int squareHeight = rect.height/8;
1595 
1596     rect.x = movedPieceLoc.x - (movedPieceLoc.x - rect.x)%squareWidth;
1597     rect.y = movedPieceLoc.y - (movedPieceLoc.y - rect.y)%squareHeight;
1598 
1599     // This is needed because the way we do rounding, it gets rounded towards
1600     // zero, which is not what we want for negative values.
1601     if (movedPieceLoc.x < 0)
1602       rect.x -= squareWidth;
1603     if (movedPieceLoc.y < 0)
1604       rect.y -= squareHeight;
1605 
1606     rect.width = squareWidth;
1607     rect.height = squareHeight;
1608 
1609     return rect;
1610   }
1611 
1612 
1613 
1614 
1615   /**
1616    * Returns a rectangle (in pixels) which completely covers the area that needs
1617    * to be redrawn when a piece is being moved.
1618    * Throws an <code>IllegalStateException</code> if a piece is not currently
1619    * being moved.
1620    */
1621 
getMoveAreaRect(Rectangle rect)1622   private Rectangle getMoveAreaRect(Rectangle rect){
1623     if (movedPieceLoc == null)
1624       throw new IllegalStateException("No piece is being moved");
1625 
1626     if (isPieceFollowsCursor()){
1627       if (isHighlightMadeMoveSquares()){
1628         if (rect == null)
1629           rect = new Rectangle();
1630 
1631         Rectangle pieceGraphic = getMovedPieceGraphicRect(null);
1632         Rectangle targetSquare = getTargetSquareRect(null);
1633 
1634         rect.setBounds(pieceGraphic.union(targetSquare));
1635 
1636         return rect;
1637       }
1638       else
1639         return getMovedPieceGraphicRect(rect);
1640     }
1641     else{
1642       if (isHighlightMadeMoveSquares())
1643         return getTargetSquareRect(rect);
1644       else{
1645         if (rect == null)
1646           return new Rectangle(0,0,0,0);
1647 
1648         rect.setBounds(0,0,0,0);
1649         return rect;
1650       }
1651     }
1652   }
1653 
1654 
1655 
1656   /**
1657    * Returns the square corresponding to the given coordinate (in pixels).
1658    * Returns null if the given location is not on the visible board.
1659    */
1660 
locationToSquare(int x, int y)1661   public Square locationToSquare(int x, int y){
1662     Rectangle boardRect = getBoardRect(null);
1663     x -= boardRect.x;
1664     y -= boardRect.y;
1665 
1666     if ((x < 0) || (y < 0))
1667       return null;
1668 
1669     int squareWidth = boardRect.width/8;
1670     int squareHeight = boardRect.height/8;
1671     int file = x/squareWidth;
1672     int rank = 7-(y/squareHeight);
1673     if ((file > 7) || (rank < 0))
1674       return null;
1675 
1676     if (isFlipped())
1677       return Square.getInstance(7-file, 7-rank);
1678     else
1679       return Square.getInstance(file, rank);
1680   }
1681 
1682 
1683 
1684   /**
1685    * Returns the square corresponding to the given coordinate (in pixels).
1686    */
1687 
locationToSquare(Point p)1688   public Square locationToSquare(Point p){
1689     return locationToSquare(p.x, p.y);
1690   }
1691 
1692 
1693 
1694   /**
1695    * Makes sure no events are dispatched to this JBoard while its showing a modal
1696    * dialog.
1697    */
1698 
processEvent(AWTEvent evt)1699   protected void processEvent(AWTEvent evt){
1700     if (!isShowingModalDialog)
1701       super.processEvent(evt);
1702   }
1703 
1704 
1705 
1706   /**
1707    * Returns whether the given piece can be moved, considering the current move
1708    * mode.
1709    */
1710 
canBeMoved(Piece piece)1711   private boolean canBeMoved(Piece piece){
1712     switch(getMoveInputMode()){
1713       case NO_PIECES_MOVE:
1714         return false;
1715       case WHITE_PIECES_MOVE:
1716         return piece.isWhite();
1717       case BLACK_PIECES_MOVE:
1718         return piece.isBlack();
1719       case ALL_PIECES_MOVE:
1720         return true;
1721       case CURRENT_PLAYER_MOVES:
1722         Player player = getPosition().getCurrentPlayer();
1723         return (player.isWhite()&&piece.isWhite())||(player.isBlack()&&piece.isBlack());
1724     }
1725     throw new IllegalStateException();
1726   }
1727 
1728 
1729 
1730   /**
1731    * Processes a mouse event.
1732    */
1733 
processMouseEvent(MouseEvent evt)1734   protected void processMouseEvent(MouseEvent evt){
1735     super.processMouseEvent(evt);
1736 
1737     if (!(isEnabled() && isEditable()))
1738       return;
1739 
1740     boolean isLeftMouseButton = SwingUtilities.isLeftMouseButton(evt);
1741 
1742     int evtID = evt.getID();
1743 
1744     int inputStyle = getMoveInputStyle();
1745 
1746     int x = evt.getX();
1747     int y = evt.getY();
1748 
1749     Rectangle helpRect = null;
1750 
1751     if ((evtID == MouseEvent.MOUSE_EXITED) && (inputStyle == CLICK_N_CLICK)){
1752       if (movedPieceSquare != null){ // Fake the piece being at its original location
1753         repaint(helpRect = getMoveAreaRect(helpRect));
1754         squareToRect(movedPieceSquare, helpRect);
1755         movedPieceLoc.x = helpRect.x + helpRect.width/2;
1756         movedPieceLoc.y = helpRect.y + helpRect.height/2;
1757         repaint(getMoveAreaRect(helpRect));
1758       }
1759     }
1760 
1761     Square square = locationToSquare(x,y);
1762 
1763     if (square == null){
1764       if ((evtID == MouseEvent.MOUSE_RELEASED) && isMovingPiece())
1765         cancelMovingPiece();
1766       return;
1767     }
1768 
1769     if (isLeftMouseButton && ((evtID == MouseEvent.MOUSE_PRESSED) ||
1770        ((evtID == MouseEvent.MOUSE_RELEASED) && (inputStyle == DRAG_N_DROP)))){
1771       if (movedPieceSquare == null){
1772 
1773         // This happens if the user tries to drag an empty square into a piece.
1774         if (evtID == MouseEvent.MOUSE_RELEASED)
1775           return;
1776 
1777         movedPieceSquare = square;
1778         Piece piece = position.getPieceAt(movedPieceSquare);
1779         if ((piece == null) || (!canBeMoved(piece))){
1780           movedPieceSquare = null;
1781           movedPieceLoc = null;
1782           return;
1783         }
1784         movedPieceLoc = new Point(x, y);
1785 
1786         repaint(helpRect = squareToRect(square, helpRect));
1787         if (isPieceFollowsCursor())
1788           repaint(helpRect = getMovedPieceGraphicRect(helpRect));
1789 
1790         fireMoveProgressEvent(new MoveProgressEvent(this, MoveProgressEvent.MOVE_MAKING_STARTED));
1791       }
1792       else{
1793         if (!square.equals(movedPieceSquare)){
1794           WildVariant variant = position.getVariant();
1795           Piece [] promotionTargets = variant.getPromotionTargets(position, movedPieceSquare, square);
1796           Move madeMove;
1797           if (promotionTargets != null){
1798             Piece promotionTarget;
1799             if (isManualPromote()){
1800               isShowingModalDialog = true;
1801               promotionTarget = PieceChooser.showPieceChooser(this, x, y, promotionTargets, getPiecePainter(), promotionTargets[0]);
1802               isShowingModalDialog = false;
1803             }
1804             else
1805               promotionTarget = promotionTargets[0];
1806 
1807             madeMove = variant.createMove(position, movedPieceSquare, square, promotionTarget, null);
1808           }
1809           else
1810             madeMove = variant.createMove(position, movedPieceSquare, square, null, null);
1811 
1812           position.makeMove(madeMove);
1813         }
1814         else{ // Picked up the piece and dropped it immediately.
1815           repaint(helpRect = getMoveAreaRect(helpRect));
1816           repaint(helpRect = squareToRect(movedPieceSquare, helpRect));
1817         }
1818 
1819         movedPieceSquare = null;
1820         movedPieceLoc = null;
1821 
1822         fireMoveProgressEvent(new MoveProgressEvent(this, MoveProgressEvent.MOVE_MAKING_ENDED));
1823       }
1824     }
1825   }
1826 
1827 
1828 
1829   /**
1830    * Processes a mouse motion event.
1831    */
1832 
processMouseMotionEvent(MouseEvent evt)1833   protected void processMouseMotionEvent(MouseEvent evt){
1834     super.processMouseMotionEvent(evt);
1835 
1836     if (!(isEnabled() && isEditable()))
1837       return;
1838 
1839     int evtID = evt.getID();
1840 
1841     if ((evtID == MouseEvent.MOUSE_DRAGGED) && !SwingUtilities.isLeftMouseButton(evt))
1842       return;
1843 
1844     int inputStyle = getMoveInputStyle();
1845     if (movedPieceSquare == null)
1846       return;
1847 
1848     int x = evt.getX();
1849     int y = evt.getY();
1850 
1851     Rectangle helpRect = null;
1852 
1853     if ((evtID == MouseEvent.MOUSE_DRAGGED) ||
1854        ((evtID == MouseEvent.MOUSE_MOVED) && (inputStyle == CLICK_N_CLICK))){
1855       repaint(helpRect = getMoveAreaRect(helpRect));
1856 
1857       if ((locationToSquare(x, y) == null) && (inputStyle == CLICK_N_CLICK)){
1858         // Fake the piece being at its original location
1859         squareToRect(movedPieceSquare, helpRect);
1860         movedPieceLoc.x = helpRect.x + helpRect.width/2;
1861         movedPieceLoc.y = helpRect.y + helpRect.height/2;
1862       }
1863       else{
1864         movedPieceLoc.x = x;
1865         movedPieceLoc.y = y;
1866       }
1867       repaint(helpRect = getMoveAreaRect(helpRect));
1868     }
1869   }
1870 
1871 
1872 
1873   /**
1874    * Displays a simple <code>JFrame</code> with a <code>JBoard</code>.
1875    */
1876 
main(String [] args)1877   public static void main(String [] args){
1878     javax.swing.JFrame frame = new javax.swing.JFrame("JBoard Test");
1879     frame.addWindowListener(new free.util.AppKiller());
1880     frame.getContentPane().setLayout(new java.awt.BorderLayout());
1881     final JBoard board = new JBoard();
1882     board.setBorder(new javax.swing.border.MatteBorder(30, 40, 50, 60, Color.red));
1883     board.setMoveInputStyle(CLICK_N_CLICK);
1884     board.setFlipped(true);
1885     board.setCoordsDisplayStyle(RIM_COORDS);
1886     frame.getContentPane().add(board, java.awt.BorderLayout.CENTER);
1887     frame.setBounds(50, 50, 400, 400);
1888     frame.setVisible(true);
1889   }
1890 
1891 
1892 
1893 }
1894