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