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