Adapt for libpng changes
[wmaker-crm.git] / WINGs / wlist.c
blobb58d68d087c251f6fa321aae9166bdeaa84b7c96
2 #include "WINGsP.h"
4 char *WMListDidScrollNotification = "WMListDidScrollNotification";
5 char *WMListSelectionDidChangeNotification = "WMListSelectionDidChangeNotification";
7 typedef struct W_List {
8 W_Class widgetClass;
9 W_View *view;
11 WMArray *items; /* list of WMListItem */
12 WMArray *selectedItems; /* list of selected WMListItems */
14 short itemHeight;
16 int topItem; /* index of first visible item */
18 short fullFitLines; /* no of lines that fit entirely */
20 void *clientData;
21 WMAction *action;
22 void *doubleClientData;
23 WMAction *doubleAction;
25 WMListDrawProc *draw;
27 WMHandlerID *idleID; /* for updating the scroller after adding elements */
29 WMHandlerID *selectID; /* for selecting items in list while scrolling */
31 WMScroller *vScroller;
33 Pixmap doubleBuffer;
35 struct {
36 unsigned int allowMultipleSelection:1;
37 unsigned int allowEmptySelection:1;
38 unsigned int userDrawn:1;
39 unsigned int userItemHeight:1;
40 unsigned int dontFitAll:1; /* 1 = last item won't be fully visible */
41 unsigned int redrawPending:1;
42 unsigned int buttonPressed:1;
43 unsigned int buttonWasPressed:1;
44 } flags;
45 } List;
47 #define DEFAULT_WIDTH 150
48 #define DEFAULT_HEIGHT 150
50 #define SCROLL_DELAY 100
52 static void destroyList(List * lPtr);
53 static void paintList(List * lPtr);
55 static void handleEvents(XEvent * event, void *data);
56 static void handleActionEvents(XEvent * event, void *data);
58 static void updateScroller(void *data);
59 static void scrollForwardSelecting(void *data);
60 static void scrollBackwardSelecting(void *data);
62 static void vScrollCallBack(WMWidget * scroller, void *self);
64 static void toggleItemSelection(WMList * lPtr, int index);
66 static void updateGeometry(WMList * lPtr);
67 static void didResizeList();
69 static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis);
71 W_ViewDelegate _ListViewDelegate = {
72 NULL,
73 NULL,
74 didResizeList,
75 NULL,
76 NULL
79 static void updateDoubleBufferPixmap(WMList * lPtr)
81 WMView *view = lPtr->view;
82 WMScreen *scr = view->screen;
84 if (!view->flags.realized)
85 return;
87 if (lPtr->doubleBuffer)
88 XFreePixmap(scr->display, lPtr->doubleBuffer);
89 lPtr->doubleBuffer =
90 XCreatePixmap(scr->display, view->window, view->size.width, lPtr->itemHeight, scr->depth);
93 static void realizeObserver(void *self, WMNotification * not)
95 updateDoubleBufferPixmap(self);
98 static void releaseItem(void *data)
100 WMListItem *item = (WMListItem *) data;
102 if (item->text)
103 wfree(item->text);
104 wfree(item);
107 WMList *WMCreateList(WMWidget * parent)
109 List *lPtr;
110 W_Screen *scrPtr = W_VIEW(parent)->screen;
112 lPtr = wmalloc(sizeof(List));
114 lPtr->widgetClass = WC_List;
116 lPtr->view = W_CreateView(W_VIEW(parent));
117 if (!lPtr->view) {
118 wfree(lPtr);
119 return NULL;
121 lPtr->view->self = lPtr;
123 lPtr->view->delegate = &_ListViewDelegate;
125 WMCreateEventHandler(lPtr->view, ExposureMask | StructureNotifyMask
126 | ClientMessageMask, handleEvents, lPtr);
128 WMCreateEventHandler(lPtr->view, ButtonPressMask | ButtonReleaseMask
129 | EnterWindowMask | LeaveWindowMask | ButtonMotionMask, handleActionEvents, lPtr);
131 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
133 lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
134 lPtr->selectedItems = WMCreateArray(4);
136 /* create the vertical scroller */
137 lPtr->vScroller = WMCreateScroller(lPtr);
138 WMMoveWidget(lPtr->vScroller, 1, 1);
139 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
141 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
143 /* make the scroller map itself when it's realized */
144 WMMapWidget(lPtr->vScroller);
146 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
148 WMAddNotificationObserver(realizeObserver, lPtr, WMViewRealizedNotification, lPtr->view);
150 return lPtr;
153 void WMSetListAllowMultipleSelection(WMList * lPtr, Bool flag)
155 lPtr->flags.allowMultipleSelection = ((flag == 0) ? 0 : 1);
158 void WMSetListAllowEmptySelection(WMList * lPtr, Bool flag)
160 lPtr->flags.allowEmptySelection = ((flag == 0) ? 0 : 1);
163 static int comparator(const void *a, const void *b)
165 return (strcmp((*(WMListItem **) a)->text, (*(WMListItem **) b)->text));
168 void WMSortListItems(WMList * lPtr)
170 WMSortArray(lPtr->items, comparator);
172 paintList(lPtr);
175 void WMSortListItemsWithComparer(WMList * lPtr, WMCompareDataProc * func)
177 WMSortArray(lPtr->items, func);
179 paintList(lPtr);
182 WMListItem *WMInsertListItem(WMList * lPtr, int row, char *text)
184 WMListItem *item;
186 CHECK_CLASS(lPtr, WC_List);
188 item = wmalloc(sizeof(WMListItem));
189 item->text = wstrdup(text);
191 row = WMIN(row, WMGetArrayItemCount(lPtr->items));
193 if (row < 0)
194 WMAddToArray(lPtr->items, item);
195 else
196 WMInsertInArray(lPtr->items, row, item);
198 /* update the scroller when idle, so that we don't waste time
199 * updating it when another item is going to be added later */
200 if (!lPtr->idleID) {
201 lPtr->idleID = WMAddIdleHandler((WMCallback *) updateScroller, lPtr);
204 return item;
207 void WMRemoveListItem(WMList * lPtr, int row)
209 WMListItem *item;
210 int topItem = lPtr->topItem;
211 int selNotify = 0;
213 CHECK_CLASS(lPtr, WC_List);
215 /*wassertr(row>=0 && row<WMGetArrayItemCount(lPtr->items)); */
216 if (row < 0 || row >= WMGetArrayItemCount(lPtr->items))
217 return;
219 item = WMGetFromArray(lPtr->items, row);
220 if (item->selected) {
221 WMRemoveFromArray(lPtr->selectedItems, item);
222 selNotify = 1;
225 if (row <= lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll)
226 lPtr->topItem--;
227 if (lPtr->topItem < 0)
228 lPtr->topItem = 0;
230 WMDeleteFromArray(lPtr->items, row);
232 if (!lPtr->idleID) {
233 lPtr->idleID = WMAddIdleHandler((WMCallback *) updateScroller, lPtr);
235 if (lPtr->topItem != topItem) {
236 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
238 if (selNotify) {
239 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
243 WMListItem *WMGetListItem(WMList * lPtr, int row)
245 return WMGetFromArray(lPtr->items, row);
248 WMArray *WMGetListItems(WMList * lPtr)
250 return lPtr->items;
253 void WMSetListUserDrawProc(WMList * lPtr, WMListDrawProc * proc)
255 lPtr->flags.userDrawn = 1;
256 lPtr->draw = proc;
259 void WMSetListUserDrawItemHeight(WMList * lPtr, unsigned short height)
261 assert(height > 0);
263 lPtr->flags.userItemHeight = 1;
264 lPtr->itemHeight = height;
266 updateDoubleBufferPixmap(lPtr);
268 updateGeometry(lPtr);
271 void WMClearList(WMList * lPtr)
273 int selNo = WMGetArrayItemCount(lPtr->selectedItems);
275 WMEmptyArray(lPtr->selectedItems);
276 WMEmptyArray(lPtr->items);
278 lPtr->topItem = 0;
280 if (!lPtr->idleID) {
281 WMDeleteIdleHandler(lPtr->idleID);
282 lPtr->idleID = NULL;
284 if (lPtr->selectID) {
285 WMDeleteTimerHandler(lPtr->selectID);
286 lPtr->selectID = NULL;
288 if (lPtr->view->flags.realized) {
289 updateScroller(lPtr);
291 if (selNo > 0) {
292 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
296 void WMSetListAction(WMList * lPtr, WMAction * action, void *clientData)
298 lPtr->action = action;
299 lPtr->clientData = clientData;
302 void WMSetListDoubleAction(WMList * lPtr, WMAction * action, void *clientData)
304 lPtr->doubleAction = action;
305 lPtr->doubleClientData = clientData;
308 WMArray *WMGetListSelectedItems(WMList * lPtr)
310 return lPtr->selectedItems;
313 WMListItem *WMGetListSelectedItem(WMList * lPtr)
315 return WMGetFromArray(lPtr->selectedItems, 0);
318 int WMGetListSelectedItemRow(WMList * lPtr)
320 WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
322 return (item != NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
325 int WMGetListItemHeight(WMList * lPtr)
327 return lPtr->itemHeight;
330 void WMSetListPosition(WMList * lPtr, int row)
332 lPtr->topItem = row;
333 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
334 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
336 if (lPtr->topItem < 0)
337 lPtr->topItem = 0;
339 if (lPtr->view->flags.realized)
340 updateScroller(lPtr);
343 void WMSetListBottomPosition(WMList * lPtr, int row)
345 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
346 lPtr->topItem = row - lPtr->fullFitLines;
347 if (lPtr->topItem < 0)
348 lPtr->topItem = 0;
349 if (lPtr->view->flags.realized)
350 updateScroller(lPtr);
354 int WMGetListNumberOfRows(WMList * lPtr)
356 return WMGetArrayItemCount(lPtr->items);
359 int WMGetListPosition(WMList * lPtr)
361 return lPtr->topItem;
364 Bool WMListAllowsMultipleSelection(WMList * lPtr)
366 return lPtr->flags.allowMultipleSelection;
369 Bool WMListAllowsEmptySelection(WMList * lPtr)
371 return lPtr->flags.allowEmptySelection;
374 static void scrollByAmount(WMList * lPtr, int amount)
376 int itemCount = WMGetArrayItemCount(lPtr->items);
378 if ((amount < 0 && lPtr->topItem > 0) || (amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
380 lPtr->topItem += amount;
381 if (lPtr->topItem < 0)
382 lPtr->topItem = 0;
383 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
384 lPtr->topItem = itemCount - lPtr->fullFitLines;
386 updateScroller(lPtr);
390 static void vScrollCallBack(WMWidget * scroller, void *self)
392 WMList *lPtr = (WMList *) self;
393 int height;
394 int oldTopItem = lPtr->topItem;
395 int itemCount = WMGetArrayItemCount(lPtr->items);
397 height = lPtr->view->size.height - 4;
399 switch (WMGetScrollerHitPart((WMScroller *) scroller)) {
400 case WSDecrementLine:
401 scrollByAmount(lPtr, -1);
402 break;
404 case WSIncrementLine:
405 scrollByAmount(lPtr, 1);
406 break;
408 case WSDecrementPage:
409 scrollByAmount(lPtr, -lPtr->fullFitLines + (1 - lPtr->flags.dontFitAll) + 1);
410 break;
412 case WSIncrementPage:
413 scrollByAmount(lPtr, lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1);
414 break;
416 case WSDecrementWheel:
417 scrollByAmount(lPtr, -lPtr->fullFitLines / 3);
418 break;
420 case WSIncrementWheel:
421 scrollByAmount(lPtr, lPtr->fullFitLines / 3);
422 break;
424 case WSKnob:
425 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) * (float)(itemCount - lPtr->fullFitLines);
427 if (oldTopItem != lPtr->topItem)
428 paintList(lPtr); /* use updateScroller(lPtr) here? -Dan */
429 break;
431 case WSKnobSlot:
432 case WSNoPart:
433 default:
434 /* do nothing */
435 break;
438 if (lPtr->topItem != oldTopItem)
439 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
442 static void paintItem(List * lPtr, int index)
444 WMView *view = lPtr->view;
445 W_Screen *scr = view->screen;
446 Display *display = scr->display;
447 int width, height, x, y, tlen;
448 WMListItem *itemPtr;
449 Drawable d = lPtr->doubleBuffer;
451 itemPtr = WMGetFromArray(lPtr->items, index);
453 width = lPtr->view->size.width - 2 - 19;
454 height = lPtr->itemHeight;
455 x = 19;
456 y = 2 + (index - lPtr->topItem) * lPtr->itemHeight + 1;
457 tlen = strlen(itemPtr->text);
459 if (lPtr->flags.userDrawn) {
460 WMRect rect;
461 int flags;
463 rect.size.width = width;
464 rect.size.height = height;
465 rect.pos.x = 0;
466 rect.pos.y = 0;
468 flags = itemPtr->uflags;
469 if (itemPtr->disabled)
470 flags |= WLDSDisabled;
471 if (itemPtr->selected)
472 flags |= WLDSSelected;
473 if (itemPtr->isBranch)
474 flags |= WLDSIsBranch;
476 if (lPtr->draw)
477 (*lPtr->draw) (lPtr, index, d, itemPtr->text, flags, &rect);
479 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
480 } else {
481 WMColor *back = (itemPtr->selected ? scr->white : view->backColor);
483 XFillRectangle(display, d, WMColorGC(back), 0, 0, width, height);
485 W_PaintText(view, d, scr->normalFont, 4, 0, width, WALeft, scr->black, False, itemPtr->text, tlen);
486 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
489 if ((index - lPtr->topItem + lPtr->fullFitLines) * lPtr->itemHeight > lPtr->view->size.height - 2) {
490 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
491 lPtr->view->size.width, lPtr->view->size.height, WRSunken);
495 static void paintList(List * lPtr)
497 W_Screen *scrPtr = lPtr->view->screen;
498 int i, lim;
500 if (!lPtr->view->flags.mapped)
501 return;
503 if (WMGetArrayItemCount(lPtr->items) > 0) {
504 if (lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll > WMGetArrayItemCount(lPtr->items)) {
506 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
507 XClearArea(scrPtr->display, lPtr->view->window, 19,
508 2 + lim * lPtr->itemHeight, lPtr->view->size.width - 21,
509 lPtr->view->size.height - lim * lPtr->itemHeight - 3, False);
510 } else {
511 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
513 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
514 paintItem(lPtr, i);
516 } else {
517 XClearWindow(scrPtr->display, lPtr->view->window);
519 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width, lPtr->view->size.height, WRSunken);
522 #if 0
523 static void scrollTo(List * lPtr, int newTop)
527 #endif
529 static void updateScroller(void *data)
531 List *lPtr = (List *) data;
533 float knobProportion, floatValue, tmp;
534 int count = WMGetArrayItemCount(lPtr->items);
536 if (lPtr->idleID)
537 WMDeleteIdleHandler(lPtr->idleID);
538 lPtr->idleID = NULL;
540 paintList(lPtr);
542 if (count == 0 || count <= lPtr->fullFitLines)
543 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
544 else {
545 tmp = lPtr->fullFitLines;
546 knobProportion = tmp / (float)count;
548 floatValue = (float)lPtr->topItem / (float)(count - lPtr->fullFitLines);
550 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
554 static void scrollForwardSelecting(void *data)
556 List *lPtr = (List *) data;
557 int lastSelected;
559 lastSelected = lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll - 1;
561 if (lastSelected >= WMGetArrayItemCount(lPtr->items) - 1) {
562 lPtr->selectID = NULL;
563 if (lPtr->flags.dontFitAll)
564 scrollByAmount(lPtr, 1);
565 return;
568 /* selecting NEEDS to be done before scrolling to avoid flickering */
569 if (lPtr->flags.allowMultipleSelection) {
570 WMListItem *item;
571 WMRange range;
573 item = WMGetFromArray(lPtr->selectedItems, 0);
574 range.position = WMGetFirstInArray(lPtr->items, item);
575 if (lastSelected + 1 >= range.position) {
576 range.count = lastSelected - range.position + 2;
577 } else {
578 range.count = lastSelected - range.position;
580 WMSetListSelectionToRange(lPtr, range);
581 } else {
582 WMSelectListItem(lPtr, lastSelected + 1);
584 scrollByAmount(lPtr, 1);
586 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
589 static void scrollBackwardSelecting(void *data)
591 List *lPtr = (List *) data;
593 if (lPtr->topItem < 1) {
594 lPtr->selectID = NULL;
595 return;
598 /* selecting NEEDS to be done before scrolling to avoid flickering */
599 if (lPtr->flags.allowMultipleSelection) {
600 WMListItem *item;
601 WMRange range;
603 item = WMGetFromArray(lPtr->selectedItems, 0);
604 range.position = WMGetFirstInArray(lPtr->items, item);
605 if (lPtr->topItem - 1 >= range.position) {
606 range.count = lPtr->topItem - range.position;
607 } else {
608 range.count = lPtr->topItem - range.position - 2;
610 WMSetListSelectionToRange(lPtr, range);
611 } else {
612 WMSelectListItem(lPtr, lPtr->topItem - 1);
614 scrollByAmount(lPtr, -1);
616 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
619 static void handleEvents(XEvent * event, void *data)
621 List *lPtr = (List *) data;
623 CHECK_CLASS(data, WC_List);
625 switch (event->type) {
626 case Expose:
627 if (event->xexpose.count != 0)
628 break;
629 paintList(lPtr);
630 break;
632 case DestroyNotify:
633 destroyList(lPtr);
634 break;
639 static int matchTitle(const void *item, const void *title)
641 return (strcmp(((WMListItem *) item)->text, (char *)title) == 0 ? 1 : 0);
644 int WMFindRowOfListItemWithTitle(WMList * lPtr, char *title)
646 return WMFindInArray(lPtr->items, matchTitle, title);
649 void WMSelectListItem(WMList * lPtr, int row)
651 WMListItem *item;
653 if (row >= WMGetArrayItemCount(lPtr->items))
654 return;
656 if (row < 0) {
657 /* row = -1 will deselects all for backward compatibility.
658 * will be removed later. -Dan */
659 WMUnselectAllListItems(lPtr);
660 return;
663 item = WMGetFromArray(lPtr->items, row);
664 if (item->selected)
665 return; /* Return if already selected */
667 if (!lPtr->flags.allowMultipleSelection) {
668 /* unselect previous selected items */
669 unselectAllListItems(lPtr, NULL);
672 /* select item */
673 item->selected = 1;
674 WMAddToArray(lPtr->selectedItems, item);
676 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
677 paintItem(lPtr, row);
680 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
683 void WMUnselectListItem(WMList * lPtr, int row)
685 WMListItem *item = WMGetFromArray(lPtr->items, row);
687 if (!item || !item->selected)
688 return;
690 if (!lPtr->flags.allowEmptySelection && WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
691 return;
694 item->selected = 0;
695 WMRemoveFromArray(lPtr->selectedItems, item);
697 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
698 paintItem(lPtr, row);
701 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
704 void WMSelectListItemsInRange(WMList * lPtr, WMRange range)
706 WMListItem *item;
707 int position = range.position, k = 1, notify = 0;
708 int total = WMGetArrayItemCount(lPtr->items);
710 if (!lPtr->flags.allowMultipleSelection)
711 return;
712 if (range.count == 0)
713 return; /* Nothing to select */
715 if (range.count < 0) {
716 range.count = -range.count;
717 k = -1;
720 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
721 item = WMGetFromArray(lPtr->items, position);
722 if (!item->selected) {
723 item->selected = 1;
724 WMAddToArray(lPtr->selectedItems, item);
725 if (lPtr->view->flags.mapped && position >= lPtr->topItem
726 && position <= lPtr->topItem + lPtr->fullFitLines) {
727 paintItem(lPtr, position);
729 notify = 1;
731 position += k;
734 if (notify) {
735 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
739 void WMSetListSelectionToRange(WMList * lPtr, WMRange range)
741 WMListItem *item;
742 int mark1, mark2, i, k;
743 int position = range.position, notify = 0;
744 int total = WMGetArrayItemCount(lPtr->items);
746 if (!lPtr->flags.allowMultipleSelection)
747 return;
749 if (range.count == 0) {
750 WMUnselectAllListItems(lPtr);
751 return;
754 if (range.count < 0) {
755 mark1 = range.position + range.count + 1;
756 mark2 = range.position + 1;
757 range.count = -range.count;
758 k = -1;
759 } else {
760 mark1 = range.position;
761 mark2 = range.position + range.count;
762 k = 1;
764 if (mark1 > total)
765 mark1 = total;
766 if (mark2 < 0)
767 mark2 = 0;
769 WMEmptyArray(lPtr->selectedItems);
771 for (i = 0; i < mark1; i++) {
772 item = WMGetFromArray(lPtr->items, i);
773 if (item->selected) {
774 item->selected = 0;
775 if (lPtr->view->flags.mapped && i >= lPtr->topItem
776 && i <= lPtr->topItem + lPtr->fullFitLines) {
777 paintItem(lPtr, i);
779 notify = 1;
782 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
783 item = WMGetFromArray(lPtr->items, position);
784 if (!item->selected) {
785 item->selected = 1;
786 if (lPtr->view->flags.mapped && position >= lPtr->topItem
787 && position <= lPtr->topItem + lPtr->fullFitLines) {
788 paintItem(lPtr, position);
790 notify = 1;
792 WMAddToArray(lPtr->selectedItems, item);
793 position += k;
795 for (i = mark2; i < total; i++) {
796 item = WMGetFromArray(lPtr->items, i);
797 if (item->selected) {
798 item->selected = 0;
799 if (lPtr->view->flags.mapped && i >= lPtr->topItem
800 && i <= lPtr->topItem + lPtr->fullFitLines) {
801 paintItem(lPtr, i);
803 notify = 1;
807 if (notify) {
808 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
812 void WMSelectAllListItems(WMList * lPtr)
814 int i;
815 WMListItem *item;
817 if (!lPtr->flags.allowMultipleSelection)
818 return;
820 if (WMGetArrayItemCount(lPtr->items) == WMGetArrayItemCount(lPtr->selectedItems)) {
821 return; /* All items are selected already */
824 WMFreeArray(lPtr->selectedItems);
825 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
827 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
828 item = WMGetFromArray(lPtr->items, i);
829 if (!item->selected) {
830 item->selected = 1;
831 if (lPtr->view->flags.mapped && i >= lPtr->topItem
832 && i <= lPtr->topItem + lPtr->fullFitLines) {
833 paintItem(lPtr, i);
838 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
842 * Be careful from where you call this function! It doesn't honor the
843 * allowEmptySelection flag and doesn't send a notification about selection
844 * change! You need to manage these in the functions from where you call it.
846 * This will unselect all items if exceptThis is NULL, else will keep
847 * exceptThis selected.
848 * Make sure that exceptThis is one of the already selected items if not NULL!
851 static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis)
853 int i;
854 WMListItem *item;
856 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
857 item = WMGetFromArray(lPtr->items, i);
858 if (item != exceptThis && item->selected) {
859 item->selected = 0;
860 if (lPtr->view->flags.mapped && i >= lPtr->topItem
861 && i <= lPtr->topItem + lPtr->fullFitLines) {
862 paintItem(lPtr, i);
867 WMEmptyArray(lPtr->selectedItems);
868 if (exceptThis != NULL) {
869 exceptThis->selected = 1;
870 WMAddToArray(lPtr->selectedItems, exceptThis);
874 void WMUnselectAllListItems(WMList * lPtr)
876 int keep;
877 WMListItem *keepItem;
879 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
881 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
882 return;
884 keepItem = (keep == 1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
886 unselectAllListItems(lPtr, keepItem);
888 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
891 static int getItemIndexAt(List * lPtr, int clickY)
893 int index;
895 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
897 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
898 return -1;
900 return index;
903 static void toggleItemSelection(WMList * lPtr, int index)
905 WMListItem *item = WMGetFromArray(lPtr->items, index);
907 if (item && item->selected) {
908 WMUnselectListItem(lPtr, index);
909 } else {
910 WMSelectListItem(lPtr, index);
914 static void handleActionEvents(XEvent * event, void *data)
916 List *lPtr = (List *) data;
917 int tmp, height;
918 int topItem = lPtr->topItem;
919 static int lastClicked = -1, prevItem = -1;
921 CHECK_CLASS(data, WC_List);
923 switch (event->type) {
924 case ButtonRelease:
925 /* Ignore mouse wheel events, they're not "real" button events */
926 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
927 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
928 break;
931 lPtr->flags.buttonPressed = 0;
932 if (lPtr->selectID) {
933 WMDeleteTimerHandler(lPtr->selectID);
934 lPtr->selectID = NULL;
936 tmp = getItemIndexAt(lPtr, event->xbutton.y);
938 if (tmp >= 0) {
939 if (lPtr->action)
940 (*lPtr->action) (lPtr, lPtr->clientData);
943 if (!(event->xbutton.state & ShiftMask))
944 lastClicked = prevItem = tmp;
946 break;
948 case EnterNotify:
949 if (lPtr->selectID) {
950 WMDeleteTimerHandler(lPtr->selectID);
951 lPtr->selectID = NULL;
953 break;
955 case LeaveNotify:
956 height = WMWidgetHeight(lPtr);
957 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
958 if (event->xcrossing.y >= height) {
959 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
960 } else if (event->xcrossing.y <= 0) {
961 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
964 break;
966 case ButtonPress:
967 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
968 break;
969 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
970 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
971 int amount = 0;
973 if (event->xbutton.state & ControlMask) {
974 amount = lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1;
975 } else if (event->xbutton.state & ShiftMask) {
976 amount = 1;
977 } else {
978 amount = lPtr->fullFitLines / 3;
979 if (amount == 0)
980 amount++;
982 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
983 amount = -amount;
985 scrollByAmount(lPtr, amount);
986 break;
989 tmp = getItemIndexAt(lPtr, event->xbutton.y);
990 lPtr->flags.buttonPressed = 1;
992 if (tmp >= 0) {
993 if (tmp == lastClicked && WMIsDoubleClick(event)) {
994 WMSelectListItem(lPtr, tmp);
995 if (lPtr->doubleAction)
996 (*lPtr->doubleAction) (lPtr, lPtr->doubleClientData);
997 } else {
998 if (!lPtr->flags.allowMultipleSelection) {
999 if (event->xbutton.state & ControlMask) {
1000 toggleItemSelection(lPtr, tmp);
1001 } else {
1002 WMSelectListItem(lPtr, tmp);
1004 } else {
1005 WMRange range;
1006 WMListItem *lastSel;
1008 if (event->xbutton.state & ControlMask) {
1009 toggleItemSelection(lPtr, tmp);
1010 } else if (event->xbutton.state & ShiftMask) {
1011 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1012 WMSelectListItem(lPtr, tmp);
1013 } else {
1014 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1015 range.position = WMGetFirstInArray(lPtr->items, lastSel);
1016 if (tmp >= range.position)
1017 range.count = tmp - range.position + 1;
1018 else
1019 range.count = tmp - range.position - 1;
1021 WMSetListSelectionToRange(lPtr, range);
1023 } else {
1024 range.position = tmp;
1025 range.count = 1;
1026 WMSetListSelectionToRange(lPtr, range);
1032 if (!(event->xbutton.state & ShiftMask))
1033 lastClicked = prevItem = tmp;
1035 break;
1037 case MotionNotify:
1038 height = WMWidgetHeight(lPtr);
1039 if (lPtr->selectID && event->xmotion.y > 0 && event->xmotion.y < height) {
1040 WMDeleteTimerHandler(lPtr->selectID);
1041 lPtr->selectID = NULL;
1043 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1044 if (event->xmotion.y <= 0) {
1045 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
1046 break;
1047 } else if (event->xmotion.y >= height) {
1048 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
1049 break;
1052 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1053 if (tmp >= 0 && tmp != prevItem) {
1054 if (lPtr->flags.allowMultipleSelection) {
1055 WMRange range;
1057 range.position = lastClicked;
1058 if (tmp >= lastClicked)
1059 range.count = tmp - lastClicked + 1;
1060 else
1061 range.count = tmp - lastClicked - 1;
1062 WMSetListSelectionToRange(lPtr, range);
1063 } else {
1064 WMSelectListItem(lPtr, tmp);
1067 prevItem = tmp;
1069 break;
1071 if (lPtr->topItem != topItem)
1072 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1075 static void updateGeometry(WMList * lPtr)
1077 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1078 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1079 lPtr->flags.dontFitAll = 1;
1080 } else {
1081 lPtr->flags.dontFitAll = 0;
1084 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1085 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1086 if (lPtr->topItem < 0)
1087 lPtr->topItem = 0;
1090 updateScroller(lPtr);
1093 static void didResizeList(W_ViewDelegate * self, WMView * view)
1095 WMList *lPtr = (WMList *) view->self;
1097 WMResizeWidget(lPtr->vScroller, 1, view->size.height - 2);
1099 updateDoubleBufferPixmap(lPtr);
1101 updateGeometry(lPtr);
1104 static void destroyList(List * lPtr)
1106 if (lPtr->idleID)
1107 WMDeleteIdleHandler(lPtr->idleID);
1108 lPtr->idleID = NULL;
1110 if (lPtr->selectID)
1111 WMDeleteTimerHandler(lPtr->selectID);
1112 lPtr->selectID = NULL;
1114 if (lPtr->selectedItems)
1115 WMFreeArray(lPtr->selectedItems);
1117 if (lPtr->items)
1118 WMFreeArray(lPtr->items);
1120 if (lPtr->doubleBuffer)
1121 XFreePixmap(lPtr->view->screen->display, lPtr->doubleBuffer);
1123 WMRemoveNotificationObserver(lPtr);
1125 wfree(lPtr);