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