/* -*- mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ 
/* 
 * Copyright 1997-2008 by Eric House (xwords@eehouse.org).  All rights
 * reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
#ifdef PLATFORM_GTK

#include <stdlib.h>
#include <stdio.h>

#include <gdk/gdkdrawable.h>

#include "gtkmain.h"
#include "draw.h"
#include "board.h"
#include "linuxmain.h"

typedef enum {
    XP_GTK_JUST_NONE
    ,XP_GTK_JUST_CENTER
    ,XP_GTK_JUST_TOPLEFT
    ,XP_GTK_JUST_BOTTOMRIGHT
} XP_GTK_JUST;

typedef struct FontPerSize {
	unsigned int ht;
	PangoFontDescription* fontdesc;
	PangoLayout* layout;
} FontPerSize;

/* static GdkGC* newGCForColor( GdkWindow* window, XP_Color* newC ); */
static void
gtkInsetRect( XP_Rect* r, short i )
{
    r->top += i;
    r->left += i;
    i *= 2;

    r->width -= i;
    r->height -= i;
} /* gtkInsetRect */

#if 0
#define DRAW_WHAT(dc) ((dc)->globals->pixmap)
#else
#define DRAW_WHAT(dc) ((dc)->drawing_area->window)
#endif

#define GTKMIN_W_HT 12

static void
gtkFillRect( GtkDrawCtx* dctx, const XP_Rect* rect, const GdkColor* color )
{
    gdk_gc_set_foreground( dctx->drawGC, color );
    gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC,
                        TRUE,
                        rect->left, rect->top, rect->width, 
                        rect->height );
}

static void
gtkEraseRect( GtkDrawCtx* dctx, const XP_Rect* rect )
{
    gdk_draw_rectangle( DRAW_WHAT(dctx),
                        dctx->drawing_area->style->white_gc,
                        TRUE, rect->left, rect->top, 
                        rect->width, rect->height );
} /* gtkEraseRect */

static void
frameRect( GtkDrawCtx* dctx, const XP_Rect* rect )
{
    gdk_draw_rectangle( DRAW_WHAT(dctx),
                        dctx->drawGC, FALSE, rect->left, rect->top, 
                        rect->width, rect->height );
} /* frameRect */

#ifdef DRAW_WITH_PRIMITIVES

static void
gtk_prim_draw_setClip( DrawCtx* p_dctx, XP_Rect* newClip, XP_Rect* oldClip)
{
} /* gtk_prim_draw_setClip */

static void 
gtk_prim_draw_frameRect( DrawCtx* p_dctx, XP_Rect* rect )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    frameRect( dctx, rect );
} /* gtk_prim_draw_frameRect */

static void
gtk_prim_draw_invertRect( DrawCtx* p_dctx, XP_Rect* rect )
{
    /* not sure you can do this on GTK!! */
} /* gtk_prim_draw_invertRect */

static void
gtk_prim_draw_clearRect( DrawCtx* p_dctx, XP_Rect* rect )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    gtkEraseRect( dctx, rect );
} /* gtk_prim_draw_clearRect */

static void
gtk_prim_draw_drawString( DrawCtx* p_dctx, XP_UCHAR* str,
                          XP_U16 x, XP_U16 y )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_U16 fontHeight = 10;     /* FIX ME */
    gdk_draw_string( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC,
                     x, y + fontHeight, str );
} /* gtk_prim_draw_drawString */

static void
gtk_prim_draw_drawBitmap( DrawCtx* p_dctx, XP_Bitmap bm, 
                          XP_U16 x, XP_U16 y )
{
} /* gtk_prim_draw_drawBitmap */

static void
gtk_prim_draw_measureText( DrawCtx* p_dctx, XP_UCHAR* str, 
                           XP_U16* widthP, XP_U16* heightP )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    gint len = strlen(str);
    gint width = gdk_text_measure( dctx->gdkFont, str, len );

    *widthP = width;
    *heightP = 12;              /* ??? :-) */
} /* gtk_prim_draw_measureText */

#endif /* DRAW_WITH_PRIMITIVES */

static gint
compForHt( gconstpointer  a,
		   gconstpointer  b )
{
	FontPerSize* fps1 = (FontPerSize*)a;
	FontPerSize* fps2 = (FontPerSize*)b;
	return fps1->ht - fps2->ht;
}

static PangoLayout*
layout_for_ht( GtkDrawCtx* dctx, XP_U16 ht )
{
	PangoLayout* layout = NULL;

	/* Try to find a cached layout.  Otherwise create a new one. */
	FontPerSize fps = { .ht = ht };
	GList* gl = g_list_find_custom( dctx->fontsPerSize, &fps,
									compForHt );
	if ( NULL != gl ) {
        layout = ((FontPerSize*)gl->data)->layout;
	}

	if ( NULL == layout ) {
		FontPerSize* fps = g_malloc( sizeof(*fps) );
		dctx->fontsPerSize = g_list_insert( dctx->fontsPerSize,
											fps, 0 );

		char font[32];
		snprintf( font, sizeof(font), "helvetica normal %dpx", ht );

        layout = pango_layout_new( dctx->pangoContext );
        fps->fontdesc = pango_font_description_from_string( font );
        pango_layout_set_font_description( layout, fps->fontdesc );
        fps->layout = layout;

		/* This only happens first time??? */
		pango_layout_set_alignment( layout, PANGO_ALIGN_CENTER );
		fps->ht = ht;
	}

	return layout;
} /* layout_for_ht */

static void
draw_string_at( GtkDrawCtx* dctx, PangoLayout* layout,
                const XP_UCHAR* str, XP_U16 fontHt,
                const XP_Rect* where, XP_GTK_JUST just,
                const GdkColor* frground, const GdkColor* bkgrnd )
{
    gint xx = where->left;
    gint yy = where->top;

    if ( !layout ) {
        layout = layout_for_ht( dctx, fontHt );
    }

    pango_layout_set_text( layout, str, XP_STRLEN(str) );

    if ( just != XP_GTK_JUST_NONE ) {
        int width, height;
        pango_layout_get_pixel_size( layout, &width, &height );

        switch( just ) {
        case XP_GTK_JUST_CENTER:
            xx += (where->width - width) / 2;
            yy += (where->height - height) / 2;
            break;
        case XP_GTK_JUST_BOTTOMRIGHT:
            xx += where->width - width;
            yy += where->height - height;
            break;
        case XP_GTK_JUST_TOPLEFT:
        default:
            /* nothing to do?? */
            break;
        }
    }

    gdk_draw_layout_with_colors( DRAW_WHAT(dctx), dctx->drawGC,
                                 xx, yy, layout,
                                 frground, bkgrnd );
} /* draw_string_at */

static void
drawBitmapFromLBS( GtkDrawCtx* dctx, const XP_Bitmap bm, const XP_Rect* rect )
{
    GdkPixmap* pm;
    LinuxBMStruct* lbs = (LinuxBMStruct*)bm;
    gint x, y;
    XP_U8* bp;
    XP_U16 i;
    XP_S16 nBytes;
    XP_U16 nCols, nRows;
    
    nCols = lbs->nCols;
    nRows = lbs->nRows;
    bp = (XP_U8*)(lbs + 1);    /* point to the bitmap data */
    nBytes = lbs->nBytes;

    pm = gdk_pixmap_new( DRAW_WHAT(dctx), nCols, nRows, -1 );

    gdk_draw_rectangle( pm, dctx->drawing_area->style->white_gc, TRUE,
                        0, 0, nCols, nRows );

    x = 0;
    y = 0;

    while ( nBytes-- ) {
        XP_U8 byte = *bp++;
        for ( i = 0; i < 8; ++i ) {
            XP_Bool draw = ((byte & 0x80) != 0);
            if ( draw ) {
                gdk_draw_point( pm, dctx->drawing_area->style->black_gc, x, y );
            }
            byte <<= 1;
            if ( ++x == nCols ) {
                x = 0;
                if ( ++y == nRows ) {
                    break;
                }
            }
        }
    }

    XP_ASSERT( nBytes == -1 );   /* else we're out of sync */

    gdk_draw_drawable( DRAW_WHAT(dctx),
                       dctx->drawGC,
                       (GdkDrawable*)pm, 0, 0,
                       rect->left+2,
                       rect->top+2,
                       lbs->nCols,
                       lbs->nRows );

    g_object_unref( pm );
} /* drawBitmapFromLBS */

static void
freer( gpointer data, gpointer XP_UNUSED(user_data) )
{
	FontPerSize* fps = (FontPerSize*)data;
	pango_font_description_free( fps->fontdesc );
	g_object_unref( fps->layout );
	g_free( fps );
}

static void
gtk_draw_destroyCtxt( DrawCtx* p_dctx )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    GtkAllocation* alloc = &dctx->drawing_area->allocation;

    gdk_draw_rectangle( DRAW_WHAT(dctx),
			dctx->drawing_area->style->white_gc,
			TRUE,
			0, 0, alloc->width, alloc->height );

	g_list_foreach( dctx->fontsPerSize, freer, NULL );
	g_list_free( dctx->fontsPerSize );

    g_object_unref( dctx->pangoContext );

} /* gtk_draw_destroyCtxt */


static void
gtk_draw_dictChanged( DrawCtx* XP_UNUSED(p_dctx), 
                      const DictionaryCtxt* XP_UNUSED(dict) )
{
}

static XP_Bool
gtk_draw_boardBegin( DrawCtx* p_dctx, const XP_Rect* rect, 
                     DrawFocusState XP_UNUSED(dfs) )
{
    GdkRectangle gdkrect;
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;

    gdk_gc_set_foreground( dctx->drawGC, &dctx->black );

    gdkrect = *(GdkRectangle*)rect;
    ++gdkrect.width;
    ++gdkrect.height;
/*     gdk_gc_set_clip_rectangle( dctx->drawGC, &gdkrect ); */

    return XP_TRUE;
} /* draw_finish */

static void
gtk_draw_objFinished( DrawCtx* XP_UNUSED(p_dctx), 
                      BoardObjectType XP_UNUSED(typ),
                      const XP_Rect* XP_UNUSED(rect), 
                      DrawFocusState XP_UNUSED(dfs) )
{
} /* draw_finished */


static XP_Bool
gtk_draw_vertScrollBoard( DrawCtx* p_dctx, XP_Rect* rect,
                          XP_S16 dist, DrawFocusState XP_UNUSED(dfs) )
{
    /* Turn this on to mimic what palm does, but need to figure out some gtk
       analog to copybits for it to actually work. */
#if 1
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_Bool down = dist <= 0;
    gint ysrc, ydest;

    if ( down ) {
        ysrc = rect->top;
        dist = -dist;           /* make it positive */
        ydest = ysrc + dist;
    } else {
        ydest = rect->top;
        ysrc = ydest + dist;
    }

    gdk_draw_drawable( DRAW_WHAT(dctx),
                       dctx->drawGC,
                       DRAW_WHAT(dctx),
                       rect->left,
                       ysrc,
                       rect->left,
                       ydest,
                       rect->width,
                       rect->height - dist );

    if ( !down ) {
        rect->top += rect->height - dist;
    }
    rect->height = dist;
#endif
    return XP_TRUE;
}


static void
drawHintBorders( GtkDrawCtx* dctx, const XP_Rect* rect, HintAtts hintAtts)
{
    if ( hintAtts != HINT_BORDER_NONE && hintAtts != HINT_BORDER_CENTER ) {
        XP_Rect lrect = *rect;
        gtkInsetRect( &lrect, 1 );

        gdk_gc_set_foreground( dctx->drawGC, &dctx->black );

        if ( (hintAtts & HINT_BORDER_LEFT) != 0 ) {
            gdk_draw_rectangle( DRAW_WHAT(dctx),
                                dctx->drawGC,
                                FALSE, lrect.left, lrect.top, 
                                0, lrect.height);
        }
        if ( (hintAtts & HINT_BORDER_TOP) != 0 ) {
            gdk_draw_rectangle( DRAW_WHAT(dctx),
                                dctx->drawGC,
                                FALSE, lrect.left, lrect.top, 
                                lrect.width, 0/*rectInset.height*/);
        }
        if ( (hintAtts & HINT_BORDER_RIGHT) != 0 ) {
            gdk_draw_rectangle( DRAW_WHAT(dctx),
                                dctx->drawGC,
                                FALSE, lrect.left+lrect.width, 
                                lrect.top, 
                                0, lrect.height);
        }
        if ( (hintAtts & HINT_BORDER_BOTTOM) != 0 ) {
            gdk_draw_rectangle( DRAW_WHAT(dctx),
                                dctx->drawGC,
                                FALSE, lrect.left, 
                                lrect.top+lrect.height, 
                                lrect.width, 0 );
        }
    }
}

static XP_Bool
gtk_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* letter,
                   const XP_Bitmaps* bitmaps, Tile XP_UNUSED(tile), 
                   XP_S16 owner, XWBonusType bonus, HintAtts hintAtts, 
                   CellFlags flags )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_Rect rectInset = *rect;
    XP_Bool showGrid = dctx->globals->gridOn;
    XP_Bool highlight = (flags & CELL_HIGHLIGHT) != 0;
    GdkColor* cursor = 
        ((flags & CELL_ISCURSOR) != 0) ? &dctx->cursor : NULL;

    gtkEraseRect( dctx, rect );

    gtkInsetRect( &rectInset, 1 );

    if ( showGrid ) {
        gdk_gc_set_foreground( dctx->drawGC, &dctx->black );
        gdk_draw_rectangle( DRAW_WHAT(dctx),
                            dctx->drawGC,
                            FALSE,
                            rect->left, rect->top, rect->width, 
                            rect->height );
    }

    /* We draw just an empty, potentially colored, square IFF there's nothing
       in the cell or if CELL_DRAGSRC is set */
    if ( (flags & (CELL_DRAGSRC|CELL_ISEMPTY)) != 0 ) {
        if ( !!cursor || bonus != BONUS_NONE ) {
            GdkColor* foreground;
            if ( !!cursor ) {
                foreground = cursor;
            } else if ( bonus != BONUS_NONE ) {
                foreground = &dctx->bonusColors[bonus-1];
            } else {
                foreground = NULL;
            }
            if ( !!foreground ) {
                gdk_gc_set_foreground( dctx->drawGC, foreground );
                gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, TRUE,
                                    rectInset.left, rectInset.top,
                                    rectInset.width+1, rectInset.height+1 );
            }
        }
        if ( (flags & CELL_ISSTAR) != 0 ) {
            draw_string_at( dctx, NULL, XP_L("*"), rect->height, rect, 
                            XP_GTK_JUST_CENTER, &dctx->black, NULL );
        }
    } else if ( !!bitmaps ) {
        drawBitmapFromLBS( dctx, bitmaps->bmps[0], rect );
    } else if ( !!letter ) {
        GdkColor* foreground;
        if ( cursor ) {
            gdk_gc_set_foreground( dctx->drawGC, cursor );
        } else if ( !highlight ) {
            gdk_gc_set_foreground( dctx->drawGC, &dctx->tileBack );
        }
        gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, TRUE,
                            rectInset.left, rectInset.top,
                            rectInset.width+1, rectInset.height+1 );

        foreground = highlight? &dctx->white : &dctx->playerColors[owner];
        draw_string_at( dctx, NULL, letter, rectInset.height-2, &rectInset, 
                        XP_GTK_JUST_CENTER, foreground, cursor );

        if ( (flags & CELL_ISBLANK) != 0 ) {
            gdk_draw_arc( DRAW_WHAT(dctx), dctx->drawGC,
                          0,	/* filled */
                          rect->left, /* x */
                          rect->top, /* y */
                          rect->width,/*width, */
                          rect->height,/*width, */
                          0, 360*64 );
        }
    }

    drawHintBorders( dctx, rect, hintAtts );

    return XP_TRUE;
} /* gtk_draw_drawCell */

static void
gtk_draw_invertCell( DrawCtx* XP_UNUSED(p_dctx), 
                     const XP_Rect* XP_UNUSED(rect) )
{
/*     GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; */
/*     (void)gtk_draw_drawMiniWindow( p_dctx, "f", rect); */

/*     GdkGCValues values; */

/*     gdk_gc_get_values( dctx->drawGC, &values ); */

/*     gdk_gc_set_function( dctx->drawGC, GDK_INVERT ); */

/*     gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rect ); */
/*     gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, */
/* 			TRUE, rect->left, rect->top,  */
/* 			rect->width, rect->height ); */

/*     gdk_gc_set_function( dctx->drawGC, values.function ); */
} /* gtk_draw_invertCell */

static XP_Bool
gtk_draw_trayBegin( DrawCtx* p_dctx, const XP_Rect* XP_UNUSED(rect),
                    XP_U16 owner, DrawFocusState dfs )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    dctx->trayOwner = owner;
    dctx->topFocus = dfs == DFS_TOP;
    return XP_TRUE;
} /* gtk_draw_trayBegin */

static void
gtkDrawTileImpl( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* textP,
                 const XP_Bitmaps* bitmaps, XP_U16 val, CellFlags flags, 
                 XP_Bool clearBack )
{
    XP_UCHAR numbuf[3];
    gint len; 
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_Rect insetR = *rect;
    XP_Bool isCursor = (flags & CELL_ISCURSOR) != 0;
    XP_Bool valHidden = (flags & CELL_VALHIDDEN) != 0;
    XP_Bool notEmpty = (flags & CELL_ISEMPTY) == 0;

    if ( clearBack ) {
        gtkEraseRect( dctx, &insetR );
    }

    if ( isCursor || notEmpty ) {
        GdkColor* foreground = &dctx->playerColors[dctx->trayOwner];
        XP_Rect formatRect = insetR;

        gtkInsetRect( &insetR, 1 );

        if ( clearBack ) {
            gtkFillRect( dctx, &insetR, 
                         isCursor ? &dctx->cursor : &dctx->tileBack );
        }

        formatRect.left += 3;
        formatRect.width -= 6;

        if ( notEmpty ) {
            if ( !!bitmaps ) {
                drawBitmapFromLBS( dctx, bitmaps->bmps[1], &insetR );
            } else if ( !!textP ) {
                if ( *textP != LETTER_NONE ) { /* blank */
                    draw_string_at( dctx, NULL, textP, formatRect.height>>1,
                                    &formatRect, XP_GTK_JUST_TOPLEFT,
                                    foreground, NULL );

                }
            }
    
            if ( !valHidden ) {
                XP_SNPRINTF( numbuf, VSIZE(numbuf), XP_L("%d"), val );
                len = XP_STRLEN( numbuf );

                draw_string_at( dctx, NULL, numbuf, formatRect.height>>2,
                                &formatRect, XP_GTK_JUST_BOTTOMRIGHT,
                                foreground, NULL );
            }
        }

        /* frame the tile */
        gdk_gc_set_foreground( dctx->drawGC, &dctx->black );
        gdk_draw_rectangle( DRAW_WHAT(dctx),
                            dctx->drawGC,
                            FALSE,
                            insetR.left, insetR.top, insetR.width, 
                            insetR.height );

        if ( (flags & CELL_HIGHLIGHT) != 0 ) {
            gtkInsetRect( &insetR, 1 );
            gdk_draw_rectangle( DRAW_WHAT(dctx),
                                dctx->drawGC,
                                FALSE, insetR.left, insetR.top, 
                                insetR.width, insetR.height);
        }
    }
} /* gtkDrawTileImpl */

static void
gtk_draw_drawTile( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* textP,
                   const XP_Bitmaps* bitmaps, XP_U16 val, CellFlags flags )
{
    gtkDrawTileImpl( p_dctx, rect, textP, bitmaps, val, flags, XP_TRUE );
}

#ifdef POINTER_SUPPORT
static void
gtk_draw_drawTileMidDrag( DrawCtx* p_dctx, const XP_Rect* rect, 
                          const XP_UCHAR* textP, const XP_Bitmaps* bitmaps, 
                          XP_U16 val, XP_U16 owner, CellFlags flags )
{
    gtk_draw_trayBegin( p_dctx, rect, owner, DFS_NONE );
    gtkDrawTileImpl( p_dctx, rect, textP, bitmaps, val, 
                     flags | CELL_HIGHLIGHT, XP_FALSE );
}
#endif

static void
gtk_draw_drawTileBack( DrawCtx* p_dctx, const XP_Rect* rect, 
                       CellFlags flags )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_Bool hasCursor = (flags & CELL_ISCURSOR) != 0;
    XP_Rect r = *rect;

    gtkInsetRect( &r, 1 );

    gtkFillRect( dctx, &r, &dctx->playerColors[dctx->trayOwner] );

    gtkInsetRect( &r, 1 );
    gtkFillRect( dctx, &r, hasCursor? &dctx->cursor : &dctx->tileBack );

    draw_string_at( dctx, NULL, XP_L("?"), r.height,
                    &r, XP_GTK_JUST_CENTER,
                    &dctx->playerColors[dctx->trayOwner], NULL );

} /* gtk_draw_drawTileBack */

static void
gtk_draw_drawTrayDivider( DrawCtx* p_dctx, const XP_Rect* rect, 
                          CellFlags flags )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_Rect r = *rect;
    XP_Bool selected = 0 != (flags & CELL_HIGHLIGHT);
    XP_Bool isCursor = 0 != (flags & CELL_ISCURSOR);

    gtkEraseRect( dctx, &r );

    gtkFillRect( dctx, &r, isCursor? &dctx->cursor:&dctx->white );

    r.left += 2;
    r.width -= 4;
    if ( selected ) {
        --r.height;
    }

    gdk_gc_set_foreground( dctx->drawGC, &dctx->black );
    gdk_draw_rectangle( DRAW_WHAT(dctx),
			dctx->drawGC,
			!selected, 
			r.left, r.top, r.width, r.height);
    
} /* gtk_draw_drawTrayDivider */

static void 
gtk_draw_clearRect( DrawCtx* p_dctx, const XP_Rect* rectP )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_Rect rect = *rectP;

    ++rect.width;
    ++rect.top;

    gtkEraseRect( dctx, &rect );

} /* gtk_draw_clearRect */

static void
gtk_draw_drawBoardArrow( DrawCtx* p_dctx, const XP_Rect* rectP, 
                         XWBonusType XP_UNUSED(cursorBonus), XP_Bool vertical,
                         HintAtts hintAtts, CellFlags XP_UNUSED(flags) )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    const XP_UCHAR* curs = vertical? XP_L("|"):XP_L("-");

    /* font needs to be small enough that "|" doesn't overwrite cell below */
    draw_string_at( dctx, NULL, curs, (rectP->height*2)/3,
                    rectP, XP_GTK_JUST_CENTER,
                    &dctx->black, NULL );
    drawHintBorders( dctx, rectP, hintAtts );
} /* gtk_draw_drawBoardCursor */

static void
gtk_draw_scoreBegin( DrawCtx* p_dctx, const XP_Rect* rect, 
                     XP_U16 XP_UNUSED(numPlayers), 
                     DrawFocusState dfs )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;

/*     gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rect ); */
    gtkEraseRect( dctx, rect );
    dctx->topFocus = dfs == DFS_TOP;
} /* gtk_draw_scoreBegin */

static PangoLayout*
getLayoutToFitRect( GtkDrawCtx* dctx, const XP_UCHAR* str, const XP_Rect* rect )
{
    PangoLayout* layout;
    float ratio, ratioH;
    int width, height;
    XP_U16 len = XP_STRLEN(str);

    /* First measure it using any font at all */
    layout = layout_for_ht( dctx, 24 );
    pango_layout_set_text( layout, (char*)str, len );
    pango_layout_get_pixel_size( layout, &width, &height );

    /* Figure the ratio of is to should-be.  The smaller of these is the one
       we must use. */
    ratio = (float)rect->width / (float)width;
    ratioH = (float)rect->height / (float)height;
    if ( ratioH < ratio ) {
        ratio = ratioH;
    }
    height = 24.0 * ratio;

    layout = layout_for_ht( dctx, height );
    pango_layout_set_text( layout, (char*)str, len );
    return layout;
} /* getLayoutToFitRect */

static void
gtkDrawDrawRemText( DrawCtx* p_dctx, const XP_Rect* rect, XP_S16 nTilesLeft,
                    XP_U16* widthP, XP_U16* heightP, XP_Bool focussed )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_UCHAR buf[10];
    PangoLayout* layout;

    XP_SNPRINTF( buf, sizeof(buf), XP_L("rem:%d"), nTilesLeft );
    layout = getLayoutToFitRect( dctx, buf, rect );

    if ( !!widthP ) {
        int width, height;
        pango_layout_get_pixel_size( layout, &width, &height );
        if ( width > rect->width ) {
            width = rect->width;
        }
        if ( height > rect->height ) {
            height = rect->height;
        }
        *widthP = width;
        *heightP = height;
    } else {
        const GdkColor* cursor = NULL;
        if ( focussed ) {
            cursor = &dctx->cursor;
            gtkFillRect( dctx, rect, cursor );
        }
        draw_string_at( dctx, layout, buf, rect->height,
                        rect, XP_GTK_JUST_TOPLEFT,
                        &dctx->black, cursor );
    }
} /* gtkDrawDrawRemText */

static void
gtk_draw_measureRemText( DrawCtx* p_dctx, const XP_Rect* rect, XP_S16 nTilesLeft,
                         XP_U16* width, XP_U16* height )
{
    if ( nTilesLeft <= 0 ) {
        *width = *height = 0;
    } else {
        gtkDrawDrawRemText( p_dctx, rect, nTilesLeft, width, height, XP_FALSE );
    }
} /* gtk_draw_measureRemText */

static void
gtk_draw_drawRemText( DrawCtx* p_dctx, const XP_Rect* rInner, 
                      const XP_Rect* XP_UNUSED(rOuter), XP_S16 nTilesLeft,
                      XP_Bool focussed )
{
    gtkDrawDrawRemText( p_dctx, rInner, nTilesLeft, NULL, NULL, focussed );
} /* gtk_draw_drawRemText */

static void
formatScoreText( XP_UCHAR* buf, XP_U16 bufLen, const DrawScoreInfo* dsi )
{
    XP_S16 score = dsi->totalScore;
    XP_U16 nTilesLeft = dsi->nTilesLeft;
    XP_Bool isTurn = dsi->isTurn;
    int used;
    char* borders = "";

    if ( isTurn ) {
        borders = "*";
    }

    used = XP_SNPRINTF( buf, bufLen, XP_L("%s%.3d"), borders, score );
    if ( (nTilesLeft < MAX_TRAY_TILES) && (nTilesLeft > 0) ) {
        XP_UCHAR nbuf[10];
        XP_SNPRINTF( nbuf, VSIZE(nbuf), XP_L(":%d"), nTilesLeft );
        (void)XP_STRCAT( buf, nbuf );
    }
    XP_SNPRINTF( buf+used, bufLen-used, XP_L("%s"), borders );
} /* formatScoreText */

static void
gtk_draw_measureScoreText( DrawCtx* p_dctx, const XP_Rect* bounds, 
                           const DrawScoreInfo* dsi,
                           XP_U16* widthP, XP_U16* heightP )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_UCHAR buf[20];
    PangoLayout* layout;
    int height, width;

    formatScoreText( buf, VSIZE(buf), dsi );
    layout = getLayoutToFitRect( dctx, buf, bounds );
    pango_layout_get_pixel_size( layout, &width, &height );

    *widthP = width;
    *heightP = height;
} /* gtk_draw_measureScoreText */

static void
gtk_draw_score_drawPlayer( DrawCtx* p_dctx, const XP_Rect* rInner, 
                           const XP_Rect* rOuter, const DrawScoreInfo* dsi )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_UCHAR scoreBuf[20];
    XP_Bool hasCursor = (dsi->flags & CELL_ISCURSOR) != 0;
    GdkColor* cursor = NULL;

    formatScoreText( scoreBuf, VSIZE(scoreBuf), dsi );

    if ( hasCursor ) {
        cursor = &dctx->cursor;
        gtkFillRect( dctx, rOuter, cursor );
    }

    gdk_gc_set_foreground( dctx->drawGC, &dctx->playerColors[dsi->playerNum] );

    if ( dsi->selected ) {
        XP_Rect selRect = *rOuter;
        XP_S16 diff = selRect.width - rInner->width;
        if ( diff > 0 ) {
            selRect.width -= diff>>1;
            selRect.left += diff>>2;
        }
        gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC,
                            TRUE, selRect.left, selRect.top, 
                            selRect.width, selRect.height );
        gtkEraseRect( dctx, rInner );
    }

    draw_string_at( dctx, NULL, scoreBuf, rInner->height - 1,
                    rInner, XP_GTK_JUST_CENTER,
                    &dctx->playerColors[dsi->playerNum], cursor );

} /* gtk_draw_score_drawPlayer */

static void
gtk_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect, 
                             XP_S16 score, XP_U16 XP_UNUSED(playerNum),
                             CellFlags flags )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_UCHAR buf[5];
    XP_U16 ht;
    XP_Rect localR;
    GdkColor* cursor = ((flags & CELL_ISCURSOR) != 0) 
        ? &dctx->cursor : NULL;

    if ( score >= 0 ) {
		XP_SNPRINTF( buf, VSIZE(buf), XP_L("%.3d"), score );
    } else {
		XP_STRNCPY( buf, XP_L("???"), VSIZE(buf)  );
    }

/*     gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rect ); */

    localR = *rect;
    gtkInsetRect( &localR, 1 );

    if ( !!cursor ) {
        gtkFillRect( dctx, &localR, cursor );
    } else {
        gtkEraseRect( dctx, &localR );
    }

	ht = localR.height >> 2;
    draw_string_at( dctx, NULL, XP_L("Pts:"), ht,
                    &localR, XP_GTK_JUST_TOPLEFT,
                    &dctx->black, cursor );
    draw_string_at( dctx, NULL, buf, ht,
                    &localR, XP_GTK_JUST_BOTTOMRIGHT,
                    &dctx->black, cursor );

} /* gtk_draw_score_pendingScore */

static void
gtkFormatTimerText( XP_UCHAR* buf, XP_S16 secondsLeft )
{
    XP_U16 minutes, seconds;

    if ( secondsLeft < 0 ) {
        *buf++ = '-';
        secondsLeft *= -1;
    }

    minutes = secondsLeft / 60;
    seconds = secondsLeft % 60;
    XP_SNPRINTF( buf, VSIZE(buf), XP_L("% 1d:%02d"), minutes, seconds );
} /* gtkFormatTimerText */

static void
gtk_draw_drawTimer( DrawCtx* p_dctx, const XP_Rect* rInner, 
                    const XP_Rect* XP_UNUSED(rOuter),
                    XP_U16 XP_UNUSED(player), XP_S16 secondsLeft )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_UCHAR buf[10];

    gtkFormatTimerText( buf, secondsLeft );

/*     gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rInner ); */
    gtkEraseRect( dctx, rInner );
    draw_string_at( dctx, NULL, buf, rInner->height-1,
                    rInner, XP_GTK_JUST_CENTER,
                    &dctx->black, NULL );
} /* gtk_draw_drawTimer */

#define MINI_LINE_HT 12
#define MINI_V_PADDING 6
#define MINI_H_PADDING 8

static const XP_UCHAR*
gtk_draw_getMiniWText( DrawCtx* XP_UNUSED(p_dctx), XWMiniTextType textHint )
{
/*     GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; */
    XP_UCHAR* str;

    switch( textHint ) {
    case BONUS_DOUBLE_LETTER:
        str = XP_L("Double letter"); break;
    case BONUS_DOUBLE_WORD:
        str = XP_L("Double word"); break;
    case BONUS_TRIPLE_LETTER:
        str = XP_L("Triple letter"); break;
    case BONUS_TRIPLE_WORD:
        str = XP_L("Triple word"); break;
    case INTRADE_MW_TEXT:	
        str = XP_L("Trading tiles;\nclick D when done"); break;
    default:
        XP_ASSERT( XP_FALSE );
    }
    return str;
} /* gtk_draw_getMiniWText */

static void
gtk_draw_measureMiniWText( DrawCtx* p_dctx, const XP_UCHAR* str, 
                           XP_U16* widthP, XP_U16* heightP )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    int height, width;

	PangoLayout* layout = layout_for_ht( dctx, GTKMIN_W_HT );
    pango_layout_set_text( layout, (char*)str, XP_STRLEN(str) );
    pango_layout_get_pixel_size( layout, &width, &height );
    *heightP = height;
    *widthP = width + 6;
} /* gtk_draw_measureMiniWText */

static void
gtk_draw_drawMiniWindow( DrawCtx* p_dctx, const XP_UCHAR* text, 
                         const XP_Rect* rect, void** XP_UNUSED(closureP) )
{
    GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
    XP_Rect localR = *rect;

    gdk_gc_set_foreground( dctx->drawGC, &dctx->black );
/*     gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)&localR ); */

    /* play some skanky games to get the shadow drawn under and to the
       right... */
    gtkEraseRect( dctx, &localR );

    gtkInsetRect( &localR, 1 );
    --localR.width;
    --localR.height;
    frameRect( dctx, &localR );

    --localR.top;
    --localR.left;
    gtkEraseRect( dctx, &localR );
    frameRect( dctx, &localR );

    draw_string_at( dctx, NULL, text, GTKMIN_W_HT,
                    &localR, XP_GTK_JUST_CENTER,
                    &dctx->black, NULL );
} /* gtk_draw_drawMiniWindow */

#define SET_GDK_COLOR( c, r, g, b ) { \
     c.red = (r); \
     c.green = (g); \
     c.blue = (b); \
}
static void
draw_doNothing( DrawCtx* XP_UNUSED(dctx), ... )
{
} /* draw_doNothing */

static void
allocAndSet( GdkColormap* map, GdkColor* color, unsigned short red,
             unsigned short green, unsigned short blue )

{
    gboolean success;

    color->red = red;
    color->green = green;
    color->blue = blue;

    success = gdk_colormap_alloc_color( map,
                                        color,
                                        TRUE, /* writeable */
                                        TRUE ); /* best-match */
    XP_ASSERT( success );
} /* allocAndSet */

DrawCtx* 
gtkDrawCtxtMake( GtkWidget* drawing_area, GtkAppGlobals* globals )
{
    GtkDrawCtx* dctx = g_malloc0( sizeof(GtkDrawCtx) );
    GdkColormap* map;

    short i;

    dctx->vtable = g_malloc( sizeof(*(((GtkDrawCtx*)dctx)->vtable)) );

    for ( i = 0; i < sizeof(*dctx->vtable)/4; ++i ) {
        ((void**)(dctx->vtable))[i] = draw_doNothing;
    }

    SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, gtk );

#ifdef DRAW_WITH_PRIMITIVES
    SET_VTABLE_ENTRY( dctx->vtable, draw_setClip, gtk_prim );
    SET_VTABLE_ENTRY( dctx->vtable, draw_frameRect, gtk_prim );
    SET_VTABLE_ENTRY( dctx->vtable, draw_invertRect, gtk_prim );
    SET_VTABLE_ENTRY( dctx->vtable, draw_drawString, gtk_prim );
    SET_VTABLE_ENTRY( dctx->vtable, draw_drawBitmap, gtk_prim );
    SET_VTABLE_ENTRY( dctx->vtable, draw_measureText, gtk_prim );

    InitDrawDefaults( dctx->vtable );
#else

    SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_invertCell, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_objFinished, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_vertScrollBoard, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_drawTile, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileBack, gtk );
#ifdef POINTER_SUPPORT
    SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileMidDrag, gtk );
#endif
    SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayDivider, gtk );

    SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, gtk );

    SET_VTABLE_ENTRY( dctx->vtable, draw_scoreBegin, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_measureRemText, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_measureScoreText, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, gtk );

    SET_VTABLE_ENTRY( dctx->vtable, draw_drawTimer, gtk );

    SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, gtk );

    SET_VTABLE_ENTRY( dctx->vtable, draw_destroyCtxt, gtk );
    SET_VTABLE_ENTRY( dctx->vtable, draw_dictChanged, gtk );
#endif

	dctx->pangoContext = gtk_widget_get_pango_context( drawing_area );
    dctx->drawing_area = drawing_area;
    dctx->globals = globals;

    map = gdk_colormap_get_system();

    allocAndSet( map, &dctx->black, 0x0000, 0x0000, 0x0000 );
    allocAndSet( map, &dctx->white, 0xFFFF, 0xFFFF, 0xFFFF );

    {
        GdkWindow *window = NULL;
        if ( GTK_WIDGET_FLAGS(GTK_WIDGET(drawing_area)) & GTK_NO_WINDOW ) {
            /* XXX I'm not sure about this function because I never used it.
             * (the name seems to indicate what you want though).
             */
            window = gtk_widget_get_parent_window( GTK_WIDGET(drawing_area) );
        } else {
            window = GTK_WIDGET(drawing_area)->window;
        }
        dctx->drawGC = gdk_gc_new( window );
    }

    map = gdk_colormap_get_system();

    allocAndSet( map, &dctx->black, 0x0000, 0x0000, 0x0000 );
    allocAndSet( map, &dctx->white, 0xFFFF, 0xFFFF, 0xFFFF );

    allocAndSet( map, &dctx->bonusColors[0], 0xFFFF, 0xAFFF, 0xAFFF );
    allocAndSet( map, &dctx->bonusColors[1], 0xAFFF, 0xFFFF, 0xAFFF );
    allocAndSet( map, &dctx->bonusColors[2], 0xAFFF, 0xAFFF, 0xFFFF );
    allocAndSet( map, &dctx->bonusColors[3], 0xFFFF, 0xAFFF, 0xFFFF );

    allocAndSet( map, &dctx->playerColors[0], 0x0000, 0x0000, 0xAFFF );
    allocAndSet( map, &dctx->playerColors[1], 0xAFFF, 0x0000, 0x0000 );
    allocAndSet( map, &dctx->playerColors[2], 0x0000, 0xAFFF, 0x0000 );
    allocAndSet( map, &dctx->playerColors[3], 0xAFFF, 0x0000, 0xAFFF );

    allocAndSet( map, &dctx->tileBack, 0xFFFF, 0xFFFF, 0x9999 );
    allocAndSet( map, &dctx->cursor, 253<<8, 12<<8, 222<<8 ); 
    allocAndSet( map, &dctx->red, 0xFFFF, 0x0000, 0x0000 );

    return (DrawCtx*)dctx;
} /* gtkDrawCtxtMake */

#endif /* PLATFORM_GTK */