diff --git a/xwords4/common/dragdrpp.c b/xwords4/common/dragdrpp.c new file mode 100644 index 000000000..82c035f7a --- /dev/null +++ b/xwords4/common/dragdrpp.c @@ -0,0 +1,348 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "cd ../linux && make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997 - 2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef CPLUS +extern "C" { +#endif + +#include "dragdrpp.h" + +static XP_Bool dragDropContinueImpl( BoardCtxt* board, XP_U16 xx, XP_U16 yy, + BoardObjectType* onWhichP ); + +XP_Bool +dragDropInProgress( const BoardCtxt* board ) +{ + const DragState* ds = &board->dragState; +/* LOG_RETURNF( "%d", ds->dragInProgress ); */ + return ds->dragInProgress; +} + +static XP_Bool +ddStartBoard( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + DragState* ds = &board->dragState; + XP_Bool ignore, found; + XP_U16 modelc, modelr; + + ds->start.u.board.col = col; + ds->start.u.board.row = row; + + flipIf( board, col, row, &modelc, &modelr ); + + found = model_getTile( board->model, modelc, modelr, XP_TRUE, + board->selPlayer, &ds->tile, &ds->isBlank, + &ignore, &ignore ); + XP_ASSERT( found ); + + return XP_TRUE; +} /* ddStartBoard */ + +static XP_Bool +ddStartTray( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_Bool canDrag; + DragState* ds = &board->dragState; + + XP_Bool onDivider; + XP_S16 index = pointToTileIndex( board, x, y, &onDivider ); + canDrag = onDivider || index >= 0; + if ( canDrag ) { + XP_S16 selPlayer = board->selPlayer; + if ( onDivider ) { + ds->dividerOnly = XP_TRUE; + ds->start.u.tray.index = board->dividerLoc[selPlayer]; + } else { + Tile tile; + tile = model_getPlayerTile( board->model, selPlayer, index ); + ds->isBlank = + tile == dict_getBlankTile( model_getDictionary(board->model) ); + ds->tile = tile; + + ds->start.u.tray.index = index; + + /* during drag the moving tile is drawn as selected, so inval + currently selected tile. */ + board_invalTrayTiles( board, board->traySelBits[selPlayer] ); + } + } + + return canDrag; +} /* ddStartTray */ + +/* x and y, in board coordinates (not model, should the board be flipped), are + * already col,row (cells) in the board case, but real x/y (pixels) on the + * tray. +*/ +XP_Bool +dragDropStart( BoardCtxt* board, BoardObjectType obj, XP_U16 x, XP_U16 y ) +{ + XP_Bool result = XP_FALSE; + DragState* ds = &board->dragState; + XP_ASSERT( !dragDropInProgress(board) ); + XP_MEMSET( ds, 0, sizeof(*ds) ); + + ds->start.obj = obj; + + if ( OBJ_BOARD == obj ) { + result = ddStartBoard( board, x, y ); + } else if ( OBJ_TRAY == obj ) { + result = ddStartTray( board, x, y ); + } else { + XP_ASSERT(0); + } + + if ( result ) { + ds->cur = ds->start; + invalDragObj( board, &ds->start ); + ds->dragInProgress = XP_TRUE; + } + + return result; +} /* dragDropStart */ + +/* We're potentially dragging. If we're leaving one object, inval it. + * If we're entering another, inval it. Track where we are so we don't inval + * again. If we didn't really move, don't inval anything! + * + * Our overriding concern must be to preserve data integrity in the face of + * buggy OSes that may drop events (esp. MouseUp). So never own a tile: keep + * it attached either to the tray or the board. If the state of the board is + * to change as dragging happens, that's for the board (view) to display, not + * the model. So drawing has to be aware of drag-and-drop, but we don't + * change the model until the pen's actually lifted. If we never get the + * pen-up event at the worst the board's drawn incorrectly for a bit. + * + * Exception: since divider location is in the board rather than the model + * just change it every time we can. + */ +XP_Bool +dragDropContinue( BoardCtxt* board, XP_U16 xx, XP_U16 yy ) +{ + BoardObjectType ignore; + XP_ASSERT( dragDropInProgress(board) ); + + return dragDropContinueImpl( board, xx, yy, &ignore ); +} + +XP_Bool +dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* dragged ) +{ + DragState* ds = &board->dragState; + BoardObjectType newObj; + + XP_ASSERT( dragDropInProgress(board) ); + + (void)dragDropContinueImpl( board, xx, yy, &newObj ); + *dragged = ds->didMove; + + /* If we've dropped on something, put the tile there! Since we + don't remove it from its earlier location until it's dropped, + we need to specify where it's coming from. */ + if ( ds->dividerOnly ) { + board->dividerInvalid = XP_TRUE; + /* nothing to do */ + } else { + XP_U16 mod_startc, mod_startr; + + flipIf( board, ds->start.u.board.col, ds->start.u.board.row, + &mod_startc, &mod_startr ); + + if ( newObj == OBJ_TRAY ) { + if ( ds->start.obj == OBJ_BOARD ) { /* board->tray is pending */ + model_moveBoardToTray( board->model, board->selPlayer, + mod_startc, mod_startr, + ds->cur.u.tray.index ); + } else { + model_moveTileOnTray( board->model, board->selPlayer, + ds->start.u.tray.index, + ds->cur.u.tray.index ); + } + } else if ( newObj == OBJ_BOARD ) { + if ( !cellOccupied( board, ds->cur.u.board.col, + ds->cur.u.board.row, XP_TRUE ) ) { + if ( ds->start.obj == OBJ_TRAY ) { + /* moveTileToBoard flips its inputs */ + (void)moveTileToBoard( board, ds->cur.u.board.col, + ds->cur.u.board.row, + ds->start.u.tray.index, EMPTY_TILE ); + } else if ( ds->start.obj == OBJ_BOARD ) { + XP_U16 mod_curc, mod_curr; + flipIf( board, ds->cur.u.board.col, ds->cur.u.board.row, + &mod_curc, &mod_curr ); + model_moveTileOnBoard( board->model, board->selPlayer, + mod_startc, mod_startr, mod_curc, + mod_curr ); + /* inval points tile in case score changed */ + board_invalTrayTiles( board, 1 << (MAX_TRAY_TILES-1) ); + } + } + } else { + /* We're returning it to start, so will be re-inserted in tray */ + if ( OBJ_TRAY == ds->start.obj ) { + invalTrayTilesAbove( board, ds->start.u.tray.index ); + } + } + + /* These may change appearance, e.g. from big tile to dropped cell. */ + invalDragObj( board, &ds->cur ); + invalDragObj( board, &ds->start ); + } + ds->dragInProgress = XP_FALSE; + + return XP_TRUE; +} /* dragDropEnd */ + +XP_Bool +dragDropGetBoardTile( const BoardCtxt* board, XP_U16* col, XP_U16* row ) +{ + const DragState* ds = &board->dragState; + XP_Bool found = !ds->dividerOnly && ds->cur.obj == OBJ_BOARD; + if ( found ) { + *col = ds->cur.u.board.col; + *row = ds->cur.u.board.row; + } + return found; +} + +XP_Bool +dragDropIsBeingDragged( const BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool* isOrigin ) +{ + const DragState* ds = &board->dragState; + XP_Bool result = dragDropInProgress( board ) && !ds->dividerOnly + && ds->cur.obj == OBJ_BOARD; + if ( result ) { + const DragState* ds = &board->dragState; + if ( (ds->cur.obj == OBJ_BOARD) && (ds->cur.u.board.col == col) + && (ds->cur.u.board.row == row) ) { + *isOrigin = XP_FALSE; + } else if ( (ds->start.obj == OBJ_BOARD) + && (ds->start.u.board.col == col) + && (ds->start.u.board.row == row) ) { + *isOrigin = XP_TRUE; + } else { + result = XP_FALSE; + } + + } + return result; +} + +void +dragDropGetTrayChanges( const BoardCtxt* board, XP_U16* rmvdIndx, + XP_U16* addedIndx ) +{ + const DragState* ds = &board->dragState; + *addedIndx = *rmvdIndx = MAX_TRAY_TILES; /* too big means ignore me */ + if ( dragDropInProgress( board ) && !ds->dividerOnly ) { + if ( OBJ_TRAY == ds->start.obj ) { + *rmvdIndx = ds->start.u.tray.index; + } + if ( OBJ_TRAY == ds->cur.obj ) { + *addedIndx = ds->cur.u.tray.index; + } + } +} + +XP_Bool +dragDropIsDividerDrag( const BoardCtxt* board ) +{ + return dragDropInProgress( board ) && board->dragState.dividerOnly; +} + +void +dragDropTileInfo( const BoardCtxt* board, Tile* tile, XP_Bool* isBlank ) +{ + const DragState* ds = &board->dragState; + XP_ASSERT( dragDropInProgress( board ) ); + XP_ASSERT( !ds->dividerOnly ); + XP_ASSERT ( OBJ_BOARD == ds->start.obj || OBJ_TRAY == ds->start.obj ); + *tile = ds->tile; + *isBlank = ds->isBlank; +} + +static XP_Bool +dragDropContinueImpl( BoardCtxt* board, XP_U16 xx, XP_U16 yy, + BoardObjectType* onWhichP ) +{ + XP_Bool moving = XP_FALSE; + DragObjInfo newInfo; + DragState* ds = &board->dragState; + + if ( !pointOnSomething( board, xx, yy, &newInfo.obj ) ) { + newInfo.obj = OBJ_NONE; + } + *onWhichP = newInfo.obj; + + if ( ds->dividerOnly ) { + if ( OBJ_NONE != newInfo.obj ) { + XP_U16 newloc; + XP_U16 scale = board->trayScaleH; + xx -= board->trayBounds.left; + newloc = xx / scale; + if ( (xx % scale) > (scale/2)) { + ++newloc; + } + moving = dividerMoved( board, newloc ); + } + } else { + if ( newInfo.obj == OBJ_BOARD ) { + (void)coordToCell( board, xx, yy, &newInfo.u.board.col, + &newInfo.u.board.row ); + moving = (OBJ_TRAY == ds->cur.obj) + || (newInfo.u.board.col != ds->cur.u.board.col) + || (newInfo.u.board.row != ds->cur.u.board.row); + } else if ( newInfo.obj == OBJ_TRAY ) { + XP_Bool onDivider; + XP_S16 index = pointToTileIndex( board, xx, yy, &onDivider ); + if ( !onDivider ) { + if ( index < 0 ) { /* negative means onto empty part of tray. + Force left. */ + index = model_getNumTilesInTray( board->model, + board->selPlayer ); + if ( OBJ_TRAY == ds->start.obj ) { + --index; /* dragging right into space */ + } + } + moving = (OBJ_BOARD == ds->cur.obj) + || (index != ds->cur.u.tray.index); + if ( moving ) { + newInfo.u.tray.index = index; + } + } + } + + if ( moving ) { + invalDragObjRange( board, &ds->cur, &newInfo ); + XP_MEMCPY( &ds->cur, &newInfo, sizeof(ds->cur) ); + } + } + + if ( moving ) { + ds->didMove = XP_TRUE; + } + + return moving; +} /* dragDropContinueImpl */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/dragdrpp.h b/xwords4/common/dragdrpp.h new file mode 100644 index 000000000..31d5e8bbd --- /dev/null +++ b/xwords4/common/dragdrpp.h @@ -0,0 +1,58 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DRAGDRPP_H_ +#define _DRAGDRPP_H_ + + +#include "boardp.h" + + +#ifdef CPLUS +extern "C" { +#endif + +XP_Bool dragDropInProgress( const BoardCtxt* board ); + +/* dragDropStart + * Pass col,row for board, but raw x,y for tray. Yeah, sucks. + */ +XP_Bool dragDropStart( BoardCtxt* board, BoardObjectType obj, + XP_U16 x, XP_U16 y ); +XP_Bool dragDropContinue( BoardCtxt* board, XP_U16 x, XP_U16 y ); +XP_Bool dragDropEnd( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_Bool* dragged ); +XP_Bool dragDropGetBoardTile( const BoardCtxt* board, XP_U16* col, XP_U16* row ); +XP_Bool dragDropIsBeingDragged( const BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool* isOrigin ); + +/* return locations (0-based indices from left) in tray where a drag has added + * and removed a tile. Index larger than MAX_TRAY_TILES means invalid: don't + * use. + */ +void dragDropGetTrayChanges( const BoardCtxt* board, XP_U16* rmvdIndx, + XP_U16* addedIndx ); +XP_Bool dragDropIsDividerDrag( const BoardCtxt* board ); + +void dragDropTileInfo( const BoardCtxt* board, Tile* tile, XP_Bool* isBlank ); + +#ifdef CPLUS +} +#endif + +#endif /* __DRAGDRPP_H_ */