/* This goofed-up box whacked into shape by Elliot Lee <sopwith@cuc.edu>
(from the original listbox by Erik Troan <ewt@redhat.com>)
and contributed to newt for use under the LGPL license.
Copyright (C) 1996, 1997 Elliot Lee */
#include <slang.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "newt.h"
#include "newt_pr.h"
/* Linked list of items in the listbox */
struct items {
char * text;
const void *data;
unsigned char isSelected;
struct items *next;
};
/* Holds all the relevant information for this listbox */
struct listbox {
newtComponent sb; /* Scrollbar on right side of listbox */
int curWidth; /* size of text w/o scrollbar or border*/
int curHeight; /* size of text w/o border */
int sbAdjust;
int bdxAdjust, bdyAdjust;
int numItems, numSelected;
int userHasSetWidth;
int currItem, startShowItem; /* startShowItem is the first item displayed
on the screen */
int isActive; /* If we handle key events all the time, it seems
to do things even when they are supposed to be for
another button/whatever */
struct items *boxItems;
int grow;
int flags; /* flags for this listbox, right now just
NEWT_FLAG_RETURNEXIT */
};
static void listboxDraw(newtComponent co);
static void listboxDestroy(newtComponent co);
static struct eventResult listboxEvent(newtComponent co, struct event ev);
static void newtListboxRealSetCurrent(newtComponent co);
static void listboxPlace(newtComponent co, int newLeft, int newTop);
static inline void updateWidth(newtComponent co, struct listbox * li,
int maxField);
static void listboxMapped(newtComponent co, int isMapped);
static struct componentOps listboxOps = {
listboxDraw,
listboxEvent,
listboxDestroy,
listboxPlace,
listboxMapped,
};
static void listboxMapped(newtComponent co, int isMapped) {
struct listbox * li = co->data;
co->isMapped = isMapped;
if (li->sb)
li->sb->ops->mapped(li->sb, isMapped);
}
static void listboxPlace(newtComponent co, int newLeft, int newTop) {
struct listbox * li = co->data;
co->top = newTop;
co->left = newLeft;
if (li->sb)
li->sb->ops->place(li->sb, co->left + co->width - li->bdxAdjust - 1,
co->top + li->bdyAdjust);
}
newtComponent newtListbox(int left, int top, int height, int flags) {
newtComponent co, sb;
struct listbox * li;
if (!(co = malloc(sizeof(*co))))
return NULL;
if (!(li = malloc(sizeof(struct listbox)))) {
free(co);
return NULL;
}
li->boxItems = NULL;
li->numItems = 0;
li->currItem = 0;
li->numSelected = 0;
li->isActive = 0;
li->userHasSetWidth = 0;
li->startShowItem = 0;
li->sbAdjust = 0;
li->bdxAdjust = 0;
li->bdyAdjust = 0;
li->flags = flags & (NEWT_FLAG_RETURNEXIT | NEWT_FLAG_BORDER |
NEWT_FLAG_MULTIPLE | NEWT_FLAG_SHOWCURSOR);
if (li->flags & NEWT_FLAG_BORDER) {
li->bdxAdjust = 2;
li->bdyAdjust = 1;
}
co->height = height;
li->curHeight = co->height - (2 * li->bdyAdjust);
if (height) {
li->grow = 0;
if (flags & NEWT_FLAG_SCROLL) {
sb = newtVerticalScrollbar(left, top + li->bdyAdjust,
li->curHeight,
COLORSET_LISTBOX, COLORSET_ACTLISTBOX);
li->sbAdjust = 3;
} else {
sb = NULL;
}
} else {
li->grow = 1;
sb = NULL;
}
li->sb = sb;
co->data = li;
co->isMapped = 0;
co->left = left;
co->top = top;
co->ops = &listboxOps;
co->takesFocus = 1;
co->callback = NULL;
co->destroyCallback = NULL;
updateWidth(co, li, 5);
return co;
}
static inline void updateWidth(newtComponent co, struct listbox * li,
int maxField) {
li->curWidth = maxField;
co->width = li->curWidth + li->sbAdjust + 2 * li->bdxAdjust;
if (li->sb)
li->sb->left = co->left + co->width - li->bdxAdjust - 1;
}
void newtListboxSetCurrentByKey(newtComponent co, void * key) {
struct listbox * li = co->data;
struct items * item;
int i;
item = li->boxItems, i = 0;
while (item && item->data != key)
item = item->next, i++;
if (item)
newtListboxSetCurrent(co, i);
}
void newtListboxSetCurrent(newtComponent co, int num)
{
struct listbox * li = co->data;
if (num >= li->numItems)
li->currItem = li->numItems - 1;
else if (num < 0)
li->currItem = 0;
else
li->currItem = num;
if (li->currItem < li->startShowItem)
li->startShowItem = li->currItem;
else if (li->currItem - li->startShowItem > li->curHeight - 1)
li->startShowItem = li->currItem - li->curHeight + 1;
if (li->startShowItem + li->curHeight > li->numItems)
li->startShowItem = li->numItems - li->curHeight;
if(li->startShowItem < 0)
li->startShowItem = 0;
newtListboxRealSetCurrent(co);
}
static void newtListboxRealSetCurrent(newtComponent co)
{
struct listbox * li = co->data;
if(li->sb)
newtScrollbarSet(li->sb, li->currItem + 1, li->numItems);
listboxDraw(co);
if(co->callback) co->callback(co, co->callbackData);
}
void newtListboxSetWidth(newtComponent co, int width) {
struct listbox * li = co->data;
co->width = width;
li->curWidth = co->width - li->sbAdjust - 2 * li->bdxAdjust;
li->userHasSetWidth = 1;
if (li->sb)
li->sb->left = co->left + co->width - li->bdxAdjust - 1;
listboxDraw(co);
}
void * newtListboxGetCurrent(newtComponent co) {
struct listbox * li = co->data;
int i;
struct items *item;
for(i = 0, item = li->boxItems; item != NULL && i < li->currItem;
i++, item = item->next);
if (item)
return (void *)item->data;
else
return NULL;
}
void newtListboxSelectItem(newtComponent co, const void * key,
enum newtFlagsSense sense)
{
struct listbox * li = co->data;
int i;
struct items * item;
item = li->boxItems, i = 0;
while (item && item->data != key)
item = item->next, i++;
if (!item) return;
if (item->isSelected)
li->numSelected--;
switch(sense) {
case NEWT_FLAGS_RESET:
item->isSelected = 0; break;
case NEWT_FLAGS_SET:
item->isSelected = 1; break;
case NEWT_FLAGS_TOGGLE:
item->isSelected = !item->isSelected;
}
if (item->isSelected)
li->numSelected++;
listboxDraw(co);
}
void newtListboxClearSelection(newtComponent co)
{
struct items *item;
struct listbox * li = co->data;
for(item = li->boxItems; item != NULL;
item = item->next)
item->isSelected = 0;
li->numSelected = 0;
listboxDraw(co);
}
/* Free the returned array after use, but NOT the values in the array */
void ** newtListboxGetSelection(newtComponent co, int *numitems)
{
struct listbox * li;
int i;
void **retval;
struct items *item;
if(!co || !numitems) return NULL;
li = co->data;
if(!li || !li->numSelected) return NULL;
retval = malloc(li->numSelected * sizeof(void *));
for(i = 0, item = li->boxItems; item != NULL;
item = item->next)
if(item->isSelected)
retval[i++] = (void *)item->data;
*numitems = li->numSelected;
return retval;
}
void newtListboxSetEntry(newtComponent co, int num, const char * text) {
struct listbox * li = co->data;
int i;
struct items *item;
for(i = 0, item = li->boxItems; item != NULL && i < num;
i++, item = item->next);
if(!item)
return;
else {
free(item->text);
item->text = strdup(text);
}
if (li->userHasSetWidth == 0 && wstrlen(text,-1) > li->curWidth) {
updateWidth(co, li, wstrlen(text,-1));
}
if (num >= li->startShowItem && num <= li->startShowItem + co->height)
listboxDraw(co);
}
void newtListboxSetData(newtComponent co, int num, void * data) {
struct listbox * li = co->data;
int i;
struct items *item;
for(i = 0, item = li->boxItems; item != NULL && i < num;
i++, item = item->next);
if (item)
item->data = data;
}
int newtListboxAppendEntry(newtComponent co, const char * text,
const void * data) {
struct listbox * li = co->data;
struct items *item;
if(li->boxItems) {
for (item = li->boxItems; item->next != NULL; item = item->next);
item = item->next = malloc(sizeof(struct items));
} else {
item = li->boxItems = malloc(sizeof(struct items));
}
if (!li->userHasSetWidth && text && (wstrlen(text,-1) > li->curWidth))
updateWidth(co, li, wstrlen(text,-1));
item->text = strdup(text); item->data = data; item->next = NULL;
item->isSelected = 0;
if (li->grow)
co->height++, li->curHeight++;
li->numItems++;
return 0;
}
int newtListboxInsertEntry(newtComponent co, const char * text,
const void * data, void * key) {
struct listbox * li = co->data;
struct items *item, *t;
if (li->boxItems) {
if (key) {
item = li->boxItems;
while (item && item->data != key) item = item->next;
if (!item) return 1;
t = item->next;
item = item->next = malloc(sizeof(struct items));
item->next = t;
} else {
t = li->boxItems;
item = li->boxItems = malloc(sizeof(struct items));
item->next = t;
}
} else if (key) {
return 1;
} else {
item = li->boxItems = malloc(sizeof(struct items));
item->next = NULL;
}
if (!li->userHasSetWidth && text && (wstrlen(text,-1) > li->curWidth))
updateWidth(co, li, wstrlen(text,-1));
item->text = strdup(text?text:"(null)"); item->data = data;
item->isSelected = 0;
if (li->sb)
li->sb->left = co->left + co->width - li->bdxAdjust - 1;
li->numItems++;
listboxDraw(co);
return 0;
}
int newtListboxDeleteEntry(newtComponent co, void * key) {
struct listbox * li = co->data;
int widest = 0, t;
struct items *item, *item2 = NULL;
int num;
if (li->boxItems == NULL || li->numItems <= 0)
return 0;
num = 0;
item2 = NULL, item = li->boxItems;
while (item && item->data != key) {
item2 = item;
item = item->next;
num++;
}
if (!item)
return -1;
if (item2)
item2->next = item->next;
else
li->boxItems = item->next;
free(item->text);
free(item);
li->numItems--;
if (!li->userHasSetWidth) {
widest = 0;
for (item = li->boxItems; item != NULL; item = item->next)
if ((t = wstrlen(item->text,-1)) > widest) widest = t;
}
if (li->currItem >= num)
li->currItem--;
if (!li->userHasSetWidth) {
updateWidth(co, li, widest);
}
listboxDraw(co);
return 0;
}
void newtListboxClear(newtComponent co)
{
struct listbox * li;
struct items *anitem, *nextitem;
if(co == NULL || (li = co->data) == NULL)
return;
for(anitem = li->boxItems; anitem != NULL; anitem = nextitem) {
nextitem = anitem->next;
free(anitem->text);
free(anitem);
}
li->numItems = li->numSelected = li->currItem = li->startShowItem = 0;
li->boxItems = NULL;
if (!li->userHasSetWidth)
updateWidth(co, li, 5);
}
int newtListboxItemCount(newtComponent co)
{
struct listbox *li = co->data;
return li->numItems;
}
/* If you don't want to get back the text, pass in NULL for the ptr-ptr. Same
goes for the data. */
void newtListboxGetEntry(newtComponent co, int num, char **text, void **data) {
struct listbox * li = co->data;
int i;
struct items *item;
if (!li->boxItems || num >= li->numItems) {
if(text)
*text = NULL;
if(data)
*data = NULL;
return;
}
i = 0;
item = li->boxItems;
while (item && i < num) {
i++, item = item->next;
}
if (item) {
if (text)
*text = item->text;
if (data)
*data = (void *)item->data;
}
}
static void listboxDraw(newtComponent co)
{
struct listbox * li = co->data;
struct items *item;
int i, j;
if (!co->isMapped) return ;
newtTrashScreen();
if(li->flags & NEWT_FLAG_BORDER) {
if(li->isActive)
SLsmg_set_color(NEWT_COLORSET_ACTLISTBOX);
else
SLsmg_set_color(NEWT_COLORSET_LISTBOX);
newtDrawBox(co->left, co->top, co->width, co->height, 0);
}
if(li->sb)
li->sb->ops->draw(li->sb);
SLsmg_set_color(NEWT_COLORSET_LISTBOX);
for(i = 0, item = li->boxItems; item != NULL && i < li->startShowItem;
i++, item = item->next);
j = i;
for (i = 0; item != NULL && i < li->curHeight; i++, item = item->next) {
if (!item->text) continue;
newtGotorc(co->top + i + li->bdyAdjust, co->left + li->bdxAdjust);
if(j + i == li->currItem) {
if(li->isActive)
SLsmg_set_color(NEWT_COLORSET_ACTSELLISTBOX);
else
SLsmg_set_color(NEWT_COLORSET_ACTLISTBOX);
} else if(item->isSelected)
SLsmg_set_color(NEWT_COLORSET_SELLISTBOX);
else
SLsmg_set_color(NEWT_COLORSET_LISTBOX);
SLsmg_write_nstring(item->text, li->curWidth);
if (li->flags & NEWT_FLAG_MULTIPLE) {
newtGotorc(co->top + i + li->bdyAdjust, co->left + li->bdxAdjust);
SLsmg_set_color(item->isSelected ?
NEWT_COLORSET_SELLISTBOX : NEWT_COLORSET_LISTBOX);
SLsmg_write_nstring(item->text, 1);
}
}
newtGotorc(co->top + (li->currItem - li->startShowItem) + li->bdyAdjust,
co->left + li->bdxAdjust);
}
static struct eventResult listboxEvent(newtComponent co, struct event ev) {
struct eventResult er;
struct listbox * li = co->data;
struct items *item;
int i;
er.result = ER_IGNORED;
if(ev.when == EV_EARLY || ev.when == EV_LATE) {
return er;
}
switch(ev.event) {
case EV_KEYPRESS:
if (!li->isActive) break;
switch(ev.u.key) {
case ' ':
if(!(li->flags & NEWT_FLAG_MULTIPLE)) break;
newtListboxSelectItem(co, newtListboxGetCurrent(co),
NEWT_FLAGS_TOGGLE);
er.result = ER_SWALLOWED;
/* We don't break here, because it is cool to be able to
hold space to select a bunch of items in a list at once */
case NEWT_KEY_DOWN:
if(li->numItems <= 0) break;
if(li->currItem < li->numItems - 1) {
li->currItem++;
if(li->currItem > (li->startShowItem + li->curHeight - 1)) {
li->startShowItem = li->currItem - li->curHeight + 1;
if(li->startShowItem + li->curHeight > li->numItems)
li->startShowItem = li->numItems - li->curHeight;
}
if(li->sb)
newtScrollbarSet(li->sb, li->currItem + 1, li->numItems);
listboxDraw(co);
}
if(co->callback) co->callback(co, co->callbackData);
er.result = ER_SWALLOWED;
break;
case NEWT_KEY_ENTER:
if(li->numItems <= 0) break;
if(li->flags & NEWT_FLAG_RETURNEXIT)
er.result = ER_EXITFORM;
break;
case NEWT_KEY_UP:
if(li->numItems <= 0) break;
if(li->currItem > 0) {
li->currItem--;
if(li->currItem < li->startShowItem)
li->startShowItem = li->currItem;
if(li->sb)
newtScrollbarSet(li->sb, li->currItem + 1, li->numItems);
listboxDraw(co);
}
if(co->callback) co->callback(co, co->callbackData);
er.result = ER_SWALLOWED;
break;
case NEWT_KEY_PGUP:
if(li->numItems <= 0) break;
li->startShowItem -= li->curHeight - 1;
if(li->startShowItem < 0)
li->startShowItem = 0;
li->currItem -= li->curHeight - 1;
if(li->currItem < 0)
li->currItem = 0;
newtListboxRealSetCurrent(co);
er.result = ER_SWALLOWED;
break;
case NEWT_KEY_PGDN:
if(li->numItems <= 0) break;
li->startShowItem += li->curHeight;
if(li->startShowItem > (li->numItems - li->curHeight)) {
li->startShowItem = li->numItems - li->curHeight;
}
li->currItem += li->curHeight;
if(li->currItem >= li->numItems) {
li->currItem = li->numItems - 1;
}
newtListboxRealSetCurrent(co);
er.result = ER_SWALLOWED;
break;
case NEWT_KEY_HOME:
if(li->numItems <= 0) break;
newtListboxSetCurrent(co, 0);
er.result = ER_SWALLOWED;
break;
case NEWT_KEY_END:
if(li->numItems <= 0) break;
li->startShowItem = li->numItems - li->curHeight;
if(li->startShowItem < 0)
li->startShowItem = 0;
li->currItem = li->numItems - 1;
newtListboxRealSetCurrent(co);
er.result = ER_SWALLOWED;
break;
default:
if (li->numItems <= 0) break;
if (ev.u.key < NEWT_KEY_EXTRA_BASE && isalpha(ev.u.key)) {
for(i = 0, item = li->boxItems; item != NULL &&
i < li->currItem; i++, item = item->next);
if (item && item->text && (toupper(*item->text) == toupper(ev.u.key))) {
item = item->next;
i++;
} else {
item = li->boxItems;
i = 0;
}
while (item && item->text &&
toupper(*item->text) != toupper(ev.u.key)) {
item = item->next;
i++;
}
if (item) {
li->currItem = i;
if(li->currItem < li->startShowItem ||
li->currItem > li->startShowItem)
li->startShowItem =
li->currItem > li->numItems - li->curHeight ?
li->numItems - li->curHeight :
li->currItem;
if(li->sb)
newtScrollbarSet(li->sb, li->currItem + 1, li->numItems);
newtListboxRealSetCurrent(co);
er.result = ER_SWALLOWED;
}
}
}
break;
case EV_FOCUS:
li->isActive = 1;
listboxDraw(co);
if(li->flags & NEWT_FLAG_SHOWCURSOR)
newtCursorOn();
er.result = ER_SWALLOWED;
break;
case EV_UNFOCUS:
li->isActive = 0;
listboxDraw(co);
if(li->flags & NEWT_FLAG_SHOWCURSOR)
newtCursorOff();
er.result = ER_SWALLOWED;
break;
case EV_MOUSE:
/* if this mouse click was within the listbox, make the current
item the item clicked on. */
/* Up scroll arrow */
if (li->sb &&
ev.u.mouse.x == co->left + co->width - li->bdxAdjust - 1 &&
ev.u.mouse.y == co->top + li->bdyAdjust) {
if(li->numItems <= 0) break;
if(li->currItem > 0) {
li->currItem--;
if(li->currItem < li->startShowItem)
li->startShowItem = li->currItem;
if(li->sb)
newtScrollbarSet(li->sb, li->currItem + 1, li->numItems);
listboxDraw(co);
}
if(co->callback) co->callback(co, co->callbackData);
er.result = ER_SWALLOWED;
break;
}
/* Down scroll arrow */
if (li->sb &&
ev.u.mouse.x == co->left + co->width - li->bdxAdjust - 1 &&
ev.u.mouse.y == co->top + co->height - li->bdyAdjust - 1) {
if(li->numItems <= 0) break;
if(li->currItem < li->numItems - 1) {
li->currItem++;
if(li->currItem > (li->startShowItem + li->curHeight - 1)) {
li->startShowItem = li->currItem - li->curHeight + 1;
if(li->startShowItem + li->curHeight > li->numItems)
li->startShowItem = li->numItems - li->curHeight;
}
if(li->sb)
newtScrollbarSet(li->sb, li->currItem + 1, li->numItems);
listboxDraw(co);
}
if(co->callback) co->callback(co, co->callbackData);
er.result = ER_SWALLOWED;
break;
}
if ((ev.u.mouse.y >= co->top + li->bdyAdjust) &&
(ev.u.mouse.y <= co->top + co->height - (li->bdyAdjust * 2)) &&
(ev.u.mouse.x >= co->left + li->bdxAdjust) &&
(ev.u.mouse.x <= co->left + co->width + (li->bdxAdjust * 2))) {
li->currItem = li->startShowItem +
(ev.u.mouse.y - li->bdyAdjust - co->top);
newtListboxRealSetCurrent(co);
listboxDraw(co);
if(co->callback) co->callback(co, co->callbackData);
er.result = ER_SWALLOWED;
break;
}
}
return er;
}
static void listboxDestroy(newtComponent co) {
struct listbox * li = co->data;
struct items * item, * nextitem;
nextitem = item = li->boxItems;
while (item != NULL) {
nextitem = item->next;
free(item->text);
free(item);
item = nextitem;
}
if (li->sb) li->sb->ops->destroy(li->sb);
free(li);
free(co);
}