- Added double buffering when drawing a WMFrame title with an AA font to avoid
[wmaker-crm.git] / WINGs / wlist.c
blob8b250ffbb793c62d26ef7abd5cb87378f8ed1a6a
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 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==0) ? 0 : 1);
152 void
153 WMSetListAllowEmptySelection(WMList *lPtr, Bool flag)
155 lPtr->flags.allowEmptySelection = ((flag==0) ? 0 : 1);
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 Display *display = scr->display;
495 int width, height, x, y, tlen;
496 WMListItem *itemPtr;
498 itemPtr = WMGetFromArray(lPtr->items, index);
500 width = lPtr->view->size.width - 2 - 19;
501 height = lPtr->itemHeight;
502 x = 19;
503 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
504 tlen = strlen(itemPtr->text);
506 if (lPtr->flags.userDrawn) {
507 WMRect rect;
508 Drawable d = view->window;
509 int flags;
512 rect.size.width = width;
513 rect.size.height = height;
514 rect.pos.x = x;
515 rect.pos.y = y;
517 flags = itemPtr->uflags;
518 if (itemPtr->disabled)
519 flags |= WLDSDisabled;
520 if (itemPtr->selected)
521 flags |= WLDSSelected;
522 if (itemPtr->isBranch)
523 flags |= WLDSIsBranch;
525 #ifdef DOUBLE_BUFFER_no
526 d = XCreatePixmap(display, view->window, view->size.width,
527 view->size.height, scr->depth);
528 #endif
530 if (lPtr->draw)
531 (*lPtr->draw)(lPtr, index, d, itemPtr->text, flags, &rect);
533 #ifdef DOUBLE_BUFFER_no
534 XCopyArea(display, d, view->window, scr->copyGC, x, y, width, height, x, y);
535 XFreePixmap(display, d);
536 #endif
537 } else {
538 #ifdef DOUBLE_BUFFER
539 WMColor *back = (itemPtr->selected ? scr->white : view->backColor);
540 Drawable d;
542 d = XCreatePixmap(display, view->window, width, height, scr->depth);
543 XFillRectangle(display, d, WMColorGC(back), 0, 0, width, height);
545 W_PaintText(view, d, scr->normalFont, 4, 0, width, WALeft, scr->black,
546 False, itemPtr->text, tlen);
547 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
548 XFreePixmap(display, d);
549 #else
550 if (itemPtr->selected) {
551 XFillRectangle(display, view->window, WMColorGC(scr->white),
552 x, y, width, height);
553 } else {
554 XClearArea(display, view->window, x, y, width, height, False);
557 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
558 WALeft, scr->black, False, itemPtr->text, tlen);
559 #endif
562 if ((index-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight >
563 lPtr->view->size.height-2) {
564 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
565 lPtr->view->size.width, lPtr->view->size.height,
566 WRSunken);
572 static void
573 paintList(List *lPtr)
575 W_Screen *scrPtr = lPtr->view->screen;
576 int i, lim;
578 if (!lPtr->view->flags.mapped)
579 return;
581 if (WMGetArrayItemCount(lPtr->items) > 0) {
582 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
583 > WMGetArrayItemCount(lPtr->items)) {
585 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
586 XClearArea(scrPtr->display, lPtr->view->window, 19,
587 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
588 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
589 } else {
590 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
592 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
593 paintItem(lPtr, i);
595 } else {
596 XClearWindow(scrPtr->display, lPtr->view->window);
598 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
599 lPtr->view->size.height, WRSunken);
602 #if 0
603 static void
604 scrollTo(List *lPtr, int newTop)
608 #endif
610 static void
611 updateScroller(void *data)
613 List *lPtr = (List*)data;
615 float knobProportion, floatValue, tmp;
616 int count = WMGetArrayItemCount(lPtr->items);
618 if (lPtr->idleID)
619 WMDeleteIdleHandler(lPtr->idleID);
620 lPtr->idleID = NULL;
622 paintList(lPtr);
624 if (count == 0 || count <= lPtr->fullFitLines)
625 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
626 else {
627 tmp = lPtr->fullFitLines;
628 knobProportion = tmp/(float)count;
630 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
632 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
637 static void
638 scrollForwardSelecting(void *data)
640 List *lPtr = (List*)data;
641 int lastSelected;
643 lastSelected = lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll-1;
645 if (lastSelected >= WMGetArrayItemCount(lPtr->items)-1) {
646 lPtr->selectID = NULL;
647 if (lPtr->flags.dontFitAll)
648 scrollByAmount(lPtr, 1);
649 return;
652 /* selecting NEEDS to be done before scrolling to avoid flickering */
653 if (lPtr->flags.allowMultipleSelection) {
654 WMListItem *item;
655 WMRange range;
657 item = WMGetFromArray(lPtr->selectedItems, 0);
658 range.position = WMGetFirstInArray(lPtr->items, item);
659 if (lastSelected+1 >= range.position) {
660 range.count = lastSelected - range.position + 2;
661 } else {
662 range.count = lastSelected - range.position;
664 WMSetListSelectionToRange(lPtr, range);
665 } else {
666 WMSelectListItem(lPtr, lastSelected+1);
668 scrollByAmount(lPtr, 1);
670 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting,
671 lPtr);
675 static void
676 scrollBackwardSelecting(void *data)
678 List *lPtr = (List*)data;
680 if (lPtr->topItem < 1) {
681 lPtr->selectID = NULL;
682 return;
685 /* selecting NEEDS to be done before scrolling to avoid flickering */
686 if (lPtr->flags.allowMultipleSelection) {
687 WMListItem *item;
688 WMRange range;
690 item = WMGetFromArray(lPtr->selectedItems, 0);
691 range.position = WMGetFirstInArray(lPtr->items, item);
692 if (lPtr->topItem-1 >= range.position) {
693 range.count = lPtr->topItem - range.position;
694 } else {
695 range.count = lPtr->topItem - range.position - 2;
697 WMSetListSelectionToRange(lPtr, range);
698 } else {
699 WMSelectListItem(lPtr, lPtr->topItem-1);
701 scrollByAmount(lPtr, -1);
703 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting,
704 lPtr);
708 static void
709 handleEvents(XEvent *event, void *data)
711 List *lPtr = (List*)data;
713 CHECK_CLASS(data, WC_List);
716 switch (event->type) {
717 case Expose:
718 if (event->xexpose.count!=0)
719 break;
720 paintList(lPtr);
721 break;
723 case DestroyNotify:
724 destroyList(lPtr);
725 break;
731 static int
732 matchTitle(void *item, void *title)
734 return (strcmp(((WMListItem*)item)->text, (char*)title)==0 ? 1 : 0);
739 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
741 return WMFindInArray(lPtr->items, matchTitle, title);
745 void
746 WMSelectListItem(WMList *lPtr, int row)
748 WMListItem *item;
750 if (row >= WMGetArrayItemCount(lPtr->items))
751 return;
753 if (row < 0) {
754 /* row = -1 will deselects all for backward compatibility.
755 * will be removed later. -Dan */
756 WMUnselectAllListItems(lPtr);
757 return;
760 item = WMGetFromArray(lPtr->items, row);
761 if (item->selected)
762 return; /* Return if already selected */
764 if (!lPtr->flags.allowMultipleSelection) {
765 /* unselect previous selected items */
766 unselectAllListItems(lPtr, NULL);
769 /* select item */
770 item->selected = 1;
771 WMAddToArray(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 WMUnselectListItem(WMList *lPtr, int row)
785 WMListItem *item = WMGetFromArray(lPtr->items, row);
787 if (!item || !item->selected)
788 return;
790 if (!lPtr->flags.allowEmptySelection &&
791 WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
792 return;
795 item->selected = 0;
796 WMRemoveFromArray(lPtr->selectedItems, item);
798 if (lPtr->view->flags.mapped && row>=lPtr->topItem
799 && row<=lPtr->topItem+lPtr->fullFitLines) {
800 paintItem(lPtr, row);
803 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
807 void
808 WMSelectListItemsInRange(WMList *lPtr, WMRange range)
810 WMListItem *item;
811 int position = range.position, k = 1, notify = 0;
812 int total = WMGetArrayItemCount(lPtr->items);
814 if (!lPtr->flags.allowMultipleSelection)
815 return;
816 if (range.count==0)
817 return; /* Nothing to select */
819 if (range.count < 0) {
820 range.count = -range.count;
821 k = -1;
824 for (; range.count>0 && position>=0 && position<total; range.count--) {
825 item = WMGetFromArray(lPtr->items, position);
826 if (!item->selected) {
827 item->selected = 1;
828 WMAddToArray(lPtr->selectedItems, item);
829 if (lPtr->view->flags.mapped && position>=lPtr->topItem
830 && position<=lPtr->topItem+lPtr->fullFitLines) {
831 paintItem(lPtr, position);
833 notify = 1;
835 position += k;
838 if (notify) {
839 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
844 void
845 WMSetListSelectionToRange(WMList *lPtr, WMRange range)
847 WMListItem *item;
848 int mark1, mark2, i, k;
849 int position = range.position, notify = 0;
850 int total = WMGetArrayItemCount(lPtr->items);
852 if (!lPtr->flags.allowMultipleSelection)
853 return;
855 if (range.count==0) {
856 WMUnselectAllListItems(lPtr);
857 return;
860 if (range.count < 0) {
861 mark1 = range.position + range.count + 1;
862 mark2 = range.position + 1;
863 range.count = -range.count;
864 k = -1;
865 } else {
866 mark1 = range.position;
867 mark2 = range.position + range.count;
868 k = 1;
870 if (mark1 > total)
871 mark1 = total;
872 if (mark2 < 0)
873 mark2 = 0;
875 WMEmptyArray(lPtr->selectedItems);
877 for (i=0; i<mark1; i++) {
878 item = WMGetFromArray(lPtr->items, i);
879 if (item->selected) {
880 item->selected = 0;
881 if (lPtr->view->flags.mapped && i>=lPtr->topItem
882 && i<=lPtr->topItem+lPtr->fullFitLines) {
883 paintItem(lPtr, i);
885 notify = 1;
888 for (; range.count>0 && position>=0 && position<total; range.count--) {
889 item = WMGetFromArray(lPtr->items, position);
890 if (!item->selected) {
891 item->selected = 1;
892 if (lPtr->view->flags.mapped && position>=lPtr->topItem
893 && position<=lPtr->topItem+lPtr->fullFitLines) {
894 paintItem(lPtr, position);
896 notify = 1;
898 WMAddToArray(lPtr->selectedItems, item);
899 position += k;
901 for (i=mark2; i<total; i++) {
902 item = WMGetFromArray(lPtr->items, i);
903 if (item->selected) {
904 item->selected = 0;
905 if (lPtr->view->flags.mapped && i>=lPtr->topItem
906 && i<=lPtr->topItem+lPtr->fullFitLines) {
907 paintItem(lPtr, i);
909 notify = 1;
913 if (notify) {
914 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
919 void
920 WMSelectAllListItems(WMList *lPtr)
922 int i;
923 WMListItem *item;
925 if (!lPtr->flags.allowMultipleSelection)
926 return;
928 if (WMGetArrayItemCount(lPtr->items) ==
929 WMGetArrayItemCount(lPtr->selectedItems)) {
930 return; /* All items are selected already */
933 WMFreeArray(lPtr->selectedItems);
934 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
936 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
937 item = WMGetFromArray(lPtr->items, i);
938 if (!item->selected) {
939 item->selected = 1;
940 if (lPtr->view->flags.mapped && i>=lPtr->topItem
941 && i<=lPtr->topItem+lPtr->fullFitLines) {
942 paintItem(lPtr, i);
947 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
951 * Be careful from where you call this function! It doesn't honor the
952 * allowEmptySelection flag and doesn't send a notification about selection
953 * change! You need to manage these in the functions from where you call it.
955 * This will unselect all items if exceptThis is NULL, else will keep
956 * exceptThis selected.
957 * Make sure that exceptThis is one of the already selected items if not NULL!
960 static void
961 unselectAllListItems(WMList *lPtr, WMListItem *exceptThis)
963 int i;
964 WMListItem *item;
966 for (i=0; i<WMGetArrayItemCount(lPtr->items); i++) {
967 item = WMGetFromArray(lPtr->items, i);
968 if (item!=exceptThis && item->selected) {
969 item->selected = 0;
970 if (lPtr->view->flags.mapped && i>=lPtr->topItem
971 && i<=lPtr->topItem+lPtr->fullFitLines) {
972 paintItem(lPtr, i);
977 WMEmptyArray(lPtr->selectedItems);
978 if (exceptThis!=NULL) {
979 exceptThis->selected = 1;
980 WMAddToArray(lPtr->selectedItems, exceptThis);
985 void
986 WMUnselectAllListItems(WMList *lPtr)
988 int keep;
989 WMListItem *keepItem;
991 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
993 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
994 return;
996 keepItem = (keep==1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
998 unselectAllListItems(lPtr, keepItem);
1000 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
1004 static int
1005 getItemIndexAt(List *lPtr, int clickY)
1007 int index;
1009 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
1011 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
1012 return -1;
1014 return index;
1018 static void
1019 toggleItemSelection(WMList *lPtr, int index)
1021 WMListItem *item = WMGetFromArray(lPtr->items, index);
1023 if (item && item->selected) {
1024 WMUnselectListItem(lPtr, index);
1025 } else {
1026 WMSelectListItem(lPtr, index);
1031 static void
1032 handleActionEvents(XEvent *event, void *data)
1034 List *lPtr = (List*)data;
1035 int tmp, height;
1036 int topItem = lPtr->topItem;
1037 static int lastClicked = -1, prevItem = -1;
1039 CHECK_CLASS(data, WC_List);
1041 switch (event->type) {
1042 case ButtonRelease:
1043 /* Ignore mouse wheel events, they're not "real" button events */
1044 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
1045 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
1046 break;
1049 lPtr->flags.buttonPressed = 0;
1050 if (lPtr->selectID) {
1051 WMDeleteTimerHandler(lPtr->selectID);
1052 lPtr->selectID = NULL;
1054 tmp = getItemIndexAt(lPtr, event->xbutton.y);
1056 if (tmp >= 0) {
1057 if (lPtr->action)
1058 (*lPtr->action)(lPtr, lPtr->clientData);
1061 if (!(event->xbutton.state & ShiftMask))
1062 lastClicked = prevItem = tmp;
1064 break;
1066 case EnterNotify:
1067 if (lPtr->selectID) {
1068 WMDeleteTimerHandler(lPtr->selectID);
1069 lPtr->selectID = NULL;
1071 break;
1073 case LeaveNotify:
1074 height = WMWidgetHeight(lPtr);
1075 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1076 if (event->xcrossing.y >= height) {
1077 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1078 scrollForwardSelecting,
1079 lPtr);
1080 } else if (event->xcrossing.y <= 0) {
1081 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1082 scrollBackwardSelecting,
1083 lPtr);
1086 break;
1088 case ButtonPress:
1089 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
1090 break;
1091 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
1092 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
1093 int amount = 0;
1095 if (event->xbutton.state & ControlMask) {
1096 amount = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
1097 } else if (event->xbutton.state & ShiftMask) {
1098 amount = 1;
1099 } else {
1100 amount = lPtr->fullFitLines / 3;
1101 if (amount == 0)
1102 amount++;
1104 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
1105 amount = -amount;
1107 scrollByAmount(lPtr, amount);
1108 break;
1111 tmp = getItemIndexAt(lPtr, event->xbutton.y);
1112 lPtr->flags.buttonPressed = 1;
1114 if (tmp >= 0) {
1115 if (tmp == lastClicked && WMIsDoubleClick(event)) {
1116 WMSelectListItem(lPtr, tmp);
1117 if (lPtr->doubleAction)
1118 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
1119 } else {
1120 if (!lPtr->flags.allowMultipleSelection) {
1121 if (event->xbutton.state & ControlMask) {
1122 toggleItemSelection(lPtr, tmp);
1123 } else {
1124 WMSelectListItem(lPtr, tmp);
1126 } else {
1127 WMRange range;
1128 WMListItem *lastSel;
1130 if (event->xbutton.state & ControlMask) {
1131 toggleItemSelection(lPtr, tmp);
1132 } else if (event->xbutton.state & ShiftMask) {
1133 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1134 WMSelectListItem(lPtr, tmp);
1135 } else {
1136 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1137 range.position = WMGetFirstInArray(lPtr->items,
1138 lastSel);
1139 if (tmp >= range.position)
1140 range.count = tmp - range.position + 1;
1141 else
1142 range.count = tmp - range.position - 1;
1144 WMSetListSelectionToRange(lPtr, range);
1146 } else {
1147 range.position = tmp;
1148 range.count = 1;
1149 WMSetListSelectionToRange(lPtr, range);
1155 if (!(event->xbutton.state & ShiftMask))
1156 lastClicked = prevItem = tmp;
1158 break;
1160 case MotionNotify:
1161 height = WMWidgetHeight(lPtr);
1162 if (lPtr->selectID && event->xmotion.y>0 && event->xmotion.y<height) {
1163 WMDeleteTimerHandler(lPtr->selectID);
1164 lPtr->selectID = NULL;
1166 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1167 if (event->xmotion.y <= 0) {
1168 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1169 scrollBackwardSelecting,
1170 lPtr);
1171 break;
1172 } else if (event->xmotion.y >= height) {
1173 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY,
1174 scrollForwardSelecting,
1175 lPtr);
1176 break;
1179 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1180 if (tmp>=0 && tmp!=prevItem) {
1181 if (lPtr->flags.allowMultipleSelection) {
1182 WMRange range;
1184 range.position = lastClicked;
1185 if (tmp >= lastClicked)
1186 range.count = tmp - lastClicked + 1;
1187 else
1188 range.count = tmp - lastClicked - 1;
1189 WMSetListSelectionToRange(lPtr, range);
1190 } else {
1191 WMSelectListItem(lPtr, tmp);
1194 prevItem = tmp;
1196 break;
1198 if (lPtr->topItem != topItem)
1199 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1203 static void
1204 updateGeometry(WMList *lPtr)
1206 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1207 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1208 lPtr->flags.dontFitAll = 1;
1209 } else {
1210 lPtr->flags.dontFitAll = 0;
1213 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1214 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1215 if (lPtr->topItem < 0)
1216 lPtr->topItem = 0;
1219 updateScroller(lPtr);
1223 static void
1224 didResizeList(W_ViewDelegate *self, WMView *view)
1226 WMList *lPtr = (WMList*)view->self;
1228 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
1230 updateGeometry(lPtr);
1234 static void
1235 destroyList(List *lPtr)
1237 if (lPtr->idleID)
1238 WMDeleteIdleHandler(lPtr->idleID);
1239 lPtr->idleID = NULL;
1241 if (lPtr->selectID)
1242 WMDeleteTimerHandler(lPtr->selectID);
1243 lPtr->selectID = NULL;
1245 if (lPtr->selectedItems)
1246 WMFreeArray(lPtr->selectedItems);
1248 if (lPtr->items)
1249 WMFreeArray(lPtr->items);
1251 wfree(lPtr);