Initial revision
[wmaker-crm.git] / WINGs / wlist.c
blob0b11c135ffef9f1da97dffe7831df9dbd6fd2c77
5 #include "WINGsP.h"
8 typedef struct W_List {
9 W_Class widgetClass;
10 W_View *view;
12 WMListItem *items; /* array of items */
13 short itemCount;
15 short selectedItem;
17 short itemHeight;
19 short topItem; /* index of first visible item */
21 short fullFitLines; /* no of lines that fit entirely */
23 void *clientData;
24 WMAction *action;
25 void *doubleClientData;
26 WMAction *doubleAction;
28 WMListDrawProc *draw;
30 WMHandlerID *idleID; /* for updating the scroller after
31 * adding elements */
33 WMScroller *vScroller;
35 WMScroller *hScroller;
38 struct {
39 unsigned int allowMultipleSelection:1;
40 unsigned int userDrawn:1;
41 unsigned int userItemHeight:1;
42 /* */
43 unsigned int dontFitAll:1; /* 1 = last item won't be fully visible */
44 unsigned int redrawPending:1;
45 unsigned int buttonPressed:1;
46 unsigned int buttonWasPressed:1;
47 } flags;
48 } List;
52 #define DEFAULT_WIDTH 150
53 #define DEFAULT_HEIGHT 150
56 static void destroyList(List *lPtr);
57 static void paintList(List *lPtr);
60 static void handleEvents(XEvent *event, void *data);
61 static void handleActionEvents(XEvent *event, void *data);
62 static void updateScroller(List *lPtr);
64 static void vScrollCallBack(WMWidget *scroller, void *self);
66 static void resizeList();
69 W_ViewProcedureTable _ListViewProcedures = {
70 NULL,
71 resizeList,
72 NULL
77 WMList*
78 WMCreateList(WMWidget *parent)
80 List *lPtr;
81 W_Screen *scrPtr = W_VIEW(parent)->screen;
83 lPtr = wmalloc(sizeof(List));
84 memset(lPtr, 0, sizeof(List));
86 lPtr->widgetClass = WC_List;
88 lPtr->view = W_CreateView(W_VIEW(parent));
89 if (!lPtr->view) {
90 free(lPtr);
91 return NULL;
93 lPtr->view->self = lPtr;
95 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
96 |ClientMessageMask, handleEvents, lPtr);
98 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
99 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
100 handleActionEvents, lPtr);
102 lPtr->itemHeight = scrPtr->normalFont->height + 1;
104 /* create the vertical scroller */
105 lPtr->vScroller = WMCreateScroller(lPtr);
106 WMMoveWidget(lPtr->vScroller, 1, 1);
107 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
109 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
111 /* make the scroller map itself when it's realized */
112 WMMapWidget(lPtr->vScroller);
114 resizeList(lPtr, DEFAULT_WIDTH, DEFAULT_HEIGHT);
116 lPtr->selectedItem = -1;
118 return lPtr;
123 WMListItem*
124 WMAddSortedListItem(WMList *lPtr, char *text)
126 WMListItem *item;
127 WMListItem *tmp;
128 int index;
130 item = wmalloc(sizeof(WMListItem));
131 memset(item, 0, sizeof(WMListItem));
132 item->text = wstrdup(text);
133 if (!lPtr->items) {
134 lPtr->items = item;
135 index = 0;
136 } else if (strcmp(lPtr->items->text, text) > 0) {
137 item->nextPtr = lPtr->items;
138 lPtr->items = item;
139 index = 1;
140 } else {
141 int added = 0;
143 index = 0;
144 tmp = lPtr->items;
145 while (tmp->nextPtr) {
146 if (strcmp(tmp->nextPtr->text, text) >= 0) {
147 item->nextPtr = tmp->nextPtr;
148 tmp->nextPtr = item;
149 added = 1;
150 break;
152 index++;
153 tmp = tmp->nextPtr;
155 if (!added) {
156 tmp->nextPtr = item;
160 lPtr->itemCount++;
162 if (lPtr->selectedItem >= index)
163 lPtr->selectedItem++;
165 /* update the scroller when idle, so that we don't waste time
166 * updating it when another item is going to be added later */
167 if (!lPtr->idleID) {
168 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
171 return item;
176 WMListItem*
177 WMInsertListItem(WMList *lPtr, int row, char *text)
179 WMListItem *item;
180 WMListItem *tmp = lPtr->items;
182 CHECK_CLASS(lPtr, WC_List);
184 item = wmalloc(sizeof(WMListItem));
185 memset(item, 0, sizeof(WMListItem));
186 item->text = wstrdup(text);
188 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0)
189 lPtr->selectedItem++;
191 if (lPtr->items==NULL) {
192 lPtr->items = item;
193 } else if (row == 0) {
194 item->nextPtr = lPtr->items;
195 lPtr->items = item;
196 } else if (row < 0) {
197 while (tmp->nextPtr)
198 tmp = tmp->nextPtr;
200 tmp->nextPtr = item;
201 row = lPtr->itemCount;
202 } else {
203 while (--row > 0)
204 tmp = tmp->nextPtr;
206 item->nextPtr = tmp->nextPtr;
207 tmp->nextPtr = item;
210 lPtr->itemCount++;
212 /* update the scroller when idle, so that we don't waste time
213 * updating it when another item is going to be added later */
214 if (!lPtr->idleID) {
215 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
218 return item;
222 void
223 WMRemoveListItem(WMList *lPtr, int row)
225 WMListItem *llist;
226 WMListItem *tmp;
228 CHECK_CLASS(lPtr, WC_List);
230 if (row < 0 || row >= lPtr->itemCount)
231 return;
233 if (lPtr->selectedItem == row)
234 lPtr->selectedItem = -1;
235 else if (lPtr->selectedItem > row)
236 lPtr->selectedItem--;
238 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
239 lPtr->topItem--;
240 if (lPtr->topItem < 0)
241 lPtr->topItem = 0;
243 if (row == 0) {
244 if (lPtr->items->text)
245 free(lPtr->items->text);
247 tmp = lPtr->items->nextPtr;
248 free(lPtr->items);
250 lPtr->items = tmp;
251 } else {
252 llist = lPtr->items;
253 while (--row > 0)
254 llist = llist->nextPtr;
255 tmp = llist->nextPtr;
256 llist->nextPtr = llist->nextPtr->nextPtr;
258 if (tmp->text)
259 free(tmp->text);
261 free(tmp);
264 lPtr->itemCount--;
266 if (!lPtr->idleID) {
267 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
273 WMListItem*
274 WMGetListItem(WMList *lPtr, int row)
276 WMListItem *listPtr;
278 listPtr = lPtr->items;
280 while (row-- > 0)
281 listPtr = listPtr->nextPtr;
283 return listPtr;
288 void
289 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
291 lPtr->flags.userDrawn = 1;
292 lPtr->draw = proc;
296 void
297 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
299 assert(height > 0);
301 lPtr->flags.userItemHeight = 1;
302 lPtr->itemHeight = height;
306 void
307 WMClearList(WMList *lPtr)
309 WMListItem *item, *tmp;
311 item = lPtr->items;
312 while (item) {
313 free(item->text);
314 tmp = item->nextPtr;
315 free(item);
316 item = tmp;
318 lPtr->items = NULL;
319 lPtr->itemCount = 0;
320 lPtr->topItem = 0;
321 lPtr->selectedItem = -1;
323 if (!lPtr->idleID) {
324 WMDeleteIdleHandler(lPtr->idleID);
325 lPtr->idleID = NULL;
327 if (lPtr->view->flags.realized) {
328 updateScroller(lPtr);
333 void
334 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
336 lPtr->action = action;
337 lPtr->clientData = clientData;
341 void
342 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
344 lPtr->doubleAction = action;
345 lPtr->doubleClientData = clientData;
349 WMListItem*
350 WMGetListSelectedItem(WMList *lPtr)
352 int i = lPtr->selectedItem;
353 WMListItem *item;
355 if (lPtr->selectedItem < 0)
356 return NULL;
358 item = lPtr->items;
359 while (i-- > 0) {
360 item = item->nextPtr;
362 return item;
367 WMGetListSelectedItemRow(WMList *lPtr)
369 return lPtr->selectedItem;
373 void
374 WMSetListPosition(WMList *lPtr, int row)
376 lPtr->topItem = row;
377 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
378 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
380 if (lPtr->topItem < 0)
381 lPtr->topItem = 0;
383 if (lPtr->view->flags.realized)
384 updateScroller(lPtr);
388 void
389 WMSetListBottomPosition(WMList *lPtr, int row)
391 if (lPtr->itemCount > lPtr->fullFitLines) {
392 lPtr->topItem = row - lPtr->fullFitLines;
393 if (lPtr->topItem < 0)
394 lPtr->topItem = 0;
395 if (lPtr->view->flags.realized)
396 updateScroller(lPtr);
402 WMGetListNumberOfRows(WMList *lPtr)
404 return lPtr->itemCount;
408 WMGetListPosition(WMList *lPtr)
410 return lPtr->topItem;
414 static void
415 vScrollCallBack(WMWidget *scroller, void *self)
417 WMList *lPtr = (WMList*)self;
418 WMScroller *sPtr = (WMScroller*)scroller;
419 int height;
421 height = lPtr->view->size.height - 4;
423 switch (WMGetScrollerHitPart(sPtr)) {
424 case WSDecrementLine:
425 if (lPtr->topItem > 0) {
426 lPtr->topItem--;
428 updateScroller(lPtr);
430 break;
432 case WSDecrementPage:
433 if (lPtr->topItem > 0) {
434 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
435 if (lPtr->topItem < 0)
436 lPtr->topItem = 0;
438 updateScroller(lPtr);
440 break;
443 case WSIncrementLine:
444 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
445 lPtr->topItem++;
447 updateScroller(lPtr);
449 break;
451 case WSIncrementPage:
452 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
453 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
455 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
456 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
458 updateScroller(lPtr);
460 break;
462 case WSKnob:
464 int oldTopItem = lPtr->topItem;
466 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
467 (float)(lPtr->itemCount - lPtr->fullFitLines);
469 if (oldTopItem != lPtr->topItem)
470 paintList(lPtr);
472 break;
474 case WSKnobSlot:
475 case WSNoPart:
476 /* do nothing */
477 break;
482 static void
483 paintItem(List *lPtr, int index)
485 WMView *view = lPtr->view;
486 W_Screen *scr = view->screen;
487 int width, height, x, y;
488 WMListItem *itemPtr;
489 int i;
491 i = index;
492 itemPtr = lPtr->items;
493 while (i-- > 0)
494 itemPtr = itemPtr->nextPtr;
496 width = lPtr->view->size.width - 2 - 19;
497 height = lPtr->itemHeight;
498 x = 19;
499 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
501 if (lPtr->flags.userDrawn) {
502 WMRect rect;
503 int flags;
505 rect.size.width = width;
506 rect.size.height = height;
507 rect.pos.x = x;
508 rect.pos.y = y;
510 flags = itemPtr->uflags;
511 if (itemPtr->disabled)
512 flags |= WLDSDisabled;
513 if (itemPtr->selected)
514 flags |= WLDSSelected;
515 if (itemPtr->isBranch)
516 flags |= WLDSIsBranch;
518 if (lPtr->draw)
519 (*lPtr->draw)(lPtr, view->window, itemPtr->text, flags, &rect);
521 } else {
522 if (itemPtr->selected)
523 XFillRectangle(scr->display, view->window, W_GC(scr->white), x, y,
524 width, height);
525 else
526 XClearArea(scr->display, view->window, x, y, width, height, False);
528 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
529 WALeft, W_GC(scr->black), False,
530 itemPtr->text, strlen(itemPtr->text));
536 static void
537 paintList(List *lPtr)
539 W_Screen *scrPtr = lPtr->view->screen;
540 int i, lim;
542 if (!lPtr->view->flags.mapped)
543 return;
545 if (lPtr->itemCount>0) {
546 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll > lPtr->itemCount) {
547 lim = lPtr->itemCount - lPtr->topItem;
548 XClearArea(scrPtr->display, lPtr->view->window, 19,
549 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
550 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
551 } else {
552 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
554 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
555 paintItem(lPtr, i);
557 } else {
558 XClearWindow(scrPtr->display, lPtr->view->window);
560 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
561 lPtr->view->size.height, WRSunken);
564 #if 0
565 static void
566 scrollTo(List *lPtr, int newTop)
570 #endif
572 static void
573 updateScroller(List *lPtr)
575 float knobProportion, floatValue, tmp;
577 if (lPtr->idleID)
578 WMDeleteIdleHandler(lPtr->idleID);
579 lPtr->idleID = NULL;
581 paintList(lPtr);
583 if (lPtr->itemCount == 0 || lPtr->itemCount <= lPtr->fullFitLines)
584 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
585 else {
586 tmp = lPtr->fullFitLines;
587 knobProportion = tmp/(float)lPtr->itemCount;
589 floatValue = (float)lPtr->topItem/(float)(lPtr->itemCount - lPtr->fullFitLines);
591 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
596 static void
597 handleEvents(XEvent *event, void *data)
599 List *lPtr = (List*)data;
601 CHECK_CLASS(data, WC_List);
604 switch (event->type) {
605 case Expose:
606 if (event->xexpose.count!=0)
607 break;
608 paintList(lPtr);
609 break;
611 case DestroyNotify:
612 destroyList(lPtr);
613 break;
620 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
622 WMListItem *item;
623 int i;
624 int ok = 0;
626 for (i=0, item=lPtr->items; item!=NULL; item=item->nextPtr, i++) {
627 if (strcmp(item->text, title)==0) {
628 ok = 1;
629 break;
633 return ok ? i : -1;
637 void
638 WMSelectListItem(WMList *lPtr, int row)
640 WMListItem *itemPtr;
641 int i;
643 if (row >= lPtr->itemCount)
644 return;
646 if (!lPtr->flags.allowMultipleSelection) {
647 /* unselect previous selected item */
648 if (lPtr->selectedItem >= 0) {
649 itemPtr = lPtr->items;
650 for (i=0; i<lPtr->selectedItem; i++)
651 itemPtr = itemPtr->nextPtr;
653 if (itemPtr->selected) {
654 itemPtr->selected = 0;
655 if (lPtr->view->flags.mapped && i>=lPtr->topItem
656 && i<=lPtr->topItem+lPtr->fullFitLines)
657 paintItem(lPtr, i);
662 if (row < 0) {
663 if (!lPtr->flags.allowMultipleSelection)
664 lPtr->selectedItem = -1;
665 return;
668 /* select item */
669 itemPtr = lPtr->items;
670 for (i=0; i<row; i++)
671 itemPtr = itemPtr->nextPtr;
672 if (lPtr->flags.allowMultipleSelection)
673 itemPtr->selected = !itemPtr->selected;
674 else
675 itemPtr->selected = 1;
677 if (lPtr->view->flags.mapped) {
678 paintItem(lPtr, row);
680 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
681 > lPtr->view->size.height-2)
682 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
683 lPtr->view->size.width, lPtr->view->size.height,
684 WRSunken);
686 lPtr->selectedItem = row;
690 static int
691 getItemIndexAt(List *lPtr, int clickY)
693 int index;
695 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
697 if (index < 0 || index >= lPtr->itemCount)
698 return -1;
700 return index;
704 static void
705 handleActionEvents(XEvent *event, void *data)
707 List *lPtr = (List*)data;
708 int tmp;
710 CHECK_CLASS(data, WC_List);
712 switch (event->type) {
713 case ButtonRelease:
714 lPtr->flags.buttonPressed = 0;
715 tmp = getItemIndexAt(lPtr, event->xbutton.y);
716 if (tmp == lPtr->selectedItem && tmp >= 0) {
717 if (WMIsDoubleClick(event)) {
718 if (lPtr->doubleAction)
719 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
720 } else {
721 if (lPtr->action)
722 (*lPtr->action)(lPtr, lPtr->clientData);
725 break;
727 case EnterNotify:
728 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
729 lPtr->flags.buttonWasPressed = 0;
730 break;
732 case LeaveNotify:
733 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
734 lPtr->flags.buttonPressed = 0;
735 break;
737 case ButtonPress:
738 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
739 tmp = getItemIndexAt(lPtr, event->xbutton.y);
741 if (tmp>=0) {
742 WMSelectListItem(lPtr, tmp);
743 lPtr->selectedItem = tmp;
745 lPtr->flags.buttonPressed = 1;
747 break;
749 case MotionNotify:
750 if (lPtr->flags.buttonPressed) {
751 tmp = getItemIndexAt(lPtr, event->xmotion.y);
752 if (tmp>=0 && tmp != lPtr->selectedItem) {
753 WMSelectListItem(lPtr, tmp);
754 lPtr->selectedItem = tmp;
757 break;
762 static void
763 resizeList(WMList *lPtr, unsigned int width, unsigned int height)
765 W_ResizeView(lPtr->view, width, height);
767 WMResizeWidget(lPtr->vScroller, 1, height-2);
769 lPtr->fullFitLines = (height - 4) / lPtr->itemHeight;
770 if (lPtr->fullFitLines * lPtr->itemHeight < height-4) {
771 lPtr->flags.dontFitAll = 1;
772 } else {
773 lPtr->flags.dontFitAll = 0;
778 static void
779 destroyList(List *lPtr)
781 WMListItem *itemPtr;
783 if (lPtr->idleID)
784 WMDeleteIdleHandler(lPtr->idleID);
785 lPtr->idleID = NULL;
787 while (lPtr->items!=NULL) {
788 itemPtr = lPtr->items;
789 if (itemPtr->text)
790 free(itemPtr->text);
792 lPtr->items = itemPtr->nextPtr;
793 free(itemPtr);
796 free(lPtr);