/* * The callbacks for the X interface for 3Dc */ /* 3Dc, a game of 3-Dimensional Chess Copyright (C) 1995 Paul Hicks 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., 675 Mass Ave, Cambridge, MA 02139, USA. E-Mail: paulh@euristix.ie */ #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <X11/X.h> #include <X11/Intrinsic.h> #include <X11/StringDefs.h> #include <X11/cursorfont.h> #include <X11/Shell.h> #include <X11/Xaw/Form.h> #include <X11/Xaw/Viewport.h> #include <X11/Xaw/Command.h> #include <X11/Xaw/AsciiText.h> #include <X11/Xaw/List.h> #include "DrawingA.h" #include <X11/xpm.h> #include "local.h" #include "machine.h" #include "3Dc.h" Local GfxInfo * Widget2GfxInfo(Widget w) { if ((secondGFX != NULL) && (XtDisplay(secondGFX->mainWindow) == XtDisplay(w))) return secondGFX; return firstGFX; } /* Draw one level of the board */ Global void DrawBoard(Widget w, XtPointer client, XtPointer call) { unsigned curX, curY, minX = 0, minY = 0, maxX = 7, maxY = 7; Level boardNum; XEvent *event; GfxInfo *gfx; Dimension width, height; gfx = Widget2GfxInfo(w); for (boardNum = 0; boardNum < LEVELS; ++boardNum) { if (w == gfx->board[boardNum]) break; } if (!CHECK(boardNum != LEVELS)) return; width = gfx->width [boardNum] / FILES; height = gfx->height[boardNum] / RANKS; /* call is NULL if called manually */ if ( call != NULL ) { event = ((XawDrawingAreaCallbackStruct *)call)->event; if ( event->type == ConfigureNotify ) { gfx->width [boardNum] = event->xconfigure.width; gfx->height[boardNum] = event->xconfigure.height; } width = gfx->width [boardNum] / FILES; height = gfx->height[boardNum] / RANKS; /* If call is null, then the function is called by us * and the whole board (ignoring expose-rects) is to be redrawn. * Similarly, the whole board is redrawn when resizing. */ if (event->type == Expose) { minX = event->xexpose.x / width; minY = event->xexpose.y / height; maxX = MIN(FILES-1, 1+ minX + (event->xexpose.width / width)); maxY = MIN(RANKS-1, 1+ minY + (event->xexpose.height / height)); } } XSetForeground(XtDisplay(w), gfx->gc, gfx->greyPixel); for (curY = minY; curY <= maxY; ++curY) { for (curX = minX; curX <= maxX; ++curX) { if ( Board[boardNum][curY][curX] == NULL ) { XRectangle rect = {0, 0, XPM_SIZE, XPM_SIZE}; rect.width = width; rect.height = height; XSetClipRectangles(XtDisplay(gfx->mainWindow), gfx->gc, SQ_POS_X(gfx, boardNum, curX), SQ_POS_Y(gfx, boardNum, curY), &rect, 1, Unsorted); /* White in the bottom right... */ if ( ((curX + curY) %2) == 1 ) XFillRectangle(XtDisplay(w), XtWindow(w), gfx->gc, SQ_POS_X(gfx, boardNum, curX), SQ_POS_Y(gfx, boardNum, curY), width, height); else XClearArea(XtDisplay(w), XtWindow(w), SQ_POS_X(gfx, boardNum, curX), SQ_POS_Y(gfx, boardNum, curY), width, height, FALSE); } else /* if (Board[boardNum][curY][curX] != NULL) */ { PieceDisplay(Board[boardNum][curY][curX], TRUE); } } } return; } /* If you want to use the cursor font, define FONTCURSOR. It's not * really worth it, 'cos the pixmap cursors look better */ /* #define FONTCURSOR */ Global void MouseInput(Widget w, XtPointer client, XtPointer call) { XADCS *data; Piece *piece; int moved; Level boardNum; int xWinRel, yWinRel, dummy1, dummy2, dummy3; Window win, dummy; XColor fore, back; Local Cursor cursor = 0; Pixmap pixmap; GfxInfo *gfx; Local Coord src; #ifdef FONTCURSOR if (!cursor) cursor = XCreateFontCursor(XtDisplay(w), XC_exchange); #endif /* FONTCURSOR */ gfx = Widget2GfxInfo(w); data = (XADCS *)call; if (data->event->type == ButtonPress) { /* Getting boardNum is easy for ButtonPress */ for (boardNum = 0; (w != gfx->board[boardNum]) && (boardNum < LEVELS); ++boardNum); if (boardNum == LEVELS) return; /* Error! */ src.xFile = data->event->xbutton.x / (gfx->width [boardNum]/FILES); src.yRank = data->event->xbutton.y / (gfx->height[boardNum]/RANKS); src.zLevel = boardNum; if (Board[src.zLevel][src.yRank][src.xFile] == NULL) { src.zLevel = LEVELS; return; } #ifndef FONTCURSOR /* Define the colours */ if (Board[src.zLevel][src.yRank][src.xFile]->bwSide == BLACK) { fore.pixel = gfx->whitePixel; back.pixel = gfx->blackPixel; } else { fore.pixel = gfx->blackPixel; back.pixel = gfx->whitePixel; } XQueryColor(XtDisplay(w), DefaultColormapOfScreen(XtScreen(w)), &fore); XQueryColor(XtDisplay(w), DefaultColormapOfScreen(XtScreen(w)), &back); /* Create the pixmap */ pixmap = XCreatePixmap(XtDisplay(w), XtWindow(w), XPM_SIZE, XPM_SIZE, 1); /* Create the GC */ if (gfx->monoGC == NULL) { gfx->monoGC = XCreateGC(XtDisplay(w), pixmap, 0, NULL); XSetFunction(XtDisplay(w), gfx->monoGC, GXcopy); } XCopyPlane(XtDisplay(w), gfx->face[WHITE] [Board[src.zLevel] [src.yRank] [src.xFile]->nName], pixmap, gfx->monoGC, 0, 0, XPM_SIZE, XPM_SIZE, 0, 0, 1); /* Now we can create the cursor */ cursor = XCreatePixmapCursor(XtDisplay(w), pixmap, gfx->mask[Board[src.zLevel] [src.yRank] [src.xFile]->nName], &fore, &back, 15, 15); XFreePixmap(XtDisplay(w), pixmap); #endif /* FONTCURSOR */ /* Now let's change the cursor */ for (boardNum = 0; boardNum < LEVELS; ++boardNum) { XDefineCursor(XtDisplay(w), XtWindow(w), cursor); } } else if (data->event->type == ButtonRelease) { /* First of all - if we didn't pick up a piece, there's no * point in continuing */ if (src.zLevel == LEVELS) return; for (boardNum = 0; boardNum < LEVELS; ++boardNum) { /* Now we have to restore the cursor */ XUndefineCursor(XtDisplay(w), XtWindow(w)); } /* For ButtonRelease, we have to take care of the release being * in another board */ for (boardNum = 0; boardNum < LEVELS; ++boardNum) { /* Before I added in pixmap cursors, I needed only run * XQueryPointer once, but now I must do it once per * board. Even if I define FONTCURSOR. Presumably * I changed something about XQueryPointer and searching * through many levels of children, but I can't find what * it was. O well. */ XQueryPointer(XtDisplay(w), XtWindow(XtParent(gfx->board[boardNum])), &dummy, &win, &dummy1, &dummy2, &xWinRel, &yWinRel, &dummy3); if (win == XtWindow(gfx->board[boardNum])) break; } #ifndef FONTCURSOR XFreeCursor(XtDisplay(w), cursor); cursor = 0; #endif if (boardNum == LEVELS) { Err3Dc(gfx, "Can't move outside boards", TRUE); return; } /* First - ignore clicks on the one square */ if ((src.xFile == (xWinRel / (gfx->width [boardNum]/FILES))) && (src.yRank == (yWinRel / (gfx->height[boardNum]/RANKS))) && (src.zLevel == boardNum)) { Err3Dc(gfx, NULL, FALSE); return; } piece = Board[src.zLevel][src.yRank][src.xFile]; if ((!piece) || (!piece->bVisible)) { Err3Dc(gfx, "There's no piece there!", FALSE); return; } else if ( (piece->bwSide == Computer()) || ( (secondGFX != NULL) && ( ((gfx == firstGFX) && (piece->bwSide == BLACK)) || (((gfx == secondGFX) && (piece->bwSide == WHITE))) ) ) ) { Err3Dc(gfx, "That's not your piece!", FALSE); return; } else if (piece->bwSide != bwToMove) { Err3Dc(gfx, "It's not your go yet!", FALSE); return; } moved = PieceMove(piece, xWinRel / (gfx->width [boardNum]/FILES), yWinRel / (gfx->height[boardNum]/RANKS), boardNum); if (moved) { PieceDisplay(piece, TRUE); UpdateMuster(piece->bwSide, piece->nName, TRUE); PrintMove( StackPeek( MoveStack, 0 ) ); bwToMove = (bwToMove == WHITE ? BLACK : WHITE); } /* End "this was a legal move" */ else Err3Dc(gfx, "That piece %s.", TRUE); } /* End "this was a buttonrelease" */ } Global void ResignGame(Widget w, XtPointer client, XtPointer call) { Widget dialog, form, cont, restart, quit; XtPopdownID popMeDown; Dimension x, y; GfxInfo *gfx; PauseGame(); /* Suspend thinking */ gfx = Widget2GfxInfo(w); /* This is one smart function. XtCreatePopupShell will create * an unmanaged widget if it doesn't already exist, or do nothing * otherwise. In combination with XtCallbackPopdown, this makes * dialogs a doddle (kudos to the Xt people - everything else handy * about programming in X seems to be from Motif, this is nice for * a change). * * Addendum: * Hmm. It appears that I spoke hastily. In my version of Xt, * XtCallbackPopdown appears to do nothing; at least, I can't get * it to do anything. So I've written cancelDialog, which does * exactly the same thing (well, actually it works, you know, * pops the dialog down :). */ XtVaGetValues(gfx->mainWindow, XtNx, &x, XtNy, &y, NULL); dialog = XtVaCreatePopupShell("Resign Game?", transientShellWidgetClass, gfx->mainWindow, XtNx, x + 50, XtNy, y + 50, NULL); form = XtVaCreateManagedWidget("form", formWidgetClass, dialog, NULL); cont = XtVaCreateManagedWidget("Continue", commandWidgetClass, form, XtNlabel, "Continue", NULL); restart = XtVaCreateManagedWidget("Restart", commandWidgetClass, form, XtNlabel, "Restart", XtNfromHoriz, cont, NULL); quit = XtVaCreateManagedWidget("Quit", commandWidgetClass, form, XtNlabel, "Quit", XtNfromHoriz, restart, NULL); popMeDown = (XtPopdownID)XtMalloc(sizeof(XtPopdownIDRec)); popMeDown->shell_widget = dialog; popMeDown->enable_widget = gfx->mainWindow; XtAddCallback(cont, XtNcallback, CancelDialog, popMeDown); XtAddCallback(restart, XtNcallback, Restart3Dc, popMeDown); XtAddCallback(quit, XtNcallback, Quit3Dc, NULL); XtManageChild(dialog); return; } Global void UndoMove(Widget w, XtPointer client, XtPointer call) { GfxInfo *gfx; gfx = Widget2GfxInfo(w); /* This fearsome conditional disallows undoing any but your * own moves. I don't know if it should be in or not; * thus it is conditionally compiled */ #ifndef UNDO_ANY_MOVE if ( ((Computer() != NOCOL) && (bwToMove != Computer())) || ( (secondGFX != NULL) && ( ((gfx == secondGFX) && (bwToMove == BLACK)) || ((gfx == firstGFX) && (bwToMove == WHITE)) ) ) ) { Err3Dc( gfx, "You can only undo your own moves!", FALSE ); return; } #endif /* UNDO_ANY_MOVE */ if (PieceUndo() == FALSE) { Err3Dc(gfx, "Nothing to undo!", FALSE); return; } /* Because we don't know what type was done, update them all */ DrawMuster(gfx->muster, NULL, NULL); if (secondGFX != NULL) DrawMuster(secondGFX->muster, NULL, NULL); /* TODO: * pop up dialog asking for permission to undo * in multi-display games */ /* Undo means that the same guy goes again... */ bwToMove = (bwToMove == WHITE ? BLACK : WHITE); return; } /* Draw the muster window */ Global void DrawMuster(Widget w, XtPointer client, XtPointer call) { Dimension musterWidth, musterHeight; int leftX, curX, curY, x; GfxInfo *gfx; /* On which display are we working? */ gfx = Widget2GfxInfo( w ); XtVaGetValues(w, XtNheight, &musterHeight, XtNwidth, &musterWidth, NULL); /* Resize the remark label */ XtVaSetValues(gfx->remark, XtNwidth, musterWidth, NULL); /* Factor in centring */ leftX = curX = (musterWidth % 5) / 2 + ((musterWidth / 5) - XPM_SIZE) / 2; curY = (musterHeight % 5) / 2 + ((musterHeight / 5) - XPM_SIZE) / 2; XSetForeground(XtDisplay(w), gfx->gc, gfx->blackPixel); /* Draw white royalty */ for (x = 0; x < 5; ++x) { XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY); XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[x]); XCopyArea(XtDisplay(w), gfx->face[WHITE][x], XtWindow(w), gfx->gc, 0, 0, XPM_SIZE, XPM_SIZE, curX, curY); UpdateMuster(WHITE, x, FALSE); curX += musterWidth / 5; } curX = leftX; curY += musterHeight / 5; /* Draw white nobility */ for (x = 0; x < 5; ++x) { XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY); XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[x+5]); XCopyArea(XtDisplay(w), gfx->face[WHITE][x+5], XtWindow(w), gfx->gc, 0, 0, XPM_SIZE, XPM_SIZE, curX, curY); UpdateMuster(WHITE, x+5, FALSE); curX += musterWidth / 5; } curX = ((musterWidth % 2) / 2) + (((musterWidth / 2) - XPM_SIZE) / 2); curY += musterHeight / 5; /* Draw pawns */ for (x = 0; x < 2; ++x) { if (curX < 0) curX = 0; XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY); XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[pawn]); XCopyArea(XtDisplay(w), gfx->face[x][pawn], XtWindow(w), gfx->gc, 0, 0, XPM_SIZE, XPM_SIZE, curX, curY); UpdateMuster(x, pawn, FALSE); curX += musterWidth / 2; } curX = leftX; curY += musterHeight / 5; /* Draw black nobility */ for (x = 0; x < 5; ++x) { XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY); XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[x+5]); XCopyArea(XtDisplay(w), gfx->face[BLACK][x+5], XtWindow(w), gfx->gc, 0, 0, XPM_SIZE, XPM_SIZE, curX, curY); UpdateMuster(BLACK, x+5, FALSE); curX += musterWidth / 5; } curX = leftX; curY += musterHeight / 5; /* Draw BLACK royalty */ for (x = 0; x < 5; ++x) { XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY); XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[x]); XCopyArea(XtDisplay(w), gfx->face[BLACK][x], XtWindow(w), gfx->gc, 0, 0, XPM_SIZE, XPM_SIZE, curX, curY); UpdateMuster(BLACK, x, FALSE); curX += musterWidth / 5; } return; } /* The promotion callback */ Global void PromotePiece(Widget w, XtPointer client, XtPointer call) { Piece *piece; piece = (Piece *)client; PieceDisplay(piece, FALSE); switch (((XawListReturnStruct *)call)->list_index) { case 0: piece->nName = queen; break; case 1: piece->nName = rook; break; case 2: piece->nName = bishop; break; case 3: piece->nName = knight; break; case 4: piece->nName = princess; break; case 5: piece->nName = galley; break; case 6: piece->nName = abbey; break; case 7: piece->nName = cannon; break; } PieceDisplay(piece, TRUE); UpdateMuster(piece->bwSide, piece->nName, TRUE); XtPopdown(XtParent(w)); XtDestroyWidget(XtParent(w)); ResumeGame(); /* Resume thinking */ return; } Global void Restart3Dc(Widget w, XtPointer client, XtPointer call) { int i; if (w) { XtPopdownID popMeDown; popMeDown = (XtPopdownID)client; XtPopdown(popMeDown->shell_widget); XtDestroyWidget(popMeDown->shell_widget); XtSetSensitive(popMeDown->enable_widget, TRUE); XtFree((char *)popMeDown); } XtSetSensitive(firstGFX->undo, TRUE); if ( secondGFX != NULL ) XtSetSensitive(secondGFX->undo, TRUE); /* Destroy all the pieces */ for (i = 0; i < PIECES; ++i) { PieceDelete(Muster[WHITE][i]); PieceDelete(Muster[BLACK][i]); } /* Free the move stack */ StackDelete(MoveStack); /* Recreate 3Dc, with pieces in the right places and an empty move stack */ Init3Dc(); bwToMove = WHITE; ResumeGame(); /* Refresh the screen */ Draw3DcBoard(); for (i = 0; i < TITLES; ++i) { UpdateMuster(WHITE, i, TRUE); UpdateMuster(BLACK, i, TRUE); } return; } Global void CancelDialog(Widget w, XtPointer client, XtPointer call) { XtPopdownID popMeDown; popMeDown = (XtPopdownID)client; XtPopdown(popMeDown->shell_widget); XtDestroyWidget(popMeDown->shell_widget); XtSetSensitive(popMeDown->enable_widget, TRUE); XtFree((char *)popMeDown); ResumeGame(); /* Most dialogs are modal, requiring the use of this line */ return; } Global void Quit3Dc(Widget w, XtPointer client, XtPointer call) { /* XCloseDisplay frees fonts, pixmaps, everything---cool! */ XtDestroyApplicationContext( XtWidgetToApplicationContext( firstGFX->mainWindow ) ); firstGFX->mainWindow = NULL; if (secondGFX != NULL) { XtDestroyApplicationContext( XtWidgetToApplicationContext( secondGFX->mainWindow ) ); } return; }