Better behavior for the multiple selection in lists.
[wmaker-crm.git] / WINGs / wlist.c
blobaeaa7d85db686e68378ce12a983637debd8761c0
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();
65 static void unselectAllListItems(WMList *lPtr, WMListItem *exceptThis);
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 return lPtr;
136 void
137 WMSetListAllowMultipleSelection(WMList *lPtr, Bool flag)
139 lPtr->flags.allowMultipleSelection = flag ? 1 : 0;
143 void
144 WMSetListAllowEmptySelection(WMList *lPtr, Bool flag)
146 lPtr->flags.allowEmptySelection = flag ? 1 : 0;
150 static int
151 comparator(const void *a, const void *b)
153 return (strcmp((*(WMListItem**)a)->text, (*(WMListItem**)b)->text));
157 void
158 WMSortListItems(WMList *lPtr)
160 WMSortArray(lPtr->items, comparator);
162 paintList(lPtr);
167 void
168 WMSortListItemsWithComparer(WMList *lPtr, WMCompareDataProc *func)
170 WMSortArray(lPtr->items, func);
172 paintList(lPtr);
177 WMListItem*
178 WMInsertListItem(WMList *lPtr, int row, char *text)
180 WMListItem *item;
182 CHECK_CLASS(lPtr, WC_List);
184 item = wmalloc(sizeof(WMListItem));
185 memset(item, 0, sizeof(WMListItem));
186 item->text = wstrdup(text);
188 row = WMIN(row, WMGetArrayItemCount(lPtr->items));
190 if (row < 0)
191 WMAddToArray(lPtr->items, item);
192 else
193 WMInsertInArray(lPtr->items, row, item);
195 /* update the scroller when idle, so that we don't waste time
196 * updating it when another item is going to be added later */
197 if (!lPtr->idleID) {
198 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
201 return item;
205 void
206 WMRemoveListItem(WMList *lPtr, int row)
208 WMListItem *item;
209 int topItem = lPtr->topItem;
210 int selNotify = 0;
212 CHECK_CLASS(lPtr, WC_List);
214 /*wassertr(row>=0 && row<WMGetArrayItemCount(lPtr->items));*/
215 if (row<0 || row>=WMGetArrayItemCount(lPtr->items))
216 return;
218 item = WMGetFromArray(lPtr->items, row);
219 if (item->selected) {
220 WMRemoveFromArray(lPtr->selectedItems, item);
221 selNotify = 1;
224 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
225 lPtr->topItem--;
226 if (lPtr->topItem < 0)
227 lPtr->topItem = 0;
229 WMDeleteFromArray(lPtr->items, row);
231 if (!lPtr->idleID) {
232 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
234 if (lPtr->topItem != topItem) {
235 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
237 if (selNotify) {
238 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
243 WMListItem*
244 WMGetListItem(WMList *lPtr, int row)
246 return WMGetFromArray(lPtr->items, row);
250 WMArray*
251 WMGetListItems(WMList *lPtr)
253 return lPtr->items;
257 void
258 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
260 lPtr->flags.userDrawn = 1;
261 lPtr->draw = proc;
265 void
266 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
268 assert(height > 0);
270 lPtr->flags.userItemHeight = 1;
271 lPtr->itemHeight = height;
273 updateGeometry(lPtr);
277 void
278 WMClearList(WMList *lPtr)
280 int selNo = WMGetArrayItemCount(lPtr->selectedItems);
282 WMEmptyArray(lPtr->selectedItems);
283 WMEmptyArray(lPtr->items);
285 lPtr->topItem = 0;
287 if (!lPtr->idleID) {
288 WMDeleteIdleHandler(lPtr->idleID);
289 lPtr->idleID = NULL;
291 if (lPtr->view->flags.realized) {
292 updateScroller(lPtr);
294 if (selNo > 0) {
295 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
300 void
301 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
303 lPtr->action = action;
304 lPtr->clientData = clientData;
308 void
309 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
311 lPtr->doubleAction = action;
312 lPtr->doubleClientData = clientData;
316 WMArray*
317 WMGetListSelectedItems(WMList *lPtr)
319 return lPtr->selectedItems;
323 WMListItem*
324 WMGetListSelectedItem(WMList *lPtr)
326 return WMGetFromArray(lPtr->selectedItems, 0);
331 WMGetListSelectedItemRow(WMList *lPtr)
333 WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
335 return (item!=NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
340 WMGetListItemHeight(WMList *lPtr)
342 return lPtr->itemHeight;
346 void
347 WMSetListPosition(WMList *lPtr, int row)
349 lPtr->topItem = row;
350 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
351 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
353 if (lPtr->topItem < 0)
354 lPtr->topItem = 0;
356 if (lPtr->view->flags.realized)
357 updateScroller(lPtr);
361 void
362 WMSetListBottomPosition(WMList *lPtr, int row)
364 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
365 lPtr->topItem = row - lPtr->fullFitLines;
366 if (lPtr->topItem < 0)
367 lPtr->topItem = 0;
368 if (lPtr->view->flags.realized)
369 updateScroller(lPtr);
375 WMGetListNumberOfRows(WMList *lPtr)
377 return WMGetArrayItemCount(lPtr->items);
382 WMGetListPosition(WMList *lPtr)
384 return lPtr->topItem;
388 Bool
389 WMListAllowsMultipleSelection(WMList *lPtr)
391 return lPtr->flags.allowMultipleSelection;
395 Bool
396 WMListAllowsEmptySelection(WMList *lPtr)
398 return lPtr->flags.allowEmptySelection;
402 static void
403 scrollByAmount(WMList *lPtr, int amount)
405 int itemCount = WMGetArrayItemCount(lPtr->items);
407 if ((amount < 0 && lPtr->topItem > 0) ||
408 (amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
410 lPtr->topItem += amount;
411 if (lPtr->topItem < 0)
412 lPtr->topItem = 0;
413 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
414 lPtr->topItem = itemCount - lPtr->fullFitLines;
416 updateScroller(lPtr);
421 static void
422 vScrollCallBack(WMWidget *scroller, void *self)
424 WMList *lPtr = (WMList*)self;
425 int height;
426 int oldTopItem = lPtr->topItem;
427 int itemCount = WMGetArrayItemCount(lPtr->items);
429 height = lPtr->view->size.height - 4;
431 switch (WMGetScrollerHitPart((WMScroller*)scroller)) {
432 case WSDecrementLine:
433 scrollByAmount(lPtr, -1);
434 break;
436 case WSIncrementLine:
437 scrollByAmount(lPtr, 1);
438 break;
440 case WSDecrementPage:
441 scrollByAmount(lPtr, -lPtr->fullFitLines+(1-lPtr->flags.dontFitAll)+1);
442 break;
444 case WSIncrementPage:
445 scrollByAmount(lPtr, lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1);
446 break;
448 case WSDecrementWheel:
449 scrollByAmount(lPtr, -lPtr->fullFitLines / 3);
450 break;
452 case WSIncrementWheel:
453 scrollByAmount(lPtr, lPtr->fullFitLines / 3);
454 break;
456 case WSKnob:
457 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
458 (float)(itemCount - lPtr->fullFitLines);
460 if (oldTopItem != lPtr->topItem)
461 paintList(lPtr); // use updateScroller(lPtr) here?
462 break;
464 case WSKnobSlot:
465 case WSNoPart:
466 default:
467 /* do nothing */
468 break;
471 if (lPtr->topItem != oldTopItem)
472 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
476 static void
477 paintItem(List *lPtr, int index)
479 WMView *view = lPtr->view;
480 W_Screen *scr = view->screen;
481 int width, height, x, y;
482 WMListItem *itemPtr;
484 itemPtr = WMGetFromArray(lPtr->items, index);
486 width = lPtr->view->size.width - 2 - 19;
487 height = lPtr->itemHeight;
488 x = 19;
489 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
491 if (lPtr->flags.userDrawn) {
492 WMRect rect;
493 int flags;
495 rect.size.width = width;
496 rect.size.height = height;
497 rect.pos.x = x;
498 rect.pos.y = y;
500 flags = itemPtr->uflags;
501 if (itemPtr->disabled)
502 flags |= WLDSDisabled;
503 if (itemPtr->selected)
504 flags |= WLDSSelected;
505 if (itemPtr->isBranch)
506 flags |= WLDSIsBranch;
508 if (lPtr->draw)
509 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
510 &rect);
511 } else {
512 if (itemPtr->selected) {
513 XFillRectangle(scr->display, view->window, WMColorGC(scr->white),
514 x, y, width, height);
515 } else {
516 XClearArea(scr->display, view->window, x, y, width, height, False);
519 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
520 WALeft, WMColorGC(scr->black), False,
521 itemPtr->text, strlen(itemPtr->text));
524 if ((index-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight >
525 lPtr->view->size.height-2) {
526 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
527 lPtr->view->size.width, lPtr->view->size.height,
528 WRSunken);
534 static void
535 paintList(List *lPtr)
537 W_Screen *scrPtr = lPtr->view->screen;
538 int i, lim;
540 if (!lPtr->view->flags.mapped)
541 return;
543 if (WMGetArrayItemCount(lPtr->items) > 0) {
544 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
545 > WMGetArrayItemCount(lPtr->items)) {
547 lim = WMGetArrayItemCount(lPtr->items) - 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;
576 int count = WMGetArrayItemCount(lPtr->items);
578 if (lPtr->idleID)
579 WMDeleteIdleHandler(lPtr->idleID);
580 lPtr->idleID = NULL;
582 paintList(lPtr);
584 if (count == 0 || count <= lPtr->fullFitLines)
585 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
586 else {
587 tmp = lPtr->fullFitLines;
588 knobProportion = tmp/(float)count;
590 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
592 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
597 static void
598 handleEvents(XEvent *event, void *data)
600 List *lPtr = (List*)data;
602 CHECK_CLASS(data, WC_List);
605 switch (event->type) {
606 case Expose:
607 if (event->xexpose.count!=0)
608 break;
609 paintList(lPtr);
610 break;
612 case DestroyNotify:
613 destroyList(lPtr);
614 break;
620 static int
621 matchTitle(void *item, void *title)
623 return (strcmp(((WMListItem*)item)->text, (char*)title)==0 ? 1 : 0);
628 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
630 return WMFindInArray(lPtr->items, matchTitle, title);
634 void
635 WMSelectListItem(WMList *lPtr, int row)
637 WMListItem *item;
639 if (row >= WMGetArrayItemCount(lPtr->items))
640 return;
642 if (row < 0) {
643 /* row = -1 will deselects all for backward compatibility.
644 * will be removed later. -Dan */
645 WMUnselectAllListItems(lPtr);
646 return;
649 item = WMGetFromArray(lPtr->items, row);
650 if (item->selected)
651 return; /* Return if already selected */
653 if (!lPtr->flags.allowMultipleSelection) {
654 /* unselect previous selected items */
655 unselectAllListItems(lPtr, NULL);
658 /* select item */
659 item->selected = 1;
660 WMAddToArray(lPtr->selectedItems, item);
662 if (lPtr->view->flags.mapped && row>=lPtr->topItem
663 && row<=lPtr->topItem+lPtr->fullFitLines) {
664 paintItem(lPtr, row);
667 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
671 void
672 WMUnselectListItem(WMList *lPtr, int row)
674 WMListItem *item = WMGetFromArray(lPtr->items, row);
676 if (!item || !item->selected)
677 return;
679 if (!lPtr->flags.allowEmptySelection &&
680 WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
681 return;
684 item->selected = 0;
685 WMRemoveFromArray(lPtr->selectedItems, item);
687 if (lPtr->view->flags.mapped && row>=lPtr->topItem
688 && row<=lPtr->topItem+lPtr->fullFitLines) {
689 paintItem(lPtr, row);
692 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
696 void
697 WMSelectListItemsInRange(WMList *lPtr, WMRange range)
699 WMListItem *item;
700 int position = range.position, k = 1, notify = 0;
701 int total = WMGetArrayItemCount(lPtr->items);
703 if (!lPtr->flags.allowMultipleSelection)
704 return;
705 if (range.count==0)
706 return; /* Nothing to select */
708 if (range.count < 0) {
709 range.count = -range.count;
710 k = -1;
713 for (; range.count>0 && position>=0 && position<total; range.count--) {
714 item = WMGetFromArray(lPtr->items, position);
715 if (!item->selected) {
716 item->selected = 1;
717 WMAddToArray(lPtr->selectedItems, item);
718 if (lPtr->view->flags.mapped && position>=lPtr->topItem
719 && position<=lPtr->topItem+lPtr->fullFitLines) {
720 paintItem(lPtr, position);
722 notify = 1;
724 position += k;
727 if (notify) {
728 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
733 void
734 WMSetListSelectionToRange(WMList *lPtr, WMRange range)
736 WMListItem *item;
737 int mark1, mark2, i, k;
738 int position = range.position, notify = 0;
739 int total = WMGetArrayItemCount(lPtr->items);
741 if (!lPtr->flags.allowMultipleSelection)
742 return;
744 if (range.count==0) {
745 WMUnselectAllListItems(lPtr);
746 return;
749 if (range.count < 0) {
750 mark1 = range.position + range.count + 1;
751 mark2 = range.position + 1;
752 range.count = -range.count;
753 k = -1;
754 } else {
755 mark1 = range.position;
756 mark2 = range.position + range.count;
757 k = 1;
759 if (mark1 > total)
760 mark1 = total;
761 if (mark2 < 0)
762 mark2 = 0;
764 WMEmptyArray(lPtr->selectedItems);
766 for (i=0; i<mark1; i++) {
767 item = WMGetFromArray(lPtr->items, i);
768 if (item->selected) {
769 item->selected = 0;
770 if (lPtr->view->flags.mapped && i>=lPtr->topItem
771 && i<=lPtr->topItem+lPtr->fullFitLines) {
772 paintItem(lPtr, i);
774 notify = 1;
777 for (; range.count>0 && position>=0 && position<total; range.count--) {
778 item = WMGetFromArray(lPtr->items, position);
779 if (!item->selected) {
780 item->selected = 1;
781 if (lPtr->view->flags.mapped && position>=lPtr->topItem
782 && position<=lPtr->topItem+lPtr->fullFitLines) {
783 paintItem(lPtr, position);
785 notify = 1;
787 WMAddToArray(lPtr->selectedItems, item);
788 position += k;
790 for (i=mark2; i<total; i++) {
791 item = WMGetFromArray(lPtr->items, i);
792 if (item->selected) {
793 item->selected = 0;
794 if (lPtr->view->flags.mapped && i>=lPtr->topItem
795 && i<=lPtr->topItem+lPtr->fullFitLines) {
796 paintItem(lPtr, i);
798 notify = 1;
802 if (notify) {
803 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
808 void
809 WMSelectAllListItems(WMList *lPtr)
811 int i;
812 WMListItem *item;
814 if (!lPtr->flags.allowMultipleSelection)
815 return;
817 if (WMGetArrayItemCount(lPtr->items) ==
818 WMGetArrayItemCount(lPtr->selectedItems)) {
819 return; /* All items are selected already */
822 WMFreeArray(lPtr->selectedItems);
823 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
825 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
826 item = WMGetFromArray(lPtr->items, i);
827 if (!item->selected) {
828 item->selected = 1;
829 if (lPtr->view->flags.mapped && i>=lPtr->topItem
830 && i<=lPtr->topItem+lPtr->fullFitLines) {
831 paintItem(lPtr, i);
836 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
840 * Be careful from where you call this function! It doesn't honor the
841 * allowEmptySelection flag and doesn't send a notification about selection
842 * change! You need to manage these in the functions from where you call it.
844 * This will unselect all items if exceptThis is NULL, else will keep
845 * exceptThis selected.
846 * Make sure that exceptThis is one of the already selected items if not NULL!
849 static void
850 unselectAllListItems(WMList *lPtr, WMListItem *exceptThis)
852 int i;
853 WMListItem *item;
855 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
856 item = WMGetFromArray(lPtr->items, i);
857 if (item!=exceptThis && item->selected) {
858 item->selected = 0;
859 if (lPtr->view->flags.mapped && i>=lPtr->topItem
860 && i<=lPtr->topItem+lPtr->fullFitLines) {
861 paintItem(lPtr, i);
866 WMEmptyArray(lPtr->selectedItems);
867 if (exceptThis!=NULL) {
868 exceptThis->selected = 1;
869 WMAddToArray(lPtr->selectedItems, exceptThis);
874 void
875 WMUnselectAllListItems(WMList *lPtr)
877 int keep;
878 WMListItem *keepItem;
880 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
882 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
883 return;
885 keepItem = (keep==1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
887 unselectAllListItems(lPtr, keepItem);
889 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
893 static int
894 getItemIndexAt(List *lPtr, int clickY)
896 int index;
898 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
900 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
901 return -1;
903 return index;
907 static void
908 toggleItemSelection(WMList *lPtr, int index)
910 WMListItem *item = WMGetFromArray(lPtr->items, index);
912 if (item && item->selected) {
913 WMUnselectListItem(lPtr, index);
914 } else {
915 WMSelectListItem(lPtr, index);
920 static void
921 handleActionEvents(XEvent *event, void *data)
923 List *lPtr = (List*)data;
924 int tmp;
925 int topItem = lPtr->topItem;
926 static int lastClicked = -1, prevItem = -1;
928 CHECK_CLASS(data, WC_List);
930 switch (event->type) {
931 case ButtonRelease:
932 /* Ignore mouse wheel events, they're not "real" button events */
933 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
934 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
935 break;
938 lPtr->flags.buttonPressed = 0;
939 tmp = getItemIndexAt(lPtr, event->xbutton.y);
941 if (tmp >= 0) {
942 if (lPtr->action)
943 (*lPtr->action)(lPtr, lPtr->clientData);
946 if (!(event->xbutton.state & ShiftMask))
947 lastClicked = prevItem = tmp;
949 break;
951 case EnterNotify:
952 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
953 lPtr->flags.buttonWasPressed = 0;
954 break;
956 case LeaveNotify:
957 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
958 lPtr->flags.buttonPressed = 0;
959 break;
961 case ButtonPress:
962 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
963 break;
964 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
965 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
966 int amount = 0;
968 if (event->xbutton.state & ControlMask) {
969 amount = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
970 } else if (event->xbutton.state & ShiftMask) {
971 amount = 1;
972 } else {
973 amount = lPtr->fullFitLines / 3;
974 if (amount == 0)
975 amount++;
977 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
978 amount = -amount;
980 scrollByAmount(lPtr, amount);
981 break;
984 tmp = getItemIndexAt(lPtr, event->xbutton.y);
985 lPtr->flags.buttonPressed = 1;
987 if (tmp >= 0) {
988 if (tmp == lastClicked && WMIsDoubleClick(event)) {
989 WMSelectListItem(lPtr, tmp);
990 if (lPtr->doubleAction)
991 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
992 } else {
993 if (!lPtr->flags.allowMultipleSelection) {
994 if (event->xbutton.state & ControlMask) {
995 toggleItemSelection(lPtr, tmp);
996 } else {
997 WMSelectListItem(lPtr, tmp);
999 } else {
1000 WMRange range;
1001 WMListItem *lastSel;
1003 if (event->xbutton.state & ControlMask) {
1004 toggleItemSelection(lPtr, tmp);
1005 } else if (event->xbutton.state & ShiftMask) {
1006 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1007 WMSelectListItem(lPtr, tmp);
1008 } else {
1009 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1010 range.position = WMGetFirstInArray(lPtr->items,
1011 lastSel);
1012 if (tmp >= range.position)
1013 range.count = tmp - range.position + 1;
1014 else
1015 range.count = tmp - range.position - 1;
1017 WMSetListSelectionToRange(lPtr, range);
1019 } else {
1020 range.position = tmp;
1021 range.count = 1;
1022 WMSetListSelectionToRange(lPtr, range);
1028 if (!(event->xbutton.state & ShiftMask))
1029 lastClicked = prevItem = tmp;
1031 break;
1033 case MotionNotify:
1034 if (lPtr->flags.buttonPressed) {
1035 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1036 if (tmp>=0 && tmp!=prevItem) {
1037 if (lPtr->flags.allowMultipleSelection) {
1038 WMRange range;
1040 range.position = lastClicked;
1041 if (tmp >= lastClicked)
1042 range.count = tmp - lastClicked + 1;
1043 else
1044 range.count = tmp - lastClicked - 1;
1045 WMSetListSelectionToRange(lPtr, range);
1046 } else {
1047 WMSelectListItem(lPtr, tmp);
1050 prevItem = tmp;
1052 break;
1054 if (lPtr->topItem != topItem)
1055 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1059 static void
1060 updateGeometry(WMList *lPtr)
1062 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1063 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1064 lPtr->flags.dontFitAll = 1;
1065 } else {
1066 lPtr->flags.dontFitAll = 0;
1069 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1070 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1071 if (lPtr->topItem < 0)
1072 lPtr->topItem = 0;
1075 updateScroller(lPtr);
1079 static void
1080 didResizeList(W_ViewDelegate *self, WMView *view)
1082 WMList *lPtr = (WMList*)view->self;
1084 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
1086 updateGeometry(lPtr);
1090 static void
1091 destroyList(List *lPtr)
1093 if (lPtr->idleID)
1094 WMDeleteIdleHandler(lPtr->idleID);
1095 lPtr->idleID = NULL;
1097 WMFreeArray(lPtr->items);
1099 wfree(lPtr);