ugfx/src/gwin/list.c

596 lines
14 KiB
C
Raw Normal View History

2013-07-17 15:49:21 +00:00
/*
* This file is subject to the terms of the GFX License. If a copy of
* the license was not distributed with this file, you can obtain one at:
*
2013-07-21 20:20:37 +00:00
* http://ugfx.org/license.html
2013-07-17 15:49:21 +00:00
*/
/**
* @file include/gwin/list.h
* @brief GWIN list widget header file.
*
* @defgroup List List
* @ingroup GWIN
*
* @{
*/
#include "gfx.h"
#if GFX_USE_GWIN && GWIN_NEED_LIST
#include "gwin/class_gwin.h"
2013-07-25 17:15:51 +00:00
#include <string.h>
2013-07-28 05:00:08 +00:00
// user for the default drawing routine
#define SCROLLWIDTH 16 // the border from the scroll buttons to the frame
2013-07-28 05:00:08 +00:00
#define ARROW 10 // arrow side length
#define TEXTGAP 1 // extra vertical padding for text
2013-07-27 20:55:32 +00:00
// Macro's to assist in data type conversions
#define gh2obj ((GListObject *)gh)
#define gw2obj ((GListObject *)gw)
#define qi2li ((ListItem *)qi)
#define qix2li ((ListItem *)qix)
#define ple ((GEventGWinList *)pe)
2013-07-25 17:15:51 +00:00
// Flags for the GListObject
2013-07-25 17:15:51 +00:00
#define GLIST_FLG_MULTISELECT (GWIN_FIRST_CONTROL_FLAG << 0)
#define GLIST_FLG_HASIMAGES (GWIN_FIRST_CONTROL_FLAG << 1)
2013-10-23 14:26:34 +00:00
#define GLIST_FLG_SCROLLALWAYS (GWIN_FIRST_CONTROL_FLAG << 2)
// Flags on a ListItem.
#define GLIST_FLG_SELECTED 0x0001
2013-07-25 17:15:51 +00:00
typedef struct ListItem {
gfxQueueASyncItem q_item; // This must be the first member in the struct
uint16_t flags;
2013-07-27 13:23:52 +00:00
uint16_t param; // A parameter the user can specify himself
2013-07-25 17:15:51 +00:00
const char* text;
#if GWIN_NEED_LIST_IMAGES
gdispImage* pimg;
2013-07-25 17:15:51 +00:00
#endif
} ListItem;
static void sendListEvent(GWidgetObject *gw, int item) {
2013-07-25 17:15:51 +00:00
GSourceListener* psl;
GEvent* pe;
// Trigger a GWIN list event
psl = 0;
2013-07-27 13:23:52 +00:00
2013-07-25 17:15:51 +00:00
while ((psl = geventGetSourceListener(GWIDGET_SOURCE, psl))) {
if (!(pe = geventGetEventBuffer(psl)))
continue;
2013-07-25 17:15:51 +00:00
ple->type = GEVENT_GWIN_LIST;
ple->list = (GHandle)gw;
ple->item = item;
2013-07-25 17:15:51 +00:00
geventSendEvent(psl);
}
}
2013-07-17 15:49:21 +00:00
static void gwinListDefaultDraw(GWidgetObject* gw, void* param) {
2013-07-25 17:15:51 +00:00
(void)param;
#if GDISP_NEED_CONVEX_POLYGON
static const point upArrow[] = { {0, ARROW}, {ARROW, ARROW}, {ARROW/2, 0} };
static const point downArrow[] = { {0, 0}, {ARROW, 0}, {ARROW/2, ARROW} };
#endif
2013-07-17 15:49:21 +00:00
const gfxQueueASyncItem* qi;
int i;
coord_t x, y, iheight, iwidth;
color_t fill;
const GColorSet * ps;
#if GWIN_NEED_LIST_IMAGES
coord_t sy;
#endif
ps = (gw->g.flags & GWIN_FLG_ENABLED) ? &gw->pstyle->enabled : &gw->pstyle->disabled;
2013-08-01 08:13:21 +00:00
iheight = gdispGetFontMetric(gw->g.font, fontHeight) + TEXTGAP;
x = 1;
// the scroll area
2013-10-23 14:26:34 +00:00
if (gw2obj->cnt > (gw->g.height-2) / iheight || gw->g.flags & GLIST_FLG_SCROLLALWAYS) {
iwidth = gw->g.width - (SCROLLWIDTH+3);
gdispFillArea(gw->g.x+iwidth+2, gw->g.y+1, SCROLLWIDTH, gw->g.height-2, gdispBlendColor(ps->fill, gw->pstyle->background, 128));
gdispDrawLine(gw->g.x+iwidth+1, gw->g.y+1, gw->g.x+iwidth+1, gw->g.y+gw->g.height-2, ps->edge);
#if GDISP_NEED_CONVEX_POLYGON
gdispFillConvexPoly(gw->g.x+iwidth+((SCROLLWIDTH-ARROW)/2+2), gw->g.y+(ARROW/2+1), upArrow, 3, ps->fill);
gdispFillConvexPoly(gw->g.x+iwidth+((SCROLLWIDTH-ARROW)/2+2), gw->g.y+gw->g.height-(ARROW+ARROW/2+1), downArrow, 3, ps->fill);
#else
#warning "GWIN: Lists display better when GDISP_NEED_CONVEX_POLGON is turned on"
gdispFillArea(gw->g.x+iwidth+((SCROLLWIDTH-ARROW)/2+2), gw->g.y+(ARROW/2+1), ARROW, ARROW, ps->fill);
gdispFillArea(gw->g.x+iwidth+((SCROLLWIDTH-ARROW)/2+2), gw->g.y+gw->g.height-(ARROW+ARROW/2+1), ARROW, ARROW, ps->fill);
#endif
} else
iwidth = gw->g.width - 2;
#if GWIN_NEED_LIST_IMAGES
if ((gw->g.flags & GLIST_FLG_HASIMAGES)) {
x += iheight;
iwidth -= iheight;
}
#endif
// Find the top item
for (qi = gfxQueueASyncPeek(&gw2obj->list_head), i = 0; i < gw2obj->top && qi; qi = gfxQueueASyncNext(qi), i++);
// Draw until we run out of room or items
for (y=1; y+iheight < gw->g.height-1 && qi; qi = gfxQueueASyncNext(qi), y += iheight) {
fill = (qi2li->flags & GLIST_FLG_SELECTED) ? ps->fill : gw->pstyle->background;
#if GWIN_NEED_LIST_IMAGES
if ((gw->g.flags & GLIST_FLG_HASIMAGES)) {
// Clear the image area
gdispFillArea(gw->g.x+1, gw->g.y+y, x-1, iheight, fill);
if (qi2li->pimg && gdispImageIsOpen(qi2li->pimg)) {
// Calculate which image
sy = (qi2li->flags & GLIST_FLG_SELECTED) ? 0 : (iheight-TEXTGAP);
if (!(gw->g.flags & GWIN_FLG_ENABLED))
sy += 2*(iheight-TEXTGAP);
while (sy > qi2li->pimg->height)
sy -= iheight-TEXTGAP;
// Draw the image
gdispImageSetBgColor(qi2li->pimg, fill);
gdispImageDraw(qi2li->pimg, gw->g.x+1, gw->g.y+y, iheight-TEXTGAP, iheight-TEXTGAP, 0, sy);
}
}
#endif
2013-08-01 08:13:21 +00:00
gdispFillStringBox(gw->g.x+x, gw->g.y+y, iwidth, iheight, qi2li->text, gw->g.font, ps->text, fill, justifyLeft);
2013-07-25 17:15:51 +00:00
}
// Fill any remaining item space
if (y < gw->g.height-1)
gdispFillArea(gw->g.x+1, gw->g.y+y, iwidth, gw->g.height-1-y, gw->pstyle->background);
// the list frame
gdispDrawBox(gw->g.x, gw->g.y, gw->g.width, gw->g.height, ps->edge);
2013-07-17 15:49:21 +00:00
}
2013-07-25 17:15:51 +00:00
#if GINPUT_NEED_MOUSE
// a mouse down has occurred over the list area
2013-07-25 17:15:51 +00:00
static void MouseDown(GWidgetObject* gw, coord_t x, coord_t y) {
const gfxQueueASyncItem* qi;
int item, i, pgsz;
coord_t iheight;
(void) x;
2013-08-01 08:13:21 +00:00
iheight = gdispGetFontMetric(gw->g.font, fontHeight) + TEXTGAP;
pgsz = (gw->g.height-2)/iheight;
if (pgsz < 1) pgsz = 1;
// Handle click over the scroll bar
if (gw2obj->cnt > pgsz && x >= gw->g.width-(SCROLLWIDTH+2)) {
if (y < 2*ARROW) {
if (gw2obj->top > 0) {
gw2obj->top--;
_gwidgetRedraw(&gw->g);
}
} else if (y >= gw->g.height - 2*ARROW) {
if (gw2obj->top < gw2obj->cnt - pgsz) {
gw2obj->top++;
_gwidgetRedraw(&gw->g);
}
} else if (y < gw->g.height/2) {
if (gw2obj->top > 0) {
if (gw2obj->top > pgsz)
gw2obj->top -= pgsz;
else
gw2obj->top = 0;
_gwidgetRedraw(&gw->g);
}
} else {
if (gw2obj->top < gw2obj->cnt - pgsz) {
if (gw2obj->top < gw2obj->cnt - 2*pgsz)
gw2obj->top += pgsz;
else
gw2obj->top = gw2obj->cnt - pgsz;
_gwidgetRedraw(&gw->g);
}
2013-07-28 13:26:59 +00:00
}
return;
2013-07-25 17:15:51 +00:00
}
// Handle click over the list area
item = gw2obj->top + y / iheight;
2013-07-27 20:55:32 +00:00
if (item < 0 || item >= gw2obj->cnt)
2013-07-27 20:55:32 +00:00
return;
for(qi = gfxQueueASyncPeek(&gw2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
2013-08-01 05:58:46 +00:00
if ((gw->g.flags & GLIST_FLG_MULTISELECT)) {
if (item == i) {
qi2li->flags ^= GLIST_FLG_SELECTED;
break;
}
} else {
if (item == i)
qi2li->flags |= GLIST_FLG_SELECTED;
else
qi2li->flags &=~ GLIST_FLG_SELECTED;
}
}
2013-07-25 17:15:51 +00:00
_gwidgetRedraw(&gw->g);
sendListEvent(gw, item);
2013-07-25 17:15:51 +00:00
}
#endif
2013-07-28 05:00:08 +00:00
#if GINPUT_NEED_TOGGLE
// a toggle-on has occurred
static void ToggleOn(GWidgetObject *gw, uint16_t role) {
const gfxQueueASyncItem * qi;
const gfxQueueASyncItem * qix;
int i;
2013-07-28 05:00:08 +00:00
switch (role) {
2013-07-28 21:18:59 +00:00
// select down
2013-07-28 05:00:08 +00:00
case 0:
for (i = 0, qi = gfxQueueASyncPeek(&gw2obj->list_head); qi; qi = gfxQueueASyncNext(qi), i++) {
if ((qi2li->flags & GLIST_FLG_SELECTED)) {
qix = gfxQueueASyncNext(qi);
if (qix) {
qi2li->flags &=~ GLIST_FLG_SELECTED;
qix2li->flags |= GLIST_FLG_SELECTED;
_gwidgetRedraw(&gw->g);
}
break;
}
}
2013-07-28 05:00:08 +00:00
break;
2013-07-28 21:18:59 +00:00
// select up
2013-07-28 05:00:08 +00:00
case 1:
qi = gfxQueueASyncPeek(&gw2obj->list_head);
qix = 0;
for (i = 0; qi; qix = qi, qi = gfxQueueASyncNext(qi), i++) {
if ((qi2li->flags & GLIST_FLG_SELECTED))
if (qix) {
qi2li->flags &=~ GLIST_FLG_SELECTED;
qix2li->flags |= GLIST_FLG_SELECTED;
_gwidgetRedraw(&gw->g);
}
break;
}
}
2013-07-28 05:00:08 +00:00
break;
}
}
2013-07-28 21:18:59 +00:00
static void ToggleAssign(GWidgetObject *gw, uint16_t role, uint16_t instance) {
if (role)
gw2obj->t_up = instance;
2013-07-28 21:18:59 +00:00
else
gw2obj->t_dn = instance;
2013-07-28 21:18:59 +00:00
}
static uint16_t ToggleGet(GWidgetObject *gw, uint16_t role) {
return role ? gw2obj->t_up : gw2obj->t_dn;
2013-07-28 21:18:59 +00:00
}
2013-07-28 05:00:08 +00:00
#endif
static void _destroy(GHandle gh) {
2013-07-27 23:15:36 +00:00
const gfxQueueASyncItem* qi;
while((qi = gfxQueueASyncGet(&gh2obj->list_head)))
gfxFree((void *)qi);
2013-07-27 20:55:32 +00:00
_gwidgetDestroy(gh);
}
2013-07-17 15:49:21 +00:00
static const gwidgetVMT listVMT = {
{
"List", // The class name
sizeof(GListObject), // The object size
2013-07-27 20:55:32 +00:00
_destroy, // The destroy routine
2013-07-17 15:49:21 +00:00
_gwidgetRedraw, // The redraw routine
0, // The after-clear routine
},
gwinListDefaultDraw, // default drawing routine
2013-07-25 17:15:51 +00:00
#if GINPUT_NEED_MOUSE
2013-07-17 15:49:21 +00:00
{
2013-07-25 17:15:51 +00:00
MouseDown,
2013-07-17 15:49:21 +00:00
0,
0,
},
#endif
#if GINPUT_NEED_TOGGLE
{
2013-07-28 05:04:04 +00:00
2, // two toggle roles
2013-07-28 21:18:59 +00:00
ToggleAssign, // Assign toggles
2013-07-28 05:00:08 +00:00
ToggleGet, // get toggles
2013-07-17 15:49:21 +00:00
0,
2013-07-28 05:00:08 +00:00
ToggleOn, // process toggle on event
2013-07-17 15:49:21 +00:00
},
#endif
#if GINPUT_NEED_DIAL
{
0,
0,
0,
0,
},
#endif
};
2013-08-01 05:58:46 +00:00
GHandle gwinListCreate(GListObject* gobj, GWidgetInit* pInit, bool_t multiselect) {
if (!(gobj = (GListObject *)_gwidgetCreate(&gobj->w, pInit, &listVMT)))
2013-07-17 15:49:21 +00:00
return 0;
2013-07-25 17:15:51 +00:00
// initialize the item queue
gfxQueueASyncInit(&gobj->list_head);
gobj->cnt = 0;
gobj->top = 0;
2013-08-01 05:58:46 +00:00
if (multiselect)
gobj->w.g.flags |= GLIST_FLG_MULTISELECT;
2013-10-23 14:26:34 +00:00
gobj->w.g.flags |= GLIST_FLG_SCROLLALWAYS;
2013-07-25 17:15:51 +00:00
gwinSetVisible(&gobj->w.g, pInit->g.show);
2013-07-17 15:49:21 +00:00
return (GHandle)gobj;
2013-07-17 15:49:21 +00:00
}
2013-10-23 14:26:34 +00:00
void gwinListSetScroll(GHandle gh, scroll_t flag) {
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return 0;
switch (flag) {
case scrollAlways:
((GListObject*)gh)->w.g.flags |= GLIST_FLG_SCROLLALWAYS;
break;
case scrollAuto:
((GListObject*)gh)->w.g.flags &=~ GLIST_FLG_SCROLLALWAYS;
break;
}
}
2013-07-25 17:15:51 +00:00
int gwinListAddItem(GHandle gh, const char* item_name, bool_t useAlloc) {
ListItem *newItem;
2013-07-25 17:15:51 +00:00
if (useAlloc) {
if (!(newItem = (ListItem *)gfxAlloc(sizeof(ListItem)+strlen(item_name)+1)))
return -1;
strcpy((char *)(newItem+1), item_name);
item_name = (const char *)(newItem+1);
} else {
if (!(newItem = (ListItem *)gfxAlloc(sizeof(ListItem))))
return -1;
2013-07-25 17:15:51 +00:00
}
// the item is not selected when added
newItem->flags = 0;
newItem->param = 0;
newItem->text = item_name;
#if GWIN_NEED_LIST_IMAGES
newItem->pimg = 0;
#endif
2013-07-25 17:15:51 +00:00
2013-07-28 05:00:08 +00:00
// select the item if it's the first in the list
2013-08-01 05:58:46 +00:00
if (gh2obj->cnt == 0 && !(gh->flags & GLIST_FLG_MULTISELECT))
2013-07-28 05:00:08 +00:00
newItem->flags |= GLIST_FLG_SELECTED;
2013-07-25 17:15:51 +00:00
// add the new item to the list
gfxQueueASyncPut(&gh2obj->list_head, &newItem->q_item);
2013-07-25 17:15:51 +00:00
// increment the total amount of entries in the list widget
gh2obj->cnt++;
_gwidgetRedraw(gh);
2013-07-27 20:55:32 +00:00
// return the position in the list (-1 because we start with index 0)
return gh2obj->cnt-1;
2013-07-25 17:15:51 +00:00
}
const char* gwinListItemGetText(GHandle gh, int item) {
const gfxQueueASyncItem* qi;
int i;
2013-07-27 20:55:32 +00:00
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return 0;
// watch out for an invalid item
if (item < 0 || item >= gh2obj->cnt)
2013-07-27 20:55:32 +00:00
return 0;
for(qi = gfxQueueASyncPeek(&gh2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
if (i == item)
return qi2li->text;
2013-07-27 20:55:32 +00:00
}
return 0;
2013-07-27 20:55:32 +00:00
}
int gwinListFindText(GHandle gh, const char* text) {
const gfxQueueASyncItem* qi;
int i;
2013-07-27 20:55:32 +00:00
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return -1;
// watch out for NULL pointers
if (!text)
return -1;
for(qi = gfxQueueASyncPeek(&gh2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
2013-07-27 20:55:32 +00:00
if (strcmp(((ListItem *)qi)->text, text) == 0)
return i;
}
return -1;
}
2013-07-25 17:15:51 +00:00
int gwinListGetSelected(GHandle gh) {
const gfxQueueASyncItem * qi;
int i;
2013-07-25 17:15:51 +00:00
2013-07-27 20:55:32 +00:00
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return -1;
2013-07-25 17:15:51 +00:00
2013-08-01 05:58:46 +00:00
// Multi-select always returns -1. Use gwinListItemIsSelected() instead
if ((gh->flags & GLIST_FLG_MULTISELECT))
return -1;
for(qi = gfxQueueASyncPeek(&gh2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
if (qi2li->flags & GLIST_FLG_SELECTED)
return i;
}
return -1;
2013-07-25 17:15:51 +00:00
}
2013-07-27 20:55:32 +00:00
void gwinListItemSetParam(GHandle gh, int item, uint16_t param) {
const gfxQueueASyncItem * qi;
int i;
2013-07-27 20:55:32 +00:00
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return;
// watch out for an invalid item
if (item < 0 || item > (gh2obj->cnt) - 1)
2013-07-27 20:55:32 +00:00
return;
for(qi = gfxQueueASyncPeek(&gh2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
if (i == item) {
qi2li->param = param;
break;
}
2013-07-27 20:55:32 +00:00
}
}
void gwinListDeleteAll(GHandle gh) {
gfxQueueASyncItem* qi;
2013-07-27 20:55:32 +00:00
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return;
while((qi = gfxQueueASyncGet(&gh2obj->list_head)))
gfxFree(qi);
gh->flags &= ~GLIST_FLG_HASIMAGES;
gh2obj->cnt = 0;
gh2obj->top = 0;
_gwidgetRedraw(gh);
2013-07-27 20:55:32 +00:00
}
void gwinListItemDelete(GHandle gh, int item) {
const gfxQueueASyncItem * qi;
int i;
2013-07-27 20:55:32 +00:00
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return;
// watch out for an invalid item
if (item < 0 || item >= gh2obj->cnt)
2013-07-27 20:55:32 +00:00
return;
for(qi = gfxQueueASyncPeek(&gh2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
if (i == item) {
gfxQueueASyncRemove(&gh2obj->list_head, (gfxQueueASyncItem*)qi);
gfxFree((void *)qi);
if (gh2obj->top >= item && gh2obj->top)
gh2obj->top--;
_gwidgetRedraw(gh);
break;
}
2013-07-27 20:55:32 +00:00
}
}
uint16_t gwinListItemGetParam(GHandle gh, int item) {
const gfxQueueASyncItem * qi;
int i;
2013-07-27 20:55:32 +00:00
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return 0;
// watch out for an invalid item
if (item < 0 || item > (gh2obj->cnt) - 1)
2013-07-27 20:55:32 +00:00
return 0;
for(qi = gfxQueueASyncPeek(&gh2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
if (i == item)
return qi2li->param;
2013-07-27 20:55:32 +00:00
}
return 0;
2013-07-27 20:55:32 +00:00
}
bool_t gwinListItemIsSelected(GHandle gh, int item) {
const gfxQueueASyncItem * qi;
int i;
2013-07-27 20:55:32 +00:00
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return FALSE;
// watch out for an invalid item
if (item < 0 || item > (gh2obj->cnt) - 1)
2013-07-27 20:55:32 +00:00
return FALSE;
for(qi = gfxQueueASyncPeek(&gh2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
if (i == item)
return (qi2li->flags & GLIST_FLG_SELECTED) ? TRUE : FALSE;
2013-07-27 20:55:32 +00:00
}
return FALSE;
2013-07-27 20:55:32 +00:00
}
int gwinListItemCount(GHandle gh) {
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return 0;
return gh2obj->cnt;
2013-07-27 20:55:32 +00:00
}
2013-10-22 22:18:03 +00:00
const char* gwinListGetSelectedText(GHandle gh) {
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return 0;
// return NULL if nothing is selected (or multi-select)
if (gwinListGetSelected(gh) < 0)
return 0;
return gwinListItemGetText(gh, gwinListGetSelected(gh));
}
#if GWIN_NEED_LIST_IMAGES
void gwinListItemSetImage(GHandle gh, int item, gdispImage *pimg) {
const gfxQueueASyncItem * qi;
int i;
// is it a valid handle?
if (gh->vmt != (gwinVMT *)&listVMT)
return;
// watch out for an invalid item
if (item < 0 || item > (gh2obj->cnt) - 1)
return;
for(qi = gfxQueueASyncPeek(&gh2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
if (i == item) {
qi2li->pimg = pimg;
if (pimg)
gh->flags |= GLIST_FLG_HASIMAGES;
break;
}
}
}
#endif
2013-07-17 15:49:21 +00:00
#endif // GFX_USE_GWIN && GWIN_NEED_LIST
2013-07-21 20:02:57 +00:00
/** @} */
2013-07-17 15:49:21 +00:00