mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2024-12-28 09:58:30 +01:00
1181e908dc
When rematching, some users have a convention that e.g. lowest scoring player in the "parent" game goes first. So allow that, providing the choice on each rematch until a default has been chosen. Support changing that default in a new prefs setting. The place I chose to enforce the order was on the host as invitees are registering and being assigned slots. But by then there's no longer any connection to the game that was rematched, e.g. to use its scores. So during the rematched game creation process I create and store with the new game the necessary ordering information. For the 3-and-4 device case, it was also necessary to tweak the information about other guests that the host sends guests (added during earlier work on rematching.)
1508 lines
46 KiB
C
1508 lines
46 KiB
C
/* -*- compile-command: "make MEMDEBUG=TRUE -j5"; -*- */
|
|
/*
|
|
* Copyright 1997 - 2017 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 <math.h> // for M_PI
|
|
|
|
#undef GDK_DISABLE_DEPRECATED
|
|
|
|
#include <gdk/gdk.h>
|
|
|
|
#include "gtkboard.h"
|
|
#include "draw.h"
|
|
#include "board.h"
|
|
#include "linuxmain.h"
|
|
#include "linuxutl.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 void gtk_draw_measureScoreText( DrawCtx* p_dctx, XWEnv xwe,
|
|
const XP_Rect* bounds,
|
|
const DrawScoreInfo* dsi,
|
|
XP_U16* widthP, XP_U16* heightP );
|
|
static gdouble figureColor( int in );
|
|
|
|
/* static GdkGC* newGCForColor( GdkWindow* window, XP_Color* newC ); */
|
|
static void
|
|
gtkInsetRect( XP_Rect* r, short i )
|
|
{
|
|
r->top += i;
|
|
r->left += i;
|
|
i *= 2;
|
|
|
|
XP_ASSERT( r->height >= i && r->width >= i );
|
|
r->width -= i;
|
|
r->height -= i;
|
|
} /* gtkInsetRect */
|
|
|
|
#define GTKMIN_W_HT 12
|
|
|
|
# define XP_UNUSED_CAIRO(var) UNUSED__ ## var __attribute__((unused))
|
|
# define GDKDRAWABLE void
|
|
# define GDKGC void
|
|
# define GDKCOLORMAP void
|
|
#define LOG_CAIRO_PENDING() XP_LOGF( "%s(): CAIRO work pending", __func__ )
|
|
|
|
static XP_Bool
|
|
initCairo( GtkDrawCtx* dctx )
|
|
{
|
|
/* XP_LOGF( "%s(dctx=%p)", __func__, dctx ); */
|
|
XP_ASSERT( !dctx->_cairo );
|
|
cairo_t* cairo = NULL;
|
|
|
|
if ( !!dctx->surface ) { /* the thumbnail case */
|
|
XP_LOGF( "%s(): have surface; doing nothing", __func__ );
|
|
cairo = cairo_create( dctx->surface );
|
|
cairo_surface_destroy( dctx->surface );
|
|
// XP_ASSERT( 0 );
|
|
} else if ( !!dctx->drawing_area ) {
|
|
GdkWindow* window = gtk_widget_get_window( dctx->drawing_area );
|
|
const cairo_region_t* region = gdk_window_get_visible_region( window );
|
|
dctx->dc = gdk_window_begin_draw_frame( window, region );
|
|
cairo = gdk_drawing_context_get_cairo_context( dctx->dc );
|
|
} else {
|
|
XP_ASSERT( 0 );
|
|
}
|
|
XP_Bool inited = !!cairo;
|
|
if ( inited ) {
|
|
dctx->_cairo = cairo;
|
|
if ( !!dctx->surface ) {
|
|
cairo_set_line_width( cairo, 0.1 );
|
|
} else {
|
|
cairo_set_line_width( cairo, 1.0 );
|
|
}
|
|
cairo_set_line_cap( cairo, CAIRO_LINE_CAP_SQUARE );
|
|
}
|
|
return inited;
|
|
}
|
|
|
|
static void
|
|
destroyCairo( GtkDrawCtx* dctx )
|
|
{
|
|
/* XP_LOGF( "%s(dctx=%p)", __func__, dctx ); */
|
|
XP_ASSERT( !!dctx->_cairo );
|
|
if ( !!dctx->surface ) { /* the thumbnail case */
|
|
XP_LOGF( "%s(): have surface; doing nothing", __func__ );
|
|
} else {
|
|
GdkWindow* window = gtk_widget_get_window( dctx->drawing_area );
|
|
gdk_window_end_draw_frame( window, dctx->dc );
|
|
}
|
|
dctx->_cairo = NULL;
|
|
}
|
|
|
|
static XP_Bool
|
|
haveCairo( const GtkDrawCtx* dctx )
|
|
{
|
|
return !!dctx->_cairo;
|
|
}
|
|
|
|
static cairo_t*
|
|
getCairo( const GtkDrawCtx* dctx )
|
|
{
|
|
XP_ASSERT( !!dctx->_cairo );
|
|
return dctx->_cairo;
|
|
}
|
|
|
|
static void
|
|
draw_rectangle( const GtkDrawCtx* dctx,
|
|
gboolean fill, gint left, gint top, gint width,
|
|
gint height )
|
|
{
|
|
cairo_t *cr = getCairo( dctx );
|
|
|
|
cairo_rectangle( cr, left, top, width, height );
|
|
cairo_stroke_preserve( cr );
|
|
if ( fill ) {
|
|
cairo_fill( cr );
|
|
} else {
|
|
cairo_stroke( cr );
|
|
}
|
|
/* } else { */
|
|
/* cairo_stroke( dctx->cairo ); */
|
|
/* } */
|
|
}
|
|
|
|
/* Not perfect, but good enough for now. Ideally we'd scale to accomodate
|
|
width != height. */
|
|
static void
|
|
draw_circle( const GtkDrawCtx* dctx, const XP_Rect* rect )
|
|
{
|
|
cairo_t* cr = getCairo( dctx );
|
|
cairo_translate( cr, rect->left + (rect->width/2), rect->top + (rect->height/2));
|
|
int diameter = XP_MAX(rect->width, rect->height);
|
|
cairo_arc( cr, 0, 0, diameter/2, 0, 2 * M_PI);
|
|
cairo_stroke( cr );
|
|
}
|
|
|
|
static void
|
|
gtkSetForeground( const GtkDrawCtx* dctx, const GdkRGBA* color )
|
|
{
|
|
gdk_cairo_set_source_rgba( getCairo(dctx), color );
|
|
}
|
|
|
|
static void
|
|
gtkFillRect( GtkDrawCtx* dctx, const XP_Rect* rect, const GdkRGBA* color )
|
|
{
|
|
gtkSetForeground( dctx, color );
|
|
draw_rectangle( dctx, TRUE, rect->left, rect->top,
|
|
rect->width, rect->height );
|
|
}
|
|
|
|
static void
|
|
set_color_cairo( const GtkDrawCtx* dctx, unsigned short red,
|
|
unsigned short green, unsigned short blue )
|
|
{
|
|
GdkRGBA color = { figureColor(red),
|
|
figureColor(green),
|
|
figureColor(blue),
|
|
1.0 };
|
|
gdk_cairo_set_source_rgba( getCairo(dctx), &color );
|
|
}
|
|
|
|
static void
|
|
gtkEraseRect( const GtkDrawCtx* dctx, const XP_Rect* rect )
|
|
{
|
|
set_color_cairo( dctx, 0xFFFF, 0xFFFF, 0xFFFF );
|
|
// const GtkStyle* style = gtk_widget_get_style( dctx->drawing_area );
|
|
draw_rectangle( dctx, TRUE, rect->left, rect->top,
|
|
rect->width, rect->height );
|
|
} /* gtkEraseRect */
|
|
|
|
#ifdef DRAW_WITH_PRIMITIVES
|
|
|
|
static void
|
|
gtk_prim_draw_setClip( DrawCtx* p_dctx, XWEnv xwe, XP_Rect* newClip, XP_Rect* oldClip)
|
|
{
|
|
} /* gtk_prim_draw_setClip */
|
|
|
|
static void
|
|
gtk_prim_draw_frameRect( DrawCtx* p_dctx, XWEnv xwe, XP_Rect* rect )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
|
|
frameRect( dctx, rect );
|
|
} /* gtk_prim_draw_frameRect */
|
|
|
|
static void
|
|
gtk_prim_draw_invertRect( DrawCtx* p_dctx, XWEnv xwe, XP_Rect* rect )
|
|
{
|
|
/* not sure you can do this on GTK!! */
|
|
} /* gtk_prim_draw_invertRect */
|
|
|
|
static void
|
|
gtk_prim_draw_clearRect( DrawCtx* p_dctx, XWEnv xwe, XP_Rect* rect )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
|
|
gtkEraseRect( dctx, rect );
|
|
} /* gtk_prim_draw_clearRect */
|
|
|
|
static void
|
|
gtk_prim_draw_drawString( DrawCtx* p_dctx, XWEnv xwe, 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, XWEnv xwe, XP_Bitmap bm,
|
|
XP_U16 x, XP_U16 y )
|
|
{
|
|
} /* gtk_prim_draw_drawBitmap */
|
|
|
|
static void
|
|
gtk_prim_draw_measureText( DrawCtx* p_dctx, XWEnv xwe, 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 = g_object_ref(((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 );
|
|
|
|
PangoContext* pc = pango_cairo_create_context( getCairo(dctx) );
|
|
layout = pango_layout_new( pc );
|
|
g_object_unref( pc );
|
|
fps->fontdesc = pango_font_description_from_string( font );
|
|
pango_layout_set_font_description( layout, fps->fontdesc );
|
|
fps->layout = g_object_ref( 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 GdkRGBA* frground,
|
|
const GdkRGBA* bkgrnd )
|
|
{
|
|
// XP_LOGFF( "(%s, %d, %d)", str, where->left, where->top );
|
|
gint xx = where->left;
|
|
gint yy = where->top;
|
|
cairo_t* cr = getCairo( dctx );
|
|
|
|
gdk_cairo_set_source_rgba( cr, frground );
|
|
if ( !!layout ) {
|
|
g_object_ref( layout );
|
|
} else {
|
|
layout = layout_for_ht( dctx, fontHt );
|
|
}
|
|
|
|
pango_layout_set_text( layout, (char*)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;
|
|
}
|
|
}
|
|
|
|
XP_USE(bkgrnd);
|
|
cairo_save( cr );
|
|
cairo_translate( cr, xx, yy );
|
|
pango_cairo_show_layout( cr, layout );
|
|
cairo_restore( cr );
|
|
g_object_unref(layout);
|
|
} /* draw_string_at */
|
|
|
|
static void
|
|
drawBitmapFromLBS( GtkDrawCtx* dctx, const XP_Bitmap bm, const XP_Rect* rect )
|
|
{
|
|
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;
|
|
|
|
draw_rectangle( dctx, 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 ) {
|
|
LOG_CAIRO_PENDING();
|
|
}
|
|
byte <<= 1;
|
|
if ( ++x == nCols ) {
|
|
x = 0;
|
|
if ( ++y == nRows ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
XP_ASSERT( nBytes == -1 ); /* else we're out of sync */
|
|
|
|
rect = rect;
|
|
LOG_CAIRO_PENDING();
|
|
} /* 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, XWEnv XP_UNUSED(xwe) )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
GtkAllocation alloc;
|
|
gtk_widget_get_allocation( dctx->drawing_area, &alloc );
|
|
|
|
draw_rectangle( dctx, TRUE, 0, 0, alloc.width, alloc.height );
|
|
|
|
g_list_foreach( dctx->fontsPerSize, freer, NULL );
|
|
g_list_free( dctx->fontsPerSize );
|
|
|
|
} /* gtk_draw_destroyCtxt */
|
|
|
|
|
|
static void
|
|
gtk_draw_dictChanged( DrawCtx* XP_UNUSED(p_dctx), XWEnv XP_UNUSED(xwe),
|
|
XP_S16 XP_UNUSED(playerNum),
|
|
const DictionaryCtxt* XP_UNUSED(dict) )
|
|
{
|
|
}
|
|
|
|
static XP_Bool
|
|
gtk_draw_beginDraw( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe) )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
return initCairo( dctx );
|
|
}
|
|
|
|
static void
|
|
gtk_draw_endDraw( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe) )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
destroyCairo( dctx );
|
|
|
|
/* This fixes the window content not changing until I resize the window,
|
|
but it also results in board_draw() getting called constantly. This app
|
|
is for development only, so I don't have to care, but I need to
|
|
understand gtk/cairo better at some point. */
|
|
gtk_widget_queue_draw( dctx->drawing_area );
|
|
}
|
|
|
|
static XP_Bool
|
|
gtk_draw_boardBegin( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe),
|
|
const XP_Rect* XP_UNUSED(rect),
|
|
XP_U16 XP_UNUSED(width), XP_U16 height,
|
|
DrawFocusState XP_UNUSED(dfs), TileValueType tvType )
|
|
{
|
|
/* XP_LOGFF( "rect: %d,%d,%d,%d; width: %d; height: %d", */
|
|
/* rect->left, rect->top, rect->width, rect->height, */
|
|
/* width, height ); */
|
|
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
dctx->cellHeight = height;
|
|
dctx->tvType = tvType;
|
|
|
|
gtkSetForeground( dctx, &dctx->black );
|
|
|
|
/* WTF is this??? */
|
|
/* GdkRectangle gdkrect = *(GdkRectangle*)(void*)rect; */
|
|
/* ++gdkrect.width; */
|
|
/* ++gdkrect.height; */
|
|
|
|
return XP_TRUE;
|
|
} /* gtk_draw_boardBegin */
|
|
|
|
static void
|
|
gtk_draw_objFinished( DrawCtx* XP_UNUSED(p_dctx), XWEnv XP_UNUSED(xwe),
|
|
BoardObjectType XP_UNUSED(typ),
|
|
const XP_Rect* XP_UNUSED(rect),
|
|
DrawFocusState XP_UNUSED(dfs) )
|
|
{
|
|
} /* gtk_draw_objFinished */
|
|
|
|
static XP_Bool
|
|
gtk_draw_vertScrollBoard( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), XP_Rect* rect,
|
|
XP_S16 dist, DrawFocusState XP_UNUSED(dfs) )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)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;
|
|
}
|
|
|
|
dctx = dctx;
|
|
if ( !down ) {
|
|
rect->top += rect->height - dist;
|
|
}
|
|
rect->height = dist;
|
|
/* XP_LOGF( "%s=>(%d,%d,%d,%d)", __func__, rect->left, rect->top, */
|
|
/* rect->width, rect->height ); */
|
|
|
|
return XP_TRUE;
|
|
} /* gtk_draw_vertScrollBoard */
|
|
|
|
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 );
|
|
|
|
gtkSetForeground( dctx, &dctx->black );
|
|
|
|
if ( (hintAtts & HINT_BORDER_LEFT) != 0 ) {
|
|
draw_rectangle( dctx, FALSE, lrect.left, lrect.top,
|
|
0, lrect.height);
|
|
}
|
|
if ( (hintAtts & HINT_BORDER_TOP) != 0 ) {
|
|
draw_rectangle( dctx, FALSE, lrect.left, lrect.top,
|
|
lrect.width, 0/*rectInset.height*/);
|
|
}
|
|
if ( (hintAtts & HINT_BORDER_RIGHT) != 0 ) {
|
|
draw_rectangle( dctx, FALSE, lrect.left+lrect.width,
|
|
lrect.top,
|
|
0, lrect.height);
|
|
}
|
|
if ( (hintAtts & HINT_BORDER_BOTTOM) != 0 ) {
|
|
draw_rectangle( dctx, FALSE, lrect.left,
|
|
lrect.top+lrect.height,
|
|
lrect.width, 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef XWFEATURE_CROSSHAIRS
|
|
static void
|
|
drawCrosshairs( GtkDrawCtx* dctx, const XP_Rect* rect, CellFlags flags )
|
|
{
|
|
XP_Rect hairRect;
|
|
if ( 0 != (flags & CELL_CROSSHOR) ) {
|
|
hairRect = *rect;
|
|
hairRect.height /= 3;
|
|
hairRect.top += hairRect.height;
|
|
gtkFillRect( dctx, &hairRect, &dctx->cursor );
|
|
}
|
|
if ( 0 != (flags & CELL_CROSSVERT) ) {
|
|
hairRect = *rect;
|
|
hairRect.width /= 3;
|
|
hairRect.left += hairRect.width;
|
|
gtkFillRect( dctx, &hairRect, &dctx->cursor );
|
|
}
|
|
}
|
|
#else
|
|
# define drawCrosshairs( a, b, c )
|
|
#endif
|
|
|
|
static XP_Bool
|
|
gtk_draw_drawCell( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rect,
|
|
const XP_UCHAR* letter,
|
|
const XP_Bitmaps* bitmaps, Tile XP_UNUSED(tile),
|
|
const XP_U16 tileValue, XP_S16 owner, XWBonusType bonus,
|
|
HintAtts hintAtts, CellFlags flags )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_Rect rectInset = *rect;
|
|
GtkGameGlobals* globals = dctx->globals;
|
|
XP_Bool showGrid = globals->gridOn;
|
|
XP_Bool recent = (flags & CELL_RECENT) != 0;
|
|
XP_Bool pending = (flags & CELL_PENDING) != 0;
|
|
GdkRGBA* cursor =
|
|
((flags & CELL_ISCURSOR) != 0) ? &dctx->cursor : NULL;
|
|
GdkRGBA* foreground = &dctx->white;
|
|
|
|
XP_UCHAR valBuf[8];
|
|
XP_SNPRINTF( valBuf, sizeof(valBuf), "%d", tileValue );
|
|
const XP_UCHAR* value = valBuf;
|
|
|
|
gtkEraseRect( dctx, rect );
|
|
|
|
gtkInsetRect( &rectInset, 1 );
|
|
|
|
cairo_t* cr = getCairo( dctx );
|
|
|
|
if ( showGrid ) {
|
|
cairo_set_source_rgb( cr, 0, 0, 0 );
|
|
draw_rectangle( dctx, 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 ) {
|
|
if ( !!cursor ) {
|
|
foreground = cursor;
|
|
} else if ( bonus != BONUS_NONE ) {
|
|
foreground = &dctx->bonusColors[bonus-1];
|
|
/* } else { */
|
|
/* foreground = &dctx->white; */
|
|
}
|
|
if ( !!foreground ) {
|
|
gtkFillRect( dctx, &rectInset, foreground );
|
|
// gdk_cairo_set_source_rgba( cr, foreground );
|
|
}
|
|
}
|
|
if ( (flags & CELL_ISSTAR) != 0 ) {
|
|
draw_string_at( dctx, NULL, "*", dctx->cellHeight, rect,
|
|
XP_GTK_JUST_CENTER, &dctx->black, NULL );
|
|
}
|
|
} else if ( !!bitmaps && !!bitmaps->bmps[0] ) {
|
|
XP_ASSERT(0); /* we don't handle this now */
|
|
XP_Rect tmpRect = *rect;
|
|
if ( !!value ) {
|
|
tmpRect.width = tmpRect.width * 3 / 4;
|
|
tmpRect.height = tmpRect.height * 3 / 4;
|
|
}
|
|
drawBitmapFromLBS( dctx, bitmaps->bmps[0], &tmpRect );
|
|
} else if ( !!letter ) {
|
|
TileValueType useTyp = dctx->tvType;
|
|
if ( TVT_VALUES == useTyp ) {
|
|
letter = value;
|
|
useTyp = TVT_FACES;
|
|
}
|
|
if ( TVT_FACES == useTyp ) {
|
|
value = NULL;
|
|
}
|
|
XP_Bool isBlank = (flags & CELL_ISBLANK) != 0;
|
|
if ( cursor ) {
|
|
gtkSetForeground( dctx, cursor );
|
|
} else if ( !recent && !pending ) {
|
|
gtkSetForeground( dctx, &dctx->tileBack );
|
|
}
|
|
draw_rectangle( dctx, TRUE, rectInset.left, rectInset.top,
|
|
rectInset.width+1, rectInset.height+1 );
|
|
|
|
if ( isBlank && 0 == strcmp("_",letter ) ) {
|
|
letter = "?";
|
|
isBlank = XP_FALSE;
|
|
}
|
|
|
|
if ( pending ) {
|
|
foreground = &dctx->white;
|
|
} else if ( recent ) {
|
|
foreground = &dctx->grey;
|
|
} else {
|
|
foreground = &dctx->playerColors[owner];
|
|
}
|
|
XP_Rect tmpRect = rectInset;
|
|
XP_U16 fontHt = dctx->cellHeight;
|
|
if ( !!value ) {
|
|
tmpRect.width = tmpRect.width * 3 / 4;
|
|
tmpRect.height = tmpRect.height * 3 / 4;
|
|
fontHt = fontHt * 3 / 4;
|
|
}
|
|
|
|
draw_string_at( dctx, NULL, letter, fontHt, &tmpRect,
|
|
XP_GTK_JUST_CENTER, foreground, cursor );
|
|
if ( isBlank ) {
|
|
draw_circle( dctx, &tmpRect );
|
|
}
|
|
}
|
|
|
|
if ( !!value ) {
|
|
const int fraction = 3;
|
|
XP_Rect tmpRect = *rect;
|
|
tmpRect.left += tmpRect.width * (fraction-1) / fraction;
|
|
tmpRect.width /= fraction;
|
|
tmpRect.top += tmpRect.height * (fraction-1) / fraction;
|
|
tmpRect.height /= fraction;
|
|
draw_string_at( dctx, NULL, value, dctx->cellHeight/fraction, &tmpRect,
|
|
XP_GTK_JUST_CENTER, foreground, cursor );
|
|
}
|
|
|
|
drawHintBorders( dctx, rect, hintAtts );
|
|
drawCrosshairs( dctx, rect, flags );
|
|
|
|
return XP_TRUE;
|
|
} /* gtk_draw_drawCell */
|
|
|
|
static void
|
|
gtk_draw_invertCell( DrawCtx* XP_UNUSED(p_dctx), XWEnv XP_UNUSED(xwe),
|
|
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 ); */
|
|
/* 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, XWEnv XP_UNUSED(xwe), const XP_Rect* XP_UNUSED(rect),
|
|
XP_U16 owner, XP_S16 XP_UNUSED(owner),
|
|
DrawFocusState XP_UNUSED(dfs) )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_Bool doDraw = !dctx->surface;
|
|
if ( doDraw ) {
|
|
dctx->trayOwner = owner;
|
|
}
|
|
return doDraw;
|
|
} /* gtk_draw_trayBegin */
|
|
|
|
static XP_Bool
|
|
gtkDrawTileImpl( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rect, const XP_UCHAR* textP,
|
|
const XP_Bitmaps* bitmaps, XP_S16 val, CellFlags flags,
|
|
XP_Bool clearBack )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_UCHAR numbuf[8];
|
|
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 ) {
|
|
GdkRGBA* 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 && !!bitmaps->bmps[1] ) {
|
|
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), "%d", val );
|
|
|
|
draw_string_at( dctx, NULL, numbuf, formatRect.height>>2,
|
|
&formatRect, XP_GTK_JUST_BOTTOMRIGHT,
|
|
foreground, NULL );
|
|
}
|
|
}
|
|
|
|
/* frame the tile */
|
|
gtkSetForeground( dctx, &dctx->black );
|
|
draw_rectangle( dctx, FALSE,
|
|
insetR.left, insetR.top, insetR.width,
|
|
insetR.height );
|
|
|
|
if ( (flags & (CELL_PENDING|CELL_RECENT)) != 0 ) {
|
|
gtkInsetRect( &insetR, 1 );
|
|
draw_rectangle( dctx, FALSE, insetR.left, insetR.top,
|
|
insetR.width, insetR.height);
|
|
}
|
|
}
|
|
return XP_TRUE;
|
|
} /* gtkDrawTileImpl */
|
|
|
|
static XP_Bool
|
|
gtk_draw_drawTile( DrawCtx* p_dctx, XWEnv xwe, const XP_Rect* rect, const XP_UCHAR* textP,
|
|
const XP_Bitmaps* bitmaps, XP_S16 val, CellFlags flags )
|
|
{
|
|
return gtkDrawTileImpl( p_dctx, xwe, rect, textP, bitmaps, val, flags, XP_TRUE );
|
|
}
|
|
|
|
#ifdef POINTER_SUPPORT
|
|
static XP_Bool
|
|
gtk_draw_drawTileMidDrag( DrawCtx* p_dctx, XWEnv xwe, 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, xwe, rect, owner, 0, DFS_NONE );
|
|
return gtkDrawTileImpl( p_dctx, xwe, rect, textP, bitmaps, val,
|
|
flags | (CELL_PENDING|CELL_RECENT), XP_FALSE );
|
|
}
|
|
#endif
|
|
|
|
static XP_Bool
|
|
gtk_draw_drawTileBack( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rect,
|
|
CellFlags flags )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)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, "?", r.height,
|
|
&r, XP_GTK_JUST_CENTER,
|
|
&dctx->playerColors[dctx->trayOwner], NULL );
|
|
return XP_TRUE;
|
|
} /* gtk_draw_drawTileBack */
|
|
|
|
static void
|
|
gtk_draw_drawTrayDivider( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe),
|
|
const XP_Rect* rect, CellFlags flags )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_Rect r = *rect;
|
|
XP_Bool selected = 0 != (flags & (CELL_RECENT|CELL_PENDING));
|
|
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;
|
|
}
|
|
|
|
gtkSetForeground( dctx, &dctx->black );
|
|
draw_rectangle( dctx, !selected,
|
|
r.left, r.top, r.width, r.height);
|
|
} /* gtk_draw_drawTrayDivider */
|
|
|
|
static void
|
|
gtk_draw_clearRect( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rectP )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_Rect rect = *rectP;
|
|
|
|
++rect.width;
|
|
++rect.top;
|
|
|
|
gtkEraseRect( dctx, &rect );
|
|
|
|
} /* gtk_draw_clearRect */
|
|
|
|
static void
|
|
gtk_draw_drawBoardArrow( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rectP,
|
|
XWBonusType XP_UNUSED(cursorBonus), XP_Bool vertical,
|
|
HintAtts hintAtts, CellFlags XP_UNUSED(flags) )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
const XP_UCHAR* curs = vertical? "|":"-";
|
|
|
|
/* 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 XP_Bool
|
|
gtk_draw_scoreBegin( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rect,
|
|
XP_U16 XP_UNUSED(numPlayers),
|
|
const XP_S16* const XP_UNUSED(scores),
|
|
XP_S16 XP_UNUSED(remCount),
|
|
DrawFocusState XP_UNUSED(dfs) )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_Bool doDraw = !dctx->surface;
|
|
if ( doDraw ) {
|
|
gtkEraseRect( dctx, rect );
|
|
dctx->scoreIsVertical = rect->height > rect->width;
|
|
}
|
|
return doDraw;
|
|
} /* gtk_draw_scoreBegin */
|
|
|
|
static PangoLayout*
|
|
getLayoutToFitRect( GtkDrawCtx* dctx, const XP_UCHAR* str, const XP_Rect* rect,
|
|
int* heightP )
|
|
{
|
|
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 );
|
|
g_object_unref( layout );
|
|
|
|
/* 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;
|
|
if ( !!heightP && *heightP < height ) {
|
|
height = *heightP;
|
|
}
|
|
|
|
layout = layout_for_ht( dctx, height );
|
|
pango_layout_set_text( layout, (char*)str, len );
|
|
if ( !!heightP ) {
|
|
*heightP = height;
|
|
}
|
|
return layout;
|
|
} /* getLayoutToFitRect */
|
|
|
|
static void
|
|
gtkDrawDrawRemText( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rect,
|
|
XP_S16 nTilesLeft, XP_U16* widthP, XP_U16* heightP, XP_Bool focussed )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_UCHAR buf[16];
|
|
XP_SNPRINTF( buf, sizeof(buf), "rem:%d", nTilesLeft );
|
|
PangoLayout* layout = getLayoutToFitRect( dctx, buf, rect, NULL );
|
|
|
|
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 GdkRGBA* 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, NULL );
|
|
}
|
|
g_object_unref( layout );
|
|
} /* gtkDrawDrawRemText */
|
|
|
|
static void
|
|
gtk_draw_score_drawPlayer( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rInner,
|
|
const XP_Rect* rOuter,
|
|
XP_U16 XP_UNUSED(gotPct), const DrawScoreInfo* dsi )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_Bool hasCursor = (dsi->flags & CELL_ISCURSOR) != 0;
|
|
GdkRGBA* cursor = NULL;
|
|
XP_U16 playerNum = dsi->playerNum;
|
|
const XP_UCHAR* scoreBuf = dctx->scoreCache[playerNum].str;
|
|
XP_U16 fontHt = dctx->scoreCache[playerNum].fontHt;
|
|
|
|
if ( hasCursor ) {
|
|
cursor = &dctx->cursor;
|
|
gtkFillRect( dctx, rOuter, cursor );
|
|
}
|
|
|
|
gtkSetForeground( dctx, &dctx->playerColors[playerNum] );
|
|
|
|
if ( dsi->selected ) {
|
|
XP_Rect selRect = *rOuter;
|
|
XP_S16 diff;
|
|
if ( dctx->scoreIsVertical ) {
|
|
diff = selRect.height - rInner->height;
|
|
} else {
|
|
diff = selRect.width - rInner->width;
|
|
}
|
|
if ( diff > 0 ) {
|
|
if ( dctx->scoreIsVertical ) {
|
|
selRect.height -= diff>>1;
|
|
selRect.top += diff>>2;
|
|
} else {
|
|
selRect.width -= diff>>1;
|
|
selRect.left += diff>>2;
|
|
}
|
|
}
|
|
|
|
draw_rectangle( dctx, TRUE, selRect.left, selRect.top,
|
|
selRect.width, selRect.height );
|
|
if ( hasCursor ) {
|
|
gtkFillRect( dctx, rInner, cursor );
|
|
} else {
|
|
gtkEraseRect( dctx, rInner );
|
|
}
|
|
}
|
|
|
|
/* XP_U16 fontHt = rInner->height; */
|
|
/* if ( strstr( scoreBuf, "\n" ) ) { */
|
|
/* fontHt >>= 1; */
|
|
/* } */
|
|
|
|
draw_string_at( dctx, NULL, scoreBuf, fontHt/*-1*/,
|
|
rInner, XP_GTK_JUST_CENTER,
|
|
&dctx->playerColors[playerNum], cursor );
|
|
|
|
} /* gtk_draw_score_drawPlayer */
|
|
|
|
#ifdef XWFEATURE_SCOREONEPASS
|
|
static XP_Bool
|
|
gtk_draw_drawRemText( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), XP_S16 nTilesLeft,
|
|
XP_Bool focussed, XP_Rect* rect )
|
|
{
|
|
XP_Bool drawIt = 0 <= nTilesLeft;
|
|
if ( drawIt ) {
|
|
XP_U16 width, height;
|
|
gtkDrawDrawRemText( p_dctx, rect, nTilesLeft, &width, &height, focussed );
|
|
rect->width = width;
|
|
rect->height = height;
|
|
gtkDrawDrawRemText( p_dctx, rect, nTilesLeft, NULL, NULL, focussed );
|
|
}
|
|
return drawIt;
|
|
}
|
|
|
|
static void
|
|
gtk_draw_score_drawPlayers( DrawCtx* p_dctx, XWEnv xwe, const XP_Rect* scoreRect,
|
|
XP_U16 nPlayers,
|
|
DrawScoreInfo playerData[],
|
|
XP_Rect playerRects[] )
|
|
{
|
|
XP_USE( playerData );
|
|
XP_USE( p_dctx );
|
|
|
|
XP_U16 ii;
|
|
XP_Rect rect = *scoreRect;
|
|
rect.width /= nPlayers;
|
|
for ( ii = 0; ii < nPlayers; ++ii ) {
|
|
XP_U16 ignoreW, ignoreH;
|
|
XP_Rect innerR;
|
|
gtk_draw_measureScoreText( p_dctx, xwe, &rect, &playerData[ii], &ignoreW,
|
|
&ignoreH );
|
|
|
|
innerR = rect;
|
|
innerR.left += 4;
|
|
innerR.width -= 8;
|
|
gtk_draw_score_drawPlayer( p_dctx, xwe, &innerR, &rect, 0, &playerData[ii] );
|
|
|
|
playerRects[ii] = rect;
|
|
rect.left += rect.width;
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
static XP_Bool
|
|
gtk_draw_measureRemText( DrawCtx* p_dctx, XWEnv xwe, const XP_Rect* rect, XP_S16 nTilesLeft,
|
|
XP_U16* width, XP_U16* height )
|
|
{
|
|
XP_Bool drawIt = 0 <= nTilesLeft;
|
|
if ( drawIt ) {
|
|
gtkDrawDrawRemText( p_dctx, xwe, rect, nTilesLeft, width, height, XP_FALSE );
|
|
}
|
|
return drawIt;
|
|
} /* gtk_draw_measureRemText */
|
|
|
|
static void
|
|
gtk_draw_drawRemText( DrawCtx* p_dctx, XWEnv xwe, const XP_Rect* rInner,
|
|
const XP_Rect* XP_UNUSED(rOuter), XP_S16 nTilesLeft,
|
|
XP_Bool focussed )
|
|
{
|
|
gtkDrawDrawRemText( p_dctx, xwe, rInner, nTilesLeft, NULL, NULL, focussed );
|
|
} /* gtk_draw_drawRemText */
|
|
|
|
#endif
|
|
static void
|
|
formatScoreText( PangoLayout* layout, XP_UCHAR* buf, XP_U16 bufLen,
|
|
const DrawScoreInfo* dsi, const XP_Rect* bounds,
|
|
XP_Bool scoreIsVertical, XP_U16* widthP, int* nLines )
|
|
{
|
|
XP_S16 score = dsi->totalScore;
|
|
XP_U16 nTilesLeft = dsi->nTilesLeft;
|
|
XP_Bool isTurn = XP_TRUE; // dsi->isTurn;
|
|
XP_S16 maxWidth = bounds->width;
|
|
XP_UCHAR numBuf[16];
|
|
int width, height;
|
|
*nLines = 1;
|
|
|
|
XP_SNPRINTF( numBuf, VSIZE(numBuf), "%d", score );
|
|
if ( (nTilesLeft < MAX_TRAY_TILES) && (nTilesLeft > 0) ) {
|
|
XP_UCHAR tmp[10];
|
|
XP_SNPRINTF( tmp, VSIZE(tmp), ":%d", nTilesLeft );
|
|
(void)XP_STRCAT( numBuf, tmp );
|
|
}
|
|
|
|
if ( !!layout ) {
|
|
pango_layout_set_text( layout, (char*)numBuf, XP_STRLEN(numBuf) );
|
|
pango_layout_get_pixel_size( layout, &width, &height );
|
|
if ( !scoreIsVertical ) {
|
|
maxWidth -= width;
|
|
*widthP = width;
|
|
}
|
|
}
|
|
|
|
/* Reformat name + ':' until it fits */
|
|
XP_UCHAR name[MAX_SCORE_LEN] = { 0 };
|
|
if ( isTurn && maxWidth > 0 ) {
|
|
XP_U16 len = 1 + XP_STRLEN( dsi->name ); /* +1 for "\0" */
|
|
if ( scoreIsVertical ) {
|
|
++*nLines;
|
|
} else {
|
|
++len; /* for ':' */
|
|
}
|
|
if ( len >= VSIZE(name) ) {
|
|
len = VSIZE(name) - 1;
|
|
}
|
|
for ( ; ; ) {
|
|
XP_SNPRINTF( name, len-1, "%s", dsi->name );
|
|
if ( !scoreIsVertical ) {
|
|
name[len-2] = ':';
|
|
name[len-1] = '\0';
|
|
}
|
|
|
|
pango_layout_set_text( layout, (char*)name, len );
|
|
pango_layout_get_pixel_size( layout, &width, &height );
|
|
if ( width <= maxWidth ) {
|
|
if ( !scoreIsVertical ) {
|
|
*widthP += width;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ( --len < 2 ) {
|
|
name[0] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( scoreIsVertical ) {
|
|
*widthP = bounds->width;
|
|
}
|
|
|
|
XP_SNPRINTF( buf, bufLen, "%s%s%s", name, (*nLines>1? XP_CR:""), numBuf );
|
|
} /* formatScoreText */
|
|
|
|
static void
|
|
gtk_draw_measureScoreText( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* bounds,
|
|
const DrawScoreInfo* dsi,
|
|
XP_U16* widthP, XP_U16* heightP )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_UCHAR buf[VSIZE(dctx->scoreCache[0].str)];
|
|
PangoLayout* layout;
|
|
int lineHeight = GTK_HOR_SCORE_HEIGHT, nLines;
|
|
|
|
layout = getLayoutToFitRect( dctx, "M", bounds, &lineHeight );
|
|
formatScoreText( layout, buf, VSIZE(buf), dsi, bounds,
|
|
dctx->scoreIsVertical, widthP, &nLines );
|
|
*heightP = nLines * lineHeight;
|
|
|
|
XP_U16 playerNum = dsi->playerNum;
|
|
XP_ASSERT( playerNum < VSIZE(dctx->scoreCache) );
|
|
XP_SNPRINTF( dctx->scoreCache[playerNum].str,
|
|
VSIZE(dctx->scoreCache[playerNum].str), "%s", buf );
|
|
dctx->scoreCache[playerNum].fontHt = lineHeight;
|
|
} /* gtk_draw_measureScoreText */
|
|
|
|
static void
|
|
gtk_draw_score_pendingScore( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rect,
|
|
XP_S16 score, XP_U16 XP_UNUSED(playerNum),
|
|
XP_Bool curTurn, CellFlags flags )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_UCHAR buf[8];
|
|
XP_U16 ht;
|
|
XP_Rect localR;
|
|
GdkRGBA* cursor = ((flags & CELL_ISCURSOR) != 0)
|
|
? &dctx->cursor : NULL;
|
|
GdkRGBA* txtColor;
|
|
|
|
if ( score >= 0 ) {
|
|
XP_SNPRINTF( buf, VSIZE(buf), "%.3d", score );
|
|
} else {
|
|
XP_STRNCPY( buf, "???", 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;
|
|
txtColor = curTurn ? &dctx->black : &dctx->grey;
|
|
draw_string_at( dctx, NULL, "Pts:", ht,
|
|
&localR, XP_GTK_JUST_TOPLEFT, txtColor, cursor );
|
|
draw_string_at( dctx, NULL, buf, ht,
|
|
&localR, XP_GTK_JUST_BOTTOMRIGHT, txtColor, cursor );
|
|
|
|
} /* gtk_draw_score_pendingScore */
|
|
|
|
static void
|
|
gtk_draw_drawTimer( DrawCtx* p_dctx, XWEnv XP_UNUSED(xwe), const XP_Rect* rInner,
|
|
XP_U16 playerNum, XP_S16 secondsLeft,
|
|
XP_Bool localTurnDone )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)(void*)p_dctx;
|
|
XP_Bool hadCairo = haveCairo( dctx );
|
|
if ( hadCairo || initCairo( dctx ) ) {
|
|
gtkEraseRect( dctx, rInner );
|
|
|
|
GdkRGBA* color = localTurnDone ? &dctx->grey
|
|
: &dctx->playerColors[playerNum];
|
|
|
|
gchar buf[16];
|
|
formatTimerText( buf, VSIZE(buf), secondsLeft );
|
|
|
|
draw_string_at( dctx, NULL, buf, rInner->height-1,
|
|
rInner, XP_GTK_JUST_CENTER, color, NULL );
|
|
if ( !hadCairo ) {
|
|
destroyCairo( dctx );
|
|
}
|
|
}
|
|
} /* gtk_draw_drawTimer */
|
|
|
|
#ifdef XWFEATURE_MINIWIN
|
|
# define MINI_LINE_HT 12
|
|
# define MINI_V_PADDING 6
|
|
# define MINI_H_PADDING 8
|
|
|
|
static void
|
|
frameRect( GtkDrawCtx* dctx, const XP_Rect* rect )
|
|
{
|
|
draw_rectangle( dctx, DRAW_WHAT(dctx), dctx->drawGC,
|
|
FALSE, rect->left, rect->top,
|
|
rect->width, rect->height );
|
|
} /* frameRect */
|
|
|
|
static const XP_UCHAR*
|
|
gtk_draw_getMiniWText( DrawCtx* XP_UNUSED(p_dctx), XWEnv XP_UNUSED(xwe),
|
|
XWMiniTextType textHint )
|
|
{
|
|
/* GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; */
|
|
XP_UCHAR* str;
|
|
switch( textHint ) {
|
|
case BONUS_DOUBLE_LETTER:
|
|
str = "Double letter"; break;
|
|
case BONUS_DOUBLE_WORD:
|
|
str = "Double word"; break;
|
|
case BONUS_TRIPLE_LETTER:
|
|
str = "Triple letter"; break;
|
|
case BONUS_QUAD_WORD:
|
|
str = "Quad word"; break;
|
|
case BONUS_QUAD_LETTER:
|
|
str = "Quad letter"; break;
|
|
case BONUS_TRIPLE_WORD:
|
|
str = "Triple word"; break;
|
|
case INTRADE_MW_TEXT:
|
|
str = "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, XWEnv XP_UNUSED(xwe), 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, XWEnv XP_UNUSED(xwe), const XP_UCHAR* text,
|
|
const XP_Rect* rect, void** XP_UNUSED(closureP) )
|
|
{
|
|
GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
|
|
XP_Rect localR = *rect;
|
|
|
|
gtkSetForeground( dctx, &dctx->black );
|
|
|
|
/* 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 */
|
|
#endif
|
|
|
|
#define SET_GDK_COLOR( c, r, g, b ) { \
|
|
c.red = (r); \
|
|
c.green = (g); \
|
|
c.blue = (b); \
|
|
}
|
|
|
|
static gdouble
|
|
figureColor( int in )
|
|
{
|
|
gdouble asDouble = (gdouble)in;
|
|
gdouble result = asDouble / 0xFFFF;
|
|
// XP_LOGF( "%s(%d): asDouble: %lf; result: %lf", __func__, in, asDouble, result );
|
|
XP_ASSERT( result >= 0 && result <= 1.0 );
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
allocAndSet( GdkRGBA* color, unsigned short red,
|
|
unsigned short green, unsigned short blue )
|
|
|
|
{
|
|
color->red = figureColor(red);
|
|
color->green = figureColor(green);
|
|
color->blue = figureColor(blue);
|
|
color->alpha = 1.0;
|
|
} /* allocAndSet */
|
|
|
|
DrawCtx*
|
|
gtkDrawCtxtMake( GtkWidget* drawing_area, GtkGameGlobals* globals )
|
|
{
|
|
GtkDrawCtx* dctx = g_malloc0( sizeof(*dctx) );
|
|
|
|
size_t tableSize = sizeof(*(((GtkDrawCtx*)dctx)->vtable));
|
|
dctx->vtable = g_malloc( tableSize );
|
|
/* void** ptr = (void**)dctx->vtable; */
|
|
/* void** end = (void**)(((unsigned char*)ptr) + tableSize); */
|
|
/* while ( ptr < end ) { */
|
|
/* *ptr++ = 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_beginDraw, gtk );
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_endDraw, gtk );
|
|
|
|
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 );
|
|
|
|
#ifdef XWFEATURE_SCOREONEPASS
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, gtk );
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayers, gtk );
|
|
#else
|
|
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 );
|
|
#endif
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, gtk );
|
|
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_drawTimer, gtk );
|
|
|
|
#ifdef XWFEATURE_MINIWIN
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, gtk );
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, gtk );
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, gtk );
|
|
#endif
|
|
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_destroyCtxt, gtk );
|
|
SET_VTABLE_ENTRY( dctx->vtable, draw_dictChanged, gtk );
|
|
#endif
|
|
|
|
assertDrawCallbacksSet( dctx->vtable );
|
|
|
|
dctx->drawing_area = drawing_area;
|
|
dctx->globals = globals;
|
|
|
|
XP_ASSERT( !!gtk_widget_get_window(drawing_area) );
|
|
|
|
allocAndSet( &dctx->black, 0x0000, 0x0000, 0x0000 );
|
|
allocAndSet( &dctx->grey, 0x7FFF, 0x7FFF, 0x7FFF );
|
|
allocAndSet( &dctx->white, 0xFFFF, 0xFFFF, 0xFFFF );
|
|
|
|
allocAndSet( &dctx->bonusColors[0], 0xFFFF, 0xAFFF, 0xAFFF );
|
|
allocAndSet( &dctx->bonusColors[1], 0xAFFF, 0xFFFF, 0xAFFF );
|
|
allocAndSet( &dctx->bonusColors[2], 0xAFFF, 0xAFFF, 0xFFFF );
|
|
allocAndSet( &dctx->bonusColors[3], 0xFFFF, 0xAFFF, 0xFFFF );
|
|
|
|
allocAndSet( &dctx->playerColors[0], 0x0000, 0x0000, 0xAFFF );
|
|
allocAndSet( &dctx->playerColors[1], 0xAFFF, 0x0000, 0x0000 );
|
|
allocAndSet( &dctx->playerColors[2], 0x0000, 0xAFFF, 0x0000 );
|
|
allocAndSet( &dctx->playerColors[3], 0xAFFF, 0x0000, 0xAFFF );
|
|
|
|
allocAndSet( &dctx->tileBack, 0xFFFF, 0xFFFF, 0x9999 );
|
|
allocAndSet( &dctx->cursor, 253<<8, 12<<8, 222<<8 );
|
|
allocAndSet( &dctx->red, 0xFFFF, 0x0000, 0x0000 );
|
|
|
|
return (DrawCtx*)dctx;
|
|
} /* gtkDrawCtxtMake */
|
|
|
|
void
|
|
addSurface( GtkDrawCtx* dctx, int width, int height )
|
|
{
|
|
XP_ASSERT( !dctx->surface );
|
|
dctx->surface = cairo_image_surface_create( CAIRO_FORMAT_RGB24, width, height );
|
|
XP_ASSERT( !!dctx->surface );
|
|
}
|
|
|
|
void
|
|
removeSurface( GtkDrawCtx* dctx )
|
|
{
|
|
XP_ASSERT( !!dctx->surface );
|
|
cairo_surface_destroy( dctx->surface );
|
|
dctx->surface = NULL;
|
|
}
|
|
|
|
static cairo_status_t
|
|
write_func( void *closure, const unsigned char *data,
|
|
unsigned int length )
|
|
{
|
|
XWStreamCtxt* stream = (XWStreamCtxt*)closure;
|
|
stream_putBytes( stream, data, length );
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
void
|
|
getImage( GtkDrawCtx* dctx, XWStreamCtxt* stream )
|
|
{
|
|
LOG_FUNC();
|
|
XP_ASSERT( !!dctx->surface );
|
|
#ifdef DEBUG
|
|
cairo_status_t status =
|
|
#endif
|
|
cairo_surface_write_to_png_stream( dctx->surface,
|
|
write_func, stream );
|
|
XP_ASSERT( CAIRO_STATUS_SUCCESS == status );
|
|
}
|
|
|
|
void
|
|
draw_gtk_status( GtkDrawCtx* dctx, char ch )
|
|
{
|
|
if ( initCairo( dctx ) ) {
|
|
GtkGameGlobals* globals = dctx->globals;
|
|
|
|
XP_Rect rect = {
|
|
.left = globals->netStatLeft,
|
|
.top = globals->netStatTop,
|
|
.width = GTK_NETSTAT_WIDTH,
|
|
.height = GTK_HOR_SCORE_HEIGHT
|
|
};
|
|
gtkEraseRect( dctx, &rect );
|
|
|
|
const XP_UCHAR str[2] = { ch, '\0' };
|
|
draw_string_at( dctx, NULL, str, GTKMIN_W_HT,
|
|
&rect, XP_GTK_JUST_CENTER,
|
|
&dctx->black, NULL );
|
|
|
|
destroyCairo( dctx );
|
|
}
|
|
}
|
|
|
|
void
|
|
frame_active_rect( GtkDrawCtx* dctx, const XP_Rect* rect )
|
|
{
|
|
gtkFillRect( dctx, rect, &dctx->grey );
|
|
}
|
|
|
|
#endif /* PLATFORM_GTK */
|