mirror of
https://github.com/gryf/wmaker.git
synced 2025-12-29 01:42:32 +01:00
It is dangerous to let the compiler know about a function without letting him know the arguments because he won't be able to report invalid calls. This patch concern the cases where adding the arguments did not need other code change.
1130 lines
27 KiB
C
1130 lines
27 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;
|
|
|
|
Pixmap doubleBuffer;
|
|
|
|
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(W_ViewDelegate * self, WMView * view);
|
|
|
|
static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis);
|
|
|
|
W_ViewDelegate _ListViewDelegate = {
|
|
NULL,
|
|
NULL,
|
|
didResizeList,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
static void updateDoubleBufferPixmap(WMList * lPtr)
|
|
{
|
|
WMView *view = lPtr->view;
|
|
WMScreen *scr = view->screen;
|
|
|
|
if (!view->flags.realized)
|
|
return;
|
|
|
|
if (lPtr->doubleBuffer)
|
|
XFreePixmap(scr->display, lPtr->doubleBuffer);
|
|
lPtr->doubleBuffer =
|
|
XCreatePixmap(scr->display, view->window, view->size.width, lPtr->itemHeight, scr->depth);
|
|
}
|
|
|
|
static void realizeObserver(void *self, WMNotification * not)
|
|
{
|
|
updateDoubleBufferPixmap(self);
|
|
}
|
|
|
|
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));
|
|
|
|
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);
|
|
|
|
WMAddNotificationObserver(realizeObserver, lPtr, WMViewRealizedNotification, lPtr->view);
|
|
|
|
return lPtr;
|
|
}
|
|
|
|
void WMSetListAllowMultipleSelection(WMList * lPtr, Bool flag)
|
|
{
|
|
lPtr->flags.allowMultipleSelection = ((flag == 0) ? 0 : 1);
|
|
}
|
|
|
|
void WMSetListAllowEmptySelection(WMList * lPtr, Bool flag)
|
|
{
|
|
lPtr->flags.allowEmptySelection = ((flag == 0) ? 0 : 1);
|
|
}
|
|
|
|
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, const char *text)
|
|
{
|
|
WMListItem *item;
|
|
|
|
CHECK_CLASS(lPtr, WC_List);
|
|
|
|
item = wmalloc(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;
|
|
|
|
updateDoubleBufferPixmap(lPtr);
|
|
|
|
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 oldTopItem = lPtr->topItem;
|
|
int itemCount = WMGetArrayItemCount(lPtr->items);
|
|
|
|
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;
|
|
Display *display = scr->display;
|
|
int width, height, x, y, tlen;
|
|
WMListItem *itemPtr;
|
|
Drawable d = lPtr->doubleBuffer;
|
|
|
|
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;
|
|
tlen = strlen(itemPtr->text);
|
|
|
|
if (lPtr->flags.userDrawn) {
|
|
WMRect rect;
|
|
int flags;
|
|
|
|
rect.size.width = width;
|
|
rect.size.height = height;
|
|
rect.pos.x = 0;
|
|
rect.pos.y = 0;
|
|
|
|
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, d, itemPtr->text, flags, &rect);
|
|
|
|
XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
|
|
} else {
|
|
WMColor *back = (itemPtr->selected ? scr->white : view->backColor);
|
|
|
|
XFillRectangle(display, d, WMColorGC(back), 0, 0, width, height);
|
|
|
|
W_PaintText(view, d, scr->normalFont, 4, 0, width, WALeft, scr->black, False, itemPtr->text, tlen);
|
|
XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
|
|
}
|
|
|
|
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(const void *item, const void *title)
|
|
{
|
|
return (strcmp(((WMListItem *) item)->text, (const char *)title) == 0 ? 1 : 0);
|
|
}
|
|
|
|
int WMFindRowOfListItemWithTitle(WMList * lPtr, const char *title)
|
|
{
|
|
/*
|
|
* We explicitely discard the 'const' attribute here because the
|
|
* call-back function handler must not be made with a const
|
|
* attribute, but our local call-back function (above) does have
|
|
* it properly set, so we're consistent
|
|
*/
|
|
return WMFindInArray(lPtr->items, matchTitle, (char *) 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);
|
|
|
|
updateDoubleBufferPixmap(lPtr);
|
|
|
|
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);
|
|
|
|
if (lPtr->doubleBuffer)
|
|
XFreePixmap(lPtr->view->screen->display, lPtr->doubleBuffer);
|
|
|
|
WMRemoveNotificationObserver(lPtr);
|
|
|
|
wfree(lPtr);
|
|
}
|