warzone2100/lib/widget/editbox.c

791 lines
18 KiB
C
Raw Normal View History

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2007 Warzone Resurrection Project
Warzone 2100 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.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/** @file
* Functions for the edit box widget.
*/
#include <string.h>
#include "lib/framework/frame.h"
#include "widget.h"
#include "widgint.h"
#include "editbox.h"
#include "form.h"
// FIXME Direct iVis implementation include!
#include "lib/ivis_common/rendmode.h"
#include "lib/ivis_common/textdraw.h"
#include "scrap.h"
/* Pixel gap between edge of edit box and text */
#define WEDB_XGAP 4
#define WEDB_YGAP 2
/* Size of the overwrite cursor */
#define WEDB_CURSORSIZE 8
/* Whether the cursor blinks or not */
#define CURSOR_BLINK 1
/* The time the cursor blinks for */
#define WEDB_BLINKRATE 800
/* Number of characters to jump the edit box text when moving the cursor */
#define WEDB_CHARJUMP 6
/* Calculate how much of the start of a string can fit into the edit box */
static void fitStringStart(char *pBuffer, UDWORD boxWidth, UWORD *pCount, UWORD *pCharWidth);
/* Create an edit box widget data structure */
W_EDITBOX* editBoxCreate(const W_EDBINIT* psInit)
{
W_EDITBOX* psWidget;
if (psInit->style & ~(WEDB_PLAIN | WIDG_HIDDEN | WEDB_DISABLED))
{
ASSERT( false, "Unknown edit box style" );
return NULL;
}
/* Allocate the required memory */
psWidget = (W_EDITBOX *)malloc(sizeof(W_EDITBOX));
if (psWidget == NULL)
{
debug(LOG_ERROR, "editBoxCreate: Out of memory");
abort();
return NULL;
}
/* Initialise the structure */
psWidget->type = WIDG_EDITBOX;
psWidget->id = psInit->id;
psWidget->formID = psInit->formID;
psWidget->style = psInit->style;
psWidget->x = psInit->x;
psWidget->y = psInit->y;
psWidget->width = psInit->width;
psWidget->height = psInit->height;
psWidget->FontID = psInit->FontID;
if (psInit->pDisplay)
{
psWidget->display = psInit->pDisplay;
}
else
{
psWidget->display = editBoxDisplay;
}
psWidget->callback = psInit->pCallback;
psWidget->pUserData = psInit->pUserData;
psWidget->UserData = psInit->UserData;
psWidget->pBoxDisplay = psInit->pBoxDisplay;
psWidget->pFontDisplay = psInit->pFontDisplay;
psWidget->AudioCallback = WidgGetAudioCallback();
psWidget->HilightAudioID = WidgGetHilightAudioID();
psWidget->ClickedAudioID = WidgGetClickedAudioID();
if (psInit->pText)
{
sstrcpy(psWidget->aText, psInit->pText);
}
else
{
psWidget->aText[0] = '\0';
}
editBoxInitialise(psWidget);
init_scrap();
return psWidget;
}
/* Free the memory used by an edit box */
void editBoxFree(W_EDITBOX *psWidget)
{
free(psWidget);
}
/* Initialise an edit box widget */
void editBoxInitialise(W_EDITBOX *psWidget)
{
ASSERT( psWidget != NULL,
"editBoxInitialise: Invalid edit box pointer" );
psWidget->state = WEDBS_FIXED;
psWidget->printStart = 0;
iV_SetFont(psWidget->FontID);
fitStringStart(psWidget->aText, psWidget->width,
&psWidget->printChars, &psWidget->printWidth);
}
/* Insert a character into a text buffer */
static void insertChar(char *pBuffer, UDWORD *pPos, char ch)
{
char *pSrc, *pDest;
UDWORD len, count;
if (ch == '\0') return;
ASSERT( *pPos <= strlen(pBuffer),
"insertChar: Invalid insertion point" );
len = strlen(pBuffer);
if (len == WIDG_MAXSTR - 1)
{
/* Buffer is full */
return;
}
/* Move the end of the string up by one (including terminating \0) */
count = len - *pPos + 1;
pSrc = pBuffer + len;
pDest = pSrc + 1;
while (count--)
{
*pDest-- = *pSrc--;
}
/* Insert the character */
*pDest = ch;
/* Update the insertion point */
*pPos += 1;
}
/* Put a character into a text buffer overwriting any text under the cursor */
static void overwriteChar(char *pBuffer, UDWORD *pPos, char ch)
{
char *pDest;
UDWORD len;
if (ch == '\0') return;
ASSERT( *pPos <= strlen(pBuffer),
"insertChar: Invalid insertion point" );
len = strlen(pBuffer);
if (len == WIDG_MAXSTR - 1)
{
/* Buffer is full */
return;
}
/* Store the character */
pDest = pBuffer + *pPos;
*pDest = ch;
if (*pPos == len)
{
/* At the end of the string, move the \0 up one */
*(pDest + 1) = '\0';
}
/* Update the insertion point */
*pPos += 1;
}
/* Put a character into a text buffer overwriting any text under the cursor */
static void putSelection(char *pBuffer, UDWORD *pPos)
{
static char* scrap = NULL;
int scraplen;
get_scrap(T('T','E','X','T'), &scraplen, &scrap);
if (scraplen > 0 && scraplen < WIDG_MAXSTR-2)
{
strlcpy(pBuffer, scrap, scraplen);
*pPos = scraplen - 1;
}
}
/* Delete a character to the left of the position */
static void delCharLeft(char *pBuffer, UDWORD *pPos)
{
char *pSrc, *pDest;
UDWORD len, count;
ASSERT(*pPos <= strlen(pBuffer), "Invalid insertion point");
/* Can't delete if we are at the start of the string */
if (*pPos == 0)
{
return;
}
len = strlen(pBuffer);
/* Move the end of the string down by one */
count = len - *pPos + 1;
pSrc = pBuffer + *pPos;
pDest = pSrc - 1;
while (count--)
{
*pDest++ = *pSrc ++;
}
/* Update the insertion point */
*pPos -= 1;
}
/* Delete a character to the right of the position */
static void delCharRight(char *pBuffer, UDWORD *pPos)
{
char *pSrc, *pDest;
UDWORD len, count;
ASSERT(*pPos <= strlen(pBuffer), "Invalid insertion point" );
len = strlen(pBuffer);
/* Can't delete if we are at the end of the string */
if (*pPos == len)
{
return;
}
/* Move the end of the string down by one */
count = len - *pPos;
pDest = pBuffer + *pPos;
pSrc = pDest + 1;
while (count--)
{
*pDest++ = *pSrc ++;
}
}
/* Calculate how much of the start of a string can fit into the edit box */
static void fitStringStart(char *pBuffer, UDWORD boxWidth, UWORD *pCount, UWORD *pCharWidth)
{
UDWORD len;
UWORD printWidth, printChars, width;
char *pCurr;
len = strlen(pBuffer);
printWidth = 0;
printChars = 0;
pCurr = pBuffer;
/* Find the number of characters that will fit in boxWidth */
while (printChars < len)
{
width = (UWORD)(printWidth + iV_GetCharWidth(*pCurr));
if (width > boxWidth - WEDB_XGAP*2)
{
/* We've got as many characters as will fit in the box */
break;
}
printWidth = width;
printChars += 1;
pCurr += 1;
}
/* Return the number of characters and their width */
*pCount = printChars;
*pCharWidth = printWidth;
}
/* Calculate how much of the end of a string can fit into the edit box */
static void fitStringEnd(char *pBuffer, UDWORD boxWidth,
UWORD *pStart, UWORD *pCount, UWORD *pCharWidth)
{
UDWORD len;
UWORD printWidth, printChars, width;
char *pCurr;
len = strlen(pBuffer);
pCurr = pBuffer + len - 1;
printChars = 0;
printWidth = 0;
/* Find the number of characters that will fit in boxWidth */
while (printChars < len)
{
width = (UWORD)(printWidth + iV_GetCharWidth(*pCurr));
if (width > boxWidth - (WEDB_XGAP*2 + WEDB_CURSORSIZE))
{
/* Got as many characters as will fit into the box */
break;
}
printWidth = width;
printChars += 1;
pCurr -= 1;
}
/* Return the number of characters and their width */
*pStart = (UWORD)(len - printChars);
*pCount = printChars;
*pCharWidth = printWidth;
}
/* Run an edit box widget */
void editBoxRun(W_EDITBOX *psWidget, W_CONTEXT *psContext)
{
UDWORD key, len = 0, editState;
UDWORD pos;
char *pBuffer;
BOOL done;
UWORD printStart, printWidth, printChars;
SDWORD mx,my;
/* Note the edit state */
editState = psWidget->state & WEDBS_MASK;
/* Only have anything to do if the widget is being edited */
if ((editState & WEDBS_MASK) == WEDBS_FIXED)
{
return;
}
/* If there is a mouse click outside of the edit box - stop editing */
mx = psContext->mx;
my = psContext->my;
if (mousePressed(MOUSE_LMB) &&
(mx < psWidget->x ||
(mx > psWidget->x + psWidget->width) ||
my < psWidget->y ||
(my > psWidget->y + psWidget->height)))
{
screenClearFocus(psContext->psScreen);
return;
}
/* note the widget state */
pos = psWidget->insPos;
pBuffer = psWidget->aText;
printStart = psWidget->printStart;
printWidth = psWidget->printWidth;
printChars = psWidget->printChars;
iV_SetFont(psWidget->FontID);
/* Loop through the characters in the input buffer */
done = false;
for (key = inputGetKey(); key != 0 && !done; key = inputGetKey())
{
/* Deal with all the control keys, assume anything else is a printable character */
switch (key)
{
case INPBUF_LEFT :
/* Move the cursor left */
if (pos > 0)
{
pos -= 1;
}
/* If the cursor has gone off the left of the edit box,
* need to update the printable text.
*/
if (pos < printStart)
{
if (printStart <= WEDB_CHARJUMP)
{
/* Got to the start of the string */
printStart = 0;
fitStringStart(pBuffer, psWidget->width, &printChars, &printWidth);
}
else
{
printStart -= WEDB_CHARJUMP;
fitStringStart(pBuffer + printStart, psWidget->width,
&printChars, &printWidth);
}
}
break;
case INPBUF_RIGHT :
/* Move the cursor right */
len = strlen(pBuffer);
if (pos < len)
{
pos += 1;
}
/* If the cursor has gone off the right of the edit box,
* need to update the printable text.
*/
if (pos > (UDWORD)(printStart + printChars))
{
printStart += WEDB_CHARJUMP;
if (printStart >= len)
{
printStart = (UWORD)(len - 1);
}
fitStringStart(pBuffer + printStart, psWidget->width,
&printChars, &printWidth);
}
break;
case INPBUF_UP :
break;
case INPBUF_DOWN :
break;
case INPBUF_HOME :
/* Move the cursor to the start of the buffer */
pos = 0;
printStart = 0;
fitStringStart(pBuffer, psWidget->width, &printChars, &printWidth);
break;
case INPBUF_END :
/* Move the cursor to the end of the buffer */
pos = strlen(pBuffer);
if (pos != (UWORD)(printStart + printChars))
{
fitStringEnd(pBuffer, psWidget->width, &printStart, &printChars, &printWidth);
}
break;
case INPBUF_INS :
if (editState == WEDBS_INSERT)
{
editState = WEDBS_OVER;
}
else
{
editState = WEDBS_INSERT;
}
break;
case INPBUF_DEL :
delCharRight(pBuffer, &pos);
/* Update the printable text */
fitStringStart(pBuffer + printStart, psWidget->width,
&printChars, &printWidth);
break;
case INPBUF_PGUP :
break;
case INPBUF_PGDN :
break;
case INPBUF_BKSPACE :
/* Delete the character to the left of the cursor */
delCharLeft(pBuffer, &pos);
/* Update the printable text */
if (pos <= printStart)
{
if (printStart <= WEDB_CHARJUMP)
{
/* Got to the start of the string */
printStart = 0;
fitStringStart(pBuffer, psWidget->width, &printChars, &printWidth);
}
else
{
printStart -= WEDB_CHARJUMP;
fitStringStart(pBuffer + printStart, psWidget->width,
&printChars, &printWidth);
}
}
else
{
fitStringStart(pBuffer + printStart, psWidget->width,
&printChars, &printWidth);
}
break;
case INPBUF_TAB :
putSelection(pBuffer, &pos);
/* Update the printable text */
fitStringEnd(pBuffer, psWidget->width, &printStart, &printChars, &printWidth);
break;
case INPBUF_CR :
/* Finish editing */
editBoxFocusLost(psContext->psScreen, psWidget);
screenClearFocus(psContext->psScreen);
return;
break;
case INPBUF_ESC :
break;
default:
/* Dealt with everything else this must be a printable character */
if (editState == WEDBS_INSERT)
{
insertChar(pBuffer, &pos, inputGetCharKey());
}
else
{
overwriteChar(pBuffer, &pos, inputGetCharKey());
}
/* Update the printable chars */
if (pos == strlen(pBuffer))
{
fitStringEnd(pBuffer, psWidget->width, &printStart, &printChars, &printWidth);
}
else
{
fitStringStart(pBuffer + printStart, psWidget->width, &printChars, &printWidth);
if (pos > (UDWORD)(printStart + printChars))
{
printStart += WEDB_CHARJUMP;
if (printStart >= len)
{
printStart = (UWORD)(len - 1);
fitStringStart(pBuffer + printStart, psWidget->width,
&printChars, &printWidth);
}
}
}
break;
}
}
/* Store the current widget state */
psWidget->insPos = (UWORD)pos;
psWidget->state = (psWidget->state & ~WEDBS_MASK) | editState;
psWidget->printStart = printStart;
psWidget->printWidth = printWidth;
psWidget->printChars = printChars;
}
/* Set the current string for the edit box */
void editBoxSetString(W_EDITBOX *psWidget, const char *pText)
{
ASSERT( psWidget != NULL,
"editBoxSetString: Invalid edit box pointer" );
sstrcpy(psWidget->aText, pText);
psWidget->state = WEDBS_FIXED;
psWidget->printStart = 0;
iV_SetFont(psWidget->FontID);
fitStringStart(psWidget->aText, psWidget->width,
&psWidget->printChars, &psWidget->printWidth);
}
/* Respond to a mouse click */
void editBoxClicked(W_EDITBOX *psWidget, W_CONTEXT *psContext)
{
UDWORD len;
if(psWidget->state & WEDBS_DISABLE) // disabled button.
{
return;
}
if ((psWidget->state & WEDBS_MASK) == WEDBS_FIXED)
{
if(!(psWidget->style & WEDB_DISABLED)) {
if(psWidget->AudioCallback) {
psWidget->AudioCallback(psWidget->ClickedAudioID);
}
/* Set up the widget state */
psWidget->state = (psWidget->state & ~WEDBS_MASK) | WEDBS_INSERT;
len = strlen(psWidget->aText);
psWidget->insPos = (UWORD)len;
/* Calculate how much of the string can appear in the box */
iV_SetFont(psWidget->FontID);
fitStringEnd(psWidget->aText, psWidget->width,
&psWidget->printStart, &psWidget->printChars, &psWidget->printWidth);
/* Clear the input buffer */
inputClearBuffer();
/* Tell the form that the edit box has focus */
screenSetFocus(psContext->psScreen, (WIDGET *)psWidget);
}
}
}
/* Respond to loss of focus */
void editBoxFocusLost(W_SCREEN* psScreen, W_EDITBOX *psWidget)
{
ASSERT( !(psWidget->state & WEDBS_DISABLE),
"editBoxFocusLost: disabled edit box" );
/* Stop editing the widget */
psWidget->state = WEDBS_FIXED;
psWidget->printStart = 0;
fitStringStart(psWidget->aText,psWidget->width,
&psWidget->printChars, &psWidget->printWidth);
widgSetReturn(psScreen, (WIDGET *)psWidget);
}
/* Respond to a mouse button up */
void editBoxReleased(W_EDITBOX *psWidget)
{
(void)psWidget;
}
/* Respond to a mouse moving over an edit box */
void editBoxHiLite(W_EDITBOX *psWidget)
{
if(psWidget->state & WEDBS_DISABLE)
{
return;
}
if(psWidget->AudioCallback) {
psWidget->AudioCallback(psWidget->HilightAudioID);
}
psWidget->state |= WEDBS_HILITE;
}
/* Respond to the mouse moving off an edit box */
void editBoxHiLiteLost(W_EDITBOX *psWidget)
{
if(psWidget->state & WEDBS_DISABLE)
{
return;
}
psWidget->state = psWidget->state & WEDBS_MASK;
}
/* The edit box display function */
void editBoxDisplay(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset, PIELIGHT *pColours)
{
W_EDITBOX *psEdBox;
SDWORD x0,y0,x1,y1, fx,fy, cx,cy;
enum iV_fonts CurrFontID;
char ch, *pInsPoint, *pPrint;
#if CURSOR_BLINK
BOOL blink;
#endif
psEdBox = (W_EDITBOX *)psWidget;
CurrFontID = psEdBox->FontID;
x0=psEdBox->x + xOffset;
y0=psEdBox->y + yOffset;
x1=x0 + psEdBox->width;
y1=y0 + psEdBox->height;
if(psEdBox->pBoxDisplay) {
psEdBox->pBoxDisplay((WIDGET *)psEdBox, xOffset, yOffset, pColours);
} else {
pie_BoxFill(x0, y0, x1, y1, pColours[WCOL_BKGRND]);
iV_Line(x0,y0, x1,y0, pColours[WCOL_DARK]);
iV_Line(x0,y0, x0,y1, pColours[WCOL_DARK]);
iV_Line(x0,y1, x1,y1, pColours[WCOL_LIGHT]);
iV_Line(x1,y1, x1,y0, pColours[WCOL_LIGHT]);
}
fx = x0 + WEDB_XGAP;// + (psEdBox->width - fw) / 2;
iV_SetFont(CurrFontID);
iV_SetTextColour(pColours[WCOL_TEXT]);
fy = y0 + (psEdBox->height - iV_GetTextLineSize())/2 - iV_GetTextAboveBase();
/* If there is more text than will fit into the box,
display the bit with the cursor in it */
pPrint = psEdBox->aText + psEdBox->printStart;
pInsPoint = pPrint + psEdBox->printChars;
ch = *pInsPoint;
*pInsPoint = '\0';
// if(psEdBox->pFontDisplay) {
// psEdBox->pFontDisplay(fx,fy, pPrint);
// } else {
iV_DrawText(pPrint,fx,fy);
// }
*pInsPoint = ch;
/* Display the cursor if editing */
#if CURSOR_BLINK
blink = (SDL_GetTicks()/WEDB_BLINKRATE) % 2;
if ((psEdBox->state & WEDBS_MASK) == WEDBS_INSERT && blink)
#else
if ((psEdBox->state & WEDBS_MASK) == WEDBS_INSERT)
#endif
{
pInsPoint = psEdBox->aText + psEdBox->insPos;
ch = *pInsPoint;
*pInsPoint = '\0';
cx = x0 + WEDB_XGAP + iV_GetTextWidth(psEdBox->aText + psEdBox->printStart);
cx += iV_GetTextWidth("-");
*pInsPoint = ch;
cy = fy;
iV_Line(cx, cy + iV_GetTextAboveBase(), cx, cy - iV_GetTextBelowBase(), pColours[WCOL_CURSOR]);
}
#if CURSOR_BLINK
else if ((psEdBox->state & WEDBS_MASK) == WEDBS_OVER && blink)
#else
else if ((psEdBox->state & WEDBS_MASK) == WEDBS_OVER)
#endif
{
pInsPoint = psEdBox->aText + psEdBox->insPos;
ch = *pInsPoint;
*pInsPoint = '\0';
cx = x0 + WEDB_XGAP + iV_GetTextWidth(psEdBox->aText + psEdBox->printStart);
*pInsPoint = ch;
cy = fy;
iV_Line(cx, cy, cx + WEDB_CURSORSIZE, cy, pColours[WCOL_CURSOR]);
}
if(psEdBox->pBoxDisplay == NULL) {
if (psEdBox->state & WEDBS_HILITE)
{
/* Display the button hilite */
iV_Line(x0-2,y0-2, x1+2,y0-2, pColours[WCOL_HILITE]);
iV_Line(x0-2,y0-2, x0-2,y1+2, pColours[WCOL_HILITE]);
iV_Line(x0-2,y1+2, x1+2,y1+2, pColours[WCOL_HILITE]);
iV_Line(x1+2,y1+2, x1+2,y0-2, pColours[WCOL_HILITE]);
}
}
}
/* Set an edit box'sstate */
void editBoxSetState(W_EDITBOX *psEditBox, UDWORD state)
{
if (state & WEDBS_DISABLE)
{
psEditBox->state |= WEDBS_DISABLE;
}
else
{
psEditBox->state &= ~WEDBS_DISABLE;
}
}