Make expose handler generic
[xboard.git] / draw.c
1 /*
2  * draw.c -- drawing routines for XBoard
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * The following terms apply to Digital Equipment Corporation's copyright
11  * interest in XBoard:
12  * ------------------------------------------------------------------------
13  * All Rights Reserved
14  *
15  * Permission to use, copy, modify, and distribute this software and its
16  * documentation for any purpose and without fee is hereby granted,
17  * provided that the above copyright notice appear in all copies and that
18  * both that copyright notice and this permission notice appear in
19  * supporting documentation, and that the name of Digital not be
20  * used in advertising or publicity pertaining to distribution of the
21  * software without specific, written prior permission.
22  *
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29  * SOFTWARE.
30  * ------------------------------------------------------------------------
31  *
32  * The following terms apply to the enhanced version of XBoard
33  * distributed by the Free Software Foundation:
34  * ------------------------------------------------------------------------
35  *
36  * GNU XBoard is free software: you can redistribute it and/or modify
37  * it under the terms of the GNU General Public License as published by
38  * the Free Software Foundation, either version 3 of the License, or (at
39  * your option) any later version.
40  *
41  * GNU XBoard is distributed in the hope that it will be useful, but
42  * WITHOUT ANY WARRANTY; without even the implied warranty of
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44  * General Public License for more details.
45  *
46  * You should have received a copy of the GNU General Public License
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *
48  *
49  *------------------------------------------------------------------------
50  ** See the file ChangeLog for a revision history.  */
51
52 #include "config.h"
53
54 #include <stdio.h>
55 #include <math.h>
56 #include <cairo/cairo.h>
57 #include <cairo/cairo-xlib.h>
58
59 #if STDC_HEADERS
60 # include <stdlib.h>
61 # include <string.h>
62 #else /* not STDC_HEADERS */
63 extern char *getenv();
64 # if HAVE_STRING_H
65 #  include <string.h>
66 # else /* not HAVE_STRING_H */
67 #  include <strings.h>
68 # endif /* not HAVE_STRING_H */
69 #endif /* not STDC_HEADERS */
70
71 #if ENABLE_NLS
72 #include <locale.h>
73 #endif
74
75
76 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
77 #include "common.h"
78
79 #if HAVE_LIBXPM
80 #include "pixmaps/pixmaps.h"
81 #else
82 #include "bitmaps/bitmaps.h"
83 #endif
84
85 #include "frontend.h"
86 #include "backend.h"
87 #include "xevalgraph.h"
88 #include "board.h"
89 #include "menus.h"
90 #include "dialogs.h"
91 #include "gettext.h"
92 #include "draw.h"
93
94
95 #ifdef __EMX__
96 #ifndef HAVE_USLEEP
97 #define HAVE_USLEEP
98 #endif
99 #define usleep(t)   _sleep2(((t)+500)/1000)
100 #endif
101
102 #ifdef ENABLE_NLS
103 # define  _(s) gettext (s)
104 # define N_(s) gettext_noop (s)
105 #else
106 # define  _(s) (s)
107 # define N_(s)  s
108 #endif
109
110 #define SOLID 0
111 #define OUTLINE 1
112 Boolean cairoAnimate;
113 static cairo_surface_t *csBoardWindow, *csBoardBackup, *csDualBoard;
114 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
115 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
116 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
117 static cairo_surface_t *pngBoardBitmap[2];
118 int useTexture, textureW[2], textureH[2];
119
120 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
121 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
122
123 #define White(piece) ((int)(piece) < (int)BlackPawn)
124
125 struct {
126   int x1, x2, y1, y2;
127 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
128
129 static int dual = 0;
130
131 void
132 SwitchWindow ()
133 {
134     cairo_surface_t *cstmp = csBoardWindow;
135     csBoardWindow = csDualBoard;
136     dual = !dual;
137     if(!csDualBoard) {
138         csBoardWindow = GetOutputSurface(&dualOptions[3], 0, 0);
139         dual = 1;
140     }
141     csDualBoard = cstmp;
142 }
143
144 void
145 NewSurfaces ()
146 {
147 return;
148     // delete surfaces after size becomes invalid, so they will be recreated
149     if(csBoardWindow) cairo_surface_destroy(csBoardWindow);
150     if(csBoardBackup) cairo_surface_destroy(csBoardBackup);
151     if(csDualBoard) cairo_surface_destroy(csDualBoard);
152     csBoardWindow = csBoardBackup = csDualBoard = NULL;
153 }
154
155 #define BoardSize int
156 void
157 InitDrawingSizes (BoardSize boardSize, int flags)
158 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
159     int boardWidth, boardHeight;
160     int i;
161     static int oldWidth, oldHeight;
162     static VariantClass oldVariant;
163     static int oldMono = -1, oldTwoBoards = 0;
164     extern Widget formWidget;
165
166     if(!formWidget) return;
167
168     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
169     oldTwoBoards = twoBoards;
170
171     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
172     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
173     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
174
175   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
176
177     oldWidth = boardWidth; oldHeight = boardHeight;
178     CreateGrid();
179     NewSurfaces();
180
181     /*
182      * Inhibit shell resizing.
183      */
184     ResizeBoardWindow(boardWidth, boardHeight, 0);
185
186     DelayedDrag();
187   }
188
189     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
190     // (only for xpm)
191
192   if(gameInfo.variant != oldVariant) { // and only if variant changed
193
194     for(i=0; i<2; i++) {
195         int p;
196         for(p=0; p<=(int)WhiteKing; p++)
197            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
198         if(gameInfo.variant == VariantShogi) {
199            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
200            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
201            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
202            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
203            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
204         }
205 #ifdef GOTHIC
206         if(gameInfo.variant == VariantGothic) {
207            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
208         }
209 #endif
210         if(gameInfo.variant == VariantSChess) {
211            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
212            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
213         }
214     }
215     oldMono = -10; // kludge to force recreation of animation masks
216     oldVariant = gameInfo.variant;
217   }
218   CreateAnimVars();
219   oldMono = appData.monoMode;
220 }
221
222 static void
223 CreatePNGBoard (char *s, int kind)
224 {
225     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
226     if(strstr(s, ".png")) {
227         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
228         if(img) {
229             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
230             textureW[kind] = cairo_image_surface_get_width (img);
231             textureH[kind] = cairo_image_surface_get_height (img);
232         }
233     }
234 }
235
236 char *pngPieceNames[] = // must be in same order as internal piece encoding
237 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner", 
238   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King", 
239   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
240 };
241
242 cairo_surface_t *
243 ConvertPixmap (int color, int piece)
244 {
245   int i, j, stride, f, colcode[10], w, b;
246   char ch[10];
247   cairo_surface_t *res;
248   XpmPieces *p = builtInXpms + 10;
249   char **pixels = p->xpm[piece % BlackPawn][2*color];
250   int *buf;
251   sscanf(pixels[0], "%*d %*d %d", &f);
252   sscanf(appData.whitePieceColor+1, "%x", &w);
253   sscanf(appData.blackPieceColor+1, "%x", &b);
254   res = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, p->size, p->size);
255   stride = cairo_image_surface_get_stride(res);
256   buf = (int *) cairo_image_surface_get_data(res);
257   for(i=0; i<f; i++) {
258     ch[i] = pixels[i+1][0];
259     colcode[i] = 0;
260     if(strstr(pixels[i+1], "black")) colcode[i] = 0xFF000000; // 0xFF000000 + (color ? b : 0);
261     if(strstr(pixels[i+1], "white")) colcode[i] = 0xFFFFFFCC; // 0xFF000000 + w;
262   }
263   for(i=0; i<p->size; i++) {
264     for(j=0; j<p->size; j++) {
265       char c = pixels[i+f+1][j];
266       int k;
267       for(k=0; ch[k] != c && k < f; k++);
268       buf[i*p->size + j] = colcode[k];
269     }
270   }
271   cairo_surface_mark_dirty(res);
272   return res;
273 }
274
275 static void
276 ScaleOnePiece (char *name, int color, int piece)
277 {
278   float w, h;
279   char buf[MSG_SIZ];
280   cairo_surface_t *img, *cs;
281   cairo_t *cr;
282
283   if((img = pngPieceImages[color][piece]) == NULL) { // if PNG file for this piece was not yet read, read it now and store it
284     if(!*appData.pngDirectory) img = ConvertPixmap(color, piece); else {
285       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pngDirectory, color ? "Black" : "White", pngPieceNames[piece]);
286       img = cairo_image_surface_create_from_png (buf);
287       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) img = ConvertPixmap(color, piece);
288     }
289   }
290   pngPieceImages[color][piece] = img;
291   // create new bitmap to hold scaled piece image (and remove any old)
292   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
293   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
294   if(piece <= WhiteKing) pngPieceBitmaps[color][piece] = cs;
295   // scaled copying of the raw png image
296   cr = cairo_create(cs);
297   w = cairo_image_surface_get_width (img);
298   h = cairo_image_surface_get_height (img);
299   cairo_scale(cr, squareSize/w, squareSize/h);
300   cairo_set_source_surface (cr, img, 0, 0);
301   cairo_paint (cr);
302   cairo_destroy (cr);
303   { // operate on bitmap to color it (king-size hack...)
304     int stride = cairo_image_surface_get_stride(cs)/4;
305     int *buf = (int *) cairo_image_surface_get_data(cs);
306     int i, j, p;
307     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
308     cairo_surface_flush(cs);
309     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
310         int r, a;
311         float f;
312         unsigned int c = buf[i*stride + j];
313         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
314         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
315         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
316         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
317         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
318     }
319     cairo_surface_mark_dirty(cs);
320   }
321 }
322
323 void
324 CreatePNGPieces ()
325 {
326   int p;
327
328   for(p=0; pngPieceNames[p]; p++) {
329     ScaleOnePiece(pngPieceNames[p], 0, p);
330     ScaleOnePiece(pngPieceNames[p], 1, p);
331   }
332 }
333
334 void
335 CreateAnyPieces ()
336 {   // [HGM] taken out of main
337     CreatePNGPieces();
338     CreatePNGBoard(appData.liteBackTextureFile, 1);
339     CreatePNGBoard(appData.darkBackTextureFile, 0);
340 }
341
342 void
343 InitDrawingParams (int reloadPieces)
344 {
345     int i, p;
346     MakeColors();
347     if(reloadPieces)
348     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
349         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
350         pngPieceImages[i][p] = NULL;
351     }
352     CreateAnyPieces();
353 }
354
355 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
356
357 float
358 Color (char *col, int n)
359 {
360   int c;
361   sscanf(col, "#%x", &c);
362   c = c >> 4*n & 255;
363   return c/255.;
364 }
365
366 void
367 SetPen (cairo_t *cr, float w, char *col, int dash)
368 {
369   static const double dotted[] = {4.0, 4.0};
370   static int len  = sizeof(dotted) / sizeof(dotted[0]);
371   cairo_set_line_width (cr, w);
372   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
373   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
374 }
375
376 void DrawSeekAxis( int x, int y, int xTo, int yTo )
377 {
378     cairo_t *cr;
379
380     /* get a cairo_t */
381     cr = cairo_create (csBoardWindow);
382
383     cairo_move_to (cr, x, y);
384     cairo_line_to(cr, xTo, yTo );
385
386     SetPen(cr, 2, "#000000", 0);
387     cairo_stroke(cr);
388
389     /* free memory */
390     cairo_destroy (cr);
391 }
392
393 void DrawSeekBackground( int left, int top, int right, int bottom )
394 {
395     cairo_t *cr = cairo_create (csBoardWindow);
396
397     cairo_rectangle (cr, left, top, right-left, bottom-top);
398
399     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
400     cairo_fill(cr);
401
402     /* free memory */
403     cairo_destroy (cr);
404 }
405
406 void DrawSeekText(char *buf, int x, int y)
407 {
408     cairo_t *cr = cairo_create (csBoardWindow);
409
410     cairo_select_font_face (cr, "Sans",
411                             CAIRO_FONT_SLANT_NORMAL,
412                             CAIRO_FONT_WEIGHT_NORMAL);
413
414     cairo_set_font_size (cr, 12.0);
415
416     cairo_move_to (cr, x, y+4);
417     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
418     cairo_show_text( cr, buf);
419
420     /* free memory */
421     cairo_destroy (cr);
422 }
423
424 void DrawSeekDot(int x, int y, int colorNr)
425 {
426     cairo_t *cr = cairo_create (csBoardWindow);
427     int square = colorNr & 0x80;
428     colorNr &= 0x7F;
429
430     if(square)
431         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
432     else
433         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
434
435     SetPen(cr, 2, "#000000", 0);
436     cairo_stroke_preserve(cr);
437     switch (colorNr) {
438       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
439       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
440       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
441     }
442     cairo_fill(cr);
443
444     /* free memory */
445     cairo_destroy (cr);
446 }
447
448 void
449 DrawSeekOpen ()
450 {
451     csBoardWindow = (cairo_surface_t *) mainOptions[W_BOARD].choice;
452 }
453
454 void
455 DrawSeekClose ()
456 {
457 }
458
459 void
460 CreateGrid ()
461 {
462     int i, j;
463
464     if (lineGap == 0) return;
465
466     /* [HR] Split this into 2 loops for non-square boards. */
467
468     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
469         gridSegments[i].x1 = 0;
470         gridSegments[i].x2 =
471           lineGap + BOARD_WIDTH * (squareSize + lineGap);
472         gridSegments[i].y1 = gridSegments[i].y2
473           = lineGap / 2 + (i * (squareSize + lineGap));
474     }
475
476     for (j = 0; j < BOARD_WIDTH + 1; j++) {
477         gridSegments[j + i].y1 = 0;
478         gridSegments[j + i].y2 =
479           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
480         gridSegments[j + i].x1 = gridSegments[j + i].x2
481           = lineGap / 2 + (j * (squareSize + lineGap));
482     }
483 }
484
485 void
486 DrawGrid()
487 {
488   /* draws a grid starting around Nx, Ny squares starting at x,y */
489   int i;
490   cairo_t *cr;
491
492   DrawSeekOpen();
493   /* get a cairo_t */
494   cr = cairo_create (csBoardWindow);
495
496   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
497   SetPen(cr, lineGap, "#000000", 0);
498
499   /* lines in X */
500   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
501     {
502       cairo_move_to (cr, gridSegments[i].x1, gridSegments[i].y1);
503       cairo_line_to (cr, gridSegments[i].x2, gridSegments[i].y2);
504       cairo_stroke (cr);
505     }
506
507   /* free memory */
508   cairo_destroy (cr);
509
510   return;
511 }
512
513 void
514 DrawBorder (int x, int y, int type)
515 {
516     cairo_t *cr;
517     DrawSeekOpen();
518     char *col;
519
520     switch(type) {
521         case 0: col = "#000000"; break;
522         case 1: col = appData.highlightSquareColor; break;
523         case 2: col = appData.premoveHighlightColor; break;
524     }
525     cr = cairo_create(csBoardWindow);
526     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
527     cairo_rectangle(cr, x, y, squareSize+lineGap, squareSize+lineGap);
528     SetPen(cr, lineGap, col, 0);
529     cairo_stroke(cr);
530     cairo_destroy(cr);
531     DrawExpose(NULL, x, y, squareSize+2*lineGap, squareSize+2*lineGap);
532 }
533
534 static int
535 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
536 {
537     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
538     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
539     *x0 = 0; *y0 = 0;
540     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
541     if(textureW[kind] < W*squareSize)
542         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
543     else
544         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
545     if(textureH[kind] < H*squareSize)
546         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
547     else
548         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
549     return 1;
550 }
551
552 void
553 DrawLogo (void *handle, void *logo)
554 {
555     cairo_surface_t *img, *cs;
556     cairo_t *cr;
557     int w, h;
558
559     if(!logo || !handle) return;
560     cs = GetOutputSurface(handle, appData.logoSize, appData.logoSize/2);
561     img = cairo_image_surface_create_from_png (logo);
562     w = cairo_image_surface_get_width (img);
563     h = cairo_image_surface_get_height (img);
564     cr = cairo_create(cs);
565     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
566     cairo_set_source_surface (cr, img, 0, 0);
567     cairo_paint (cr);
568     cairo_destroy (cr);
569     cairo_surface_destroy (img);
570     cairo_surface_destroy (cs);
571 }
572
573 static void
574 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
575 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
576     int x0, y0;
577     cairo_t *cr;
578
579     cr = cairo_create (dest);
580
581     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
582             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
583             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
584             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
585             cairo_fill (cr);
586             cairo_destroy (cr);
587     } else { // evenly colored squares
588         char *col;
589         switch (color) {
590           case 0: col = appData.darkSquareColor; break;
591           case 1: col = appData.lightSquareColor; break;
592           case 2: col = "#000000"; break;
593         }
594         SetPen(cr, 2.0, col, 0);
595         cairo_rectangle (cr, x, y, squareSize, squareSize);
596         cairo_fill (cr);
597         cairo_destroy (cr);
598     }
599 }
600
601 static void
602 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
603 {
604     int kind, p = piece;
605     cairo_t *cr;
606
607     if ((int)piece < (int) BlackPawn) {
608         kind = 0;
609     } else {
610         kind = 1;
611         piece -= BlackPawn;
612     }
613     if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
614     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
615     cr = cairo_create (dest);
616     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
617     cairo_paint(cr);
618     cairo_destroy (cr);
619 }
620
621 void
622 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
623 {
624         cairo_t *cr;
625
626         cr = cairo_create(cs);
627         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
628         if(appData.monoMode) {
629             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
630             cairo_stroke_preserve(cr);
631             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
632         } else {
633             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
634         }
635         cairo_fill(cr);
636
637         cairo_destroy(cr);
638 }
639
640 void
641 DrawDot (int marker, int x, int y, int r)
642 { // used for atomic captures; no need to draw on backup
643   DrawSeekOpen();
644   DoDrawDot(csBoardWindow, marker, x, y, r);
645 }
646
647 void
648 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
649 {   // basic front-end board-draw function: takes care of everything that can be in square:
650     // piece, background, coordinate/count, marker dot
651     cairo_t *cr;
652
653     DrawSeekOpen();
654
655     if (piece == EmptySquare) {
656         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
657     } else {
658         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
659     }
660
661     if(align) { // square carries inscription (coord or piece count)
662         int xx = x, yy = y;
663         cairo_text_extents_t te;
664
665         cr = cairo_create (csBoardWindow);
666         cairo_select_font_face (cr, "Sans",
667                     CAIRO_FONT_SLANT_NORMAL,
668                     CAIRO_FONT_WEIGHT_BOLD);
669
670         cairo_set_font_size (cr, squareSize/4);
671         // calculate where it goes
672         cairo_text_extents (cr, string, &te);
673
674         if (align == 1) {
675             xx += squareSize - te.width - te.x_bearing - 1;
676             yy += squareSize - te.height - te.y_bearing - 1;
677         } else if (align == 2) {
678             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
679         } else if (align == 3) {
680             xx += squareSize - te.width -te.x_bearing - 1;
681             yy += -te.y_bearing + 3;
682         } else if (align == 4) {
683             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
684         }
685
686         cairo_move_to (cr, xx-1, yy);
687         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
688         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
689         cairo_show_text (cr, string);
690         cairo_destroy (cr);
691     }
692
693     if(marker) { // print fat marker dot, if requested
694         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
695     }
696 }
697
698 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
699
700 /*      Masks for XPM pieces. Black and white pieces can have
701         different shapes, but in the interest of retaining my
702         sanity pieces must have the same outline on both light
703         and dark squares, and all pieces must use the same
704         background square colors/images.                */
705
706 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
707
708 static void
709 InitAnimState (AnimNr anr)
710 {
711     DrawSeekOpen(); // set cs to board widget
712     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
713     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
714     c_animBufs[anr+4] = csBoardWindow;
715     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
716     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
717 }
718
719 void
720 CreateAnimVars ()
721 {
722   InitAnimState(Game);
723   InitAnimState(Player);
724 }
725
726 static void
727 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
728 {
729   static cairo_t *pieceSource;
730   pieceSource = cairo_create (dest);
731   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
732   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
733   else cairo_paint(pieceSource);
734   cairo_destroy (pieceSource);
735 }
736
737 void
738 InsertPiece (AnimNr anr, ChessSquare piece)
739 {
740     CairoOverlayPiece(piece, c_animBufs[anr]);
741 }
742
743 void
744 DrawBlank (AnimNr anr, int x, int y, int startColor)
745 {
746     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
747 }
748
749 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
750                  int srcX, int srcY, int width, int height, int destX, int destY)
751 {
752         cairo_t *cr = cairo_create (c_animBufs[anr+destBuf]);
753         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
754         cairo_rectangle (cr, destX, destY, width, height);
755         cairo_fill (cr);
756         cairo_destroy (cr);
757         if(c_animBufs[anr+destBuf] == csBoardWindow)
758             DrawExpose(NULL, destX, destY, squareSize, squareSize);
759 }
760
761 void
762 SetDragPiece (AnimNr anr, ChessSquare piece)
763 {
764 }
765
766 /* [AS] Arrow highlighting support */
767
768 void
769 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
770 {
771     cairo_t *cr;
772     int i;
773     cr = cairo_create (cs);
774     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
775     for (i=0;i<nr;i++) {
776         cairo_line_to(cr, arrow[i].x, arrow[i].y);
777     }
778     if(appData.monoMode) { // should we always outline arrow?
779         cairo_line_to(cr, arrow[0].x, arrow[0].y);
780         SetPen(cr, 2, "#000000", 0);
781         cairo_stroke_preserve(cr);
782     }
783     SetPen(cr, 2, appData.highlightSquareColor, 0);
784     cairo_fill(cr);
785
786     /* free memory */
787     cairo_destroy (cr);
788 }
789
790 void
791 DrawPolygon (Pnt arrow[], int nr)
792 {
793     DoDrawPolygon(csBoardWindow, arrow, nr);
794 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
795 }
796
797