diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index 5129afad5..8dedf2200 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -28,10 +28,13 @@ CFLAGS += -Os endif DO_CURSES = -DPLATFORM_NCURSES +ifdef CURSES_SMALL_SCREEN +DO_CURSES += -DCURSES_SMALL_SCREEN +endif DO_GTK = -DPLATFORM_GTK -SVNDEF = -D'SVN_REV="$(shell svnversion -n .)"' -CFLAGS += $(SVNDEF) +SVN_REV ?= "$(shell svnversion -n .)" +SVNDEF = -D'SVN_REV=$(SVN_REV)' ifdef CURSES_ONLY DO_GTK = @@ -43,28 +46,31 @@ DO_CURSES = PLATFORM := $(PLATFORM)_gtk endif -DEFINES += $(DO_CURSES) $(DO_GTK) +DEFINES += $(DO_CURSES) $(DO_GTK) $(SVNDEF) TARGET=$(PLATFORM)/xwords -ifdef BUILD_COMMAND -CC = ${BUILD_COMMAND} -else -CC = gcc -endif - include ../common/config.mk DEFINES += -DPLATFORM_LINUX -DKEY_SUPPORT -DKEYBOARD_NAV -DNODE_CAN_4 -DEFINES += -DSTUBBED_DICT +# DEFINES += -DSTUBBED_DICT ifdef DO_GTK DEFINES += -DXWFEATURE_SEARCHLIMIT endif DEFINES += -DFEATURE_TRAY_EDIT #DEFINES += -DDRAW_WITH_PRIMITIVES +ifdef CURSES_CELL_HT +DEFINES += -DCURSES_CELL_HT=$(CURSES_CELL_HT) +endif +ifdef CURSES_CELL_WIDTH +DEFINES += -DCURSES_CELL_WIDTH=$(CURSES_CELL_WIDTH) +endif + # Bluetooth support +ifndef NO_BLUETOOTH BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_L2CAP +endif #BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_RFCOMM # DEFINES += -DXWFEATURE_IR DEFINES += ${BLUETOOTH} @@ -114,7 +120,7 @@ OBJ = $(PLATFORM)/linuxmain.o \ # $(PLATFORM)/linuxcommpipe.o \ -LIBS = -lm -lmcheck -L $(HOME)/usr/local/pilot/lib $(GPROFFLAG) +LIBS = -lm -lmcheck $(GPROFFLAG) ifdef BLUETOOTH LIBS += -lbluetooth endif @@ -126,7 +132,7 @@ ifneq (,$(findstring DPLATFORM_GTK,$(DEFINES))) endif ifneq (,$(findstring DPLATFORM_NCURSES,$(DEFINES))) - LIBS += -lncurses + LIBS += $(OE_LIBDIR) -lncurses endif # provides an all: target diff --git a/xwords4/linux/cursesdraw.c b/xwords4/linux/cursesdraw.c index 5efc3bbc0..b3984964b 100644 --- a/xwords4/linux/cursesdraw.c +++ b/xwords4/linux/cursesdraw.c @@ -1,6 +1,7 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ /* - * Copyright 1997-2007 by Eric House (xwords@eehouse.org). All rights reserved. + * 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 @@ -36,6 +37,7 @@ typedef struct CursesDrawCtx { } CursesDrawCtx; static void curses_draw_clearRect( DrawCtx* p_dctx, const XP_Rect* rectP ); +static void getTops( const XP_Rect* rect, int* toptop, int* topbot ); static void drawRect( WINDOW* win, const XP_Rect* rect, char vert, char hor ) @@ -64,7 +66,6 @@ static void cursesHiliteRect( WINDOW* window, const XP_Rect* rect ) { int right, width, x, y; - LOG_FUNC(); width = rect->width; right = width + rect->left; wstandout( window ); @@ -112,26 +113,22 @@ curses_draw_scoreBegin( DrawCtx* p_dctx, const XP_Rect* rect, } /* curses_draw_scoreBegin */ static void -formatRemText( char* buf, XP_S16 nTilesLeft ) +formatRemText( char* buf, int bufLen, XP_S16 nTilesLeft, int width ) { - strcpy( buf, "Tiles left in pool: " ); - buf += strlen( buf ); - if ( nTilesLeft < 0 ) { - strcpy( buf, "***" ); - } else { - sprintf( buf, "%.3d", nTilesLeft ); + snprintf( buf, bufLen, "Tiles left in pool: %.3d", nTilesLeft ); + if ( strlen(buf)+1 >= width ) { + snprintf( buf, bufLen, "Rem: %.3d", nTilesLeft ); } } /* formatRemText */ static void curses_draw_measureRemText( DrawCtx* XP_UNUSED(dctx), - const XP_Rect* XP_UNUSED(r), + const XP_Rect* r, XP_S16 nTilesLeft, XP_U16* width, XP_U16* height ) { char buf[32]; - - formatRemText( buf, nTilesLeft ); + formatRemText( buf, sizeof(buf), nTilesLeft, r->width ); *width = strlen(buf); *height = 1; @@ -144,60 +141,104 @@ curses_draw_drawRemText( DrawCtx* p_dctx, const XP_Rect* rInner, CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; char buf[32]; - formatRemText( buf, nTilesLeft ); + formatRemText( buf, sizeof(buf), nTilesLeft, rInner->width ); mvwprintw( dctx->boardWin, rInner->top, rInner->left, buf ); } /* curses_draw_drawRemText */ -#define SCORE_COL 25 -#define RECENT_COL 31 +static int +fitIn( char* buf, int len, int* rem, char* str ) +{ + int slen = strlen(str); + if ( !!rem && (*rem != 0) ) { + ++len; + --*rem; + } + if ( slen > len ) { + slen = len; + } + + memcpy( buf, str, slen ); + return len; +} /* fitIn */ static void -formatScoreText( XP_UCHAR* buf, const DrawScoreInfo* dsi ) +formatScoreText( XP_UCHAR* out, int outLen, const DrawScoreInfo* dsi, + int width ) { - XP_S16 nTilesLeft = dsi->nTilesLeft; - XP_Bool isRobot = dsi->isRobot; - char label = isRobot? 'r':'n'; - int len; - char recbuf[32]; - XP_U16 recBufLen = sizeof(recbuf); + /* Long and short formats. We'll try long first. If it fits, cool. + Otherwise we use short. Either way, we fill the whole rect so it can + overwrite anything that was there before.*/ + char tmp[width+1]; + char buf[width+1]; + int scoreWidth = 4; - if ( nTilesLeft < 0 ) { - nTilesLeft = MAX_TRAY_TILES; + XP_ASSERT( width < outLen ); + + XP_MEMSET( buf, ' ', width ); + buf[width] = '\0'; + + /* Status/role chars at start */ + if ( dsi->isTurn ) { + buf[0] = 'T'; } - if ( dsi->isRemote ) { - label = toupper(label); + if ( dsi->selected ) { + buf[1] = 'S'; + } + if ( dsi->isRobot) { + buf[2] = 'r'; } - len = sprintf( buf, "%c:%c [%c] %s (%d)", - (dsi->isTurn? 'T' : ' '), - (dsi->selected? 'S' : ' '), - label, dsi->name, nTilesLeft ); - while ( len < SCORE_COL ) { - ++len; - strcat( buf, " " ); - } - len += sprintf( buf + len, "%.3d", dsi->totalScore ); + /* Score always goes at end. Will overwrite status if width is really small */ + snprintf( tmp, scoreWidth, "%.3d", dsi->totalScore ); + memcpy( &buf[width-scoreWidth+1], tmp, scoreWidth-1 ); - if ( (*dsi->lsc)( dsi->lscClosure, dsi->playerNum, recbuf, &recBufLen ) ) { - while ( len < RECENT_COL ) { - ++len; - strcat( buf, " " ); + /* Now we want to fit name, rem tiles, last score, and last move, if + there's space. Allocate to each so they're in columns. */ + + width -= 8; /* status chars plus space; score plus space */ + if ( width > 0 ) { + int pos = 4; + int nCols = 2; + int perCol = (width - ( nCols - 1)) / nCols; /* not counting spaces */ + if ( perCol > 0 ) { + int rem = (width - ( nCols - 1)) % nCols; + + pos += 1 + fitIn( &buf[pos], perCol, &rem, dsi->name ); + + XP_U16 len = sizeof(tmp); + if ( (*dsi->lsc)( dsi->lscClosure, dsi->playerNum, tmp, &len ) ) { + char* s = tmp; + if ( len > perCol ) { + /* We want to preserve the score first, then the first part of + word. That is, WORD:20 prints as W0:20, not WORD: or + RD:20 */ + char* colon = strstr( tmp, ":" ); + if ( colon ) { + s += len - perCol; + memmove( s, tmp, colon - tmp - (len - perCol) ); + } + } + pos += 1 + fitIn( &buf[pos], perCol, NULL, s ); + } + } else { + fitIn( &buf[pos], width, NULL, dsi->name ); } - strcat( buf, recbuf ); - } + } + snprintf( out, outLen, "%s", buf ); } /* formatScoreText */ static void curses_draw_measureScoreText( DrawCtx* XP_UNUSED(p_dctx), - const XP_Rect* XP_UNUSED(r), + const XP_Rect* r, const DrawScoreInfo* dsi, XP_U16* width, XP_U16* height ) { XP_UCHAR buf[100]; - formatScoreText( buf, dsi ); + formatScoreText( buf, sizeof(buf), dsi, r->width ); *width = strlen( buf ); + XP_ASSERT( *width <= r->width ); *height = 1; /* one line per player */ } /* curses_draw_measureScoreText */ @@ -215,8 +256,11 @@ curses_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect, strcpy( buf, "???" ); } - mvwprintw( dctx->boardWin, rect->top+1, rect->left, "pt:" ); - mvwprintw( dctx->boardWin, rect->top+2, rect->left, "%s", buf ); + int toptop, topbot; + getTops( rect, &toptop, &topbot ); + + mvwprintw( dctx->boardWin, toptop, rect->left, "pt:" ); + mvwprintw( dctx->boardWin, topbot, rect->left, "%s", buf ); } /* curses_draw_score_pendingScore */ static void @@ -241,7 +285,7 @@ curses_draw_score_drawPlayer( DrawCtx* p_dctx, const XP_Rect* rInner, curses_draw_clearRect( p_dctx, rOuter ); /* print the name and turn/remoteness indicator */ - formatScoreText( buf, dsi ); + formatScoreText( buf, sizeof(buf), dsi, rInner->width ); mvwprintw( dctx->boardWin, y, rOuter->left, buf ); if ( (dsi->flags&CELL_ISCURSOR) != 0 ) { @@ -260,7 +304,6 @@ curses_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, XP_UCHAR loc[4] = { ' ', ' ', ' ', '\0' }; XP_ASSERT( XP_STRLEN(letter) < sizeof(loc) ); XP_ASSERT( rect->width < sizeof(loc) ); - XP_ASSERT( rect->height == 1 ); XP_MEMCPY( loc, letter, strlen(letter) ); if ( letter[0] == LETTER_NONE ) { @@ -296,21 +339,40 @@ curses_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, return XP_TRUE; } /* curses_draw_drawCell */ +static void +getTops( const XP_Rect* rect, int* toptop, int* topbot ) +{ + int top = rect->top; + if ( rect->height >= 4 ) { + ++top; + } + *toptop = top; + + top = rect->top + rect->height - 1; + if ( rect->height >= 3 ) { + --top; + } + *topbot = top; +} /* getTops */ + static void curses_stringInTile( CursesDrawCtx* dctx, const XP_Rect* rect, XP_UCHAR* letter, XP_UCHAR* val ) { eraseRect( dctx, rect ); + int toptop, topbot; + getTops( rect, &toptop, &topbot ); + if ( !!letter ) { - mvwaddnstr( dctx->boardWin, rect->top+1, rect->left+(rect->width/2), + mvwaddnstr( dctx->boardWin, toptop, rect->left+(rect->width/2), letter, strlen(letter) ); } if ( !!val ) { int len = strlen( val ); - mvwaddnstr( dctx->boardWin, rect->top+rect->height-2, - rect->left + rect->width - len, val, len ); + mvwaddnstr( dctx->boardWin, topbot, rect->left + rect->width - len, + val, len ); } } /* curses_stringInTile */ diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 471ff84eb..0cf975874 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -56,10 +56,128 @@ #include "util.h" #include "dbgutil.h" -#define MENU_WINDOW_HEIGHT 5 /* three lines plus borders */ -#define INFINITE_TIMEOUT -1 +#ifdef CURSES_SMALL_SCREEN +# define MENU_WINDOW_HEIGHT 1 +# define BOARD_OFFSET 0 +#else +# define MENU_WINDOW_HEIGHT 5 /* three lines plus borders */ +# define BOARD_OFFSET 1 +#endif -CursesAppGlobals globals; /* must be global b/c of SIGWINCH_handler */ +#ifndef CURSES_CELL_HT +# define CURSES_CELL_HT 1 +#endif +#ifndef CURSES_CELL_WIDTH +# define CURSES_CELL_WIDTH 2 +#endif + +#ifndef CURSES_MAX_HEIGHT +# define CURSES_MAX_HEIGHT 40 +#endif +#ifndef CURSES_MAX_WIDTH +//# define CURSES_MAX_WIDTH 50 +# define CURSES_MAX_WIDTH 70 +#endif + +#define INFINITE_TIMEOUT -1 +#define BOARD_SCORE_PADDING 3 + + +typedef XP_Bool (*CursesMenuHandler)(CursesAppGlobals* globals); +typedef struct MenuList { + CursesMenuHandler handler; + char* desc; + char* keyDesc; + char key; +} MenuList; + +static XP_Bool handleQuit( CursesAppGlobals* globals ); +static XP_Bool handleRight( CursesAppGlobals* globals ); +static XP_Bool handleSpace( CursesAppGlobals* globals ); +static XP_Bool handleRet( CursesAppGlobals* globals ); +static XP_Bool handleHint( CursesAppGlobals* globals ); +static XP_Bool handleLeft( CursesAppGlobals* globals ); +static XP_Bool handleRight( CursesAppGlobals* globals ); +static XP_Bool handleUp( CursesAppGlobals* globals ); +static XP_Bool handleDown( CursesAppGlobals* globals ); +static XP_Bool handleCommit( CursesAppGlobals* globals ); +static XP_Bool handleFlip( CursesAppGlobals* globals ); +static XP_Bool handleToggleValues( CursesAppGlobals* globals ); +static XP_Bool handleBackspace( CursesAppGlobals* globals ); +static XP_Bool handleUndo( CursesAppGlobals* globals ); +static XP_Bool handleReplace( CursesAppGlobals* globals ); +static XP_Bool handleRootKeyShow( CursesAppGlobals* globals ); +static XP_Bool handleRootKeyHide( CursesAppGlobals* globals ); +static XP_Bool handleJuggle( CursesAppGlobals* globals ); +static XP_Bool handleHide( CursesAppGlobals* globals ); +static XP_Bool handleAltLeft( CursesAppGlobals* globals ); +static XP_Bool handleAltRight( CursesAppGlobals* globals ); +static XP_Bool handleAltUp( CursesAppGlobals* globals ); +static XP_Bool handleAltDown( CursesAppGlobals* globals ); + + +MenuList g_sharedMenuList[] = { + { handleQuit, "Quit", "Q", 'Q' }, + { handleRight, "Tab right", "", '\t' }, + { handleSpace, "Raise focus", "", ' ' }, + { handleRet, "Click/tap", "", '\r' }, + { handleHint, "Hint", "?", '?' }, + +#ifdef KEYBOARD_NAV + { handleLeft, "Left", "H", 'H' }, + { handleRight, "Right", "L", 'L' }, + { handleUp, "Up", "J", 'J' }, + { handleDown, "Down", "K", 'K' }, +#endif + + { handleCommit, "Commit move", "C", 'C' }, + { handleFlip, "Flip", "F", 'F' }, + { handleToggleValues, "Show values", "V", 'V' }, + + { handleBackspace, "Remove from board", "", 8 }, + { handleUndo, "Undo prev", "U", 'U' }, + { handleReplace, "uNdo cur", "N", 'N' }, + + { NULL, NULL, NULL, '\0'} +}; + +MenuList g_boardMenuList[] = { + { handleAltLeft, "Force left", "{", '{' }, + { handleAltRight, "Force right", "}", '}' }, + { handleAltUp, "Force up", "_", '_' }, + { handleAltDown, "Force down", "+", '+' }, + { NULL, NULL, NULL, '\0'} +}; + +MenuList g_scoreMenuList[] = { +#ifdef KEYBOARD_NAV +#endif + { NULL, NULL, NULL, '\0'} +}; + +MenuList g_trayMenuList[] = { + { handleJuggle, "Juggle", "G", 'G' }, + { handleHide, "[un]hIde", "I", 'I' }, + { handleAltLeft, "Divider left", "{", '{' }, + { handleAltRight, "Divider right", "}", '}' }, + + { NULL, NULL, NULL, '\0'} +}; + +#ifdef CURSES_SMALL_SCREEN +MenuList g_rootMenuListShow[] = { + { handleRootKeyShow, "Press . for menu", "", '.' }, + { NULL, NULL, NULL, '\0'} +}; + +MenuList g_rootMenuListHide[] = { + { handleRootKeyHide, "Clear menu", ".", '.' }, + { NULL, NULL, NULL, '\0'} +}; +#endif + + +static CursesAppGlobals g_globals; /* must be global b/c of SIGWINCH_handler */ static void changeMenuForFocus( CursesAppGlobals* globals, BoardObjectType obj ); @@ -68,6 +186,11 @@ static XP_Bool handleRight( CursesAppGlobals* globals ); static XP_Bool handleUp( CursesAppGlobals* globals ); static XP_Bool handleDown( CursesAppGlobals* globals ); static XP_Bool handleFocusKey( CursesAppGlobals* globals, XP_Key key ); +static void countMenuLines( MenuList** menuLists, int maxX, int padding, + int* nLinesP, int* nColsP ); +static void drawMenuFromList( WINDOW* win, MenuList** menuLists, + int nLines, int padding ); +static CursesMenuHandler getHandlerForKey( MenuList* list, char ch ); #ifdef MEM_DEBUG @@ -257,7 +380,7 @@ initCurses( CursesAppGlobals* globals ) WINDOW* menuWin; WINDOW* boardWin; - int x, y; + int width, height; /* ncurses man page says most apps want this sequence */ mainWin = initscr(); @@ -265,16 +388,22 @@ initCurses( CursesAppGlobals* globals ) noecho(); nonl(); intrflush(stdscr, FALSE); - keypad(stdscr, TRUE); + keypad(stdscr, TRUE); /* effects wgetch only? */ - getmaxyx(mainWin, y, x); - globals->statusLine = y - MENU_WINDOW_HEIGHT - 1; - menuWin = newwin( MENU_WINDOW_HEIGHT, x, y-MENU_WINDOW_HEIGHT, 0 ); + getmaxyx(mainWin, height, width ); + XP_LOGF( "getmaxyx->w:%d; h:%d", width, height ); + if ( height > CURSES_MAX_HEIGHT ) { + height = CURSES_MAX_HEIGHT; + } + if ( width > CURSES_MAX_WIDTH ) { + width = CURSES_MAX_WIDTH; + } + + globals->statusLine = height - MENU_WINDOW_HEIGHT - 1; + menuWin = newwin( MENU_WINDOW_HEIGHT, width, + height-MENU_WINDOW_HEIGHT, 0 ); nodelay(menuWin, 1); /* don't block on getch */ - boardWin = newwin( MAX_ROWS+1, x, 0, 0 ); - -/* leaveok( boardWin, 1 ); */ -/* leaveok( menuWin, 1 ); */ + boardWin = newwin( height-MENU_WINDOW_HEIGHT, width, 0, 0 ); globals->menuWin = menuWin; globals->boardWin = boardWin; @@ -308,13 +437,6 @@ showStatus( CursesAppGlobals* globals ) } /* showStatus */ #endif -typedef XP_Bool (*CursesMenuHandler)(CursesAppGlobals* globals); -typedef struct MenuList { - CursesMenuHandler handler; - char* desc; - char* keyDesc; - char key; -} MenuList; static XP_Bool handleQuit( CursesAppGlobals* globals ) { @@ -391,6 +513,70 @@ handleHide( CursesAppGlobals* globals ) return XP_TRUE; } /* handleJuggle */ +#ifdef CURSES_SMALL_SCREEN +static XP_Bool +handleRootKeyShow( CursesAppGlobals* globals ) +{ + WINDOW* win; + MenuList* lists[] = { g_sharedMenuList, globals->menuList, + g_rootMenuListHide, NULL }; + int winMaxY, winMaxX; + + wclear( globals->menuWin ); + wrefresh( globals->menuWin ); + + getmaxyx( globals->boardWin, winMaxY, winMaxX ); + + int border = 2; + int width = winMaxX - (border * 2); + int padding = 1; /* for the box */ + int nLines, nCols; + countMenuLines( lists, width, padding, &nLines, &nCols ); + + if ( width > nCols ) { + width = nCols; + } + + win = newwin( nLines+(padding*2), width+(padding*2), + ((winMaxY-nLines-padding-padding)/2), (winMaxX-width)/2 ); + wclear( win ); + box( win, '|', '-'); + + drawMenuFromList( win, lists, nLines, padding ); + wrefresh( win ); + + CursesMenuHandler handler = NULL; + while ( !handler ) { + int ch = fgetc( stdin ); + + int i; + for ( i = 0; !!lists[i]; ++i ) { + handler = getHandlerForKey( lists[i], ch ); + if ( !!handler ) { + break; + } + } + } + + delwin( win ); + + touchwin( globals->boardWin ); + wrefresh( globals->boardWin ); + MenuList* ml[] = { g_rootMenuListShow, NULL }; + drawMenuFromList( globals->menuWin, ml, 1, 0 ); + wrefresh( globals->menuWin ); + + return handler != NULL && (*handler)(globals); +} /* handleRootKeyShow */ + +static XP_Bool +handleRootKeyHide( CursesAppGlobals* globals ) +{ + globals->doDraw = XP_TRUE; + return XP_TRUE; +} +#endif + static XP_Bool handleAltLeft( CursesAppGlobals* globals ) { @@ -452,31 +638,6 @@ handleReplace( CursesAppGlobals* globals ) return XP_TRUE; } /* handleReplace */ -MenuList sharedMenuList[] = { - { handleQuit, "Quit", "Q", 'Q' }, - { handleRight, "Tab right", "", '\t' }, - { handleSpace, "Raise focus", "", ' ' }, - { handleRet, "Click/tap", "", '\r' }, - { handleHint, "Hint", "?", '?' }, - -#ifdef KEYBOARD_NAV - { handleLeft, "Left", "H", 'H' }, - { handleRight, "Right", "L", 'L' }, - { handleUp, "Up", "J", 'J' }, - { handleDown, "Down", "K", 'K' }, -#endif - - { handleCommit, "Commit move", "C", 'C' }, - { handleFlip, "Flip", "F", 'F' }, - { handleToggleValues, "Show values", "V", 'V' }, - - { handleBackspace, "Remove from board", "", 8 }, - { handleUndo, "Undo prev", "U", 'U' }, - { handleReplace, "uNdo cur", "N", 'N' }, - - { NULL, NULL, NULL, '\0'} -}; - #ifdef KEYBOARD_NAV static XP_Bool handleFocusKey( CursesAppGlobals* globals, XP_Key key ) @@ -525,109 +686,112 @@ handleDown( CursesAppGlobals* globals ) } /* handleDown */ #endif -MenuList boardMenuList[] = { - { handleAltLeft, "Force left", "{", '{' }, - { handleAltRight, "Force right", "}", '}' }, - { handleAltUp, "Force up", "_", '_' }, - { handleAltDown, "Force down", "+", '+' }, - { NULL, NULL, NULL, '\0'} -}; +static void +fmtMenuItem( const MenuList* item, char* buf, int maxLen ) +{ + snprintf( buf, maxLen, "%s %s", item->keyDesc, item->desc ); +} -MenuList scoreMenuList[] = { -#ifdef KEYBOARD_NAV -#endif - { NULL, NULL, NULL, '\0'} -}; - -MenuList trayMenuList[] = { - { handleJuggle, "Juggle", "G", 'G' }, - { handleHide, "[un]hIde", "I", 'I' }, - { handleAltLeft, "Divider left", "{", '{' }, - { handleAltRight, "Divider right", "}", '}' }, - - { NULL, NULL, NULL, '\0'} -}; static void -figureMaxes( MenuList* mList, short maxLines, short* maxKeyP, short* maxCmdP ) +countMenuLines( MenuList** menuLists, int maxX, int padding, + int* nLinesP, int* nColsP ) { - short i; + int nCols = 0; + /* The menu space should be wider rather than taller, but line up by + column. So we want to use as many columns as possible to minimize the + number of lines. So start with one line and lay out. If that doesn't + fit, try two. Given the number of lines, get the max width of each + column. + */ - *maxKeyP = *maxCmdP = 0; + maxX -= padding * 2; /* on left and right side */ - for ( i = 0; i < maxLines && mList->handler != NULL; ++i ) { - short keyLen = strlen(mList->keyDesc); - short cmdLen = strlen(mList->desc); - *maxKeyP = XP_MAX( *maxKeyP, keyLen ); - *maxCmdP= XP_MAX( *maxCmdP, cmdLen ); - ++mList; + int nLines; + for ( nLines = 1; ; ++nLines ) { + short line = 0; + XP_Bool tooFewLines = XP_FALSE; + int maxThisCol = 0; + int i; + nCols = 0; + + for ( i = 0; !tooFewLines && (NULL != menuLists[i]); ++i ) { + MenuList* entry; + for ( entry = menuLists[i]; !tooFewLines && !!entry->handler; + ++entry ) { + int width; + char buf[32]; + + /* time to switch to new column? */ + if ( line == nLines ) { + nCols += maxThisCol; + if ( nCols > maxX ) { + tooFewLines = XP_TRUE; + break; + } + maxThisCol = 0; + line = 0; + } + + fmtMenuItem( entry, buf, sizeof(buf) ); + width = strlen(buf) + 2; /* padding */ + + if ( maxThisCol < width ) { + maxThisCol = width; + } + + ++line; + } + } + /* If we get here without running out of space, we're done */ + nCols += maxThisCol; + if ( !tooFewLines && (nCols < maxX) ) { + break; + } } -} /* figureMaxes */ + + *nColsP = nCols; + *nLinesP = nLines; +} /* countMenuLines */ static void -drawMenuFromList( CursesAppGlobals* globals, MenuList* menuList ) +drawMenuFromList( WINDOW* win, MenuList** menuLists, + int nLines, int padding ) { - short i; - short maxKey = 0, maxCmd = 0; - short line = 0, col; - short nLines; + short line = 0, col, i; int winMaxY, winMaxX; - WINDOW* win = globals->menuWin; - XP_Bool done = XP_FALSE; getmaxyx( win, winMaxY, winMaxX ); - nLines = globals->nLinesMenu; - if ( nLines == 0 ) { - nLines = 1; + int maxColWidth = 0; + if ( 0 == nLines ) { + int ignore; + countMenuLines( menuLists, winMaxX, padding, &nLines, &ignore ); } + col = 0; - for ( ; !done; ++nLines ) { - MenuList* entry = sharedMenuList; - XP_Bool isShared = XP_TRUE; + for ( i = 0; NULL != menuLists[i]; ++i ) { + MenuList* entry; + for ( entry = menuLists[i]; !!entry->handler; ++entry ) { + char buf[32]; - wclear( win ); + fmtMenuItem( entry, buf, sizeof(buf) ); - maxKey = maxCmd = 0; - for ( line = 0, col = -2, i = 0; ; ++entry, ++line ) { - char* key; + mvwaddstr( win, line+padding, col+padding, buf ); - if ( entry->handler == NULL ) { - if ( !isShared ) { - done = XP_TRUE; - break; - } else if ( menuList->handler == NULL ) { - done = XP_TRUE; - break; - } else { - isShared = XP_FALSE; - entry = menuList; - XP_ASSERT( !!entry->handler ); - } + int width = strlen(buf) + 2; + if ( width > maxColWidth ) { + maxColWidth = width; } - XP_ASSERT( nLines > 0 ); - if ( line % nLines == 0 ) { + if ( ++line == nLines ) { line = 0; - col += maxKey + maxCmd + 2; - figureMaxes( entry, nLines, &maxKey, &maxCmd ); - if ( (col + maxCmd + strlen(entry->keyDesc)) >= winMaxX ) { - break; - } + col += maxColWidth; + maxColWidth = 0; } - key = entry->keyDesc; - - wstandout( win ); - mvwaddstr( win, line, col+maxKey-strlen(key), key ); - wstandend( win ); - mvwaddstr( win, line, col+maxKey+1, entry->desc ); } } - - globals->nLinesMenu = nLines - 1; - - wrefresh( win ); } /* drawMenuFromList */ static void @@ -642,9 +806,9 @@ SIGWINCH_handler( int signal ) /* (*globals.drawMenu)( &globals ); */ getmaxyx( stdscr, y, x ); - wresize( globals.mainWin, y-MENU_WINDOW_HEIGHT, x ); + wresize( g_globals.mainWin, y-MENU_WINDOW_HEIGHT, x ); - board_draw( globals.cGlobals.game.board ); + board_draw( g_globals.cGlobals.game.board ); } /* SIGWINCH_handler */ static void @@ -748,7 +912,7 @@ blocking_gotEvent( CursesAppGlobals* globals, int* ch ) /* stdin first */ if ( (globals->fdArray[FD_STDIN].revents & POLLIN) != 0 ) { - int evtCh = fgetc(stdin); + int evtCh = wgetch(globals->mainWin); XP_LOGF( "%s: got key: %x", __func__, evtCh ); *ch = evtCh; result = XP_TRUE; @@ -854,22 +1018,68 @@ blocking_gotEvent( CursesAppGlobals* globals, int* ch ) return result; } /* blocking_gotEvent */ +static void +remapKey( int* kp ) +{ + /* There's what the manual says I should get, and what I actually do from + * a funky M$ keyboard.... + */ + int key = *kp; + switch( key ) { + case KEY_B2: /* "center of keypad" */ + key = '\r'; + break; + case KEY_DOWN: + case 526: + key = 'K'; + break; + case KEY_UP: + case 523: + key = 'J'; + break; + case KEY_LEFT: + case 524: + key = 'H'; + break; + case KEY_RIGHT: + case 525: + key = 'L'; + break; + default: + if ( key > 0xFF ) { + XP_LOGF( "%s(%d): no mapping", __func__, key ); + } + break; + } + *kp = key; +} + +static void +drawMenuLargeOrSmall( CursesAppGlobals* globals, const MenuList* menuList ) +{ +#ifdef CURSES_SMALL_SCREEN + MenuList* lists[] = { g_rootMenuListShow, NULL }; +#else + MenuList* lists[] = { g_sharedMenuList, menuList, NULL }; +#endif + drawMenuFromList( globals->menuWin, lists, 0, 0 ); + wrefresh( globals->menuWin ); +} + static void changeMenuForFocus( CursesAppGlobals* globals, BoardObjectType focussed ) { #ifdef KEYBOARD_NAV if ( focussed == OBJ_TRAY ) { - globals->menuList = trayMenuList; - drawMenuFromList( globals, trayMenuList ); + globals->menuList = g_trayMenuList; } else if ( focussed == OBJ_BOARD ) { - globals->menuList = boardMenuList; - drawMenuFromList( globals, boardMenuList ); + globals->menuList = g_boardMenuList; } else if ( focussed == OBJ_SCORE ) { - globals->menuList = scoreMenuList; - drawMenuFromList( globals, scoreMenuList ); + globals->menuList = g_scoreMenuList; } else { XP_ASSERT(0); } + drawMenuLargeOrSmall( globals, globals->menuList ); #endif } /* changeMenuForFocus */ @@ -989,18 +1199,29 @@ sendOnClose( XWStreamCtxt* stream, void* closure ) } /* sendOnClose */ #endif -static XP_Bool -handleKeyEvent( CursesAppGlobals* globals, MenuList* list, char ch ) +static CursesMenuHandler +getHandlerForKey( MenuList* list, char ch ) { + CursesMenuHandler handler = NULL; while ( list->handler != NULL ) { if ( list->key == ch ) { - if ( (*list->handler)(globals) ) { - return XP_TRUE; - } + handler = list->handler; + break; } ++list; } - return XP_FALSE; + return handler; +} + +static XP_Bool +handleKeyEvent( CursesAppGlobals* globals, MenuList* list, char ch ) +{ + CursesMenuHandler handler = getHandlerForKey( list, ch ); + XP_Bool result = XP_FALSE; + if ( !!handler ) { + result = (*handler)(globals); + } + return result; } /* handleKeyEvent */ static XP_Bool @@ -1015,59 +1236,110 @@ passKeyToBoard( CursesAppGlobals* globals, char ch ) return handled; } /* passKeyToBoard */ +static void +positionSizeStuff( CursesAppGlobals* globals, int width, int height ) +{ + XP_U16 cellWidth, cellHt, scoreLeft, scoreWidth; + BoardCtxt* board = globals->cGlobals.game.board; + int remWidth = width; + + board_setPos( board, BOARD_OFFSET, BOARD_OFFSET, XP_FALSE ); + cellWidth = CURSES_CELL_HT; + cellHt = CURSES_CELL_WIDTH; + board_setScale( board, cellWidth, cellHt ); + scoreLeft = (cellWidth * MAX_COLS);// + BOARD_SCORE_PADDING; + remWidth -= cellWidth * MAX_COLS; + + /* If the scoreboard will right of the board, put it there. Otherwise try + to fit it below the boards. */ + int tileWidth = 3; + int trayWidth = (tileWidth*MAX_TRAY_TILES); + int trayLeft = scoreLeft; + int trayTop; + int trayHt = 4; + if ( trayWidth < remWidth ) { + trayLeft += XP_MIN(remWidth - trayWidth, BOARD_SCORE_PADDING ); + trayTop = 8; + } else { + trayLeft = BOARD_OFFSET; + trayTop = BOARD_OFFSET + (cellHt * MAX_ROWS); + if ( trayTop + trayHt > height ) { + trayHt = height - trayTop; + } + } + board_setTrayLoc( board, trayLeft, trayTop, (3*MAX_TRAY_TILES)+1, + trayHt, 1 ); + + scoreWidth = remWidth; + if ( scoreWidth > 45 ) { + scoreWidth = 45; + scoreLeft += (remWidth - scoreWidth) / 2; + } + board_setScoreboardLoc( board, scoreLeft, 1, + scoreWidth, 5, /*4 players + rem*/ XP_FALSE ); + + /* no divider -- yet */ + /* board_setTrayVisible( globals.board, XP_TRUE, XP_FALSE ); */ + + board_invalAll( board ); +} /* positionSizeStuff */ + void cursesmain( XP_Bool isServer, LaunchParams* params ) { int piperesult; DictionaryCtxt* dict; XP_U16 gameID; - XP_U16 colWidth, scoreLeft; + int width, height; - memset( &globals, 0, sizeof(globals) ); + memset( &g_globals, 0, sizeof(g_globals) ); - globals.amServer = isServer; - globals.cGlobals.params = params; + g_globals.amServer = isServer; + g_globals.cGlobals.params = params; #ifdef XWFEATURE_RELAY - globals.cGlobals.socket = -1; + g_globals.cGlobals.socket = -1; #endif - globals.cGlobals.socketChanged = curses_socket_changed; - globals.cGlobals.socketChangedClosure = &globals; - globals.cGlobals.addAcceptor = curses_socket_acceptor; + g_globals.cGlobals.socketChanged = curses_socket_changed; + g_globals.cGlobals.socketChangedClosure = &g_globals; + g_globals.cGlobals.addAcceptor = curses_socket_acceptor; - globals.cp.showBoardArrow = XP_TRUE; - globals.cp.showRobotScores = params->showRobotScores; + g_globals.cp.showBoardArrow = XP_TRUE; + g_globals.cp.showRobotScores = params->showRobotScores; dict = params->dict; - setupCursesUtilCallbacks( &globals, params->util ); + setupCursesUtilCallbacks( &g_globals, params->util ); #ifdef XWFEATURE_RELAY if ( params->conType == COMMS_CONN_RELAY ) { - globals.cGlobals.defaultServerName + g_globals.cGlobals.defaultServerName = params->connInfo.relay.relayName; } #endif - cursesListenOnSocket( &globals, 0 ); /* stdin */ + cursesListenOnSocket( &g_globals, 0 ); /* stdin */ - piperesult = pipe( globals.timepipe ); + piperesult = pipe( g_globals.timepipe ); XP_ASSERT( piperesult == 0 ); /* reader pipe */ - cursesListenOnSocket( &globals, globals.timepipe[0] ); + cursesListenOnSocket( &g_globals, g_globals.timepipe[0] ); signal( SIGWINCH, SIGWINCH_handler ); - initCurses( &globals ); - globals.draw = (struct CursesDrawCtx*)cursesDrawCtxtMake( globals.boardWin ); + initCurses( &g_globals ); + getmaxyx( g_globals.boardWin, height, width ); + + g_globals.draw = (struct CursesDrawCtx*) + cursesDrawCtxtMake( g_globals.boardWin ); - gameID = (XP_U16)util_getCurSeconds( globals.cGlobals.params->util ); - game_makeNewGame( MEMPOOL &globals.cGlobals.game, ¶ms->gi, - params->util, (DrawCtx*)globals.draw, - gameID, &globals.cp, LINUX_SEND, - IF_CH(linux_reset) &globals ); + gameID = (XP_U16)util_getCurSeconds( g_globals.cGlobals.params->util ); + game_makeNewGame( MEMPOOL &g_globals.cGlobals.game, ¶ms->gi, + params->util, (DrawCtx*)g_globals.draw, + gameID, &g_globals.cp, LINUX_SEND, + IF_CH(linux_reset) &g_globals ); #ifndef XWFEATURE_STANDALONE_ONLY - if ( globals.cGlobals.game.comms ) { + if ( g_globals.cGlobals.game.comms ) { CommsAddrRec addr; if ( 0 ) { @@ -1090,60 +1362,52 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) sizeof(params->connInfo.bt.hostAddr) ); # endif } - comms_setAddr( globals.cGlobals.game.comms, &addr ); + comms_setAddr( g_globals.cGlobals.game.comms, &addr ); } #endif - model_setDictionary( globals.cGlobals.game.model, params->dict ); + model_setDictionary( g_globals.cGlobals.game.model, params->dict ); - board_setPos( globals.cGlobals.game.board, 1, 1, XP_FALSE ); - colWidth = 2; - board_setScale( globals.cGlobals.game.board, colWidth, 1 ); - scoreLeft = (colWidth * MAX_COLS) + 3; - board_setScoreboardLoc( globals.cGlobals.game.board, - scoreLeft, 1, - 45, 5, /*4 players + rem*/ XP_FALSE ); - - board_setTrayLoc( globals.cGlobals.game.board, - scoreLeft, 8, (3*MAX_TRAY_TILES)+1, - 4, 1 ); - /* no divider -- yet */ - /* board_setTrayVisible( globals.board, XP_TRUE, XP_FALSE ); */ - - board_invalAll( globals.cGlobals.game.board ); + positionSizeStuff( &g_globals, width, height ); #ifndef XWFEATURE_STANDALONE_ONLY /* send any events that need to get off before the event loop begins */ if ( !isServer ) { if ( 1 /* stream_open( params->info.clientInfo.stream ) */) { - server_initClientConnection( globals.cGlobals.game.server, + server_initClientConnection( g_globals.cGlobals.game.server, mem_stream_make( MEMPOOL params->vtMgr, - &globals, + &g_globals, (XP_PlayerAddr)0, sendOnClose ) ); } else { - cursesUserError( &globals, "Unable to open connection to server"); + cursesUserError( &g_globals, "Unable to open connection to server"); exit( 0 ); } } #endif - server_do( globals.cGlobals.game.server ); + server_do( g_globals.cGlobals.game.server ); - globals.menuList = boardMenuList; - drawMenuFromList( &globals, boardMenuList ); - board_draw( globals.cGlobals.game.board ); + g_globals.menuList = g_boardMenuList; + drawMenuLargeOrSmall( &g_globals, g_boardMenuList ); + board_draw( g_globals.cGlobals.game.board ); - while ( !globals.timeToExit ) { + while ( !g_globals.timeToExit ) { int ch; - if ( blocking_gotEvent( &globals, &ch ) - && (handleKeyEvent( &globals, globals.menuList, ch ) - || handleKeyEvent( &globals, sharedMenuList, ch ) - || passKeyToBoard( &globals, ch ) ) ) { - if ( globals.doDraw ) { - board_draw( globals.cGlobals.game.board ); - globals.doDraw = XP_FALSE; + if ( blocking_gotEvent( &g_globals, &ch ) ) { + remapKey( &ch ); + if ( +#ifdef CURSES_SMALL_SCREEN + handleKeyEvent( &g_globals, g_rootMenuListShow, ch ) || +#endif + handleKeyEvent( &g_globals, g_globals.menuList, ch ) + || handleKeyEvent( &g_globals, g_sharedMenuList, ch ) + || passKeyToBoard( &g_globals, ch ) ) { + if ( g_globals.doDraw ) { + board_draw( g_globals.cGlobals.game.board ); + g_globals.doDraw = XP_FALSE; + } } } }