mirror of
https://github.com/gryf/wmaker.git
synced 2025-12-19 12:28:22 +01:00
1230 lines
30 KiB
C
1230 lines
30 KiB
C
|
|
|
|
|
|
|
|
#include "WINGsP.h"
|
|
|
|
char *WMListDidScrollNotification = "WMListDidScrollNotification";
|
|
char *WMListSelectionDidChangeNotification = "WMListSelectionDidChangeNotification";
|
|
|
|
typedef struct W_List {
|
|
W_Class widgetClass;
|
|
W_View *view;
|
|
|
|
WMArray *items; /* list of WMListItem */
|
|
WMArray *selectedItems; /* list of selected WMListItems */
|
|
|
|
short itemHeight;
|
|
|
|
int topItem; /* index of first visible item */
|
|
|
|
short fullFitLines; /* no of lines that fit entirely */
|
|
|
|
void *clientData;
|
|
WMAction *action;
|
|
void *doubleClientData;
|
|
WMAction *doubleAction;
|
|
|
|
WMListDrawProc *draw;
|
|
|
|
WMHandlerID *idleID; /* for updating the scroller after adding elements */
|
|
|
|
WMHandlerID *selectID; /* for selecting items in list while scrolling */
|
|
|
|
WMScroller *vScroller;
|
|
|
|
struct {
|
|
unsigned int allowMultipleSelection:1;
|
|
unsigned int allowEmptySelection:1;
|
|
unsigned int userDrawn:1;
|
|
unsigned int userItemHeight:1;
|
|
unsigned int dontFitAll:1; /* 1 = last item won't be fully visible */
|
|
unsigned int redrawPending:1;
|
|
unsigned int buttonPressed:1;
|
|
unsigned int buttonWasPressed:1;
|
|
} flags;
|
|
} List;
|
|
|
|
|
|
|
|
#define DEFAULT_WIDTH 150
|
|
#define DEFAULT_HEIGHT 150
|
|
|
|
#define SCROLL_DELAY 100
|
|
|
|
|
|
static void destroyList(List *lPtr);
|
|
static void paintList(List *lPtr);
|
|
|
|
|
|
static void handleEvents(XEvent *event, void *data);
|
|
static void handleActionEvents(XEvent *event, void *data);
|
|
|
|
static void updateScroller(void *data);
|
|
static void scrollForwardSelecting(void *data);
|
|
static void scrollBackwardSelecting(void *data);
|
|
|
|
static void vScrollCallBack(WMWidget *scroller, void *self);
|
|
|
|
static void toggleItemSelection(WMList *lPtr, int index);
|
|
|
|
static void updateGeometry(WMList *lPtr);
|
|
static void didResizeList();
|
|
|
|
static void unselectAllListItems(WMList *lPtr, WMListItem *exceptThis);
|
|
|
|
|
|
W_ViewDelegate _ListViewDelegate = {
|
|
NULL,
|
|
NULL,
|
|
didResizeList,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
|
|
static void
|
|
releaseItem(void *data)
|
|
{
|
|
WMListItem *item = (WMListItem*)data;
|
|
|
|
if (item->text)
|
|
wfree(item->text);
|
|
wfree(item);
|
|
}
|
|
|
|
|
|
WMList*
|
|
WMCreateList(WMWidget *parent)
|
|
{
|
|
List *lPtr;
|
|
W_Screen *scrPtr = W_VIEW(parent)->screen;
|
|
|
|
lPtr = wmalloc(sizeof(List));
|
|
memset(lPtr, 0, sizeof(List));
|
|
|
|
lPtr->widgetClass = WC_List;
|
|
|
|
lPtr->view = W_CreateView(W_VIEW(parent));
|
|
if (!lPtr->view) {
|
|
wfree(lPtr);
|
|
return NULL;
|
|
}
|
|
lPtr->view->self = lPtr;
|
|
|
|
lPtr->view->delegate = &_ListViewDelegate;
|
|
|
|
WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
|
|
|ClientMessageMask, handleEvents, lPtr);
|
|
|
|
WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
|
|
|EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
|
|
handleActionEvents, lPtr);
|
|
|
|
lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
|
|
|
|
lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
|
|
lPtr->selectedItems = WMCreateArray(4);
|
|
|
|
/* create the vertical scroller */
|
|
lPtr->vScroller = WMCreateScroller(lPtr);
|
|
WMMoveWidget(lPtr->vScroller, 1, 1);
|
|
WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
|
|
|
|
WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
|
|
|
|
/* make the scroller map itself when it's realized */
|
|
WMMapWidget(lPtr->vScroller);
|
|
|
|
W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
|
|
|
return lPtr;
|
|
}
|
|
|
|
|
|
void
|
|
WMSetListAllowMultipleSelection(WMList *lPtr, Bool flag)
|
|
{
|
|
lPtr->flags.allowMultipleSelection = flag ? 1 : 0;
|
|
}
|
|
|
|
|
|
void
|
|
WMSetListAllowEmptySelection(WMList *lPtr, Bool flag)
|
|
{
|
|
lPtr->flags.allowEmptySelection = flag ? 1 : 0;
|
|
}
|
|
|
|
|
|
static int
|
|
comparator(const void *a, const void *b)
|
|
{
|
|
return (strcmp((*(WMListItem**)a)->text, (*(WMListItem**)b)->text));
|
|
}
|
|
|
|
|
|
void
|
|
WMSortListItems(WMList *lPtr)
|
|
{
|
|
WMSortArray(lPtr->items, comparator);
|
|
|
|
paintList(lPtr);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
WMSortListItemsWithComparer(WMList *lPtr, WMCompareDataProc *func)
|
|
{
|
|
WMSortArray(lPtr->items, func);
|
|
|
|
paintList(lPtr);
|
|
}
|
|
|
|
|
|
|
|
WMListItem*
|
|
WMInsertListItem(WMList *lPtr, int row, char *text)
|
|
{
|
|
WMListItem *item;
|
|
|
|
CHECK_CLASS(lPtr, WC_List);
|
|
|
|
item = wmalloc(sizeof(WMListItem));
|
|
memset(item, 0, sizeof(WMListItem));
|
|
item->text = wstrdup(text);
|
|
|
|
row = WMIN(row, WMGetArrayItemCount(lPtr->items));
|
|
|
|
if (row < 0)
|
|
WMAddToArray(lPtr->items, item);
|
|
else
|
|
WMInsertInArray(lPtr->items, row, item);
|
|
|
|
/* update the scroller when idle, so that we don't waste time
|
|
* updating it when another item is going to be added later */
|
|
if (!lPtr->idleID) {
|
|
lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
|
|
void
|
|
WMRemoveListItem(WMList *lPtr, int row)
|
|
{
|
|
WMListItem *item;
|
|
int topItem = lPtr->topItem;
|
|
int selNotify = 0;
|
|
|
|
CHECK_CLASS(lPtr, WC_List);
|
|
|
|
/*wassertr(row>=0 && row<WMGetArrayItemCount(lPtr->items));*/
|
|
if (row<0 || row>=WMGetArrayItemCount(lPtr->items))
|
|
return;
|
|
|
|
item = WMGetFromArray(lPtr->items, row);
|
|
if (item->selected) {
|
|
WMRemoveFromArray(lPtr->selectedItems, item);
|
|
selNotify = 1;
|
|
}
|
|
|
|
if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
|
|
lPtr->topItem--;
|
|
if (lPtr->topItem < 0)
|
|
lPtr->topItem = 0;
|
|
|
|
WMDeleteFromArray(lPtr->items, row);
|
|
|
|
if (!lPtr->idleID) {
|
|
lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
|
|
}
|
|
if (lPtr->topItem != topItem) {
|
|
WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
|
|
}
|
|
if (selNotify) {
|
|
WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
WMListItem*
|
|
WMGetListItem(WMList *lPtr, int row)
|
|
{
|
|
return WMGetFromArray(lPtr->items, row);
|
|
}
|
|
|
|
|
|
WMArray*
|
|
WMGetListItems(WMList *lPtr)
|
|
{
|
|
return lPtr->items;
|
|
}
|
|
|
|
|
|
void
|
|
WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
|
|
{
|
|
lPtr->flags.userDrawn = 1;
|
|
lPtr->draw = proc;
|
|
}
|
|
|
|
|
|
void
|
|
WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
|
|
{
|
|
assert(height > 0);
|
|
|
|
lPtr->flags.userItemHeight = 1;
|
|
lPtr->itemHeight = height;
|
|
|
|
updateGeometry(lPtr);
|
|
}
|
|
|
|
|
|
void
|
|
WMClearList(WMList *lPtr)
|
|
{
|
|
int selNo = WMGetArrayItemCount(lPtr->selectedItems);
|
|
|
|
WMEmptyArray(lPtr->selectedItems);
|
|
WMEmptyArray(lPtr->items);
|
|
|
|
lPtr->topItem = 0;
|
|
|
|
if (!lPtr->idleID) {
|
|
WMDeleteIdleHandler(lPtr->idleID);
|
|
lPtr->idleID = NULL;
|
|
}
|
|
if (lPtr->selectID) {
|
|
WMDeleteTimerHandler(lPtr->selectID);
|
|
lPtr->selectID = NULL;
|
|
}
|
|
if (lPtr->view->flags.realized) {
|
|
updateScroller(lPtr);
|
|
}
|
|
if (selNo > 0) {
|
|
WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
|
|
{
|
|
lPtr->action = action;
|
|
lPtr->clientData = clientData;
|
|
}
|
|
|
|
|
|
void
|
|
WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
|
|
{
|
|
lPtr->doubleAction = action;
|
|
lPtr->doubleClientData = clientData;
|
|
}
|
|
|
|
|
|
WMArray*
|
|
WMGetListSelectedItems(WMList *lPtr)
|
|
{
|
|
return lPtr->selectedItems;
|
|
}
|
|
|
|
|
|
WMListItem*
|
|
WMGetListSelectedItem(WMList *lPtr)
|
|
{
|
|
return WMGetFromArray(lPtr->selectedItems, 0);
|
|
}
|
|
|
|
|
|
int
|
|
WMGetListSelectedItemRow(WMList *lPtr)
|
|
{
|
|
WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
|
|
|
|
return (item!=NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
|
|
}
|
|
|
|
|
|
int
|
|
WMGetListItemHeight(WMList *lPtr)
|
|
{
|
|
return lPtr->itemHeight;
|
|
}
|
|
|
|
|
|
void
|
|
WMSetListPosition(WMList *lPtr, int row)
|
|
{
|
|
lPtr->topItem = row;
|
|
if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
|
|
lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
|
|
|
|
if (lPtr->topItem < 0)
|
|
lPtr->topItem = 0;
|
|
|
|
if (lPtr->view->flags.realized)
|
|
updateScroller(lPtr);
|
|
}
|
|
|
|
|
|
void
|
|
WMSetListBottomPosition(WMList *lPtr, int row)
|
|
{
|
|
if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
|
|
lPtr->topItem = row - lPtr->fullFitLines;
|
|
if (lPtr->topItem < 0)
|
|
lPtr->topItem = 0;
|
|
if (lPtr->view->flags.realized)
|
|
updateScroller(lPtr);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
WMGetListNumberOfRows(WMList *lPtr)
|
|
{
|
|
return WMGetArrayItemCount(lPtr->items);
|
|
}
|
|
|
|
|
|
int
|
|
WMGetListPosition(WMList *lPtr)
|
|
{
|
|
return lPtr->topItem;
|
|
}
|
|
|
|
|
|
Bool
|
|
WMListAllowsMultipleSelection(WMList *lPtr)
|
|
{
|
|
return lPtr->flags.allowMultipleSelection;
|
|
}
|
|
|
|
|
|
Bool
|
|
WMListAllowsEmptySelection(WMList *lPtr)
|
|
{
|
|
return lPtr->flags.allowEmptySelection;
|
|
}
|
|
|
|
|
|
static void
|
|
scrollByAmount(WMList *lPtr, int amount)
|
|
{
|
|
int itemCount = WMGetArrayItemCount(lPtr->items);
|
|
|
|
if ((amount < 0 && lPtr->topItem > 0) ||
|
|
(amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
|
|
|
|
lPtr->topItem += amount;
|
|
if (lPtr->topItem < 0)
|
|
lPtr->topItem = 0;
|
|
if (lPtr->topItem + lPtr->fullFitLines > itemCount)
|
|
lPtr->topItem = itemCount - lPtr->fullFitLines;
|
|
|
|
updateScroller(lPtr);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
vScrollCallBack(WMWidget *scroller, void *self)
|
|
{
|
|
WMList *lPtr = (WMList*)self;
|
|
int height;
|
|
int oldTopItem = lPtr->topItem;
|
|
int itemCount = WMGetArrayItemCount(lPtr->items);
|
|
|
|
height = lPtr->view->size.height - 4;
|
|
|
|
switch (WMGetScrollerHitPart((WMScroller*)scroller)) {
|
|
case WSDecrementLine:
|
|
scrollByAmount(lPtr, -1);
|
|
break;
|
|
|
|
case WSIncrementLine:
|
|
scrollByAmount(lPtr, 1);
|
|
break;
|
|
|
|
case WSDecrementPage:
|
|
scrollByAmount(lPtr, -lPtr->fullFitLines+(1-lPtr->flags.dontFitAll)+1);
|
|
break;
|
|
|
|
case WSIncrementPage:
|
|
scrollByAmount(lPtr, lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1);
|
|
break;
|
|
|
|
case WSDecrementWheel:
|
|
scrollByAmount(lPtr, -lPtr->fullFitLines / 3);
|
|
break;
|
|
|
|
case WSIncrementWheel:
|
|
scrollByAmount(lPtr, lPtr->fullFitLines / 3);
|
|
break;
|
|
|
|
case WSKnob:
|
|
lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
|
|
(float)(itemCount - lPtr->fullFitLines);
|
|
|
|
if (oldTopItem != lPtr->topItem)
|
|
paintList(lPtr); /* use updateScroller(lPtr) here? -Dan */
|
|
break;
|
|
|
|
case WSKnobSlot:
|
|
case WSNoPart:
|
|
default:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
|
|
if (lPtr->topItem != oldTopItem)
|
|
WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
paintItem(List *lPtr, int index)
|
|
{
|
|
WMView *view = lPtr->view;
|
|
W_Screen *scr = view->screen;
|
|
int width, height, x, y;
|
|
WMListItem *itemPtr;
|
|
|
|
itemPtr = WMGetFromArray(lPtr->items, index);
|
|
|
|
width = lPtr->view->size.width - 2 - 19;
|
|
height = lPtr->itemHeight;
|
|
x = 19;
|
|
y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
|
|
|
|
if (lPtr->flags.userDrawn) {
|
|
WMRect rect;
|
|
int flags;
|
|
|
|
rect.size.width = width;
|
|
rect.size.height = height;
|
|
rect.pos.x = x;
|
|
rect.pos.y = y;
|
|
|
|
flags = itemPtr->uflags;
|
|
if (itemPtr->disabled)
|
|
flags |= WLDSDisabled;
|
|
if (itemPtr->selected)
|
|
flags |= WLDSSelected;
|
|
if (itemPtr->isBranch)
|
|
flags |= WLDSIsBranch;
|
|
|
|
if (lPtr->draw)
|
|
(*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
|
|
&rect);
|
|
} else {
|
|
if (itemPtr->selected) {
|
|
XFillRectangle(scr->display, view->window, WMColorGC(scr->white),
|
|
x, y, width, height);
|
|
} else {
|
|
XClearArea(scr->display, view->window, x, y, width, height, False);
|
|
}
|
|
|
|
W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
|
|
WALeft, WMColorGC(scr->black), False,
|
|
itemPtr->text, strlen(itemPtr->text));
|
|
}
|
|
|
|
if ((index-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight >
|
|
lPtr->view->size.height-2) {
|
|
W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
|
|
lPtr->view->size.width, lPtr->view->size.height,
|
|
WRSunken);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
paintList(List *lPtr)
|
|
{
|
|
W_Screen *scrPtr = lPtr->view->screen;
|
|
int i, lim;
|
|
|
|
if (!lPtr->view->flags.mapped)
|
|
return;
|
|
|
|
if (WMGetArrayItemCount(lPtr->items) > 0) {
|
|
if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
|
|
> WMGetArrayItemCount(lPtr->items)) {
|
|
|
|
lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
|
|
XClearArea(scrPtr->display, lPtr->view->window, 19,
|
|
2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
|
|
lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
|
|
} else {
|
|
lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
|
|
}
|
|
for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
|
|
paintItem(lPtr, i);
|
|
}
|
|
} else {
|
|
XClearWindow(scrPtr->display, lPtr->view->window);
|
|
}
|
|
W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
|
|
lPtr->view->size.height, WRSunken);
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
scrollTo(List *lPtr, int newTop)
|
|
{
|
|
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
updateScroller(void *data)
|
|
{
|
|
List *lPtr = (List*)data;
|
|
|
|
float knobProportion, floatValue, tmp;
|
|
int count = WMGetArrayItemCount(lPtr->items);
|
|
|
|
if (lPtr->idleID)
|
|
WMDeleteIdleHandler(lPtr->idleID);
|
|
lPtr->idleID = NULL;
|
|
|
|
paintList(lPtr);
|
|
|
|
if (count == 0 || count <= lPtr->fullFitLines)
|
|
WMSetScrollerParameters(lPtr->vScroller, 0, 1);
|
|
else {
|
|
tmp = lPtr->fullFitLines;
|
|
knobProportion = tmp/(float)count;
|
|
|
|
floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
|
|
|
|
WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
scrollForwardSelecting(void *data)
|
|
{
|
|
List *lPtr = (List*)data;
|
|
int lastSelected;
|
|
|
|
lastSelected = lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll-1;
|
|
|
|
if (lastSelected >= WMGetArrayItemCount(lPtr->items)-1) {
|
|
lPtr->selectID = NULL;
|
|
if (lPtr->flags.dontFitAll)
|
|
scrollByAmount(lPtr, 1);
|
|
return;
|
|
}
|
|
|
|
/* selecting NEEDS to be done before scrolling to avoid flickering */
|
|
if (lPtr->flags.allowMultipleSelection) {
|
|
WMListItem *item;
|
|
WMRange range;
|
|
|
|
item = WMGetFromArray(lPtr->selectedItems, 0);
|
|
range.position = WMGetFirstInArray(lPtr->items, item);
|
|
if (lastSelected+1 >= range.position) {
|
|
range.count = lastSelected - range.position + 2;
|
|
} else {
|
|
range.count = lastSelected - range.position;
|
|
}
|
|
WMSetListSelectionToRange(lPtr, range);
|
|
} else {
|
|
WMSelectListItem(lPtr, lastSelected+1);
|
|
}
|
|
scrollByAmount(lPtr, 1);
|
|
|
|
lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting,
|
|
lPtr);
|
|
}
|
|
|
|
|
|
static void
|
|
scrollBackwardSelecting(void *data)
|
|
{
|
|
List *lPtr = (List*)data;
|
|
|
|
if (lPtr->topItem < 1) {
|
|
lPtr->selectID = NULL;
|
|
return;
|
|
}
|
|
|
|
/* selecting NEEDS to be done before scrolling to avoid flickering */
|
|
if (lPtr->flags.allowMultipleSelection) {
|
|
WMListItem *item;
|
|
WMRange range;
|
|
|
|
item = WMGetFromArray(lPtr->selectedItems, 0);
|
|
range.position = WMGetFirstInArray(lPtr->items, item);
|
|
if (lPtr->topItem-1 >= range.position) {
|
|
range.count = lPtr->topItem - range.position;
|
|
} else {
|
|
range.count = lPtr->topItem - range.position - 2;
|
|
}
|
|
WMSetListSelectionToRange(lPtr, range);
|
|
} else {
|
|
WMSelectListItem(lPtr, lPtr->topItem-1);
|
|
}
|
|
scrollByAmount(lPtr, -1);
|
|
|
|
lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting,
|
|
lPtr);
|
|
}
|
|
|
|
|
|
static void
|
|
handleEvents(XEvent *event, void *data)
|
|
{
|
|
List *lPtr = (List*)data;
|
|
|
|
CHECK_CLASS(data, WC_List);
|
|
|
|
|
|
switch (event->type) {
|
|
case Expose:
|
|
if (event->xexpose.count!=0)
|
|
break;
|
|
paintList(lPtr);
|
|
break;
|
|
|
|
case DestroyNotify:
|
|
destroyList(lPtr);
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
matchTitle(void *item, void *title)
|
|
{
|
|
return (strcmp(((WMListItem*)item)->text, (char*)title)==0 ? 1 : 0);
|
|
}
|
|
|
|
|
|
int
|
|
WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
|
|
{
|
|
return WMFindInArray(lPtr->items, matchTitle, title);
|
|
}
|
|
|
|
|
|
void
|
|
WMSelectListItem(WMList *lPtr, int row)
|
|
{
|
|
WMListItem *item;
|
|
|
|
if (row >= WMGetArrayItemCount(lPtr->items))
|
|
return;
|
|
|
|
if (row < 0) {
|
|
/* row = -1 will deselects all for backward compatibility.
|
|
* will be removed later. -Dan */
|
|
WMUnselectAllListItems(lPtr);
|
|
return;
|
|
}
|
|
|
|
item = WMGetFromArray(lPtr->items, row);
|
|
if (item->selected)
|
|
return; /* Return if already selected */
|
|
|
|
if (!lPtr->flags.allowMultipleSelection) {
|
|
/* unselect previous selected items */
|
|
unselectAllListItems(lPtr, NULL);
|
|
}
|
|
|
|
/* select item */
|
|
item->selected = 1;
|
|
WMAddToArray(lPtr->selectedItems, item);
|
|
|
|
if (lPtr->view->flags.mapped && row>=lPtr->topItem
|
|
&& row<=lPtr->topItem+lPtr->fullFitLines) {
|
|
paintItem(lPtr, row);
|
|
}
|
|
|
|
WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
|
|
}
|
|
|
|
|
|
void
|
|
WMUnselectListItem(WMList *lPtr, int row)
|
|
{
|
|
WMListItem *item = WMGetFromArray(lPtr->items, row);
|
|
|
|
if (!item || !item->selected)
|
|
return;
|
|
|
|
if (!lPtr->flags.allowEmptySelection &&
|
|
WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
|
|
return;
|
|
}
|
|
|
|
item->selected = 0;
|
|
WMRemoveFromArray(lPtr->selectedItems, item);
|
|
|
|
if (lPtr->view->flags.mapped && row>=lPtr->topItem
|
|
&& row<=lPtr->topItem+lPtr->fullFitLines) {
|
|
paintItem(lPtr, row);
|
|
}
|
|
|
|
WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
|
|
}
|
|
|
|
|
|
void
|
|
WMSelectListItemsInRange(WMList *lPtr, WMRange range)
|
|
{
|
|
WMListItem *item;
|
|
int position = range.position, k = 1, notify = 0;
|
|
int total = WMGetArrayItemCount(lPtr->items);
|
|
|
|
if (!lPtr->flags.allowMultipleSelection)
|
|
return;
|
|
if (range.count==0)
|
|
return; /* Nothing to select */
|
|
|
|
if (range.count < 0) {
|
|
range.count = -range.count;
|
|
k = -1;
|
|
}
|
|
|
|
for (; range.count>0 && position>=0 && position<total; range.count--) {
|
|
item = WMGetFromArray(lPtr->items, position);
|
|
if (!item->selected) {
|
|
item->selected = 1;
|
|
WMAddToArray(lPtr->selectedItems, item);
|
|
if (lPtr->view->flags.mapped && position>=lPtr->topItem
|
|
&& position<=lPtr->topItem+lPtr->fullFitLines) {
|
|
paintItem(lPtr, position);
|
|
}
|
|
notify = 1;
|
|
}
|
|
position += k;
|
|
}
|
|
|
|
if (notify) {
|
|
WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
WMSetListSelectionToRange(WMList *lPtr, WMRange range)
|
|
{
|
|
WMListItem *item;
|
|
int mark1, mark2, i, k;
|
|
int position = range.position, notify = 0;
|
|
int total = WMGetArrayItemCount(lPtr->items);
|
|
|
|
if (!lPtr->flags.allowMultipleSelection)
|
|
return;
|
|
|
|
if (range.count==0) {
|
|
WMUnselectAllListItems(lPtr);
|
|
return;
|
|
}
|
|
|
|
if (range.count < 0) {
|
|
mark1 = range.position + range.count + 1;
|
|
mark2 = range.position + 1;
|
|
range.count = -range.count;
|
|
k = -1;
|
|
} else {
|
|
mark1 = range.position;
|
|
mark2 = range.position + range.count;
|
|
k = 1;
|
|
}
|
|
if (mark1 > total)
|
|
mark1 = total;
|
|
if (mark2 < 0)
|
|
mark2 = 0;
|
|
|
|
WMEmptyArray(lPtr->selectedItems);
|
|
|
|
for (i=0; i<mark1; i++) {
|
|
item = WMGetFromArray(lPtr->items, i);
|
|
if (item->selected) {
|
|
item->selected = 0;
|
|
if (lPtr->view->flags.mapped && i>=lPtr->topItem
|
|
&& i<=lPtr->topItem+lPtr->fullFitLines) {
|
|
paintItem(lPtr, i);
|
|
}
|
|
notify = 1;
|
|
}
|
|
}
|
|
for (; range.count>0 && position>=0 && position<total; range.count--) {
|
|
item = WMGetFromArray(lPtr->items, position);
|
|
if (!item->selected) {
|
|
item->selected = 1;
|
|
if (lPtr->view->flags.mapped && position>=lPtr->topItem
|
|
&& position<=lPtr->topItem+lPtr->fullFitLines) {
|
|
paintItem(lPtr, position);
|
|
}
|
|
notify = 1;
|
|
}
|
|
WMAddToArray(lPtr->selectedItems, item);
|
|
position += k;
|
|
}
|
|
for (i=mark2; i<total; i++) {
|
|
item = WMGetFromArray(lPtr->items, i);
|
|
if (item->selected) {
|
|
item->selected = 0;
|
|
if (lPtr->view->flags.mapped && i>=lPtr->topItem
|
|
&& i<=lPtr->topItem+lPtr->fullFitLines) {
|
|
paintItem(lPtr, i);
|
|
}
|
|
notify = 1;
|
|
}
|
|
}
|
|
|
|
if (notify) {
|
|
WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
WMSelectAllListItems(WMList *lPtr)
|
|
{
|
|
int i;
|
|
WMListItem *item;
|
|
|
|
if (!lPtr->flags.allowMultipleSelection)
|
|
return;
|
|
|
|
if (WMGetArrayItemCount(lPtr->items) ==
|
|
WMGetArrayItemCount(lPtr->selectedItems)) {
|
|
return; /* All items are selected already */
|
|
}
|
|
|
|
WMFreeArray(lPtr->selectedItems);
|
|
lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
|
|
|
|
for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
|
|
item = WMGetFromArray(lPtr->items, i);
|
|
if (!item->selected) {
|
|
item->selected = 1;
|
|
if (lPtr->view->flags.mapped && i>=lPtr->topItem
|
|
&& i<=lPtr->topItem+lPtr->fullFitLines) {
|
|
paintItem(lPtr, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
|
|
}
|
|
|
|
/*
|
|
* Be careful from where you call this function! It doesn't honor the
|
|
* allowEmptySelection flag and doesn't send a notification about selection
|
|
* change! You need to manage these in the functions from where you call it.
|
|
*
|
|
* This will unselect all items if exceptThis is NULL, else will keep
|
|
* exceptThis selected.
|
|
* Make sure that exceptThis is one of the already selected items if not NULL!
|
|
*
|
|
*/
|
|
static void
|
|
unselectAllListItems(WMList *lPtr, WMListItem *exceptThis)
|
|
{
|
|
int i;
|
|
WMListItem *item;
|
|
|
|
for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
|
|
item = WMGetFromArray(lPtr->items, i);
|
|
if (item!=exceptThis && item->selected) {
|
|
item->selected = 0;
|
|
if (lPtr->view->flags.mapped && i>=lPtr->topItem
|
|
&& i<=lPtr->topItem+lPtr->fullFitLines) {
|
|
paintItem(lPtr, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
WMEmptyArray(lPtr->selectedItems);
|
|
if (exceptThis!=NULL) {
|
|
exceptThis->selected = 1;
|
|
WMAddToArray(lPtr->selectedItems, exceptThis);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
WMUnselectAllListItems(WMList *lPtr)
|
|
{
|
|
int keep;
|
|
WMListItem *keepItem;
|
|
|
|
keep = lPtr->flags.allowEmptySelection ? 0 : 1;
|
|
|
|
if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
|
|
return;
|
|
|
|
keepItem = (keep==1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
|
|
|
|
unselectAllListItems(lPtr, keepItem);
|
|
|
|
WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
|
|
}
|
|
|
|
|
|
static int
|
|
getItemIndexAt(List *lPtr, int clickY)
|
|
{
|
|
int index;
|
|
|
|
index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
|
|
|
|
if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
|
|
return -1;
|
|
|
|
return index;
|
|
}
|
|
|
|
|
|
static void
|
|
toggleItemSelection(WMList *lPtr, int index)
|
|
{
|
|
WMListItem *item = WMGetFromArray(lPtr->items, index);
|
|
|
|
if (item && item->selected) {
|
|
WMUnselectListItem(lPtr, index);
|
|
} else {
|
|
WMSelectListItem(lPtr, index);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
handleActionEvents(XEvent *event, void *data)
|
|
{
|
|
List *lPtr = (List*)data;
|
|
int tmp, height;
|
|
int topItem = lPtr->topItem;
|
|
static int lastClicked = -1, prevItem = -1;
|
|
|
|
CHECK_CLASS(data, WC_List);
|
|
|
|
switch (event->type) {
|
|
case ButtonRelease:
|
|
/* Ignore mouse wheel events, they're not "real" button events */
|
|
if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
|
|
event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
|
|
break;
|
|
}
|
|
|
|
lPtr->flags.buttonPressed = 0;
|
|
if (lPtr->selectID) {
|
|
WMDeleteTimerHandler(lPtr->selectID);
|
|
lPtr->selectID = NULL;
|
|
}
|
|
tmp = getItemIndexAt(lPtr, event->xbutton.y);
|
|
|
|
if (tmp >= 0) {
|
|
if (lPtr->action)
|
|
(*lPtr->action)(lPtr, lPtr->clientData);
|
|
}
|
|
|
|
if (!(event->xbutton.state & ShiftMask))
|
|
lastClicked = prevItem = tmp;
|
|
|
|
break;
|
|
|
|
case EnterNotify:
|
|
if (lPtr->selectID) {
|
|
WMDeleteTimerHandler(lPtr->selectID);
|
|
lPtr->selectID = NULL;
|
|
}
|
|
break;
|
|
|
|
case LeaveNotify:
|
|
height = WMWidgetHeight(lPtr);
|
|
if (lPtr->flags.buttonPressed && !lPtr->selectID) {
|
|
if (event->xcrossing.y >= height) {
|
|
lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
|
|
scrollForwardSelecting,
|
|
lPtr);
|
|
} else if (event->xcrossing.y <= 0) {
|
|
lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
|
|
scrollBackwardSelecting,
|
|
lPtr);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ButtonPress:
|
|
if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
|
|
break;
|
|
if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
|
|
event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
|
|
int amount = 0;
|
|
|
|
if (event->xbutton.state & ControlMask) {
|
|
amount = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
|
|
} else if (event->xbutton.state & ShiftMask) {
|
|
amount = 1;
|
|
} else {
|
|
amount = lPtr->fullFitLines / 3;
|
|
if (amount == 0)
|
|
amount++;
|
|
}
|
|
if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
|
|
amount = -amount;
|
|
|
|
scrollByAmount(lPtr, amount);
|
|
break;
|
|
}
|
|
|
|
tmp = getItemIndexAt(lPtr, event->xbutton.y);
|
|
lPtr->flags.buttonPressed = 1;
|
|
|
|
if (tmp >= 0) {
|
|
if (tmp == lastClicked && WMIsDoubleClick(event)) {
|
|
WMSelectListItem(lPtr, tmp);
|
|
if (lPtr->doubleAction)
|
|
(*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
|
|
} else {
|
|
if (!lPtr->flags.allowMultipleSelection) {
|
|
if (event->xbutton.state & ControlMask) {
|
|
toggleItemSelection(lPtr, tmp);
|
|
} else {
|
|
WMSelectListItem(lPtr, tmp);
|
|
}
|
|
} else {
|
|
WMRange range;
|
|
WMListItem *lastSel;
|
|
|
|
if (event->xbutton.state & ControlMask) {
|
|
toggleItemSelection(lPtr, tmp);
|
|
} else if (event->xbutton.state & ShiftMask) {
|
|
if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
|
|
WMSelectListItem(lPtr, tmp);
|
|
} else {
|
|
lastSel = WMGetFromArray(lPtr->items, lastClicked);
|
|
range.position = WMGetFirstInArray(lPtr->items,
|
|
lastSel);
|
|
if (tmp >= range.position)
|
|
range.count = tmp - range.position + 1;
|
|
else
|
|
range.count = tmp - range.position - 1;
|
|
|
|
WMSetListSelectionToRange(lPtr, range);
|
|
}
|
|
} else {
|
|
range.position = tmp;
|
|
range.count = 1;
|
|
WMSetListSelectionToRange(lPtr, range);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(event->xbutton.state & ShiftMask))
|
|
lastClicked = prevItem = tmp;
|
|
|
|
break;
|
|
|
|
case MotionNotify:
|
|
height = WMWidgetHeight(lPtr);
|
|
if (lPtr->selectID && event->xmotion.y>0 && event->xmotion.y<height) {
|
|
WMDeleteTimerHandler(lPtr->selectID);
|
|
lPtr->selectID = NULL;
|
|
}
|
|
if (lPtr->flags.buttonPressed && !lPtr->selectID) {
|
|
if (event->xmotion.y <= 0) {
|
|
lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
|
|
scrollBackwardSelecting,
|
|
lPtr);
|
|
break;
|
|
} else if (event->xmotion.y >= height) {
|
|
lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
|
|
scrollForwardSelecting,
|
|
lPtr);
|
|
break;
|
|
}
|
|
|
|
tmp = getItemIndexAt(lPtr, event->xmotion.y);
|
|
if (tmp>=0 && tmp!=prevItem) {
|
|
if (lPtr->flags.allowMultipleSelection) {
|
|
WMRange range;
|
|
|
|
range.position = lastClicked;
|
|
if (tmp >= lastClicked)
|
|
range.count = tmp - lastClicked + 1;
|
|
else
|
|
range.count = tmp - lastClicked - 1;
|
|
WMSetListSelectionToRange(lPtr, range);
|
|
} else {
|
|
WMSelectListItem(lPtr, tmp);
|
|
}
|
|
}
|
|
prevItem = tmp;
|
|
}
|
|
break;
|
|
}
|
|
if (lPtr->topItem != topItem)
|
|
WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
updateGeometry(WMList *lPtr)
|
|
{
|
|
lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
|
|
if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
|
|
lPtr->flags.dontFitAll = 1;
|
|
} else {
|
|
lPtr->flags.dontFitAll = 0;
|
|
}
|
|
|
|
if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
|
|
lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
|
|
if (lPtr->topItem < 0)
|
|
lPtr->topItem = 0;
|
|
}
|
|
|
|
updateScroller(lPtr);
|
|
}
|
|
|
|
|
|
static void
|
|
didResizeList(W_ViewDelegate *self, WMView *view)
|
|
{
|
|
WMList *lPtr = (WMList*)view->self;
|
|
|
|
WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
|
|
|
|
updateGeometry(lPtr);
|
|
}
|
|
|
|
|
|
static void
|
|
destroyList(List *lPtr)
|
|
{
|
|
if (lPtr->idleID)
|
|
WMDeleteIdleHandler(lPtr->idleID);
|
|
lPtr->idleID = NULL;
|
|
|
|
if (lPtr->selectID)
|
|
WMDeleteTimerHandler(lPtr->selectID);
|
|
lPtr->selectID = NULL;
|
|
|
|
if (lPtr->selectedItems)
|
|
WMFreeArray(lPtr->selectedItems);
|
|
|
|
if (lPtr->items)
|
|
WMFreeArray(lPtr->items);
|
|
|
|
wfree(lPtr);
|
|
}
|
|
|
|
|