Updated copyright notice to 2015
[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, 2013, 2014, 2015 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 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
60
61 #if STDC_HEADERS
62 # include <stdlib.h>
63 # include <string.h>
64 #else /* not STDC_HEADERS */
65 extern char *getenv();
66 # if HAVE_STRING_H
67 #  include <string.h>
68 # else /* not HAVE_STRING_H */
69 #  include <strings.h>
70 # endif /* not HAVE_STRING_H */
71 #endif /* not STDC_HEADERS */
72
73 #if ENABLE_NLS
74 #include <locale.h>
75 #endif
76
77 #include "common.h"
78
79 #include "backend.h"
80 #include "board.h"
81 #include "menus.h"
82 #include "dialogs.h"
83 #include "evalgraph.h"
84 #include "gettext.h"
85 #include "draw.h"
86
87
88 #ifdef __EMX__
89 #ifndef HAVE_USLEEP
90 #define HAVE_USLEEP
91 #endif
92 #define usleep(t)   _sleep2(((t)+500)/1000)
93 #endif
94
95 #ifdef ENABLE_NLS
96 # define  _(s) gettext (s)
97 # define N_(s) gettext_noop (s)
98 #else
99 # define  _(s) (s)
100 # define N_(s)  s
101 #endif
102
103 #define SOLID 0
104 #define OUTLINE 1
105 Boolean cairoAnimate;
106 Option *currBoard;
107 cairo_surface_t *csBoardWindow;
108 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
109 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
110 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
111 static RsvgHandle *svgPieces[2][(int)BlackPawn+4]; // vector pieces in store
112 static cairo_surface_t *pngBoardBitmap[2], *pngOriginalBoardBitmap[2];
113 int useTexture, textureW[2], textureH[2];
114
115 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
116 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
117
118 #define White(piece) ((int)(piece) < (int)BlackPawn)
119
120 char svgDir[MSG_SIZ] = SVGDIR;
121
122 char *crWhite = "#FFFFB0";
123 char *crBlack = "#AD5D3D";
124
125 struct {
126   int x1, x2, y1, y2;
127 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
128
129 void
130 SwitchWindow (int main)
131 {
132     currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
133     csBoardWindow = DRAWABLE(currBoard);
134 }
135
136 void
137 SelectPieces(VariantClass v)
138 {
139     int i;
140     for(i=0; i<2; i++) {
141         int p;
142         for(p=0; p<=(int)WhiteKing; p++)
143            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
144         if(v == VariantShogi && BOARD_HEIGHT != 7) { // no exceptions in Tori Shogi
145            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteTokin];
146            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
147            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+3];
148            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+4];
149            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
150            pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteMonarch]; // for Sho Shogi
151         }
152 #ifdef GOTHIC
153         if(v == VariantGothic) {
154            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
155         }
156 #endif
157         if(v == VariantSChess) {
158            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
159            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
160         }
161         if(v == VariantChuChess) {
162            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
163         }
164         if(v == VariantChu) {
165            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteClaw];
166            pngPieceBitmaps[i][(int)WhiteClaw]    = pngPieceBitmaps2[i][(int)WhiteNightrider];
167            pngPieceBitmaps[i][(int)WhiteUnicorn] = pngPieceBitmaps2[i][(int)WhiteHorned];
168            pngPieceBitmaps[i][(int)WhiteSilver]  = pngPieceBitmaps2[i][(int)WhiteStag];
169            pngPieceBitmaps[i][(int)WhiteFalcon]  = pngPieceBitmaps2[i][(int)WhiteEagle];
170            pngPieceBitmaps[i][(int)WhiteHorned]  = pngPieceBitmaps2[i][(int)WhiteUnicorn];
171            pngPieceBitmaps[i][(int)WhiteStag]    = pngPieceBitmaps2[i][(int)WhiteSilver];
172            pngPieceBitmaps[i][(int)WhiteEagle]   = pngPieceBitmaps2[i][(int)WhiteFalcon];
173         }
174     }
175 }
176
177 #define BoardSize int
178 void
179 InitDrawingSizes (BoardSize boardSize, int flags)
180 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
181     int boardWidth, boardHeight;
182     static int oldWidth, oldHeight;
183     static VariantClass oldVariant;
184     static int oldTwoBoards = 0, oldNrOfFiles = 0;
185
186     if(!mainOptions[W_BOARD].handle) return;
187
188     if(boardSize == -2 && gameInfo.variant != oldVariant
189                        && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
190         squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
191         if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
192         squareSize -= lineGap;
193         CreatePNGPieces();
194         CreateGrid();
195     }
196     oldNrOfFiles = BOARD_WIDTH;
197
198     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
199     oldTwoBoards = twoBoards;
200
201     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
202     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
203     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
204
205   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
206
207     oldWidth = boardWidth; oldHeight = boardHeight;
208     CreateGrid();
209     CreateAnyPieces(0); // redo texture scaling
210
211     /*
212      * Inhibit shell resizing.
213      */
214     ResizeBoardWindow(boardWidth, boardHeight, 0);
215
216     DelayedDrag();
217   }
218
219     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
220     // (only for xpm)
221
222   if(gameInfo.variant != oldVariant) { // and only if variant changed
223
224     SelectPieces(gameInfo.variant);
225
226     oldVariant = gameInfo.variant;
227   }
228   CreateAnimVars();
229 }
230
231 void
232 ExposeRedraw (Option *graph, int x, int y, int w, int h)
233 {   // copy a selected part of the buffer bitmap to the display
234     cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
235     cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
236     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
237     cairo_rectangle(cr, x, y, w, h);
238     cairo_fill(cr);
239     cairo_destroy(cr);
240 }
241
242 static void
243 CreatePNGBoard (char *s, int kind)
244 {
245     float w, h;
246     static float n[2] = { 1., 1. };
247     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
248     textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
249     if(strstr(s, ".png")) {
250         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
251         if(img) {
252             char c, *p = s, *q;
253             int r, f;
254             if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
255             if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
256             useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
257             w = textureW[kind] = cairo_image_surface_get_width (img);
258             h = textureH[kind] = cairo_image_surface_get_height (img);
259             n[kind] = 1.;
260             while((q = strchr(p+1, '-'))) p = q; // find last '-'
261             if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
262                 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
263                 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
264                 textureH[kind] = (h*BOARD_HEIGHT)/r;
265                 n[kind] = r*squareSize/h; // scale to make it fit exactly vertically
266             } else
267             if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
268                 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
269             } else {
270                 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
271             }
272             if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
273                 // create scaled-up copy of the raw png image when it was too small
274                 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
275                 cairo_t *cr = cairo_create(cs);
276                 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
277 //              cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
278                 cairo_scale(cr, n[kind], n[kind]);
279                 cairo_set_source_surface (cr, img, 0, 0);
280                 cairo_paint (cr);
281                 cairo_destroy (cr);
282             }
283         }
284     }
285 }
286
287 char *pngPieceNames[] = // must be in same order as internal piece encoding
288 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
289   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
290   "GoldPawn", "Claw", "PromoHorse", "PromoDragon", "GoldLance", "PromoSword", "Prince", "Phoenix", "Kylin", "PromoRook", "PromoHSword",
291   "Dolphin", "Sword", "Leopard", "HSword", "GoldSilver", "Princess", "HCrown", "Knight", "Elephant", "PromoBishop", "King",
292   "Claw", "GoldKnight", "GoldLance", "GoldSilver", NULL
293 };
294
295 char *backupPiece[] = { "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
296                         NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion" }; // pieces that map on other when not kanji
297
298 RsvgHandle *
299 LoadSVG (char *dir, int color, int piece, int retry)
300 {
301     char buf[MSG_SIZ];
302   RsvgHandle *svg=svgPieces[color][piece];
303   RsvgDimensionData svg_dimensions;
304   GError *svgerror=NULL;
305   cairo_surface_t *img;
306   cairo_t *cr;
307   char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
308
309     if(!name) return NULL;
310
311     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
312
313     if(svg || *dir && (svg = rsvg_handle_new_from_file(buf, &svgerror))) {
314
315       rsvg_handle_get_dimensions(svg, &svg_dimensions);
316       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
317
318       cr = cairo_create(img);
319       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
320       rsvg_handle_render_cairo(svg, cr);
321       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
322         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
323         pngPieceImages[color][piece] = img;
324       }
325       cairo_destroy(cr);
326
327       return svg;
328     }
329     if(!retry && piece >= WhiteGrasshopper && piece <= WhiteNothing) // pieces that are only different in kanji sets
330         return LoadSVG(dir, color, piece, 1);
331     if(svgerror)
332         g_error_free(svgerror);
333     return NULL;
334 }
335
336 static void
337 ScaleOnePiece (int color, int piece)
338 {
339   float w, h;
340   char buf[MSG_SIZ];
341   cairo_surface_t *img, *cs;
342   cairo_t *cr;
343
344   g_type_init ();
345
346   svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
347
348   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
349     if(*appData.pieceDirectory) { // user specified piece directory
350       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
351       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
352       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
353         svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
354       } else pngPieceImages[color][piece] = img;
355     }
356   }
357
358   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
359     static int warned = 0;
360     if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) && !warned) { // try to fall back on installed svg 
361       char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
362       printf("%s\n", msg); // give up
363       DisplayError(msg, 0);
364       warned = 1; // prevent error message being repeated for each piece type
365     }
366   }
367
368   img = pngPieceImages[color][piece];
369
370   // create new bitmap to hold scaled piece image (and remove any old)
371   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
372   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
373
374   if(!img) return;
375
376   // scaled copying of the raw png image
377   cr = cairo_create(cs);
378   w = cairo_image_surface_get_width (img);
379   h = cairo_image_surface_get_height (img);
380   cairo_scale(cr, squareSize/w, squareSize/h);
381   cairo_set_source_surface (cr, img, 0, 0);
382   cairo_paint (cr);
383   cairo_destroy (cr);
384
385   if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
386     int stride = cairo_image_surface_get_stride(cs)/4;
387     int *buf = (int *) cairo_image_surface_get_data(cs);
388     int i, j, p;
389     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
390     cairo_surface_flush(cs);
391     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
392         int r, a;
393         float f;
394         unsigned int c = buf[i*stride + j];
395         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
396         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
397         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
398         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
399         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
400         if(appData.monoMode) {
401             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
402             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
403             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
404         }
405     }
406     cairo_surface_mark_dirty(cs);
407   }
408 }
409
410 void
411 CreatePNGPieces ()
412 {
413   int p;
414
415   for(p=0; pngPieceNames[p]; p++) {
416     ScaleOnePiece(0, p);
417     ScaleOnePiece(1, p);
418   }
419   SelectPieces(gameInfo.variant);
420 }
421
422 void
423 CreateAnyPieces (int p)
424 {   // [HGM] taken out of main
425     if(p) CreatePNGPieces();
426     CreatePNGBoard(appData.liteBackTextureFile, 1);
427     CreatePNGBoard(appData.darkBackTextureFile, 0);
428 }
429
430 void
431 InitDrawingParams (int reloadPieces)
432 {
433     int i, p;
434     if(reloadPieces)
435     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
436         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
437         pngPieceImages[i][p] = NULL;
438         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
439         svgPieces[i][p] = NULL;
440     }
441     CreateAnyPieces(1);
442 }
443
444 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
445
446 float
447 Color (char *col, int n)
448 {
449   int c;
450   sscanf(col, "#%x", &c);
451   c = c >> 4*n & 255;
452   return c/255.;
453 }
454
455 void
456 SetPen (cairo_t *cr, float w, char *col, int dash)
457 {
458   static const double dotted[] = {4.0, 4.0};
459   static int len  = sizeof(dotted) / sizeof(dotted[0]);
460   cairo_set_line_width (cr, w);
461   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
462   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
463 }
464
465 void DrawSeekAxis( int x, int y, int xTo, int yTo )
466 {
467     cairo_t *cr;
468
469     /* get a cairo_t */
470     cr = cairo_create (csBoardWindow);
471
472     cairo_move_to (cr, x, y);
473     cairo_line_to(cr, xTo, yTo );
474
475     SetPen(cr, 2, "#000000", 0);
476     cairo_stroke(cr);
477
478     /* free memory */
479     cairo_destroy (cr);
480     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
481 }
482
483 void DrawSeekBackground( int left, int top, int right, int bottom )
484 {
485     cairo_t *cr = cairo_create (csBoardWindow);
486
487     cairo_rectangle (cr, left, top, right-left, bottom-top);
488
489     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
490     cairo_fill(cr);
491
492     /* free memory */
493     cairo_destroy (cr);
494     GraphExpose(currBoard, left, top, right-left, bottom-top);
495 }
496
497 void DrawSeekText(char *buf, int x, int y)
498 {
499     cairo_t *cr = cairo_create (csBoardWindow);
500
501     cairo_select_font_face (cr, "Sans",
502                             CAIRO_FONT_SLANT_NORMAL,
503                             CAIRO_FONT_WEIGHT_NORMAL);
504
505     cairo_set_font_size (cr, 12.0);
506
507     cairo_move_to (cr, x, y+4);
508     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
509     cairo_show_text( cr, buf);
510
511     /* free memory */
512     cairo_destroy (cr);
513     GraphExpose(currBoard, x-5, y-10, 60, 15);
514 }
515
516 void DrawSeekDot(int x, int y, int colorNr)
517 {
518     cairo_t *cr = cairo_create (csBoardWindow);
519     int square = colorNr & 0x80;
520     colorNr &= 0x7F;
521
522     if(square)
523         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
524     else
525         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
526
527     SetPen(cr, 2, "#000000", 0);
528     cairo_stroke_preserve(cr);
529     switch (colorNr) {
530       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
531       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
532       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
533     }
534     cairo_fill(cr);
535
536     /* free memory */
537     cairo_destroy (cr);
538     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
539 }
540
541 void
542 InitDrawingHandle (Option *opt)
543 {
544     csBoardWindow = DRAWABLE(opt);
545 }
546
547 void
548 CreateGrid ()
549 {
550     int i, j;
551
552     if (lineGap == 0) return;
553
554     /* [HR] Split this into 2 loops for non-square boards. */
555
556     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
557         gridSegments[i].x1 = 0;
558         gridSegments[i].x2 =
559           lineGap + BOARD_WIDTH * (squareSize + lineGap);
560         gridSegments[i].y1 = gridSegments[i].y2
561           = lineGap / 2 + (i * (squareSize + lineGap));
562     }
563
564     for (j = 0; j < BOARD_WIDTH + 1; j++) {
565         gridSegments[j + i].y1 = 0;
566         gridSegments[j + i].y2 =
567           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
568         gridSegments[j + i].x1 = gridSegments[j + i].x2
569           = lineGap / 2 + (j * (squareSize + lineGap));
570     }
571 }
572
573 void
574 DrawGrid()
575 {
576   /* draws a grid starting around Nx, Ny squares starting at x,y */
577   int i;
578   float odd = (lineGap & 1)/2.;
579   cairo_t *cr;
580
581   /* get a cairo_t */
582   cr = cairo_create (csBoardWindow);
583
584   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
585   SetPen(cr, lineGap, "#000000", 0);
586
587   /* lines in X */
588   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
589     {
590       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
591       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
592       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
593       cairo_stroke (cr);
594     }
595
596   /* free memory */
597   cairo_destroy (cr);
598
599   return;
600 }
601
602 void
603 DrawBorder (int x, int y, int type, int odd)
604 {
605     cairo_t *cr;
606     char *col;
607
608     switch(type) {
609         case 0: col = "#000000"; break;
610         case 1: col = appData.highlightSquareColor; break;
611         case 2: col = appData.premoveHighlightColor; break;
612         default: col = "#808080"; break; // cannot happen
613     }
614     cr = cairo_create(csBoardWindow);
615     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
616     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
617     SetPen(cr, lineGap, col, 0);
618     cairo_stroke(cr);
619     cairo_destroy(cr);
620     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
621 }
622
623 static int
624 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
625 {
626     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
627     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
628     *x0 = 0; *y0 = 0;
629     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
630     if(textureW[kind] < W*squareSize)
631         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
632     else
633         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
634     if(textureH[kind] < H*squareSize)
635         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
636     else
637         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
638     return 1;
639 }
640
641 void
642 DrawLogo (Option *opt, void *logo)
643 {
644     cairo_surface_t *img;
645     cairo_t *cr;
646     int w, h;
647
648     if(!logo || !opt) return;
649     img = cairo_image_surface_create_from_png (logo);
650     w = cairo_image_surface_get_width (img);
651     h = cairo_image_surface_get_height (img);
652     cr = cairo_create(DRAWABLE(opt));
653 //    cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
654     cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
655     cairo_set_source_surface (cr, img, 0, 0);
656     cairo_paint (cr);
657     cairo_destroy (cr);
658     cairo_surface_destroy (img);
659     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
660 }
661
662 static void
663 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
664 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
665     int x0, y0;
666     cairo_t *cr;
667
668     cr = cairo_create (dest);
669
670     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
671             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
672             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
673             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
674             cairo_fill (cr);
675             cairo_destroy (cr);
676     } else { // evenly colored squares
677         char *col = NULL;
678         switch (color) {
679           case 0: col = appData.darkSquareColor; break;
680           case 1: col = appData.lightSquareColor; break;
681           case 2: col = "#000000"; break;
682           default: col = "#808080"; break; // cannot happen
683         }
684         SetPen(cr, 2.0, col, 0);
685         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
686         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
687         cairo_fill (cr);
688         cairo_destroy (cr);
689     }
690 }
691
692 static void
693 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
694 {
695     int kind;
696     cairo_t *cr;
697
698     if ((int)piece < (int) BlackPawn) {
699         kind = 0;
700     } else {
701         kind = 1;
702         piece -= BlackPawn;
703     }
704     if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
705     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
706     cr = cairo_create (dest);
707     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
708     cairo_paint(cr);
709     cairo_destroy (cr);
710 }
711
712 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
713
714 void
715 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
716 {
717         cairo_t *cr;
718
719         cr = cairo_create(cs);
720         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
721         if(appData.monoMode) {
722             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
723             cairo_stroke_preserve(cr);
724             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
725         } else {
726             SetPen(cr, 2, markerColor[marker-1], 0);
727         }
728         cairo_fill(cr);
729
730         cairo_destroy(cr);
731 }
732
733 void
734 DrawDot (int marker, int x, int y, int r)
735 { // used for atomic captures; no need to draw on backup
736   DoDrawDot(csBoardWindow, marker, x, y, r);
737   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
738 }
739
740 static void
741 DrawText (char *string, int x, int y, int align)
742 {
743         int xx = x, yy = y;
744         cairo_text_extents_t te;
745         cairo_t *cr;
746
747         cr = cairo_create (csBoardWindow);
748         cairo_select_font_face (cr, "Sans",
749                     CAIRO_FONT_SLANT_NORMAL,
750                     CAIRO_FONT_WEIGHT_BOLD);
751
752         cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
753         // calculate where it goes
754         cairo_text_extents (cr, string, &te);
755
756         if (align == 1) {
757             xx += squareSize - te.width - te.x_bearing - 1;
758             yy += squareSize - te.height - te.y_bearing - 1;
759         } else if (align == 2) {
760             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
761         } else if (align == 3) {
762             xx += squareSize - te.width -te.x_bearing - 1;
763             yy += -te.y_bearing + 3;
764         } else if (align == 4) {
765             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
766         } else if (align < 0) {
767             xx += squareSize/2 - te.width/2, yy += 9*squareSize/16 - te.y_bearing/2;
768         }
769
770         cairo_move_to (cr, xx-1, yy);
771         if(align == -2) cairo_set_source_rgb (cr, 1.0, 0.0, 0.0); else
772         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
773         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
774         cairo_show_text (cr, string);
775         cairo_destroy (cr);
776 }
777
778 void
779 InscribeKanji (ChessSquare piece, int x, int y)
780 {
781     char *p, *q, buf[10];
782     int n;
783     if(piece == EmptySquare) return;
784     if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
785     p = appData.inscriptions;
786     n = piece;
787     while(piece > WhitePawn) {
788       if(*p++ == NULLCHAR) {
789         if(n != WhiteKing) return;
790         p = q;
791         break;
792       }
793       q = p - 1;
794       while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
795       piece--;
796     }
797     strncpy(buf, p, 10);
798     for(q=buf; (*++q & 0xC0) == 0x80;);
799     *q = NULLCHAR;
800     DrawText(buf, x, y, n > WhiteLion ? -2 : -1);
801 }
802
803 void
804 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
805 {   // basic front-end board-draw function: takes care of everything that can be in square:
806     // piece, background, coordinate/count, marker dot
807
808     if (piece == EmptySquare) {
809         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
810     } else {
811         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
812         if(appData.inscriptions[0]) InscribeKanji(piece, x, y);
813     }
814
815     if(align) { // square carries inscription (coord or piece count)
816         if(align > 1) DrawText(tString, x, y, align);       // top (rank or count)
817         if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
818     }
819
820     if(marker) { // print fat marker dot, if requested
821         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
822     }
823 }
824
825 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
826
827 /*      Masks for XPM pieces. Black and white pieces can have
828         different shapes, but in the interest of retaining my
829         sanity pieces must have the same outline on both light
830         and dark squares, and all pieces must use the same
831         background square colors/images.                */
832
833 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
834
835 static void
836 InitAnimState (AnimNr anr)
837 {
838     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
839     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
840     c_animBufs[anr+4] = csBoardWindow;
841     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
842     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
843 }
844
845 void
846 CreateAnimVars ()
847 {
848   InitAnimState(Game);
849   InitAnimState(Player);
850 }
851
852 static void
853 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
854 {
855   static cairo_t *pieceSource;
856   pieceSource = cairo_create (dest);
857   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
858   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
859   else cairo_paint(pieceSource);
860   cairo_destroy (pieceSource);
861 }
862
863 void
864 InsertPiece (AnimNr anr, ChessSquare piece)
865 {
866     CairoOverlayPiece(piece, c_animBufs[anr]);
867 }
868
869 void
870 DrawBlank (AnimNr anr, int x, int y, int startColor)
871 {
872     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
873 }
874
875 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
876                  int srcX, int srcY, int width, int height, int destX, int destY)
877 {
878         cairo_t *cr;
879         c_animBufs[anr+4] = csBoardWindow;
880         cr = cairo_create (c_animBufs[anr+destBuf]);
881         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
882         cairo_rectangle (cr, destX, destY, width, height);
883         cairo_fill (cr);
884         cairo_destroy (cr);
885         if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
886             GraphExpose(currBoard, destX, destY, width, height);
887 }
888
889 void
890 SetDragPiece (AnimNr anr, ChessSquare piece)
891 {
892 }
893
894 /* [AS] Arrow highlighting support */
895
896 void
897 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
898 {
899     cairo_t *cr;
900     int i;
901     cr = cairo_create (cs);
902     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
903     for (i=0;i<nr;i++) {
904         cairo_line_to(cr, arrow[i].x, arrow[i].y);
905     }
906     if(appData.monoMode) { // should we always outline arrow?
907         cairo_line_to(cr, arrow[0].x, arrow[0].y);
908         SetPen(cr, 2, "#000000", 0);
909         cairo_stroke_preserve(cr);
910     }
911     SetPen(cr, 2, appData.highlightSquareColor, 0);
912     cairo_fill(cr);
913
914     /* free memory */
915     cairo_destroy (cr);
916 }
917
918 void
919 DrawPolygon (Pnt arrow[], int nr)
920 {
921     DoDrawPolygon(csBoardWindow, arrow, nr);
922 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
923 }
924
925 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
926
927 static void
928 ChoosePen(cairo_t *cr, int i)
929 {
930   switch(i) {
931     case PEN_BLACK:
932       SetPen(cr, 1.0, "#000000", 0);
933       break;
934     case PEN_DOTTED:
935       SetPen(cr, 1.0, "#A0A0A0", 1);
936       break;
937     case PEN_BLUEDOTTED:
938       SetPen(cr, 1.0, "#0000FF", 1);
939       break;
940     case PEN_BOLDWHITE:
941       SetPen(cr, 3.0, crWhite, 0);
942       break;
943     case PEN_BOLDBLACK:
944       SetPen(cr, 3.0, crBlack, 0);
945       break;
946     case PEN_BACKGD:
947       SetPen(cr, 3.0, "#E0E0F0", 0);
948       break;
949   }
950 }
951
952 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
953 void
954 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
955 {
956   static int curX, curY;
957
958   if(penType != PEN_NONE) {
959     cairo_t *cr = cairo_create(DRAWABLE(disp));
960     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
961     cairo_move_to (cr, curX, curY);
962     cairo_line_to (cr, x,y);
963     ChoosePen(cr, penType);
964     cairo_stroke (cr);
965     cairo_destroy (cr);
966   }
967
968   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
969   curX = x; curY = y;
970 }
971
972 // front-end wrapper for drawing functions to do rectangles
973 void
974 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
975 {
976   cairo_t *cr;
977
978   cr = cairo_create (DRAWABLE(disp));
979   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
980   cairo_rectangle (cr, left, top, right-left, bottom-top);
981   switch(side)
982     {
983     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
984     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
985     case 2: ChoosePen(cr, PEN_BACKGD); break;
986     }
987   cairo_fill (cr);
988
989   if(style != FILLED)
990     {
991       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
992       ChoosePen(cr, PEN_BLACK);
993       cairo_stroke (cr);
994     }
995
996   cairo_destroy(cr);
997 }
998
999 // front-end wrapper for putting text in graph
1000 void
1001 DrawEvalText (char *buf, int cbBuf, int y)
1002 {
1003     // the magic constants 8 and 5 should really be derived from the font size somehow
1004   cairo_text_extents_t extents;
1005   cairo_t *cr = cairo_create(DRAWABLE(disp));
1006
1007   /* GTK-TODO this has to go into the font-selection */
1008   cairo_select_font_face (cr, "Sans",
1009                           CAIRO_FONT_SLANT_NORMAL,
1010                           CAIRO_FONT_WEIGHT_NORMAL);
1011   cairo_set_font_size (cr, 12.0);
1012
1013
1014   cairo_text_extents (cr, buf, &extents);
1015
1016   cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1017   cairo_text_path (cr, buf);
1018   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1019   cairo_fill_preserve (cr);
1020   cairo_set_source_rgb (cr, 0, 1.0, 0);
1021   cairo_set_line_width (cr, 0.1);
1022   cairo_stroke (cr);
1023
1024   /* free memory */
1025   cairo_destroy (cr);
1026 }