/*
HEBUFFER.C
Text-buffer management for the Hugo Engine
Copyright (c) 2002-2006 by Kent Tessman
To use text-buffer management:
1. #define USE_TEXTBUFFER (probably in the appropriate
section in heheader.h).
2. Add a call to TB_Scroll() in hugo_scrollwindowup().
3. Add calls to TB_Clear() in hugo_clearwindow() and
hugo_clearfullscreen(), as appropriate (as well as
wherever else the port may clear a portion or all of
the display without going through the hugo_*()
functions).
4. Make sure the port manages current_text_x and
(especially) current_text_y with regard to positioning
and printing text.
Then:
5. Call TB_Find() to get the word at a given location.
NOTES: All TB_*() calls are done in individual units, i.e., in
characters if the port is character-based or in pixels if
the port is pixel-based.
*/
#include "heheader.h"
#ifdef USE_TEXTBUFFER
#ifndef MAX_TEXTBUFFER_COUNT
#define MAX_TEXTBUFFER_COUNT 1000
#endif
#define TB_NONE (-1)
#define WINDOW_CELL " _win"
char allow_text_selection = true;
int tb_first_used; /* indexes */
int tb_first_unused;
int tb_last_used;
int tb_last_unused;
int tb_used = 0; /* counts */
int tb_unused;
typedef struct
{
char *data;
int left, top, right, bottom;
int prev, next;
#ifdef TEXTBUFFER_FORMATTING
int font, fcolor, bgcolor;
#endif
} tb_list_struct;
tb_list_struct tb_list[MAX_TEXTBUFFER_COUNT];
int tb_selected;
/* TB_Init()
Initializes text-buffer management.
*/
void TB_Init()
{
int i;
for (i=0; i<MAX_TEXTBUFFER_COUNT; i++)
{
tb_list[i].data = NULL;
tb_list[i].prev = i?i-1:TB_NONE;
tb_list[i].next = (i<MAX_TEXTBUFFER_COUNT-1)?i+1:TB_NONE;
}
tb_first_unused = 0;
tb_first_used = TB_NONE;
tb_last_unused = MAX_TEXTBUFFER_COUNT-1;
tb_last_used = TB_NONE;
tb_used = 0;
tb_unused = MAX_TEXTBUFFER_COUNT;
}
/* TB_IsUsed(n)
Returns true if cell n is used.
*/
char TB_IsUsed(int n)
{
return (tb_list[n].data)?false:true;
}
/* TB_FirstCell()
Returns the first used cell, or -1 if none.
*/
int TB_FirstCell()
{
if (tb_used==0) return TB_NONE;
return tb_first_used;
}
/* TB_Remove(int n, list)
Removes the specified word cell (at index n) from either
the TB_USED or TB_UNUSED list.
*/
void TB_Remove(int n)
{
/* Take the cell out of the used list */
if (tb_list[n].prev != TB_NONE)
tb_list[tb_list[n].prev].next = tb_list[n].next;
if (tb_list[n].next != TB_NONE)
tb_list[tb_list[n].next].prev = tb_list[n].prev;
if (n==tb_first_used)
tb_first_used = tb_list[n].next;
if (n==tb_last_used)
tb_last_used = tb_list[n].prev;
tb_used--;
/* Free this cell (TB_IsUsed() sanity check just in case) */
if (!TB_IsUsed(n))
{
#ifdef TB_DEBUG
printf("TB_Remove(): freeing tb_list[%d]: '%s', tb_used=%d\n",
n, tb_list[n].data, tb_used);
#endif
hugo_blockfree(tb_list[n].data);
tb_list[n].data = NULL;
}
#ifdef TB_DEBUG
else
{
printf("TB_Remove(): unallocated data: tb_list[%d]\n", n);
}
#endif
/* Move it to the unused list */
tb_list[n].prev = tb_last_unused;
if (tb_last_unused != TB_NONE)
tb_list[tb_last_unused].next = n;
tb_list[n].next = TB_NONE;
if (tb_unused==0)
tb_first_unused = n;
tb_last_unused = n;
tb_unused++;
}
/* TB_AddWord(char *word, left, top, right, bottom)
Adds the specified word cell with the given boundaries.
Called by TB_AddWord() once the word has been properly
trimmed.
*/
int TB_AddWord(char *w, int left, int top, int right, int bottom)
{
int i, c;
int n;
#ifdef MINIMAL_WINDOWING
if (minimal_windowing && illegal_window) return TB_NONE;
#endif
if (!w || !strcmp(w, "") || !strcmp(w, " "))
return TB_NONE;
/* Make sure the word doesn't begin with any control codes */
while ((unsigned char)w[0]<' ')
{
w++;
if (w[0]=='\0') return TB_NONE;
}
/* If we've used everything, use the oldest used cell */
if (tb_used >= MAX_TEXTBUFFER_COUNT)
{
#ifdef TB_DEBUG
printf("TB_AddWord(): MAX_TEXTBUFFER_COUNT reached; removing tb_first_used\n");
#endif
TB_Remove(tb_first_used);
}
/* Get the cell we're going to add */
n = tb_first_unused;
/* Try to allocate storage for the word */
if (!(tb_list[n].data = hugo_blockalloc(strlen(w)+1)))
{
/* If we fail, try to free older used cells */
while (tb_used)
{
#ifdef TB_DEBUG
printf("TB_AddWord(): Initial alloc failed (tb_used = %d)\n", tb_used);
#endif
TB_Remove(tb_first_used);
if ((tb_list[n].data = hugo_blockalloc(strlen(w)+1)))
break;
}
if (tb_used==0)
{
#ifdef TB_DEBUG
printf("TB_AddWord(): Unable to allocate for '%s'\n", w);
#endif
return TB_NONE;
}
}
/* Take the cell out of the unused list */
if (tb_list[n].prev != TB_NONE)
tb_list[tb_list[n].prev].next = tb_list[n].next;
if (tb_list[n].next != TB_NONE)
tb_list[tb_list[n].next].prev = tb_list[n].prev;
if (n==tb_first_unused)
tb_first_unused = tb_list[n].next;
if (n==tb_last_unused)
tb_last_unused = tb_list[n].prev;
tb_unused--;
/* Move it to the used list */
tb_list[n].prev = tb_last_used;
if (tb_last_used != TB_NONE)
tb_list[tb_last_used].next = n;
tb_list[n].next = TB_NONE;
if (tb_used==0)
tb_first_used = n;
tb_last_used = n;
tb_used++;
/* Set its coordinates */
/* Just using strcpy() will let font/color change codes through */
/* strcpy(tb_list[n].data, w); */
c = 0;
for (i=0; i<(int)strlen(w); i++)
{
if ((unsigned char)w[i]>=' ')
tb_list[n].data[c++] = w[i];
}
tb_list[n].data[c] = '\0';
tb_list[n].left = left;
tb_list[n].top = top;
tb_list[n].right = right;
tb_list[n].bottom = bottom;
#ifdef TEXTBUFFER_FORMATTING
tb_list[n].font = currentfont;
tb_list[n].fcolor = fcolor;
tb_list[n].bgcolor = bgcolor;
#endif
#ifdef TB_DEBUG
printf("TB_AddWord(): Added tb_list[%d]: '%s' (%d, %d, %d, %d), tb_used=%d\n",
n, w, left, top, right, bottom, tb_used);
#endif
return n;
}
/* TB_AddWin(left, top, right, bottom)
Adds a window area. Generally only useful when
TEXTBUFFER_FORMATTING is #defined.
*/
int TB_AddWin(int left, int top, int right, int bottom)
{
return TB_AddWord(WINDOW_CELL, left, top, right, bottom);
}
/* TB_InBounds(n, left, top, right, bottom)
Returns true if tb_list[n] is even partially within
the given boundaries.
*/
char TB_InBounds(int n, int left, int top, int right, int bottom)
{
if ((tb_list[n].left>=left && tb_list[n].left<=right &&
tb_list[n].top>=top && tb_list[n].top<=bottom) ||
(tb_list[n].right>=left && tb_list[n].right<=right &&
tb_list[n].bottom>=top && tb_list[n].bottom<=bottom))
{
return true;
}
return false;
}
/* TB_Clear(left, top, right, bottom)
Removes all cells within the given boundaries.
*/
void TB_Clear(int left, int top, int right, int bottom)
{
int i = tb_first_used;
/* right may be <0 when called from Printout() with
only control characters in the string */
if (right < 0) return;
#ifdef TB_DEBUG
printf("TB_Clear(%d, %d, %d, %d)\n", left, top, right, bottom);
#endif
while (i!=TB_NONE)
{
/* Have to find out what the next cell is
before we remove this current one */
int next = tb_list[i].next;
if (TB_InBounds(i, left, top, right, bottom))
{
TB_Remove(i);
i = next;
continue;
}
if (tb_list[i].top < physical_windowtop
|| tb_list[i].bottom<0)
{
TB_Remove(i);
}
i = next;
}
}
/* TB_Scroll(left, top, right, bottom, size)
Scrolls all cells within the given window up by <size>
units.
*/
void TB_Scroll()
{
int i;
i = tb_first_used;
while (i!=TB_NONE)
{
int next = tb_list[i].next;
/* Remove first the cells which will be scrolled away */
if (TB_InBounds(i, physical_windowleft, physical_windowtop,
physical_windowright, physical_windowtop+lineheight/2))
{
#ifdef TB_DEBUG
printf("TB_Scroll(): ");
#endif
TB_Remove(i);
}
/* Then scroll up the remaining cells */
if (TB_InBounds(i, physical_windowleft, physical_windowtop+lineheight,
physical_windowright, physical_windowbottom))
{
#ifdef TB_DEBUG
// printf("TB_Scroll(): scrolling tb_list[%d]: '%s'\n", i, tb_list[i].data);
#endif
tb_list[i].top -= lineheight;
tb_list[i].bottom -= lineheight;
/* Don't scroll areas into the invalidation zone until the
bottom is invalidated
*/
if ((tb_list[i].data) &&
!strcmp(tb_list[i].data, WINDOW_CELL) && tb_list[i].top<0 &&
tb_list[i].bottom > tb_list[i].top)
{
tb_list[i].top = 0;
}
}
i = next;
}
}
/* TB_FindWord(x, y)
Returns the (first) word found at (x, y). Returns NULL if no
word is found.
*/
char *TB_FindWord(int x, int y)
{
int i;
tb_selected = TB_NONE;
if (!allow_text_selection) return NULL;
for (i=tb_last_used; i!=TB_NONE; i=tb_list[i].prev)
{
if (x >= tb_list[i].left && x <=tb_list[i].right &&
y >= tb_list[i].top && y <= tb_list[i].bottom)
{
static char buf[255];
char instring = false;
int n = 0, len;
char *w = tb_list[i].data;
char c;
if (!strcmp(w, WINDOW_CELL)) return NULL;
tb_selected = i;
len = strlen(w);
for (i=0; i<len; i++)
{
/* Start only on a useful word */
c = w[i];
if ((c>='0' && c<='9') || (unsigned char)c>='A')
{
#ifdef USE_SMARTFORMATTING
if (smartformatting)
{
switch ((unsigned char)c)
{
case 145:
case 146:
c = '\'';
break;
case 147:
case 148:
c = '\"';
break;
case 151:
buf[n++] = '-';
c = '-';
break;
}
}
#endif
buf[n++] = c;
instring = true;
}
else if ((unsigned char)c>=' ' && instring)
{
buf[n++] = c;
}
}
buf[n] = '\0';
/* Strip off any trailing punctuation */
i = strlen(buf);
for (; i; i--)
{
if ((buf[i]>='0' && buf[i]<='9') || (unsigned char)buf[i]>='A')
break;
buf[i] = '\0';
}
if (n==0)
{
tb_selected = TB_NONE;
return NULL;
}
#ifdef TB_DEBUG
printf("TB_FindWord(%d, %d) = '%s'\n", x, y, w);
#endif
return buf;
}
}
#ifdef TB_DEBUG
printf("TB_FindWord(%d, %d): no match\n", x, y);
#endif
return NULL;
}
#endif /* USE_TEXTBUFFER */
/*
HEBUFFER.C
Text-buffer management for the Hugo Engine
Copyright (c) 2002-2006 by Kent Tessman
To use text-buffer management:
1. #define USE_TEXTBUFFER (probably in the appropriate
section in heheader.h).
2. Add a call to TB_Scroll() in hugo_scrollwindowup().
3. Add calls to TB_Clear() in hugo_clearwindow() and
hugo_clearfullscreen(), as appropriate (as well as
wherever else the port may clear a portion or all of
the display without going through the hugo_*()
functions).
4. Make sure the port manages current_text_x and
(especially) current_text_y with regard to positioning
and printing text.
Then:
5. Call TB_Find() to get the word at a given location.
NOTES: All TB_*() calls are done in individual units, i.e., in
characters if the port is character-based or in pixels if
the port is pixel-based.
*/
#include "heheader.h"
#ifdef USE_TEXTBUFFER
#ifndef MAX_TEXTBUFFER_COUNT
#define MAX_TEXTBUFFER_COUNT 1000
#endif
#define TB_NONE (-1)
#define WINDOW_CELL " _win"
char allow_text_selection = true;
int tb_first_used; /* indexes */
int tb_first_unused;
int tb_last_used;
int tb_last_unused;
int tb_used = 0; /* counts */
int tb_unused;
typedef struct
{
char *data;
int left, top, right, bottom;
int prev, next;
#ifdef TEXTBUFFER_FORMATTING
int font, fcolor, bgcolor;
#endif
} tb_list_struct;
tb_list_struct tb_list[MAX_TEXTBUFFER_COUNT];
int tb_selected;
/* TB_Init()
Initializes text-buffer management.
*/
void TB_Init()
{
int i;
for (i=0; i<MAX_TEXTBUFFER_COUNT; i++)
{
tb_list[i].data = NULL;
tb_list[i].prev = i?i-1:TB_NONE;
tb_list[i].next = (i<MAX_TEXTBUFFER_COUNT-1)?i+1:TB_NONE;
}
tb_first_unused = 0;
tb_first_used = TB_NONE;
tb_last_unused = MAX_TEXTBUFFER_COUNT-1;
tb_last_used = TB_NONE;
tb_used = 0;
tb_unused = MAX_TEXTBUFFER_COUNT;
}
/* TB_IsUsed(n)
Returns true if cell n is used.
*/
char TB_IsUsed(int n)
{
return (tb_list[n].data)?false:true;
}
/* TB_FirstCell()
Returns the first used cell, or -1 if none.
*/
int TB_FirstCell()
{
if (tb_used==0) return TB_NONE;
return tb_first_used;
}
/* TB_Remove(int n, list)
Removes the specified word cell (at index n) from either
the TB_USED or TB_UNUSED list.
*/
void TB_Remove(int n)
{
/* Take the cell out of the used list */
if (tb_list[n].prev != TB_NONE)
tb_list[tb_list[n].prev].next = tb_list[n].next;
if (tb_list[n].next != TB_NONE)
tb_list[tb_list[n].next].prev = tb_list[n].prev;
if (n==tb_first_used)
tb_first_used = tb_list[n].next;
if (n==tb_last_used)
tb_last_used = tb_list[n].prev;
tb_used--;
/* Free this cell (TB_IsUsed() sanity check just in case) */
if (!TB_IsUsed(n))
{
#ifdef TB_DEBUG
printf("TB_Remove(): freeing tb_list[%d]: '%s', tb_used=%d\n",
n, tb_list[n].data, tb_used);
#endif
hugo_blockfree(tb_list[n].data);
tb_list[n].data = NULL;
}
#ifdef TB_DEBUG
else
{
printf("TB_Remove(): unallocated data: tb_list[%d]\n", n);
}
#endif
/* Move it to the unused list */
tb_list[n].prev = tb_last_unused;
if (tb_last_unused != TB_NONE)
tb_list[tb_last_unused].next = n;
tb_list[n].next = TB_NONE;
if (tb_unused==0)
tb_first_unused = n;
tb_last_unused = n;
tb_unused++;
}
/* TB_AddWord(char *word, left, top, right, bottom)
Adds the specified word cell with the given boundaries.
Called by TB_AddWord() once the word has been properly
trimmed.
*/
int TB_AddWord(char *w, int left, int top, int right, int bottom)
{
int i, c;
int n;
#ifdef MINIMAL_WINDOWING
if (minimal_windowing && illegal_window) return TB_NONE;
#endif
if (!w || !strcmp(w, "") || !strcmp(w, " "))
return TB_NONE;
/* Make sure the word doesn't begin with any control codes */
while ((unsigned char)w[0]<' ')
{
w++;
if (w[0]=='\0') return TB_NONE;
}
/* If we've used everything, use the oldest used cell */
if (tb_used >= MAX_TEXTBUFFER_COUNT)
{
#ifdef TB_DEBUG
printf("TB_AddWord(): MAX_TEXTBUFFER_COUNT reached; removing tb_first_used\n");
#endif
TB_Remove(tb_first_used);
}
/* Get the cell we're going to add */
n = tb_first_unused;
/* Try to allocate storage for the word */
if (!(tb_list[n].data = hugo_blockalloc(strlen(w)+1)))
{
/* If we fail, try to free older used cells */
while (tb_used)
{
#ifdef TB_DEBUG
printf("TB_AddWord(): Initial alloc failed (tb_used = %d)\n", tb_used);
#endif
TB_Remove(tb_first_used);
if ((tb_list[n].data = hugo_blockalloc(strlen(w)+1)))
break;
}
if (tb_used==0)
{
#ifdef TB_DEBUG
printf("TB_AddWord(): Unable to allocate for '%s'\n", w);
#endif
return TB_NONE;
}
}
/* Take the cell out of the unused list */
if (tb_list[n].prev != TB_NONE)
tb_list[tb_list[n].prev].next = tb_list[n].next;
if (tb_list[n].next != TB_NONE)
tb_list[tb_list[n].next].prev = tb_list[n].prev;
if (n==tb_first_unused)
tb_first_unused = tb_list[n].next;
if (n==tb_last_unused)
tb_last_unused = tb_list[n].prev;
tb_unused--;
/* Move it to the used list */
tb_list[n].prev = tb_last_used;
if (tb_last_used != TB_NONE)
tb_list[tb_last_used].next = n;
tb_list[n].next = TB_NONE;
if (tb_used==0)
tb_first_used = n;
tb_last_used = n;
tb_used++;
/* Set its coordinates */
/* Just using strcpy() will let font/color change codes through */
/* strcpy(tb_list[n].data, w); */
c = 0;
for (i=0; i<(int)strlen(w); i++)
{
if ((unsigned char)w[i]>=' ')
tb_list[n].data[c++] = w[i];
}
tb_list[n].data[c] = '\0';
tb_list[n].left = left;
tb_list[n].top = top;
tb_list[n].right = right;
tb_list[n].bottom = bottom;
#ifdef TEXTBUFFER_FORMATTING
tb_list[n].font = currentfont;
tb_list[n].fcolor = fcolor;
tb_list[n].bgcolor = bgcolor;
#endif
#ifdef TB_DEBUG
printf("TB_AddWord(): Added tb_list[%d]: '%s' (%d, %d, %d, %d), tb_used=%d\n",
n, w, left, top, right, bottom, tb_used);
#endif
return n;
}
/* TB_AddWin(left, top, right, bottom)
Adds a window area. Generally only useful when
TEXTBUFFER_FORMATTING is #defined.
*/
int TB_AddWin(int left, int top, int right, int bottom)
{
return TB_AddWord(WINDOW_CELL, left, top, right, bottom);
}
/* TB_InBounds(n, left, top, right, bottom)
Returns true if tb_list[n] is even partially within
the given boundaries.
*/
char TB_InBounds(int n, int left, int top, int right, int bottom)
{
if ((tb_list[n].left>=left && tb_list[n].left<=right &&
tb_list[n].top>=top && tb_list[n].top<=bottom) ||
(tb_list[n].right>=left && tb_list[n].right<=right &&
tb_list[n].bottom>=top && tb_list[n].bottom<=bottom))
{
return true;
}
return false;
}
/* TB_Clear(left, top, right, bottom)
Removes all cells within the given boundaries.
*/
void TB_Clear(int left, int top, int right, int bottom)
{
int i = tb_first_used;
/* right may be <0 when called from Printout() with
only control characters in the string */
if (right < 0) return;
#ifdef TB_DEBUG
printf("TB_Clear(%d, %d, %d, %d)\n", left, top, right, bottom);
#endif
while (i!=TB_NONE)
{
/* Have to find out what the next cell is
before we remove this current one */
int next = tb_list[i].next;
if (TB_InBounds(i, left, top, right, bottom))
{
TB_Remove(i);
i = next;
continue;
}
if (tb_list[i].top < physical_windowtop
|| tb_list[i].bottom<0)
{
TB_Remove(i);
}
i = next;
}
}
/* TB_Scroll(left, top, right, bottom, size)
Scrolls all cells within the given window up by <size>
units.
*/
void TB_Scroll()
{
int i;
i = tb_first_used;
while (i!=TB_NONE)
{
int next = tb_list[i].next;
/* Remove first the cells which will be scrolled away */
if (TB_InBounds(i, physical_windowleft, physical_windowtop,
physical_windowright, physical_windowtop+lineheight/2))
{
#ifdef TB_DEBUG
printf("TB_Scroll(): ");
#endif
TB_Remove(i);
}
/* Then scroll up the remaining cells */
if (TB_InBounds(i, physical_windowleft, physical_windowtop+lineheight,
physical_windowright, physical_windowbottom))
{
#ifdef TB_DEBUG
// printf("TB_Scroll(): scrolling tb_list[%d]: '%s'\n", i, tb_list[i].data);
#endif
tb_list[i].top -= lineheight;
tb_list[i].bottom -= lineheight;
/* Don't scroll areas into the invalidation zone until the
bottom is invalidated
*/
if ((tb_list[i].data) &&
!strcmp(tb_list[i].data, WINDOW_CELL) && tb_list[i].top<0 &&
tb_list[i].bottom > tb_list[i].top)
{
tb_list[i].top = 0;
}
}
i = next;
}
}
/* TB_FindWord(x, y)
Returns the (first) word found at (x, y). Returns NULL if no
word is found.
*/
char *TB_FindWord(int x, int y)
{
int i;
tb_selected = TB_NONE;
if (!allow_text_selection) return NULL;
for (i=tb_last_used; i!=TB_NONE; i=tb_list[i].prev)
{
if (x >= tb_list[i].left && x <=tb_list[i].right &&
y >= tb_list[i].top && y <= tb_list[i].bottom)
{
static char buf[255];
char instring = false;
int n = 0, len;
char *w = tb_list[i].data;
char c;
if (!strcmp(w, WINDOW_CELL)) return NULL;
tb_selected = i;
len = strlen(w);
for (i=0; i<len; i++)
{
/* Start only on a useful word */
c = w[i];
if ((c>='0' && c<='9') || (unsigned char)c>='A')
{
#ifdef USE_SMARTFORMATTING
if (smartformatting)
{
switch ((unsigned char)c)
{
case 145:
case 146:
c = '\'';
break;
case 147:
case 148:
c = '\"';
break;
case 151:
buf[n++] = '-';
c = '-';
break;
}
}
#endif
buf[n++] = c;
instring = true;
}
else if ((unsigned char)c>=' ' && instring)
{
buf[n++] = c;
}
}
buf[n] = '\0';
/* Strip off any trailing punctuation */
i = strlen(buf);
for (; i; i--)
{
if ((buf[i]>='0' && buf[i]<='9') || (unsigned char)buf[i]>='A')
break;
buf[i] = '\0';
}
if (n==0)
{
tb_selected = TB_NONE;
return NULL;
}
#ifdef TB_DEBUG
printf("TB_FindWord(%d, %d) = '%s'\n", x, y, w);
#endif
return buf;
}
}
#ifdef TB_DEBUG
printf("TB_FindWord(%d, %d): no match\n", x, y);
#endif
return NULL;
}
#endif /* USE_TEXTBUFFER */