WMList compiles now, single selection seems to be ok (as it used to be) but
[wmaker-crm.git] / WINGs / wlist.c
blobbad5c16c45f0cbdc1a1107ee88937beda21758a9
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 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 adding elements */
32 WMScroller *vScroller;
34 struct {
35 unsigned int allowMultipleSelection:1;
36 unsigned int allowEmptySelection:1;
37 unsigned int userDrawn:1;
38 unsigned int userItemHeight:1;
39 unsigned int dontFitAll:1; /* 1 = last item won't be fully visible */
40 unsigned int redrawPending:1;
41 unsigned int buttonPressed:1;
42 unsigned int buttonWasPressed:1;
43 } flags;
44 } List;
48 #define DEFAULT_WIDTH 150
49 #define DEFAULT_HEIGHT 150
52 static void destroyList(List *lPtr);
53 static void paintList(List *lPtr);
56 static void handleEvents(XEvent *event, void *data);
57 static void handleActionEvents(XEvent *event, void *data);
58 static void updateScroller(List *lPtr);
60 static void vScrollCallBack(WMWidget *scroller, void *self);
62 static void updateGeometry(WMList *lPtr);
63 static void didResizeList();
66 W_ViewDelegate _ListViewDelegate = {
67 NULL,
68 NULL,
69 didResizeList,
70 NULL,
71 NULL
75 static void
76 releaseItem(void *data)
78 WMListItem *item = (WMListItem*)data;
80 if (item->text)
81 wfree(item->text);
82 wfree(item);
86 WMList*
87 WMCreateList(WMWidget *parent)
89 List *lPtr;
90 W_Screen *scrPtr = W_VIEW(parent)->screen;
92 lPtr = wmalloc(sizeof(List));
93 memset(lPtr, 0, sizeof(List));
95 lPtr->widgetClass = WC_List;
97 lPtr->view = W_CreateView(W_VIEW(parent));
98 if (!lPtr->view) {
99 wfree(lPtr);
100 return NULL;
102 lPtr->view->self = lPtr;
104 lPtr->view->delegate = &_ListViewDelegate;
106 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
107 |ClientMessageMask, handleEvents, lPtr);
109 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
110 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
111 handleActionEvents, lPtr);
113 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
115 lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
116 lPtr->selectedItems = WMCreateArray(4);
118 /* create the vertical scroller */
119 lPtr->vScroller = WMCreateScroller(lPtr);
120 WMMoveWidget(lPtr->vScroller, 1, 1);
121 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
123 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
125 /* make the scroller map itself when it's realized */
126 WMMapWidget(lPtr->vScroller);
128 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
130 return lPtr;
134 void
135 WMSetListAllowMultipleSelection(WMList *lPtr, Bool flag)
137 lPtr->flags.allowMultipleSelection = flag ? 1 : 0;
142 void
143 WMSetListAllowEmptySelection(WMList *lPtr, Bool flag)
145 lPtr->flags.allowEmptySelection = flag ? 1 : 0;
149 static int
150 comparator(const void *a, const void *b)
152 return (strcmp((*(WMListItem**)a)->text, (*(WMListItem**)b)->text));
156 void
157 WMSortListItems(WMList *lPtr)
159 WMSortArray(lPtr->items, comparator);
161 paintList(lPtr);
166 void
167 WMSortListItemsWithComparer(WMList *lPtr, WMCompareDataProc *func)
169 WMSortArray(lPtr->items, func);
171 paintList(lPtr);
176 WMListItem*
177 WMInsertListItem(WMList *lPtr, int row, char *text)
179 WMListItem *item;
181 CHECK_CLASS(lPtr, WC_List);
183 item = wmalloc(sizeof(WMListItem));
184 memset(item, 0, sizeof(WMListItem));
185 item->text = wstrdup(text);
187 row = WMIN(row, WMGetArrayItemCount(lPtr->items));
189 if (row < 0)
190 WMAddToArray(lPtr->items, item);
191 else
192 WMInsertInArray(lPtr->items, row, item);
194 /* update the scroller when idle, so that we don't waste time
195 * updating it when another item is going to be added later */
196 if (!lPtr->idleID) {
197 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
200 return item;
205 WMRemoveListItem(WMList *lPtr, int row)
207 WMListItem *item;
208 int topItem = lPtr->topItem;
209 int selNotify = 0;
211 CHECK_CLASS(lPtr, WC_List);
213 if (row < 0 || row >= WMGetArrayItemCount(lPtr->items))
214 return 0;
216 item = WMGetFromArray(lPtr->items, row);
217 if (item->selected) {
218 WMRemoveFromArray(lPtr->selectedItems, item);
219 //WMUnselectListItem(lPtr, row);
220 selNotify = 1;
223 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
224 lPtr->topItem--;
225 if (lPtr->topItem < 0)
226 lPtr->topItem = 0;
228 WMDeleteFromArray(lPtr->items, row);
230 if (!lPtr->idleID) {
231 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
233 if (lPtr->topItem != topItem) {
234 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
236 if (selNotify) {
237 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
240 return 1;
244 WMListItem*
245 WMGetListItem(WMList *lPtr, int row)
247 return WMGetFromArray(lPtr->items, row);
251 WMArray*
252 WMGetListItems(WMList *lPtr)
254 return lPtr->items;
258 void
259 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
261 lPtr->flags.userDrawn = 1;
262 lPtr->draw = proc;
266 void
267 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
269 assert(height > 0);
271 lPtr->flags.userItemHeight = 1;
272 lPtr->itemHeight = height;
274 updateGeometry(lPtr);
278 void
279 WMClearList(WMList *lPtr)
281 int selNo = WMGetArrayItemCount(lPtr->selectedItems);
283 WMEmptyArray(lPtr->selectedItems);
284 WMEmptyArray(lPtr->items);
286 lPtr->topItem = 0;
288 if (!lPtr->idleID) {
289 WMDeleteIdleHandler(lPtr->idleID);
290 lPtr->idleID = NULL;
292 if (lPtr->view->flags.realized) {
293 updateScroller(lPtr);
295 if (selNo > 0) {
296 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
301 void
302 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
304 lPtr->action = action;
305 lPtr->clientData = clientData;
309 void
310 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
312 lPtr->doubleAction = action;
313 lPtr->doubleClientData = clientData;
317 WMArray*
318 WMGetListSelectedItems(WMList *lPtr)
320 return lPtr->selectedItems;
324 WMListItem*
325 WMGetListSelectedItem(WMList *lPtr)
327 return WMGetFromArray(lPtr->selectedItems, 0);
332 WMGetListSelectedItemRow(WMList *lPtr)
334 WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
336 return (item!=NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
341 WMGetListItemHeight(WMList *lPtr)
343 return lPtr->itemHeight;
347 void
348 WMSetListPosition(WMList *lPtr, int row)
350 lPtr->topItem = row;
351 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
352 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
354 if (lPtr->topItem < 0)
355 lPtr->topItem = 0;
357 if (lPtr->view->flags.realized)
358 updateScroller(lPtr);
362 void
363 WMSetListBottomPosition(WMList *lPtr, int row)
365 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
366 lPtr->topItem = row - lPtr->fullFitLines;
367 if (lPtr->topItem < 0)
368 lPtr->topItem = 0;
369 if (lPtr->view->flags.realized)
370 updateScroller(lPtr);
376 WMGetListNumberOfRows(WMList *lPtr)
378 return WMGetArrayItemCount(lPtr->items);
383 WMGetListPosition(WMList *lPtr)
385 return lPtr->topItem;
389 Bool
390 WMListAllowsMultipleSelection(WMList *lPtr)
392 return lPtr->flags.allowMultipleSelection;
396 Bool
397 WMListAllowsEmptySelection(WMList *lPtr)
399 return lPtr->flags.allowEmptySelection;
403 static void
404 scrollByAmount(WMList *lPtr, int amount)
406 int itemCount = WMGetArrayItemCount(lPtr->items);
408 if ((amount < 0 && lPtr->topItem > 0) ||
409 (amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
411 lPtr->topItem += amount;
412 if (lPtr->topItem < 0)
413 lPtr->topItem = 0;
414 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
415 lPtr->topItem = itemCount - lPtr->fullFitLines;
417 updateScroller(lPtr);
422 static void
423 vScrollCallBack(WMWidget *scroller, void *self)
425 WMList *lPtr = (WMList*)self;
426 int height;
427 int oldTopItem = lPtr->topItem;
428 int itemCount = WMGetArrayItemCount(lPtr->items);
430 height = lPtr->view->size.height - 4;
432 switch (WMGetScrollerHitPart((WMScroller*)scroller)) {
433 case WSDecrementLine:
434 scrollByAmount(lPtr, -1);
435 break;
437 case WSIncrementLine:
438 scrollByAmount(lPtr, 1);
439 break;
441 case WSDecrementPage:
442 scrollByAmount(lPtr, -lPtr->fullFitLines+(1-lPtr->flags.dontFitAll)+1);
443 break;
445 case WSIncrementPage:
446 scrollByAmount(lPtr, lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1);
447 break;
449 case WSKnob:
450 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
451 (float)(itemCount - lPtr->fullFitLines);
453 if (oldTopItem != lPtr->topItem)
454 paintList(lPtr); // use updateScroller(lPtr) here?
455 break;
457 case WSKnobSlot:
458 case WSNoPart:
459 default:
460 /* do nothing */
461 break;
464 if (lPtr->topItem != oldTopItem)
465 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
469 static void
470 paintItem(List *lPtr, int index)
472 WMView *view = lPtr->view;
473 W_Screen *scr = view->screen;
474 int width, height, x, y;
475 WMListItem *itemPtr;
477 itemPtr = WMGetFromArray(lPtr->items, index);
479 width = lPtr->view->size.width - 2 - 19;
480 height = lPtr->itemHeight;
481 x = 19;
482 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
484 if (lPtr->flags.userDrawn) {
485 WMRect rect;
486 int flags;
488 rect.size.width = width;
489 rect.size.height = height;
490 rect.pos.x = x;
491 rect.pos.y = y;
493 flags = itemPtr->uflags;
494 if (itemPtr->disabled)
495 flags |= WLDSDisabled;
496 if (itemPtr->selected)
497 flags |= WLDSSelected;
498 if (itemPtr->isBranch)
499 flags |= WLDSIsBranch;
501 if (lPtr->draw)
502 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
503 &rect);
504 } else {
505 if (itemPtr->selected) {
506 XFillRectangle(scr->display, view->window, WMColorGC(scr->white),
507 x, y, width, height);
508 } else {
509 XClearArea(scr->display, view->window, x, y, width, height, False);
512 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
513 WALeft, WMColorGC(scr->black), False,
514 itemPtr->text, strlen(itemPtr->text));
520 static void
521 paintList(List *lPtr)
523 W_Screen *scrPtr = lPtr->view->screen;
524 int i, lim;
526 if (!lPtr->view->flags.mapped)
527 return;
529 if (WMGetArrayItemCount(lPtr->items) > 0) {
530 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
531 > WMGetArrayItemCount(lPtr->items)) {
533 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
534 XClearArea(scrPtr->display, lPtr->view->window, 19,
535 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
536 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
537 } else {
538 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
540 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
541 paintItem(lPtr, i);
543 } else {
544 XClearWindow(scrPtr->display, lPtr->view->window);
546 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
547 lPtr->view->size.height, WRSunken);
550 #if 0
551 static void
552 scrollTo(List *lPtr, int newTop)
556 #endif
558 static void
559 updateScroller(List *lPtr)
561 float knobProportion, floatValue, tmp;
562 int count = WMGetArrayItemCount(lPtr->items);
564 if (lPtr->idleID)
565 WMDeleteIdleHandler(lPtr->idleID);
566 lPtr->idleID = NULL;
568 paintList(lPtr);
570 if (count == 0 || count <= lPtr->fullFitLines)
571 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
572 else {
573 tmp = lPtr->fullFitLines;
574 knobProportion = tmp/(float)count;
576 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
578 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
583 static void
584 handleEvents(XEvent *event, void *data)
586 List *lPtr = (List*)data;
588 CHECK_CLASS(data, WC_List);
591 switch (event->type) {
592 case Expose:
593 if (event->xexpose.count!=0)
594 break;
595 paintList(lPtr);
596 break;
598 case DestroyNotify:
599 destroyList(lPtr);
600 break;
606 static int
607 matchTitle(void *item, void *title)
609 return (strcmp(((WMListItem*)item)->text, (char*)title)==0 ? 1 : 0);
614 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
616 return WMFindInArray(lPtr->items, matchTitle, title);
620 void
621 WMSelectListItem(WMList *lPtr, int row)
623 WMListItem *item, *oldSelected;
625 if (row >= WMGetArrayItemCount(lPtr->items))
626 return;
627 if (row < 0) {
628 /* Should row = -1 deselect all or just do nothing ?. Check it. -Dan */
629 if (!lPtr->flags.allowMultipleSelection) {
630 WMUnselectAllListItems(lPtr);
632 return;
635 item = WMGetFromArray(lPtr->items, row);
636 if (item->selected)
637 return; /* Return if already selected */
639 oldSelected = WMGetFromArray(lPtr->selectedItems, 0);
641 /* unselect previous selected item if case */
642 if (!lPtr->flags.allowMultipleSelection && oldSelected) {
643 int oldSelectedRow = WMGetListSelectedItemRow(lPtr);
645 // better call WMUnselectAllListItems() here? -Dan
646 oldSelected->selected = 0;
647 WMDeleteFromArray(lPtr->selectedItems, 0);
648 // This is faster and have the same effect in the single selected case
649 // but may leave xxx->selected flags set after a multi->single selected
650 // switch
651 //WMEmptyArray(lPtr->selectedItems);
653 if (lPtr->view->flags.mapped && oldSelectedRow>=lPtr->topItem
654 && oldSelectedRow<=lPtr->topItem+lPtr->fullFitLines) {
655 paintItem(lPtr, oldSelectedRow);
659 /* select item */
660 item->selected = 1;
661 WMAddToArray(lPtr->selectedItems, item);
663 if (lPtr->view->flags.mapped) {
664 paintItem(lPtr, row);
666 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
667 > lPtr->view->size.height-2)
668 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
669 lPtr->view->size.width, lPtr->view->size.height,
670 WRSunken);
673 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
677 // make them return an int
679 void
680 WMUnselectListItem(WMList *lPtr, int row)
682 WMListItem *item = WMGetFromArray(lPtr->items, row);
684 if (!item || !item->selected)
685 return;
687 // also add check for allowEmptySelection
689 item->selected = 0;
690 WMRemoveFromArray(lPtr->selectedItems, item);
692 if (lPtr->view->flags.mapped && row>=lPtr->topItem
693 && row<=lPtr->topItem+lPtr->fullFitLines) {
694 paintItem(lPtr, row);
699 void
700 WMSelectAllListItems(WMList *lPtr)
702 int i;
703 WMListItem *item;
705 if (!lPtr->flags.allowMultipleSelection)
706 return;
708 // implement some WMDuplicateArray() ?
709 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
710 item = WMGetFromArray(lPtr->items, i);
711 if (!item->selected) {
712 item->selected = 1;
713 WMAddToArray(lPtr->selectedItems, item);
714 if (lPtr->view->flags.mapped && i>=lPtr->topItem
715 && i<=lPtr->topItem+lPtr->fullFitLines) {
716 paintItem(lPtr, i);
723 void
724 WMUnselectAllListItems(WMList *lPtr)
726 int i;
727 WMListItem *item;
729 // check for allowEmptySelection
731 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
732 item = WMGetFromArray(lPtr->items, i);
733 if (item->selected) {
734 item->selected = 0;
735 if (lPtr->view->flags.mapped && i>=lPtr->topItem
736 && i<=lPtr->topItem+lPtr->fullFitLines) {
737 paintItem(lPtr, i);
742 WMEmptyArray(lPtr->selectedItems);
746 static int
747 getItemIndexAt(List *lPtr, int clickY)
749 int index;
751 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
753 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
754 return -1;
756 return index;
760 static void
761 handleActionEvents(XEvent *event, void *data)
763 List *lPtr = (List*)data;
764 int tmp;
765 int topItem = lPtr->topItem;
766 static int oldClicked = -1;
768 CHECK_CLASS(data, WC_List);
770 switch (event->type) {
771 case ButtonRelease:
772 #define CHECK_WHEEL_PATCH
773 #ifdef CHECK_WHEEL_PATCH
774 /* Ignore mouse wheel events, they're not "real" button events */
775 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
776 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
777 break;
779 #endif
781 lPtr->flags.buttonPressed = 0;
782 tmp = getItemIndexAt(lPtr, event->xbutton.y);
784 if (/*tmp == lPtr->selectedItem && */tmp >= 0) {
785 if (lPtr->action)
786 (*lPtr->action)(lPtr, lPtr->clientData);
789 oldClicked = tmp;
790 break;
792 case EnterNotify:
793 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
794 lPtr->flags.buttonWasPressed = 0;
795 break;
797 case LeaveNotify:
798 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
799 lPtr->flags.buttonPressed = 0;
800 break;
802 case ButtonPress:
803 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
804 #ifdef CHECK_WHEEL_PATCH
805 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
806 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
807 int amount = 0;
809 if (event->xbutton.state & ShiftMask) {
810 amount = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
811 } else if (event->xbutton.state & ControlMask) {
812 amount = 1;
813 } else {
814 amount = lPtr->fullFitLines / 3;
815 if (amount == 0)
816 amount++;
818 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
819 amount = -amount;
821 scrollByAmount(lPtr, amount);
822 break;
824 #endif
826 tmp = getItemIndexAt(lPtr, event->xbutton.y);
827 lPtr->flags.buttonPressed = 1;
829 if (tmp >= 0) {
830 if (tmp == oldClicked && WMIsDoubleClick(event)) {
831 WMSelectListItem(lPtr, tmp);
832 if (lPtr->doubleAction)
833 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
834 } else {
835 WMSelectListItem(lPtr, tmp);
839 oldClicked = tmp;
841 break;
843 case MotionNotify:
844 if (lPtr->flags.buttonPressed) {
845 tmp = getItemIndexAt(lPtr, event->xmotion.y);
846 if (tmp>=0 /*&& tmp != lPtr->selectedItem*/) {
847 WMSelectListItem(lPtr, tmp);
850 break;
852 if (lPtr->topItem != topItem)
853 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
857 static void
858 updateGeometry(WMList *lPtr)
860 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
861 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
862 lPtr->flags.dontFitAll = 1;
863 } else {
864 lPtr->flags.dontFitAll = 0;
867 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
868 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
869 if (lPtr->topItem < 0)
870 lPtr->topItem = 0;
873 updateScroller(lPtr);
877 static void
878 didResizeList(W_ViewDelegate *self, WMView *view)
880 WMList *lPtr = (WMList*)view->self;
882 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
884 updateGeometry(lPtr);
888 static void
889 destroyList(List *lPtr)
891 if (lPtr->idleID)
892 WMDeleteIdleHandler(lPtr->idleID);
893 lPtr->idleID = NULL;
895 WMFreeArray(lPtr->items);
897 wfree(lPtr);