1# -*- coding: UTF-8 -*- 2 3 4from math import floor, ceil, pi 5from time import time 6from io import StringIO 7 8import cairo 9from gi.repository import GLib, Gtk, Gdk, GObject, Pango, PangoCairo 10 11from pychess.Savers import pgn 12from pychess.System.prefix import addDataPrefix 13from pychess.System import conf 14from pychess.gfx import Pieces 15from pychess.Savers.pgn import comment_arrows_re, comment_circles_re 16from pychess.Utils.Cord import Cord 17from pychess.Utils.GameModel import GameModel 18from pychess.Utils.const import ASEAN_VARIANTS, DROP_VARIANTS, WAITING_TO_START, REMOTE, \ 19 LOCAL, DRAW, WHITEWON, BLACKWON, ABORTED, KILLED, DROP, \ 20 KING_CASTLE, QUEEN_CASTLE, WILDCASTLESHUFFLECHESS, \ 21 WILDCASTLECHESS, PAWN, KNIGHT, SITTUYINCHESS, BLACK 22from pychess.Variants.blindfold import BlindfoldBoard, HiddenPawnsBoard, \ 23 HiddenPiecesBoard, AllWhiteBoard 24from . import preferencesDialog 25from pychess.perspectives import perspective_manager 26 27# This file contains the class that is used to draw the board 28 29 30# util functions on rectangles to redner board used by the class 31 32 33def intersects(r_zero, r_one): 34 """ Takes two square and determines if they have an Intersection 35 Returns a boolean 36 """ 37 w_zero = r_zero.width + r_zero.x 38 h_zero = r_zero.height + r_zero.y 39 w_one = r_one.width + r_one.x 40 h_one = r_one.height + r_one.y 41 return (w_one < r_one.x or w_one > r_zero.x) and \ 42 (h_one < r_one.y or h_one > r_zero.y) and \ 43 (w_zero < r_zero.x or w_zero > r_one.x) and \ 44 (h_zero < r_zero.y or h_zero > r_one.y) 45 46 47def contains(r_zero, r_one): 48 """ Takes two squares and determines if square one is contained 49 within square zero 50 Returns a boolean 51 """ 52 w_zero = r_zero.width + r_zero.x 53 h_zero = r_zero.height + r_zero.y 54 w_one = r_one.width + r_one.x 55 h_one = r_one.height + r_one.y 56 return \ 57 r_zero.x <= r_one.x and w_zero >= w_one and \ 58 r_zero.y <= r_one.y and h_zero >= h_one 59 60 61def union(r_zero, r_one): 62 """ Takes 2 rectangles and returns a rectangle that represents 63 the union of the two areas 64 Returns a Gdk.Rectangle 65 """ 66 x_min = min(r_zero.x, r_one.x) 67 y_min = min(r_zero.y, r_one.y) 68 w_max = max(r_zero.x + r_zero.width, r_one.x + r_one.width) - x_min 69 h_max = max(r_zero.y + r_zero.height, r_one.y + r_one.height) - y_min 70 rct = Gdk.Rectangle() 71 rct.x, rct.y, rct.width, rct.height = (x_min, y_min, w_max, h_max) 72 return rct 73 74 75def join(r_zero, r_one): 76 """ Take(x, y, w, [h]) squares """ 77 78 if not r_zero: 79 return r_one 80 if not r_one: 81 return r_zero 82 if not r_zero and not r_one: 83 return None 84 85 if len(r_zero) == 3: 86 r_zero = (r_zero[0], r_zero[1], r_zero[2], r_zero[2]) 87 if len(r_one) == 3: 88 r_one = (r_one[0], r_one[1], r_one[2], r_one[2]) 89 90 x_one = min(r_zero[0], r_one[0]) 91 x_two = max(r_zero[0] + r_zero[2], r_one[0] + r_one[2]) 92 y_one = min(r_zero[1], r_one[1]) 93 y_two = max(r_zero[1] + r_zero[3], r_one[1] + r_one[3]) 94 95 return (x_one, y_one, x_two - x_one, y_two - y_one) 96 97 98def rect(rectangle): 99 """ 100 Takes a list of 3 variables x,y,height and generates a rectangle 101 rectangle(list) : contains screen locations 102 returns a Gdk.Rectangle 103 """ 104 x_size, y_size = [int(floor(v)) for v in rectangle[:2]] 105 width = int(ceil(rectangle[2])) 106 if len(rectangle) == 4: 107 height = int(ceil(rectangle[3])) 108 else: 109 height = width 110 rct = Gdk.Rectangle() 111 rct.x, rct.y, rct.width, rct.height = (x_size, y_size, width, height) 112 return rct 113 114 115def matrixAround(rotated_matrix, anchor_x, anchor_y): 116 """ 117 Description : Rotates a matrix through the hypotenuse so that the original 118 matrix becomes the inverse matrix and the inverse matrix becomes matrix 119 Returns a tuple representing the matrix and its inverse 120 121 """ 122 corner = rotated_matrix[0] 123 side = rotated_matrix[1] 124 anchor_yside = anchor_y * side 125 anchor_xside = anchor_x * side 126 anchor_ycorner = anchor_y * (1 - corner) 127 anchor_xcorner = anchor_x * (1 - corner) 128 matrix = cairo.Matrix(corner, side, -side, corner, 129 anchor_xcorner + anchor_yside, 130 anchor_ycorner - anchor_xside) 131 invmatrix = cairo.Matrix(corner, -side, side, corner, 132 anchor_xcorner - anchor_yside, 133 anchor_ycorner + anchor_xside) 134 return matrix, invmatrix 135 136 137ANIMATION_TIME = 0.5 138 139# If this is true, the board is scaled so that everything fits inside the window 140# even if the board is rotated 45 degrees 141SCALE_ROTATED_BOARD = False 142 143CORD_PADDING = 1.5 144 145 146class BoardView(Gtk.DrawingArea): 147 """ Description The BoardView instance is used to render the board to screen and supports 148 event updates associated with the game 149 """ 150 151 __gsignals__ = { # Signals emitted by class 152 'shownChanged': (GObject.SignalFlags.RUN_FIRST, None, (int,)) 153 } 154 155 def __init__(self, gamemodel=None, preview=False, setup_position=False): 156 GObject.GObject.__init__(self) 157 158 if gamemodel is None: 159 gamemodel = GameModel() 160 self.model = gamemodel 161 162 self.allwhite = self.model.variant == AllWhiteBoard 163 self.asean = self.model.variant.variant in ASEAN_VARIANTS 164 self.preview = preview 165 self.setup_position = setup_position 166 self.shown_variation_idx = 0 # the main variation is the first in gamemodel.variations list 167 168 self.model_cids = [ 169 self.model.connect("game_started", self.gameStarted), 170 self.model.connect("game_changed", self.gameChanged), 171 self.model.connect("moves_undoing", self.movesUndoing), 172 self.model.connect("variation_undoing", self.variationUndoing), 173 self.model.connect("game_loading", self.gameLoading), 174 self.model.connect("game_loaded", self.gameLoaded), 175 self.model.connect("game_ended", self.gameEnded), 176 ] 177 178 self.board_style_name = None 179 self.board_frame_name = None 180 181 self.draw_cid = self.connect("draw", self.expose) 182 self.realize_cid = self.connect_after("realize", self.onRealized) 183 self.notify_cids = [ 184 conf.notify_add("drawGrid", self.onDrawGrid), 185 conf.notify_add("showCords", self.onShowCords), 186 conf.notify_add("showCaptured", self.onShowCaptured), 187 conf.notify_add("faceToFace", self.onFaceToFace), 188 conf.notify_add("noAnimation", self.onNoAnimation), 189 conf.notify_add("autoRotate", self.onAutoRotate), 190 conf.notify_add("pieceTheme", self.onPieceTheme), 191 conf.notify_add("board_frame", self.onBoardFrame), 192 conf.notify_add("board_style", self.onBoardStyle), 193 conf.notify_add("lightcolour", self.onBoardColour), 194 conf.notify_add("darkcolour", self.onBoardColour), 195 conf.notify_add("activateSupportAlgorithm", self.onSupportAlgorithmActivation) 196 ] 197 self.RANKS = self.model.boards[0].RANKS 198 self.FILES = self.model.boards[0].FILES 199 self.FILES_FOR_HOLDING = 6 200 201 self.animation_start = time() 202 self.last_shown = None 203 self.deadlist = [] 204 205 self.auto_update_shown = True 206 207 self.real_set_shown = True 208 # only false when self.shown set temporarily(change shown variation) 209 # to avoid redraw_misc in animation 210 211 self.padding = 0 # Set to self.pad when setcords is active 212 self.square = 0, 0, self.FILES, 1 # An object global variable with the current 213 # board size 214 self.pad = 0.06 # Padding applied only when setcords is active 215 216 self._selected = None 217 self._hover = None 218 self._active = None 219 self._premove0 = None 220 self._premove1 = None 221 self._redarrow = None 222 self._greenarrow = None 223 self._bluearrow = None 224 225 # this is an integer that contains the last move that is shown 226 self._shown = self.model.ply 227 228 self.no_frame = False 229 self._show_cords = False 230 self.show_cords = conf.get("showCords") 231 232 self._draw_grid = False 233 self.draw_grid = conf.get("drawGrid") 234 235 self._show_captured = None 236 if self.setup_position: 237 self.set_size_request(int(40 * (self.FILES + self.FILES_FOR_HOLDING)), 40 * self.RANKS) 238 self.redrawCanvas() 239 240 self.noAnimation = conf.get("noAnimation") 241 self.faceToFace = conf.get("faceToFace") 242 self.autoRotate = conf.get("autoRotate") 243 if conf.get("activateSupportAlgorithm"): 244 self.onSupportAlgorithmActivation() 245 246 self.onBoardColour() 247 self.onBoardStyle() 248 self.onBoardFrame() 249 250 self._show_enpassant = False 251 252 self.lastMove = None 253 254 self.matrix = cairo.Matrix() 255 self.matrix_pi = cairo.Matrix.init_rotate(pi) 256 self.invmatrix = cairo.Matrix().invert() 257 self.cord_matrices_state = (0, 0) 258 self._rotation = 0 259 260 self.drawcount = 0 261 self.drawtime = 0 262 263 self.got_started = False 264 self.animating = False 265 266 self.dragged_piece = None # a piece being dragged by the user 267 self.premove_piece = None 268 self.premove_promotion = None 269 270 # right click circles and arrows 271 # Contains Cord object, and are the coordinates in which we need to draw the circles or arrows 272 self.arrows = set() 273 self.circles = set() 274 self.pre_arrow = None 275 self.pre_circle = None 276 277 # circles and arrows from .pgn comments 278 self.saved_circles = set() 279 self.saved_arrows = set() 280 281 # store in memory the last number of move to know whether or not a new turn has started, 282 # of we went back to history 283 self.last_shown = 0 284 285 def _del(self): 286 self.disconnect(self.draw_cid) 287 self.disconnect(self.realize_cid) 288 for cid in self.notify_cids: 289 conf.notify_remove(cid) 290 for cid in self.model_cids: 291 self.model.disconnect(cid) 292 293 def gameStarted(self, model): 294 if model.lesson_game: 295 self.shown = model.lowply 296 297 if self.noAnimation: 298 self.got_started = True 299 self.redrawCanvas() 300 else: 301 if model.moves: 302 self.lastMove = model.moves[-1] 303 304 for row in self.model.boards[-1].data: 305 for piece in row.values(): # row: 306 if piece: 307 piece.opacity = 0 308 309 self.got_started = True 310 self.startAnimation() 311 312 self.emit("shownChanged", self.shown) 313 314 def gameChanged(self, model, ply): 315 # Play sounds 316 if self.model.players and self.model.status != WAITING_TO_START: 317 move = model.moves[-1] 318 if move.is_capture(model.boards[-2]): 319 sound = "aPlayerCaptures" 320 else: 321 sound = "aPlayerMoves" 322 323 if model.boards[-1].board.isChecked(): 324 sound = "aPlayerChecks" 325 326 if model.players[0].__type__ == REMOTE and \ 327 model.players[1].__type__ == REMOTE: 328 sound = "observedMoves" 329 330 preferencesDialog.SoundTab.playAction(sound) 331 332 # Auto updating self.shown can be disabled. Useful for loading games. 333 # If we are not at the latest game we are probably browsing the history, 334 # and we won't like auto updating. 335 if self.auto_update_shown and self.shown + 1 >= ply and self.shownIsMainLine(): 336 self.shown = ply 337 338 # Rotate board 339 if self.autoRotate: 340 if self.model.players and self.model.curplayer.__type__ == LOCAL: 341 self.rotation = self.model.boards[-1].color * pi 342 343 def movesUndoing(self, model, moves): 344 if self.shownIsMainLine(): 345 self.shown = model.ply - moves 346 else: 347 # Go back to the mainline to let animation system work 348 board = model.getBoardAtPly(self.shown, self.shown_variation_idx) 349 while board not in model.variations[0]: 350 board = model.variations[self.shown_variation_idx][board.ply - model.lowply - 1] 351 self.shown = board.ply 352 self.shown_variation_idx = 0 353 self.shown = model.ply - moves 354 self.redrawCanvas() 355 356 def variationUndoing(self, model): 357 self.showPrev() 358 359 def gameLoading(self, model, uri): 360 self.auto_update_shown = False 361 362 def gameLoaded(self, model, uri): 363 self.auto_update_shown = True 364 self._shown = model.ply 365 366 def gameEnded(self, model, reason): 367 self.redrawCanvas() 368 369 if self.model.players: 370 sound = "" 371 372 if model.status == DRAW: 373 sound = "gameIsDrawn" 374 elif model.status == WHITEWON: 375 if model.players[0].__type__ == LOCAL: 376 sound = "gameIsWon" 377 elif model.players[1].__type__ == LOCAL: 378 sound = "gameIsLost" 379 elif model.status == BLACKWON: 380 if model.players[1].__type__ == LOCAL: 381 sound = "gameIsWon" 382 elif model.players[0].__type__ == LOCAL: 383 sound = "gameIsLost" 384 elif model.status in (ABORTED, KILLED): 385 sound = "gameIsLost" 386 387 if model.status in (DRAW, WHITEWON, BLACKWON, KILLED, ABORTED) and \ 388 model.players[0].__type__ == REMOTE and \ 389 model.players[1].__type__ == REMOTE: 390 sound = "oberservedEnds" 391 392 # This should never be false, unless status is set to UNKNOWN or 393 # something strange 394 if sound != "": 395 preferencesDialog.SoundTab.playAction(sound) 396 397 def onDrawGrid(self, *args): 398 """ Checks the configuration / preferences to see if the board 399 grid should be displayed. 400 """ 401 self.draw_grid = conf.get("drawGrid") 402 403 def onShowCords(self, *args): 404 """ Checks the configuration / preferences to see if the board 405 co-ordinates should be displayed. 406 """ 407 self.show_cords = conf.get("showCords") 408 409 def onShowCaptured(self, *args): 410 """ Check the configuration / preferences to see if 411 the captured pieces should be displayed 412 """ 413 self._setShowCaptured(conf.get("showCaptured"), force_restore=True) 414 415 def onNoAnimation(self, *args): 416 """ Check the configuration / preferences to see if 417 no animation needed at all 418 """ 419 self.noAnimation = conf.get("noAnimation") 420 421 def onFaceToFace(self, *args): 422 """ If the preference for pieces to be displayed facing each other 423 has been set then refresh the board 424 """ 425 self.faceToFace = conf.get("faceToFace") 426 self.redrawCanvas() 427 428 def onAutoRotate(self, *args): 429 self.autoRotate = conf.get("autoRotate") 430 431 def onPieceTheme(self, *args): 432 """ If the preference to display another chess set has been 433 selected then refresh the board 434 """ 435 self.redrawCanvas() 436 437 def onBoardColour(self, *args): 438 """ If the preference to display another set of board colours has been 439 selected then refresh the board 440 """ 441 self.light_colour = conf.get("lightcolour") 442 self.dark_colour = conf.get("darkcolour") 443 self.redrawCanvas() 444 445 def onBoardStyle(self, *args): 446 """ If the preference to display another set of board colours has been 447 selected then refresh the board 448 """ 449 board_style = conf.get("board_style") 450 self.colors_only = board_style == 0 451 if not self.colors_only: 452 # create dark and light square surfaces 453 board_style_name = preferencesDialog.board_items[board_style][1] 454 if self.board_style_name is None or self.board_style_name != board_style_name: 455 self.board_style_name = board_style_name 456 dark_png = addDataPrefix("boards/%s_d.png" % board_style_name) 457 light_png = addDataPrefix("boards/%s_l.png" % board_style_name) 458 self.dark_surface = cairo.ImageSurface.create_from_png(dark_png) 459 self.light_surface = cairo.ImageSurface.create_from_png(light_png) 460 461 self.redrawCanvas() 462 463 def onBoardFrame(self, *args): 464 board_frame = conf.get("board_frame") 465 self.no_frame = board_frame == 0 466 if not self.no_frame: 467 # create board frame surface 468 board_frame_name = preferencesDialog.board_items[board_frame][1] 469 if self.board_frame_name is None or self.board_frame_name != board_frame_name: 470 self.board_frame_name = board_frame_name 471 frame_png = addDataPrefix("boards/%s_d.png" % board_frame_name) 472 self.frame_surface = cairo.ImageSurface.create_from_png(frame_png) 473 474 if not self.show_cords and self.no_frame: 475 self.padding = 0. 476 else: 477 self.padding = self.pad 478 479 self.redrawCanvas() 480 481 def onSupportAlgorithmActivation(self, *args): 482 '''activation of the support algorithm''' 483 activation_wanted = conf.get("activateSupportAlgorithm") 484 self.model.support_algorithm.enableDisableAlgo(activation_wanted) 485 486 ############################### 487 # Animation # 488 ############################### 489 490 def paintBoxAround(self, move): 491 paint_box = self.cord2RectRelative(move.cord1) 492 if move.flag != DROP: 493 paint_box = join(paint_box, self.cord2RectRelative(move.cord0)) 494 if move.flag in (KING_CASTLE, QUEEN_CASTLE): 495 board = self.model.boards[-1].board 496 color = board.color 497 wildcastle = \ 498 Cord(board.ini_kings[color]).x == 3 and \ 499 board.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS) 500 if move.flag == KING_CASTLE: 501 side = 0 if wildcastle else 1 502 paint_box = join(paint_box, self.cord2RectRelative(Cord(board.ini_rooks[color][side]))) 503 paint_box = join(paint_box, self.cord2RectRelative(Cord(board.fin_rooks[color][side]))) 504 paint_box = join(paint_box, self.cord2RectRelative(Cord(board.fin_kings[color][side]))) 505 else: 506 side = 1 if wildcastle else 0 507 paint_box = join(paint_box, self.cord2RectRelative(Cord(board.ini_rooks[color][side]))) 508 paint_box = join(paint_box, self.cord2RectRelative(Cord(board.fin_rooks[color][side]))) 509 paint_box = join(paint_box, self.cord2RectRelative(Cord(board.fin_kings[color][side]))) 510 return paint_box 511 512 def setShownBoard(self, board): 513 """ Set shown to the index of the given board in board list. 514 If the board belongs to a different variationd, 515 adjust the shown variation index too. 516 If board is in the main line, reset the shown variation idx to 0(the main line). 517 """ 518 if board in self.model.variations[self.shown_variation_idx]: 519 # if the board to be shown is in the current shown variation, we are ok 520 self.shown = \ 521 self.model.variations[self.shown_variation_idx].index(board) + \ 522 self.model.lowply 523 else: 524 # else we have to go back first 525 for vari in self.model.variations: 526 if board in vari: 527 # Go back to the common board of variations to let animation system work 528 board_in_vari = board 529 while board_in_vari not in self.model.variations[self.shown_variation_idx]: 530 board_in_vari = vari[board_in_vari.ply - self.model.lowply - 1] 531 self.real_set_shown = False 532 self.shown = board_in_vari.ply 533 break 534 # swich to the new variation 535 self.shown_variation_idx = self.model.variations.index(vari) 536 self.real_set_shown = True 537 self.shown = \ 538 self.model.variations[self.shown_variation_idx].index(board) + \ 539 self.model.lowply 540 541 def shownIsMainLine(self): 542 return self.shown_variation_idx == 0 543 544 @property 545 def has_unsaved_shapes(self): 546 return self.saved_arrows != self.arrows or self.saved_circles != self.circles 547 548 def _getShown(self): 549 return self._shown 550 551 def _setShown(self, shown, old_variation_idx=None): 552 """ 553 Adjust the index in current variation board list. 554 old_variation_index is used when variation was added 555 to the last played move and we want to step back. 556 557 This function is called before draw, so this is where we preprocess data before drawing 558 This function is called at each turn 559 """ 560 assert shown >= 0 561 if shown < self.model.lowply: 562 shown = self.model.lowply 563 564 # This would cause IndexErrors later 565 if shown > self.model.variations[self.shown_variation_idx][-1].ply: 566 return 567 568 if old_variation_idx is None: 569 old_variation_idx = self.shown_variation_idx 570 571 self.redarrow = None 572 self.greenarrow = None 573 self.bluearrow = None 574 575 # remove all circles and arrows 576 need_redraw = False 577 if self.saved_circles: 578 self.saved_circles.clear() 579 need_redraw = True 580 if self.saved_arrows: 581 self.saved_arrows.clear() 582 need_redraw = True 583 if self.arrows: 584 self.arrows.clear() 585 need_redraw = True 586 if self.circles: 587 self.circles.clear() 588 need_redraw = True 589 if self.pre_arrow is not None: 590 self.pre_arrow = None 591 need_redraw = True 592 if self.pre_circle is not None: 593 self.pre_circle = None 594 need_redraw = True 595 596 if (self.shown != shown) and not self.setup_position: 597 algorithm = self.model.support_algorithm 598 x = shown - self.model.lowply 599 if x >= len(self.model.boards): 600 x = -1 601 algorithm.calculate_coordinate_in_danger( 602 self.model.boards[x], (shown % 2) 603 ) 604 need_redraw = True 605 606 # search circles/arrows in move comments 607 board = self.model.getBoardAtPly(shown, self.shown_variation_idx).board 608 if board.children: 609 for child in board.children: 610 if isinstance(child, str): 611 if "[%csl" in child: 612 match = comment_circles_re.search(child) 613 circles = match.groups()[0].split(",") 614 for circle in circles: 615 self.saved_circles.add(Cord(circle[1:3], color=circle[0])) 616 self.circles.add(Cord(circle[1:3], color=circle[0])) 617 need_redraw = True 618 619 if "[%cal" in child: 620 match = comment_arrows_re.search(child) 621 arrows = match.groups()[0].split(",") 622 for arrow in arrows: 623 self.saved_arrows.add((Cord(arrow[1:3], color=arrow[0]), Cord(arrow[3:5]))) 624 self.arrows.add((Cord(arrow[1:3], color=arrow[0]), Cord(arrow[3:5]))) 625 need_redraw = True 626 627 if need_redraw: 628 self.redrawCanvas() 629 630 # If there is only one board, we don't do any animation, but simply 631 # redraw the entire board. Same if we are at first draw. 632 if len(self.model.boards) == 1 or self.shown < self.model.lowply: 633 self._shown = shown 634 if shown > self.model.lowply: 635 self.lastMove = self.model.getMoveAtPly(shown - 1, self.shown_variation_idx) 636 self.emit("shownChanged", self.shown) 637 self.redrawCanvas() 638 return 639 640 step = shown > self.shown and 1 or -1 641 642 deadset = set() 643 for i in range(self.shown, shown, step): 644 board = self.model.getBoardAtPly(i, old_variation_idx) 645 board1 = self.model.getBoardAtPly(i + step, self.shown_variation_idx) 646 if step == 1: 647 move = self.model.getMoveAtPly(i, self.shown_variation_idx) 648 moved, new, dead = board.simulateMove(board1, move) 649 else: 650 move = self.model.getMoveAtPly(i - 1, old_variation_idx) 651 moved, new, dead = board.simulateUnmove(board1, move) 652 653 # We need to ensure, that the piece coordinate is saved in the 654 # piece 655 for piece, cord0 in moved: 656 # Test if the piece already has a realcoord(has been dragged) 657 if (piece is not None) and piece.x is None: 658 # We don't want newly restored pieces to flew from their 659 # deadspot to their old position, as it doesn't work 660 # vice versa 661 if piece.opacity == 1: 662 piece.x = cord0.x 663 piece.y = cord0.y 664 665 for piece in dead: 666 deadset.add(piece) 667 # Reset the location of the piece to avoid a small visual 668 # jump, when it is at some other time waken to life. 669 piece.x = None 670 piece.y = None 671 672 for piece in new: 673 piece.opacity = 0 674 675 self.deadlist = [] 676 for y_loc, row in enumerate(self.model.getBoardAtPly(self.shown, old_variation_idx).data): 677 for x_loc, piece in row.items(): 678 if piece in deadset: 679 self.deadlist.append((piece, x_loc, y_loc)) 680 681 self._shown = shown 682 if self.real_set_shown: 683 board = self.model.getBoardAtPly(self.shown, self.shown_variation_idx) 684 if board in self.model.variations[0]: 685 self.shown_variation_idx = 0 686 else: 687 for vari in self.model.variations: 688 if board in vari: 689 # swich to the new variation 690 self.shown_variation_idx = self.model.variations.index(vari) 691 break 692 self.emit("shownChanged", self.shown) 693 694 self.animation_start = time() 695 self.animating = True 696 697 if self.lastMove: 698 paint_box = self.paintBoxAround(self.lastMove) 699 self.lastMove = None 700 self.redrawCanvas(rect(paint_box)) 701 702 if self.shown > self.model.lowply: 703 self.lastMove = self.model.getMoveAtPly(self.shown - 1, self.shown_variation_idx) 704 paint_box = self.paintBoxAround(self.lastMove) 705 self.redrawCanvas(rect(paint_box)) 706 else: 707 self.lastMove = None 708 709 self.runAnimation(redraw_misc=self.real_set_shown) 710 if not self.noAnimation: 711 while self.animating: 712 self.runAnimation() 713 714 shown = property(_getShown, _setShown) 715 716 def runAnimation(self, redraw_misc=False): 717 """ 718 The animationsystem in pychess is very loosely inspired by the one of 719 chessmonk. The idea is, that every piece has a place in an array(the 720 board.data one) for where to be drawn. If a piece is to be animated, it 721 can set its x and y properties, to some cord(or part cord like 0.42 for 722 42% right to file 0). Each time runAnimation is run, it will set those x 723 and y properties a little closer to the location in the array. When it 724 has reached its final location, x and y will be set to None. _setShown, 725 which starts the animation, also sets a timestamp for the acceleration 726 to work properply. 727 """ 728 if self.model is None: 729 return False 730 731 if not self.animating: 732 return False 733 734 paint_box = None 735 736 mod = min(1, (time() - self.animation_start) / ANIMATION_TIME) 737 board = self.model.getBoardAtPly(self.shown, self.shown_variation_idx) 738 739 for y_loc, row in enumerate(board.data): 740 for x_loc, piece in row.items(): 741 if not piece: 742 continue 743 if piece == self.dragged_piece: 744 continue 745 if piece == self.premove_piece: 746 # if premove move is being made, the piece will already be 747 # sitting on the cord it needs to move to- 748 # do not animate and reset premove to None 749 if self.shown == self.premove_ply: 750 piece.x = None 751 piece.y = None 752 self.setPremove(None, None, None, None) 753 continue 754 # otherwise, animate premove piece moving to the premove cord 755 # rather than the cord it actually exists on 756 elif self.premove0 and self.premove1: 757 x_loc = self.premove1.x 758 y_loc = self.premove1.y 759 760 if piece.x is not None: 761 if not self.noAnimation: 762 if piece.piece == KNIGHT: 763 newx = piece.x + (x_loc - piece.x) * mod ** (1.5) 764 newy = piece.y + (y_loc - piece.y) * mod 765 else: 766 newx = piece.x + (x_loc - piece.x) * mod 767 newy = piece.y + (y_loc - piece.y) * mod 768 else: 769 newx, newy = x_loc, y_loc 770 771 paint_box = join(paint_box, self.cord2RectRelative(piece.x, 772 piece.y)) 773 paint_box = join(paint_box, self.cord2RectRelative(newx, newy)) 774 775 if (newx <= x_loc <= piece.x or newx >= x_loc >= piece.x) and \ 776 (newy <= y_loc <= piece.y or newy >= y_loc >= piece.y) or \ 777 abs(newx - x_loc) < 0.005 and abs(newy - y_loc) < 0.005: 778 paint_box = join(paint_box, self.cord2RectRelative(x_loc, y_loc)) 779 piece.x = None 780 piece.y = None 781 else: 782 piece.x = newx 783 piece.y = newy 784 785 if piece.opacity < 1: 786 if piece.x is not None: 787 px_loc = piece.x 788 py_loc = piece.y 789 else: 790 px_loc = x_loc 791 py_loc = y_loc 792 793 if paint_box: 794 paint_box = join(paint_box, self.cord2RectRelative(px_loc, py_loc)) 795 else: 796 paint_box = self.cord2RectRelative(px_loc, py_loc) 797 798 if not self.noAnimation: 799 new_op = piece.opacity + (1 - piece.opacity) * mod 800 else: 801 new_op = 1 802 803 if new_op >= 1 >= piece.opacity or abs(1 - new_op) < 0.005: 804 piece.opacity = 1 805 else: 806 piece.opacity = new_op 807 808 ready = [] 809 for i, dead in enumerate(self.deadlist): 810 piece, x_loc, y_loc = dead 811 if not paint_box: 812 paint_box = self.cord2RectRelative(x_loc, y_loc) 813 else: 814 paint_box = join(paint_box, self.cord2RectRelative(x_loc, y_loc)) 815 816 if not self.noAnimation: 817 new_op = piece.opacity + (0 - piece.opacity) * mod 818 else: 819 new_op = 0 820 821 if new_op <= 0 <= piece.opacity or abs(0 - new_op) < 0.005: 822 ready.append(dead) 823 else: 824 piece.opacity = new_op 825 826 for dead in ready: 827 self.deadlist.remove(dead) 828 829 if paint_box: 830 self.redrawCanvas(rect(paint_box)) 831 832 if self.noAnimation: 833 self.animating = False 834 return False 835 else: 836 if not paint_box: 837 self.animating = False 838 return paint_box and True or False 839 840 def startAnimation(self): 841 self.animation_start = time() 842 self.animating = True 843 844 self.runAnimation(redraw_misc=True) 845 if not self.noAnimation: 846 while self.animating: 847 self.runAnimation() 848 849 ############################# 850 # Drawing # 851 ############################# 852 853 def onRealized(self, widget): 854 padding = (1 - self.padding) 855 alloc = self.get_allocation() 856 square = float(min(alloc.width, alloc.height)) * padding 857 xc_loc = alloc.width / 2. - square / 2 858 yc_loc = alloc.height / 2. - square / 2 859 size = square / self.FILES 860 self.square = (xc_loc, yc_loc, square, size) 861 862 def expose(self, widget, ctx): 863 context = widget.get_window().cairo_create() 864 865 start = time() 866 rectangle = Gdk.Rectangle() 867 clip_ext = ctx.clip_extents() 868 rectangle.x, rectangle.y = clip_ext[0], clip_ext[1] 869 rectangle.width, rectangle.height = clip_ext[2] - clip_ext[0], clip_ext[3] - clip_ext[1] 870 871 if False: 872 import profile 873 profile.runctx("self.draw(context, rectangle)", locals(), globals(), "/tmp/pychessprofile") 874 from pstats import Stats 875 stats = Stats("/tmp/pychessprofile") 876 stats.sort_stats('cumulative') 877 stats.print_stats() 878 else: 879 self.draw(context, rectangle) 880 # self.drawcount += 1 881 # self.drawtime += time() - start 882 # if self.drawcount % 100 == 0: 883 # print( "Average FPS: %0.3f - %d / %d" % \ 884 # (self.drawcount/self.drawtime, self.drawcount, self.drawtime)) 885 886 return False 887 888 ############################################################################ 889 # drawing functions # 890 ############################################################################ 891 892 ############################### 893 # redrawCanvas # 894 ############################### 895 896 def redrawCanvas(self, rect=None): 897 if self.get_window(): 898 if not rect: 899 alloc = self.get_allocation() 900 rect = Gdk.Rectangle() 901 rect.x, rect.y, rect.width, rect.height = (0, 0, alloc.width, alloc.height) 902 self.get_window().invalidate_rect(rect, True) 903 self.get_window().process_updates(True) 904 905 ############################### 906 # draw # 907 ############################### 908 909 # draw called each time we hover on a case. WARNING it only redraw the case 910 def draw(self, context, r): 911 912 # context.set_antialias(cairo.ANTIALIAS_NONE) 913 if self.shown < self.model.lowply: 914 print("exiting cause to lowlpy", self.shown, self.model.lowply) 915 return 916 917 alloc = self.get_allocation() 918 919 self.matrix, self.invmatrix = matrixAround( 920 self.matrix, alloc.width / 2., alloc.height / 2.) 921 cos_, sin_ = self.matrix[0], self.matrix[1] 922 context.transform(self.matrix) 923 924 square = float(min(alloc.width, alloc.height)) * (1 - self.padding) 925 if SCALE_ROTATED_BOARD: 926 square /= abs(cos_) + abs(sin_) 927 xc_loc = alloc.width / 2. - square / 2 928 yc_loc = alloc.height / 2. - square / 2 929 side = square / self.FILES 930 self.square = (xc_loc, yc_loc, square, side) 931 932 # draw all the different components by calling all the draw methods of this class 933 self.drawBoard(context, r) 934 935 self.drawSupportAlgorithm(context, r) 936 937 if min(alloc.width, alloc.height) > 32: 938 self.drawCords(context, r) 939 940 if self.got_started: 941 self.drawSpecial(context, r) 942 self.drawEnpassant(context, r) 943 944 self.drawCircles(context) 945 self.drawArrows(context) 946 self.drawPieces(context, r) 947 if not self.setup_position: 948 self.drawLastMove(context, r) 949 950 if self.model.status == KILLED: 951 pass 952 # self.drawCross(context, r) 953 954 # At this point we have real values of self.get_allocation() 955 # and can adjust board paned divider if needed 956 if self._show_captured is None: 957 self.showCaptured = conf.get("showCaptured") 958 959 # Unselect to mark redrawn areas - for debugging purposes 960 # context.transform(self.invmatrix) 961 # context.rectangle(r.x,r.y,r.width,r.height) 962 # dc = self.drawcount*50 963 # dc = dc % 1536 964 # c = dc % 256 / 255. 965 # if dc < 256: 966 # context.set_source_rgb(1, ,c,0) 967 # elif dc < 512: 968 # context.set_source_rgb(1-c,1, 0) 969 # elif dc < 768: 970 # context.set_source_rgb(0, 1,c) 971 # elif dc < 1024: 972 # context.set_source_rgb(0, 1-c,1) 973 # elif dc < 1280: 974 # context.set_source_rgb(c,0, 1) 975 # elif dc < 1536: 976 # context.set_source_rgb(1, 0, 1-c) 977 # context.stroke() 978 979 ############################### 980 # drawCords # 981 ############################### 982 983 def drawCords(self, context, rectangle): 984 thickness = 0.01 985 signsize = 0.02 986 987 if (not self.show_cords) and (not self.setup_position): 988 return 989 990 xc_loc, yc_loc, square, side = self.square 991 992 if rectangle is not None and contains(rect((xc_loc, yc_loc, square)), rectangle): 993 return 994 995 thick = thickness * square 996 sign_size = signsize * square 997 998 pangoScale = float(Pango.SCALE) 999 if self.no_frame: 1000 context.set_source_rgb(0.0, 0.0, 0.0) 1001 else: 1002 context.set_source_rgb(1.0, 1.0, 1.0) 1003 1004 def paint(inv): 1005 for num in range(self.RANKS): 1006 rank = inv and num + 1 or self.RANKS - num 1007 layout = self.create_pango_layout("%d" % rank) 1008 layout.set_font_description( 1009 Pango.FontDescription("bold %d" % sign_size)) 1010 width = layout.get_extents()[1].width / pangoScale 1011 height = layout.get_extents()[0].height / pangoScale 1012 1013 # Draw left side 1014 context.move_to(xc_loc - thick - width, side * num + yc_loc + height / 2 + thick * 3) 1015 PangoCairo.show_layout(context, layout) 1016 1017 file = inv and self.FILES - num or num + 1 1018 layout = self.create_pango_layout(chr(file + ord("A") - 1)) 1019 layout.set_font_description( 1020 Pango.FontDescription("bold %d" % sign_size)) 1021 1022 # Draw bottom 1023 context.move_to(xc_loc + side * num + side / 2 - width / 2, yc_loc + square) 1024 PangoCairo.show_layout(context, layout) 1025 1026 matrix, invmatrix = matrixAround(self.matrix_pi, xc_loc + square / 2, yc_loc + square / 2) 1027 if self.rotation == 0: 1028 paint(False) 1029 else: 1030 context.transform(matrix) 1031 paint(True) 1032 context.transform(invmatrix) 1033 1034 if self.faceToFace: 1035 if self.rotation == 0: 1036 context.transform(matrix) 1037 paint(True) 1038 context.transform(invmatrix) 1039 else: 1040 paint(False) 1041 1042 def draw_image(self, context, image_surface, left, top, width, height): 1043 """ Draw a scaled image on a given context. """ 1044 1045 # calculate scale 1046 image_width = image_surface.get_width() 1047 image_height = image_surface.get_height() 1048 width_ratio = float(width) / float(image_width) 1049 height_ratio = float(height) / float(image_height) 1050 scale_xy = min(width_ratio, height_ratio) 1051 1052 # scale image and add it 1053 context.save() 1054 context.translate(left, top) 1055 context.scale(scale_xy, scale_xy) 1056 context.set_source_surface(image_surface) 1057 1058 context.paint() 1059 context.restore() 1060 1061 def draw_frame(self, context, image_surface, left, top, width, height): 1062 """ Draw a repeated image pattern on a given context. """ 1063 1064 pat = cairo.SurfacePattern(image_surface) 1065 pat.set_extend(cairo.EXTEND_REPEAT) 1066 1067 context.rectangle(left, top, width, height) 1068 context.set_source(pat) 1069 1070 context.fill() 1071 1072 ############################### 1073 # drawBoard # 1074 ############################### 1075 1076 def drawBoard(self, context, r): 1077 xc_loc, yc_loc, square, side = self.square 1078 1079 col = Gdk.RGBA() 1080 col.parse(self.light_colour) 1081 context.set_source_rgba(col.red, col.green, col.blue, col.alpha) 1082 1083 if self.model.variant.variant in ASEAN_VARIANTS: 1084 # just fill the whole board with light color 1085 if self.colors_only: 1086 context.rectangle(xc_loc, yc_loc, side * self.FILES, side * self.RANKS) 1087 else: 1088 self.draw_image(context, self.light_surface, xc_loc, yc_loc, side * self.FILES, side * self.RANKS) 1089 if self.colors_only: 1090 context.fill() 1091 else: 1092 # light squares 1093 for x_loc in range(self.FILES): 1094 for y_loc in range(self.RANKS): 1095 if x_loc % 2 + y_loc % 2 != 1: 1096 if self.colors_only: 1097 context.rectangle(xc_loc + x_loc * side, yc_loc + y_loc * side, side, side) 1098 else: 1099 self.draw_image(context, self.light_surface, xc_loc + x_loc * side, yc_loc + y_loc * side, 1100 side, side) 1101 if self.colors_only: 1102 context.fill() 1103 1104 col = Gdk.RGBA() 1105 col.parse(self.dark_colour) 1106 context.set_source_rgba(col.red, col.green, col.blue, col.alpha) 1107 1108 if self.model.variant.variant in ASEAN_VARIANTS: 1109 # diagonals 1110 if self.model.variant.variant == SITTUYINCHESS: 1111 context.move_to(xc_loc, yc_loc) 1112 context.rel_line_to(square, square) 1113 context.move_to(xc_loc + square, yc_loc) 1114 context.rel_line_to(-square, square) 1115 context.stroke() 1116 else: 1117 # dark squares 1118 for x_loc in range(self.FILES): 1119 for y_loc in range(self.RANKS): 1120 if x_loc % 2 + y_loc % 2 == 1: 1121 if self.colors_only: 1122 context.rectangle((xc_loc + x_loc * side), (yc_loc + y_loc * side), side, side) 1123 else: 1124 self.draw_image(context, self.dark_surface, (xc_loc + x_loc * side), 1125 (yc_loc + y_loc * side), side, side) 1126 if self.colors_only: 1127 context.fill() 1128 1129 if not self.no_frame: 1130 # board frame 1131 delta = side / 4 1132 # top 1133 self.draw_frame(context, self.frame_surface, xc_loc - delta, yc_loc - delta, self.FILES * side + delta * 2, 1134 delta) 1135 # bottom 1136 self.draw_frame(context, self.frame_surface, xc_loc - delta, yc_loc + self.RANKS * side, 1137 self.FILES * side + delta * 2, delta) 1138 # left 1139 self.draw_frame(context, self.frame_surface, xc_loc - delta, yc_loc, delta, self.FILES * side) 1140 # right 1141 self.draw_frame(context, self.frame_surface, xc_loc + self.FILES * side, yc_loc, delta, self.FILES * side) 1142 1143 if self.draw_grid: 1144 # grid lines between squares 1145 context.set_source_rgb(0.0, 0.0, 0.0) 1146 context.set_line_width(0.5 if r is None else 1.0) 1147 1148 for loc in range(self.FILES): 1149 context.move_to(xc_loc + side * loc, yc_loc) 1150 context.line_to(xc_loc + side * loc, yc_loc + self.FILES * side) 1151 context.move_to(xc_loc, yc_loc + side * loc) 1152 context.line_to(xc_loc + self.FILES * side, yc_loc + side * loc) 1153 1154 context.rectangle(xc_loc, yc_loc, self.FILES * side, self.RANKS * side) 1155 context.stroke() 1156 1157 context.set_source_rgba(col.red, col.green, col.blue, col.alpha) 1158 1159 ############################### 1160 # drawPieces # 1161 ############################### 1162 1163 def getCordMatrices(self, x_loc, y_loc, inv=False): 1164 square, side = self.square[2], self.square[3] 1165 rot_ = self.cord_matrices_state[1] 1166 if square != self.square or rot_ != self.rotation: 1167 self.cord_matrices = [None] * self.FILES * self.RANKS + [None] * self.FILES * 4 1168 self.cord_matrices_state = (self.square, self.rotation) 1169 c_loc = x_loc * self.FILES + y_loc 1170 if isinstance(c_loc, int) and self.cord_matrices[c_loc]: 1171 matrices = self.cord_matrices[c_loc] 1172 else: 1173 cx_loc, cy_loc = self.cord2Point(x_loc, y_loc) 1174 matrices = matrixAround(self.matrix, cx_loc + side / 2., cy_loc + side / 2.) 1175 matrices += (cx_loc, cy_loc) 1176 if isinstance(c_loc, int): 1177 self.cord_matrices[c_loc] = matrices 1178 return matrices 1179 1180 def __drawPiece(self, context, piece, x_loc, y_loc): 1181 # Maybe a premove was reset from another thread 1182 if piece is None: 1183 print("Trying to draw a None piece") 1184 return 1185 if self.model.variant == BlindfoldBoard: 1186 return 1187 elif self.model.variant == HiddenPawnsBoard: 1188 if piece.piece == PAWN: 1189 return 1190 elif self.model.variant == HiddenPiecesBoard: 1191 if piece.piece != PAWN: 1192 return 1193 1194 if piece.captured and not self.showCaptured: 1195 return 1196 1197 side = self.square[3] 1198 1199 if not self.faceToFace: 1200 matrix, invmatrix, cx_loc, cy_loc = self.getCordMatrices(x_loc, y_loc) 1201 else: 1202 cx_loc, cy_loc = self.cord2Point(x_loc, y_loc) 1203 if piece.color == BLACK: 1204 matrix, invmatrix = matrixAround((-1, 0), cx_loc + side / 2., cy_loc + side / 2.) 1205 else: 1206 matrix = invmatrix = cairo.Matrix(1, 0, 0, 1, 0, 0) 1207 1208 context.transform(invmatrix) 1209 Pieces.drawPiece(piece, context, 1210 cx_loc + CORD_PADDING, cy_loc + CORD_PADDING, 1211 side - CORD_PADDING * 2, allwhite=self.allwhite, asean=self.asean, 1212 variant=self.model.variant.variant) 1213 context.transform(matrix) 1214 1215 def drawPieces(self, context, rectangle): 1216 pieces = self.model.getBoardAtPly(self.shown, self.shown_variation_idx) 1217 1218 style_ctxt = self.get_style_context() 1219 1220 col = style_ctxt.lookup_color("p_fg_color")[1] 1221 fg_n = (col.red, col.green, col.blue) 1222 fg_s = fg_n 1223 1224 col = style_ctxt.lookup_color("p_fg_active")[1] 1225 fg_a = (col.red, col.green, col.blue) 1226 1227 col = style_ctxt.lookup_color("p_fg_prelight")[1] 1228 fg_p = (col.red, col.green, col.blue) 1229 1230 fg_m = fg_n 1231 1232 # As default we use normal foreground for selected cords, as it looks 1233 # less confusing. However for some themes, the normal foreground is so 1234 # similar to the selected background, that we have to use the selected 1235 # foreground. 1236 1237 col = style_ctxt.lookup_color("p_bg_selected")[1] 1238 bg_sl = (col.red, col.green, col.blue) 1239 1240 col = style_ctxt.lookup_color("p_dark_selected")[1] 1241 bg_sd = (col.red, col.green, col.blue) 1242 1243 if min((fg_n[0] - bg_sl[0]) ** 2 + (fg_n[1] - bg_sl[1]) ** 2 + (fg_n[2] - bg_sl[2]) ** 2, 1244 (fg_n[0] - bg_sd[0]) ** 2 + (fg_n[1] - bg_sd[1]) ** 2 + (fg_n[2] - bg_sd[2]) ** 2) < 0.2: 1245 col = style_ctxt.lookup_color("p_fg_selected")[1] 1246 fg_s = (col.red, col.green, col.blue) 1247 1248 # Draw dying pieces(Found in self.deadlist) 1249 for piece, x_loc, y_loc in self.deadlist: 1250 context.set_source_rgba(fg_n[0], fg_n[1], fg_n[2], piece.opacity) 1251 self.__drawPiece(context, piece, x_loc, y_loc) 1252 1253 # Draw pieces reincarnating(With opacity < 1) 1254 for y_loc, row in enumerate(pieces.data): 1255 for x_loc, piece in row.items(): 1256 if not piece or piece.opacity == 1: 1257 continue 1258 if piece.x: 1259 x_loc, y_loc = piece.x, piece.y 1260 context.set_source_rgba(fg_n[0], fg_n[1], fg_n[2], piece.opacity) 1261 self.__drawPiece(context, piece, x_loc, y_loc) 1262 1263 # Draw standing pieces(Only those who intersect drawn area) 1264 for y_loc, row in enumerate(pieces.data): 1265 for x_loc, piece in row.items(): 1266 if piece == self.premove_piece: 1267 continue 1268 if not piece or piece.x is not None or piece.opacity < 1: 1269 continue 1270 if rectangle is not None and not intersects(rect(self.cord2RectRelative(x_loc, y_loc)), rectangle): 1271 continue 1272 if Cord(x_loc, y_loc) == self.selected: 1273 context.set_source_rgb(*fg_s) 1274 elif Cord(x_loc, y_loc) == self.active: 1275 context.set_source_rgb(*fg_a) 1276 elif Cord(x_loc, y_loc) == self.hover: 1277 context.set_source_rgb(*fg_p) 1278 else: 1279 context.set_source_rgb(*fg_n) 1280 1281 self.__drawPiece(context, piece, x_loc, y_loc) 1282 1283 # Draw moving or dragged pieces(Those with piece.x and piece.y != None) 1284 context.set_source_rgb(*fg_p) 1285 for y_loc, row in enumerate(pieces.data): 1286 for x_loc, piece in row.items(): 1287 if not piece or piece.x is None or piece.opacity < 1: 1288 continue 1289 self.__drawPiece(context, piece, piece.x, piece.y) 1290 1291 # Draw standing premove piece 1292 context.set_source_rgb(*fg_m) 1293 if self.premove_piece and self.premove_piece.x is None and self.premove0 and self.premove1: 1294 self.__drawPiece(context, self.premove_piece, self.premove1.x, self.premove1.y) 1295 1296 ############################### 1297 # drawSpecial # 1298 ############################### 1299 1300 def drawSpecial(self, context, redrawn): 1301 1302 light_blue = (0.550, 0.775, 0.950, 0.8) 1303 dark_blue = (0.475, 0.700, 0.950, 0.5) 1304 1305 used = [] 1306 for cord, state in ((self.active, "_active"), 1307 (self.selected, "_selected"), 1308 (self.premove0, "_selected"), 1309 (self.premove1, "_selected"), 1310 (self.hover, "_prelight")): 1311 if not cord: 1312 continue 1313 if cord in used: 1314 continue 1315 # Ensure that same cord, if having multiple "tasks", doesn't get 1316 # painted more than once 1317 used.append(cord) 1318 1319 bounding = self.cord2RectRelative(cord) 1320 if not intersects(rect(bounding), redrawn): 1321 continue 1322 1323 board = self.model.getBoardAtPly(self.shown, self.shown_variation_idx) 1324 if board[cord] is None and (cord.x < 0 or cord.x > self.FILES - 1): 1325 continue 1326 1327 side = self.square[3] 1328 x_loc, y_loc = self.cord2Point(cord) 1329 context.rectangle(x_loc, y_loc, side, side) 1330 if cord == self.premove0 or cord == self.premove1: 1331 if self.isLight(cord): 1332 context.set_source_rgba(*light_blue) 1333 else: 1334 context.set_source_rgba(*dark_blue) 1335 else: 1336 style_ctxt = self.get_style_context() 1337 if self.isLight(cord): 1338 # bg 1339 found, color = style_ctxt.lookup_color("p_bg" + state) 1340 else: 1341 # dark 1342 found, color = style_ctxt.lookup_color("p_dark" + state) 1343 if not found: 1344 print("color not found in boardview.py:", "p_dark" + state) 1345 red, green, blue, alpha = color.red, color.green, color.blue, color.alpha 1346 context.set_source_rgba(red, green, blue, alpha) 1347 context.fill() 1348 1349 def color2rgba(self, color): 1350 if color == "R": 1351 rgba = (.643, 0, 0, 0.8) 1352 elif color == "B": 1353 rgba = (.204, .396, .643, 0.8) 1354 elif color == "Y": 1355 rgba = (.961, .475, 0, 0.8) 1356 else: 1357 # light_green 1358 rgba = (0.337, 0.612, 0.117, 0.8) 1359 return rgba 1360 1361 def drawCircles(self, context): 1362 radius = self.square[3] / 2.0 1363 context.set_line_width(4) 1364 1365 for cord in self.circles: 1366 rgba = self.color2rgba(cord.color) 1367 context.set_source_rgb(*rgba[:3]) 1368 x_loc, y_loc = self.cord2Point(cord) 1369 context.new_sub_path() 1370 context.arc(x_loc + radius, y_loc + radius, radius - 3, 0, 2 * pi) 1371 context.stroke() 1372 1373 if self.pre_circle is not None: 1374 rgba = self.color2rgba(self.pre_circle.color) 1375 context.set_source_rgb(*rgba[:3]) 1376 x_loc, y_loc = self.cord2Point(self.pre_circle) 1377 context.new_sub_path() 1378 context.arc(x_loc + radius, y_loc + radius, radius - 3, 0, 2 * pi) 1379 context.stroke() 1380 1381 arw = 0.15 # Arrow width 1382 arhw = 0.6 # Arrow head width 1383 arhh = 0.6 # Arrow head height 1384 arsw = 0.0 # Arrow stroke width 1385 for arrow_cords in self.arrows: 1386 rgba = self.color2rgba(arrow_cords[0].color) 1387 self.__drawArrow(context, arrow_cords, arw, arhw, arhh, arsw, rgba, rgba) 1388 if self.pre_arrow is not None: 1389 rgba = self.color2rgba(self.pre_arrow[0].color) 1390 self.__drawArrow(context, self.pre_arrow, arw, arhw, arhh, arsw, rgba, rgba) 1391 1392 ############################### 1393 # drawLastMove # 1394 ############################### 1395 1396 def drawLastMove(self, context, redrawn): 1397 if not self.lastMove: 1398 return 1399 if self.shown <= self.model.lowply: 1400 return 1401 show_board = self.model.getBoardAtPly(self.shown, self.shown_variation_idx) 1402 last_board = self.model.getBoardAtPly(self.shown - 1, self.shown_variation_idx) 1403 capture = self.lastMove.is_capture(last_board) 1404 1405 mark_width = 0.27 # Width of marker 1406 padding_last = 0.155 # Padding on last cord 1407 padding_curr = 0.085 # Padding on current cord 1408 stroke_width = 0.02 # Stroke width 1409 1410 side = self.square[3] 1411 1412 context.save() 1413 context.set_line_width(stroke_width * side) 1414 1415 dic0 = {-1: 1 - padding_last, 1: padding_last} 1416 dic1 = {-1: 1 - padding_curr, 1: padding_curr} 1417 matrix_scaler = ((1, 1), (-1, 1), (-1, -1), (1, -1)) 1418 1419 light_yellow = (.929, .831, 0, 0.8) 1420 dark_yellow = (.769, .627, 0, 0.5) 1421 light_orange = (.961, .475, 0, 0.8) 1422 dark_orange = (.808, .361, 0, 0.5) 1423 light_green = (0.337, 0.612, 0.117, 0.8) 1424 dark_green = (0.237, 0.512, 0.17, 0.5) 1425 1426 if self.lastMove.flag in (KING_CASTLE, QUEEN_CASTLE): 1427 ksq0 = last_board.board.kings[last_board.color] 1428 ksq1 = show_board.board.kings[last_board.color] 1429 wildcastle = \ 1430 Cord(last_board.board.ini_kings[last_board.color]).x == 3 and \ 1431 last_board.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS) 1432 if self.lastMove.flag == KING_CASTLE: 1433 side = 0 if wildcastle else 1 1434 rsq0 = show_board.board.ini_rooks[last_board.color][side] 1435 rsq1 = show_board.board.fin_rooks[last_board.color][side] 1436 else: 1437 side = 1 if wildcastle else 0 1438 rsq0 = show_board.board.ini_rooks[last_board.color][side] 1439 rsq1 = show_board.board.fin_rooks[last_board.color][side] 1440 cord_pairs = [[Cord(ksq0), Cord(ksq1)], [Cord(rsq0), Cord(rsq1)]] 1441 else: 1442 cord_pairs = [[self.lastMove.cord0, self.lastMove.cord1]] 1443 1444 for [cord0, cord1] in cord_pairs: 1445 if cord0 is not None: 1446 rel = self.cord2RectRelative(cord0) 1447 if intersects(rect(rel), redrawn): 1448 rectangle = self.cord2Rect(cord0) 1449 for scaler in matrix_scaler: 1450 context.move_to( 1451 rectangle[0] + (dic0[scaler[0]] + mark_width * scaler[0]) * rectangle[2], 1452 rectangle[1] + (dic0[scaler[1]] + mark_width * scaler[1]) * rectangle[2]) 1453 context.rel_line_to( 1454 0, -mark_width * rectangle[2] * scaler[1]) 1455 context.rel_curve_to(0, mark_width * rectangle[2] * scaler[1] / 2.0, 1456 -mark_width * rectangle[2] * scaler[0] / 2.0, 1457 mark_width * rectangle[2] * scaler[1], 1458 -mark_width * rectangle[2] * scaler[0], 1459 mark_width * rectangle[2] * scaler[1]) 1460 context.close_path() 1461 1462 context.set_source_rgba(*light_yellow) 1463 context.fill_preserve() 1464 context.set_source_rgba(*dark_yellow) 1465 context.stroke() 1466 1467 rel = self.cord2RectRelative(cord1) 1468 if intersects(rect(rel), redrawn): 1469 rectangle = self.cord2Rect(cord1) 1470 1471 for scaler in matrix_scaler: 1472 context.move_to( 1473 rectangle[0] + dic1[scaler[0]] * rectangle[2], 1474 rectangle[1] + dic1[scaler[1]] * rectangle[2]) 1475 context.rel_line_to( 1476 mark_width * rectangle[2] * scaler[0], 0) 1477 context.rel_curve_to( 1478 -mark_width * rectangle[2] * scaler[0] / 2.0, 0, 1479 -mark_width * rectangle[2] * scaler[0], mark_width * rectangle[2] * scaler[1] / 2.0, 1480 -mark_width * rectangle[2] * scaler[0], mark_width * rectangle[2] * scaler[1]) 1481 context.close_path() 1482 1483 if capture: 1484 context.set_source_rgba(*light_orange) 1485 context.fill_preserve() 1486 context.set_source_rgba(*dark_orange) 1487 context.stroke() 1488 elif cord0 is None: # DROP move 1489 context.set_source_rgba(*light_green) 1490 context.fill_preserve() 1491 context.set_source_rgba(*dark_green) 1492 context.stroke() 1493 else: 1494 context.set_source_rgba(*light_yellow) 1495 context.fill_preserve() 1496 context.set_source_rgba(*dark_yellow) 1497 context.stroke() 1498 1499 ############################### 1500 # drawArrows # 1501 ############################### 1502 1503 def __drawArrow(self, context, cords, aw, ahw, ahh, asw, fillc, strkc): 1504 context.save() 1505 1506 lvx = cords[1].x - cords[0].x 1507 lvy = cords[0].y - cords[1].y 1508 hypotenuse = float((lvx ** 2 + lvy ** 2) ** .5) 1509 vec_x = lvx / hypotenuse 1510 vec_y = lvy / hypotenuse 1511 v1x = -vec_y 1512 v1y = vec_x 1513 1514 rectangle = self.cord2Rect(cords[0]) 1515 1516 px_loc = rectangle[0] + rectangle[2] / 2.0 1517 py_loc = rectangle[1] + rectangle[2] / 2.0 1518 ax_loc = v1x * rectangle[2] * aw / 2 1519 ay_loc = v1y * rectangle[2] * aw / 2 1520 context.move_to(px_loc + ax_loc, py_loc + ay_loc) 1521 1522 p1x = px_loc + (lvx - vec_x * ahh) * rectangle[2] 1523 p1y = py_loc + (lvy - vec_y * ahh) * rectangle[2] 1524 context.line_to(p1x + ax_loc, p1y + ay_loc) 1525 1526 lax = v1x * rectangle[2] * ahw / 2 1527 lay = v1y * rectangle[2] * ahw / 2 1528 context.line_to(p1x + lax, p1y + lay) 1529 1530 context.line_to(px_loc + lvx * rectangle[2], py_loc + lvy * rectangle[2]) 1531 context.line_to(p1x - lax, p1y - lay) 1532 context.line_to(p1x - ax_loc, p1y - ay_loc) 1533 context.line_to(px_loc - ax_loc, py_loc - ay_loc) 1534 context.close_path() 1535 1536 context.set_source_rgba(*fillc) 1537 context.fill_preserve() 1538 context.set_line_join(cairo.LINE_JOIN_ROUND) 1539 context.set_line_width(asw * rectangle[2]) 1540 context.set_source_rgba(*strkc) 1541 context.stroke() 1542 1543 context.restore() 1544 1545 def drawArrows(self, context): 1546 arw = 0.3 # Arrow width 1547 arhw = 0.72 # Arrow head width 1548 arhh = 0.64 # Arrow head height 1549 arsw = 0.08 # Arrow stroke width 1550 1551 if self.bluearrow: 1552 self.__drawArrow(context, self.bluearrow, arw, arhw, arhh, arsw, 1553 (.447, .624, .812, 0.9), (.204, .396, .643, 1)) 1554 1555 if self.greenarrow: 1556 self.__drawArrow(context, self.greenarrow, arw, arhw, arhh, arsw, 1557 (.54, .886, .2, 0.9), (.306, .604, .024, 1)) 1558 1559 if self.redarrow: 1560 self.__drawArrow(context, self.redarrow, arw, arhw, arhh, arsw, 1561 (.937, .16, .16, 0.9), (.643, 0, 0, 1)) 1562 1563 ############################### 1564 # drawEnpassant # 1565 ############################### 1566 1567 def drawEnpassant(self, context, redrawn): 1568 if not self.showEnpassant: 1569 return 1570 enpassant = self.model.boards[-1].enpassant 1571 if not enpassant: 1572 return 1573 1574 context.set_source_rgb(0, 0, 0) 1575 side = self.square[3] 1576 x_loc, y_loc = self.cord2Point(enpassant) 1577 if not intersects(rect((x_loc, y_loc, side, side)), redrawn): 1578 return 1579 1580 x_loc, y_loc = self.cord2Point(enpassant) 1581 crr = context 1582 crr.set_font_size(side / 2.) 1583 fdescent, fheight = crr.font_extents()[1], crr.font_extents()[2] 1584 chars = "en" 1585 xbearing, width = crr.text_extents(chars)[0], crr.text_extents(chars)[2] 1586 crr.move_to(x_loc + side / 2. - xbearing - width / 2.0 - 1, 1587 side / 2. + y_loc - fdescent + fheight / 2.) 1588 crr.show_text(chars) 1589 1590 ############################### 1591 # drawCross # 1592 ############################### 1593 1594 def drawCross(self, context, redrawn): 1595 xc_loc, yc_loc, square, side = self.square 1596 1597 context.move_to(xc_loc, yc_loc) 1598 context.rel_line_to(square, square) 1599 context.move_to(xc_loc + square, yc_loc) 1600 context.rel_line_to(-square, square) 1601 1602 context.set_line_cap(cairo.LINE_CAP_SQUARE) 1603 context.set_source_rgba(0, 0, 0, 0.65) 1604 context.set_line_width(side) 1605 context.stroke_preserve() 1606 1607 context.set_source_rgba(1, 0, 0, 0.8) 1608 context.set_line_width(side / 2.) 1609 context.stroke() 1610 1611 ############################### 1612 # drawSupportAlgorithm # 1613 ############################### 1614 1615 # Here is how we implemented representation of support algorithm : 1616 # piece not protected -> yellow circle 1617 # piece attacked and not protected -> red circle 1618 # see SupportAlgorithm in file utils.DecisionSupportAlgorithm for more details on the algorithm 1619 1620 def drawSupportAlgorithm(self, context, redrawn): 1621 radius = self.square[3] / 2.0 1622 context.set_line_width(4) 1623 1624 algorithm = self.model.support_algorithm 1625 coord_attacked_not_protected = algorithm.coordinate_in_danger 1626 1627 for cord in coord_attacked_not_protected: 1628 rgba = self.color2rgba(cord.color) 1629 context.set_source_rgb(*rgba[:3]) 1630 x_loc, y_loc = self.cord2Point(cord) 1631 context.new_sub_path() 1632 context.arc(x_loc + radius, y_loc + radius, radius - 3, 0, 2 * pi) 1633 context.stroke() 1634 1635 ############################################################################ 1636 # Attributes # 1637 ############################################################################ 1638 1639 ############################### 1640 # Cord vars # 1641 ############################### 1642 1643 def _setSelected(self, cord): 1644 self._active = None 1645 if self._selected == cord: 1646 return 1647 if self._selected: 1648 rectangle = rect(self.cord2RectRelative(self._selected)) 1649 if cord: 1650 rectangle = union(rectangle, rect(self.cord2RectRelative(cord))) 1651 elif cord: 1652 rectangle = rect(self.cord2RectRelative(cord)) 1653 self._selected = cord 1654 self.redrawCanvas(rectangle) 1655 1656 def _getSelected(self): 1657 return self._selected 1658 1659 selected = property(_getSelected, _setSelected) 1660 1661 def _setHover(self, cord): 1662 if self._hover == cord: 1663 return 1664 if self._hover: 1665 rectangle = rect(self.cord2RectRelative(self._hover)) 1666 # convert r from tuple to rect 1667 # tmpr = r 1668 # r = Gdk.Rectangle() 1669 # r.x, r.y, r.width, r.height = tmpr 1670 # if cord: r = r.union(rect(self.cord2RectRelative(cord))) 1671 if cord: 1672 rectangle = union(rectangle, rect(self.cord2RectRelative(cord))) 1673 elif cord: 1674 rectangle = rect(self.cord2RectRelative(cord)) 1675 # convert r from tuple to rect 1676 # tmpr = r 1677 # r = Gdk.Rectangle() 1678 # r.x, r.y, r.width, r.height = tmpr 1679 self._hover = cord 1680 self.redrawCanvas(rectangle) 1681 1682 def _getHover(self): 1683 return self._hover 1684 1685 hover = property(_getHover, _setHover) 1686 1687 def _setActive(self, cord): 1688 if self._active == cord: 1689 return 1690 if self._active: 1691 rectangle = rect(self.cord2RectRelative(self._active)) 1692 if cord: 1693 rectangle = union(rectangle, rect(self.cord2RectRelative(cord))) 1694 elif cord: 1695 rectangle = rect(self.cord2RectRelative(cord)) 1696 self._active = cord 1697 self.redrawCanvas(rectangle) 1698 1699 def _getActive(self): 1700 return self._active 1701 1702 active = property(_getActive, _setActive) 1703 1704 def _setPremove0(self, cord): 1705 if self._premove0 == cord: 1706 return 1707 if self._premove0: 1708 rectangle = rect(self.cord2RectRelative(self._premove0)) 1709 if cord: 1710 rectangle = union(rectangle, rect(self.cord2RectRelative(cord))) 1711 elif cord: 1712 rectangle = rect(self.cord2RectRelative(cord)) 1713 self._premove0 = cord 1714 self.redrawCanvas(rectangle) 1715 1716 def _getPremove0(self): 1717 return self._premove0 1718 1719 premove0 = property(_getPremove0, _setPremove0) 1720 1721 def _setPremove1(self, cord): 1722 if self._premove1 == cord: 1723 return 1724 if self._premove1: 1725 rectangle = rect(self.cord2RectRelative(self._premove1)) 1726 if cord: 1727 rectangle = union(rectangle, rect(self.cord2RectRelative(cord))) 1728 elif cord: 1729 rectangle = rect(self.cord2RectRelative(cord)) 1730 self._premove1 = cord 1731 self.redrawCanvas(rectangle) 1732 1733 def _getPremove1(self): 1734 return self._premove1 1735 1736 premove1 = property(_getPremove1, _setPremove1) 1737 1738 ################################ 1739 # Arrow vars # 1740 ################################ 1741 1742 def _setRedarrow(self, cords): 1743 if cords == self._redarrow: 1744 return 1745 paint_cords = [] 1746 if cords: 1747 paint_cords += cords 1748 if self._redarrow: 1749 paint_cords += self._redarrow 1750 rectangle = rect(self.cord2RectRelative(paint_cords[0])) 1751 for cord in paint_cords[1:]: 1752 rectangle = union(rectangle, rect(self.cord2RectRelative(cord))) 1753 self._redarrow = cords 1754 self.redrawCanvas(rectangle) 1755 1756 def _getRedarrow(self): 1757 return self._redarrow 1758 1759 redarrow = property(_getRedarrow, _setRedarrow) 1760 1761 def _setGreenarrow(self, cords): 1762 if cords == self._greenarrow: 1763 return 1764 paint_cords = [] 1765 if cords: 1766 paint_cords += cords 1767 if self._greenarrow: 1768 paint_cords += self._greenarrow 1769 rectangle = rect(self.cord2RectRelative(paint_cords[0])) 1770 for cord in paint_cords[1:]: 1771 rectangle = union(rectangle, rect(self.cord2RectRelative(cord))) 1772 self._greenarrow = cords 1773 self.redrawCanvas(rectangle) 1774 1775 def _getGreenarrow(self): 1776 return self._greenarrow 1777 1778 greenarrow = property(_getGreenarrow, _setGreenarrow) 1779 1780 def _setBluearrow(self, cords): 1781 if cords == self._bluearrow: 1782 return 1783 paint_cords = [] 1784 if cords: 1785 paint_cords += cords 1786 if self._bluearrow: 1787 paint_cords += self._bluearrow 1788 rectangle = rect(self.cord2RectRelative(paint_cords[0])) 1789 for cord in paint_cords[1:]: 1790 rectangle = union(rectangle, rect(self.cord2RectRelative(cord))) 1791 self._bluearrow = cords 1792 self.redrawCanvas(rectangle) 1793 1794 def _getBluearrow(self): 1795 return self._bluearrow 1796 1797 bluearrow = property(_getBluearrow, _setBluearrow) 1798 1799 ################################ 1800 # Other vars # 1801 ################################ 1802 1803 def _setRotation(self, radians): 1804 if not conf.get("fullAnimation"): 1805 self._rotation = radians 1806 self.next_rotation = radians 1807 self.matrix = cairo.Matrix.init_rotate(radians) 1808 self.redrawCanvas() 1809 else: 1810 if hasattr(self, "next_rotation") and \ 1811 self.next_rotation != self.rotation: 1812 return 1813 self.next_rotation = radians 1814 oldr = self.rotation 1815 start = time() 1816 1817 def rotate(): 1818 amount = (time() - start) / ANIMATION_TIME 1819 if amount > 1: 1820 amount = 1 1821 next = False 1822 self.animating = False 1823 else: 1824 next = True 1825 self._rotation = new = oldr + amount * (radians - oldr) 1826 self.matrix = cairo.Matrix.init_rotate(new) 1827 self.redrawCanvas() 1828 return next 1829 1830 self.animating = True 1831 GLib.idle_add(rotate) 1832 1833 def _getRotation(self): 1834 return self._rotation 1835 1836 rotation = property(_getRotation, _setRotation) 1837 1838 def _setDrawGrid(self, draw_grid): 1839 self._draw_grid = draw_grid 1840 self.redrawCanvas() 1841 1842 def _getDrawGrid(self): 1843 return self._draw_grid 1844 1845 draw_grid = property(_getDrawGrid, _setDrawGrid) 1846 1847 def _setShowCords(self, show_cords): 1848 if not show_cords and self.no_frame: 1849 self.padding = 0. 1850 else: 1851 self.padding = self.pad 1852 self._show_cords = show_cords 1853 self.redrawCanvas() 1854 1855 def _getShowCords(self): 1856 return self._show_cords 1857 1858 show_cords = property(_getShowCords, _setShowCords) 1859 1860 def _setShowCaptured(self, show_captured, force_restore=False): 1861 self._show_captured = show_captured or self.model.variant.variant in DROP_VARIANTS 1862 1863 alloc = self.get_allocation() 1864 size = alloc.height / self.RANKS 1865 1866 persp = perspective_manager.get_perspective("games") 1867 if self._show_captured: 1868 needed_width = size * (self.FILES + self.FILES_FOR_HOLDING) + self.padding * 2 1869 if alloc.width < needed_width: 1870 persp.adjust_divider(needed_width - alloc.width) 1871 elif force_restore: 1872 needed_width = size * self.FILES + self.padding * 2 1873 if alloc.width > needed_width: 1874 persp.adjust_divider(needed_width - alloc.width) 1875 1876 self.redrawCanvas() 1877 1878 def _getShowCaptured(self): 1879 return False if self.preview else self._show_captured 1880 1881 showCaptured = property(_getShowCaptured, _setShowCaptured) 1882 1883 def _setShowEnpassant(self, show_enpassant): 1884 if self._show_enpassant == show_enpassant: 1885 return 1886 if self.model: 1887 enpascord = self.model.boards[-1].enpassant 1888 if enpascord: 1889 rectangle = rect(self.cord2RectRelative(enpascord)) 1890 self.redrawCanvas(rectangle) 1891 self._show_enpassant = show_enpassant 1892 1893 def _getShowEnpassant(self): 1894 return self._show_enpassant 1895 1896 showEnpassant = property(_getShowEnpassant, _setShowEnpassant) 1897 1898 ########################### 1899 # Other # 1900 ########################### 1901 1902 def cord2Rect(self, cord, y_loc=None): 1903 if y_loc is None: 1904 x_loc, y_loc = cord.x, cord.y 1905 else: 1906 x_loc = cord 1907 xc_loc, yc_loc, side = self.square[0], self.square[1], self.square[3] 1908 return ((xc_loc + (x_loc * side)), (yc_loc + (self.RANKS - 1 - y_loc) * side), side) 1909 1910 def cord2Point(self, cord, y_loc=None): 1911 point = self.cord2Rect(cord, y_loc) 1912 return point[:2] 1913 1914 def cord2RectRelative(self, cord, y_loc=None): 1915 """ Like cord2Rect, but gives you bounding rect in case board is beeing 1916 Rotated """ 1917 if isinstance(cord, tuple): 1918 cx_loc, cy_loc, square = cord 1919 else: 1920 cx_loc, cy_loc, square = self.cord2Rect(cord, y_loc) 1921 x_zero, y_zero = self.matrix.transform_point(cx_loc, cy_loc) 1922 x_one, y_one = self.matrix.transform_point(cx_loc + square, cy_loc) 1923 x_two, y_two = self.matrix.transform_point(cx_loc, cy_loc + square) 1924 x_three, y_three = self.matrix.transform_point(cx_loc + square, cy_loc + square) 1925 x_loc = min(x_zero, x_one, x_two, x_three) 1926 y_loc = min(y_zero, y_one, y_two, y_three) 1927 square = max(y_zero, y_one, y_two, y_three) - y_loc 1928 return (x_loc, y_loc, square) 1929 1930 def isLight(self, cord): 1931 """ Description: Given a board co-ordinate it returns True 1932 if the square at that co-ordinate is light 1933 Return : Boolean 1934 """ 1935 if self.model.variant.variant in ASEAN_VARIANTS: 1936 return False 1937 x_loc, y_loc = cord.cords 1938 return (x_loc % 2 + y_loc % 2) == 1 1939 1940 def showFirst(self): 1941 if self.model.examined and self.model.noTD: 1942 self.model.goFirst() 1943 else: 1944 self.shown = self.model.lowply 1945 self.shown_variation_idx = 0 1946 1947 def showPrev(self, step=1): 1948 # If prev board belongs to a higher level variation 1949 # we have to update shown_variation_idx 1950 old_variation_idx = None 1951 if not self.shownIsMainLine(): 1952 board = self.model.getBoardAtPly(self.shown - step, self.shown_variation_idx) 1953 for vari in self.model.variations: 1954 if board in vari: 1955 break 1956 # swich to the new variation 1957 old_variation_idx = self.shown_variation_idx 1958 self.shown_variation_idx = self.model.variations.index(vari) 1959 1960 if self.model.examined and self.model.noTD: 1961 self.model.goPrev(step) 1962 else: 1963 if self.shown > self.model.lowply: 1964 if self.shown - step > self.model.lowply: 1965 self._setShown(self.shown - step, old_variation_idx) 1966 else: 1967 self._setShown(self.model.lowply, old_variation_idx) 1968 1969 def showNext(self, step=1): 1970 if self.model.examined and self.model.noTD: 1971 self.model.goNext(step) 1972 else: 1973 maxply = self.model.variations[self.shown_variation_idx][-1].ply 1974 if self.shown < maxply: 1975 if self.shown + step < maxply: 1976 self.shown += step 1977 else: 1978 self.shown = maxply 1979 1980 def showLast(self): 1981 if self.model.examined and self.model.noTD: 1982 self.model.goLast() 1983 else: 1984 maxply = self.model.variations[self.shown_variation_idx][-1].ply 1985 self.shown = maxply 1986 1987 def backToMainLine(self): 1988 if self.model.examined and self.model.noTD: 1989 self.model.backToMainLine() 1990 else: 1991 while not self.shownIsMainLine(): 1992 self.showPrev() 1993 1994 def backToParentLine(self): 1995 if self.model.examined and self.model.noTD: 1996 self.model.backToMainLine() 1997 else: 1998 varline = self.shown_variation_idx 1999 while True: 2000 self.showPrev() 2001 if self.shownIsMainLine() or self.shown_variation_idx != varline: 2002 break 2003 2004 def setPremove(self, premove_piece, premove0, premove1, premove_ply, promotion=None): 2005 self.premove_piece = premove_piece 2006 self.premove0 = premove0 2007 self.premove1 = premove1 2008 self.premove_ply = premove_ply 2009 self.premove_promotion = promotion 2010 2011 def copy_pgn(self): 2012 output = StringIO() 2013 clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) 2014 clipboard.set_text(pgn.save(output, self.model), -1) 2015 2016 def copy_fen(self): 2017 clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) 2018 fen = self.model.getBoardAtPly(self.shown, self.shown_variation_idx).asFen() 2019 clipboard.set_text(fen, -1) 2020