Change to the linux kernel coding style
[wmaker-crm.git] / WINGs / wlist.c
blob759fe62ee3e1ec9b596cb78ecaa3946bc094b37d
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));
113 memset(lPtr, 0, sizeof(List));
115 lPtr->widgetClass = WC_List;
117 lPtr->view = W_CreateView(W_VIEW(parent));
118 if (!lPtr->view) {
119 wfree(lPtr);
120 return NULL;
122 lPtr->view->self = lPtr;
124 lPtr->view->delegate = &_ListViewDelegate;
126 WMCreateEventHandler(lPtr->view, ExposureMask | StructureNotifyMask
127 | ClientMessageMask, handleEvents, lPtr);
129 WMCreateEventHandler(lPtr->view, ButtonPressMask | ButtonReleaseMask
130 | EnterWindowMask | LeaveWindowMask | ButtonMotionMask, handleActionEvents, lPtr);
132 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
134 lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
135 lPtr->selectedItems = WMCreateArray(4);
137 /* create the vertical scroller */
138 lPtr->vScroller = WMCreateScroller(lPtr);
139 WMMoveWidget(lPtr->vScroller, 1, 1);
140 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
142 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
144 /* make the scroller map itself when it's realized */
145 WMMapWidget(lPtr->vScroller);
147 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
149 WMAddNotificationObserver(realizeObserver, lPtr, WMViewRealizedNotification, lPtr->view);
151 return lPtr;
154 void WMSetListAllowMultipleSelection(WMList * lPtr, Bool flag)
156 lPtr->flags.allowMultipleSelection = ((flag == 0) ? 0 : 1);
159 void WMSetListAllowEmptySelection(WMList * lPtr, Bool flag)
161 lPtr->flags.allowEmptySelection = ((flag == 0) ? 0 : 1);
164 static int comparator(const void *a, const void *b)
166 return (strcmp((*(WMListItem **) a)->text, (*(WMListItem **) b)->text));
169 void WMSortListItems(WMList * lPtr)
171 WMSortArray(lPtr->items, comparator);
173 paintList(lPtr);
176 void WMSortListItemsWithComparer(WMList * lPtr, WMCompareDataProc * func)
178 WMSortArray(lPtr->items, func);
180 paintList(lPtr);
183 WMListItem *WMInsertListItem(WMList * lPtr, int row, char *text)
185 WMListItem *item;
187 CHECK_CLASS(lPtr, WC_List);
189 item = wmalloc(sizeof(WMListItem));
190 memset(item, 0, sizeof(WMListItem));
191 item->text = wstrdup(text);
193 row = WMIN(row, WMGetArrayItemCount(lPtr->items));
195 if (row < 0)
196 WMAddToArray(lPtr->items, item);
197 else
198 WMInsertInArray(lPtr->items, row, item);
200 /* update the scroller when idle, so that we don't waste time
201 * updating it when another item is going to be added later */
202 if (!lPtr->idleID) {
203 lPtr->idleID = WMAddIdleHandler((WMCallback *) updateScroller, lPtr);
206 return item;
209 void WMRemoveListItem(WMList * lPtr, int row)
211 WMListItem *item;
212 int topItem = lPtr->topItem;
213 int selNotify = 0;
215 CHECK_CLASS(lPtr, WC_List);
217 /*wassertr(row>=0 && row<WMGetArrayItemCount(lPtr->items)); */
218 if (row < 0 || row >= WMGetArrayItemCount(lPtr->items))
219 return;
221 item = WMGetFromArray(lPtr->items, row);
222 if (item->selected) {
223 WMRemoveFromArray(lPtr->selectedItems, item);
224 selNotify = 1;
227 if (row <= lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll)
228 lPtr->topItem--;
229 if (lPtr->topItem < 0)
230 lPtr->topItem = 0;
232 WMDeleteFromArray(lPtr->items, row);
234 if (!lPtr->idleID) {
235 lPtr->idleID = WMAddIdleHandler((WMCallback *) updateScroller, lPtr);
237 if (lPtr->topItem != topItem) {
238 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
240 if (selNotify) {
241 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
245 WMListItem *WMGetListItem(WMList * lPtr, int row)
247 return WMGetFromArray(lPtr->items, row);
250 WMArray *WMGetListItems(WMList * lPtr)
252 return lPtr->items;
255 void WMSetListUserDrawProc(WMList * lPtr, WMListDrawProc * proc)
257 lPtr->flags.userDrawn = 1;
258 lPtr->draw = proc;
261 void WMSetListUserDrawItemHeight(WMList * lPtr, unsigned short height)
263 assert(height > 0);
265 lPtr->flags.userItemHeight = 1;
266 lPtr->itemHeight = height;
268 updateDoubleBufferPixmap(lPtr);
270 updateGeometry(lPtr);
273 void WMClearList(WMList * lPtr)
275 int selNo = WMGetArrayItemCount(lPtr->selectedItems);
277 WMEmptyArray(lPtr->selectedItems);
278 WMEmptyArray(lPtr->items);
280 lPtr->topItem = 0;
282 if (!lPtr->idleID) {
283 WMDeleteIdleHandler(lPtr->idleID);
284 lPtr->idleID = NULL;
286 if (lPtr->selectID) {
287 WMDeleteTimerHandler(lPtr->selectID);
288 lPtr->selectID = NULL;
290 if (lPtr->view->flags.realized) {
291 updateScroller(lPtr);
293 if (selNo > 0) {
294 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
298 void WMSetListAction(WMList * lPtr, WMAction * action, void *clientData)
300 lPtr->action = action;
301 lPtr->clientData = clientData;
304 void WMSetListDoubleAction(WMList * lPtr, WMAction * action, void *clientData)
306 lPtr->doubleAction = action;
307 lPtr->doubleClientData = clientData;
310 WMArray *WMGetListSelectedItems(WMList * lPtr)
312 return lPtr->selectedItems;
315 WMListItem *WMGetListSelectedItem(WMList * lPtr)
317 return WMGetFromArray(lPtr->selectedItems, 0);
320 int WMGetListSelectedItemRow(WMList * lPtr)
322 WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
324 return (item != NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
327 int WMGetListItemHeight(WMList * lPtr)
329 return lPtr->itemHeight;
332 void WMSetListPosition(WMList * lPtr, int row)
334 lPtr->topItem = row;
335 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
336 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
338 if (lPtr->topItem < 0)
339 lPtr->topItem = 0;
341 if (lPtr->view->flags.realized)
342 updateScroller(lPtr);
345 void WMSetListBottomPosition(WMList * lPtr, int row)
347 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
348 lPtr->topItem = row - lPtr->fullFitLines;
349 if (lPtr->topItem < 0)
350 lPtr->topItem = 0;
351 if (lPtr->view->flags.realized)
352 updateScroller(lPtr);
356 int WMGetListNumberOfRows(WMList * lPtr)
358 return WMGetArrayItemCount(lPtr->items);
361 int WMGetListPosition(WMList * lPtr)
363 return lPtr->topItem;
366 Bool WMListAllowsMultipleSelection(WMList * lPtr)
368 return lPtr->flags.allowMultipleSelection;
371 Bool WMListAllowsEmptySelection(WMList * lPtr)
373 return lPtr->flags.allowEmptySelection;
376 static void scrollByAmount(WMList * lPtr, int amount)
378 int itemCount = WMGetArrayItemCount(lPtr->items);
380 if ((amount < 0 && lPtr->topItem > 0) || (amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
382 lPtr->topItem += amount;
383 if (lPtr->topItem < 0)
384 lPtr->topItem = 0;
385 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
386 lPtr->topItem = itemCount - lPtr->fullFitLines;
388 updateScroller(lPtr);
392 static void vScrollCallBack(WMWidget * scroller, void *self)
394 WMList *lPtr = (WMList *) self;
395 int height;
396 int oldTopItem = lPtr->topItem;
397 int itemCount = WMGetArrayItemCount(lPtr->items);
399 height = lPtr->view->size.height - 4;
401 switch (WMGetScrollerHitPart((WMScroller *) scroller)) {
402 case WSDecrementLine:
403 scrollByAmount(lPtr, -1);
404 break;
406 case WSIncrementLine:
407 scrollByAmount(lPtr, 1);
408 break;
410 case WSDecrementPage:
411 scrollByAmount(lPtr, -lPtr->fullFitLines + (1 - lPtr->flags.dontFitAll) + 1);
412 break;
414 case WSIncrementPage:
415 scrollByAmount(lPtr, lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1);
416 break;
418 case WSDecrementWheel:
419 scrollByAmount(lPtr, -lPtr->fullFitLines / 3);
420 break;
422 case WSIncrementWheel:
423 scrollByAmount(lPtr, lPtr->fullFitLines / 3);
424 break;
426 case WSKnob:
427 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) * (float)(itemCount - lPtr->fullFitLines);
429 if (oldTopItem != lPtr->topItem)
430 paintList(lPtr); /* use updateScroller(lPtr) here? -Dan */
431 break;
433 case WSKnobSlot:
434 case WSNoPart:
435 default:
436 /* do nothing */
437 break;
440 if (lPtr->topItem != oldTopItem)
441 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
444 static void paintItem(List * lPtr, int index)
446 WMView *view = lPtr->view;
447 W_Screen *scr = view->screen;
448 Display *display = scr->display;
449 int width, height, x, y, tlen;
450 WMListItem *itemPtr;
451 Drawable d = lPtr->doubleBuffer;
453 itemPtr = WMGetFromArray(lPtr->items, index);
455 width = lPtr->view->size.width - 2 - 19;
456 height = lPtr->itemHeight;
457 x = 19;
458 y = 2 + (index - lPtr->topItem) * lPtr->itemHeight + 1;
459 tlen = strlen(itemPtr->text);
461 if (lPtr->flags.userDrawn) {
462 WMRect rect;
463 int flags;
465 rect.size.width = width;
466 rect.size.height = height;
467 rect.pos.x = 0;
468 rect.pos.y = 0;
470 flags = itemPtr->uflags;
471 if (itemPtr->disabled)
472 flags |= WLDSDisabled;
473 if (itemPtr->selected)
474 flags |= WLDSSelected;
475 if (itemPtr->isBranch)
476 flags |= WLDSIsBranch;
478 if (lPtr->draw)
479 (*lPtr->draw) (lPtr, index, d, itemPtr->text, flags, &rect);
481 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
482 } else {
483 WMColor *back = (itemPtr->selected ? scr->white : view->backColor);
485 XFillRectangle(display, d, WMColorGC(back), 0, 0, width, height);
487 W_PaintText(view, d, scr->normalFont, 4, 0, width, WALeft, scr->black, False, itemPtr->text, tlen);
488 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
491 if ((index - lPtr->topItem + lPtr->fullFitLines) * lPtr->itemHeight > lPtr->view->size.height - 2) {
492 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
493 lPtr->view->size.width, lPtr->view->size.height, WRSunken);
497 static void paintList(List * lPtr)
499 W_Screen *scrPtr = lPtr->view->screen;
500 int i, lim;
502 if (!lPtr->view->flags.mapped)
503 return;
505 if (WMGetArrayItemCount(lPtr->items) > 0) {
506 if (lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll > WMGetArrayItemCount(lPtr->items)) {
508 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
509 XClearArea(scrPtr->display, lPtr->view->window, 19,
510 2 + lim * lPtr->itemHeight, lPtr->view->size.width - 21,
511 lPtr->view->size.height - lim * lPtr->itemHeight - 3, False);
512 } else {
513 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
515 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
516 paintItem(lPtr, i);
518 } else {
519 XClearWindow(scrPtr->display, lPtr->view->window);
521 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width, lPtr->view->size.height, WRSunken);
524 #if 0
525 static void scrollTo(List * lPtr, int newTop)
529 #endif
531 static void updateScroller(void *data)
533 List *lPtr = (List *) data;
535 float knobProportion, floatValue, tmp;
536 int count = WMGetArrayItemCount(lPtr->items);
538 if (lPtr->idleID)
539 WMDeleteIdleHandler(lPtr->idleID);
540 lPtr->idleID = NULL;
542 paintList(lPtr);
544 if (count == 0 || count <= lPtr->fullFitLines)
545 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
546 else {
547 tmp = lPtr->fullFitLines;
548 knobProportion = tmp / (float)count;
550 floatValue = (float)lPtr->topItem / (float)(count - lPtr->fullFitLines);
552 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
556 static void scrollForwardSelecting(void *data)
558 List *lPtr = (List *) data;
559 int lastSelected;
561 lastSelected = lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll - 1;
563 if (lastSelected >= WMGetArrayItemCount(lPtr->items) - 1) {
564 lPtr->selectID = NULL;
565 if (lPtr->flags.dontFitAll)
566 scrollByAmount(lPtr, 1);
567 return;
570 /* selecting NEEDS to be done before scrolling to avoid flickering */
571 if (lPtr->flags.allowMultipleSelection) {
572 WMListItem *item;
573 WMRange range;
575 item = WMGetFromArray(lPtr->selectedItems, 0);
576 range.position = WMGetFirstInArray(lPtr->items, item);
577 if (lastSelected + 1 >= range.position) {
578 range.count = lastSelected - range.position + 2;
579 } else {
580 range.count = lastSelected - range.position;
582 WMSetListSelectionToRange(lPtr, range);
583 } else {
584 WMSelectListItem(lPtr, lastSelected + 1);
586 scrollByAmount(lPtr, 1);
588 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
591 static void scrollBackwardSelecting(void *data)
593 List *lPtr = (List *) data;
595 if (lPtr->topItem < 1) {
596 lPtr->selectID = NULL;
597 return;
600 /* selecting NEEDS to be done before scrolling to avoid flickering */
601 if (lPtr->flags.allowMultipleSelection) {
602 WMListItem *item;
603 WMRange range;
605 item = WMGetFromArray(lPtr->selectedItems, 0);
606 range.position = WMGetFirstInArray(lPtr->items, item);
607 if (lPtr->topItem - 1 >= range.position) {
608 range.count = lPtr->topItem - range.position;
609 } else {
610 range.count = lPtr->topItem - range.position - 2;
612 WMSetListSelectionToRange(lPtr, range);
613 } else {
614 WMSelectListItem(lPtr, lPtr->topItem - 1);
616 scrollByAmount(lPtr, -1);
618 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
621 static void handleEvents(XEvent * event, void *data)
623 List *lPtr = (List *) data;
625 CHECK_CLASS(data, WC_List);
627 switch (event->type) {
628 case Expose:
629 if (event->xexpose.count != 0)
630 break;
631 paintList(lPtr);
632 break;
634 case DestroyNotify:
635 destroyList(lPtr);
636 break;
641 static int matchTitle(void *item, void *title)
643 return (strcmp(((WMListItem *) item)->text, (char *)title) == 0 ? 1 : 0);
646 int WMFindRowOfListItemWithTitle(WMList * lPtr, char *title)
648 return WMFindInArray(lPtr->items, matchTitle, title);
651 void WMSelectListItem(WMList * lPtr, int row)
653 WMListItem *item;
655 if (row >= WMGetArrayItemCount(lPtr->items))
656 return;
658 if (row < 0) {
659 /* row = -1 will deselects all for backward compatibility.
660 * will be removed later. -Dan */
661 WMUnselectAllListItems(lPtr);
662 return;
665 item = WMGetFromArray(lPtr->items, row);
666 if (item->selected)
667 return; /* Return if already selected */
669 if (!lPtr->flags.allowMultipleSelection) {
670 /* unselect previous selected items */
671 unselectAllListItems(lPtr, NULL);
674 /* select item */
675 item->selected = 1;
676 WMAddToArray(lPtr->selectedItems, item);
678 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
679 paintItem(lPtr, row);
682 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
685 void WMUnselectListItem(WMList * lPtr, int row)
687 WMListItem *item = WMGetFromArray(lPtr->items, row);
689 if (!item || !item->selected)
690 return;
692 if (!lPtr->flags.allowEmptySelection && WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
693 return;
696 item->selected = 0;
697 WMRemoveFromArray(lPtr->selectedItems, item);
699 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
700 paintItem(lPtr, row);
703 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
706 void WMSelectListItemsInRange(WMList * lPtr, WMRange range)
708 WMListItem *item;
709 int position = range.position, k = 1, notify = 0;
710 int total = WMGetArrayItemCount(lPtr->items);
712 if (!lPtr->flags.allowMultipleSelection)
713 return;
714 if (range.count == 0)
715 return; /* Nothing to select */
717 if (range.count < 0) {
718 range.count = -range.count;
719 k = -1;
722 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
723 item = WMGetFromArray(lPtr->items, position);
724 if (!item->selected) {
725 item->selected = 1;
726 WMAddToArray(lPtr->selectedItems, item);
727 if (lPtr->view->flags.mapped && position >= lPtr->topItem
728 && position <= lPtr->topItem + lPtr->fullFitLines) {
729 paintItem(lPtr, position);
731 notify = 1;
733 position += k;
736 if (notify) {
737 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
741 void WMSetListSelectionToRange(WMList * lPtr, WMRange range)
743 WMListItem *item;
744 int mark1, mark2, i, k;
745 int position = range.position, notify = 0;
746 int total = WMGetArrayItemCount(lPtr->items);
748 if (!lPtr->flags.allowMultipleSelection)
749 return;
751 if (range.count == 0) {
752 WMUnselectAllListItems(lPtr);
753 return;
756 if (range.count < 0) {
757 mark1 = range.position + range.count + 1;
758 mark2 = range.position + 1;
759 range.count = -range.count;
760 k = -1;
761 } else {
762 mark1 = range.position;
763 mark2 = range.position + range.count;
764 k = 1;
766 if (mark1 > total)
767 mark1 = total;
768 if (mark2 < 0)
769 mark2 = 0;
771 WMEmptyArray(lPtr->selectedItems);
773 for (i = 0; i < mark1; i++) {
774 item = WMGetFromArray(lPtr->items, i);
775 if (item->selected) {
776 item->selected = 0;
777 if (lPtr->view->flags.mapped && i >= lPtr->topItem
778 && i <= lPtr->topItem + lPtr->fullFitLines) {
779 paintItem(lPtr, i);
781 notify = 1;
784 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
785 item = WMGetFromArray(lPtr->items, position);
786 if (!item->selected) {
787 item->selected = 1;
788 if (lPtr->view->flags.mapped && position >= lPtr->topItem
789 && position <= lPtr->topItem + lPtr->fullFitLines) {
790 paintItem(lPtr, position);
792 notify = 1;
794 WMAddToArray(lPtr->selectedItems, item);
795 position += k;
797 for (i = mark2; i < total; i++) {
798 item = WMGetFromArray(lPtr->items, i);
799 if (item->selected) {
800 item->selected = 0;
801 if (lPtr->view->flags.mapped && i >= lPtr->topItem
802 && i <= lPtr->topItem + lPtr->fullFitLines) {
803 paintItem(lPtr, i);
805 notify = 1;
809 if (notify) {
810 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
814 void WMSelectAllListItems(WMList * lPtr)
816 int i;
817 WMListItem *item;
819 if (!lPtr->flags.allowMultipleSelection)
820 return;
822 if (WMGetArrayItemCount(lPtr->items) == WMGetArrayItemCount(lPtr->selectedItems)) {
823 return; /* All items are selected already */
826 WMFreeArray(lPtr->selectedItems);
827 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
829 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
830 item = WMGetFromArray(lPtr->items, i);
831 if (!item->selected) {
832 item->selected = 1;
833 if (lPtr->view->flags.mapped && i >= lPtr->topItem
834 && i <= lPtr->topItem + lPtr->fullFitLines) {
835 paintItem(lPtr, i);
840 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
844 * Be careful from where you call this function! It doesn't honor the
845 * allowEmptySelection flag and doesn't send a notification about selection
846 * change! You need to manage these in the functions from where you call it.
848 * This will unselect all items if exceptThis is NULL, else will keep
849 * exceptThis selected.
850 * Make sure that exceptThis is one of the already selected items if not NULL!
853 static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis)
855 int i;
856 WMListItem *item;
858 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
859 item = WMGetFromArray(lPtr->items, i);
860 if (item != exceptThis && item->selected) {
861 item->selected = 0;
862 if (lPtr->view->flags.mapped && i >= lPtr->topItem
863 && i <= lPtr->topItem + lPtr->fullFitLines) {
864 paintItem(lPtr, i);
869 WMEmptyArray(lPtr->selectedItems);
870 if (exceptThis != NULL) {
871 exceptThis->selected = 1;
872 WMAddToArray(lPtr->selectedItems, exceptThis);
876 void WMUnselectAllListItems(WMList * lPtr)
878 int keep;
879 WMListItem *keepItem;
881 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
883 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
884 return;
886 keepItem = (keep == 1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
888 unselectAllListItems(lPtr, keepItem);
890 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
893 static int getItemIndexAt(List * lPtr, int clickY)
895 int index;
897 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
899 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
900 return -1;
902 return index;
905 static void toggleItemSelection(WMList * lPtr, int index)
907 WMListItem *item = WMGetFromArray(lPtr->items, index);
909 if (item && item->selected) {
910 WMUnselectListItem(lPtr, index);
911 } else {
912 WMSelectListItem(lPtr, index);
916 static void handleActionEvents(XEvent * event, void *data)
918 List *lPtr = (List *) data;
919 int tmp, height;
920 int topItem = lPtr->topItem;
921 static int lastClicked = -1, prevItem = -1;
923 CHECK_CLASS(data, WC_List);
925 switch (event->type) {
926 case ButtonRelease:
927 /* Ignore mouse wheel events, they're not "real" button events */
928 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
929 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
930 break;
933 lPtr->flags.buttonPressed = 0;
934 if (lPtr->selectID) {
935 WMDeleteTimerHandler(lPtr->selectID);
936 lPtr->selectID = NULL;
938 tmp = getItemIndexAt(lPtr, event->xbutton.y);
940 if (tmp >= 0) {
941 if (lPtr->action)
942 (*lPtr->action) (lPtr, lPtr->clientData);
945 if (!(event->xbutton.state & ShiftMask))
946 lastClicked = prevItem = tmp;
948 break;
950 case EnterNotify:
951 if (lPtr->selectID) {
952 WMDeleteTimerHandler(lPtr->selectID);
953 lPtr->selectID = NULL;
955 break;
957 case LeaveNotify:
958 height = WMWidgetHeight(lPtr);
959 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
960 if (event->xcrossing.y >= height) {
961 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
962 } else if (event->xcrossing.y <= 0) {
963 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
966 break;
968 case ButtonPress:
969 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
970 break;
971 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
972 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
973 int amount = 0;
975 if (event->xbutton.state & ControlMask) {
976 amount = lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1;
977 } else if (event->xbutton.state & ShiftMask) {
978 amount = 1;
979 } else {
980 amount = lPtr->fullFitLines / 3;
981 if (amount == 0)
982 amount++;
984 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
985 amount = -amount;
987 scrollByAmount(lPtr, amount);
988 break;
991 tmp = getItemIndexAt(lPtr, event->xbutton.y);
992 lPtr->flags.buttonPressed = 1;
994 if (tmp >= 0) {
995 if (tmp == lastClicked && WMIsDoubleClick(event)) {
996 WMSelectListItem(lPtr, tmp);
997 if (lPtr->doubleAction)
998 (*lPtr->doubleAction) (lPtr, lPtr->doubleClientData);
999 } else {
1000 if (!lPtr->flags.allowMultipleSelection) {
1001 if (event->xbutton.state & ControlMask) {
1002 toggleItemSelection(lPtr, tmp);
1003 } else {
1004 WMSelectListItem(lPtr, tmp);
1006 } else {
1007 WMRange range;
1008 WMListItem *lastSel;
1010 if (event->xbutton.state & ControlMask) {
1011 toggleItemSelection(lPtr, tmp);
1012 } else if (event->xbutton.state & ShiftMask) {
1013 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1014 WMSelectListItem(lPtr, tmp);
1015 } else {
1016 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1017 range.position = WMGetFirstInArray(lPtr->items, lastSel);
1018 if (tmp >= range.position)
1019 range.count = tmp - range.position + 1;
1020 else
1021 range.count = tmp - range.position - 1;
1023 WMSetListSelectionToRange(lPtr, range);
1025 } else {
1026 range.position = tmp;
1027 range.count = 1;
1028 WMSetListSelectionToRange(lPtr, range);
1034 if (!(event->xbutton.state & ShiftMask))
1035 lastClicked = prevItem = tmp;
1037 break;
1039 case MotionNotify:
1040 height = WMWidgetHeight(lPtr);
1041 if (lPtr->selectID && event->xmotion.y > 0 && event->xmotion.y < height) {
1042 WMDeleteTimerHandler(lPtr->selectID);
1043 lPtr->selectID = NULL;
1045 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1046 if (event->xmotion.y <= 0) {
1047 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
1048 break;
1049 } else if (event->xmotion.y >= height) {
1050 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
1051 break;
1054 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1055 if (tmp >= 0 && tmp != prevItem) {
1056 if (lPtr->flags.allowMultipleSelection) {
1057 WMRange range;
1059 range.position = lastClicked;
1060 if (tmp >= lastClicked)
1061 range.count = tmp - lastClicked + 1;
1062 else
1063 range.count = tmp - lastClicked - 1;
1064 WMSetListSelectionToRange(lPtr, range);
1065 } else {
1066 WMSelectListItem(lPtr, tmp);
1069 prevItem = tmp;
1071 break;
1073 if (lPtr->topItem != topItem)
1074 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1077 static void updateGeometry(WMList * lPtr)
1079 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1080 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1081 lPtr->flags.dontFitAll = 1;
1082 } else {
1083 lPtr->flags.dontFitAll = 0;
1086 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1087 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1088 if (lPtr->topItem < 0)
1089 lPtr->topItem = 0;
1092 updateScroller(lPtr);
1095 static void didResizeList(W_ViewDelegate * self, WMView * view)
1097 WMList *lPtr = (WMList *) view->self;
1099 WMResizeWidget(lPtr->vScroller, 1, view->size.height - 2);
1101 updateDoubleBufferPixmap(lPtr);
1103 updateGeometry(lPtr);
1106 static void destroyList(List * lPtr)
1108 if (lPtr->idleID)
1109 WMDeleteIdleHandler(lPtr->idleID);
1110 lPtr->idleID = NULL;
1112 if (lPtr->selectID)
1113 WMDeleteTimerHandler(lPtr->selectID);
1114 lPtr->selectID = NULL;
1116 if (lPtr->selectedItems)
1117 WMFreeArray(lPtr->selectedItems);
1119 if (lPtr->items)
1120 WMFreeArray(lPtr->items);
1122 if (lPtr->doubleBuffer)
1123 XFreePixmap(lPtr->view->screen->display, lPtr->doubleBuffer);
1125 WMRemoveNotificationObserver(lPtr);
1127 wfree(lPtr);