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