Fix periodic focus bug
[wmaker-crm.git] / WINGs / wlist.c
blob911b434dc28eacd749e74ce1e88a840ef56de88d
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 assert(height > 0);
308 lPtr->flags.userItemHeight = 1;
309 lPtr->itemHeight = height;
311 updateDoubleBufferPixmap(lPtr);
313 updateGeometry(lPtr);
317 void
318 WMClearList(WMList *lPtr)
320 int selNo = WMGetArrayItemCount(lPtr->selectedItems);
322 WMEmptyArray(lPtr->selectedItems);
323 WMEmptyArray(lPtr->items);
325 lPtr->topItem = 0;
327 if (!lPtr->idleID) {
328 WMDeleteIdleHandler(lPtr->idleID);
329 lPtr->idleID = NULL;
331 if (lPtr->selectID) {
332 WMDeleteTimerHandler(lPtr->selectID);
333 lPtr->selectID = NULL;
335 if (lPtr->view->flags.realized) {
336 updateScroller(lPtr);
338 if (selNo > 0) {
339 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
344 void
345 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
347 lPtr->action = action;
348 lPtr->clientData = clientData;
352 void
353 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
355 lPtr->doubleAction = action;
356 lPtr->doubleClientData = clientData;
360 WMArray*
361 WMGetListSelectedItems(WMList *lPtr)
363 return lPtr->selectedItems;
367 WMListItem*
368 WMGetListSelectedItem(WMList *lPtr)
370 return WMGetFromArray(lPtr->selectedItems, 0);
375 WMGetListSelectedItemRow(WMList *lPtr)
377 WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
379 return (item!=NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
384 WMGetListItemHeight(WMList *lPtr)
386 return lPtr->itemHeight;
390 void
391 WMSetListPosition(WMList *lPtr, int row)
393 lPtr->topItem = row;
394 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
395 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
397 if (lPtr->topItem < 0)
398 lPtr->topItem = 0;
400 if (lPtr->view->flags.realized)
401 updateScroller(lPtr);
405 void
406 WMSetListBottomPosition(WMList *lPtr, int row)
408 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
409 lPtr->topItem = row - lPtr->fullFitLines;
410 if (lPtr->topItem < 0)
411 lPtr->topItem = 0;
412 if (lPtr->view->flags.realized)
413 updateScroller(lPtr);
419 WMGetListNumberOfRows(WMList *lPtr)
421 return WMGetArrayItemCount(lPtr->items);
426 WMGetListPosition(WMList *lPtr)
428 return lPtr->topItem;
432 Bool
433 WMListAllowsMultipleSelection(WMList *lPtr)
435 return lPtr->flags.allowMultipleSelection;
439 Bool
440 WMListAllowsEmptySelection(WMList *lPtr)
442 return lPtr->flags.allowEmptySelection;
446 static void
447 scrollByAmount(WMList *lPtr, int amount)
449 int itemCount = WMGetArrayItemCount(lPtr->items);
451 if ((amount < 0 && lPtr->topItem > 0) ||
452 (amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
454 lPtr->topItem += amount;
455 if (lPtr->topItem < 0)
456 lPtr->topItem = 0;
457 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
458 lPtr->topItem = itemCount - lPtr->fullFitLines;
460 updateScroller(lPtr);
465 static void
466 vScrollCallBack(WMWidget *scroller, void *self)
468 WMList *lPtr = (WMList*)self;
469 int height;
470 int oldTopItem = lPtr->topItem;
471 int itemCount = WMGetArrayItemCount(lPtr->items);
473 height = lPtr->view->size.height - 4;
475 switch (WMGetScrollerHitPart((WMScroller*)scroller)) {
476 case WSDecrementLine:
477 scrollByAmount(lPtr, -1);
478 break;
480 case WSIncrementLine:
481 scrollByAmount(lPtr, 1);
482 break;
484 case WSDecrementPage:
485 scrollByAmount(lPtr, -lPtr->fullFitLines+(1-lPtr->flags.dontFitAll)+1);
486 break;
488 case WSIncrementPage:
489 scrollByAmount(lPtr, lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1);
490 break;
492 case WSDecrementWheel:
493 scrollByAmount(lPtr, -lPtr->fullFitLines / 3);
494 break;
496 case WSIncrementWheel:
497 scrollByAmount(lPtr, lPtr->fullFitLines / 3);
498 break;
500 case WSKnob:
501 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
502 (float)(itemCount - lPtr->fullFitLines);
504 if (oldTopItem != lPtr->topItem)
505 paintList(lPtr); /* use updateScroller(lPtr) here? -Dan */
506 break;
508 case WSKnobSlot:
509 case WSNoPart:
510 default:
511 /* do nothing */
512 break;
515 if (lPtr->topItem != oldTopItem)
516 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
520 static void
521 paintItem(List *lPtr, int index)
523 WMView *view = lPtr->view;
524 W_Screen *scr = view->screen;
525 Display *display = scr->display;
526 int width, height, x, y, tlen;
527 WMListItem *itemPtr;
528 Drawable d = lPtr->doubleBuffer;
530 itemPtr = WMGetFromArray(lPtr->items, index);
532 width = lPtr->view->size.width - 2 - 19;
533 height = lPtr->itemHeight;
534 x = 19;
535 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
536 tlen = strlen(itemPtr->text);
538 if (lPtr->flags.userDrawn) {
539 WMRect rect;
540 int flags;
543 rect.size.width = width;
544 rect.size.height = height;
545 rect.pos.x = 0;
546 rect.pos.y = 0;
548 flags = itemPtr->uflags;
549 if (itemPtr->disabled)
550 flags |= WLDSDisabled;
551 if (itemPtr->selected)
552 flags |= WLDSSelected;
553 if (itemPtr->isBranch)
554 flags |= WLDSIsBranch;
556 if (lPtr->draw)
557 (*lPtr->draw)(lPtr, index, d, itemPtr->text, flags, &rect);
559 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
560 } else {
561 WMColor *back = (itemPtr->selected ? scr->white : view->backColor);
563 XFillRectangle(display, d, WMColorGC(back), 0, 0, width, height);
565 W_PaintText(view, d, scr->normalFont, 4, 0, width, WALeft, scr->black,
566 False, itemPtr->text, tlen);
567 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
570 if ((index-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight >
571 lPtr->view->size.height-2) {
572 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
573 lPtr->view->size.width, lPtr->view->size.height,
574 WRSunken);
580 static void
581 paintList(List *lPtr)
583 W_Screen *scrPtr = lPtr->view->screen;
584 int i, lim;
586 if (!lPtr->view->flags.mapped)
587 return;
589 if (WMGetArrayItemCount(lPtr->items) > 0) {
590 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
591 > WMGetArrayItemCount(lPtr->items)) {
593 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
594 XClearArea(scrPtr->display, lPtr->view->window, 19,
595 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
596 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
597 } else {
598 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
600 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
601 paintItem(lPtr, i);
603 } else {
604 XClearWindow(scrPtr->display, lPtr->view->window);
606 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
607 lPtr->view->size.height, WRSunken);
610 #if 0
611 static void
612 scrollTo(List *lPtr, int newTop)
616 #endif
618 static void
619 updateScroller(void *data)
621 List *lPtr = (List*)data;
623 float knobProportion, floatValue, tmp;
624 int count = WMGetArrayItemCount(lPtr->items);
626 if (lPtr->idleID)
627 WMDeleteIdleHandler(lPtr->idleID);
628 lPtr->idleID = NULL;
630 paintList(lPtr);
632 if (count == 0 || count <= lPtr->fullFitLines)
633 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
634 else {
635 tmp = lPtr->fullFitLines;
636 knobProportion = tmp/(float)count;
638 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
640 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
645 static void
646 scrollForwardSelecting(void *data)
648 List *lPtr = (List*)data;
649 int lastSelected;
651 lastSelected = lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll-1;
653 if (lastSelected >= WMGetArrayItemCount(lPtr->items)-1) {
654 lPtr->selectID = NULL;
655 if (lPtr->flags.dontFitAll)
656 scrollByAmount(lPtr, 1);
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 (lastSelected+1 >= range.position) {
668 range.count = lastSelected - range.position + 2;
669 } else {
670 range.count = lastSelected - range.position;
672 WMSetListSelectionToRange(lPtr, range);
673 } else {
674 WMSelectListItem(lPtr, lastSelected+1);
676 scrollByAmount(lPtr, 1);
678 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting,
679 lPtr);
683 static void
684 scrollBackwardSelecting(void *data)
686 List *lPtr = (List*)data;
688 if (lPtr->topItem < 1) {
689 lPtr->selectID = NULL;
690 return;
693 /* selecting NEEDS to be done before scrolling to avoid flickering */
694 if (lPtr->flags.allowMultipleSelection) {
695 WMListItem *item;
696 WMRange range;
698 item = WMGetFromArray(lPtr->selectedItems, 0);
699 range.position = WMGetFirstInArray(lPtr->items, item);
700 if (lPtr->topItem-1 >= range.position) {
701 range.count = lPtr->topItem - range.position;
702 } else {
703 range.count = lPtr->topItem - range.position - 2;
705 WMSetListSelectionToRange(lPtr, range);
706 } else {
707 WMSelectListItem(lPtr, lPtr->topItem-1);
709 scrollByAmount(lPtr, -1);
711 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting,
712 lPtr);
716 static void
717 handleEvents(XEvent *event, void *data)
719 List *lPtr = (List*)data;
721 CHECK_CLASS(data, WC_List);
724 switch (event->type) {
725 case Expose:
726 if (event->xexpose.count!=0)
727 break;
728 paintList(lPtr);
729 break;
731 case DestroyNotify:
732 destroyList(lPtr);
733 break;
739 static int
740 matchTitle(void *item, void *title)
742 return (strcmp(((WMListItem*)item)->text, (char*)title)==0 ? 1 : 0);
747 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
749 return WMFindInArray(lPtr->items, matchTitle, title);
753 void
754 WMSelectListItem(WMList *lPtr, int row)
756 WMListItem *item;
758 if (row >= WMGetArrayItemCount(lPtr->items))
759 return;
761 if (row < 0) {
762 /* row = -1 will deselects all for backward compatibility.
763 * will be removed later. -Dan */
764 WMUnselectAllListItems(lPtr);
765 return;
768 item = WMGetFromArray(lPtr->items, row);
769 if (item->selected)
770 return; /* Return if already selected */
772 if (!lPtr->flags.allowMultipleSelection) {
773 /* unselect previous selected items */
774 unselectAllListItems(lPtr, NULL);
777 /* select item */
778 item->selected = 1;
779 WMAddToArray(lPtr->selectedItems, item);
781 if (lPtr->view->flags.mapped && row>=lPtr->topItem
782 && row<=lPtr->topItem+lPtr->fullFitLines) {
783 paintItem(lPtr, row);
786 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
790 void
791 WMUnselectListItem(WMList *lPtr, int row)
793 WMListItem *item = WMGetFromArray(lPtr->items, row);
795 if (!item || !item->selected)
796 return;
798 if (!lPtr->flags.allowEmptySelection &&
799 WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
800 return;
803 item->selected = 0;
804 WMRemoveFromArray(lPtr->selectedItems, item);
806 if (lPtr->view->flags.mapped && row>=lPtr->topItem
807 && row<=lPtr->topItem+lPtr->fullFitLines) {
808 paintItem(lPtr, row);
811 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
815 void
816 WMSelectListItemsInRange(WMList *lPtr, WMRange range)
818 WMListItem *item;
819 int position = range.position, k = 1, notify = 0;
820 int total = WMGetArrayItemCount(lPtr->items);
822 if (!lPtr->flags.allowMultipleSelection)
823 return;
824 if (range.count==0)
825 return; /* Nothing to select */
827 if (range.count < 0) {
828 range.count = -range.count;
829 k = -1;
832 for (; range.count>0 && position>=0 && position<total; range.count--) {
833 item = WMGetFromArray(lPtr->items, position);
834 if (!item->selected) {
835 item->selected = 1;
836 WMAddToArray(lPtr->selectedItems, item);
837 if (lPtr->view->flags.mapped && position>=lPtr->topItem
838 && position<=lPtr->topItem+lPtr->fullFitLines) {
839 paintItem(lPtr, position);
841 notify = 1;
843 position += k;
846 if (notify) {
847 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
852 void
853 WMSetListSelectionToRange(WMList *lPtr, WMRange range)
855 WMListItem *item;
856 int mark1, mark2, i, k;
857 int position = range.position, notify = 0;
858 int total = WMGetArrayItemCount(lPtr->items);
860 if (!lPtr->flags.allowMultipleSelection)
861 return;
863 if (range.count==0) {
864 WMUnselectAllListItems(lPtr);
865 return;
868 if (range.count < 0) {
869 mark1 = range.position + range.count + 1;
870 mark2 = range.position + 1;
871 range.count = -range.count;
872 k = -1;
873 } else {
874 mark1 = range.position;
875 mark2 = range.position + range.count;
876 k = 1;
878 if (mark1 > total)
879 mark1 = total;
880 if (mark2 < 0)
881 mark2 = 0;
883 WMEmptyArray(lPtr->selectedItems);
885 for (i=0; i<mark1; i++) {
886 item = WMGetFromArray(lPtr->items, i);
887 if (item->selected) {
888 item->selected = 0;
889 if (lPtr->view->flags.mapped && i>=lPtr->topItem
890 && i<=lPtr->topItem+lPtr->fullFitLines) {
891 paintItem(lPtr, i);
893 notify = 1;
896 for (; range.count>0 && position>=0 && position<total; range.count--) {
897 item = WMGetFromArray(lPtr->items, position);
898 if (!item->selected) {
899 item->selected = 1;
900 if (lPtr->view->flags.mapped && position>=lPtr->topItem
901 && position<=lPtr->topItem+lPtr->fullFitLines) {
902 paintItem(lPtr, position);
904 notify = 1;
906 WMAddToArray(lPtr->selectedItems, item);
907 position += k;
909 for (i=mark2; i<total; i++) {
910 item = WMGetFromArray(lPtr->items, i);
911 if (item->selected) {
912 item->selected = 0;
913 if (lPtr->view->flags.mapped && i>=lPtr->topItem
914 && i<=lPtr->topItem+lPtr->fullFitLines) {
915 paintItem(lPtr, i);
917 notify = 1;
921 if (notify) {
922 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
927 void
928 WMSelectAllListItems(WMList *lPtr)
930 int i;
931 WMListItem *item;
933 if (!lPtr->flags.allowMultipleSelection)
934 return;
936 if (WMGetArrayItemCount(lPtr->items) ==
937 WMGetArrayItemCount(lPtr->selectedItems)) {
938 return; /* All items are selected already */
941 WMFreeArray(lPtr->selectedItems);
942 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
944 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
945 item = WMGetFromArray(lPtr->items, i);
946 if (!item->selected) {
947 item->selected = 1;
948 if (lPtr->view->flags.mapped && i>=lPtr->topItem
949 && i<=lPtr->topItem+lPtr->fullFitLines) {
950 paintItem(lPtr, i);
955 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
959 * Be careful from where you call this function! It doesn't honor the
960 * allowEmptySelection flag and doesn't send a notification about selection
961 * change! You need to manage these in the functions from where you call it.
963 * This will unselect all items if exceptThis is NULL, else will keep
964 * exceptThis selected.
965 * Make sure that exceptThis is one of the already selected items if not NULL!
968 static void
969 unselectAllListItems(WMList *lPtr, WMListItem *exceptThis)
971 int i;
972 WMListItem *item;
974 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
975 item = WMGetFromArray(lPtr->items, i);
976 if (item!=exceptThis && item->selected) {
977 item->selected = 0;
978 if (lPtr->view->flags.mapped && i>=lPtr->topItem
979 && i<=lPtr->topItem+lPtr->fullFitLines) {
980 paintItem(lPtr, i);
985 WMEmptyArray(lPtr->selectedItems);
986 if (exceptThis!=NULL) {
987 exceptThis->selected = 1;
988 WMAddToArray(lPtr->selectedItems, exceptThis);
993 void
994 WMUnselectAllListItems(WMList *lPtr)
996 int keep;
997 WMListItem *keepItem;
999 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
1001 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
1002 return;
1004 keepItem = (keep==1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
1006 unselectAllListItems(lPtr, keepItem);
1008 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
1012 static int
1013 getItemIndexAt(List *lPtr, int clickY)
1015 int index;
1017 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
1019 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
1020 return -1;
1022 return index;
1026 static void
1027 toggleItemSelection(WMList *lPtr, int index)
1029 WMListItem *item = WMGetFromArray(lPtr->items, index);
1031 if (item && item->selected) {
1032 WMUnselectListItem(lPtr, index);
1033 } else {
1034 WMSelectListItem(lPtr, index);
1039 static void
1040 handleActionEvents(XEvent *event, void *data)
1042 List *lPtr = (List*)data;
1043 int tmp, height;
1044 int topItem = lPtr->topItem;
1045 static int lastClicked = -1, prevItem = -1;
1047 CHECK_CLASS(data, WC_List);
1049 switch (event->type) {
1050 case ButtonRelease:
1051 /* Ignore mouse wheel events, they're not "real" button events */
1052 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
1053 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
1054 break;
1057 lPtr->flags.buttonPressed = 0;
1058 if (lPtr->selectID) {
1059 WMDeleteTimerHandler(lPtr->selectID);
1060 lPtr->selectID = NULL;
1062 tmp = getItemIndexAt(lPtr, event->xbutton.y);
1064 if (tmp >= 0) {
1065 if (lPtr->action)
1066 (*lPtr->action)(lPtr, lPtr->clientData);
1069 if (!(event->xbutton.state & ShiftMask))
1070 lastClicked = prevItem = tmp;
1072 break;
1074 case EnterNotify:
1075 if (lPtr->selectID) {
1076 WMDeleteTimerHandler(lPtr->selectID);
1077 lPtr->selectID = NULL;
1079 break;
1081 case LeaveNotify:
1082 height = WMWidgetHeight(lPtr);
1083 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1084 if (event->xcrossing.y >= height) {
1085 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1086 scrollForwardSelecting,
1087 lPtr);
1088 } else if (event->xcrossing.y <= 0) {
1089 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1090 scrollBackwardSelecting,
1091 lPtr);
1094 break;
1096 case ButtonPress:
1097 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
1098 break;
1099 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
1100 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
1101 int amount = 0;
1103 if (event->xbutton.state & ControlMask) {
1104 amount = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
1105 } else if (event->xbutton.state & ShiftMask) {
1106 amount = 1;
1107 } else {
1108 amount = lPtr->fullFitLines / 3;
1109 if (amount == 0)
1110 amount++;
1112 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
1113 amount = -amount;
1115 scrollByAmount(lPtr, amount);
1116 break;
1119 tmp = getItemIndexAt(lPtr, event->xbutton.y);
1120 lPtr->flags.buttonPressed = 1;
1122 if (tmp >= 0) {
1123 if (tmp == lastClicked && WMIsDoubleClick(event)) {
1124 WMSelectListItem(lPtr, tmp);
1125 if (lPtr->doubleAction)
1126 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
1127 } else {
1128 if (!lPtr->flags.allowMultipleSelection) {
1129 if (event->xbutton.state & ControlMask) {
1130 toggleItemSelection(lPtr, tmp);
1131 } else {
1132 WMSelectListItem(lPtr, tmp);
1134 } else {
1135 WMRange range;
1136 WMListItem *lastSel;
1138 if (event->xbutton.state & ControlMask) {
1139 toggleItemSelection(lPtr, tmp);
1140 } else if (event->xbutton.state & ShiftMask) {
1141 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1142 WMSelectListItem(lPtr, tmp);
1143 } else {
1144 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1145 range.position = WMGetFirstInArray(lPtr->items,
1146 lastSel);
1147 if (tmp >= range.position)
1148 range.count = tmp - range.position + 1;
1149 else
1150 range.count = tmp - range.position - 1;
1152 WMSetListSelectionToRange(lPtr, range);
1154 } else {
1155 range.position = tmp;
1156 range.count = 1;
1157 WMSetListSelectionToRange(lPtr, range);
1163 if (!(event->xbutton.state & ShiftMask))
1164 lastClicked = prevItem = tmp;
1166 break;
1168 case MotionNotify:
1169 height = WMWidgetHeight(lPtr);
1170 if (lPtr->selectID && event->xmotion.y>0 && event->xmotion.y<height) {
1171 WMDeleteTimerHandler(lPtr->selectID);
1172 lPtr->selectID = NULL;
1174 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1175 if (event->xmotion.y <= 0) {
1176 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1177 scrollBackwardSelecting,
1178 lPtr);
1179 break;
1180 } else if (event->xmotion.y >= height) {
1181 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1182 scrollForwardSelecting,
1183 lPtr);
1184 break;
1187 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1188 if (tmp>=0 && tmp!=prevItem) {
1189 if (lPtr->flags.allowMultipleSelection) {
1190 WMRange range;
1192 range.position = lastClicked;
1193 if (tmp >= lastClicked)
1194 range.count = tmp - lastClicked + 1;
1195 else
1196 range.count = tmp - lastClicked - 1;
1197 WMSetListSelectionToRange(lPtr, range);
1198 } else {
1199 WMSelectListItem(lPtr, tmp);
1202 prevItem = tmp;
1204 break;
1206 if (lPtr->topItem != topItem)
1207 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1211 static void
1212 updateGeometry(WMList *lPtr)
1214 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1215 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1216 lPtr->flags.dontFitAll = 1;
1217 } else {
1218 lPtr->flags.dontFitAll = 0;
1221 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1222 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1223 if (lPtr->topItem < 0)
1224 lPtr->topItem = 0;
1227 updateScroller(lPtr);
1231 static void
1232 didResizeList(W_ViewDelegate *self, WMView *view)
1234 WMList *lPtr = (WMList*)view->self;
1236 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
1238 updateDoubleBufferPixmap(lPtr);
1240 updateGeometry(lPtr);
1244 static void
1245 destroyList(List *lPtr)
1247 if (lPtr->idleID)
1248 WMDeleteIdleHandler(lPtr->idleID);
1249 lPtr->idleID = NULL;
1251 if (lPtr->selectID)
1252 WMDeleteTimerHandler(lPtr->selectID);
1253 lPtr->selectID = NULL;
1255 if (lPtr->selectedItems)
1256 WMFreeArray(lPtr->selectedItems);
1258 if (lPtr->items)
1259 WMFreeArray(lPtr->items);
1261 if (lPtr->doubleBuffer)
1262 XFreePixmap(lPtr->view->screen->display, lPtr->doubleBuffer);
1264 WMRemoveNotificationObserver(lPtr);
1266 wfree(lPtr);