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