Fixed some // style comments
[wmaker-crm.git] / WINGs / wlist.c
blob676bbdedd6b77282cd3bfb4c77a83180bd3142cb
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 WMHandlerID *selectID; /* for selecting items in list while scrolling */
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
53 #define SCROLL_DELAY 100
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);
63 static void updateScroller(void *data);
64 static void scrollForwardSelecting(void *data);
65 static void scrollBackwardSelecting(void *data);
67 static void vScrollCallBack(WMWidget *scroller, void *self);
69 static void toggleItemSelection(WMList *lPtr, int index);
71 static void updateGeometry(WMList *lPtr);
72 static void didResizeList();
74 static void unselectAllListItems(WMList *lPtr, WMListItem *exceptThis);
77 W_ViewDelegate _ListViewDelegate = {
78 NULL,
79 NULL,
80 didResizeList,
81 NULL,
82 NULL
86 static void
87 releaseItem(void *data)
89 WMListItem *item = (WMListItem*)data;
91 if (item->text)
92 wfree(item->text);
93 wfree(item);
97 WMList*
98 WMCreateList(WMWidget *parent)
100 List *lPtr;
101 W_Screen *scrPtr = W_VIEW(parent)->screen;
103 lPtr = wmalloc(sizeof(List));
104 memset(lPtr, 0, sizeof(List));
106 lPtr->widgetClass = WC_List;
108 lPtr->view = W_CreateView(W_VIEW(parent));
109 if (!lPtr->view) {
110 wfree(lPtr);
111 return NULL;
113 lPtr->view->self = lPtr;
115 lPtr->view->delegate = &_ListViewDelegate;
117 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
118 |ClientMessageMask, handleEvents, lPtr);
120 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
121 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
122 handleActionEvents, lPtr);
124 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
126 lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
127 lPtr->selectedItems = WMCreateArray(4);
129 /* create the vertical scroller */
130 lPtr->vScroller = WMCreateScroller(lPtr);
131 WMMoveWidget(lPtr->vScroller, 1, 1);
132 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
134 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
136 /* make the scroller map itself when it's realized */
137 WMMapWidget(lPtr->vScroller);
139 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
141 return lPtr;
145 void
146 WMSetListAllowMultipleSelection(WMList *lPtr, Bool flag)
148 lPtr->flags.allowMultipleSelection = flag ? 1 : 0;
152 void
153 WMSetListAllowEmptySelection(WMList *lPtr, Bool flag)
155 lPtr->flags.allowEmptySelection = flag ? 1 : 0;
159 static int
160 comparator(const void *a, const void *b)
162 return (strcmp((*(WMListItem**)a)->text, (*(WMListItem**)b)->text));
166 void
167 WMSortListItems(WMList *lPtr)
169 WMSortArray(lPtr->items, comparator);
171 paintList(lPtr);
176 void
177 WMSortListItemsWithComparer(WMList *lPtr, WMCompareDataProc *func)
179 WMSortArray(lPtr->items, func);
181 paintList(lPtr);
186 WMListItem*
187 WMInsertListItem(WMList *lPtr, int row, char *text)
189 WMListItem *item;
191 CHECK_CLASS(lPtr, WC_List);
193 item = wmalloc(sizeof(WMListItem));
194 memset(item, 0, sizeof(WMListItem));
195 item->text = wstrdup(text);
197 row = WMIN(row, WMGetArrayItemCount(lPtr->items));
199 if (row < 0)
200 WMAddToArray(lPtr->items, item);
201 else
202 WMInsertInArray(lPtr->items, row, item);
204 /* update the scroller when idle, so that we don't waste time
205 * updating it when another item is going to be added later */
206 if (!lPtr->idleID) {
207 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
210 return item;
214 void
215 WMRemoveListItem(WMList *lPtr, int row)
217 WMListItem *item;
218 int topItem = lPtr->topItem;
219 int selNotify = 0;
221 CHECK_CLASS(lPtr, WC_List);
223 /*wassertr(row>=0 && row<WMGetArrayItemCount(lPtr->items));*/
224 if (row<0 || row>=WMGetArrayItemCount(lPtr->items))
225 return;
227 item = WMGetFromArray(lPtr->items, row);
228 if (item->selected) {
229 WMRemoveFromArray(lPtr->selectedItems, item);
230 selNotify = 1;
233 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
234 lPtr->topItem--;
235 if (lPtr->topItem < 0)
236 lPtr->topItem = 0;
238 WMDeleteFromArray(lPtr->items, row);
240 if (!lPtr->idleID) {
241 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
243 if (lPtr->topItem != topItem) {
244 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
246 if (selNotify) {
247 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
252 WMListItem*
253 WMGetListItem(WMList *lPtr, int row)
255 return WMGetFromArray(lPtr->items, row);
259 WMArray*
260 WMGetListItems(WMList *lPtr)
262 return lPtr->items;
266 void
267 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
269 lPtr->flags.userDrawn = 1;
270 lPtr->draw = proc;
274 void
275 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
277 assert(height > 0);
279 lPtr->flags.userItemHeight = 1;
280 lPtr->itemHeight = height;
282 updateGeometry(lPtr);
286 void
287 WMClearList(WMList *lPtr)
289 int selNo = WMGetArrayItemCount(lPtr->selectedItems);
291 WMEmptyArray(lPtr->selectedItems);
292 WMEmptyArray(lPtr->items);
294 lPtr->topItem = 0;
296 if (!lPtr->idleID) {
297 WMDeleteIdleHandler(lPtr->idleID);
298 lPtr->idleID = NULL;
300 if (lPtr->selectID) {
301 WMDeleteTimerHandler(lPtr->selectID);
302 lPtr->selectID = NULL;
304 if (lPtr->view->flags.realized) {
305 updateScroller(lPtr);
307 if (selNo > 0) {
308 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
313 void
314 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
316 lPtr->action = action;
317 lPtr->clientData = clientData;
321 void
322 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
324 lPtr->doubleAction = action;
325 lPtr->doubleClientData = clientData;
329 WMArray*
330 WMGetListSelectedItems(WMList *lPtr)
332 return lPtr->selectedItems;
336 WMListItem*
337 WMGetListSelectedItem(WMList *lPtr)
339 return WMGetFromArray(lPtr->selectedItems, 0);
344 WMGetListSelectedItemRow(WMList *lPtr)
346 WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
348 return (item!=NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
353 WMGetListItemHeight(WMList *lPtr)
355 return lPtr->itemHeight;
359 void
360 WMSetListPosition(WMList *lPtr, int row)
362 lPtr->topItem = row;
363 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
364 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
366 if (lPtr->topItem < 0)
367 lPtr->topItem = 0;
369 if (lPtr->view->flags.realized)
370 updateScroller(lPtr);
374 void
375 WMSetListBottomPosition(WMList *lPtr, int row)
377 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
378 lPtr->topItem = row - lPtr->fullFitLines;
379 if (lPtr->topItem < 0)
380 lPtr->topItem = 0;
381 if (lPtr->view->flags.realized)
382 updateScroller(lPtr);
388 WMGetListNumberOfRows(WMList *lPtr)
390 return WMGetArrayItemCount(lPtr->items);
395 WMGetListPosition(WMList *lPtr)
397 return lPtr->topItem;
401 Bool
402 WMListAllowsMultipleSelection(WMList *lPtr)
404 return lPtr->flags.allowMultipleSelection;
408 Bool
409 WMListAllowsEmptySelection(WMList *lPtr)
411 return lPtr->flags.allowEmptySelection;
415 static void
416 scrollByAmount(WMList *lPtr, int amount)
418 int itemCount = WMGetArrayItemCount(lPtr->items);
420 if ((amount < 0 && lPtr->topItem > 0) ||
421 (amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
423 lPtr->topItem += amount;
424 if (lPtr->topItem < 0)
425 lPtr->topItem = 0;
426 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
427 lPtr->topItem = itemCount - lPtr->fullFitLines;
429 updateScroller(lPtr);
434 static void
435 vScrollCallBack(WMWidget *scroller, void *self)
437 WMList *lPtr = (WMList*)self;
438 int height;
439 int oldTopItem = lPtr->topItem;
440 int itemCount = WMGetArrayItemCount(lPtr->items);
442 height = lPtr->view->size.height - 4;
444 switch (WMGetScrollerHitPart((WMScroller*)scroller)) {
445 case WSDecrementLine:
446 scrollByAmount(lPtr, -1);
447 break;
449 case WSIncrementLine:
450 scrollByAmount(lPtr, 1);
451 break;
453 case WSDecrementPage:
454 scrollByAmount(lPtr, -lPtr->fullFitLines+(1-lPtr->flags.dontFitAll)+1);
455 break;
457 case WSIncrementPage:
458 scrollByAmount(lPtr, lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1);
459 break;
461 case WSDecrementWheel:
462 scrollByAmount(lPtr, -lPtr->fullFitLines / 3);
463 break;
465 case WSIncrementWheel:
466 scrollByAmount(lPtr, lPtr->fullFitLines / 3);
467 break;
469 case WSKnob:
470 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
471 (float)(itemCount - lPtr->fullFitLines);
473 if (oldTopItem != lPtr->topItem)
474 paintList(lPtr); /* use updateScroller(lPtr) here? -Dan */
475 break;
477 case WSKnobSlot:
478 case WSNoPart:
479 default:
480 /* do nothing */
481 break;
484 if (lPtr->topItem != oldTopItem)
485 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
489 static void
490 paintItem(List *lPtr, int index)
492 WMView *view = lPtr->view;
493 W_Screen *scr = view->screen;
494 int width, height, x, y;
495 WMListItem *itemPtr;
497 itemPtr = WMGetFromArray(lPtr->items, index);
499 width = lPtr->view->size.width - 2 - 19;
500 height = lPtr->itemHeight;
501 x = 19;
502 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
504 if (lPtr->flags.userDrawn) {
505 WMRect rect;
506 int flags;
508 rect.size.width = width;
509 rect.size.height = height;
510 rect.pos.x = x;
511 rect.pos.y = y;
513 flags = itemPtr->uflags;
514 if (itemPtr->disabled)
515 flags |= WLDSDisabled;
516 if (itemPtr->selected)
517 flags |= WLDSSelected;
518 if (itemPtr->isBranch)
519 flags |= WLDSIsBranch;
521 if (lPtr->draw)
522 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
523 &rect);
524 } else {
525 if (itemPtr->selected) {
526 XFillRectangle(scr->display, view->window, WMColorGC(scr->white),
527 x, y, width, height);
528 } else {
529 XClearArea(scr->display, view->window, x, y, width, height, False);
532 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
533 WALeft, WMColorGC(scr->black), False,
534 itemPtr->text, strlen(itemPtr->text));
537 if ((index-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight >
538 lPtr->view->size.height-2) {
539 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
540 lPtr->view->size.width, lPtr->view->size.height,
541 WRSunken);
547 static void
548 paintList(List *lPtr)
550 W_Screen *scrPtr = lPtr->view->screen;
551 int i, lim;
553 if (!lPtr->view->flags.mapped)
554 return;
556 if (WMGetArrayItemCount(lPtr->items) > 0) {
557 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
558 > WMGetArrayItemCount(lPtr->items)) {
560 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
561 XClearArea(scrPtr->display, lPtr->view->window, 19,
562 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
563 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
564 } else {
565 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
567 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
568 paintItem(lPtr, i);
570 } else {
571 XClearWindow(scrPtr->display, lPtr->view->window);
573 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
574 lPtr->view->size.height, WRSunken);
577 #if 0
578 static void
579 scrollTo(List *lPtr, int newTop)
583 #endif
585 static void
586 updateScroller(void *data)
588 List *lPtr = (List*)data;
590 float knobProportion, floatValue, tmp;
591 int count = WMGetArrayItemCount(lPtr->items);
593 if (lPtr->idleID)
594 WMDeleteIdleHandler(lPtr->idleID);
595 lPtr->idleID = NULL;
597 paintList(lPtr);
599 if (count == 0 || count <= lPtr->fullFitLines)
600 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
601 else {
602 tmp = lPtr->fullFitLines;
603 knobProportion = tmp/(float)count;
605 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
607 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
612 static void
613 scrollForwardSelecting(void *data)
615 List *lPtr = (List*)data;
616 int lastSelected;
618 lastSelected = lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll-1;
620 if (lastSelected >= WMGetArrayItemCount(lPtr->items)-1) {
621 lPtr->selectID = NULL;
622 if (lPtr->flags.dontFitAll)
623 scrollByAmount(lPtr, 1);
624 return;
627 /* selecting NEEDS to be done before scrolling to avoid flickering */
628 if (lPtr->flags.allowMultipleSelection) {
629 WMListItem *item;
630 WMRange range;
632 item = WMGetFromArray(lPtr->selectedItems, 0);
633 range.position = WMGetFirstInArray(lPtr->items, item);
634 if (lastSelected+1 >= range.position) {
635 range.count = lastSelected - range.position + 2;
636 } else {
637 range.count = lastSelected - range.position;
639 WMSetListSelectionToRange(lPtr, range);
640 } else {
641 WMSelectListItem(lPtr, lastSelected+1);
643 scrollByAmount(lPtr, 1);
645 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting,
646 lPtr);
650 static void
651 scrollBackwardSelecting(void *data)
653 List *lPtr = (List*)data;
655 if (lPtr->topItem < 1) {
656 lPtr->selectID = NULL;
657 return;
660 /* selecting NEEDS to be done before scrolling to avoid flickering */
661 if (lPtr->flags.allowMultipleSelection) {
662 WMListItem *item;
663 WMRange range;
665 item = WMGetFromArray(lPtr->selectedItems, 0);
666 range.position = WMGetFirstInArray(lPtr->items, item);
667 if (lPtr->topItem-1 >= range.position) {
668 range.count = lPtr->topItem - range.position;
669 } else {
670 range.count = lPtr->topItem - range.position - 2;
672 WMSetListSelectionToRange(lPtr, range);
673 } else {
674 WMSelectListItem(lPtr, lPtr->topItem-1);
676 scrollByAmount(lPtr, -1);
678 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting,
679 lPtr);
683 static void
684 handleEvents(XEvent *event, void *data)
686 List *lPtr = (List*)data;
688 CHECK_CLASS(data, WC_List);
691 switch (event->type) {
692 case Expose:
693 if (event->xexpose.count!=0)
694 break;
695 paintList(lPtr);
696 break;
698 case DestroyNotify:
699 destroyList(lPtr);
700 break;
706 static int
707 matchTitle(void *item, void *title)
709 return (strcmp(((WMListItem*)item)->text, (char*)title)==0 ? 1 : 0);
714 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
716 return WMFindInArray(lPtr->items, matchTitle, title);
720 void
721 WMSelectListItem(WMList *lPtr, int row)
723 WMListItem *item;
725 if (row >= WMGetArrayItemCount(lPtr->items))
726 return;
728 if (row < 0) {
729 /* row = -1 will deselects all for backward compatibility.
730 * will be removed later. -Dan */
731 WMUnselectAllListItems(lPtr);
732 return;
735 item = WMGetFromArray(lPtr->items, row);
736 if (item->selected)
737 return; /* Return if already selected */
739 if (!lPtr->flags.allowMultipleSelection) {
740 /* unselect previous selected items */
741 unselectAllListItems(lPtr, NULL);
744 /* select item */
745 item->selected = 1;
746 WMAddToArray(lPtr->selectedItems, item);
748 if (lPtr->view->flags.mapped && row>=lPtr->topItem
749 && row<=lPtr->topItem+lPtr->fullFitLines) {
750 paintItem(lPtr, row);
753 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
757 void
758 WMUnselectListItem(WMList *lPtr, int row)
760 WMListItem *item = WMGetFromArray(lPtr->items, row);
762 if (!item || !item->selected)
763 return;
765 if (!lPtr->flags.allowEmptySelection &&
766 WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
767 return;
770 item->selected = 0;
771 WMRemoveFromArray(lPtr->selectedItems, item);
773 if (lPtr->view->flags.mapped && row>=lPtr->topItem
774 && row<=lPtr->topItem+lPtr->fullFitLines) {
775 paintItem(lPtr, row);
778 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
782 void
783 WMSelectListItemsInRange(WMList *lPtr, WMRange range)
785 WMListItem *item;
786 int position = range.position, k = 1, notify = 0;
787 int total = WMGetArrayItemCount(lPtr->items);
789 if (!lPtr->flags.allowMultipleSelection)
790 return;
791 if (range.count==0)
792 return; /* Nothing to select */
794 if (range.count < 0) {
795 range.count = -range.count;
796 k = -1;
799 for (; range.count>0 && position>=0 && position<total; range.count--) {
800 item = WMGetFromArray(lPtr->items, position);
801 if (!item->selected) {
802 item->selected = 1;
803 WMAddToArray(lPtr->selectedItems, item);
804 if (lPtr->view->flags.mapped && position>=lPtr->topItem
805 && position<=lPtr->topItem+lPtr->fullFitLines) {
806 paintItem(lPtr, position);
808 notify = 1;
810 position += k;
813 if (notify) {
814 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
819 void
820 WMSetListSelectionToRange(WMList *lPtr, WMRange range)
822 WMListItem *item;
823 int mark1, mark2, i, k;
824 int position = range.position, notify = 0;
825 int total = WMGetArrayItemCount(lPtr->items);
827 if (!lPtr->flags.allowMultipleSelection)
828 return;
830 if (range.count==0) {
831 WMUnselectAllListItems(lPtr);
832 return;
835 if (range.count < 0) {
836 mark1 = range.position + range.count + 1;
837 mark2 = range.position + 1;
838 range.count = -range.count;
839 k = -1;
840 } else {
841 mark1 = range.position;
842 mark2 = range.position + range.count;
843 k = 1;
845 if (mark1 > total)
846 mark1 = total;
847 if (mark2 < 0)
848 mark2 = 0;
850 WMEmptyArray(lPtr->selectedItems);
852 for (i=0; i<mark1; i++) {
853 item = WMGetFromArray(lPtr->items, i);
854 if (item->selected) {
855 item->selected = 0;
856 if (lPtr->view->flags.mapped && i>=lPtr->topItem
857 && i<=lPtr->topItem+lPtr->fullFitLines) {
858 paintItem(lPtr, i);
860 notify = 1;
863 for (; range.count>0 && position>=0 && position<total; range.count--) {
864 item = WMGetFromArray(lPtr->items, position);
865 if (!item->selected) {
866 item->selected = 1;
867 if (lPtr->view->flags.mapped && position>=lPtr->topItem
868 && position<=lPtr->topItem+lPtr->fullFitLines) {
869 paintItem(lPtr, position);
871 notify = 1;
873 WMAddToArray(lPtr->selectedItems, item);
874 position += k;
876 for (i=mark2; i<total; i++) {
877 item = WMGetFromArray(lPtr->items, i);
878 if (item->selected) {
879 item->selected = 0;
880 if (lPtr->view->flags.mapped && i>=lPtr->topItem
881 && i<=lPtr->topItem+lPtr->fullFitLines) {
882 paintItem(lPtr, i);
884 notify = 1;
888 if (notify) {
889 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
894 void
895 WMSelectAllListItems(WMList *lPtr)
897 int i;
898 WMListItem *item;
900 if (!lPtr->flags.allowMultipleSelection)
901 return;
903 if (WMGetArrayItemCount(lPtr->items) ==
904 WMGetArrayItemCount(lPtr->selectedItems)) {
905 return; /* All items are selected already */
908 WMFreeArray(lPtr->selectedItems);
909 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
911 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
912 item = WMGetFromArray(lPtr->items, i);
913 if (!item->selected) {
914 item->selected = 1;
915 if (lPtr->view->flags.mapped && i>=lPtr->topItem
916 && i<=lPtr->topItem+lPtr->fullFitLines) {
917 paintItem(lPtr, i);
922 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
926 * Be careful from where you call this function! It doesn't honor the
927 * allowEmptySelection flag and doesn't send a notification about selection
928 * change! You need to manage these in the functions from where you call it.
930 * This will unselect all items if exceptThis is NULL, else will keep
931 * exceptThis selected.
932 * Make sure that exceptThis is one of the already selected items if not NULL!
935 static void
936 unselectAllListItems(WMList *lPtr, WMListItem *exceptThis)
938 int i;
939 WMListItem *item;
941 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
942 item = WMGetFromArray(lPtr->items, i);
943 if (item!=exceptThis && item->selected) {
944 item->selected = 0;
945 if (lPtr->view->flags.mapped && i>=lPtr->topItem
946 && i<=lPtr->topItem+lPtr->fullFitLines) {
947 paintItem(lPtr, i);
952 WMEmptyArray(lPtr->selectedItems);
953 if (exceptThis!=NULL) {
954 exceptThis->selected = 1;
955 WMAddToArray(lPtr->selectedItems, exceptThis);
960 void
961 WMUnselectAllListItems(WMList *lPtr)
963 int keep;
964 WMListItem *keepItem;
966 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
968 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
969 return;
971 keepItem = (keep==1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
973 unselectAllListItems(lPtr, keepItem);
975 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
979 static int
980 getItemIndexAt(List *lPtr, int clickY)
982 int index;
984 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
986 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
987 return -1;
989 return index;
993 static void
994 toggleItemSelection(WMList *lPtr, int index)
996 WMListItem *item = WMGetFromArray(lPtr->items, index);
998 if (item && item->selected) {
999 WMUnselectListItem(lPtr, index);
1000 } else {
1001 WMSelectListItem(lPtr, index);
1006 static void
1007 handleActionEvents(XEvent *event, void *data)
1009 List *lPtr = (List*)data;
1010 int tmp, height;
1011 int topItem = lPtr->topItem;
1012 static int lastClicked = -1, prevItem = -1;
1014 CHECK_CLASS(data, WC_List);
1016 switch (event->type) {
1017 case ButtonRelease:
1018 /* Ignore mouse wheel events, they're not "real" button events */
1019 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
1020 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
1021 break;
1024 lPtr->flags.buttonPressed = 0;
1025 if (lPtr->selectID) {
1026 WMDeleteTimerHandler(lPtr->selectID);
1027 lPtr->selectID = NULL;
1029 tmp = getItemIndexAt(lPtr, event->xbutton.y);
1031 if (tmp >= 0) {
1032 if (lPtr->action)
1033 (*lPtr->action)(lPtr, lPtr->clientData);
1036 if (!(event->xbutton.state & ShiftMask))
1037 lastClicked = prevItem = tmp;
1039 break;
1041 case EnterNotify:
1042 if (lPtr->selectID) {
1043 WMDeleteTimerHandler(lPtr->selectID);
1044 lPtr->selectID = NULL;
1046 break;
1048 case LeaveNotify:
1049 height = WMWidgetHeight(lPtr);
1050 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1051 if (event->xcrossing.y >= height) {
1052 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1053 scrollForwardSelecting,
1054 lPtr);
1055 } else if (event->xcrossing.y <= 0) {
1056 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1057 scrollBackwardSelecting,
1058 lPtr);
1061 break;
1063 case ButtonPress:
1064 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
1065 break;
1066 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
1067 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
1068 int amount = 0;
1070 if (event->xbutton.state & ControlMask) {
1071 amount = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
1072 } else if (event->xbutton.state & ShiftMask) {
1073 amount = 1;
1074 } else {
1075 amount = lPtr->fullFitLines / 3;
1076 if (amount == 0)
1077 amount++;
1079 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
1080 amount = -amount;
1082 scrollByAmount(lPtr, amount);
1083 break;
1086 tmp = getItemIndexAt(lPtr, event->xbutton.y);
1087 lPtr->flags.buttonPressed = 1;
1089 if (tmp >= 0) {
1090 if (tmp == lastClicked && WMIsDoubleClick(event)) {
1091 WMSelectListItem(lPtr, tmp);
1092 if (lPtr->doubleAction)
1093 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
1094 } else {
1095 if (!lPtr->flags.allowMultipleSelection) {
1096 if (event->xbutton.state & ControlMask) {
1097 toggleItemSelection(lPtr, tmp);
1098 } else {
1099 WMSelectListItem(lPtr, tmp);
1101 } else {
1102 WMRange range;
1103 WMListItem *lastSel;
1105 if (event->xbutton.state & ControlMask) {
1106 toggleItemSelection(lPtr, tmp);
1107 } else if (event->xbutton.state & ShiftMask) {
1108 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1109 WMSelectListItem(lPtr, tmp);
1110 } else {
1111 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1112 range.position = WMGetFirstInArray(lPtr->items,
1113 lastSel);
1114 if (tmp >= range.position)
1115 range.count = tmp - range.position + 1;
1116 else
1117 range.count = tmp - range.position - 1;
1119 WMSetListSelectionToRange(lPtr, range);
1121 } else {
1122 range.position = tmp;
1123 range.count = 1;
1124 WMSetListSelectionToRange(lPtr, range);
1130 if (!(event->xbutton.state & ShiftMask))
1131 lastClicked = prevItem = tmp;
1133 break;
1135 case MotionNotify:
1136 height = WMWidgetHeight(lPtr);
1137 if (lPtr->selectID && event->xmotion.y>0 && event->xmotion.y<height) {
1138 WMDeleteTimerHandler(lPtr->selectID);
1139 lPtr->selectID = NULL;
1141 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1142 if (event->xmotion.y <= 0) {
1143 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1144 scrollBackwardSelecting,
1145 lPtr);
1146 break;
1147 } else if (event->xmotion.y >= height) {
1148 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1149 scrollForwardSelecting,
1150 lPtr);
1151 break;
1154 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1155 if (tmp>=0 && tmp!=prevItem) {
1156 if (lPtr->flags.allowMultipleSelection) {
1157 WMRange range;
1159 range.position = lastClicked;
1160 if (tmp >= lastClicked)
1161 range.count = tmp - lastClicked + 1;
1162 else
1163 range.count = tmp - lastClicked - 1;
1164 WMSetListSelectionToRange(lPtr, range);
1165 } else {
1166 WMSelectListItem(lPtr, tmp);
1169 prevItem = tmp;
1171 break;
1173 if (lPtr->topItem != topItem)
1174 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1178 static void
1179 updateGeometry(WMList *lPtr)
1181 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1182 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1183 lPtr->flags.dontFitAll = 1;
1184 } else {
1185 lPtr->flags.dontFitAll = 0;
1188 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1189 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1190 if (lPtr->topItem < 0)
1191 lPtr->topItem = 0;
1194 updateScroller(lPtr);
1198 static void
1199 didResizeList(W_ViewDelegate *self, WMView *view)
1201 WMList *lPtr = (WMList*)view->self;
1203 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
1205 updateGeometry(lPtr);
1209 static void
1210 destroyList(List *lPtr)
1212 if (lPtr->idleID)
1213 WMDeleteIdleHandler(lPtr->idleID);
1214 lPtr->idleID = NULL;
1216 if (lPtr->selectID)
1217 WMDeleteTimerHandler(lPtr->selectID);
1218 lPtr->selectID = NULL;
1220 WMFreeArray(lPtr->items);
1222 wfree(lPtr);