From 2aa96648847d416a300fb06b5df7fa85ddd074d9 Mon Sep 17 00:00:00 2001
From: ehouse <ehouse@0782aaa5-4710-0410-8820-a96bf9123855>
Date: Tue, 7 Nov 2006 05:46:44 +0000
Subject: [PATCH] When internal focus reaches edge of object, move it back up
 and onto the next object using a callback to the platform to determine what,
 if any, object is next.  Adjust curses platform to cooperate.  Works well. 
 Palm is next.

---
 xwords4/common/board.c     | 108 ++++++++++++++++++-------------------
 xwords4/common/boardp.h    |   1 +
 xwords4/common/tray.c      |  29 ++++------
 xwords4/common/util.h      |   9 ++++
 xwords4/linux/cursesmain.c |  93 ++++++++++++++++++--------------
 5 files changed, 127 insertions(+), 113 deletions(-)

diff --git a/xwords4/common/board.c b/xwords4/common/board.c
index 568ad8df4..16b89e64b 100644
--- a/xwords4/common/board.c
+++ b/xwords4/common/board.c
@@ -1,4 +1,4 @@
-/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
+E/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
 /* 
  * Copyright 1997 - 2002 by Eric House (xwords@eehouse.org).  All rights reserved.
  *
@@ -113,8 +113,7 @@ static XP_Bool advanceArrow( BoardCtxt* board );
 
 #ifdef KEY_SUPPORT
 static XP_Bool getArrow( BoardCtxt* board, XP_U16* col, XP_U16* row );
-static XP_Bool board_moveArrow( BoardCtxt* board, XP_Key cursorKey, 
-                                XP_Bool canCycle );
+static XP_Bool board_moveArrow( BoardCtxt* board, XP_Key cursorKey );
 
 static XP_Bool setArrowVisibleFor( BoardCtxt* board, XP_U16 player, 
                                    XP_Bool visible );
@@ -1921,6 +1920,9 @@ invalCell( BoardCtxt* board, XP_U16 col, XP_U16 row )
 {
     board->redrawFlags[row] |= 1 << col;
 
+    XP_ASSERT( col < MAX_ROWS );
+    XP_ASSERT( row < MAX_ROWS );
+
     /* if the trade window is up and this cell intersects it, set up to draw
        it again */
     if ( (board->trayVisState != TRAY_HIDDEN) && TRADE_IN_PROGRESS(board) ) {
@@ -1936,29 +1938,6 @@ invalCell( BoardCtxt* board, XP_U16 col, XP_U16 row )
     board->needsDrawing = XP_TRUE;
 } /* invalCell */
 
-#ifdef KEYBOARD_NAV
-static XP_Bool
-focusNext( BoardCtxt* board )
-{
-    BoardObjectType typ;
-    switch ( board->focussed ) {
-    case OBJ_SCORE:
-        typ = OBJ_BOARD;
-        break;
-    case OBJ_BOARD:
-        typ = OBJ_TRAY;
-        break;
-    case OBJ_TRAY:
-    case OBJ_NONE:
-        typ = OBJ_SCORE;
-        break;
-    }
-
-    board->focussed = typ;
-    return XP_TRUE;
-} /* focusNext */
-#endif
-
 #ifdef POINTER_SUPPORT
 static XP_Bool
 pointOnSomething( BoardCtxt* board, XP_U16 x, XP_U16 y, BoardObjectType* wp )
@@ -2738,6 +2717,10 @@ board_handleKey( BoardCtxt* board, XP_Key key )
             } else if ( board->focussed == OBJ_TRAY ) {
                 result = tray_moveCursor( board, key );
             }
+        } else {
+            invalFocusOwner( board );
+            shiftFocusUp( board, key );
+            result = XP_TRUE;
         }
         break;
 #endif
@@ -2751,12 +2734,7 @@ board_handleKey( BoardCtxt* board, XP_Key key )
 
 #ifdef KEYBOARD_NAV
     case XP_FOCUSCHANGE_KEY:
-        invalFocusOwner( board );
-        if ( board->focusHasDived ) {
-            board->focusHasDived = XP_FALSE; /* come back up */
-        } else if ( focusNext( board ) ) {
-            invalFocusOwner( board );
-        }
+        shiftFocusUp( board, XP_CURSOR_KEY_RIGHT );
         result = XP_TRUE;
         break;
 
@@ -2868,7 +2846,8 @@ board_focusChanged( BoardCtxt* board, BoardObjectType typ, XP_Bool gained )
             draw = invalFocusOwner( board ) || draw;
         }
         board->focussed = typ;
-        XP_LOGF( "%s: set focussed to %d", __FUNCTION__, (int)typ );
+        XP_LOGF( "%s: set focussed to %s", __FUNCTION__, 
+                 BoardObjectType_2str(typ) );
         draw = invalFocusOwner( board ) || draw;
         board->focusHasDived = XP_FALSE;
     } else {
@@ -2897,12 +2876,27 @@ board_toggle_arrowDir( BoardCtxt* board )
     }
 } /* board_toggle_cursorDir */
 
+void
+shiftFocusUp( BoardCtxt* board, XP_Key key )
+{
+    BoardObjectType next = OBJ_NONE;
+    util_notifyFocusChange( board->util, board->focussed, key, &next );
+
+    if ( board->focussed != next ) {
+        (void)board_focusChanged( board, board->focussed, XP_FALSE );
+
+        board->focusHasDived = XP_FALSE;
+
+        (void)board_focusChanged( board, next, XP_TRUE );
+    }
+}
+
 static XP_Bool
 moveScoreCursor( BoardCtxt* board, XP_Key key )
 {
     XP_Bool result = XP_TRUE;
     XP_U16 nPlayers = board->gi->nPlayers;
-    XP_U16 scoreCursorLoc = board->scoreCursorLoc + nPlayers;
+    XP_S16 scoreCursorLoc = board->scoreCursorLoc;
 
     switch ( key ) {
     case XP_CURSOR_KEY_DOWN:
@@ -2916,7 +2910,11 @@ moveScoreCursor( BoardCtxt* board, XP_Key key )
     default:
         result = XP_FALSE;
     }
-    board->scoreCursorLoc = scoreCursorLoc % nPlayers;
+    if ( scoreCursorLoc < 0 || scoreCursorLoc >= nPlayers ) {
+        shiftFocusUp( board, key );
+    } else {
+        board->scoreCursorLoc = scoreCursorLoc;
+    }
     board->scoreBoardInvalid = XP_TRUE;
 
     return result;
@@ -2931,17 +2929,16 @@ advanceArrow( BoardCtxt* board )
 	    
     XP_ASSERT( board->trayVisState == TRAY_REVEALED );
 
-    return board_moveArrow( board, key, XP_FALSE );
+    return board_moveArrow( board, key );
 } /* advanceArrow */
 
 static XP_Bool
-figureNextLoc( BoardCtxt* board, XP_Key cursorKey, XP_Bool canCycle, 
+figureNextLoc( BoardCtxt* board, XP_Key cursorKey, XP_Bool canShiftFocus, 
                XP_Bool avoidOccupied, XP_U16* colP, XP_U16* rowP )
 {
     XP_S16 max;
     XP_S16* useWhat;
     XP_S16 end = 0;
-    XP_U16 counter = 0;
     XP_S16 incr = 0;
     XP_U16 numCols, numRows;
     XP_Bool result = XP_FALSE;
@@ -2958,25 +2955,25 @@ figureNextLoc( BoardCtxt* board, XP_Key cursorKey, XP_Bool canCycle,
         case XP_CURSOR_KEY_DOWN:
             incr = 1;
             useWhat = (XP_S16*)rowP;
-            max = numRows;
+            max = numRows - 1;
             end = max;
             break;
         case XP_CURSOR_KEY_UP:
             incr = -1;
             useWhat = (XP_S16*)rowP;
-            max = numRows;
-            end = -1;
+            max = numRows - 1;
+            end = 0;
             break;
         case XP_CURSOR_KEY_LEFT:
             incr = -1;
             useWhat = (XP_S16*)colP;
-            max = numCols;
-            end = -1;
+            max = numCols - 1;
+            end = 0;
             break;
         case XP_CURSOR_KEY_RIGHT:
             incr = 1;
             useWhat = (XP_S16*)colP;
-            max = numCols;
+            max = numCols - 1;
             end = max;
             break;
         default:
@@ -2985,19 +2982,18 @@ figureNextLoc( BoardCtxt* board, XP_Key cursorKey, XP_Bool canCycle,
 
         XP_ASSERT( incr != 0 );
 
-        for ( counter = max; ; --counter ) {
-
-            *useWhat += incr;
-
-            if ( (counter == 0) || (!canCycle && (*useWhat == end)) ) {
+        for ( ; ; ) {
+            if ( *useWhat == end ) {
+                if ( canShiftFocus ) {
+                    shiftFocusUp( board, cursorKey );
+                    result = XP_TRUE;
+                }
                 break;
             }
-
-            *useWhat = (*useWhat + max) % max;
-
+            result = XP_TRUE;
+            *useWhat += incr;
             if ( !avoidOccupied 
-                 || !cellOccupied( board, *colP, *rowP, XP_TRUE ) ) {
-                result = XP_TRUE;
+                        || !cellOccupied( board, *colP, *rowP, XP_TRUE ) ) {
                 break;
             }
         }
@@ -3007,14 +3003,14 @@ figureNextLoc( BoardCtxt* board, XP_Key cursorKey, XP_Bool canCycle,
 } /* figureNextLoc */
 
 static XP_Bool
-board_moveArrow( BoardCtxt* board, XP_Key cursorKey, XP_Bool canCycle )
+board_moveArrow( BoardCtxt* board, XP_Key cursorKey )
 {
     XP_U16 col, row;
     XP_Bool changed;
 
     setArrowVisible( board, XP_TRUE );
     (void)getArrow( board, &col, &row );
-    changed = figureNextLoc( board, cursorKey, canCycle, XP_TRUE, &col, &row );
+    changed = figureNextLoc( board, cursorKey, XP_FALSE, XP_TRUE, &col, &row );
     if ( changed ) {
         (void)setArrow( board, col, row );
     }
diff --git a/xwords4/common/boardp.h b/xwords4/common/boardp.h
index c0c052ce3..78bee9803 100644
--- a/xwords4/common/boardp.h
+++ b/xwords4/common/boardp.h
@@ -199,6 +199,7 @@ XP_Bool rectsIntersect( XP_Rect* rect1, XP_Rect* rect2 );
 XP_Bool tray_moveCursor( BoardCtxt* board, XP_Key cursorKey );
 XP_Bool tray_keyAction( BoardCtxt* board );
 DrawFocusState dfsFor( BoardCtxt* board, BoardObjectType obj );
+void shiftFocusUp( BoardCtxt* board, XP_Key key );
 #else
 # define dfsFor( board, obj ) DFS_NONE
 #endif
diff --git a/xwords4/common/tray.c b/xwords4/common/tray.c
index e46d70db0..8478e0e6d 100644
--- a/xwords4/common/tray.c
+++ b/xwords4/common/tray.c
@@ -617,33 +617,26 @@ tray_moveCursor( BoardCtxt* board, XP_Key cursorKey )
 {
     XP_Bool result;
     XP_U16 selPlayer = board->selPlayer;
-    XP_U16 numTrayTiles = model_getNumTilesInTray( board->model, 
-                                                   selPlayer );
-    XP_U16 pos;
-    TileBit newSel;
-    TileBit oldSel = 1 << board->trayCursorLoc[selPlayer];
-
-    numTrayTiles = MAX_TRAY_TILES;
 
     if ( cursorKey == XP_CURSOR_KEY_UP ) {
         result = board_moveDivider( board, XP_FALSE );
     } else if ( cursorKey == XP_CURSOR_KEY_DOWN ) {
         result = board_moveDivider( board, XP_TRUE );
     } else {
-        pos = indexForBits( oldSel );
+        XP_S16 pos;
 
-        pos += numTrayTiles;  /* add what we'll mod by below: makes circular */
-        if ( cursorKey == XP_CURSOR_KEY_LEFT ) {
-            --pos;
-        } else if ( cursorKey == XP_CURSOR_KEY_RIGHT ) {
-            ++pos;
+        board_invalTrayTiles( board, 1 << board->trayCursorLoc[selPlayer] );
+
+        pos = board->trayCursorLoc[selPlayer];
+        pos += cursorKey == XP_CURSOR_KEY_RIGHT ? 1 : -1;
+        if ( pos < 0 || pos >= MAX_TRAY_TILES ) {
+            shiftFocusUp( board, cursorKey );
+        } else {
+            board->trayCursorLoc[selPlayer] = pos;
+            board_invalTrayTiles( board, 1 << pos );
         }
-        
-        pos %= numTrayTiles;
-        board->trayCursorLoc[selPlayer] = pos;
-        newSel = 1 << pos;
 
-        board_invalTrayTiles( board, newSel | oldSel );
+        board_invalTrayTiles( board, 1 << board->trayCursorLoc[selPlayer] );
         result = XP_TRUE;
     }
 
diff --git a/xwords4/common/util.h b/xwords4/common/util.h
index e6181794e..6583df8e0 100644
--- a/xwords4/common/util.h
+++ b/xwords4/common/util.h
@@ -155,6 +155,11 @@ typedef struct UtilVtable {
     void (*m_util_engineStarting)( XW_UtilCtxt* uc, XP_U16 nBlanks );
     void (*m_util_engineStopping)( XW_UtilCtxt* uc );
 #endif
+
+#ifdef KEYBOARD_NAV
+    void (*m_util_notifyFocusChange)( XW_UtilCtxt* uc, BoardObjectType cur,
+                                      XP_Key key, BoardObjectType* next );
+#endif
 } UtilVtable;
 
 
@@ -241,4 +246,8 @@ struct XW_UtilCtxt {
 # define util_engineStopping( uc )
 # endif
 
+# ifdef KEYBOARD_NAV
+# define util_notifyFocusChange( uc, c, k, n ) \
+    (uc)->vtable->m_util_notifyFocusChange((uc),(c),(k),(n))
+# endif
 #endif
diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c
index 2461b1533..aa93b6f76 100644
--- a/xwords4/linux/cursesmain.c
+++ b/xwords4/linux/cursesmain.c
@@ -1,4 +1,4 @@
-/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
+/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */
 /* 
  * Copyright 2000 by Eric House (xwords@eehouse.org).  All rights reserved.
  *
@@ -53,13 +53,20 @@
 #include "server.h"
 #include "memstream.h"
 #include "util.h"
+#include "dbgutil.h"
 
 #define MENU_WINDOW_HEIGHT 5	/* three lines plus borders */
 #define INFINITE_TIMEOUT -1
 
 CursesAppGlobals globals;	/* must be global b/c of SIGWINCH_handler */
 
-static void changeFocus( CursesAppGlobals* globals );
+static void changeMenuForFocus( CursesAppGlobals* globals, 
+                                BoardObjectType obj );
+static XP_Bool handleLeft( CursesAppGlobals* globals );
+static XP_Bool handleRight( CursesAppGlobals* globals );
+static XP_Bool handleUp( CursesAppGlobals* globals );
+static XP_Bool handleDown( CursesAppGlobals* globals );
+
 
 #ifdef MEM_DEBUG
 # define MEMPOOL params->util->mpool,
@@ -220,6 +227,30 @@ curses_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) )
     return XP_TRUE;
 } /* curses_util_engineProgressCallback */
 
+static void
+curses_util_notifyFocusChange( XW_UtilCtxt* uc, BoardObjectType cur,
+                              XP_Key key, BoardObjectType* nextP )
+{
+    BoardObjectType nxt;
+    CursesAppGlobals* globals;
+
+    XP_LOGF( "%s(%s)", __FUNCTION__, BoardObjectType_2str(cur) );
+    XP_Bool forward = key == XP_CURSOR_KEY_DOWN
+        || key == XP_CURSOR_KEY_RIGHT;
+    switch( cur ) {
+    case OBJ_SCORE: nxt = forward? OBJ_TRAY : OBJ_BOARD; break;
+    case OBJ_BOARD: nxt = forward? OBJ_SCORE : OBJ_TRAY; break;
+    case OBJ_TRAY: nxt = forward? OBJ_BOARD : OBJ_SCORE; break;
+    case OBJ_NONE: nxt = OBJ_BOARD;
+    }
+
+    globals = (CursesAppGlobals*)uc->closure;
+    changeMenuForFocus( globals, nxt );
+
+    *nextP = nxt;
+    XP_LOGF( "%s()=>%s", __FUNCTION__, BoardObjectType_2str(*nextP) );
+}
+
 #ifdef XWFEATURE_RELAY
 static void
 curses_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 when,
@@ -322,7 +353,6 @@ handleTab( CursesAppGlobals* globals )
 {
     globals->doDraw = board_handleKey( globals->cGlobals.game.board, 
                                        XP_FOCUSCHANGE_KEY );
-    changeFocus( globals );
     return XP_TRUE;
 } /* handleTab */
 
@@ -416,6 +446,14 @@ MenuList sharedMenuList[] = {
     { handleTab, "Change focus", "<tab>", '\t' },
     { handleRet, "Click/tap", "<ret>", '\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' },
@@ -436,22 +474,6 @@ handleLeft( CursesAppGlobals* globals )
     return XP_TRUE;
 } /* handleLeft */
 
-static XP_Bool
-handleDivLeft( CursesAppGlobals* globals )
-{
-    globals->doDraw = board_moveDivider( globals->cGlobals.game.board, 
-                                         XP_FALSE );
-    return XP_TRUE;
-} /* handleDivLeft */
-
-static XP_Bool
-handleDivRight( CursesAppGlobals* globals )
-{
-    globals->doDraw = board_moveDivider( globals->cGlobals.game.board, 
-                                         XP_TRUE );
-    return XP_TRUE;
-} /* handleDivRight */
-
 static XP_Bool
 handleRight( CursesAppGlobals* globals )
 {
@@ -478,31 +500,17 @@ handleDown( CursesAppGlobals* globals )
 #endif
 
 MenuList boardMenuList[] = {
-#ifdef KEYBOARD_NAV
-    { handleLeft, "Left", "H", 'H' },
-    { handleRight, "Right", "L", 'L' },
-    { handleUp, "Up", "J", 'J' },
-    { handleDown, "Down", "K", 'K' },
-#endif
     { NULL, NULL, NULL, '\0'}
 };
 
 MenuList scoreMenuList[] = {
 #ifdef KEYBOARD_NAV
-    { handleUp, "Up", "J", 'J' },
-    { handleDown, "Down", "K", 'K' },
 #endif
     { NULL, NULL, NULL, '\0'}
 };
 
 MenuList trayMenuList[] = {
-#ifdef KEYBOARD_NAV
-    { handleLeft, "Left", "H", 'H' },
-    { handleRight, "Right", "L", 'L' },
-    { handleDivLeft, "Div left", "{", '{' },
-    { handleDivRight, "Div right", "}", '}' },
-#endif
-    { handleJuggle, "Juggle", "J", 'J' },
+    { handleJuggle, "Juggle", "G", 'G' },
     { handleHide, "[un]hIde", "I", 'I' },
 
     { NULL, NULL, NULL, '\0'}
@@ -556,6 +564,9 @@ drawMenuFromList( CursesAppGlobals* globals, MenuList* menuList )
                 if ( !isShared ) {
                     done = XP_TRUE;
                     break;
+                } else if ( menuList->handler == NULL )  {
+                    done = XP_TRUE;
+                    break;
                 } else {
                     isShared = XP_FALSE;
                     entry = menuList;
@@ -563,6 +574,7 @@ drawMenuFromList( CursesAppGlobals* globals, MenuList* menuList )
                 }
             }
 
+            XP_ASSERT( nLines > 0 );
             if ( line % nLines == 0 ) {
                 line = 0;
                 col += maxKey + maxCmd + 2;
@@ -802,11 +814,9 @@ blocking_gotEvent( CursesAppGlobals* globals, int* ch )
 } /* blocking_gotEvent */
 
 static void
-changeFocus( CursesAppGlobals* globals )
+changeMenuForFocus( CursesAppGlobals* globals, BoardObjectType focussed )
 {
 #ifdef KEYBOARD_NAV
-    BoardObjectType focussed = 
-        board_getFocusOwner( globals->cGlobals.game.board );
     if ( focussed == OBJ_TRAY ) {
         globals->menuList = trayMenuList;
         drawMenuFromList( globals, trayMenuList );
@@ -820,7 +830,7 @@ changeFocus( CursesAppGlobals* globals )
         XP_ASSERT(0);
     }
 #endif
-} /* changeFocus */
+} /* changeMenuForFocus */
 
 #if 0
 static void
@@ -916,7 +926,12 @@ setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util )
     util->vtable->m_util_notifyGameOver = curses_util_notifyGameOver;
     util->vtable->m_util_hiliteCell = curses_util_hiliteCell;
     util->vtable->m_util_engineProgressCallback = 
-	curses_util_engineProgressCallback;
+        curses_util_engineProgressCallback;
+
+#ifdef KEYBOARD_NAV
+    util->vtable->m_util_notifyFocusChange = curses_util_notifyFocusChange;
+#endif
+
 #ifdef XWFEATURE_RELAY
     util->vtable->m_util_setTimer = curses_util_setTimer;
 #endif