Remake of the WMList code to allow multiple selection. Not complete yet.
[wmaker-crm.git] / WINGs / wlist.c
blobea127c2789e74c2c1bf30ab495dcb672179cacc5
5 #include "WINGsP.h"
7 char *WMListDidScrollNotification = "WMListDidScrollNotification";
8 char *WMListSelectionDidChangeNotification = "WMListSelectionDidChangeNotification";
10 typedef struct W_List {
11 W_Class widgetClass;
12 W_View *view;
14 WMArray *items; /* list of WMListItem */
15 WMArray *selectedItems; /* list of selected WMListItems */
17 //short selectedItem;
19 short itemHeight;
21 short topItem; /* index of first visible item */
23 short fullFitLines; /* no of lines that fit entirely */
25 void *clientData;
26 WMAction *action;
27 void *doubleClientData;
28 WMAction *doubleAction;
30 WMListDrawProc *draw;
32 WMHandlerID *idleID; /* for updating the scroller after adding elements */
34 WMScroller *vScroller;
36 struct {
37 unsigned int allowMultipleSelection:1;
38 unsigned int allowEmptySelection:1;
39 unsigned int userDrawn:1;
40 unsigned int userItemHeight:1;
41 unsigned int dontFitAll:1; /* 1 = last item won't be fully visible */
42 unsigned int redrawPending:1;
43 unsigned int buttonPressed:1;
44 unsigned int buttonWasPressed:1;
45 } flags;
46 } List;
50 #define DEFAULT_WIDTH 150
51 #define DEFAULT_HEIGHT 150
54 static void destroyList(List *lPtr);
55 static void paintList(List *lPtr);
58 static void handleEvents(XEvent *event, void *data);
59 static void handleActionEvents(XEvent *event, void *data);
60 static void updateScroller(List *lPtr);
62 static void vScrollCallBack(WMWidget *scroller, void *self);
64 static void updateGeometry(WMList *lPtr);
65 static void didResizeList();
68 W_ViewDelegate _ListViewDelegate = {
69 NULL,
70 NULL,
71 didResizeList,
72 NULL,
73 NULL
77 static void
78 releaseItem(void *data)
80 WMListItem *item = (WMListItem*)data;
82 if (item->text)
83 wfree(item->text);
84 wfree(item);
88 WMList*
89 WMCreateList(WMWidget *parent)
91 List *lPtr;
92 W_Screen *scrPtr = W_VIEW(parent)->screen;
94 lPtr = wmalloc(sizeof(List));
95 memset(lPtr, 0, sizeof(List));
97 lPtr->widgetClass = WC_List;
99 lPtr->view = W_CreateView(W_VIEW(parent));
100 if (!lPtr->view) {
101 wfree(lPtr);
102 return NULL;
104 lPtr->view->self = lPtr;
106 lPtr->view->delegate = &_ListViewDelegate;
108 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
109 |ClientMessageMask, handleEvents, lPtr);
111 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
112 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
113 handleActionEvents, lPtr);
115 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
117 lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
118 lPtr->selectedItems = WMCreateArray(4);
120 /* create the vertical scroller */
121 lPtr->vScroller = WMCreateScroller(lPtr);
122 WMMoveWidget(lPtr->vScroller, 1, 1);
123 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
125 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
127 /* make the scroller map itself when it's realized */
128 WMMapWidget(lPtr->vScroller);
130 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
132 //lPtr->selectedItem = -1;
134 return lPtr;
138 void
139 WMSetListAllowMultipleSelection(WMList *lPtr, Bool flag)
141 lPtr->flags.allowMultipleSelection = flag ? 1 : 0;
146 void
147 WMSetListAllowEmptySelection(WMList *lPtr, Bool flag)
149 lPtr->flags.allowEmptySelection = flag ? 1 : 0;
153 static int
154 comparator(const void *a, const void *b)
156 return (strcmp((*(WMListItem**)a)->text, (*(WMListItem**)b)->text));
160 void
161 WMSortListItems(WMList *lPtr)
163 WMSortArray(lPtr->items, comparator);
165 paintList(lPtr);
170 void
171 WMSortListItemsWithComparer(WMList *lPtr, WMCompareDataProc *func)
173 WMSortArray(lPtr->items, func);
175 paintList(lPtr);
180 WMListItem*
181 WMInsertListItem(WMList *lPtr, int row, char *text)
183 WMListItem *item;
185 CHECK_CLASS(lPtr, WC_List);
187 item = wmalloc(sizeof(WMListItem));
188 memset(item, 0, sizeof(WMListItem));
189 item->text = wstrdup(text);
192 //if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0
193 // && row >= 0)
194 // lPtr->selectedItem++;
196 row = WMIN(row, WMGetArrayItemCount(lPtr->items));
198 if (row < 0)
199 WMAddToArray(lPtr->items, item);
200 else
201 WMInsertInArray(lPtr->items, row, item);
203 /* update the scroller when idle, so that we don't waste time
204 * updating it when another item is going to be added later */
205 if (!lPtr->idleID) {
206 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
209 return item;
214 WMRemoveListItem(WMList *lPtr, int row)
216 WMLIstItem *item;
217 int topItem = lPtr->topItem;
218 int selNotify = 0;
220 CHECK_CLASS(lPtr, WC_List);
222 if (row < 0 || row >= WMGetArrayItemCount(lPtr->items))
223 return 0;
225 item = WMGetFromArray(lPtr->items, row);
226 if (item->flags.selected) {
227 WMRemoveFromArray(lPtr->selectedItems, item);
228 //WMUnselectListItem(lPtr, row);
229 selNotify = 1;
232 //if (lPtr->selectedItem == row) {
233 // lPtr->selectedItem = -1;
234 // selNotify = 1;
235 //} else if (lPtr->selectedItem > row) {
236 // lPtr->selectedItem--;
239 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
240 lPtr->topItem--;
241 if (lPtr->topItem < 0)
242 lPtr->topItem = 0;
244 WMDeleteFromArray(lPtr->items, row);
246 if (!lPtr->idleID) {
247 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
249 if (lPtr->topItem != topItem) {
250 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
252 if (selNotify) {
253 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
256 return 1;
260 WMListItem*
261 WMGetListItem(WMList *lPtr, int row)
263 return WMGetFromArray(lPtr->items, row);
267 WMArray*
268 WMGetListItems(WMList *lPtr)
270 return lPtr->items;
274 void
275 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
277 lPtr->flags.userDrawn = 1;
278 lPtr->draw = proc;
282 void
283 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
285 assert(height > 0);
287 lPtr->flags.userItemHeight = 1;
288 lPtr->itemHeight = height;
290 updateGeometry(lPtr);
294 void
295 WMClearList(WMList *lPtr)
297 int selNo = WMGetArrayItemCount(lPtr->selectedItems);
299 WMEmptyArray(lPtr->selectedItems);
300 WMEmptyArray(lPtr->items);
302 lPtr->topItem = 0;
303 //lPtr->selectedItem = -1;
305 if (!lPtr->idleID) {
306 WMDeleteIdleHandler(lPtr->idleID);
307 lPtr->idleID = NULL;
309 if (lPtr->view->flags.realized) {
310 updateScroller(lPtr);
312 if (selNo > 0) {
313 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
318 void
319 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
321 lPtr->action = action;
322 lPtr->clientData = clientData;
326 void
327 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
329 lPtr->doubleAction = action;
330 lPtr->doubleClientData = clientData;
334 WMArray*
335 WMGetListSelectedItems(WMList *lPtr)
337 return lPtr->selectedItems;
341 WMListItem*
342 WMGetListSelectedItem(WMList *lPtr)
344 return WMGetFromArray(lPtr->selectedItems, 0);
349 WMGetListSelectedItemRow(WMList *lPtr)
351 WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
353 return (item!=NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
358 WMGetListItemHeight(WMList *lPtr)
360 return lPtr->itemHeight;
364 void
365 WMSetListPosition(WMList *lPtr, int row)
367 lPtr->topItem = row;
368 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
369 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
371 if (lPtr->topItem < 0)
372 lPtr->topItem = 0;
374 if (lPtr->view->flags.realized)
375 updateScroller(lPtr);
379 void
380 WMSetListBottomPosition(WMList *lPtr, int row)
382 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
383 lPtr->topItem = row - lPtr->fullFitLines;
384 if (lPtr->topItem < 0)
385 lPtr->topItem = 0;
386 if (lPtr->view->flags.realized)
387 updateScroller(lPtr);
393 WMGetListNumberOfRows(WMList *lPtr)
395 return WMGetArrayItemCount(lPtr->items);
400 WMGetListPosition(WMList *lPtr)
402 return lPtr->topItem;
406 Bool
407 WMListAllowsMultipleSelection(WMList *lPtr)
409 return lPtr->flags.allowMultipleSelection;
413 Bool
414 WMListAllowsEmptySelection(WMList *lPtr)
416 return lPtr->flags.allowEmptySelection;
420 static void
421 vScrollCallBack(WMWidget *scroller, void *self)
423 WMList *lPtr = (WMList*)self;
424 WMScroller *sPtr = (WMScroller*)scroller;
425 int height;
426 int topItem = lPtr->topItem;
427 int itemCount = WMGetArrayItemCount(lPtr->items);
429 height = lPtr->view->size.height - 4;
431 switch (WMGetScrollerHitPart(sPtr)) {
432 case WSDecrementLine:
433 if (lPtr->topItem > 0) {
434 lPtr->topItem--;
436 updateScroller(lPtr);
438 break;
440 case WSDecrementPage:
441 if (lPtr->topItem > 0) {
442 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
443 if (lPtr->topItem < 0)
444 lPtr->topItem = 0;
446 updateScroller(lPtr);
448 break;
451 case WSIncrementLine:
452 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
453 lPtr->topItem++;
455 updateScroller(lPtr);
457 break;
459 case WSIncrementPage:
460 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
461 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
463 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
464 lPtr->topItem = itemCount - lPtr->fullFitLines;
466 updateScroller(lPtr);
468 break;
470 case WSKnob:
472 int oldTopItem = lPtr->topItem;
474 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
475 (float)(itemCount - lPtr->fullFitLines);
477 if (oldTopItem != lPtr->topItem)
478 paintList(lPtr);
480 break;
482 case WSKnobSlot:
483 case WSNoPart:
484 /* do nothing */
485 break;
488 if (lPtr->topItem != topItem)
489 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
493 static void
494 paintItem(List *lPtr, int index)
496 WMView *view = lPtr->view;
497 W_Screen *scr = view->screen;
498 int width, height, x, y;
499 WMListItem *itemPtr;
501 itemPtr = WMGetFromArray(lPtr->items, index);
503 width = lPtr->view->size.width - 2 - 19;
504 height = lPtr->itemHeight;
505 x = 19;
506 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
508 if (lPtr->flags.userDrawn) {
509 WMRect rect;
510 int flags;
512 rect.size.width = width;
513 rect.size.height = height;
514 rect.pos.x = x;
515 rect.pos.y = y;
517 flags = itemPtr->uflags;
518 if (itemPtr->disabled)
519 flags |= WLDSDisabled;
520 if (itemPtr->selected)
521 flags |= WLDSSelected;
522 if (itemPtr->isBranch)
523 flags |= WLDSIsBranch;
525 if (lPtr->draw)
526 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
527 &rect);
528 } else {
529 if (itemPtr->selected) {
530 XFillRectangle(scr->display, view->window, WMColorGC(scr->white),
531 x, y, width, height);
532 } else {
533 XClearArea(scr->display, view->window, x, y, width, height, False);
536 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
537 WALeft, WMColorGC(scr->black), False,
538 itemPtr->text, strlen(itemPtr->text));
544 static void
545 paintList(List *lPtr)
547 W_Screen *scrPtr = lPtr->view->screen;
548 int i, lim;
550 if (!lPtr->view->flags.mapped)
551 return;
553 if (WMGetArrayItemCount(lPtr->items) > 0) {
554 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
555 > WMGetArrayItemCount(lPtr->items)) {
557 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
558 XClearArea(scrPtr->display, lPtr->view->window, 19,
559 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
560 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
561 } else {
562 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
564 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
565 paintItem(lPtr, i);
567 } else {
568 XClearWindow(scrPtr->display, lPtr->view->window);
570 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
571 lPtr->view->size.height, WRSunken);
574 #if 0
575 static void
576 scrollTo(List *lPtr, int newTop)
580 #endif
582 static void
583 updateScroller(List *lPtr)
585 float knobProportion, floatValue, tmp;
586 int count = WMGetArrayItemCount(lPtr->items);
588 if (lPtr->idleID)
589 WMDeleteIdleHandler(lPtr->idleID);
590 lPtr->idleID = NULL;
592 paintList(lPtr);
594 if (count == 0 || count <= lPtr->fullFitLines)
595 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
596 else {
597 tmp = lPtr->fullFitLines;
598 knobProportion = tmp/(float)count;
600 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
602 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
607 static void
608 handleEvents(XEvent *event, void *data)
610 List *lPtr = (List*)data;
612 CHECK_CLASS(data, WC_List);
615 switch (event->type) {
616 case Expose:
617 if (event->xexpose.count!=0)
618 break;
619 paintList(lPtr);
620 break;
622 case DestroyNotify:
623 destroyList(lPtr);
624 break;
630 static int
631 matchTitle(void *item, void *title)
633 return (strcmp(((WMListItem*)item)->text, (char*)title)==0 ? 1 : 0);
638 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
640 return WMFindInArray(lPtr->items, matchTitle, title);
644 void
645 WMSelectListItem(WMList *lPtr, int row)
647 WMListItem *item, *oldSelected;
649 if (row >= WMGetArrayItemCount(lPtr->items))
650 return;
651 if (row < 0) {
652 /* Should row = -1 deselect all or just do nothing ?. Check it. -Dan */
653 if (!lPtr->flags.allowMultipleSelection) {
654 WMUnselectAllListItems(lPtr);
656 return;
659 item = WMGetFromArray(lPtr->items, row);
660 if (item->selected)
661 return; /* Return if already selected */
663 oldSelected = WMGetFromArray(lPtr->selectedItems, 0);
665 /* unselect previous selected item if case */
666 if (!lPtr->flags.allowMultipleSelection && oldSelected) {
667 int oldSelectedRow = WMGetListSelectedItemRow(lPtr);
669 // better call WMUnselectAllListItems() here? -Dan
670 oldSelected->selected = 0;
671 WMDeleteFromArray(lPtr->selectedItems, 0);
672 // This is faster and have the same effect in the single selected case
673 // but may leave xxx->selected flags set after a multi->single selected
674 // switch
675 //WMEmptyArray(lPtr->selectedItems);
677 if (lPtr->view->flags.mapped && oldSelectedRow>=lPtr->topItem
678 && oldSelectedRow<=lPtr->topItem+lPtr->fullFitLines) {
679 paintItem(lPtr, oldSelectedRow);
683 /* select item */
684 item->selected = 1;
685 WMAddToArray(lPtr->selectedItems, item);
687 if (lPtr->view->flags.mapped) {
688 paintItem(lPtr, row);
690 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
691 > lPtr->view->size.height-2)
692 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
693 lPtr->view->size.width, lPtr->view->size.height,
694 WRSunken);
696 //lPtr->selectedItem = row;
698 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
702 static int
703 getItemIndexAt(List *lPtr, int clickY)
705 int index;
707 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
709 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
710 return -1;
712 return index;
716 static void
717 handleActionEvents(XEvent *event, void *data)
719 List *lPtr = (List*)data;
720 int tmp;
721 int topItem = lPtr->topItem;
723 CHECK_CLASS(data, WC_List);
725 switch (event->type) {
726 case ButtonRelease:
727 #define CHECK_WHEEL_PATCH
728 #ifdef CHECK_WHEEL_PATCH
729 /* Ignore mouse wheel events, they're not "real" button events */
730 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
731 event->xbutton.button == WINGsConfiguration.mouseWheelDown)
732 break;
733 #endif
735 lPtr->flags.buttonPressed = 0;
736 tmp = getItemIndexAt(lPtr, event->xbutton.y);
738 if (tmp == lPtr->selectedItem && tmp >= 0) {
739 if (lPtr->action)
740 (*lPtr->action)(lPtr, lPtr->clientData);
742 break;
744 case EnterNotify:
745 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
746 lPtr->flags.buttonWasPressed = 0;
747 break;
749 case LeaveNotify:
750 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
751 lPtr->flags.buttonPressed = 0;
752 break;
754 case ButtonPress:
755 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
756 #ifdef CHECK_WHEEL_PATCH
757 /* Mouse wheel events need to be properly handled here. It would
758 * be best to somehow route them to lPtr->vScroller so that the
759 * correct chain of actions is triggered. However, I found no
760 * clean way to do so, so I mostly copied the code that deals with
761 * WSIncrementPage and WSDecrementPage from vScrollCallBack.
763 * - Martynas Kunigelis <diskena@linuxfreak.com> */
765 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
766 /* Wheel down */
767 int itemCount = WMGetArrayItemCount(lPtr->items);
768 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
769 int incr = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
770 lPtr->topItem += incr;
772 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
773 lPtr->topItem = itemCount - lPtr->fullFitLines;
775 updateScroller(lPtr);
777 break;
780 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
781 /* Wheel up */
782 if (lPtr->topItem > 0) {
783 int decr = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
784 lPtr->topItem -= decr;
786 if (lPtr->topItem < 0)
787 lPtr->topItem = 0;
789 updateScroller(lPtr);
791 break;
793 #endif
795 tmp = getItemIndexAt(lPtr, event->xbutton.y);
796 lPtr->flags.buttonPressed = 1;
798 if (tmp >= 0) {
799 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
800 WMSelectListItem(lPtr, tmp);
801 if (lPtr->doubleAction)
802 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
803 } else {
804 WMSelectListItem(lPtr, tmp);
808 break;
810 case MotionNotify:
811 if (lPtr->flags.buttonPressed) {
812 tmp = getItemIndexAt(lPtr, event->xmotion.y);
813 if (tmp>=0 && tmp != lPtr->selectedItem) {
814 WMSelectListItem(lPtr, tmp);
817 break;
819 if (lPtr->topItem != topItem)
820 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
824 static void
825 updateGeometry(WMList *lPtr)
827 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
828 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
829 lPtr->flags.dontFitAll = 1;
830 } else {
831 lPtr->flags.dontFitAll = 0;
834 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
835 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
836 if (lPtr->topItem < 0)
837 lPtr->topItem = 0;
840 updateScroller(lPtr);
844 static void
845 didResizeList(W_ViewDelegate *self, WMView *view)
847 WMList *lPtr = (WMList*)view->self;
849 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
851 updateGeometry(lPtr);
855 static void
856 destroyList(List *lPtr)
858 if (lPtr->idleID)
859 WMDeleteIdleHandler(lPtr->idleID);
860 lPtr->idleID = NULL;
862 WMFreeArray(lPtr->items);
864 wfree(lPtr);