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