wmaker: Added 'const' attribute to local function 'getMaxStringWidth'
[wmaker-crm.git] / WINGs / wlist.c
blobf15e900f8c6ce7393da2bdc120cba64cc4243c10
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 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, const 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 const WMListItem *wl_item = item;
639 const char *s_title = title;
641 return (strcmp(wl_item->text, s_title) == 0 ? 1 : 0);
644 int WMFindRowOfListItemWithTitle(WMList * lPtr, const char *title)
647 * We explicitely discard the 'const' attribute here because the
648 * call-back function handler must not be made with a const
649 * attribute, but our local call-back function (above) does have
650 * it properly set, so we're consistent
652 return WMFindInArray(lPtr->items, matchTitle, (char *) title);
655 void WMSelectListItem(WMList * lPtr, int row)
657 WMListItem *item;
659 if (row >= WMGetArrayItemCount(lPtr->items))
660 return;
662 if (row < 0) {
663 /* row = -1 will deselects all for backward compatibility.
664 * will be removed later. -Dan */
665 WMUnselectAllListItems(lPtr);
666 return;
669 item = WMGetFromArray(lPtr->items, row);
670 if (item->selected)
671 return; /* Return if already selected */
673 if (!lPtr->flags.allowMultipleSelection) {
674 /* unselect previous selected items */
675 unselectAllListItems(lPtr, NULL);
678 /* select item */
679 item->selected = 1;
680 WMAddToArray(lPtr->selectedItems, item);
682 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
683 paintItem(lPtr, row);
686 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
689 void WMUnselectListItem(WMList * lPtr, int row)
691 WMListItem *item = WMGetFromArray(lPtr->items, row);
693 if (!item || !item->selected)
694 return;
696 if (!lPtr->flags.allowEmptySelection && WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
697 return;
700 item->selected = 0;
701 WMRemoveFromArray(lPtr->selectedItems, item);
703 if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
704 paintItem(lPtr, row);
707 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
710 void WMSelectListItemsInRange(WMList * lPtr, WMRange range)
712 WMListItem *item;
713 int position = range.position, k = 1, notify = 0;
714 int total = WMGetArrayItemCount(lPtr->items);
716 if (!lPtr->flags.allowMultipleSelection)
717 return;
718 if (range.count == 0)
719 return; /* Nothing to select */
721 if (range.count < 0) {
722 range.count = -range.count;
723 k = -1;
726 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
727 item = WMGetFromArray(lPtr->items, position);
728 if (!item->selected) {
729 item->selected = 1;
730 WMAddToArray(lPtr->selectedItems, item);
731 if (lPtr->view->flags.mapped && position >= lPtr->topItem
732 && position <= lPtr->topItem + lPtr->fullFitLines) {
733 paintItem(lPtr, position);
735 notify = 1;
737 position += k;
740 if (notify) {
741 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
745 void WMSetListSelectionToRange(WMList * lPtr, WMRange range)
747 WMListItem *item;
748 int mark1, mark2, i, k;
749 int position = range.position, notify = 0;
750 int total = WMGetArrayItemCount(lPtr->items);
752 if (!lPtr->flags.allowMultipleSelection)
753 return;
755 if (range.count == 0) {
756 WMUnselectAllListItems(lPtr);
757 return;
760 if (range.count < 0) {
761 mark1 = range.position + range.count + 1;
762 mark2 = range.position + 1;
763 range.count = -range.count;
764 k = -1;
765 } else {
766 mark1 = range.position;
767 mark2 = range.position + range.count;
768 k = 1;
770 if (mark1 > total)
771 mark1 = total;
772 if (mark2 < 0)
773 mark2 = 0;
775 WMEmptyArray(lPtr->selectedItems);
777 for (i = 0; i < mark1; i++) {
778 item = WMGetFromArray(lPtr->items, i);
779 if (item->selected) {
780 item->selected = 0;
781 if (lPtr->view->flags.mapped && i >= lPtr->topItem
782 && i <= lPtr->topItem + lPtr->fullFitLines) {
783 paintItem(lPtr, i);
785 notify = 1;
788 for (; range.count > 0 && position >= 0 && position < total; range.count--) {
789 item = WMGetFromArray(lPtr->items, position);
790 if (!item->selected) {
791 item->selected = 1;
792 if (lPtr->view->flags.mapped && position >= lPtr->topItem
793 && position <= lPtr->topItem + lPtr->fullFitLines) {
794 paintItem(lPtr, position);
796 notify = 1;
798 WMAddToArray(lPtr->selectedItems, item);
799 position += k;
801 for (i = mark2; i < total; i++) {
802 item = WMGetFromArray(lPtr->items, i);
803 if (item->selected) {
804 item->selected = 0;
805 if (lPtr->view->flags.mapped && i >= lPtr->topItem
806 && i <= lPtr->topItem + lPtr->fullFitLines) {
807 paintItem(lPtr, i);
809 notify = 1;
813 if (notify) {
814 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
818 void WMSelectAllListItems(WMList * lPtr)
820 int i;
821 WMListItem *item;
823 if (!lPtr->flags.allowMultipleSelection)
824 return;
826 if (WMGetArrayItemCount(lPtr->items) == WMGetArrayItemCount(lPtr->selectedItems)) {
827 return; /* All items are selected already */
830 WMFreeArray(lPtr->selectedItems);
831 lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
833 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
834 item = WMGetFromArray(lPtr->items, i);
835 if (!item->selected) {
836 item->selected = 1;
837 if (lPtr->view->flags.mapped && i >= lPtr->topItem
838 && i <= lPtr->topItem + lPtr->fullFitLines) {
839 paintItem(lPtr, i);
844 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
848 * Be careful from where you call this function! It doesn't honor the
849 * allowEmptySelection flag and doesn't send a notification about selection
850 * change! You need to manage these in the functions from where you call it.
852 * This will unselect all items if exceptThis is NULL, else will keep
853 * exceptThis selected.
854 * Make sure that exceptThis is one of the already selected items if not NULL!
857 static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis)
859 int i;
860 WMListItem *item;
862 for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
863 item = WMGetFromArray(lPtr->items, i);
864 if (item != exceptThis && item->selected) {
865 item->selected = 0;
866 if (lPtr->view->flags.mapped && i >= lPtr->topItem
867 && i <= lPtr->topItem + lPtr->fullFitLines) {
868 paintItem(lPtr, i);
873 WMEmptyArray(lPtr->selectedItems);
874 if (exceptThis != NULL) {
875 exceptThis->selected = 1;
876 WMAddToArray(lPtr->selectedItems, exceptThis);
880 void WMUnselectAllListItems(WMList * lPtr)
882 int keep;
883 WMListItem *keepItem;
885 keep = lPtr->flags.allowEmptySelection ? 0 : 1;
887 if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
888 return;
890 keepItem = (keep == 1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
892 unselectAllListItems(lPtr, keepItem);
894 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
897 static int getItemIndexAt(List * lPtr, int clickY)
899 int index;
901 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
903 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
904 return -1;
906 return index;
909 static void toggleItemSelection(WMList * lPtr, int index)
911 WMListItem *item = WMGetFromArray(lPtr->items, index);
913 if (item && item->selected) {
914 WMUnselectListItem(lPtr, index);
915 } else {
916 WMSelectListItem(lPtr, index);
920 static void handleActionEvents(XEvent * event, void *data)
922 List *lPtr = (List *) data;
923 int tmp, height;
924 int topItem = lPtr->topItem;
925 static int lastClicked = -1, prevItem = -1;
927 CHECK_CLASS(data, WC_List);
929 switch (event->type) {
930 case ButtonRelease:
931 /* Ignore mouse wheel events, they're not "real" button events */
932 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
933 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
934 break;
937 lPtr->flags.buttonPressed = 0;
938 if (lPtr->selectID) {
939 WMDeleteTimerHandler(lPtr->selectID);
940 lPtr->selectID = NULL;
942 tmp = getItemIndexAt(lPtr, event->xbutton.y);
944 if (tmp >= 0) {
945 if (lPtr->action)
946 (*lPtr->action) (lPtr, lPtr->clientData);
949 if (!(event->xbutton.state & ShiftMask))
950 lastClicked = prevItem = tmp;
952 break;
954 case EnterNotify:
955 if (lPtr->selectID) {
956 WMDeleteTimerHandler(lPtr->selectID);
957 lPtr->selectID = NULL;
959 break;
961 case LeaveNotify:
962 height = WMWidgetHeight(lPtr);
963 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
964 if (event->xcrossing.y >= height) {
965 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
966 } else if (event->xcrossing.y <= 0) {
967 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
970 break;
972 case ButtonPress:
973 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
974 break;
975 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
976 event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
977 int amount = 0;
979 if (event->xbutton.state & ControlMask) {
980 amount = lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1;
981 } else if (event->xbutton.state & ShiftMask) {
982 amount = 1;
983 } else {
984 amount = lPtr->fullFitLines / 3;
985 if (amount == 0)
986 amount++;
988 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
989 amount = -amount;
991 scrollByAmount(lPtr, amount);
992 break;
995 tmp = getItemIndexAt(lPtr, event->xbutton.y);
996 lPtr->flags.buttonPressed = 1;
998 if (tmp >= 0) {
999 if (tmp == lastClicked && WMIsDoubleClick(event)) {
1000 WMSelectListItem(lPtr, tmp);
1001 if (lPtr->doubleAction)
1002 (*lPtr->doubleAction) (lPtr, lPtr->doubleClientData);
1003 } else {
1004 if (!lPtr->flags.allowMultipleSelection) {
1005 if (event->xbutton.state & ControlMask) {
1006 toggleItemSelection(lPtr, tmp);
1007 } else {
1008 WMSelectListItem(lPtr, tmp);
1010 } else {
1011 WMRange range;
1012 WMListItem *lastSel;
1014 if (event->xbutton.state & ControlMask) {
1015 toggleItemSelection(lPtr, tmp);
1016 } else if (event->xbutton.state & ShiftMask) {
1017 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1018 WMSelectListItem(lPtr, tmp);
1019 } else {
1020 lastSel = WMGetFromArray(lPtr->items, lastClicked);
1021 range.position = WMGetFirstInArray(lPtr->items, lastSel);
1022 if (tmp >= range.position)
1023 range.count = tmp - range.position + 1;
1024 else
1025 range.count = tmp - range.position - 1;
1027 WMSetListSelectionToRange(lPtr, range);
1029 } else {
1030 range.position = tmp;
1031 range.count = 1;
1032 WMSetListSelectionToRange(lPtr, range);
1038 if (!(event->xbutton.state & ShiftMask))
1039 lastClicked = prevItem = tmp;
1041 break;
1043 case MotionNotify:
1044 height = WMWidgetHeight(lPtr);
1045 if (lPtr->selectID && event->xmotion.y > 0 && event->xmotion.y < height) {
1046 WMDeleteTimerHandler(lPtr->selectID);
1047 lPtr->selectID = NULL;
1049 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1050 if (event->xmotion.y <= 0) {
1051 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
1052 break;
1053 } else if (event->xmotion.y >= height) {
1054 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
1055 break;
1058 tmp = getItemIndexAt(lPtr, event->xmotion.y);
1059 if (tmp >= 0 && tmp != prevItem) {
1060 if (lPtr->flags.allowMultipleSelection) {
1061 WMRange range;
1063 range.position = lastClicked;
1064 if (tmp >= lastClicked)
1065 range.count = tmp - lastClicked + 1;
1066 else
1067 range.count = tmp - lastClicked - 1;
1068 WMSetListSelectionToRange(lPtr, range);
1069 } else {
1070 WMSelectListItem(lPtr, tmp);
1073 prevItem = tmp;
1075 break;
1077 if (lPtr->topItem != topItem)
1078 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1081 static void updateGeometry(WMList * lPtr)
1083 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1084 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1085 lPtr->flags.dontFitAll = 1;
1086 } else {
1087 lPtr->flags.dontFitAll = 0;
1090 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1091 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1092 if (lPtr->topItem < 0)
1093 lPtr->topItem = 0;
1096 updateScroller(lPtr);
1099 static void didResizeList(W_ViewDelegate * self, WMView * view)
1101 WMList *lPtr = (WMList *) view->self;
1103 WMResizeWidget(lPtr->vScroller, 1, view->size.height - 2);
1105 updateDoubleBufferPixmap(lPtr);
1107 updateGeometry(lPtr);
1110 static void destroyList(List * lPtr)
1112 if (lPtr->idleID)
1113 WMDeleteIdleHandler(lPtr->idleID);
1114 lPtr->idleID = NULL;
1116 if (lPtr->selectID)
1117 WMDeleteTimerHandler(lPtr->selectID);
1118 lPtr->selectID = NULL;
1120 if (lPtr->selectedItems)
1121 WMFreeArray(lPtr->selectedItems);
1123 if (lPtr->items)
1124 WMFreeArray(lPtr->items);
1126 if (lPtr->doubleBuffer)
1127 XFreePixmap(lPtr->view->screen->display, lPtr->doubleBuffer);
1129 WMRemoveNotificationObserver(lPtr);
1131 wfree(lPtr);