Added (forgotten) release for notifications when destroying a WMList.
[wmaker-crm.git] / WINGs / wlist.c
blobb8b09182d8d4208267565715a8e16785efc7cd96
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 int 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 Pixmap doubleBuffer;
38 struct {
39 unsigned int allowMultipleSelection:1;
40 unsigned int allowEmptySelection:1;
41 unsigned int userDrawn:1;
42 unsigned int userItemHeight:1;
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
55 #define SCROLL_DELAY 100
58 static void destroyList(List *lPtr);
59 static void paintList(List *lPtr);
62 static void handleEvents(XEvent *event, void *data);
63 static void handleActionEvents(XEvent *event, void *data);
65 static void updateScroller(void *data);
66 static void scrollForwardSelecting(void *data);
67 static void scrollBackwardSelecting(void *data);
69 static void vScrollCallBack(WMWidget *scroller, void *self);
71 static void toggleItemSelection(WMList *lPtr, int index);
73 static void updateGeometry(WMList *lPtr);
74 static void didResizeList();
76 static void unselectAllListItems(WMList *lPtr, WMListItem *exceptThis);
79 W_ViewDelegate _ListViewDelegate = {
80 NULL,
81 NULL,
82 didResizeList,
83 NULL,
84 NULL
88 static void
89 updateDoubleBufferPixmap(WMList *lPtr)
91 WMView *view = lPtr->view;
92 WMScreen *scr = view->screen;
94 if (!view->flags.realized)
95 return;
97 if (lPtr->doubleBuffer)
98 XFreePixmap(scr->display, lPtr->doubleBuffer);
99 lPtr->doubleBuffer =
100 XCreatePixmap(scr->display, view->window, view->size.width,
101 lPtr->itemHeight, scr->depth);
105 static void
106 realizeObserver(void *self, WMNotification *not)
108 updateDoubleBufferPixmap(self);
112 static void
113 releaseItem(void *data)
115 WMListItem *item = (WMListItem*)data;
117 if (item->text)
118 wfree(item->text);
119 wfree(item);
123 WMList*
124 WMCreateList(WMWidget *parent)
126 List *lPtr;
127 W_Screen *scrPtr = W_VIEW(parent)->screen;
129 lPtr = wmalloc(sizeof(List));
130 memset(lPtr, 0, sizeof(List));
132 lPtr->widgetClass = WC_List;
134 lPtr->view = W_CreateView(W_VIEW(parent));
135 if (!lPtr->view) {
136 wfree(lPtr);
137 return NULL;
139 lPtr->view->self = lPtr;
141 lPtr->view->delegate = &_ListViewDelegate;
143 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
144 |ClientMessageMask, handleEvents, lPtr);
146 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
147 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
148 handleActionEvents, lPtr);
150 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
152 lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
153 lPtr->selectedItems = WMCreateArray(4);
155 /* create the vertical scroller */
156 lPtr->vScroller = WMCreateScroller(lPtr);
157 WMMoveWidget(lPtr->vScroller, 1, 1);
158 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
160 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
162 /* make the scroller map itself when it's realized */
163 WMMapWidget(lPtr->vScroller);
165 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
167 WMAddNotificationObserver(realizeObserver, lPtr,
168 WMViewRealizedNotification, lPtr->view);
170 return lPtr;
174 void
175 WMSetListAllowMultipleSelection(WMList *lPtr, Bool flag)
177 lPtr->flags.allowMultipleSelection = ((flag==0) ? 0 : 1);
181 void
182 WMSetListAllowEmptySelection(WMList *lPtr, Bool flag)
184 lPtr->flags.allowEmptySelection = ((flag==0) ? 0 : 1);
188 static int
189 comparator(const void *a, const void *b)
191 return (strcmp((*(WMListItem**)a)->text, (*(WMListItem**)b)->text));
195 void
196 WMSortListItems(WMList *lPtr)
198 WMSortArray(lPtr->items, comparator);
200 paintList(lPtr);
205 void
206 WMSortListItemsWithComparer(WMList *lPtr, WMCompareDataProc *func)
208 WMSortArray(lPtr->items, func);
210 paintList(lPtr);
215 WMListItem*
216 WMInsertListItem(WMList *lPtr, int row, char *text)
218 WMListItem *item;
220 CHECK_CLASS(lPtr, WC_List);
222 item = wmalloc(sizeof(WMListItem));
223 memset(item, 0, sizeof(WMListItem));
224 item->text = wstrdup(text);
226 row = WMIN(row, WMGetArrayItemCount(lPtr->items));
228 if (row < 0)
229 WMAddToArray(lPtr->items, item);
230 else
231 WMInsertInArray(lPtr->items, row, item);
233 /* update the scroller when idle, so that we don't waste time
234 * updating it when another item is going to be added later */
235 if (!lPtr->idleID) {
236 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
239 return item;
243 void
244 WMRemoveListItem(WMList *lPtr, int row)
246 WMListItem *item;
247 int topItem = lPtr->topItem;
248 int selNotify = 0;
250 CHECK_CLASS(lPtr, WC_List);
252 /*wassertr(row>=0 && row<WMGetArrayItemCount(lPtr->items));*/
253 if (row<0 || row>=WMGetArrayItemCount(lPtr->items))
254 return;
256 item = WMGetFromArray(lPtr->items, row);
257 if (item->selected) {
258 WMRemoveFromArray(lPtr->selectedItems, item);
259 selNotify = 1;
262 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
263 lPtr->topItem--;
264 if (lPtr->topItem < 0)
265 lPtr->topItem = 0;
267 WMDeleteFromArray(lPtr->items, row);
269 if (!lPtr->idleID) {
270 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
272 if (lPtr->topItem != topItem) {
273 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
275 if (selNotify) {
276 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
281 WMListItem*
282 WMGetListItem(WMList *lPtr, int row)
284 return WMGetFromArray(lPtr->items, row);
288 WMArray*
289 WMGetListItems(WMList *lPtr)
291 return lPtr->items;
295 void
296 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
298 lPtr->flags.userDrawn = 1;
299 lPtr->draw = proc;
303 void
304 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
306 W_Screen *scr = lPtr->view->screen;
308 assert(height > 0);
310 lPtr->flags.userItemHeight = 1;
311 lPtr->itemHeight = height;
313 updateDoubleBufferPixmap(lPtr);
315 updateGeometry(lPtr);
319 void
320 WMClearList(WMList *lPtr)
322 int selNo = WMGetArrayItemCount(lPtr->selectedItems);
324 WMEmptyArray(lPtr->selectedItems);
325 WMEmptyArray(lPtr->items);
327 lPtr->topItem = 0;
329 if (!lPtr->idleID) {
330 WMDeleteIdleHandler(lPtr->idleID);
331 lPtr->idleID = NULL;
333 if (lPtr->selectID) {
334 WMDeleteTimerHandler(lPtr->selectID);
335 lPtr->selectID = NULL;
337 if (lPtr->view->flags.realized) {
338 updateScroller(lPtr);
340 if (selNo > 0) {
341 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
346 void
347 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
349 lPtr->action = action;
350 lPtr->clientData = clientData;
354 void
355 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
357 lPtr->doubleAction = action;
358 lPtr->doubleClientData = clientData;
362 WMArray*
363 WMGetListSelectedItems(WMList *lPtr)
365 return lPtr->selectedItems;
369 WMListItem*
370 WMGetListSelectedItem(WMList *lPtr)
372 return WMGetFromArray(lPtr->selectedItems, 0);
377 WMGetListSelectedItemRow(WMList *lPtr)
379 WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
381 return (item!=NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
386 WMGetListItemHeight(WMList *lPtr)
388 return lPtr->itemHeight;
392 void
393 WMSetListPosition(WMList *lPtr, int row)
395 lPtr->topItem = row;
396 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
397 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
399 if (lPtr->topItem < 0)
400 lPtr->topItem = 0;
402 if (lPtr->view->flags.realized)
403 updateScroller(lPtr);
407 void
408 WMSetListBottomPosition(WMList *lPtr, int row)
410 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
411 lPtr->topItem = row - lPtr->fullFitLines;
412 if (lPtr->topItem < 0)
413 lPtr->topItem = 0;
414 if (lPtr->view->flags.realized)
415 updateScroller(lPtr);
421 WMGetListNumberOfRows(WMList *lPtr)
423 return WMGetArrayItemCount(lPtr->items);
428 WMGetListPosition(WMList *lPtr)
430 return lPtr->topItem;
434 Bool
435 WMListAllowsMultipleSelection(WMList *lPtr)
437 return lPtr->flags.allowMultipleSelection;
441 Bool
442 WMListAllowsEmptySelection(WMList *lPtr)
444 return lPtr->flags.allowEmptySelection;
448 static void
449 scrollByAmount(WMList *lPtr, int amount)
451 int itemCount = WMGetArrayItemCount(lPtr->items);
453 if ((amount < 0 && lPtr->topItem > 0) ||
454 (amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
456 lPtr->topItem += amount;
457 if (lPtr->topItem < 0)
458 lPtr->topItem = 0;
459 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
460 lPtr->topItem = itemCount - lPtr->fullFitLines;
462 updateScroller(lPtr);
467 static void
468 vScrollCallBack(WMWidget *scroller, void *self)
470 WMList *lPtr = (WMList*)self;
471 int height;
472 int oldTopItem = lPtr->topItem;
473 int itemCount = WMGetArrayItemCount(lPtr->items);
475 height = lPtr->view->size.height - 4;
477 switch (WMGetScrollerHitPart((WMScroller*)scroller)) {
478 case WSDecrementLine:
479 scrollByAmount(lPtr, -1);
480 break;
482 case WSIncrementLine:
483 scrollByAmount(lPtr, 1);
484 break;
486 case WSDecrementPage:
487 scrollByAmount(lPtr, -lPtr->fullFitLines+(1-lPtr->flags.dontFitAll)+1);
488 break;
490 case WSIncrementPage:
491 scrollByAmount(lPtr, lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1);
492 break;
494 case WSDecrementWheel:
495 scrollByAmount(lPtr, -lPtr->fullFitLines / 3);
496 break;
498 case WSIncrementWheel:
499 scrollByAmount(lPtr, lPtr->fullFitLines / 3);
500 break;
502 case WSKnob:
503 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
504 (float)(itemCount - lPtr->fullFitLines);
506 if (oldTopItem != lPtr->topItem)
507 paintList(lPtr); /* use updateScroller(lPtr) here? -Dan */
508 break;
510 case WSKnobSlot:
511 case WSNoPart:
512 default:
513 /* do nothing */
514 break;
517 if (lPtr->topItem != oldTopItem)
518 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
522 static void
523 paintItem(List *lPtr, int index)
525 WMView *view = lPtr->view;
526 W_Screen *scr = view->screen;
527 Display *display = scr->display;
528 int width, height, x, y, tlen;
529 WMListItem *itemPtr;
530 Drawable d = lPtr->doubleBuffer;
532 itemPtr = WMGetFromArray(lPtr->items, index);
534 width = lPtr->view->size.width - 2 - 19;
535 height = lPtr->itemHeight;
536 x = 19;
537 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
538 tlen = strlen(itemPtr->text);
540 if (lPtr->flags.userDrawn) {
541 WMRect rect;
542 int flags;
545 rect.size.width = width;
546 rect.size.height = height;
547 rect.pos.x = 0;
548 rect.pos.y = 0;
550 flags = itemPtr->uflags;
551 if (itemPtr->disabled)
552 flags |= WLDSDisabled;
553 if (itemPtr->selected)
554 flags |= WLDSSelected;
555 if (itemPtr->isBranch)
556 flags |= WLDSIsBranch;
558 if (lPtr->draw)
559 (*lPtr->draw)(lPtr, index, d, itemPtr->text, flags, &rect);
561 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
562 } else {
563 WMColor *back = (itemPtr->selected ? scr->white : view->backColor);
565 XFillRectangle(display, d, WMColorGC(back), 0, 0, width, height);
567 W_PaintText(view, d, scr->normalFont, 4, 0, width, WALeft, scr->black,
568 False, itemPtr->text, tlen);
569 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
572 if ((index-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight >
573 lPtr->view->size.height-2) {
574 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
575 lPtr->view->size.width, lPtr->view->size.height,
576 WRSunken);
582 static void
583 paintList(List *lPtr)
585 W_Screen *scrPtr = lPtr->view->screen;
586 int i, lim;
588 if (!lPtr->view->flags.mapped)
589 return;
591 if (WMGetArrayItemCount(lPtr->items) > 0) {
592 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
593 > WMGetArrayItemCount(lPtr->items)) {
595 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
596 XClearArea(scrPtr->display, lPtr->view->window, 19,
597 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
598 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
599 } else {
600 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
602 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
603 paintItem(lPtr, i);
605 } else {
606 XClearWindow(scrPtr->display, lPtr->view->window);
608 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
609 lPtr->view->size.height, WRSunken);
612 #if 0
613 static void
614 scrollTo(List *lPtr, int newTop)
618 #endif
620 static void
621 updateScroller(void *data)
623 List *lPtr = (List*)data;
625 float knobProportion, floatValue, tmp;
626 int count = WMGetArrayItemCount(lPtr->items);
628 if (lPtr->idleID)
629 WMDeleteIdleHandler(lPtr->idleID);
630 lPtr->idleID = NULL;
632 paintList(lPtr);
634 if (count == 0 || count <= lPtr->fullFitLines)
635 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
636 else {
637 tmp = lPtr->fullFitLines;
638 knobProportion = tmp/(float)count;
640 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
642 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
647 static void
648 scrollForwardSelecting(void *data)
650 List *lPtr = (List*)data;
651 int lastSelected;
653 lastSelected = lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll-1;
655 if (lastSelected >= WMGetArrayItemCount(lPtr->items)-1) {
656 lPtr->selectID = NULL;
657 if (lPtr->flags.dontFitAll)
658 scrollByAmount(lPtr, 1);
659 return;
662 /* selecting NEEDS to be done before scrolling to avoid flickering */
663 if (lPtr->flags.allowMultipleSelection) {
664 WMListItem *item;
665 WMRange range;
667 item = WMGetFromArray(lPtr->selectedItems, 0);
668 range.position = WMGetFirstInArray(lPtr->items, item);
669 if (lastSelected+1 >= range.position) {
670 range.count = lastSelected - range.position + 2;
671 } else {
672 range.count = lastSelected - range.position;
674 WMSetListSelectionToRange(lPtr, range);
675 } else {
676 WMSelectListItem(lPtr, lastSelected+1);
678 scrollByAmount(lPtr, 1);
680 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting,
681 lPtr);
685 static void
686 scrollBackwardSelecting(void *data)
688 List *lPtr = (List*)data;
690 if (lPtr->topItem < 1) {
691 lPtr->selectID = NULL;
692 return;
695 /* selecting NEEDS to be done before scrolling to avoid flickering */
696 if (lPtr->flags.allowMultipleSelection) {
697 WMListItem *item;
698 WMRange range;
700 item = WMGetFromArray(lPtr->selectedItems, 0);
701 range.position = WMGetFirstInArray(lPtr->items, item);
702 if (lPtr->topItem-1 >= range.position) {
703 range.count = lPtr->topItem - range.position;
704 } else {
705 range.count = lPtr->topItem - range.position - 2;
707 WMSetListSelectionToRange(lPtr, range);
708 } else {
709 WMSelectListItem(lPtr, lPtr->topItem-1);
711 scrollByAmount(lPtr, -1);
713 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting,
714 lPtr);
718 static void
719 handleEvents(XEvent *event, void *data)
721 List *lPtr = (List*)data;
723 CHECK_CLASS(data, WC_List);
726 switch (event->type) {
727 case Expose:
728 if (event->xexpose.count!=0)
729 break;
730 paintList(lPtr);
731 break;
733 case DestroyNotify:
734 destroyList(lPtr);
735 break;
741 static int
742 matchTitle(void *item, void *title)
744 return (strcmp(((WMListItem*)item)->text, (char*)title)==0 ? 1 : 0);
749 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
751 return WMFindInArray(lPtr->items, matchTitle, title);
755 void
756 WMSelectListItem(WMList *lPtr, int row)
758 WMListItem *item;
760 if (row >= WMGetArrayItemCount(lPtr->items))
761 return;
763 if (row < 0) {
764 /* row = -1 will deselects all for backward compatibility.
765 * will be removed later. -Dan */
766 WMUnselectAllListItems(lPtr);
767 return;
770 item = WMGetFromArray(lPtr->items, row);
771 if (item->selected)
772 return; /* Return if already selected */
774 if (!lPtr->flags.allowMultipleSelection) {
775 /* unselect previous selected items */
776 unselectAllListItems(lPtr, NULL);
779 /* select item */
780 item->selected = 1;
781 WMAddToArray(lPtr->selectedItems, item);
783 if (lPtr->view->flags.mapped && row>=lPtr->topItem
784 && row<=lPtr->topItem+lPtr->fullFitLines) {
785 paintItem(lPtr, row);
788 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
792 void
793 WMUnselectListItem(WMList *lPtr, int row)
795 WMListItem *item = WMGetFromArray(lPtr->items, row);
797 if (!item || !item->selected)
798 return;
800 if (!lPtr->flags.allowEmptySelection &&
801 WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
802 return;
805 item->selected = 0;
806 WMRemoveFromArray(lPtr->selectedItems, item);
808 if (lPtr->view->flags.mapped && row>=lPtr->topItem
809 && row<=lPtr->topItem+lPtr->fullFitLines) {
810 paintItem(lPtr, row);
813 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
817 void
818 WMSelectListItemsInRange(WMList *lPtr, WMRange range)
820 WMListItem *item;
821 int position = range.position, k = 1, notify = 0;
822 int total = WMGetArrayItemCount(lPtr->items);
824 if (!lPtr->flags.allowMultipleSelection)
825 return;
826 if (range.count==0)
827 return; /* Nothing to select */
829 if (range.count < 0) {
830 range.count = -range.count;
831 k = -1;
834 for (; range.count>0 && position>=0 && position<total; range.count--) {
835 item = WMGetFromArray(lPtr->items, position);
836 if (!item->selected) {
837 item->selected = 1;
838 WMAddToArray(lPtr->selectedItems, item);
839 if (lPtr->view->flags.mapped && position>=lPtr->topItem
840 && position<=lPtr->topItem+lPtr->fullFitLines) {
841 paintItem(lPtr, position);
843 notify = 1;
845 position += k;
848 if (notify) {
849 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
854 void
855 WMSetListSelectionToRange(WMList *lPtr, WMRange range)
857 WMListItem *item;
858 int mark1, mark2, i, k;
859 int position = range.position, notify = 0;
860 int total = WMGetArrayItemCount(lPtr->items);
862 if (!lPtr->flags.allowMultipleSelection)
863 return;
865 if (range.count==0) {
866 WMUnselectAllListItems(lPtr);
867 return;
870 if (range.count < 0) {
871 mark1 = range.position + range.count + 1;
872 mark2 = range.position + 1;
873 range.count = -range.count;
874 k = -1;
875 } else {
876 mark1 = range.position;
877 mark2 = range.position + range.count;
878 k = 1;
880 if (mark1 > total)
881 mark1 = total;
882 if (mark2 < 0)
883 mark2 = 0;
885 WMEmptyArray(lPtr->selectedItems);
887 for (i=0; i<mark1; i++) {
888 item = WMGetFromArray(lPtr->items, i);
889 if (item->selected) {
890 item->selected = 0;
891 if (lPtr->view->flags.mapped && i>=lPtr->topItem
892 && i<=lPtr->topItem+lPtr->fullFitLines) {
893 paintItem(lPtr, i);
895 notify = 1;
898 for (; range.count>0 && position>=0 && position<total; range.count--) {
899 item = WMGetFromArray(lPtr->items, position);
900 if (!item->selected) {
901 item->selected = 1;
902 if (lPtr->view->flags.mapped && position>=lPtr->topItem
903 && position<=lPtr->topItem+lPtr->fullFitLines) {
904 paintItem(lPtr, position);
906 notify = 1;
908 WMAddToArray(lPtr->selectedItems, item);
909 position += k;
911 for (i=mark2; i<total; i++) {
912 item = WMGetFromArray(lPtr->items, i);
913 if (item->selected) {
914 item->selected = 0;
915 if (lPtr->view->flags.mapped && i>=lPtr->topItem
916 && i<=lPtr->topItem+lPtr->fullFitLines) {
917 paintItem(lPtr, i);
919 notify = 1;
923 if (notify) {
924 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
929 void
930 WMSelectAllListItems(WMList *lPtr)
932 int i;
933 WMListItem *item;
935 if (!lPtr->flags.allowMultipleSelection)
936 return;
938 if (WMGetArrayItemCount(lPtr->items) ==
939 WMGetArrayItemCount(lPtr->selectedItems)) {
940 return; /* All items are selected already */
943 WMFreeArray(lPtr->selectedItems);
944 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
946 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
947 item = WMGetFromArray(lPtr->items, i);
948 if (!item->selected) {
949 item->selected = 1;
950 if (lPtr->view->flags.mapped && i>=lPtr->topItem
951 && i<=lPtr->topItem+lPtr->fullFitLines) {
952 paintItem(lPtr, i);
957 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
961 * Be careful from where you call this function! It doesn't honor the
962 * allowEmptySelection flag and doesn't send a notification about selection
963 * change! You need to manage these in the functions from where you call it.
965 * This will unselect all items if exceptThis is NULL, else will keep
966 * exceptThis selected.
967 * Make sure that exceptThis is one of the already selected items if not NULL!
970 static void
971 unselectAllListItems(WMList *lPtr, WMListItem *exceptThis)
973 int i;
974 WMListItem *item;
976 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
977 item = WMGetFromArray(lPtr->items, i);
978 if (item!=exceptThis && item->selected) {
979 item->selected = 0;
980 if (lPtr->view->flags.mapped && i>=lPtr->topItem
981 && i<=lPtr->topItem+lPtr->fullFitLines) {
982 paintItem(lPtr, i);
987 WMEmptyArray(lPtr->selectedItems);
988 if (exceptThis!=NULL) {
989 exceptThis->selected = 1;
990 WMAddToArray(lPtr->selectedItems, exceptThis);
995 void
996 WMUnselectAllListItems(WMList *lPtr)
998 int keep;
999 WMListItem *keepItem;
1001 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
1003 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
1004 return;
1006 keepItem = (keep==1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
1008 unselectAllListItems(lPtr, keepItem);
1010 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
1014 static int
1015 getItemIndexAt(List *lPtr, int clickY)
1017 int index;
1019 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
1021 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
1022 return -1;
1024 return index;
1028 static void
1029 toggleItemSelection(WMList *lPtr, int index)
1031 WMListItem *item = WMGetFromArray(lPtr->items, index);
1033 if (item && item->selected) {
1034 WMUnselectListItem(lPtr, index);
1035 } else {
1036 WMSelectListItem(lPtr, index);
1041 static void
1042 handleActionEvents(XEvent *event, void *data)
1044 List *lPtr = (List*)data;
1045 int tmp, height;
1046 int topItem = lPtr->topItem;
1047 static int lastClicked = -1, prevItem = -1;
1049 CHECK_CLASS(data, WC_List);
1051 switch (event->type) {
1052 case ButtonRelease:
1053 /* Ignore mouse wheel events, they're not "real" button events */
1054 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
1055 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
1056 break;
1059 lPtr->flags.buttonPressed = 0;
1060 if (lPtr->selectID) {
1061 WMDeleteTimerHandler(lPtr->selectID);
1062 lPtr->selectID = NULL;
1064 tmp = getItemIndexAt(lPtr, event->xbutton.y);
1066 if (tmp >= 0) {
1067 if (lPtr->action)
1068 (*lPtr->action)(lPtr, lPtr->clientData);
1071 if (!(event->xbutton.state & ShiftMask))
1072 lastClicked = prevItem = tmp;
1074 break;
1076 case EnterNotify:
1077 if (lPtr->selectID) {
1078 WMDeleteTimerHandler(lPtr->selectID);
1079 lPtr->selectID = NULL;
1081 break;
1083 case LeaveNotify:
1084 height = WMWidgetHeight(lPtr);
1085 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1086 if (event->xcrossing.y >= height) {
1087 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1088 scrollForwardSelecting,
1089 lPtr);
1090 } else if (event->xcrossing.y <= 0) {
1091 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1092 scrollBackwardSelecting,
1093 lPtr);
1096 break;
1098 case ButtonPress:
1099 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
1100 break;
1101 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
1102 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
1103 int amount = 0;
1105 if (event->xbutton.state & ControlMask) {
1106 amount = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
1107 } else if (event->xbutton.state & ShiftMask) {
1108 amount = 1;
1109 } else {
1110 amount = lPtr->fullFitLines / 3;
1111 if (amount == 0)
1112 amount++;
1114 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
1115 amount = -amount;
1117 scrollByAmount(lPtr, amount);
1118 break;
1121 tmp = getItemIndexAt(lPtr, event->xbutton.y);
1122 lPtr->flags.buttonPressed = 1;
1124 if (tmp >= 0) {
1125 if (tmp == lastClicked && WMIsDoubleClick(event)) {
1126 WMSelectListItem(lPtr, tmp);
1127 if (lPtr->doubleAction)
1128 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
1129 } else {
1130 if (!lPtr->flags.allowMultipleSelection) {
1131 if (event->xbutton.state & ControlMask) {
1132 toggleItemSelection(lPtr, tmp);
1133 } else {
1134 WMSelectListItem(lPtr, tmp);
1136 } else {
1137 WMRange range;
1138 WMListItem *lastSel;
1140 if (event->xbutton.state & ControlMask) {
1141 toggleItemSelection(lPtr, tmp);
1142 } else if (event->xbutton.state & ShiftMask) {
1143 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1144 WMSelectListItem(lPtr, tmp);
1145 } else {
1146 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1147 range.position = WMGetFirstInArray(lPtr->items,
1148 lastSel);
1149 if (tmp >= range.position)
1150 range.count = tmp - range.position + 1;
1151 else
1152 range.count = tmp - range.position - 1;
1154 WMSetListSelectionToRange(lPtr, range);
1156 } else {
1157 range.position = tmp;
1158 range.count = 1;
1159 WMSetListSelectionToRange(lPtr, range);
1165 if (!(event->xbutton.state & ShiftMask))
1166 lastClicked = prevItem = tmp;
1168 break;
1170 case MotionNotify:
1171 height = WMWidgetHeight(lPtr);
1172 if (lPtr->selectID && event->xmotion.y>0 && event->xmotion.y<height) {
1173 WMDeleteTimerHandler(lPtr->selectID);
1174 lPtr->selectID = NULL;
1176 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1177 if (event->xmotion.y <= 0) {
1178 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1179 scrollBackwardSelecting,
1180 lPtr);
1181 break;
1182 } else if (event->xmotion.y >= height) {
1183 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1184 scrollForwardSelecting,
1185 lPtr);
1186 break;
1189 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1190 if (tmp>=0 && tmp!=prevItem) {
1191 if (lPtr->flags.allowMultipleSelection) {
1192 WMRange range;
1194 range.position = lastClicked;
1195 if (tmp >= lastClicked)
1196 range.count = tmp - lastClicked + 1;
1197 else
1198 range.count = tmp - lastClicked - 1;
1199 WMSetListSelectionToRange(lPtr, range);
1200 } else {
1201 WMSelectListItem(lPtr, tmp);
1204 prevItem = tmp;
1206 break;
1208 if (lPtr->topItem != topItem)
1209 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1213 static void
1214 updateGeometry(WMList *lPtr)
1216 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1217 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1218 lPtr->flags.dontFitAll = 1;
1219 } else {
1220 lPtr->flags.dontFitAll = 0;
1223 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1224 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1225 if (lPtr->topItem < 0)
1226 lPtr->topItem = 0;
1229 updateScroller(lPtr);
1233 static void
1234 didResizeList(W_ViewDelegate *self, WMView *view)
1236 WMList *lPtr = (WMList*)view->self;
1237 W_Screen *scr = view->screen;
1239 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
1241 updateDoubleBufferPixmap(lPtr);
1243 updateGeometry(lPtr);
1247 static void
1248 destroyList(List *lPtr)
1250 if (lPtr->idleID)
1251 WMDeleteIdleHandler(lPtr->idleID);
1252 lPtr->idleID = NULL;
1254 if (lPtr->selectID)
1255 WMDeleteTimerHandler(lPtr->selectID);
1256 lPtr->selectID = NULL;
1258 if (lPtr->selectedItems)
1259 WMFreeArray(lPtr->selectedItems);
1261 if (lPtr->items)
1262 WMFreeArray(lPtr->items);
1264 if (lPtr->doubleBuffer)
1265 XFreePixmap(lPtr->view->screen->display, lPtr->doubleBuffer);
1267 WMRemoveNotificationObserver(lPtr);
1269 wfree(lPtr);