WINGs: Added a few missing const attributes
[wmaker-crm.git] / WINGs / wlist.c
blobe91dcde11dad24c57f8b1c685c6af76f153beaf0
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(W_ViewDelegate * self, WMView * view);
69 static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis);
71 static 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 /* Parameter not used, but tell the compiler that it is ok */
96 (void) not;
98 updateDoubleBufferPixmap(self);
101 static void releaseItem(void *data)
103 WMListItem *item = (WMListItem *) data;
105 if (item->text)
106 wfree(item->text);
107 wfree(item);
110 WMList *WMCreateList(WMWidget * parent)
112 List *lPtr;
113 W_Screen *scrPtr = W_VIEW(parent)->screen;
115 lPtr = wmalloc(sizeof(List));
117 lPtr->widgetClass = WC_List;
119 lPtr->view = W_CreateView(W_VIEW(parent));
120 if (!lPtr->view) {
121 wfree(lPtr);
122 return NULL;
124 lPtr->view->self = lPtr;
126 lPtr->view->delegate = &_ListViewDelegate;
128 WMCreateEventHandler(lPtr->view, ExposureMask | StructureNotifyMask
129 | ClientMessageMask, handleEvents, lPtr);
131 WMCreateEventHandler(lPtr->view, ButtonPressMask | ButtonReleaseMask
132 | EnterWindowMask | LeaveWindowMask | ButtonMotionMask, handleActionEvents, lPtr);
134 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
136 lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
137 lPtr->selectedItems = WMCreateArray(4);
139 /* create the vertical scroller */
140 lPtr->vScroller = WMCreateScroller(lPtr);
141 WMMoveWidget(lPtr->vScroller, 1, 1);
142 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
144 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
146 /* make the scroller map itself when it's realized */
147 WMMapWidget(lPtr->vScroller);
149 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
151 WMAddNotificationObserver(realizeObserver, lPtr, WMViewRealizedNotification, lPtr->view);
153 return lPtr;
156 void WMSetListAllowMultipleSelection(WMList * lPtr, Bool flag)
158 lPtr->flags.allowMultipleSelection = ((flag == 0) ? 0 : 1);
161 void WMSetListAllowEmptySelection(WMList * lPtr, Bool flag)
163 lPtr->flags.allowEmptySelection = ((flag == 0) ? 0 : 1);
166 static int comparator(const void *a, const void *b)
168 return (strcmp((*(WMListItem **) a)->text, (*(WMListItem **) b)->text));
171 void WMSortListItems(WMList * lPtr)
173 WMSortArray(lPtr->items, comparator);
175 paintList(lPtr);
178 void WMSortListItemsWithComparer(WMList * lPtr, WMCompareDataProc * func)
180 WMSortArray(lPtr->items, func);
182 paintList(lPtr);
185 WMListItem *WMInsertListItem(WMList * lPtr, int row, const char *text)
187 WMListItem *item;
189 CHECK_CLASS(lPtr, WC_List);
191 item = wmalloc(sizeof(WMListItem));
192 item->text = wstrdup(text);
194 row = WMIN(row, WMGetArrayItemCount(lPtr->items));
196 if (row < 0)
197 WMAddToArray(lPtr->items, item);
198 else
199 WMInsertInArray(lPtr->items, row, item);
201 /* update the scroller when idle, so that we don't waste time
202 * updating it when another item is going to be added later */
203 if (!lPtr->idleID) {
204 lPtr->idleID = WMAddIdleHandler((WMCallback *) updateScroller, lPtr);
207 return item;
210 void WMRemoveListItem(WMList * lPtr, int row)
212 WMListItem *item;
213 int topItem = lPtr->topItem;
214 int selNotify = 0;
216 CHECK_CLASS(lPtr, WC_List);
218 /*wassertr(row>=0 && row<WMGetArrayItemCount(lPtr->items)); */
219 if (row < 0 || row >= WMGetArrayItemCount(lPtr->items))
220 return;
222 item = WMGetFromArray(lPtr->items, row);
223 if (item->selected) {
224 WMRemoveFromArray(lPtr->selectedItems, item);
225 selNotify = 1;
228 if (row <= lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll)
229 lPtr->topItem--;
230 if (lPtr->topItem < 0)
231 lPtr->topItem = 0;
233 WMDeleteFromArray(lPtr->items, row);
235 if (!lPtr->idleID) {
236 lPtr->idleID = WMAddIdleHandler((WMCallback *) updateScroller, lPtr);
238 if (lPtr->topItem != topItem) {
239 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
241 if (selNotify) {
242 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
246 WMListItem *WMGetListItem(WMList * lPtr, int row)
248 return WMGetFromArray(lPtr->items, row);
251 WMArray *WMGetListItems(WMList * lPtr)
253 return lPtr->items;
256 void WMSetListUserDrawProc(WMList * lPtr, WMListDrawProc * proc)
258 lPtr->flags.userDrawn = 1;
259 lPtr->draw = proc;
262 void WMSetListUserDrawItemHeight(WMList * lPtr, unsigned short height)
264 assert(height > 0);
266 lPtr->flags.userItemHeight = 1;
267 lPtr->itemHeight = height;
269 updateDoubleBufferPixmap(lPtr);
271 updateGeometry(lPtr);
274 void WMClearList(WMList * lPtr)
276 int selNo = WMGetArrayItemCount(lPtr->selectedItems);
278 WMEmptyArray(lPtr->selectedItems);
279 WMEmptyArray(lPtr->items);
281 lPtr->topItem = 0;
283 if (!lPtr->idleID) {
284 WMDeleteIdleHandler(lPtr->idleID);
285 lPtr->idleID = NULL;
287 if (lPtr->selectID) {
288 WMDeleteTimerHandler(lPtr->selectID);
289 lPtr->selectID = NULL;
291 if (lPtr->view->flags.realized) {
292 updateScroller(lPtr);
294 if (selNo > 0) {
295 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
299 void WMSetListAction(WMList * lPtr, WMAction * action, void *clientData)
301 lPtr->action = action;
302 lPtr->clientData = clientData;
305 void WMSetListDoubleAction(WMList * lPtr, WMAction * action, void *clientData)
307 lPtr->doubleAction = action;
308 lPtr->doubleClientData = clientData;
311 WMArray *WMGetListSelectedItems(WMList * lPtr)
313 return lPtr->selectedItems;
316 WMListItem *WMGetListSelectedItem(WMList * lPtr)
318 return WMGetFromArray(lPtr->selectedItems, 0);
321 int WMGetListSelectedItemRow(WMList * lPtr)
323 WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
325 return (item != NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
328 int WMGetListItemHeight(WMList * lPtr)
330 return lPtr->itemHeight;
333 void WMSetListPosition(WMList * lPtr, int row)
335 lPtr->topItem = row;
336 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
337 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
339 if (lPtr->topItem < 0)
340 lPtr->topItem = 0;
342 if (lPtr->view->flags.realized)
343 updateScroller(lPtr);
346 void WMSetListBottomPosition(WMList * lPtr, int row)
348 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
349 lPtr->topItem = row - lPtr->fullFitLines;
350 if (lPtr->topItem < 0)
351 lPtr->topItem = 0;
352 if (lPtr->view->flags.realized)
353 updateScroller(lPtr);
357 int WMGetListNumberOfRows(WMList * lPtr)
359 return WMGetArrayItemCount(lPtr->items);
362 int WMGetListPosition(WMList * lPtr)
364 return lPtr->topItem;
367 Bool WMListAllowsMultipleSelection(WMList * lPtr)
369 return lPtr->flags.allowMultipleSelection;
372 Bool WMListAllowsEmptySelection(WMList * lPtr)
374 return lPtr->flags.allowEmptySelection;
377 static void scrollByAmount(WMList * lPtr, int amount)
379 int itemCount = WMGetArrayItemCount(lPtr->items);
381 if ((amount < 0 && lPtr->topItem > 0) || (amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
383 lPtr->topItem += amount;
384 if (lPtr->topItem < 0)
385 lPtr->topItem = 0;
386 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
387 lPtr->topItem = itemCount - lPtr->fullFitLines;
389 updateScroller(lPtr);
393 static void vScrollCallBack(WMWidget * scroller, void *self)
395 WMList *lPtr = (WMList *) self;
396 int oldTopItem = lPtr->topItem;
397 int itemCount = WMGetArrayItemCount(lPtr->items);
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 const WMListItem *wl_item = item;
642 const char *s_title = title;
644 return (strcmp(wl_item->text, s_title) == 0 ? 1 : 0);
647 int WMFindRowOfListItemWithTitle(WMList * lPtr, const char *title)
650 * We explicitely discard the 'const' attribute here because the
651 * call-back function handler must not be made with a const
652 * attribute, but our local call-back function (above) does have
653 * it properly set, so we're consistent
655 return WMFindInArray(lPtr->items, matchTitle, (char *) title);
658 void WMSelectListItem(WMList * lPtr, int row)
660 WMListItem *item;
662 if (row >= WMGetArrayItemCount(lPtr->items))
663 return;
665 if (row < 0) {
666 /* row = -1 will deselects all for backward compatibility.
667 * will be removed later. -Dan */
668 WMUnselectAllListItems(lPtr);
669 return;
672 item = WMGetFromArray(lPtr->items, row);
673 if (item->selected)
674 return; /* Return if already selected */
676 if (!lPtr->flags.allowMultipleSelection) {
677 /* unselect previous selected items */
678 unselectAllListItems(lPtr, NULL);
681 /* select item */
682 item->selected = 1;
683 WMAddToArray(lPtr->selectedItems, item);
685 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
686 paintItem(lPtr, row);
689 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
692 void WMUnselectListItem(WMList * lPtr, int row)
694 WMListItem *item = WMGetFromArray(lPtr->items, row);
696 if (!item || !item->selected)
697 return;
699 if (!lPtr->flags.allowEmptySelection && WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
700 return;
703 item->selected = 0;
704 WMRemoveFromArray(lPtr->selectedItems, item);
706 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
707 paintItem(lPtr, row);
710 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
713 void WMSelectListItemsInRange(WMList * lPtr, WMRange range)
715 WMListItem *item;
716 int position = range.position, k = 1, notify = 0;
717 int total = WMGetArrayItemCount(lPtr->items);
719 if (!lPtr->flags.allowMultipleSelection)
720 return;
721 if (range.count == 0)
722 return; /* Nothing to select */
724 if (range.count < 0) {
725 range.count = -range.count;
726 k = -1;
729 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
730 item = WMGetFromArray(lPtr->items, position);
731 if (!item->selected) {
732 item->selected = 1;
733 WMAddToArray(lPtr->selectedItems, item);
734 if (lPtr->view->flags.mapped && position >= lPtr->topItem
735 && position <= lPtr->topItem + lPtr->fullFitLines) {
736 paintItem(lPtr, position);
738 notify = 1;
740 position += k;
743 if (notify) {
744 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
748 void WMSetListSelectionToRange(WMList * lPtr, WMRange range)
750 WMListItem *item;
751 int mark1, mark2, i, k;
752 int position = range.position, notify = 0;
753 int total = WMGetArrayItemCount(lPtr->items);
755 if (!lPtr->flags.allowMultipleSelection)
756 return;
758 if (range.count == 0) {
759 WMUnselectAllListItems(lPtr);
760 return;
763 if (range.count < 0) {
764 mark1 = range.position + range.count + 1;
765 mark2 = range.position + 1;
766 range.count = -range.count;
767 k = -1;
768 } else {
769 mark1 = range.position;
770 mark2 = range.position + range.count;
771 k = 1;
773 if (mark1 > total)
774 mark1 = total;
775 if (mark2 < 0)
776 mark2 = 0;
778 WMEmptyArray(lPtr->selectedItems);
780 for (i = 0; i < mark1; i++) {
781 item = WMGetFromArray(lPtr->items, i);
782 if (item->selected) {
783 item->selected = 0;
784 if (lPtr->view->flags.mapped && i >= lPtr->topItem
785 && i <= lPtr->topItem + lPtr->fullFitLines) {
786 paintItem(lPtr, i);
788 notify = 1;
791 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
792 item = WMGetFromArray(lPtr->items, position);
793 if (!item->selected) {
794 item->selected = 1;
795 if (lPtr->view->flags.mapped && position >= lPtr->topItem
796 && position <= lPtr->topItem + lPtr->fullFitLines) {
797 paintItem(lPtr, position);
799 notify = 1;
801 WMAddToArray(lPtr->selectedItems, item);
802 position += k;
804 for (i = mark2; i < total; i++) {
805 item = WMGetFromArray(lPtr->items, i);
806 if (item->selected) {
807 item->selected = 0;
808 if (lPtr->view->flags.mapped && i >= lPtr->topItem
809 && i <= lPtr->topItem + lPtr->fullFitLines) {
810 paintItem(lPtr, i);
812 notify = 1;
816 if (notify) {
817 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
821 void WMSelectAllListItems(WMList * lPtr)
823 int i;
824 WMListItem *item;
826 if (!lPtr->flags.allowMultipleSelection)
827 return;
829 if (WMGetArrayItemCount(lPtr->items) == WMGetArrayItemCount(lPtr->selectedItems)) {
830 return; /* All items are selected already */
833 WMFreeArray(lPtr->selectedItems);
834 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
836 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
837 item = WMGetFromArray(lPtr->items, i);
838 if (!item->selected) {
839 item->selected = 1;
840 if (lPtr->view->flags.mapped && i >= lPtr->topItem
841 && i <= lPtr->topItem + lPtr->fullFitLines) {
842 paintItem(lPtr, i);
847 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
851 * Be careful from where you call this function! It doesn't honor the
852 * allowEmptySelection flag and doesn't send a notification about selection
853 * change! You need to manage these in the functions from where you call it.
855 * This will unselect all items if exceptThis is NULL, else will keep
856 * exceptThis selected.
857 * Make sure that exceptThis is one of the already selected items if not NULL!
860 static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis)
862 int i;
863 WMListItem *item;
865 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
866 item = WMGetFromArray(lPtr->items, i);
867 if (item != exceptThis && item->selected) {
868 item->selected = 0;
869 if (lPtr->view->flags.mapped && i >= lPtr->topItem
870 && i <= lPtr->topItem + lPtr->fullFitLines) {
871 paintItem(lPtr, i);
876 WMEmptyArray(lPtr->selectedItems);
877 if (exceptThis != NULL) {
878 exceptThis->selected = 1;
879 WMAddToArray(lPtr->selectedItems, exceptThis);
883 void WMUnselectAllListItems(WMList * lPtr)
885 int keep;
886 WMListItem *keepItem;
888 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
890 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
891 return;
893 keepItem = (keep == 1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
895 unselectAllListItems(lPtr, keepItem);
897 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
900 static int getItemIndexAt(List * lPtr, int clickY)
902 int index;
904 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
906 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
907 return -1;
909 return index;
912 static void toggleItemSelection(WMList * lPtr, int index)
914 WMListItem *item = WMGetFromArray(lPtr->items, index);
916 if (item && item->selected) {
917 WMUnselectListItem(lPtr, index);
918 } else {
919 WMSelectListItem(lPtr, index);
923 static void handleActionEvents(XEvent * event, void *data)
925 List *lPtr = (List *) data;
926 int tmp, height;
927 int topItem = lPtr->topItem;
928 static int lastClicked = -1, prevItem = -1;
930 CHECK_CLASS(data, WC_List);
932 switch (event->type) {
933 case ButtonRelease:
934 /* Ignore mouse wheel events, they're not "real" button events */
935 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
936 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
937 break;
940 lPtr->flags.buttonPressed = 0;
941 if (lPtr->selectID) {
942 WMDeleteTimerHandler(lPtr->selectID);
943 lPtr->selectID = NULL;
945 tmp = getItemIndexAt(lPtr, event->xbutton.y);
947 if (tmp >= 0) {
948 if (lPtr->action)
949 (*lPtr->action) (lPtr, lPtr->clientData);
952 if (!(event->xbutton.state & ShiftMask))
953 lastClicked = prevItem = tmp;
955 break;
957 case EnterNotify:
958 if (lPtr->selectID) {
959 WMDeleteTimerHandler(lPtr->selectID);
960 lPtr->selectID = NULL;
962 break;
964 case LeaveNotify:
965 height = WMWidgetHeight(lPtr);
966 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
967 if (event->xcrossing.y >= height) {
968 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
969 } else if (event->xcrossing.y <= 0) {
970 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
973 break;
975 case ButtonPress:
976 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
977 break;
978 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
979 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
980 int amount = 0;
982 if (event->xbutton.state & ControlMask) {
983 amount = lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1;
984 } else if (event->xbutton.state & ShiftMask) {
985 amount = 1;
986 } else {
987 amount = lPtr->fullFitLines / 3;
988 if (amount == 0)
989 amount++;
991 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
992 amount = -amount;
994 scrollByAmount(lPtr, amount);
995 break;
998 tmp = getItemIndexAt(lPtr, event->xbutton.y);
999 lPtr->flags.buttonPressed = 1;
1001 if (tmp >= 0) {
1002 if (tmp == lastClicked && WMIsDoubleClick(event)) {
1003 WMSelectListItem(lPtr, tmp);
1004 if (lPtr->doubleAction)
1005 (*lPtr->doubleAction) (lPtr, lPtr->doubleClientData);
1006 } else {
1007 if (!lPtr->flags.allowMultipleSelection) {
1008 if (event->xbutton.state & ControlMask) {
1009 toggleItemSelection(lPtr, tmp);
1010 } else {
1011 WMSelectListItem(lPtr, tmp);
1013 } else {
1014 WMRange range;
1015 WMListItem *lastSel;
1017 if (event->xbutton.state & ControlMask) {
1018 toggleItemSelection(lPtr, tmp);
1019 } else if (event->xbutton.state & ShiftMask) {
1020 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1021 WMSelectListItem(lPtr, tmp);
1022 } else {
1023 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1024 range.position = WMGetFirstInArray(lPtr->items, lastSel);
1025 if (tmp >= range.position)
1026 range.count = tmp - range.position + 1;
1027 else
1028 range.count = tmp - range.position - 1;
1030 WMSetListSelectionToRange(lPtr, range);
1032 } else {
1033 range.position = tmp;
1034 range.count = 1;
1035 WMSetListSelectionToRange(lPtr, range);
1041 if (!(event->xbutton.state & ShiftMask))
1042 lastClicked = prevItem = tmp;
1044 break;
1046 case MotionNotify:
1047 height = WMWidgetHeight(lPtr);
1048 if (lPtr->selectID && event->xmotion.y > 0 && event->xmotion.y < height) {
1049 WMDeleteTimerHandler(lPtr->selectID);
1050 lPtr->selectID = NULL;
1052 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1053 if (event->xmotion.y <= 0) {
1054 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
1055 break;
1056 } else if (event->xmotion.y >= height) {
1057 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
1058 break;
1061 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1062 if (tmp >= 0 && tmp != prevItem) {
1063 if (lPtr->flags.allowMultipleSelection) {
1064 WMRange range;
1066 range.position = lastClicked;
1067 if (tmp >= lastClicked)
1068 range.count = tmp - lastClicked + 1;
1069 else
1070 range.count = tmp - lastClicked - 1;
1071 WMSetListSelectionToRange(lPtr, range);
1072 } else {
1073 WMSelectListItem(lPtr, tmp);
1076 prevItem = tmp;
1078 break;
1080 if (lPtr->topItem != topItem)
1081 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1084 static void updateGeometry(WMList * lPtr)
1086 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1087 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1088 lPtr->flags.dontFitAll = 1;
1089 } else {
1090 lPtr->flags.dontFitAll = 0;
1093 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1094 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1095 if (lPtr->topItem < 0)
1096 lPtr->topItem = 0;
1099 updateScroller(lPtr);
1102 static void didResizeList(W_ViewDelegate * self, WMView * view)
1104 WMList *lPtr = (WMList *) view->self;
1106 /* Parameter not used, but tell the compiler that it is ok */
1107 (void) self;
1109 WMResizeWidget(lPtr->vScroller, 1, view->size.height - 2);
1111 updateDoubleBufferPixmap(lPtr);
1113 updateGeometry(lPtr);
1116 static void destroyList(List * lPtr)
1118 if (lPtr->idleID)
1119 WMDeleteIdleHandler(lPtr->idleID);
1120 lPtr->idleID = NULL;
1122 if (lPtr->selectID)
1123 WMDeleteTimerHandler(lPtr->selectID);
1124 lPtr->selectID = NULL;
1126 if (lPtr->selectedItems)
1127 WMFreeArray(lPtr->selectedItems);
1129 if (lPtr->items)
1130 WMFreeArray(lPtr->items);
1132 if (lPtr->doubleBuffer)
1133 XFreePixmap(lPtr->view->screen->display, lPtr->doubleBuffer);
1135 WMRemoveNotificationObserver(lPtr);
1137 wfree(lPtr);