Icon creation in only one function
[wmaker-crm.git] / WINGs / wlist.c
blobe8557544dd6b85b08af96fb916684445532bcc0b
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 oldTopItem = lPtr->topItem;
394 int itemCount = WMGetArrayItemCount(lPtr->items);
396 switch (WMGetScrollerHitPart((WMScroller *) scroller)) {
397 case WSDecrementLine:
398 scrollByAmount(lPtr, -1);
399 break;
401 case WSIncrementLine:
402 scrollByAmount(lPtr, 1);
403 break;
405 case WSDecrementPage:
406 scrollByAmount(lPtr, -lPtr->fullFitLines + (1 - lPtr->flags.dontFitAll) + 1);
407 break;
409 case WSIncrementPage:
410 scrollByAmount(lPtr, lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1);
411 break;
413 case WSDecrementWheel:
414 scrollByAmount(lPtr, -lPtr->fullFitLines / 3);
415 break;
417 case WSIncrementWheel:
418 scrollByAmount(lPtr, lPtr->fullFitLines / 3);
419 break;
421 case WSKnob:
422 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) * (float)(itemCount - lPtr->fullFitLines);
424 if (oldTopItem != lPtr->topItem)
425 paintList(lPtr); /* use updateScroller(lPtr) here? -Dan */
426 break;
428 case WSKnobSlot:
429 case WSNoPart:
430 default:
431 /* do nothing */
432 break;
435 if (lPtr->topItem != oldTopItem)
436 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
439 static void paintItem(List * lPtr, int index)
441 WMView *view = lPtr->view;
442 W_Screen *scr = view->screen;
443 Display *display = scr->display;
444 int width, height, x, y, tlen;
445 WMListItem *itemPtr;
446 Drawable d = lPtr->doubleBuffer;
448 itemPtr = WMGetFromArray(lPtr->items, index);
450 width = lPtr->view->size.width - 2 - 19;
451 height = lPtr->itemHeight;
452 x = 19;
453 y = 2 + (index - lPtr->topItem) * lPtr->itemHeight + 1;
454 tlen = strlen(itemPtr->text);
456 if (lPtr->flags.userDrawn) {
457 WMRect rect;
458 int flags;
460 rect.size.width = width;
461 rect.size.height = height;
462 rect.pos.x = 0;
463 rect.pos.y = 0;
465 flags = itemPtr->uflags;
466 if (itemPtr->disabled)
467 flags |= WLDSDisabled;
468 if (itemPtr->selected)
469 flags |= WLDSSelected;
470 if (itemPtr->isBranch)
471 flags |= WLDSIsBranch;
473 if (lPtr->draw)
474 (*lPtr->draw) (lPtr, index, d, itemPtr->text, flags, &rect);
476 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
477 } else {
478 WMColor *back = (itemPtr->selected ? scr->white : view->backColor);
480 XFillRectangle(display, d, WMColorGC(back), 0, 0, width, height);
482 W_PaintText(view, d, scr->normalFont, 4, 0, width, WALeft, scr->black, False, itemPtr->text, tlen);
483 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
486 if ((index - lPtr->topItem + lPtr->fullFitLines) * lPtr->itemHeight > lPtr->view->size.height - 2) {
487 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
488 lPtr->view->size.width, lPtr->view->size.height, WRSunken);
492 static void paintList(List * lPtr)
494 W_Screen *scrPtr = lPtr->view->screen;
495 int i, lim;
497 if (!lPtr->view->flags.mapped)
498 return;
500 if (WMGetArrayItemCount(lPtr->items) > 0) {
501 if (lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll > WMGetArrayItemCount(lPtr->items)) {
503 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
504 XClearArea(scrPtr->display, lPtr->view->window, 19,
505 2 + lim * lPtr->itemHeight, lPtr->view->size.width - 21,
506 lPtr->view->size.height - lim * lPtr->itemHeight - 3, False);
507 } else {
508 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
510 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
511 paintItem(lPtr, i);
513 } else {
514 XClearWindow(scrPtr->display, lPtr->view->window);
516 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width, lPtr->view->size.height, WRSunken);
519 #if 0
520 static void scrollTo(List * lPtr, int newTop)
524 #endif
526 static void updateScroller(void *data)
528 List *lPtr = (List *) data;
530 float knobProportion, floatValue, tmp;
531 int count = WMGetArrayItemCount(lPtr->items);
533 if (lPtr->idleID)
534 WMDeleteIdleHandler(lPtr->idleID);
535 lPtr->idleID = NULL;
537 paintList(lPtr);
539 if (count == 0 || count <= lPtr->fullFitLines)
540 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
541 else {
542 tmp = lPtr->fullFitLines;
543 knobProportion = tmp / (float)count;
545 floatValue = (float)lPtr->topItem / (float)(count - lPtr->fullFitLines);
547 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
551 static void scrollForwardSelecting(void *data)
553 List *lPtr = (List *) data;
554 int lastSelected;
556 lastSelected = lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll - 1;
558 if (lastSelected >= WMGetArrayItemCount(lPtr->items) - 1) {
559 lPtr->selectID = NULL;
560 if (lPtr->flags.dontFitAll)
561 scrollByAmount(lPtr, 1);
562 return;
565 /* selecting NEEDS to be done before scrolling to avoid flickering */
566 if (lPtr->flags.allowMultipleSelection) {
567 WMListItem *item;
568 WMRange range;
570 item = WMGetFromArray(lPtr->selectedItems, 0);
571 range.position = WMGetFirstInArray(lPtr->items, item);
572 if (lastSelected + 1 >= range.position) {
573 range.count = lastSelected - range.position + 2;
574 } else {
575 range.count = lastSelected - range.position;
577 WMSetListSelectionToRange(lPtr, range);
578 } else {
579 WMSelectListItem(lPtr, lastSelected + 1);
581 scrollByAmount(lPtr, 1);
583 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
586 static void scrollBackwardSelecting(void *data)
588 List *lPtr = (List *) data;
590 if (lPtr->topItem < 1) {
591 lPtr->selectID = NULL;
592 return;
595 /* selecting NEEDS to be done before scrolling to avoid flickering */
596 if (lPtr->flags.allowMultipleSelection) {
597 WMListItem *item;
598 WMRange range;
600 item = WMGetFromArray(lPtr->selectedItems, 0);
601 range.position = WMGetFirstInArray(lPtr->items, item);
602 if (lPtr->topItem - 1 >= range.position) {
603 range.count = lPtr->topItem - range.position;
604 } else {
605 range.count = lPtr->topItem - range.position - 2;
607 WMSetListSelectionToRange(lPtr, range);
608 } else {
609 WMSelectListItem(lPtr, lPtr->topItem - 1);
611 scrollByAmount(lPtr, -1);
613 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
616 static void handleEvents(XEvent * event, void *data)
618 List *lPtr = (List *) data;
620 CHECK_CLASS(data, WC_List);
622 switch (event->type) {
623 case Expose:
624 if (event->xexpose.count != 0)
625 break;
626 paintList(lPtr);
627 break;
629 case DestroyNotify:
630 destroyList(lPtr);
631 break;
636 static int matchTitle(const void *item, const void *title)
638 return (strcmp(((WMListItem *) item)->text, (char *)title) == 0 ? 1 : 0);
641 int WMFindRowOfListItemWithTitle(WMList * lPtr, char *title)
643 return WMFindInArray(lPtr->items, matchTitle, title);
646 void WMSelectListItem(WMList * lPtr, int row)
648 WMListItem *item;
650 if (row >= WMGetArrayItemCount(lPtr->items))
651 return;
653 if (row < 0) {
654 /* row = -1 will deselects all for backward compatibility.
655 * will be removed later. -Dan */
656 WMUnselectAllListItems(lPtr);
657 return;
660 item = WMGetFromArray(lPtr->items, row);
661 if (item->selected)
662 return; /* Return if already selected */
664 if (!lPtr->flags.allowMultipleSelection) {
665 /* unselect previous selected items */
666 unselectAllListItems(lPtr, NULL);
669 /* select item */
670 item->selected = 1;
671 WMAddToArray(lPtr->selectedItems, item);
673 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
674 paintItem(lPtr, row);
677 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
680 void WMUnselectListItem(WMList * lPtr, int row)
682 WMListItem *item = WMGetFromArray(lPtr->items, row);
684 if (!item || !item->selected)
685 return;
687 if (!lPtr->flags.allowEmptySelection && WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
688 return;
691 item->selected = 0;
692 WMRemoveFromArray(lPtr->selectedItems, item);
694 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
695 paintItem(lPtr, row);
698 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
701 void WMSelectListItemsInRange(WMList * lPtr, WMRange range)
703 WMListItem *item;
704 int position = range.position, k = 1, notify = 0;
705 int total = WMGetArrayItemCount(lPtr->items);
707 if (!lPtr->flags.allowMultipleSelection)
708 return;
709 if (range.count == 0)
710 return; /* Nothing to select */
712 if (range.count < 0) {
713 range.count = -range.count;
714 k = -1;
717 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
718 item = WMGetFromArray(lPtr->items, position);
719 if (!item->selected) {
720 item->selected = 1;
721 WMAddToArray(lPtr->selectedItems, item);
722 if (lPtr->view->flags.mapped && position >= lPtr->topItem
723 && position <= lPtr->topItem + lPtr->fullFitLines) {
724 paintItem(lPtr, position);
726 notify = 1;
728 position += k;
731 if (notify) {
732 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
736 void WMSetListSelectionToRange(WMList * lPtr, WMRange range)
738 WMListItem *item;
739 int mark1, mark2, i, k;
740 int position = range.position, notify = 0;
741 int total = WMGetArrayItemCount(lPtr->items);
743 if (!lPtr->flags.allowMultipleSelection)
744 return;
746 if (range.count == 0) {
747 WMUnselectAllListItems(lPtr);
748 return;
751 if (range.count < 0) {
752 mark1 = range.position + range.count + 1;
753 mark2 = range.position + 1;
754 range.count = -range.count;
755 k = -1;
756 } else {
757 mark1 = range.position;
758 mark2 = range.position + range.count;
759 k = 1;
761 if (mark1 > total)
762 mark1 = total;
763 if (mark2 < 0)
764 mark2 = 0;
766 WMEmptyArray(lPtr->selectedItems);
768 for (i = 0; i < mark1; i++) {
769 item = WMGetFromArray(lPtr->items, i);
770 if (item->selected) {
771 item->selected = 0;
772 if (lPtr->view->flags.mapped && i >= lPtr->topItem
773 && i <= lPtr->topItem + lPtr->fullFitLines) {
774 paintItem(lPtr, i);
776 notify = 1;
779 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
780 item = WMGetFromArray(lPtr->items, position);
781 if (!item->selected) {
782 item->selected = 1;
783 if (lPtr->view->flags.mapped && position >= lPtr->topItem
784 && position <= lPtr->topItem + lPtr->fullFitLines) {
785 paintItem(lPtr, position);
787 notify = 1;
789 WMAddToArray(lPtr->selectedItems, item);
790 position += k;
792 for (i = mark2; i < total; i++) {
793 item = WMGetFromArray(lPtr->items, i);
794 if (item->selected) {
795 item->selected = 0;
796 if (lPtr->view->flags.mapped && i >= lPtr->topItem
797 && i <= lPtr->topItem + lPtr->fullFitLines) {
798 paintItem(lPtr, i);
800 notify = 1;
804 if (notify) {
805 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
809 void WMSelectAllListItems(WMList * lPtr)
811 int i;
812 WMListItem *item;
814 if (!lPtr->flags.allowMultipleSelection)
815 return;
817 if (WMGetArrayItemCount(lPtr->items) == WMGetArrayItemCount(lPtr->selectedItems)) {
818 return; /* All items are selected already */
821 WMFreeArray(lPtr->selectedItems);
822 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
824 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
825 item = WMGetFromArray(lPtr->items, i);
826 if (!item->selected) {
827 item->selected = 1;
828 if (lPtr->view->flags.mapped && i >= lPtr->topItem
829 && i <= lPtr->topItem + lPtr->fullFitLines) {
830 paintItem(lPtr, i);
835 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
839 * Be careful from where you call this function! It doesn't honor the
840 * allowEmptySelection flag and doesn't send a notification about selection
841 * change! You need to manage these in the functions from where you call it.
843 * This will unselect all items if exceptThis is NULL, else will keep
844 * exceptThis selected.
845 * Make sure that exceptThis is one of the already selected items if not NULL!
848 static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis)
850 int i;
851 WMListItem *item;
853 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
854 item = WMGetFromArray(lPtr->items, i);
855 if (item != exceptThis && item->selected) {
856 item->selected = 0;
857 if (lPtr->view->flags.mapped && i >= lPtr->topItem
858 && i <= lPtr->topItem + lPtr->fullFitLines) {
859 paintItem(lPtr, i);
864 WMEmptyArray(lPtr->selectedItems);
865 if (exceptThis != NULL) {
866 exceptThis->selected = 1;
867 WMAddToArray(lPtr->selectedItems, exceptThis);
871 void WMUnselectAllListItems(WMList * lPtr)
873 int keep;
874 WMListItem *keepItem;
876 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
878 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
879 return;
881 keepItem = (keep == 1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
883 unselectAllListItems(lPtr, keepItem);
885 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
888 static int getItemIndexAt(List * lPtr, int clickY)
890 int index;
892 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
894 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
895 return -1;
897 return index;
900 static void toggleItemSelection(WMList * lPtr, int index)
902 WMListItem *item = WMGetFromArray(lPtr->items, index);
904 if (item && item->selected) {
905 WMUnselectListItem(lPtr, index);
906 } else {
907 WMSelectListItem(lPtr, index);
911 static void handleActionEvents(XEvent * event, void *data)
913 List *lPtr = (List *) data;
914 int tmp, height;
915 int topItem = lPtr->topItem;
916 static int lastClicked = -1, prevItem = -1;
918 CHECK_CLASS(data, WC_List);
920 switch (event->type) {
921 case ButtonRelease:
922 /* Ignore mouse wheel events, they're not "real" button events */
923 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
924 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
925 break;
928 lPtr->flags.buttonPressed = 0;
929 if (lPtr->selectID) {
930 WMDeleteTimerHandler(lPtr->selectID);
931 lPtr->selectID = NULL;
933 tmp = getItemIndexAt(lPtr, event->xbutton.y);
935 if (tmp >= 0) {
936 if (lPtr->action)
937 (*lPtr->action) (lPtr, lPtr->clientData);
940 if (!(event->xbutton.state & ShiftMask))
941 lastClicked = prevItem = tmp;
943 break;
945 case EnterNotify:
946 if (lPtr->selectID) {
947 WMDeleteTimerHandler(lPtr->selectID);
948 lPtr->selectID = NULL;
950 break;
952 case LeaveNotify:
953 height = WMWidgetHeight(lPtr);
954 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
955 if (event->xcrossing.y >= height) {
956 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
957 } else if (event->xcrossing.y <= 0) {
958 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
961 break;
963 case ButtonPress:
964 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
965 break;
966 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
967 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
968 int amount = 0;
970 if (event->xbutton.state & ControlMask) {
971 amount = lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1;
972 } else if (event->xbutton.state & ShiftMask) {
973 amount = 1;
974 } else {
975 amount = lPtr->fullFitLines / 3;
976 if (amount == 0)
977 amount++;
979 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
980 amount = -amount;
982 scrollByAmount(lPtr, amount);
983 break;
986 tmp = getItemIndexAt(lPtr, event->xbutton.y);
987 lPtr->flags.buttonPressed = 1;
989 if (tmp >= 0) {
990 if (tmp == lastClicked && WMIsDoubleClick(event)) {
991 WMSelectListItem(lPtr, tmp);
992 if (lPtr->doubleAction)
993 (*lPtr->doubleAction) (lPtr, lPtr->doubleClientData);
994 } else {
995 if (!lPtr->flags.allowMultipleSelection) {
996 if (event->xbutton.state & ControlMask) {
997 toggleItemSelection(lPtr, tmp);
998 } else {
999 WMSelectListItem(lPtr, tmp);
1001 } else {
1002 WMRange range;
1003 WMListItem *lastSel;
1005 if (event->xbutton.state & ControlMask) {
1006 toggleItemSelection(lPtr, tmp);
1007 } else if (event->xbutton.state & ShiftMask) {
1008 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1009 WMSelectListItem(lPtr, tmp);
1010 } else {
1011 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1012 range.position = WMGetFirstInArray(lPtr->items, lastSel);
1013 if (tmp >= range.position)
1014 range.count = tmp - range.position + 1;
1015 else
1016 range.count = tmp - range.position - 1;
1018 WMSetListSelectionToRange(lPtr, range);
1020 } else {
1021 range.position = tmp;
1022 range.count = 1;
1023 WMSetListSelectionToRange(lPtr, range);
1029 if (!(event->xbutton.state & ShiftMask))
1030 lastClicked = prevItem = tmp;
1032 break;
1034 case MotionNotify:
1035 height = WMWidgetHeight(lPtr);
1036 if (lPtr->selectID && event->xmotion.y > 0 && event->xmotion.y < height) {
1037 WMDeleteTimerHandler(lPtr->selectID);
1038 lPtr->selectID = NULL;
1040 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1041 if (event->xmotion.y <= 0) {
1042 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
1043 break;
1044 } else if (event->xmotion.y >= height) {
1045 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
1046 break;
1049 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1050 if (tmp >= 0 && tmp != prevItem) {
1051 if (lPtr->flags.allowMultipleSelection) {
1052 WMRange range;
1054 range.position = lastClicked;
1055 if (tmp >= lastClicked)
1056 range.count = tmp - lastClicked + 1;
1057 else
1058 range.count = tmp - lastClicked - 1;
1059 WMSetListSelectionToRange(lPtr, range);
1060 } else {
1061 WMSelectListItem(lPtr, tmp);
1064 prevItem = tmp;
1066 break;
1068 if (lPtr->topItem != topItem)
1069 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1072 static void updateGeometry(WMList * lPtr)
1074 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1075 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1076 lPtr->flags.dontFitAll = 1;
1077 } else {
1078 lPtr->flags.dontFitAll = 0;
1081 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1082 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1083 if (lPtr->topItem < 0)
1084 lPtr->topItem = 0;
1087 updateScroller(lPtr);
1090 static void didResizeList(W_ViewDelegate * self, WMView * view)
1092 WMList *lPtr = (WMList *) view->self;
1094 WMResizeWidget(lPtr->vScroller, 1, view->size.height - 2);
1096 updateDoubleBufferPixmap(lPtr);
1098 updateGeometry(lPtr);
1101 static void destroyList(List * lPtr)
1103 if (lPtr->idleID)
1104 WMDeleteIdleHandler(lPtr->idleID);
1105 lPtr->idleID = NULL;
1107 if (lPtr->selectID)
1108 WMDeleteTimerHandler(lPtr->selectID);
1109 lPtr->selectID = NULL;
1111 if (lPtr->selectedItems)
1112 WMFreeArray(lPtr->selectedItems);
1114 if (lPtr->items)
1115 WMFreeArray(lPtr->items);
1117 if (lPtr->doubleBuffer)
1118 XFreePixmap(lPtr->view->screen->display, lPtr->doubleBuffer);
1120 WMRemoveNotificationObserver(lPtr);
1122 wfree(lPtr);